mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-01 00:46:20 -05:00
248 lines
11 KiB
Python
Executable File
248 lines
11 KiB
Python
Executable File
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
***************************************************************************
|
|
Union.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'
|
|
|
|
# This will get replaced with a git SHA1 when you do a git archive
|
|
|
|
__revision__ = '$Format:%H$'
|
|
|
|
import os
|
|
|
|
from qgis.PyQt.QtGui import QIcon
|
|
|
|
from qgis.core import (QgsFeatureRequest,
|
|
QgsFeature,
|
|
QgsFeatureSink,
|
|
QgsGeometry,
|
|
QgsWkbTypes,
|
|
QgsProcessingUtils,
|
|
QgsProcessingParameterFeatureSource,
|
|
QgsProcessingParameterFeatureSink,
|
|
QgsSpatialIndex)
|
|
|
|
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
|
|
from processing.tools import vector
|
|
|
|
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
|
|
|
|
|
|
class Union(QgisAlgorithm):
|
|
|
|
INPUT = 'INPUT'
|
|
OVERLAY = 'OVERLAY'
|
|
OUTPUT = 'OUTPUT'
|
|
|
|
def icon(self):
|
|
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'union.png'))
|
|
|
|
def group(self):
|
|
return self.tr('Vector overlay')
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def initAlgorithm(self, config=None):
|
|
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'
|
|
|
|
def displayName(self):
|
|
return self.tr('Union')
|
|
|
|
def processAlgorithm(self, parameters, context, feedback):
|
|
sourceA = self.parameterAsSource(parameters, self.INPUT, context)
|
|
sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)
|
|
|
|
geomType = QgsWkbTypes.multiType(sourceA.wkbType())
|
|
fields = QgsProcessingUtils.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 = QgsSpatialIndex(sourceA, feedback)
|
|
indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)
|
|
|
|
total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1
|
|
count = 0
|
|
|
|
for featA in sourceA.getFeatures():
|
|
if feedback.isCanceled():
|
|
break
|
|
|
|
lstIntersectingB = []
|
|
geom = featA.geometry()
|
|
atMapA = featA.attributes()
|
|
intersects = indexB.intersects(geom.boundingBox())
|
|
if len(intersects) < 1:
|
|
try:
|
|
geom.convertToMultiType()
|
|
outFeat.setGeometry(geom)
|
|
outFeat.setAttributes(atMapA)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
# This really shouldn't happen, as we haven't
|
|
# edited the input geom at all
|
|
feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
|
|
else:
|
|
request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
|
|
request.setDestinationCrs(sourceA.sourceCrs())
|
|
|
|
engine = QgsGeometry.createGeometryEngine(geom.geometry())
|
|
engine.prepareGeometry()
|
|
|
|
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
|
|
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)
|
|
|
|
if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection:
|
|
# Intersection produced different geomety types
|
|
temp_list = int_geom.asGeometryCollection()
|
|
for i in temp_list:
|
|
if i.type() == geom.type():
|
|
int_geom = QgsGeometry(i)
|
|
try:
|
|
int_geom.convertToMultiType()
|
|
outFeat.setGeometry(int_geom)
|
|
outFeat.setAttributes(atMapA + atMapB)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
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
|
|
# produced by the intersection
|
|
# fix #3549
|
|
if QgsWkbTypes.geometryType(int_geom.wkbType()) == QgsWkbTypes.geometryType(geomType):
|
|
try:
|
|
int_geom.convertToMultiType()
|
|
outFeat.setGeometry(int_geom)
|
|
outFeat.setAttributes(atMapA + atMapB)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid 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() == 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:
|
|
diff_geom.convertToMultiType()
|
|
outFeat.setGeometry(diff_geom)
|
|
outFeat.setAttributes(atMapA)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
|
|
|
|
count += 1
|
|
feedback.setProgress(int(count * total))
|
|
|
|
length = len(sourceA.fields())
|
|
atMapA = [None] * length
|
|
|
|
for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())):
|
|
if feedback.isCanceled():
|
|
break
|
|
|
|
add = False
|
|
geom = featA.geometry()
|
|
diff_geom = QgsGeometry(geom)
|
|
atMap = [None] * length
|
|
atMap.extend(featA.attributes())
|
|
intersects = indexA.intersects(geom.boundingBox())
|
|
|
|
if len(intersects) < 1:
|
|
try:
|
|
geom.convertToMultiType()
|
|
outFeat.setGeometry(geom)
|
|
outFeat.setAttributes(atMap)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
|
|
else:
|
|
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 featB in sourceA.getFeatures(request):
|
|
atMapB = featB.attributes()
|
|
tmpGeom = featB.geometry()
|
|
|
|
if engine.intersects(tmpGeom.geometry()):
|
|
add = True
|
|
diff_geom = QgsGeometry(diff_geom.difference(tmpGeom))
|
|
else:
|
|
try:
|
|
# Ihis only happens if the bounding box
|
|
# intersects, but the geometry doesn't
|
|
diff_geom.convertToMultiType()
|
|
outFeat.setGeometry(diff_geom)
|
|
outFeat.setAttributes(atMap)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
|
|
|
|
if add:
|
|
try:
|
|
diff_geom.convertToMultiType()
|
|
outFeat.setGeometry(diff_geom)
|
|
outFeat.setAttributes(atMap)
|
|
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
|
|
except:
|
|
feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
|
|
|
|
count += 1
|
|
feedback.setProgress(int(count * total))
|
|
|
|
return {self.OUTPUT: dest_id}
|