From 8e8f3edc55180592725a276528e51d603e1f58b4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Sep 2017 08:28:48 +1000 Subject: [PATCH] Port merge lines and smooth to c++ --- python/plugins/processing/algs/help/qgis.yaml | 14 --- .../processing/algs/qgis/MergeLines.py | 78 ----------------- .../algs/qgis/QGISAlgorithmProvider.py | 4 - python/plugins/processing/algs/qgis/Smooth.py | 80 ----------------- .../tests/testdata/qgis_algorithm_tests.yaml | 6 +- src/core/processing/qgsnativealgorithms.cpp | 87 ++++++++++++++++++- src/core/processing/qgsnativealgorithms.h | 53 +++++++++++ 7 files changed, 142 insertions(+), 180 deletions(-) delete mode 100644 python/plugins/processing/algs/qgis/MergeLines.py delete mode 100644 python/plugins/processing/algs/qgis/Smooth.py diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index 2523ec3d98b..df24990eb72 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -302,11 +302,6 @@ qgis:meancoordinates: > If an attribute is selected in the parameters, features will be grouped according to values in this field. Instead of a single point with the center of mass of the whole layer, the output layer will contain a center of mass for the features in each category. -qgis:mergelines: > - This algorithm joins all connected parts of MultiLineString geometries into single LineString geometries. - - If any parts of the input MultiLineString geometries are not connected, the resultant geometry will be a MultiLineString containing any lines which could be merged and any non-connected line parts. - qgis:mergevectorlayers: > This algorithm combines multiple vector layers of the same geometry type into a single one. @@ -547,15 +542,6 @@ qgis:singlesidedbuffer: > The miter limit parameter is only applicable for miter join styles, and controls the maximum distance from the buffer to use when creating a mitered join. -qgis:smoothgeometry: > - This algorithm smooths the geometries in a line or polygon layer. It creates a new layer with the same features as the ones in the input layer, but with geometries containing a higher number of vertices and corners in the geometries smoothed out. - - The iterations parameter dictates how many smoothing iterations will be applied to each geometry. A higher number of iterations results in smoother geometries with the cost of greater number of nodes in the geometries. - - The offset parameter controls how "tightly" the smoothed geometries follow the original geometries. Smaller values results in a tighter fit, and larger values will create a looser fit. - - The maximum angle parameter can be used to prevent smoothing of nodes with large angles. Any node where the angle of the segments to either side is larger than this will not be smoothed. For example, setting the maximum angle to 90 degrees or lower would preserve right angles in the geometry. - qgis:snapgeometries: > This algorithm snaps the geometries in a layer. Snapping can be done either to the geometries from another layer, or to geometries within the same layer. diff --git a/python/plugins/processing/algs/qgis/MergeLines.py b/python/plugins/processing/algs/qgis/MergeLines.py deleted file mode 100644 index dc3467443ee..00000000000 --- a/python/plugins/processing/algs/qgis/MergeLines.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - Smooth.py - --------- - Date : July 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__ = 'July 2016' -__copyright__ = '(C) 2016, Nyall Dawson' - -# This will get replaced with a git SHA1 when you do a git archive323 - -__revision__ = '$Format:%H$' - -import os - -from qgis.core import (QgsProcessing, - QgsWkbTypes) - -from qgis.PyQt.QtGui import QIcon - -from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm - - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class MergeLines(QgisFeatureBasedAlgorithm): - - def icon(self): - return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'to_lines.png')) - - def tags(self): - return self.tr('line,merge,join,parts').split(',') - - def group(self): - return self.tr('Vector geometry') - - def __init__(self): - super().__init__() - - def name(self): - return 'mergelines' - - def displayName(self): - return self.tr('Merge lines') - - def outputName(self): - return self.tr('Merged') - - def outputType(self): - return QgsProcessing.TypeVectorLine - - def outputWkbType(self, input_wkb): - return QgsWkbTypes.MultiLineString - - def processFeature(self, feature, feedback): - input_geometry = feature.geometry() - if input_geometry: - output_geometry = input_geometry.mergeLines() - if not output_geometry: - feedback.reportError(self.tr('Error merging lines for feature {}').format(feature.id())) - - feature.setGeometry(output_geometry) - return feature diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index ad81c30a5f3..06bc6a74e86 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -95,7 +95,6 @@ from .LinesIntersection import LinesIntersection from .LinesToPolygons import LinesToPolygons from .MeanCoords import MeanCoords from .Merge import Merge -from .MergeLines import MergeLines from .MinimumBoundingGeometry import MinimumBoundingGeometry from .NearestNeighbourAnalysis import NearestNeighbourAnalysis from .OffsetLine import OffsetLine @@ -145,7 +144,6 @@ from .ShortestPathPointToPoint import ShortestPathPointToPoint from .SimplifyGeometries import SimplifyGeometries from .SingleSidedBuffer import SingleSidedBuffer from .Slope import Slope -from .Smooth import Smooth from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SpatialIndex import SpatialIndex @@ -235,7 +233,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): LinesToPolygons(), MeanCoords(), Merge(), - MergeLines(), MinimumBoundingGeometry(), NearestNeighbourAnalysis(), OffsetLine(), @@ -285,7 +282,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): SimplifyGeometries(), SingleSidedBuffer(), Slope(), - Smooth(), SnapGeometriesToLayer(), SpatialiteExecuteSQL(), SpatialIndex(), diff --git a/python/plugins/processing/algs/qgis/Smooth.py b/python/plugins/processing/algs/qgis/Smooth.py deleted file mode 100644 index 324b9fa3e50..00000000000 --- a/python/plugins/processing/algs/qgis/Smooth.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - Smooth.py - --------- - Date : November 2015 - Copyright : (C) 2015 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__ = 'November 2015' -__copyright__ = '(C) 2015, Nyall Dawson' - -# This will get replaced with a git SHA1 when you do a git archive323 - -__revision__ = '$Format:%H$' - -from qgis.core import (QgsProcessingException, - QgsProcessingParameterNumber) - -from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm - - -class Smooth(QgisFeatureBasedAlgorithm): - - ITERATIONS = 'ITERATIONS' - MAX_ANGLE = 'MAX_ANGLE' - OFFSET = 'OFFSET' - - def group(self): - return self.tr('Vector geometry') - - def __init__(self): - super().__init__() - - def initParameters(self, config=None): - self.addParameter(QgsProcessingParameterNumber(self.ITERATIONS, - self.tr('Iterations'), - defaultValue=1, minValue=1, maxValue=10)) - self.addParameter(QgsProcessingParameterNumber(self.OFFSET, - self.tr('Offset'), QgsProcessingParameterNumber.Double, - defaultValue=0.25, minValue=0.0, maxValue=0.5)) - self.addParameter(QgsProcessingParameterNumber(self.MAX_ANGLE, - self.tr('Maximum node angle to smooth'), QgsProcessingParameterNumber.Double, - defaultValue=180.0, minValue=0.0, maxValue=180.0)) - - def name(self): - return 'smoothgeometry' - - def displayName(self): - return self.tr('Smooth geometry') - - def outputName(self): - return self.tr('Smoothed') - - def prepareAlgorithm(self, parameters, context, feedback): - self.iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) - self.offset = self.parameterAsDouble(parameters, self.OFFSET, context) - self.max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) - return True - - def processFeature(self, feature, feedback): - if feature.hasGeometry(): - output_geometry = feature.geometry().smooth(self.iterations, self.offset, -1, self.max_angle) - if not output_geometry: - raise QgsProcessingException( - self.tr('Error smoothing geometry')) - - feature.setGeometry(output_geometry) - return feature diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index abe13d85df7..4a447cc5152 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -588,7 +588,7 @@ tests: geometry: precision: 7 - - algorithm: qgis:mergelines + - algorithm: native:mergelines name: Merge lines algorithm params: INPUT: @@ -1265,7 +1265,7 @@ tests: geometry: precision: 7 - - algorithm: qgis:smoothgeometry + - algorithm: native:smoothgeometry name: Smooth (lines) params: INPUT: @@ -1279,7 +1279,7 @@ tests: name: expected/smoothed_lines.gml type: vector - - algorithm: qgis:smoothgeometry + - algorithm: native:smoothgeometry name: Smooth (lines, with max angle) params: INPUT: diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 773fc7183bd..d6871be68d0 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -79,6 +79,8 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsSelectByLocationAlgorithm() ); addAlgorithm( new QgsExtractByLocationAlgorithm() ); addAlgorithm( new QgsFixGeometriesAlgorithm() ); + addAlgorithm( new QgsMergeLinesAlgorithm() ); + addAlgorithm( new QgsSmoothAlgorithm() ); } void QgsCentroidAlgorithm::initAlgorithm( const QVariantMap & ) @@ -1665,6 +1667,89 @@ QgsFeature QgsFixGeometriesAlgorithm::processFeature( const QgsFeature &feature, return outputFeature; } + +QString QgsMergeLinesAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm joins all connected parts of MultiLineString geometries into single LineString geometries.\n\n" + "If any parts of the input MultiLineString geometries are not connected, the resultant " + "geometry will be a MultiLineString containing any lines which could be merged and any non-connected line parts." ); +} + +QgsMergeLinesAlgorithm *QgsMergeLinesAlgorithm::createInstance() const +{ + return new QgsMergeLinesAlgorithm(); +} + +QgsFeature QgsMergeLinesAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) +{ + if ( !feature.hasGeometry() ) + return feature; + + QgsFeature out = feature; + QgsGeometry outputGeometry = feature.geometry().mergeLines(); + if ( !outputGeometry ) + feedback->reportError( QObject::tr( "Error merging lines for feature %1" ).arg( feature.id() ) ); + + out.setGeometry( outputGeometry ); + return out; +} + +QString QgsSmoothAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm smooths the geometries in a line or polygon layer. It creates a new layer with the " + "same features as the ones in the input layer, but with geometries containing a higher number of vertices " + "and corners in the geometries smoothed out.\n\n" + "The iterations parameter dictates how many smoothing iterations will be applied to each " + "geometry. A higher number of iterations results in smoother geometries with the cost of " + "greater number of nodes in the geometries.\n\n" + "The offset parameter controls how \"tightly\" the smoothed geometries follow the original geometries. " + "Smaller values results in a tighter fit, and larger values will create a looser fit.\n\n" + "The maximum angle parameter can be used to prevent smoothing of " + "nodes with large angles. Any node where the angle of the segments to either " + "side is larger than this will not be smoothed. For example, setting the maximum " + "angle to 90 degrees or lower would preserve right angles in the geometry." ); +} + +QgsSmoothAlgorithm *QgsSmoothAlgorithm::createInstance() const +{ + return new QgsSmoothAlgorithm(); +} + +void QgsSmoothAlgorithm::initParameters( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ITERATIONS" ), + QObject::tr( "Iterations" ), QgsProcessingParameterNumber::Integer, + 1, false, 1, 10 ) ); + addParameter( new QgsProcessingParameterNumber( QStringLiteral( "OFFSET" ), + QObject::tr( "Offset" ), QgsProcessingParameterNumber::Double, + 0.25, false, 0.0, 0.5 ) ); + addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_ANGLE" ), + QObject::tr( "Maximum node angle to smooth" ), QgsProcessingParameterNumber::Double, + 180.0, false, 0.0, 180.0 ) ); +} + +bool QgsSmoothAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +{ + mIterations = parameterAsInt( parameters, QStringLiteral( "ITERATIONS" ), context ); + mOffset = parameterAsDouble( parameters, QStringLiteral( "OFFSET" ), context ); + mMaxAngle = parameterAsDouble( parameters, QStringLiteral( "MAX_ANGLE" ), context ); + return true; +} + +QgsFeature QgsSmoothAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) +{ + QgsFeature f = feature; + if ( f.hasGeometry() ) + { + QgsGeometry outputGeometry = f.geometry().smooth( mIterations, mOffset, -1, mMaxAngle ); + if ( !outputGeometry ) + { + feedback->reportError( QObject::tr( "Error smoothing geometry %1" ).arg( feature.id() ) ); + } + f.setGeometry( outputGeometry ); + } + return f; +} + ///@endcond - diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index a7a34b60369..4c51f4ca399 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -579,6 +579,59 @@ class QgsFixGeometriesAlgorithm : public QgsProcessingFeatureBasedAlgorithm }; +/** + * Native merge lines algorithm. + */ +class QgsMergeLinesAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsMergeLinesAlgorithm() = default; + QString name() const override { return QStringLiteral( "mergelines" ); } + QString displayName() const override { return QObject::tr( "Merge lines" ); } + virtual QStringList tags() const override { return QObject::tr( "line,merge,join,parts" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry" ); } + QString shortHelpString() const override; + QgsMergeLinesAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + QString outputName() const override { return QObject::tr( "Merged" ); } + QgsProcessing::SourceType outputLayerType() const override { return QgsProcessing::TypeVectorLine; } + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type ) const override { return QgsWkbTypes::MultiLineString; } + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + +}; + +/** + * Native smooth algorithm. + */ +class QgsSmoothAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsSmoothAlgorithm() = default; + QString name() const override { return QStringLiteral( "smoothgeometry" ); } + QString displayName() const override { return QObject::tr( "Smooth geometries" ); } + virtual QStringList tags() const override { return QObject::tr( "smooth,curve,generalize,round,bend,corners" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry" ); } + QString shortHelpString() const override; + QgsSmoothAlgorithm *createInstance() const override SIP_FACTORY; + void initParameters( const QVariantMap &configuration = QVariantMap() ) override; + + protected: + QString outputName() const override { return QObject::tr( "Smoothed" ); } + QgsProcessing::SourceType outputLayerType() const override { return QgsProcessing::TypeVectorLine; } + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + + private: + int mIterations = 1; + double mOffset = 0.25; + double mMaxAngle = 0; +}; + ///@endcond PRIVATE #endif // QGSNATIVEALGORITHMS_H