""" *************************************************************************** ExportGeometryInfo.py --------------------- Date : August 2012 Copyright : (C) 2012 by Victor Olaya Email : volayaf at gmail dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** """ __author__ = "Victor Olaya" __date__ = "August 2012" __copyright__ = "(C) 2012, Victor Olaya" import os import math from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtCore import QMetaType from qgis.core import ( NULL, Qgis, QgsApplication, QgsCoordinateTransform, QgsField, QgsFields, QgsWkbTypes, QgsPointXY, QgsFeatureSink, QgsDistanceArea, QgsProcessingUtils, QgsProcessingException, QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, QgsProcessingParameterFeatureSink, QgsUnitTypes, ) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class ExportGeometryInfo(QgisAlgorithm): INPUT = "INPUT" METHOD = "CALC_METHOD" OUTPUT = "OUTPUT" def icon(self): return QgsApplication.getThemeIcon( "/algorithms/mAlgorithmAddGeometryAttributes.svg" ) def svgIconPath(self): return QgsApplication.iconPath( "/algorithms/mAlgorithmAddGeometryAttributes.svg" ) def tags(self): return self.tr( "export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons,sinuosity,fields" ).split(",") def group(self): return self.tr("Vector geometry") def groupId(self): return "vectorgeometry" def __init__(self): super().__init__() self.export_z = False self.export_m = False self.distance_area = None self.distance_conversion_factor = 1 self.area_conversion_factor = 1 self.calc_methods = [ self.tr("Layer CRS"), self.tr("Project CRS"), self.tr("Ellipsoidal"), ] def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterFeatureSource(self.INPUT, self.tr("Input layer")) ) self.addParameter( QgsProcessingParameterEnum( self.METHOD, self.tr("Calculate using"), options=self.calc_methods, defaultValue=0, ) ) self.addParameter( QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr("Added geom info")) ) def name(self): return "exportaddgeometrycolumns" def displayName(self): return self.tr("Add geometry attributes") def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT) ) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if ( QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.GeometryType.PolygonGeometry ): new_fields.append(QgsField("area", QMetaType.Type.Double)) new_fields.append(QgsField("perimeter", QMetaType.Type.Double)) elif ( QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.GeometryType.LineGeometry ): new_fields.append(QgsField("length", QMetaType.Type.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField("straightdis", QMetaType.Type.Double)) new_fields.append(QgsField("sinuosity", QMetaType.Type.Double)) else: if QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField("numparts", QMetaType.Type.Int)) else: new_fields.append(QgsField("xcoord", QMetaType.Type.Double)) new_fields.append(QgsField("ycoord", QMetaType.Type.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField("zcoord", QMetaType.Type.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField("mvalue", QMetaType.Type.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink( parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs() ) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs( source.sourceCrs(), context.transformContext() ) self.distance_area.setEllipsoid(context.ellipsoid()) self.distance_conversion_factor = QgsUnitTypes.fromUnitToUnitFactor( self.distance_area.lengthUnits(), context.distanceUnit() ) self.area_conversion_factor = QgsUnitTypes.fromUnitToUnitFactor( self.distance_area.areaUnits(), context.areaUnit() ) elif method == 1: if not context.project(): raise QgsProcessingException( self.tr("No project is available in this context") ) coordTransform = QgsCoordinateTransform( source.sourceCrs(), context.project().crs(), context.project() ) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.GeometryType.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.GeometryType.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.Flag.FastInsert) feedback.setProgress(int(current * total)) sink.finalize() return {self.OUTPUT: dest_id} def point_attributes(self, geometry): attrs = [] if not geometry.isMultipart(): pt = geometry.constGet() attrs.append(pt.x()) attrs.append(pt.y()) # add point z/m if self.export_z: attrs.append(pt.z()) if self.export_m: attrs.append(pt.m()) else: attrs = [geometry.constGet().numGeometries()] return attrs def line_attributes(self, geometry): if geometry.isMultipart(): return [self.distance_area.measureLength(geometry)] else: curve = geometry.constGet() p1 = curve.startPoint() p2 = curve.endPoint() straight_distance = ( self.distance_conversion_factor * self.distance_area.measureLine(QgsPointXY(p1), QgsPointXY(p2)) ) sinuosity = curve.sinuosity() if math.isnan(sinuosity): sinuosity = NULL return [ self.distance_conversion_factor * self.distance_area.measureLength(geometry), straight_distance, sinuosity, ] def polygon_attributes(self, geometry): area = self.area_conversion_factor * self.distance_area.measureArea(geometry) perimeter = ( self.distance_conversion_factor * self.distance_area.measurePerimeter(geometry) ) return [area, perimeter]