[processing] restore union algorithm

This commit is contained in:
Mathieu Pellerin 2017-07-12 20:54:51 +07:00 committed by GitHub
parent 846abe7104
commit b46373b084
2 changed files with 77 additions and 74 deletions

View File

@ -75,6 +75,7 @@ from .Smooth import Smooth
from .SnapGeometries import SnapGeometriesToLayer from .SnapGeometries import SnapGeometriesToLayer
from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SpatialiteExecuteSQL import SpatialiteExecuteSQL
from .SymmetricalDifference import SymmetricalDifference from .SymmetricalDifference import SymmetricalDifference
from .Union import Union
from .VectorSplit import VectorSplit from .VectorSplit import VectorSplit
from .VoronoiPolygons import VoronoiPolygons from .VoronoiPolygons import VoronoiPolygons
from .ZonalStatistics import ZonalStatistics from .ZonalStatistics import ZonalStatistics
@ -101,7 +102,6 @@ from .ZonalStatistics import ZonalStatistics
# from .RandomSelection import RandomSelection # from .RandomSelection import RandomSelection
# from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets # from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets
# from .SelectByLocation import SelectByLocation # from .SelectByLocation import SelectByLocation
# from .Union import Union
# from .SpatialJoin import SpatialJoin # from .SpatialJoin import SpatialJoin
# from .DeleteDuplicateGeometries import DeleteDuplicateGeometries # from .DeleteDuplicateGeometries import DeleteDuplicateGeometries
# from .TextToFloat import TextToFloat # from .TextToFloat import TextToFloat
@ -194,7 +194,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
# PolygonsToLines(), LinesToPolygons(), ExtractNodes(), # PolygonsToLines(), LinesToPolygons(), ExtractNodes(),
# ConvexHull(), FixedDistanceBuffer(), # ConvexHull(), FixedDistanceBuffer(),
# VariableDistanceBuffer(), # VariableDistanceBuffer(),
# Intersection(), Union(), # Intersection(),
# RandomSelection(), RandomSelectionWithinSubsets(), # RandomSelection(), RandomSelectionWithinSubsets(),
# SelectByLocation(), # SelectByLocation(),
# ExtractByLocation(), # ExtractByLocation(),
@ -274,6 +274,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
SnapGeometriesToLayer(), SnapGeometriesToLayer(),
SpatialiteExecuteSQL(), SpatialiteExecuteSQL(),
SymmetricalDifference(), SymmetricalDifference(),
Union(),
VectorSplit(), VectorSplit(),
VoronoiPolygons(), VoronoiPolygons(),
ZonalStatistics() ZonalStatistics()

View File

@ -35,11 +35,11 @@ from qgis.core import (QgsFeatureRequest,
QgsGeometry, QgsGeometry,
QgsWkbTypes, QgsWkbTypes,
QgsMessageLog, QgsMessageLog,
QgsProcessingUtils) QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsSpatialIndex)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import ParameterVector
from processing.core.outputs import OutputVector
from processing.tools import vector from processing.tools import vector
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
@ -57,7 +57,7 @@ for key, value in list(wkbTypeGroups.items()):
class Union(QgisAlgorithm): class Union(QgisAlgorithm):
INPUT = 'INPUT' INPUT = 'INPUT'
INPUT2 = 'INPUT2' OVERLAY = 'OVERLAY'
OUTPUT = 'OUTPUT' OUTPUT = 'OUTPUT'
def icon(self): def icon(self):
@ -70,11 +70,12 @@ class Union(QgisAlgorithm):
super().__init__() super().__init__()
def initAlgorithm(self, config=None): def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(Union.INPUT, self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'))) self.tr('Input layer')))
self.addParameter(ParameterVector(Union.INPUT2, self.addParameter(QgsProcessingParameterFeatureSource(self.OVERLAY,
self.tr('Input layer 2'))) self.tr('Union layer')))
self.addOutput(OutputVector(Union.OUTPUT, self.tr('Union')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Union')))
def name(self): def name(self):
return 'union' return 'union'
@ -83,50 +84,52 @@ class Union(QgisAlgorithm):
return self.tr('Union') return self.tr('Union')
def processAlgorithm(self, parameters, context, feedback): def processAlgorithm(self, parameters, context, feedback):
vlayerA = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(Union.INPUT), context) sourceA = self.parameterAsSource(parameters, self.INPUT, context)
vlayerB = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(Union.INPUT2), context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)
geomType = vlayerA.wkbType() geomType = QgsWkbTypes.multiType(sourceA.wkbType())
fields = vector.combineFields(vlayerA.fields(), vlayerB.fields()) fields = vector.combineFields(sourceA.fields(), sourceB.fields())
writer = self.getOutputFromName(Union.OUTPUT).getVectorWriter(fields, geomType, vlayerA.crs(), context)
inFeatA = QgsFeature() (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
inFeatB = QgsFeature() fields, geomType, sourceA.sourceCrs())
featA = QgsFeature()
featB = QgsFeature()
outFeat = 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 count = 0
nElement = 0
featuresA = QgsProcessingUtils.getFeatures(vlayerA, context) for featA in sourceA.getFeatures():
nFeat = QgsProcessingUtils.featureCount(vlayerA, context) if feedback.isCanceled():
for inFeatA in featuresA: break
feedback.setProgress(nElement / float(nFeat) * 50)
nElement += 1
lstIntersectingB = [] lstIntersectingB = []
geom = inFeatA.geometry() geom = featA.geometry()
atMapA = inFeatA.attributes() atMapA = featA.attributes()
intersects = indexA.intersects(geom.boundingBox()) intersects = indexB.intersects(geom.boundingBox())
if len(intersects) < 1: if len(intersects) < 1:
try: try:
outFeat.setGeometry(geom) outFeat.setGeometry(geom)
outFeat.setAttributes(atMapA) outFeat.setAttributes(atMapA)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
# This really shouldn't happen, as we haven't # This really shouldn't happen, as we haven't
# edited the input geom at all # edited the input geom at all
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
else: else:
request = QgsFeatureRequest().setFilterFids(intersects) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
request.setDestinationCrs(sourceA.sourceCrs())
engine = QgsGeometry.createGeometryEngine(geom.geometry()) engine = QgsGeometry.createGeometryEngine(geom.geometry())
engine.prepareGeometry() engine.prepareGeometry()
for inFeatB in vlayerB.getFeatures(request): for featB in sourceB.getFeatures(request):
count += 1 atMapB = featB.attributes()
tmpGeom = featB.geometry()
atMapB = inFeatB.attributes()
tmpGeom = inFeatB.geometry()
if engine.intersects(tmpGeom.geometry()): if engine.intersects(tmpGeom.geometry()):
int_geom = geom.intersection(tmpGeom) int_geom = geom.intersection(tmpGeom)
@ -134,8 +137,7 @@ class Union(QgisAlgorithm):
if not int_geom: if not int_geom:
# There was a problem creating the intersection # There was a problem creating the intersection
QgsMessageLog.logMessage(self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
int_geom = QgsGeometry() int_geom = QgsGeometry()
else: else:
int_geom = QgsGeometry(int_geom) int_geom = QgsGeometry(int_geom)
@ -149,10 +151,9 @@ class Union(QgisAlgorithm):
try: try:
outFeat.setGeometry(int_geom) outFeat.setGeometry(int_geom)
outFeat.setAttributes(atMapA + atMapB) outFeat.setAttributes(atMapA + atMapB)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
else: else:
# Geometry list: prevents writing error # Geometry list: prevents writing error
# in geometries of different types # in geometries of different types
@ -162,19 +163,18 @@ class Union(QgisAlgorithm):
try: try:
outFeat.setGeometry(int_geom) outFeat.setGeometry(int_geom)
outFeat.setAttributes(atMapA + atMapB) outFeat.setAttributes(atMapA + atMapB)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
# 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 # if there is nothing left, this will just silently fail and we're good
diff_geom = QgsGeometry(geom) diff_geom = QgsGeometry(geom)
if len(lstIntersectingB) != 0: if len(lstIntersectingB) != 0:
intB = QgsGeometry.unaryUnion(lstIntersectingB) intB = QgsGeometry.unaryUnion(lstIntersectingB)
diff_geom = diff_geom.difference(intB) 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() temp_list = diff_geom.asGeometryCollection()
for i in temp_list: for i in temp_list:
if i.type() == geom.type(): if i.type() == geom.type():
@ -182,43 +182,45 @@ class Union(QgisAlgorithm):
try: try:
outFeat.setGeometry(diff_geom) outFeat.setGeometry(diff_geom)
outFeat.setAttributes(atMapA) outFeat.setAttributes(atMapA)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
length = len(vlayerA.fields()) count += 1
feedback.setProgress(int(count * total))
length = len(sourceA.fields())
atMapA = [None] * length atMapA = [None] * length
featuresA = QgsProcessingUtils.getFeatures(vlayerB, context) for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())):
nFeat = QgsProcessingUtils.featureCount(vlayerB, context) if feedback.isCanceled():
for inFeatA in featuresA: break
feedback.setProgress(nElement / float(nFeat) * 100)
add = False add = False
geom = inFeatA.geometry() geom = featA.geometry()
diff_geom = QgsGeometry(geom) diff_geom = QgsGeometry(geom)
atMap = [None] * length atMap = [None] * length
atMap.extend(inFeatA.attributes()) atMap.extend(featA.attributes())
intersects = indexB.intersects(geom.boundingBox()) intersects = indexA.intersects(geom.boundingBox())
if len(intersects) < 1: if len(intersects) < 1:
try: try:
outFeat.setGeometry(geom) outFeat.setGeometry(geom)
outFeat.setAttributes(atMap) outFeat.setAttributes(atMap)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
else: else:
request = QgsFeatureRequest().setFilterFids(intersects) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
request.setDestinationCrs(sourceA.sourceCrs())
# use prepared geometries for faster intersection tests # use prepared geometries for faster intersection tests
engine = QgsGeometry.createGeometryEngine(diff_geom.geometry()) engine = QgsGeometry.createGeometryEngine(diff_geom.geometry())
engine.prepareGeometry() engine.prepareGeometry()
for inFeatB in vlayerA.getFeatures(request): for featB in sourceA.getFeatures(request):
atMapB = inFeatB.attributes() atMapB = featB.attributes()
tmpGeom = inFeatB.geometry() tmpGeom = featB.geometry()
if engine.intersects(tmpGeom.geometry()): if engine.intersects(tmpGeom.geometry()):
add = True add = True
@ -229,19 +231,19 @@ class Union(QgisAlgorithm):
# intersects, but the geometry doesn't # intersects, but the geometry doesn't
outFeat.setGeometry(diff_geom) outFeat.setGeometry(diff_geom)
outFeat.setAttributes(atMap) outFeat.setAttributes(atMap)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
if add: if add:
try: try:
outFeat.setGeometry(diff_geom) outFeat.setGeometry(diff_geom)
outFeat.setAttributes(atMap) outFeat.setAttributes(atMap)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert) sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except: except:
QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
self.tr('Processing'), QgsMessageLog.INFO)
nElement += 1
del writer count += 1
feedback.setProgress(int(count * total))
return {self.OUTPUT: dest_id}