From 85919503498c701dc9358a3f7581962da3a5c0a9 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 12 May 2020 13:40:35 +0300 Subject: [PATCH] [processing] port Snap Geometries algorithm to C++ --- .../algs/qgis/QgisAlgorithmProvider.py | 2 - .../processing/algs/qgis/SnapGeometries.py | 148 ------------- src/analysis/CMakeLists.txt | 1 + .../processing/qgsalgorithmsnapgeometries.cpp | 195 ++++++++++++++++++ .../processing/qgsalgorithmsnapgeometries.h | 56 +++++ .../processing/qgsnativealgorithms.cpp | 2 + 6 files changed, 254 insertions(+), 150 deletions(-) delete mode 100644 python/plugins/processing/algs/qgis/SnapGeometries.py create mode 100644 src/analysis/processing/qgsalgorithmsnapgeometries.cpp create mode 100644 src/analysis/processing/qgsalgorithmsnapgeometries.h diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py index a44bf55f7d9a..76466d254c6c 100644 --- a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py @@ -80,7 +80,6 @@ from .SelectByExpression import SelectByExpression from .SetRasterStyle import SetRasterStyle from .SetVectorStyle import SetVectorStyle -from .SnapGeometries import SnapGeometriesToLayer from .SpatialJoinSummary import SpatialJoinSummary from .StatisticsByCategories import StatisticsByCategories from .TextToFloat import TextToFloat @@ -157,7 +156,6 @@ def getAlgs(self): SelectByExpression(), SetRasterStyle(), SetVectorStyle(), - SnapGeometriesToLayer(), SpatialJoinSummary(), StatisticsByCategories(), TextToFloat(), diff --git a/python/plugins/processing/algs/qgis/SnapGeometries.py b/python/plugins/processing/algs/qgis/SnapGeometries.py deleted file mode 100644 index 634b2fa9e65d..000000000000 --- a/python/plugins/processing/algs/qgis/SnapGeometries.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - SnapGeometries.py - ----------------- - Date : October 2016 - Copyright : (C) 2016 by Nyall Dawson - Email : nyall dot dawson 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__ = 'Nyall Dawson' -__date__ = 'October 2016' -__copyright__ = '(C) 2016, Nyall Dawson' - -from qgis.analysis import (QgsGeometrySnapper, - QgsGeometrySnapperSingleSource, - QgsInternalGeometrySnapper) -from qgis.core import (QgsFeatureSink, - QgsProcessing, - QgsMapLayer, - QgsProcessingAlgorithm, - QgsProcessingException, - QgsProcessingParameterDistance, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink, - QgsProcessingParameterEnum) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm - - -class SnapGeometriesToLayer(QgisAlgorithm): - INPUT = 'INPUT' - REFERENCE_LAYER = 'REFERENCE_LAYER' - TOLERANCE = 'TOLERANCE' - OUTPUT = 'OUTPUT' - BEHAVIOR = 'BEHAVIOR' - - def group(self): - return self.tr('Vector geometry') - - def groupId(self): - return 'vectorgeometry' - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), [QgsProcessing.TypeVectorPoint, QgsProcessing.TypeVectorLine, QgsProcessing.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterFeatureSource(self.REFERENCE_LAYER, self.tr('Reference layer'), - [QgsProcessing.TypeVectorPoint, - QgsProcessing.TypeVectorLine, - QgsProcessing.TypeVectorPolygon])) - - self.addParameter(QgsProcessingParameterDistance(self.TOLERANCE, self.tr('Tolerance'), parentParameterName=self.INPUT, - minValue=0.00000001, defaultValue=10.0)) - - self.modes = [self.tr('Prefer aligning nodes, insert extra vertices where required'), - self.tr('Prefer closest point, insert extra vertices where required'), - self.tr('Prefer aligning nodes, don\'t insert new vertices'), - self.tr('Prefer closest point, don\'t insert new vertices'), - self.tr('Move end points only, prefer aligning nodes'), - self.tr('Move end points only, prefer closest point'), - self.tr('Snap end points to end points only'), - self.tr('Snap to anchor nodes (single layer only)')] - self.addParameter(QgsProcessingParameterEnum( - self.BEHAVIOR, - self.tr('Behavior'), - options=self.modes, defaultValue=0)) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Snapped geometry'))) - - def name(self): - return 'snapgeometries' - - def displayName(self): - return self.tr('Snap geometries to layer') - - def flags(self): - return super().flags() | QgsProcessingAlgorithm.FlagSupportsInPlaceEdits - - def supportInPlaceEdit(self, layer): - return layer.type() == QgsMapLayer.VectorLayer - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - if source is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) - - reference_source = self.parameterAsSource(parameters, self.REFERENCE_LAYER, context) - if reference_source is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.REFERENCE_LAYER)) - - tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) - mode = self.parameterAsEnum(parameters, self.BEHAVIOR, context) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) - if sink is None: - raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - - if parameters[self.INPUT] != parameters[self.REFERENCE_LAYER]: - if mode == 7: - raise QgsProcessingException(self.tr('This mode applies when the input and reference layer are the same.')) - - snapper = QgsGeometrySnapper(reference_source) - processed = 0 - for f in features: - if feedback.isCanceled(): - break - if f.hasGeometry(): - out_feature = f - out_feature.setGeometry(snapper.snapGeometry(f.geometry(), tolerance, mode)) - sink.addFeature(out_feature, QgsFeatureSink.FastInsert) - else: - sink.addFeature(f) - processed += 1 - feedback.setProgress(processed * total) - elif mode == 7: - # input layer == ref layer - modified_count = QgsGeometrySnapperSingleSource.run(source, sink, tolerance, feedback) - feedback.pushInfo(self.tr('Snapped {} geometries.').format(modified_count)) - else: - # snapping internally - snapper = QgsInternalGeometrySnapper(tolerance, mode) - processed = 0 - for f in features: - if feedback.isCanceled(): - break - - out_feature = f - out_feature.setGeometry(snapper.snapFeature(f)) - sink.addFeature(out_feature, QgsFeatureSink.FastInsert) - processed += 1 - feedback.setProgress(processed * total) - - return {self.OUTPUT: dest_id} diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index ad044f672554..c2b7e3eeb670 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -153,6 +153,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmsinglesidedbuffer.cpp processing/qgsalgorithmslope.cpp processing/qgsalgorithmsmooth.cpp + processing/qgsalgorithmsnapgeometries.cpp processing/qgsalgorithmsnaptogrid.cpp processing/qgsalgorithmspatialindex.cpp processing/qgsalgorithmsplitfeaturesbyattributecharacter.cpp diff --git a/src/analysis/processing/qgsalgorithmsnapgeometries.cpp b/src/analysis/processing/qgsalgorithmsnapgeometries.cpp new file mode 100644 index 000000000000..086f695f868f --- /dev/null +++ b/src/analysis/processing/qgsalgorithmsnapgeometries.cpp @@ -0,0 +1,195 @@ +/*************************************************************************** + qgsalgorithmsnapgeometries.cpp + --------------------- + begin : May 2020 + copyright : (C) 2020 by Alexander Bruy + email : alexander dot bruy 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 "qgsalgorithmsnapgeometries.h" + +#include "qgsvectorlayer.h" +#include "vector/qgsgeometrysnapper.h" +#include "vector/qgsgeometrysnappersinglesource.h" + +///@cond PRIVATE + +QString QgsSnapGeometriesAlgorithm::name() const +{ + return QStringLiteral( "snapgeometries" ); +} + +QString QgsSnapGeometriesAlgorithm::displayName() const +{ + return QObject::tr( "Snap geometries to layer" ); +} + +QString QgsSnapGeometriesAlgorithm::shortHelpString() const +{ + return QObject::tr( "Snaps the geometries in a layer. Snapping can be done either to the geometries " + "from another layer, or to geometries within the same layer." ) + + QStringLiteral( "\n\n" ) + + QObject::tr( "A tolerance is specified in layer units to control how close vertices need " + "to be to the reference layer geometries before they are snapped." ) + + QStringLiteral( "\n\n" ) + + QObject::tr( "Snapping occurs to both nodes and edges. Depending on the snapping behavior, " + "either nodes or edges will be preferred." ) + + QStringLiteral( "\n\n" ) + + QObject::tr( "Vertices will be inserted or removed as required to make the geometries match " + "the reference geometries." ); +} + +QStringList QgsSnapGeometriesAlgorithm::tags() const +{ + return QObject::tr( "geometry,snap,tolerance" ).split( ',' ); +} + +QString QgsSnapGeometriesAlgorithm::group() const +{ + return QObject::tr( "Vector geometry" ); +} + +QString QgsSnapGeometriesAlgorithm::groupId() const +{ + return QStringLiteral( "vectorgeometry" ); +} + +QgsProcessingAlgorithm::Flags QgsSnapGeometriesAlgorithm::flags() const +{ + Flags f = QgsProcessingAlgorithm::flags(); + f |= QgsProcessingAlgorithm::FlagSupportsInPlaceEdits; + return f; +} + +bool QgsSnapGeometriesAlgorithm::supportInPlaceEdit( const QgsMapLayer *l ) const +{ + const QgsVectorLayer *layer = qobject_cast< const QgsVectorLayer * >( l ); + if ( !layer ) + return false; + + return layer->isSpatial(); +} + +void QgsSnapGeometriesAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), + QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine << QgsProcessing::TypeVectorPolygon ) ); + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE_LAYER" ), QObject::tr( "Reference layer" ), + QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine << QgsProcessing::TypeVectorPolygon ) ); + addParameter( new QgsProcessingParameterDistance( QStringLiteral( "TOLERANCE" ), QObject::tr( "Tolerance" ), + 10.0, QStringLiteral( "INPUT" ), false, 0.00000001 ) ); + + QStringList options = QStringList() + << QObject::tr( "Prefer aligning nodes, insert extra vertices where required" ) + << QObject::tr( "Prefer closest point, insert extra vertices where required" ) + << QObject::tr( "Prefer aligning nodes, don't insert new vertices" ) + << QObject::tr( "Prefer closest point, don't insert new vertices" ) + << QObject::tr( "Move end points only, prefer aligning nodes" ) + << QObject::tr( "Move end points only, prefer closest point" ) + << QObject::tr( "Snap end points to end points only" ) + << QObject::tr( "Snap to anchor nodes (single layer only)" ); + addParameter( new QgsProcessingParameterEnum( QStringLiteral( "BEHAVIOR" ), QObject::tr( "Behavior" ), options, false, QVariantList() << 0 ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), + QObject::tr( "Snapped geometry" ), QgsProcessing::TypeVectorPolygon ) ); +} + +QgsSnapGeometriesAlgorithm *QgsSnapGeometriesAlgorithm::createInstance() const +{ + return new QgsSnapGeometriesAlgorithm(); +} + +QVariantMap QgsSnapGeometriesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); + + std::unique_ptr< QgsProcessingFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE_LAYER" ), context ) ); + if ( !referenceSource ) + throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE_LAYER" ) ) ); + + double tolerance = parameterAsDouble( parameters, QStringLiteral( "TOLERANCE" ), context ); + QgsGeometrySnapper::SnapMode mode = static_cast( parameterAsEnum( parameters, QStringLiteral( "BEHAVIOR" ), context ) ); + + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), source->wkbType(), source->sourceCrs() ) ); + if ( !sink ) + throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); + + double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 1; + QgsFeatureIterator features = source->getFeatures(); + + if ( parameters.value( QStringLiteral( "INPUT" ) ) != parameters.value( QStringLiteral( "REFERENCE_LAYER" ) ) ) + { + if ( mode == 7 ) + throw QgsProcessingException( QObject::tr( "This mode applies when the input and reference layer are the same." ) ); + + QgsGeometrySnapper snapper( referenceSource.get() ); + long long processed = 0; + QgsFeature f; + while ( features.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + break; + + if ( f.hasGeometry() ) + { + QgsFeature outFeature( f ); + outFeature.setGeometry( snapper.snapGeometry( f.geometry(), tolerance, mode ) ); + sink->addFeature( outFeature, QgsFeatureSink::FastInsert ); + } + else + { + sink->addFeature( f ); + } + processed += 1; + feedback->setProgress( processed * step ); + } + } + else if ( mode == 7 ) + { + // input layer == reference layer + int modified = QgsGeometrySnapperSingleSource::run( *source, *sink, tolerance, feedback ); + feedback->pushInfo( QObject::tr( "Snapped %1 geometries." ).arg( modified ) ); + } + else + { + // snapping internally + QgsInternalGeometrySnapper snapper( tolerance, mode ); + long long processed = 0; + QgsFeature f; + while ( features.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + break; + + if ( f.hasGeometry() ) + { + QgsFeature outFeature( f ); + outFeature.setGeometry( snapper.snapFeature( f ) ); + sink->addFeature( outFeature, QgsFeatureSink::FastInsert ); + } + else + { + sink->addFeature( f ); + } + processed += 1; + feedback->setProgress( processed * step ); + } + } + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + return outputs; +} + +///@endcond diff --git a/src/analysis/processing/qgsalgorithmsnapgeometries.h b/src/analysis/processing/qgsalgorithmsnapgeometries.h new file mode 100644 index 000000000000..71beba9f62ae --- /dev/null +++ b/src/analysis/processing/qgsalgorithmsnapgeometries.h @@ -0,0 +1,56 @@ +/*************************************************************************** + qgsalgorithmsnapgeometries.h + --------------------- + begin : May 2020 + copyright : (C) 2020 by Alexander Bruy + email : alexander dot bruy 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 QGSALGORITHMSNAPGEOMETRIES_H +#define QGSALGORITHMSNAPGEOMETRIES_H + +#define SIP_NO_FILE + +#include "qgis_sip.h" +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +/** + * Native Snap geometries algorithm. + */ +class QgsSnapGeometriesAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsSnapGeometriesAlgorithm() = default; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QgsProcessingAlgorithm::Flags flags() const override; + bool supportInPlaceEdit( const QgsMapLayer *layer ) const override; + QgsSnapGeometriesAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMSNAPGEOMETRIES_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 62760bf91026..7a560127c8d8 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -147,6 +147,7 @@ #include "qgsalgorithmsinglesidedbuffer.h" #include "qgsalgorithmslope.h" #include "qgsalgorithmsmooth.h" +#include "qgsalgorithmsnapgeometries.h" #include "qgsalgorithmsnaptogrid.h" #include "qgsalgorithmspatialindex.h" #include "qgsalgorithmsplitfeaturesbyattributecharacter.h" @@ -371,6 +372,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsSingleSidedBufferAlgorithm() ); addAlgorithm( new QgsSlopeAlgorithm() ); addAlgorithm( new QgsSmoothAlgorithm() ); + addAlgorithm( new QgsSnapGeometriesAlgorithm() ); addAlgorithm( new QgsSnapToGridAlgorithm() ); addAlgorithm( new QgsSpatialIndexAlgorithm() ); addAlgorithm( new QgsSplitFeaturesByAttributeCharacterAlgorithm() );