Skip to content

Commit

Permalink
[processing] restore union algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn committed Jul 12, 2017
1 parent 846abe7 commit b46373b
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 75 deletions.
5 changes: 3 additions & 2 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -194,7 +194,7 @@ def getAlgs(self):
# PolygonsToLines(), LinesToPolygons(), ExtractNodes(),
# ConvexHull(), FixedDistanceBuffer(),
# VariableDistanceBuffer(),
# Intersection(), Union(),
# Intersection(),
# RandomSelection(), RandomSelectionWithinSubsets(),
# SelectByLocation(),
# ExtractByLocation(),
Expand Down Expand Up @@ -274,6 +274,7 @@ def getAlgs(self):
SnapGeometriesToLayer(),
SpatialiteExecuteSQL(),
SymmetricalDifference(),
Union(),
VectorSplit(),
VoronoiPolygons(),
ZonalStatistics()
Expand Down
148 changes: 75 additions & 73 deletions python/plugins/processing/algs/qgis/Union.py
Expand Up @@ -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]
Expand All @@ -57,7 +57,7 @@
class Union(QgisAlgorithm):

INPUT = 'INPUT'
INPUT2 = 'INPUT2'
OVERLAY = 'OVERLAY'
OUTPUT = 'OUTPUT'

def icon(self):
Expand All @@ -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'
Expand All @@ -83,59 +84,60 @@ 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)
lstIntersectingB.append(tmpGeom)

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)
Expand All @@ -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
Expand All @@ -162,63 +163,64 @@ 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():
diff_geom = QgsGeometry(i)
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
Expand All @@ -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}

0 comments on commit b46373b

Please sign in to comment.