From 9cbc8cc20f9ccb800a05fb5729ca6e24f4d23936 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 16 Jul 2017 20:39:43 +1000 Subject: [PATCH 1/3] Port Offset line, Orthogonalize and Pole of Inaccessibility to new API Improvements: - Fix handling of multiline outputs for Offset Line algorithm --- .../processing/algs/qgis/OffsetLine.py | 76 ++--- .../processing/algs/qgis/Orthogonalize.py | 68 +++-- .../algs/qgis/PoleOfInaccessibility.py | 57 ++-- .../algs/qgis/QGISAlgorithmProvider.py | 16 +- .../testdata/expected/multiline_offset.gml | 6 +- .../tests/testdata/qgis_algorithm_tests.yaml | 266 +++++++++--------- 6 files changed, 255 insertions(+), 234 deletions(-) diff --git a/python/plugins/processing/algs/qgis/OffsetLine.py b/python/plugins/processing/algs/qgis/OffsetLine.py index 4dc7ce7738c..64ed774907b 100644 --- a/python/plugins/processing/algs/qgis/OffsetLine.py +++ b/python/plugins/processing/algs/qgis/OffsetLine.py @@ -27,24 +27,22 @@ __revision__ = '$Format:%H$' import os -from qgis.core import (QgsApplication, - QgsWkbTypes, - QgsFeatureSink, - QgsProcessingUtils) +from qgis.core import (QgsFeatureSink, + QgsProcessing, + QgsProcessingException, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterSelection, ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class OffsetLine(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' DISTANCE = 'DISTANCE' SEGMENTS = 'SEGMENTS' JOIN_STYLE = 'JOIN_STYLE' @@ -57,23 +55,29 @@ class OffsetLine(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterNumber(self.DISTANCE, - self.tr('Distance'), default=10.0)) - self.addParameter(ParameterNumber(self.SEGMENTS, - self.tr('Segments'), 1, default=8)) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), + [QgsProcessing.TypeVectorLine])) + self.addParameter(QgsProcessingParameterNumber(self.DISTANCE, + self.tr('Distance'), + type=QgsProcessingParameterNumber.Double, + defaultValue=10.0)) + self.addParameter(QgsProcessingParameterNumber(self.SEGMENTS, + self.tr('Segments'), + type=QgsProcessingParameterNumber.Integer, + minValue=1, defaultValue=8)) self.join_styles = [self.tr('Round'), 'Mitre', 'Bevel'] - self.addParameter(ParameterSelection( + self.addParameter(QgsProcessingParameterEnum( self.JOIN_STYLE, self.tr('Join style'), - self.join_styles)) - self.addParameter(ParameterNumber(self.MITRE_LIMIT, - self.tr('Mitre limit'), 1, default=2)) + options=self.join_styles)) + self.addParameter(QgsProcessingParameterNumber(self.MITRE_LIMIT, + self.tr('Mitre limit'), type=QgsProcessingParameterNumber.Double, + minValue=1, defaultValue=2)) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Offset'), datatype=[dataobjects.TYPE_VECTOR_LINE])) + self.addParameter( + QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Offset'), QgsProcessing.TypeVectorLine)) def name(self): return 'offsetline' @@ -82,31 +86,33 @@ class OffsetLine(QgisAlgorithm): return self.tr('Offset line') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + source = self.parameterAsSource(parameters, self.INPUT, context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), QgsWkbTypes.LineString, layer.crs(), context) + distance = self.parameterAsDouble(parameters, self.DISTANCE, context) + segments = self.parameterAsInt(parameters, self.SEGMENTS, context) + join_style = self.parameterAsEnum(parameters, self.JOIN_STYLE, context) + 1 + miter_limit = self.parameterAsDouble(parameters, self.MITRE_LIMIT, context) - distance = self.getParameterValue(self.DISTANCE) - segments = int(self.getParameterValue(self.SEGMENTS)) - join_style = self.getParameterValue(self.JOIN_STYLE) + 1 - miter_limit = self.getParameterValue(self.MITRE_LIMIT) - - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): + if feedback.isCanceled(): + break + output_feature = input_feature input_geometry = input_feature.geometry() if input_geometry: output_geometry = input_geometry.offsetCurve(distance, segments, join_style, miter_limit) if not output_geometry: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Error calculating line offset')) output_feature.setGeometry(output_geometry) - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/Orthogonalize.py b/python/plugins/processing/algs/qgis/Orthogonalize.py index c9866ea654a..c5d6759b77c 100644 --- a/python/plugins/processing/algs/qgis/Orthogonalize.py +++ b/python/plugins/processing/algs/qgis/Orthogonalize.py @@ -25,21 +25,19 @@ __copyright__ = '(C) 2016, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsApplication, - QgsFeatureSink, - QgsProcessingUtils, - QgsProcessingParameterDefinition) +from qgis.core import (QgsFeatureSink, + QgsProcessingException, + QgsProcessing, + QgsProcessingParameterDefinition, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class Orthogonalize(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' MAX_ITERATIONS = 'MAX_ITERATIONS' DISTANCE_THRESHOLD = 'DISTANCE_THRESHOLD' ANGLE_TOLERANCE = 'ANGLE_TOLERANCE' @@ -54,20 +52,24 @@ class Orthogonalize(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE, - dataobjects.TYPE_VECTOR_POLYGON])) - self.addParameter(ParameterNumber(self.ANGLE_TOLERANCE, - self.tr('Maximum angle tolerance (degrees)'), - 0.0, 45.0, 15.0)) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), + [QgsProcessing.TypeVectorLine, + QgsProcessing.TypeVectorPolygon])) - max_iterations = ParameterNumber(self.MAX_ITERATIONS, - self.tr('Maximum algorithm iterations'), - 1, 10000, 1000) + self.addParameter(QgsProcessingParameterNumber(self.ANGLE_TOLERANCE, + self.tr('Maximum angle tolerance (degrees)'), + type=QgsProcessingParameterNumber.Double, + minValue=0.0, maxValue=45.0, defaultValue=15.0)) + + max_iterations = QgsProcessingParameterNumber(self.MAX_ITERATIONS, + self.tr('Maximum algorithm iterations'), + type=QgsProcessingParameterNumber.Integer, + minValue=1, maxValue=10000, defaultValue=1000) max_iterations.setFlags(max_iterations.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(max_iterations) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Orthogonalized'))) + self.addParameter( + QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Orthogonalized'))) def name(self): return 'orthogonalize' @@ -76,27 +78,31 @@ class Orthogonalize(QgisAlgorithm): return self.tr('Orthogonalize') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) - max_iterations = self.getParameterValue(self.MAX_ITERATIONS) - angle_tolerance = self.getParameterValue(self.ANGLE_TOLERANCE) - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) + source = self.parameterAsSource(parameters, self.INPUT, context) + max_iterations = self.parameterAsInt(parameters, self.MAX_ITERATIONS, context) + angle_tolerance = self.parameterAsDouble(parameters, self.ANGLE_TOLERANCE, context) - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) + + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): + if feedback.isCanceled(): + break + output_feature = input_feature input_geometry = input_feature.geometry() if input_geometry: output_geometry = input_geometry.orthogonalize(1.0e-8, max_iterations, angle_tolerance) if not output_geometry: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Error orthogonalizing geometry')) output_feature.setGeometry(output_geometry) - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/PoleOfInaccessibility.py b/python/plugins/processing/algs/qgis/PoleOfInaccessibility.py index 563c998287f..3fb46ddfbde 100644 --- a/python/plugins/processing/algs/qgis/PoleOfInaccessibility.py +++ b/python/plugins/processing/algs/qgis/PoleOfInaccessibility.py @@ -27,25 +27,29 @@ __revision__ = '$Format:%H$' import os -from qgis.core import QgsWkbTypes, QgsField, NULL, QgsFeatureSink, QgsProcessingUtils +from qgis.core import (QgsWkbTypes, + QgsField, + NULL, + QgsFeatureSink, + QgsProcessing, + QgsProcessingException, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink) from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtGui import QIcon from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class PoleOfInaccessibility(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' + INPUT = 'INPUT' TOLERANCE = 'TOLERANCE' - OUTPUT_LAYER = 'OUTPUT_LAYER' + OUTPUT = 'OUTPUT' def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'centroids.png')) @@ -60,12 +64,15 @@ class PoleOfInaccessibility(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), - [dataobjects.TYPE_VECTOR_POLYGON])) - self.addParameter(ParameterNumber(self.TOLERANCE, - self.tr('Tolerance (layer units)'), default=1.0, minValue=0.0)) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Point'), datatype=[dataobjects.TYPE_VECTOR_POINT])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), + [QgsProcessing.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, + self.tr('Tolerance (layer units)'), + type=QgsProcessingParameterNumber.Double, + defaultValue=1.0, minValue=0.0)) + + self.addParameter( + QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Point'), QgsProcessing.TypeVectorPoint)) def name(self): return 'poleofinaccessibility' @@ -74,25 +81,27 @@ class PoleOfInaccessibility(QgisAlgorithm): return self.tr('Pole of Inaccessibility') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) - tolerance = self.getParameterValue(self.TOLERANCE) + source = self.parameterAsSource(parameters, self.INPUT, context) + tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) - fields = layer.fields() + fields = source.fields() fields.append(QgsField('dist_pole', QVariant.Double)) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Point, source.sourceCrs()) - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) - - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): + if feedback.isCanceled(): + break + output_feature = input_feature input_geometry = input_feature.geometry() if input_geometry: output_geometry, distance = input_geometry.poleOfInaccessibility(tolerance) if not output_geometry: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Error calculating pole of inaccessibility')) attrs = input_feature.attributes() attrs.append(distance) @@ -104,7 +113,7 @@ class PoleOfInaccessibility(QgisAlgorithm): attrs.append(NULL) output_feature.setAttributes(attrs) - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 567b7aa7a61..27ed6fd4b27 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -70,10 +70,13 @@ from .LinesToPolygons import LinesToPolygons from .MeanCoords import MeanCoords from .Merge import Merge from .NearestNeighbourAnalysis import NearestNeighbourAnalysis +from .OffsetLine import OffsetLine +from .Orthogonalize import Orthogonalize from .PointDistance import PointDistance from .PointOnSurface import PointOnSurface from .PointsInPolygon import PointsInPolygon from .PointsLayerFromTable import PointsLayerFromTable +from .PoleOfInaccessibility import PoleOfInaccessibility from .PolygonsToLines import PolygonsToLines from .PostGISExecuteSQL import PostGISExecuteSQL from .RandomExtract import RandomExtract @@ -145,7 +148,6 @@ from .ZonalStatistics import ZonalStatistics # from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable # from .RectanglesOvalsDiamondsFixed import RectanglesOvalsDiamondsFixed # from .MergeLines import MergeLines -# from .OffsetLine import OffsetLine # from .Translate import Translate # from .SingleSidedBuffer import SingleSidedBuffer # from .PointsAlongGeometry import PointsAlongGeometry @@ -156,9 +158,7 @@ from .ZonalStatistics import ZonalStatistics # from .ExtendLines import ExtendLines # from .ExtractSpecificNodes import ExtractSpecificNodes # from .GeometryByExpression import GeometryByExpression -# from .PoleOfInaccessibility import PoleOfInaccessibility # from .RasterCalculator import RasterCalculator -# from .Orthogonalize import Orthogonalize # from .ShortestPathPointToPoint import ShortestPathPointToPoint # from .ShortestPathPointToLayer import ShortestPathPointToLayer # from .ShortestPathLayerToPoint import ShortestPathLayerToPoint @@ -215,17 +215,14 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # ReverseLineDirection(), SpatialIndex(), DefineProjection(), # RectanglesOvalsDiamondsVariable(), # RectanglesOvalsDiamondsFixed(), MergeLines(), - # OffsetLine(), Translate(), + # Translate(), # SingleSidedBuffer(), PointsAlongGeometry(), - # Slope(), Ruggedness(), Hillshade(), # Relief(), # IdwInterpolation(), TinInterpolation(), # RemoveNullGeometry(), # ExtendLines(), ExtractSpecificNodes(), # GeometryByExpression(), - # PoleOfInaccessibility(), - # - # RasterCalculator(), Heatmap(), Orthogonalize(), + # RasterCalculator(), # ShortestPathPointToPoint(), ShortestPathPointToLayer(), # ShortestPathLayerToPoint(), ServiceAreaFromPoint(), # ServiceAreaFromLayer(), TruncateTable(), Polygonize(), @@ -262,10 +259,13 @@ class QGISAlgorithmProvider(QgsProcessingProvider): MeanCoords(), Merge(), NearestNeighbourAnalysis(), + OffsetLine(), + Orthogonalize(), PointDistance(), PointOnSurface(), PointsInPolygon(), PointsLayerFromTable(), + PoleOfInaccessibility(), PolygonsToLines(), PostGISExecuteSQL(), RandomExtract(), diff --git a/python/plugins/processing/tests/testdata/expected/multiline_offset.gml b/python/plugins/processing/tests/testdata/expected/multiline_offset.gml index 05b7f15b8cb..45fd7843520 100644 --- a/python/plugins/processing/tests/testdata/expected/multiline_offset.gml +++ b/python/plugins/processing/tests/testdata/expected/multiline_offset.gml @@ -13,12 +13,12 @@ - -1,0 1,0 + -1,0 1,0 - 3,2 5,26.024038190337471,2.397687750474408 5.999853929301003,0.982908479841009 + 3,2 5,26.02403819033747,2.39768775047441 5.999853929301,0.982908479841009 @@ -27,7 +27,7 @@ - 1,0 1,2 1.01921471959677,2.195090322016128 1.076120467488713,2.38268343236509 1.168530387697455,2.555570233019602 1.292893218813453,2.707106781186547 1.444429766980398,2.831469612302545 1.61731656763491,2.923879532511287 1.804909677983872,2.98078528040323 2,32.915503652020259,5.046801099766013 5.430666799812965,5.1193538828754173.020599614854323,3.999787805420657 5.601021879729563,3.946620818856359 + 1,0 1,2 1.01921471959677,2.19509032201613 1.07612046748871,2.38268343236509 1.16853038769745,2.5555702330196 1.29289321881345,2.70710678118655 1.4444297669804,2.83146961230255 1.61731656763491,2.92387953251129 1.80490967798387,2.98078528040323 2,32.91550365202026,5.04680109976601 5.43066679981296,5.119353882875423.02059961485432,3.99978780542066 5.60102187972956,3.94662081885636 diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 6b7f7a88089..857ba231cca 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -555,96 +555,96 @@ tests: name: expected/point_on_line.gml type: vector -# - algorithm: qgis:offsetline -# name: Offset line positive -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# JOIN_STYLE: '0' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# results: -# OUTPUT_LAYER: -# name: expected/line_offset_round_positive.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:offsetline -# name: Offset line negative -# params: -# DISTANCE: -1.0 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# JOIN_STYLE: '0' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# results: -# OUTPUT_LAYER: -# name: expected/line_offset_round_negative.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:offsetline -# name: Offset line mitre -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# JOIN_STYLE: '1' -# MITRE_LIMIT: 2 -# SEGMENTS: 4 -# results: -# OUTPUT_LAYER: -# name: expected/line_offset_mitre.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:offsetline -# name: Offset line bevel -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# JOIN_STYLE: '2' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# results: -# OUTPUT_LAYER: -# name: expected/line_offset_bevel.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:offsetline -# name: Offset multilines -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: multilines.gml -# type: vector -# JOIN_STYLE: '0' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# results: -# OUTPUT_LAYER: -# name: expected/multiline_offset.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# + - algorithm: qgis:offsetline + name: Offset line positive + params: + DISTANCE: 1.0 + INPUT: + name: lines.gml + type: vector + JOIN_STYLE: '0' + MITRE_LIMIT: 2 + SEGMENTS: 8 + results: + OUTPUT: + name: expected/line_offset_round_positive.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:offsetline + name: Offset line negative + params: + DISTANCE: -1.0 + INPUT: + name: lines.gml + type: vector + JOIN_STYLE: '0' + MITRE_LIMIT: 2 + SEGMENTS: 8 + results: + OUTPUT: + name: expected/line_offset_round_negative.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:offsetline + name: Offset line mitre + params: + DISTANCE: 1.0 + INPUT: + name: lines.gml + type: vector + JOIN_STYLE: '1' + MITRE_LIMIT: 2 + SEGMENTS: 4 + results: + OUTPUT: + name: expected/line_offset_mitre.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:offsetline + name: Offset line bevel + params: + DISTANCE: 1.0 + INPUT: + name: lines.gml + type: vector + JOIN_STYLE: '2' + MITRE_LIMIT: 2 + SEGMENTS: 8 + results: + OUTPUT: + name: expected/line_offset_bevel.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:offsetline + name: Offset multilines + params: + DISTANCE: 1.0 + INPUT: + name: multilines.gml + type: vector + JOIN_STYLE: '0' + MITRE_LIMIT: 2 + SEGMENTS: 8 + results: + OUTPUT: + name: expected/multiline_offset.gml + type: vector + compare: + geometry: + precision: 7 + # - algorithm: qgis:fixeddistancebuffer # name: Buffer polygons using bevel # params: @@ -1481,21 +1481,21 @@ tests: name: expected/snap_internal.gml type: vector -# - algorithm: qgis:poleofinaccessibility -# name: Pole of inaccessibility (polygons) -# params: -# INPUT_LAYER: -# name: polys.gml -# type: vector -# TOLERANCE: 1.0e-05 -# results: -# OUTPUT_LAYER: -# name: expected/pole_inaccessibility_polys.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# + - algorithm: qgis:poleofinaccessibility + name: Pole of inaccessibility (polygons) + params: + INPUT: + name: polys.gml + type: vector + TOLERANCE: 1.0e-05 + results: + OUTPUT: + name: expected/pole_inaccessibility_polys.gml + type: vector + compare: + geometry: + precision: 7 + - algorithm: native:extractbyattribute name: Extract by attribute (is null) params: @@ -1945,34 +1945,34 @@ tests: # compare: # geometry: # precision: 7 -# -# - algorithm: qgis:orthogonalize -# name: Orthogonalize polys -# params: -# INPUT_LAYER: -# name: custom/polys_to_orth.gml -# type: vector -# results: -# OUTPUT_LAYER: -# name: expected/orthagonal_polys.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:orthogonalize -# name: Orthogonalize lines -# params: -# INPUT_LAYER: -# name: custom/lines_to_orth.gml -# type: vector -# results: -# OUTPUT_LAYER: -# name: expected/orthagonal_lines.gml -# type: vector -# compare: -# geometry: -# precision: 7 + + - algorithm: qgis:orthogonalize + name: Orthogonalize polys + params: + INPUT: + name: custom/polys_to_orth.gml + type: vector + results: + OUTPUT: + name: expected/orthagonal_polys.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:orthogonalize + name: Orthogonalize lines + params: + INPUT: + name: custom/lines_to_orth.gml + type: vector + results: + OUTPUT: + name: expected/orthagonal_lines.gml + type: vector + compare: + geometry: + precision: 7 - algorithm: native:reprojectlayer From 6487fbb2d1e85b9f12b0046e386eea8db12e5836 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 16 Jul 2017 20:48:05 +1000 Subject: [PATCH 2/3] Port Reverse Line Direction to new API, add test --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../algs/qgis/ReverseLineDirection.py | 53 +++++++++---------- .../testdata/expected/lines_reversed.gfs | 16 ++++++ .../testdata/expected/lines_reversed.gml | 48 +++++++++++++++++ .../tests/testdata/qgis_algorithm_tests.yaml | 14 +++++ 5 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/lines_reversed.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/lines_reversed.gml diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 27ed6fd4b27..7f28a47995f 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -82,6 +82,7 @@ from .PostGISExecuteSQL import PostGISExecuteSQL from .RandomExtract import RandomExtract from .RandomExtractWithinSubsets import RandomExtractWithinSubsets from .RegularPoints import RegularPoints +from .ReverseLineDirection import ReverseLineDirection from .Ruggedness import Ruggedness from .SaveSelectedFeatures import SaveSelectedFeatures from .SelectByAttribute import SelectByAttribute @@ -142,7 +143,6 @@ from .ZonalStatistics import ZonalStatistics # from .FieldsMapper import FieldsMapper # from .Datasources2Vrt import Datasources2Vrt # from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox -# from .ReverseLineDirection import ReverseLineDirection # from .SpatialIndex import SpatialIndex # from .DefineProjection import DefineProjection # from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable @@ -212,7 +212,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # SplitWithLines(), CreateConstantRaster(), # FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(), # OrientedMinimumBoundingBox(), - # ReverseLineDirection(), SpatialIndex(), DefineProjection(), + # SpatialIndex(), DefineProjection(), # RectanglesOvalsDiamondsVariable(), # RectanglesOvalsDiamondsFixed(), MergeLines(), # Translate(), @@ -271,6 +271,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): RandomExtract(), RandomExtractWithinSubsets(), RegularPoints(), + ReverseLineDirection(), Ruggedness(), SaveSelectedFeatures(), SelectByAttribute(), diff --git a/python/plugins/processing/algs/qgis/ReverseLineDirection.py b/python/plugins/processing/algs/qgis/ReverseLineDirection.py index 94223af6c59..b8ed34524c0 100644 --- a/python/plugins/processing/algs/qgis/ReverseLineDirection.py +++ b/python/plugins/processing/algs/qgis/ReverseLineDirection.py @@ -25,22 +25,20 @@ __copyright__ = '(C) 2015, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsApplication, - QgsGeometry, +from qgis.core import (QgsGeometry, QgsFeature, QgsFeatureSink, - QgsProcessingUtils) + QgsProcessingException, + QgsProcessing, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class ReverseLineDirection(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' def group(self): return self.tr('Vector geometry tools') @@ -49,9 +47,10 @@ class ReverseLineDirection(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Reversed'), datatype=[dataobjects.TYPE_VECTOR_LINE])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), + [QgsProcessing.TypeVectorLine])) + self.addParameter( + QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Reversed'), QgsProcessing.TypeVectorLine)) def name(self): return 'reverselinedirection' @@ -60,30 +59,28 @@ class ReverseLineDirection(QgisAlgorithm): return self.tr('Reverse line direction') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + source = self.parameterAsSource(parameters, self.INPUT, context) - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - outFeat = QgsFeature() - - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): - inGeom = inFeat.geometry() - attrs = inFeat.attributes() + if feedback.isCanceled(): + break - outGeom = None - if not inGeom.isNull(): + outFeat = inFeat + if inFeat.geometry(): + inGeom = inFeat.geometry() reversedLine = inGeom.geometry().reversed() if not reversedLine: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Error reversing line')) outGeom = QgsGeometry(reversedLine) - outFeat.setGeometry(outGeom) - outFeat.setAttributes(attrs) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + outFeat.setGeometry(outGeom) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/tests/testdata/expected/lines_reversed.gfs b/python/plugins/processing/tests/testdata/expected/lines_reversed.gfs new file mode 100644 index 00000000000..db180982a6c --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/lines_reversed.gfs @@ -0,0 +1,16 @@ + + + lines_reversed + lines_reversed + + 2 + EPSG:4326 + + 7 + -1.00000 + 11.00000 + -3.00000 + 5.00000 + + + diff --git a/python/plugins/processing/tests/testdata/expected/lines_reversed.gml b/python/plugins/processing/tests/testdata/expected/lines_reversed.gml new file mode 100644 index 00000000000..a2f9f2b09af --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/lines_reversed.gml @@ -0,0 +1,48 @@ + + + + + -1-3 + 115 + + + + + + 11,5 9,3 9,2 6,2 + + + + + 1,-1 -1,-1 + + + + + 3,3 3,2 2,2 2,0 + + + + + 5,1 3,1 + + + + + 10,-3 7,-3 + + + + + 10,1 6,-3 + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 857ba231cca..85ca783c8dc 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -555,6 +555,20 @@ tests: name: expected/point_on_line.gml type: vector + - algorithm: qgis:reverselinedirection + name: Reverse line direction + params: + INPUT: + name: lines.gml + type: vector + results: + OUTPUT: + name: expected/lines_reversed.gml + type: vector + compare: + geometry: + precision: 7 + - algorithm: qgis:offsetline name: Offset line positive params: From 38a13ff5af23e8c56df656a880b4ccf4a584362d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 07:20:03 +1000 Subject: [PATCH 3/3] Make pole of inaccessibility calculation handle multipolygons --- .../geometry/qgsinternalgeometryengine.cpp | 84 ++++++++++++++----- tests/src/core/testqgsgeometry.cpp | 7 ++ 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/core/geometry/qgsinternalgeometryengine.cpp b/src/core/geometry/qgsinternalgeometryengine.cpp index 400c0952172..cd93aa0d210 100644 --- a/src/core/geometry/qgsinternalgeometryengine.cpp +++ b/src/core/geometry/qgsinternalgeometryengine.cpp @@ -155,25 +155,10 @@ Cell *getCentroidCell( const QgsPolygonV2 *polygon ) return new Cell( x / area, y / area, 0.0, polygon ); } -///@endcond - -QgsGeometry QgsInternalGeometryEngine::poleOfInaccessibility( double precision, double *distanceFromBoundary ) const +QgsPoint surfacePoleOfInaccessibility( const QgsSurface *surface, double precision, double &distanceFromBoundary ) { - if ( distanceFromBoundary ) - *distanceFromBoundary = DBL_MAX; - - if ( !mGeometry || mGeometry->isEmpty() ) - return QgsGeometry(); - - if ( precision <= 0 ) - return QgsGeometry(); - - const QgsSurface *surface = dynamic_cast< const QgsSurface * >( mGeometry ); - if ( !surface ) - return QgsGeometry(); - std::unique_ptr< QgsPolygonV2 > segmentizedPoly; - const QgsPolygonV2 *polygon = dynamic_cast< const QgsPolygonV2 * >( mGeometry ); + const QgsPolygonV2 *polygon = dynamic_cast< const QgsPolygonV2 * >( surface ); if ( !polygon ) { segmentizedPoly.reset( static_cast< QgsPolygonV2 *>( surface->segmentize() ) ); @@ -187,7 +172,7 @@ QgsGeometry QgsInternalGeometryEngine::poleOfInaccessibility( double precision, double cellSize = qMin( bounds.width(), bounds.height() ); if ( qgsDoubleNear( cellSize, 0.0 ) ) - return QgsGeometry( new QgsPoint( bounds.xMinimum(), bounds.yMinimum() ) ); + return QgsPoint( bounds.xMinimum(), bounds.yMinimum() ); double h = cellSize / 2.0; std::priority_queue< Cell *, std::vector, GreaterThanByMax > cellQueue; @@ -238,15 +223,70 @@ QgsGeometry QgsInternalGeometryEngine::poleOfInaccessibility( double precision, cellQueue.push( new Cell( currentCell->x + h, currentCell->y + h, h, polygon ) ); } - if ( distanceFromBoundary ) - *distanceFromBoundary = bestCell->d; + distanceFromBoundary = bestCell->d; - return QgsGeometry( new QgsPoint( bestCell->x, bestCell->y ) ); + return QgsPoint( bestCell->x, bestCell->y ); +} + +///@endcond + +QgsGeometry QgsInternalGeometryEngine::poleOfInaccessibility( double precision, double *distanceFromBoundary ) const +{ + if ( distanceFromBoundary ) + *distanceFromBoundary = DBL_MAX; + + if ( !mGeometry || mGeometry->isEmpty() ) + return QgsGeometry(); + + if ( precision <= 0 ) + return QgsGeometry(); + + if ( const QgsGeometryCollection *gc = dynamic_cast< const QgsGeometryCollection *>( mGeometry ) ) + { + int numGeom = gc->numGeometries(); + double maxDist = 0; + QgsPoint bestPoint; + bool found = false; + for ( int i = 0; i < numGeom; ++i ) + { + const QgsSurface *surface = dynamic_cast< const QgsSurface * >( gc->geometryN( i ) ); + if ( !surface ) + continue; + + found = true; + double dist = DBL_MAX; + QgsPoint p = surfacePoleOfInaccessibility( surface, precision, dist ); + if ( dist > maxDist ) + { + maxDist = dist; + bestPoint = p; + } + } + + if ( !found ) + return QgsGeometry(); + + if ( distanceFromBoundary ) + *distanceFromBoundary = maxDist; + return QgsGeometry( new QgsPoint( bestPoint ) ); + } + else + { + const QgsSurface *surface = dynamic_cast< const QgsSurface * >( mGeometry ); + if ( !surface ) + return QgsGeometry(); + + double dist = DBL_MAX; + QgsPoint p = surfacePoleOfInaccessibility( surface, precision, dist ); + if ( distanceFromBoundary ) + *distanceFromBoundary = dist; + return QgsGeometry( new QgsPoint( p ) ); + } } // helpers for orthogonalize -// adapted from original code in potlach/id osm editor +// adapted from original code in potlatch/id osm editor bool dotProductWithinAngleTolerance( double dotProduct, double lowerThreshold, double upperThreshold ) { diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 773a5f7317e..485093e642d 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -5451,6 +5451,13 @@ void TestQgsGeometry::poleOfInaccessibility() point = curved.poleOfInaccessibility( 0.01 ).asPoint(); QGSCOMPARENEAR( point.x(), -0.4324, 0.0001 ); QGSCOMPARENEAR( point.y(), -0.2434, 0.0001 ); + + // multipolygon + QgsGeometry multiPoly = QgsGeometry::fromWkt( QStringLiteral( "MultiPolygon (((0 0, 10 0, 10 10, 0 10, 0 0)),((30 30, 50 30, 50 60, 30 60, 30 30)))" ) ); + point = multiPoly.poleOfInaccessibility( 0.01, &distance ).asPoint(); + QGSCOMPARENEAR( point.x(), 40, 0.0001 ); + QGSCOMPARENEAR( point.y(), 45, 0.0001 ); + QGSCOMPARENEAR( distance, 10.0, 0.00001 ); } void TestQgsGeometry::makeValid()