From b46373b08462fd9d506dbfd8de0d07ff3628a50d Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Wed, 12 Jul 2017 20:54:51 +0700 Subject: [PATCH] [processing] restore union algorithm --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- python/plugins/processing/algs/qgis/Union.py | 148 +++++++++--------- 2 files changed, 78 insertions(+), 75 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 47a5bc98f4a4..4e2ebd4b1dde 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -75,6 +75,7 @@ from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SymmetricalDifference import SymmetricalDifference +from .Union import Union from .VectorSplit import VectorSplit from .VoronoiPolygons import VoronoiPolygons from .ZonalStatistics import ZonalStatistics @@ -101,7 +102,6 @@ # from .RandomSelection import RandomSelection # from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets # from .SelectByLocation import SelectByLocation -# from .Union import Union # from .SpatialJoin import SpatialJoin # from .DeleteDuplicateGeometries import DeleteDuplicateGeometries # from .TextToFloat import TextToFloat @@ -194,7 +194,7 @@ def getAlgs(self): # PolygonsToLines(), LinesToPolygons(), ExtractNodes(), # ConvexHull(), FixedDistanceBuffer(), # VariableDistanceBuffer(), - # Intersection(), Union(), + # Intersection(), # RandomSelection(), RandomSelectionWithinSubsets(), # SelectByLocation(), # ExtractByLocation(), @@ -274,6 +274,7 @@ def getAlgs(self): SnapGeometriesToLayer(), SpatialiteExecuteSQL(), SymmetricalDifference(), + Union(), VectorSplit(), VoronoiPolygons(), ZonalStatistics() diff --git a/python/plugins/processing/algs/qgis/Union.py b/python/plugins/processing/algs/qgis/Union.py index 632afb3845be..0030d6715e46 100644 --- a/python/plugins/processing/algs/qgis/Union.py +++ b/python/plugins/processing/algs/qgis/Union.py @@ -35,11 +35,11 @@ QgsGeometry, QgsWkbTypes, QgsMessageLog, - QgsProcessingUtils) + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsSpatialIndex) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector from processing.tools import vector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -57,7 +57,7 @@ class Union(QgisAlgorithm): INPUT = 'INPUT' - INPUT2 = 'INPUT2' + OVERLAY = 'OVERLAY' OUTPUT = 'OUTPUT' def icon(self): @@ -70,11 +70,12 @@ def __init__(self): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(Union.INPUT, - self.tr('Input layer'))) - self.addParameter(ParameterVector(Union.INPUT2, - self.tr('Input layer 2'))) - self.addOutput(OutputVector(Union.OUTPUT, self.tr('Union'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.OVERLAY, + self.tr('Union layer'))) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Union'))) def name(self): return 'union' @@ -83,50 +84,52 @@ def displayName(self): return self.tr('Union') def processAlgorithm(self, parameters, context, feedback): - vlayerA = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(Union.INPUT), context) - vlayerB = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(Union.INPUT2), context) - - geomType = vlayerA.wkbType() - fields = vector.combineFields(vlayerA.fields(), vlayerB.fields()) - writer = self.getOutputFromName(Union.OUTPUT).getVectorWriter(fields, geomType, vlayerA.crs(), context) - inFeatA = QgsFeature() - inFeatB = QgsFeature() + sourceA = self.parameterAsSource(parameters, self.INPUT, context) + sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) + + geomType = QgsWkbTypes.multiType(sourceA.wkbType()) + fields = vector.combineFields(sourceA.fields(), sourceB.fields()) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, geomType, sourceA.sourceCrs()) + + featA = QgsFeature() + featB = QgsFeature() outFeat = QgsFeature() - indexA = QgsProcessingUtils.createSpatialIndex(vlayerB, context) - indexB = QgsProcessingUtils.createSpatialIndex(vlayerA, context) + indexA = QgsSpatialIndex(sourceA) + indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs()))) + + total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 - nElement = 0 - featuresA = QgsProcessingUtils.getFeatures(vlayerA, context) - nFeat = QgsProcessingUtils.featureCount(vlayerA, context) - for inFeatA in featuresA: - feedback.setProgress(nElement / float(nFeat) * 50) - nElement += 1 + + for featA in sourceA.getFeatures(): + if feedback.isCanceled(): + break + lstIntersectingB = [] - geom = inFeatA.geometry() - atMapA = inFeatA.attributes() - intersects = indexA.intersects(geom.boundingBox()) + geom = featA.geometry() + atMapA = featA.attributes() + intersects = indexB.intersects(geom.boundingBox()) if len(intersects) < 1: try: outFeat.setGeometry(geom) outFeat.setAttributes(atMapA) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: # This really shouldn't happen, as we haven't # edited the input geom at all - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: - request = QgsFeatureRequest().setFilterFids(intersects) + request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) + request.setDestinationCrs(sourceA.sourceCrs()) engine = QgsGeometry.createGeometryEngine(geom.geometry()) engine.prepareGeometry() - for inFeatB in vlayerB.getFeatures(request): - count += 1 - - atMapB = inFeatB.attributes() - tmpGeom = inFeatB.geometry() + for featB in sourceB.getFeatures(request): + atMapB = featB.attributes() + tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): int_geom = geom.intersection(tmpGeom) @@ -134,8 +137,7 @@ def processAlgorithm(self, parameters, context, feedback): if not int_geom: # There was a problem creating the intersection - QgsMessageLog.logMessage(self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) int_geom = QgsGeometry() else: int_geom = QgsGeometry(int_geom) @@ -149,10 +151,9 @@ def processAlgorithm(self, parameters, context, feedback): try: outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: # Geometry list: prevents writing error # in geometries of different types @@ -162,19 +163,18 @@ def processAlgorithm(self, parameters, context, feedback): try: outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) - # the remaining bit of inFeatA's geometry + # the remaining bit of featA's geometry # if there is nothing left, this will just silently fail and we're good diff_geom = QgsGeometry(geom) if len(lstIntersectingB) != 0: intB = QgsGeometry.unaryUnion(lstIntersectingB) diff_geom = diff_geom.difference(intB) - if diff_geom.wkbType() == 0 or QgsWkbTypes.flatType(diff_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection: + if diff_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(diff_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection: temp_list = diff_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): @@ -182,43 +182,45 @@ def processAlgorithm(self, parameters, context, feedback): try: outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMapA) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) - length = len(vlayerA.fields()) + count += 1 + feedback.setProgress(int(count * total)) + + length = len(sourceA.fields()) atMapA = [None] * length - featuresA = QgsProcessingUtils.getFeatures(vlayerB, context) - nFeat = QgsProcessingUtils.featureCount(vlayerB, context) - for inFeatA in featuresA: - feedback.setProgress(nElement / float(nFeat) * 100) + for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())): + if feedback.isCanceled(): + break + add = False - geom = inFeatA.geometry() + geom = featA.geometry() diff_geom = QgsGeometry(geom) atMap = [None] * length - atMap.extend(inFeatA.attributes()) - intersects = indexB.intersects(geom.boundingBox()) + atMap.extend(featA.attributes()) + intersects = indexA.intersects(geom.boundingBox()) if len(intersects) < 1: try: outFeat.setGeometry(geom) outFeat.setAttributes(atMap) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: - request = QgsFeatureRequest().setFilterFids(intersects) + request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) + request.setDestinationCrs(sourceA.sourceCrs()) # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(diff_geom.geometry()) engine.prepareGeometry() - for inFeatB in vlayerA.getFeatures(request): - atMapB = inFeatB.attributes() - tmpGeom = inFeatB.geometry() + for featB in sourceA.getFeatures(request): + atMapB = featB.attributes() + tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): add = True @@ -229,19 +231,19 @@ def processAlgorithm(self, parameters, context, feedback): # intersects, but the geometry doesn't outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) if add: try: outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: - QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), - self.tr('Processing'), QgsMessageLog.INFO) - nElement += 1 + feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) + + count += 1 + feedback.setProgress(int(count * total)) - del writer + return {self.OUTPUT: dest_id}