ExportGeometryInfo.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. """
  2. ***************************************************************************
  3. ExportGeometryInfo.py
  4. ---------------------
  5. Date : August 2012
  6. Copyright : (C) 2012 by Victor Olaya
  7. Email : volayaf at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Victor Olaya'
  18. __date__ = 'August 2012'
  19. __copyright__ = '(C) 2012, Victor Olaya'
  20. import os
  21. import math
  22. from qgis.PyQt.QtGui import QIcon
  23. from qgis.PyQt.QtCore import QVariant
  24. from qgis.core import (NULL,
  25. Qgis,
  26. QgsApplication,
  27. QgsCoordinateTransform,
  28. QgsField,
  29. QgsFields,
  30. QgsWkbTypes,
  31. QgsPointXY,
  32. QgsFeatureSink,
  33. QgsDistanceArea,
  34. QgsProcessingUtils,
  35. QgsProcessingException,
  36. QgsProcessingParameterFeatureSource,
  37. QgsProcessingParameterEnum,
  38. QgsProcessingParameterFeatureSink,
  39. QgsUnitTypes)
  40. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  41. pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
  42. class ExportGeometryInfo(QgisAlgorithm):
  43. INPUT = 'INPUT'
  44. METHOD = 'CALC_METHOD'
  45. OUTPUT = 'OUTPUT'
  46. def icon(self):
  47. return QgsApplication.getThemeIcon("/algorithms/mAlgorithmAddGeometryAttributes.svg")
  48. def svgIconPath(self):
  49. return QgsApplication.iconPath("/algorithms/mAlgorithmAddGeometryAttributes.svg")
  50. def tags(self):
  51. return self.tr('export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons,sinuosity,fields').split(',')
  52. def group(self):
  53. return self.tr('Vector geometry')
  54. def groupId(self):
  55. return 'vectorgeometry'
  56. def __init__(self):
  57. super().__init__()
  58. self.export_z = False
  59. self.export_m = False
  60. self.distance_area = None
  61. self.distance_conversion_factor = 1
  62. self.area_conversion_factor = 1
  63. self.calc_methods = [self.tr('Layer CRS'),
  64. self.tr('Project CRS'),
  65. self.tr('Ellipsoidal')]
  66. def initAlgorithm(self, config=None):
  67. self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
  68. self.tr('Input layer')))
  69. self.addParameter(QgsProcessingParameterEnum(self.METHOD,
  70. self.tr('Calculate using'), options=self.calc_methods, defaultValue=0))
  71. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Added geom info')))
  72. def name(self):
  73. return 'exportaddgeometrycolumns'
  74. def displayName(self):
  75. return self.tr('Add geometry attributes')
  76. def processAlgorithm(self, parameters, context, feedback):
  77. source = self.parameterAsSource(parameters, self.INPUT, context)
  78. if source is None:
  79. raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
  80. method = self.parameterAsEnum(parameters, self.METHOD, context)
  81. wkb_type = source.wkbType()
  82. fields = source.fields()
  83. new_fields = QgsFields()
  84. if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry:
  85. new_fields.append(QgsField('area', QVariant.Double))
  86. new_fields.append(QgsField('perimeter', QVariant.Double))
  87. elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry:
  88. new_fields.append(QgsField('length', QVariant.Double))
  89. if not QgsWkbTypes.isMultiType(source.wkbType()):
  90. new_fields.append(QgsField('straightdis', QVariant.Double))
  91. new_fields.append(QgsField('sinuosity', QVariant.Double))
  92. else:
  93. if QgsWkbTypes.isMultiType(source.wkbType()):
  94. new_fields.append(QgsField('numparts', QVariant.Int))
  95. else:
  96. new_fields.append(QgsField('xcoord', QVariant.Double))
  97. new_fields.append(QgsField('ycoord', QVariant.Double))
  98. if QgsWkbTypes.hasZ(source.wkbType()):
  99. self.export_z = True
  100. new_fields.append(QgsField('zcoord', QVariant.Double))
  101. if QgsWkbTypes.hasM(source.wkbType()):
  102. self.export_m = True
  103. new_fields.append(QgsField('mvalue', QVariant.Double))
  104. fields = QgsProcessingUtils.combineFields(fields, new_fields)
  105. (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
  106. fields, wkb_type, source.sourceCrs())
  107. if sink is None:
  108. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  109. coordTransform = None
  110. # Calculate with:
  111. # 0 - layer CRS
  112. # 1 - project CRS
  113. # 2 - ellipsoidal
  114. self.distance_area = QgsDistanceArea()
  115. if method == 2:
  116. self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext())
  117. self.distance_area.setEllipsoid(context.ellipsoid())
  118. self.distance_conversion_factor = QgsUnitTypes.fromUnitToUnitFactor(self.distance_area.lengthUnits(),
  119. context.distanceUnit())
  120. self.area_conversion_factor = QgsUnitTypes.fromUnitToUnitFactor(self.distance_area.areaUnits(),
  121. context.areaUnit())
  122. elif method == 1:
  123. if not context.project():
  124. raise QgsProcessingException(self.tr('No project is available in this context'))
  125. coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project())
  126. features = source.getFeatures()
  127. total = 100.0 / source.featureCount() if source.featureCount() else 0
  128. for current, f in enumerate(features):
  129. if feedback.isCanceled():
  130. break
  131. outFeat = f
  132. attrs = f.attributes()
  133. inGeom = f.geometry()
  134. if inGeom:
  135. if coordTransform is not None:
  136. inGeom.transform(coordTransform)
  137. if inGeom.type() == QgsWkbTypes.PointGeometry:
  138. attrs.extend(self.point_attributes(inGeom))
  139. elif inGeom.type() == QgsWkbTypes.PolygonGeometry:
  140. attrs.extend(self.polygon_attributes(inGeom))
  141. else:
  142. attrs.extend(self.line_attributes(inGeom))
  143. # ensure consistent count of attributes - otherwise null
  144. # geometry features will have incorrect attribute length
  145. # and provider may reject them
  146. if len(attrs) < len(fields):
  147. attrs += [NULL] * (len(fields) - len(attrs))
  148. outFeat.setAttributes(attrs)
  149. sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
  150. feedback.setProgress(int(current * total))
  151. return {self.OUTPUT: dest_id}
  152. def point_attributes(self, geometry):
  153. attrs = []
  154. if not geometry.isMultipart():
  155. pt = geometry.constGet()
  156. attrs.append(pt.x())
  157. attrs.append(pt.y())
  158. # add point z/m
  159. if self.export_z:
  160. attrs.append(pt.z())
  161. if self.export_m:
  162. attrs.append(pt.m())
  163. else:
  164. attrs = [geometry.constGet().numGeometries()]
  165. return attrs
  166. def line_attributes(self, geometry):
  167. if geometry.isMultipart():
  168. return [self.distance_area.measureLength(geometry)]
  169. else:
  170. curve = geometry.constGet()
  171. p1 = curve.startPoint()
  172. p2 = curve.endPoint()
  173. straight_distance = self.distance_conversion_factor * self.distance_area.measureLine(QgsPointXY(p1), QgsPointXY(p2))
  174. sinuosity = curve.sinuosity()
  175. if math.isnan(sinuosity):
  176. sinuosity = NULL
  177. return [self.distance_conversion_factor * self.distance_area.measureLength(geometry), straight_distance, sinuosity]
  178. def polygon_attributes(self, geometry):
  179. area = self.area_conversion_factor * self.distance_area.measureArea(geometry)
  180. perimeter = self.distance_conversion_factor * self.distance_area.measurePerimeter(geometry)
  181. return [area, perimeter]