QGIS/python/plugins/processing/algs/qgis/ExportGeometryInfo.py
2024-11-29 15:38:02 +01:00

267 lines
9.2 KiB
Python

"""
***************************************************************************
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]