diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index e78e76eca37..95b929848c0 100644 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -530,10 +530,6 @@ qgis:truncatetable: > Warning - this algorithm modifies the layer in place, and deleted features cannot be restored! -qgis:union: > - This algorithm creates a layer containing all the features from both input layers. In the case of polygon layers, separate features are created for overlapping and non-overlapping features. The attribute table of the union layer contains attribute values from the respective input layer for non-overlapping features, and attribute values from both input layers for overlapping features. - - qgis:variabledistancebuffer: > This algorithm computes a buffer area for all the features in an input layer. The size of the buffer for a given feature is defined by an attribute, so it allows different features to have different buffer sizes. diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py index 924f625ebbd..5cd7565827e 100644 --- a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py @@ -137,7 +137,6 @@ from .TextToFloat import TextToFloat from .TinInterpolation import TinInterpolation from .TopoColors import TopoColor from .TruncateTable import TruncateTable -from .Union import Union from .UniqueValues import UniqueValues from .VariableDistanceBuffer import VariableDistanceBuffer from .VectorSplit import VectorSplit @@ -253,7 +252,6 @@ class QgisAlgorithmProvider(QgsProcessingProvider): TinInterpolation(), TopoColor(), TruncateTable(), - Union(), UniqueValues(), VariableDistanceBuffer(), VectorSplit(), diff --git a/python/plugins/processing/algs/qgis/Union.py b/python/plugins/processing/algs/qgis/Union.py deleted file mode 100644 index 2cf9c05327f..00000000000 --- a/python/plugins/processing/algs/qgis/Union.py +++ /dev/null @@ -1,250 +0,0 @@ -# -*- 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 groupId(self): - return 'vectoroverlay' - - 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(), context.transformContext())), 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(), context.transformContext()) - - engine = QgsGeometry.createGeometryEngine(geom.constGet()) - engine.prepareGeometry() - - for featB in sourceB.getFeatures(request): - atMapB = featB.attributes() - tmpGeom = featB.geometry() - - if engine.intersects(tmpGeom.constGet()): - 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.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.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(), context.transformContext())): - 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(), context.transformContext()) - - # use prepared geometries for faster intersection tests - engine = QgsGeometry.createGeometryEngine(diff_geom.constGet()) - engine.prepareGeometry() - - for featB in sourceA.getFeatures(request): - atMapB = featB.attributes() - tmpGeom = featB.geometry() - - if engine.intersects(tmpGeom.constGet()): - 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} diff --git a/python/plugins/processing/tests/testdata/expected/union1.gml b/python/plugins/processing/tests/testdata/expected/union1.gml new file mode 100644 index 00000000000..f2434b728f4 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/union1.gml @@ -0,0 +1,98 @@ + + + + + 01 + 911 + + + + + + 2,6 2,5 1,5 1,6 2,6 + A1 + B2 + + + + + 4,5 3,5 3,6 4,6 4,5 + A2 + B2 + + + + + 8,7 7,7 7,8 8,8 8,7 + A3 + B1 + + + + + 6,7 5,7 5,8 6,8 6,7 + A3 + B3 + + + + + 2,5 2,3 1,3 1,5 2,51,6 1,11 8,11 8,10 2,10 2,6 1,6 + A1 + + + + + + 3,3 3,4 4,4 4,3 3,3 + A4 + + + + + + 4,6 6,6 6,5 4,5 4,6 + A2 + + + + + + 7,7 6,7 6,8 7,8 7,78,8 9,8 9,7 8,7 8,8 + A3 + + + + + + 8,7 8,1 1,1 1,2 7,2 7,7 8,77,8 7,9 8,9 8,8 7,8 + + B1 + + + + + 5,3 6,3 6,4 5,4 5,3 + + B4 + + + + + 1,5 0,5 0,6 1,6 1,53,5 2,5 2,6 3,6 3,5 + + B2 + + + + + 5,7 3,7 3,8 5,8 5,7 + + B3 + + + diff --git a/python/plugins/processing/tests/testdata/expected/union1.xsd b/python/plugins/processing/tests/testdata/expected/union1.xsd new file mode 100644 index 00000000000..50a20418bbd --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/union1.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/union3.gml b/python/plugins/processing/tests/testdata/expected/union3.gml new file mode 100644 index 00000000000..a20c283db9a --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/union3.gml @@ -0,0 +1,63 @@ + + + + + 11 + 85 + + + + + + 6,2 5,2 5,3 6,3 6,2 + A1 + B4 + + + + + 3,2 2,2 2,3 3,3 3,2 + A1 + B1 + + + + + 4,2 3,2 3,3 4,3 5,3 5,2 4,27,2 6,2 6,3 7,3 7,2 + A1 + + + + + + 4,2 4,1 2,1 2,2 3,2 4,2 + + B1 + + + + + 2,3 1,3 1,4 2,4 2,3 + + B2 + + + + + 7,3 7,4 8,4 8,1 7,1 7,2 7,3 + + B3 + + + + + 4,3 4,5 6,5 6,3 5,3 5,4 4,3 + + B4 + + + diff --git a/python/plugins/processing/tests/testdata/expected/union3.xsd b/python/plugins/processing/tests/testdata/expected/union3.xsd new file mode 100644 index 00000000000..0c59b769085 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/union3.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 00d1997282b..ed1eafb8846 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -5328,4 +5328,40 @@ tests: fields: fid: skip + - algorithm: native:union + name: Test Union (basic) + params: + INPUT: + name: custom/overlay1_a.geojson + type: vector + OVERLAY: + name: custom/overlay1_b.geojson + type: vector + results: + OUTPUT: + name: expected/union1.gml + type: vector + pk: [id_a, id_b] + compare: + fields: + fid: skip + + - algorithm: native:union + name: Test Union (geom types) + params: + INPUT: + name: custom/overlay3_a.geojson + type: vector + OVERLAY: + name: custom/overlay3_b.geojson + type: vector + results: + OUTPUT: + name: expected/union3.gml + type: vector + pk: [id_a, id_b] + compare: + fields: + fid: skip + # See ../README.md for a description of the file format diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index e73ca4de634..f67db0b79eb 100755 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -78,6 +78,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmtransect.cpp processing/qgsalgorithmtransform.cpp processing/qgsalgorithmtranslate.cpp + processing/qgsalgorithmunion.cpp processing/qgsalgorithmuniquevalueindex.cpp processing/qgsalgorithmwedgebuffers.cpp diff --git a/src/analysis/processing/qgsalgorithmunion.cpp b/src/analysis/processing/qgsalgorithmunion.cpp new file mode 100644 index 00000000000..e380fb7ea01 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmunion.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + qgsalgorithmunion.cpp + --------------------- + Date : April 2018 + Copyright : (C) 2018 by Martin Dobias + Email : wonder dot sk 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. * + * * + ***************************************************************************/ + +#include "qgsalgorithmunion.h" + +#include "qgsoverlayutils.h" + +///@cond PRIVATE + + +QString QgsUnionAlgorithm::name() const +{ + return QStringLiteral( "union" ); +} + +QString QgsUnionAlgorithm::displayName() const +{ + return QObject::tr( "Union" ); +} + +QString QgsUnionAlgorithm::group() const +{ + return QObject::tr( "Vector overlay" ); +} + +QString QgsUnionAlgorithm::groupId() const +{ + return QStringLiteral( "vectoroverlay" ); +} + +QString QgsUnionAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm creates a layer containing all the features from both input layers. In the case of polygon layers, separate features are created for overlapping and non-overlapping features. The attribute table of the union layer contains attribute values from the respective input layer for non-overlapping features, and attribute values from both input layers for overlapping features." ); +} + +QgsProcessingAlgorithm *QgsUnionAlgorithm::createInstance() const +{ + return new QgsUnionAlgorithm(); +} + +void QgsUnionAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "OVERLAY" ), QObject::tr( "Union layer" ) ) ); + + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Union" ) ) ); +} + + +QVariantMap QgsUnionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + std::unique_ptr< QgsFeatureSource > sourceA( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !sourceA ) + throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) ); + + std::unique_ptr< QgsFeatureSource > sourceB( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); + if ( !sourceB ) + throw QgsProcessingException( QObject::tr( "Could not load source layer for OVERLAY" ) ); + + QgsWkbTypes::Type geomType = QgsWkbTypes::multiType( sourceA->wkbType() ); + + QgsFields fields = QgsProcessingUtils::combineFields( sourceA->fields(), sourceB->fields() ); + + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, geomType, sourceA->sourceCrs() ) ); + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + + QList fieldIndicesA = QgsOverlayUtils::fieldNamesToIndices( QStringList(), sourceA->fields() ); + QList fieldIndicesB = QgsOverlayUtils::fieldNamesToIndices( QStringList(), sourceB->fields() ); + + int count = 0; + int total = sourceA->featureCount() * 2 + sourceB->featureCount(); + + QgsOverlayUtils::intersection( *sourceA.get(), *sourceB.get(), *sink.get(), context, feedback, count, total, fieldIndicesA, fieldIndicesB ); + + QgsOverlayUtils::difference( *sourceA.get(), *sourceB.get(), *sink.get(), context, feedback, count, total, QgsOverlayUtils::OutputAB ); + + QgsOverlayUtils::difference( *sourceB.get(), *sourceA.get(), *sink.get(), context, feedback, count, total, QgsOverlayUtils::OutputBA ); + + return outputs; +} + +///@endcond diff --git a/src/analysis/processing/qgsalgorithmunion.h b/src/analysis/processing/qgsalgorithmunion.h new file mode 100644 index 00000000000..35e4d0fc5b3 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmunion.h @@ -0,0 +1,46 @@ +/*************************************************************************** + qgsalgorithmunion.h + --------------------- + Date : April 2018 + Copyright : (C) 2018 by Martin Dobias + Email : wonder dot sk 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. * + * * + ***************************************************************************/ + +#ifndef QGSALGORITHMUNION_H +#define QGSALGORITHMUNION_H + + +#define SIP_NO_FILE + +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +class QgsUnionAlgorithm : public QgsProcessingAlgorithm +{ + public: + QgsUnionAlgorithm() = default; + + virtual QString name() const override; + virtual QString displayName() const override; + virtual QString group() const override; + virtual QString groupId() const override; + QString shortHelpString() const override; + + protected: + virtual QgsProcessingAlgorithm *createInstance() const override; + virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMUNION_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 4778320a616..d2ff0517b37 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -75,6 +75,7 @@ #include "qgsalgorithmtransect.h" #include "qgsalgorithmtransform.h" #include "qgsalgorithmtranslate.h" +#include "qgsalgorithmunion.h" #include "qgsalgorithmuniquevalueindex.h" #include "qgsalgorithmwedgebuffers.h" @@ -180,6 +181,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsTransectAlgorithm() ); addAlgorithm( new QgsTransformAlgorithm() ); addAlgorithm( new QgsTranslateAlgorithm() ); + addAlgorithm( new QgsUnionAlgorithm() ); addAlgorithm( new QgsWedgeBuffersAlgorithm() ); }