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

286 lines
10 KiB
Python

"""
***************************************************************************
Gridify.py
---------------------
Date : May 2010
Copyright : (C) 2010 by Michael Minn
Email : pyqgis at michaelminn 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__ = "Michael Minn"
__date__ = "May 2010"
__copyright__ = "(C) 2010, Michael Minn"
from qgis.core import (
QgsFeature,
QgsGeometry,
QgsMultiPoint,
QgsMultiLineString,
QgsLineString,
QgsPolygon,
QgsFeatureSink,
QgsWkbTypes,
QgsProcessingException,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterEnum,
QgsProcessingParameterFeatureSink,
)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
class GeometryConvert(QgisAlgorithm):
INPUT = "INPUT"
TYPE = "TYPE"
OUTPUT = "OUTPUT"
def group(self):
return self.tr("Vector geometry")
def groupId(self):
return "vectorgeometry"
def __init__(self):
super().__init__()
def initAlgorithm(self, config=None):
self.types = [
self.tr("Centroids"),
self.tr("Nodes"),
self.tr("Linestrings"),
self.tr("Multilinestrings"),
self.tr("Polygons"),
]
self.addParameter(
QgsProcessingParameterFeatureSource(self.INPUT, self.tr("Input layer"))
)
self.addParameter(
QgsProcessingParameterEnum(
self.TYPE, self.tr("New geometry type"), options=self.types
)
)
self.addParameter(
QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr("Converted"))
)
def name(self):
return "convertgeometrytype"
def displayName(self):
return self.tr("Convert geometry type")
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
if source is None:
raise QgsProcessingException(
self.invalidSourceError(parameters, self.INPUT)
)
index = self.parameterAsEnum(parameters, self.TYPE, context)
if index == 0:
newType = QgsWkbTypes.Type.Point
elif index == 1:
newType = QgsWkbTypes.Type.Point
if QgsWkbTypes.hasM(source.wkbType()):
newType = QgsWkbTypes.addM(newType)
if QgsWkbTypes.hasZ(source.wkbType()):
newType = QgsWkbTypes.addZ(newType)
elif index == 2:
newType = QgsWkbTypes.Type.LineString
if QgsWkbTypes.hasM(source.wkbType()):
newType = QgsWkbTypes.addM(newType)
if QgsWkbTypes.hasZ(source.wkbType()):
newType = QgsWkbTypes.addZ(newType)
elif index == 3:
newType = QgsWkbTypes.Type.MultiLineString
if QgsWkbTypes.hasM(source.wkbType()):
newType = QgsWkbTypes.addM(newType)
if QgsWkbTypes.hasZ(source.wkbType()):
newType = QgsWkbTypes.addZ(newType)
else:
newType = QgsWkbTypes.Type.Polygon
if QgsWkbTypes.hasM(source.wkbType()):
newType = QgsWkbTypes.addM(newType)
if QgsWkbTypes.hasZ(source.wkbType()):
newType = QgsWkbTypes.addZ(newType)
(sink, dest_id) = self.parameterAsSink(
parameters,
self.OUTPUT,
context,
source.fields(),
newType,
source.sourceCrs(),
)
if sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, f in enumerate(features):
if feedback.isCanceled():
break
if not f.hasGeometry():
sink.addFeature(f, QgsFeatureSink.Flag.FastInsert)
else:
for p in self.convertGeometry(f.geometry(), index):
feat = QgsFeature()
feat.setAttributes(f.attributes())
feat.setGeometry(p)
sink.addFeature(feat, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))
sink.finalize()
return {self.OUTPUT: dest_id}
def convertGeometry(self, geom, target_type):
# returns an array of output geometries for the input geometry
if target_type == 0:
# centroid
return self.convertToCentroid(geom)
elif target_type == 1:
# nodes
return self.convertToNodes(geom)
elif target_type == 2:
# linestrings
return self.convertToLineStrings(geom)
elif target_type == 3:
# multilinestrings
return self.convertToMultiLineStrings(geom)
elif target_type == 4:
# polygon
return self.convertToPolygon(geom)
def convertToCentroid(self, geom):
return [geom.centroid()]
def convertToNodes(self, geom):
mp = QgsMultiPoint()
# TODO: mega inefficient - needs rework when geometry iterators land
# (but at least it doesn't lose Z/M values)
for g in geom.constGet().coordinateSequence():
for r in g:
for p in r:
mp.addGeometry(p)
return [QgsGeometry(mp)]
def convertToLineStrings(self, geom):
if (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.PointGeometry
):
raise QgsProcessingException(
self.tr("Cannot convert from {0} to LineStrings").format(
QgsWkbTypes.displayString(geom.wkbType())
)
)
elif (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.LineGeometry
):
if QgsWkbTypes.isMultiType(geom.wkbType()):
return geom.asGeometryCollection()
else:
# line to line
return [geom]
else:
# polygons to lines
# we just use the boundary here - that consists of all rings in the (multi)polygon
boundary = QgsGeometry(geom.constGet().boundary())
# boundary will be multipart
return boundary.asGeometryCollection()
def convertToMultiLineStrings(self, geom):
if (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.PointGeometry
):
raise QgsProcessingException(
self.tr("Cannot convert from {0} to MultiLineStrings").format(
QgsWkbTypes.displayString(geom.wkbType())
)
)
elif (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.LineGeometry
):
if QgsWkbTypes.isMultiType(geom.wkbType()):
return [geom]
else:
# line to multiLine
ml = QgsMultiLineString()
ml.addGeometry(geom.constGet().clone())
return [QgsGeometry(ml)]
else:
# polygons to multilinestring
# we just use the boundary here - that consists of all rings in the (multi)polygon
return [QgsGeometry(geom.constGet().boundary())]
def convertToPolygon(self, geom):
if (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.PointGeometry
and geom.constGet().nCoordinates() < 3
):
raise QgsProcessingException(
self.tr("Cannot convert from Point to Polygon").format(
QgsWkbTypes.displayString(geom.wkbType())
)
)
elif (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.PointGeometry
):
# multipoint with at least 3 points
# TODO: mega inefficient - needs rework when geometry iterators land
# (but at least it doesn't lose Z/M values)
points = []
for g in geom.constGet().coordinateSequence():
for r in g:
for p in r:
points.append(p)
linestring = QgsLineString(points)
linestring.close()
p = QgsPolygon()
p.setExteriorRing(linestring)
return [QgsGeometry(p)]
elif (
QgsWkbTypes.geometryType(geom.wkbType())
== QgsWkbTypes.GeometryType.LineGeometry
):
if QgsWkbTypes.isMultiType(geom.wkbType()):
parts = []
for i in range(geom.constGet().numGeometries()):
p = QgsPolygon()
linestring = geom.constGet().geometryN(i).clone()
linestring.close()
p.setExteriorRing(linestring)
parts.append(QgsGeometry(p))
return QgsGeometry.collectGeometry(parts)
else:
# linestring to polygon
p = QgsPolygon()
linestring = geom.constGet().clone()
linestring.close()
p.setExteriorRing(linestring)
return [QgsGeometry(p)]
else:
# polygon
if QgsWkbTypes.isMultiType(geom.wkbType()):
return geom.asGeometryCollection()
else:
return [geom]