From f30fcca5ece7019d83d2b385f954a0335c84e7c6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 12 Jun 2019 09:11:16 +1000 Subject: [PATCH] [processing] Port "Points along geometry" to QgsFeatureBasedAlgorithm (and c++), allow distance/start/end offset to be dynamic, avoid algorithm "hangs" when inappropriately small (or 0) distances are used --- python/plugins/processing/algs/help/qgis.yaml | 5 - .../algs/qgis/PointsAlongGeometry.py | 142 ---- .../algs/qgis/QgisAlgorithmProvider.py | 2 - .../points_along_lines_end_offset.gml | 104 +++ .../points_along_lines_end_offset.xsd | 35 + .../expected/points_along_lines_m.dbf | Bin 0 -> 8917 bytes .../expected/points_along_lines_m.prj | 1 + .../expected/points_along_lines_m.qpj | 1 + .../expected/points_along_lines_m.shp | Bin 0 -> 1120 bytes .../expected/points_along_lines_m.shx | Bin 0 -> 332 bytes .../points_along_lines_start_offset.gml | 146 ++++ .../points_along_lines_start_offset.xsd | 35 + .../expected/points_along_lines_z.dbf | Bin 0 -> 1782 bytes .../expected/points_along_lines_z.prj | 1 + .../expected/points_along_lines_z.qpj | 1 + .../expected/points_along_lines_z.shp | Bin 0 -> 1108 bytes .../expected/points_along_lines_z.shx | Bin 0 -> 324 bytes .../testdata/expected/points_along_polys.gml | 695 ++++++++++++++++++ .../testdata/expected/points_along_polys.xsd | 55 ++ .../tests/testdata/qgis_algorithm_tests2.yaml | 72 +- src/analysis/CMakeLists.txt | 1 + .../qgsalgorithmpointsalonggeometry.cpp | 221 ++++++ .../qgsalgorithmpointsalonggeometry.h | 81 ++ .../processing/qgsnativealgorithms.cpp | 2 + 24 files changed, 1450 insertions(+), 150 deletions(-) delete mode 100644 python/plugins/processing/algs/qgis/PointsAlongGeometry.py create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.gml create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.xsd create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_m.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_m.prj create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_m.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_m.shp create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_m.shx create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.gml create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.xsd create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_z.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_z.prj create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_z.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_z.shp create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_lines_z.shx create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_polys.gml create mode 100644 python/plugins/processing/tests/testdata/expected/points_along_polys.xsd create mode 100644 src/analysis/processing/qgsalgorithmpointsalonggeometry.cpp create mode 100644 src/analysis/processing/qgsalgorithmpointsalonggeometry.h diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index 628466c7a5e8..4a1fe300697e 100644 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -94,11 +94,6 @@ qgis:creategridlines: > The top-left point (minX, maxY) is used as the reference point. That means that, at that point, an element is guaranteed to be placed. Unless the width and height of the selected extent is a multiple of the selected spacing, that is not true for the other points that define that extent. -qgis:createpointsalonglines: > - This algorithm creates a points layer, with points distributed along the lines of an input vector layer. the distance between points (measured along the line) is defined as a parameter. - - Start and end points can be defined, so the first and last point do not fall on the line first and last node. Start and end points are defined as distances, measured from the first and last nodes of the lines, in the units of the projection used by the lines layer. - qgis:createspatialindex: > Creates an index to speed up access to the features in a layer based on their spatial location. Support for spatial index creation is dependent on the layer's data provider. diff --git a/python/plugins/processing/algs/qgis/PointsAlongGeometry.py b/python/plugins/processing/algs/qgis/PointsAlongGeometry.py deleted file mode 100644 index 849e133e5c79..000000000000 --- a/python/plugins/processing/algs/qgis/PointsAlongGeometry.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - PointsAlongGeometry.py - --------------------- - Date : August 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__ = 'August 2016' -__copyright__ = '(C) 2016, Nyall Dawson' - -import os -import math - -from qgis.PyQt.QtCore import QVariant -from qgis.PyQt.QtGui import QIcon - -from qgis.core import (QgsApplication, - QgsFeature, - QgsFeatureSink, - QgsWkbTypes, - QgsField, - QgsProcessing, - QgsProcessingException, - QgsProcessingUtils, - QgsProcessingParameterDistance, - QgsProcessingParameterNumber, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class PointsAlongGeometry(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' - DISTANCE = 'DISTANCE' - START_OFFSET = 'START_OFFSET' - END_OFFSET = 'END_OFFSET' - - def icon(self): - return QgsApplication.getThemeIcon("/algorithms/mAlgorithmExtractVertices.svg") - - def svgIconPath(self): - return QgsApplication.iconPath("/algorithms/mAlgorithmExtractVertices.svg") - - def tags(self): - return self.tr('create,interpolate,points,lines,regular,distance,by').split(',') - - 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.TypeVectorPolygon, QgsProcessing.TypeVectorLine])) - self.addParameter(QgsProcessingParameterDistance(self.DISTANCE, - self.tr('Distance'), parentParameterName=self.INPUT, minValue=0.0, defaultValue=1.0)) - self.addParameter(QgsProcessingParameterDistance(self.START_OFFSET, - self.tr('Start offset'), parentParameterName=self.INPUT, minValue=0.0, defaultValue=0.0)) - self.addParameter(QgsProcessingParameterDistance(self.END_OFFSET, - self.tr('End offset'), parentParameterName=self.INPUT, minValue=0.0, defaultValue=0.0)) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Points'), QgsProcessing.TypeVectorPoint)) - - def name(self): - return 'pointsalonglines' - - def displayName(self): - return self.tr('Points along geometry') - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - if source is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) - - distance = self.parameterAsDouble(parameters, self.DISTANCE, context) - start_offset = self.parameterAsDouble(parameters, self.START_OFFSET, context) - end_offset = self.parameterAsDouble(parameters, self.END_OFFSET, context) - - fields = source.fields() - fields.append(QgsField('distance', QVariant.Double)) - fields.append(QgsField('angle', QVariant.Double)) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) - if sink is None: - raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - - input_geometry = input_feature.geometry() - if not input_geometry: - sink.addFeature(input_feature, QgsFeatureSink.FastInsert) - else: - if input_geometry.type == QgsWkbTypes.PolygonGeometry: - length = input_geometry.constGet().perimeter() - else: - length = input_geometry.length() - end_offset - current_distance = start_offset - - while current_distance <= length: - point = input_geometry.interpolate(current_distance) - angle = math.degrees(input_geometry.interpolateAngle(current_distance)) - - output_feature = QgsFeature() - output_feature.setGeometry(point) - attrs = input_feature.attributes() - attrs.append(current_distance) - attrs.append(angle) - output_feature.setAttributes(attrs) - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - - current_distance += distance - - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py index e8f821c9c1ef..4bad20e573f7 100644 --- a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py @@ -84,7 +84,6 @@ from .NearestNeighbourAnalysis import NearestNeighbourAnalysis from .Orthogonalize import Orthogonalize from .PointDistance import PointDistance -from .PointsAlongGeometry import PointsAlongGeometry from .PointsDisplacement import PointsDisplacement from .PointsFromLines import PointsFromLines from .PointsFromPolygons import PointsFromPolygons @@ -195,7 +194,6 @@ def getAlgs(self): NearestNeighbourAnalysis(), Orthogonalize(), PointDistance(), - PointsAlongGeometry(), PointsDisplacement(), PointsFromLines(), PointsFromPolygons(), diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.gml b/python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.gml new file mode 100644 index 000000000000..9f58fb751216 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.gml @@ -0,0 +1,104 @@ + + + + + 2-3 + 93 + + + + + + 6,2 + 0 + 90 + + + + + 7,2 + 1 + 90 + + + + + 8,2 + 2 + 90 + + + + + 9,2 + 3 + 45 + + + + + 9,3 + 4 + 22.5 + + + + + 2,0 + 0 + 0 + + + + + 2,1 + 1 + 0 + + + + + 7,-3 + 0 + 90 + + + + + 6,-3 + 0 + 45 + + + + + 6.70710678118655,-2.29289321881345 + 1 + 45 + + + + + 7.41421356237309,-1.58578643762691 + 2 + 45 + + + + + 8.12132034355964,-0.878679656440358 + 3 + 45 + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.xsd b/python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.xsd new file mode 100644 index 000000000000..e1c81dc7dc65 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_lines_end_offset.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_m.dbf b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.dbf new file mode 100644 index 0000000000000000000000000000000000000000..fbd692b01fc052197696c0bf022e8abd05614d70 GIT binary patch literal 8917 zcmeI1K?{OF5QUAPOGMWQ0?!>xO@+?g`wx*8f_O3L+#iqDO%FBm4s&g053atAh5eZK z9BKNP%{M_1JcHfDO=Mi_a@3Cf&1-xb=CbbY=k`*$$5St@mczm@o!eW}FS^05FT$p7 zt1gN|)gzAL@A08=pt|95XcEIEu}f;WB)IlGt}G|InbC302o?`JO{4rL(+rm*L#xQ+ zvg+~}$fe+`3*t)14ypv&F!cRe6sb7ML-l`sdB492YR7;Ub0Q z1z<+_W>j8X(ym@PF3{COGw%5!)uPf!_a}#amvUSn>J1m~7ZCLjSH^FnMU|RSIWDOg zmE$rqigE<>T`;?H(e8iuV~}02t5=Q-WS8OMjRCUDaPhc6b{Q@n7x-p0Ts$u6KS2e* L&EKnA;aI-`1)ReN literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_m.prj b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.prj new file mode 100644 index 000000000000..a30c00a55de1 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_m.qpj b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.qpj new file mode 100644 index 000000000000..5fbc831e7433 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_m.shp b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.shp new file mode 100644 index 0000000000000000000000000000000000000000..05a649c364f1bafc9a6f71603907eeb201dd1cfe GIT binary patch literal 1120 zcmZvbO(;ZB6vwae^#+46%#4rmH4|k;#6rv|3k$Nt5-CbqDGOOl7B<4h!bsU#DhpA1 zlPGE;MUe%PuP9GbN-55{?_JL5{p-AU&w0Oh?s?~)Cq!+Qu$80!DOHG~-)DwyF16Uy z<4N3e7J|g_TC{fE4Zn!BSdNm8!j?YY~ zK%WGCy`*zu($H9*YCxZ4?R7~^I+_imL8H_iF&T>4O^iM`$AZ@Px?rhdqHS{Wo|sI< z{O`#}_Gahy_9HQxV(^YqeN=Y=7<8hsdeD$ac z@|_%e#t55xJyTwnJtKI3a+4Vi&?oUe=b_K)QH*9Wa<6=QM%5$F%bm>R1Ntb{gBz;3 z0FCWc2NsJc*j0ph|{}>+z7^D6R zEoRo7pzrc4#+NpMH}xKWFV0<4o-)O3>}5t8 a3WD%eH!b|Z+i=g;LL6GphzX!M(8LehOS*^v literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_m.shx b/python/plugins/processing/tests/testdata/expected/points_along_lines_m.shx new file mode 100644 index 0000000000000000000000000000000000000000..c5238c144cc8f3e34c010238f52afcfea005ab28 GIT binary patch literal 332 zcmZQzQ0HR64wk)OW?&G7%YE1nVR9U(dVc oN~b{SGAP{vrDs9uHBfpVl)eC^A3^Ca5Sob%N=ra#Eg;PV0Jwo6U;qFB literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.gml b/python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.gml new file mode 100644 index 000000000000..c20dfc33ecc2 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.gml @@ -0,0 +1,146 @@ + + + + + 0.5-3 + 10.767766952966374.767766952966369 + + + + + + 7.5,2.0 + 1.5 + 90 + + + + + 8.5,2.0 + 2.5 + 90 + + + + + 9.0,2.5 + 3.5 + 0 + + + + + 9.35355339059327,3.35355339059327 + 4.5 + 45 + + + + + 10.0606601717798,4.06066017177982 + 5.5 + 45 + + + + + 10.7677669529664,4.76776695296637 + 6.5 + 45 + + + + + 0.5,-1.0 + 1.5 + 90 + + + + + 2.0,1.5 + 1.5 + 0 + + + + + 2.5,2.0 + 2.5 + 90 + + + + + 3.0,2.5 + 3.5 + 0 + + + + + 4.5,1.0 + 1.5 + 90 + + + + + 8.5,-3.0 + 1.5 + 90 + + + + + 9.5,-3.0 + 2.5 + 90 + + + + + 7.06066017177982,-1.93933982822018 + 1.5 + 45 + + + + + 7.76776695296637,-1.23223304703363 + 2.5 + 45 + + + + + 8.47487373415292,-0.525126265847084 + 3.5 + 45 + + + + + 9.18198051533946,0.181980515339464 + 4.5 + 45 + + + + + 9.88908729652601,0.889087296526011 + 5.5 + 45 + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.xsd b/python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.xsd new file mode 100644 index 000000000000..ebcf1e0e7395 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_lines_start_offset.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/points_along_lines_z.dbf b/python/plugins/processing/tests/testdata/expected/points_along_lines_z.dbf new file mode 100644 index 0000000000000000000000000000000000000000..1d77d744b16c1babd5ca642ae31a2ba93fa2fcfc GIT binary patch literal 1782 zcmbu9!3u&v5QZ0m4n=g0=-?BGuDe3?4!wh9g%Dl}I``(eFP1(c{&f)ce#P@+}|>f$v?P(lt4EqH~wX#z?pEI|o{ zC8$DSBupr5ZR%rRR8R=$9}1V#`|9+~0jg*YP(^b*pK!wbn}8AtOHd+VZv639#WVpW Wrin|q*16Nom*LjvIf`VWrSR59?!ba4_!iZpN zDOiYtydVk+!9!3ux#AS@<52KPKoA6-*?p7j9`6ss+u8Tqnc0~Yq9tE=%GY|IEkr^3 zpJS_~mH2u0D_M3Hfz-)*yk#Q*D{<0s)I}kEB(uoEWl<(poy%AWg*bH9ho&)&p#B*Q zR-`i_FDA!iB3{fVlL@AocBvHA^O+fS-x~0@4UhdE^d)xtM#mb4WX-wEs9w}V$3ZMU zGW2T4j+)!6Djz70#Z2d)uC4m7jj+}~n~Z#Uv=?6I;!l$|#N;uf;xqw#D_p65IkY#&W?bNoV153b%1L@ zFpPjqJ|K31(qT|K14>sx=^iLO4@z%<(ubh*6)61-O8(t1$Z0ZIo! k=@ckk2BkZo^eia721@UP(ifogBPjg^LNl>JX$cSw0PDsfBme*a literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/points_along_polys.gml b/python/plugins/processing/tests/testdata/expected/points_along_polys.gml new file mode 100644 index 000000000000..d1665e6470b1 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_polys.gml @@ -0,0 +1,695 @@ + + + + + -1-3 + 106 + + + + + + -1,-1 + aaaaa + 33 + 44.123456 + 0 + 315 + + + + + -1,0 + aaaaa + 33 + 44.123456 + 1 + 0 + + + + + -1,1 + aaaaa + 33 + 44.123456 + 2 + 0 + + + + + -1,2 + aaaaa + 33 + 44.123456 + 3 + 0 + + + + + -1,3 + aaaaa + 33 + 44.123456 + 4 + 45 + + + + + 0,3 + aaaaa + 33 + 44.123456 + 5 + 90 + + + + + 1,3 + aaaaa + 33 + 44.123456 + 6 + 90 + + + + + 2,3 + aaaaa + 33 + 44.123456 + 7 + 90 + + + + + 3,3 + aaaaa + 33 + 44.123456 + 8 + 135 + + + + + 3,2 + aaaaa + 33 + 44.123456 + 9 + 225 + + + + + 2,2 + aaaaa + 33 + 44.123456 + 10 + 225 + + + + + 2,1 + aaaaa + 33 + 44.123456 + 11 + 180 + + + + + 2,0 + aaaaa + 33 + 44.123456 + 12 + 180 + + + + + 2,-1 + aaaaa + 33 + 44.123456 + 13 + 225 + + + + + 1,-1 + aaaaa + 33 + 44.123456 + 14 + 270 + + + + + 0,-1 + aaaaa + 33 + 44.123456 + 15 + 270 + + + + + -1,-1 + aaaaa + 33 + 44.123456 + 16 + 315 + + + + + 5,5 + Aaaaa + -33 + 0 + 0 + 90 + + + + + 5.70710678118655,4.29289321881345 + Aaaaa + -33 + 0 + 1 + 135 + + + + + 5.41421356237309,4.0 + Aaaaa + -33 + 0 + 2 + 270 + + + + + 4.41421356237309,4.0 + Aaaaa + -33 + 0 + 3 + 270 + + + + + 4.41421356237309,4.41421356237309 + Aaaaa + -33 + 0 + 4 + 45 + + + + + 2,5 + bbaaa + + 0.123 + 0 + 315 + + + + + 2,6 + bbaaa + + 0.123 + 1 + 45 + + + + + 3,6 + bbaaa + + 0.123 + 2 + 135 + + + + + 3,5 + bbaaa + + 0.123 + 3 + 225 + + + + + 2,5 + bbaaa + + 0.123 + 4 + 315 + + + + + 6,1 + ASDF + 0 + + 0 + 45 + + + + + 7,1 + ASDF + 0 + + 1 + 90 + + + + + 8,1 + ASDF + 0 + + 2 + 90 + + + + + 9,1 + ASDF + 0 + + 3 + 90 + + + + + 10,1 + ASDF + 0 + + 4 + 135 + + + + + 10,0 + ASDF + 0 + + 5 + 180 + + + + + 10,-1 + ASDF + 0 + + 6 + 180 + + + + + 10,-2 + ASDF + 0 + + 7 + 180 + + + + + 10,-3 + ASDF + 0 + + 8 + 225 + + + + + 9,-3 + ASDF + 0 + + 9 + 270 + + + + + 8,-3 + ASDF + 0 + + 10 + 270 + + + + + 7,-3 + ASDF + 0 + + 11 + 270 + + + + + 6,-3 + ASDF + 0 + + 12 + 315 + + + + + 6,-2 + ASDF + 0 + + 13 + 0 + + + + + 6,-1 + ASDF + 0 + + 14 + 0 + + + + + 6,0 + ASDF + 0 + + 15 + 0 + + + + + 6,1 + ASDF + 0 + + 16 + 45 + + + + + ASDF + 0 + + 17 + 135 + + + + + ASDF + 0 + + 18 + 180 + + + + + ASDF + 0 + + 19 + 180 + + + + + ASDF + 0 + + 20 + 90 + + + + + ASDF + 0 + + 21 + 90 + + + + + ASDF + 0 + + 22 + 0 + + + + + ASDF + 0 + + 23 + 0 + + + + + ASDF + 0 + + 24 + 270 + + + + + + 120 + -100291.43213 + + + + + + + 3,2 + elim + 2 + 3.33 + 0 + 99.217474411461 + + + + + 3.94868329805051,1.68377223398316 + elim + 2 + 3.33 + 1 + 108.434948822922 + + + + + 4.89736659610103,1.36754446796632 + elim + 2 + 3.33 + 2 + 108.434948822922 + + + + + 5.84604989415154,1.05131670194949 + elim + 2 + 3.33 + 3 + 108.434948822922 + + + + + 6.0,0.16227766016838 + elim + 2 + 3.33 + 4 + 180 + + + + + 6.0,-0.83772233983162 + elim + 2 + 3.33 + 5 + 180 + + + + + 6.0,-1.83772233983162 + elim + 2 + 3.33 + 6 + 180 + + + + + 6.0,-2.83772233983162 + elim + 2 + 3.33 + 7 + 180 + + + + + 5.25071836074653,-2.62535918037326 + elim + 2 + 3.33 + 8 + 296.565051177078 + + + + + 4.35629116974661,-2.17814558487331 + elim + 2 + 3.33 + 9 + 296.565051177078 + + + + + 3.4618639787467,-1.73093198937335 + elim + 2 + 3.33 + 10 + 296.565051177078 + + + + + 2.56743678774678,-1.28371839387339 + elim + 2 + 3.33 + 11 + 296.565051177078 + + + + + 2.0,-0.63441361516796 + elim + 2 + 3.33 + 12 + 0 + + + + + 2.0,0.36558638483204 + elim + 2 + 3.33 + 13 + 0 + + + + + 2.0,1.36558638483204 + elim + 2 + 3.33 + 14 + 0 + + + + + 2.36558638483204,2.0 + elim + 2 + 3.33 + 15 + 90 + + + diff --git a/python/plugins/processing/tests/testdata/expected/points_along_polys.xsd b/python/plugins/processing/tests/testdata/expected/points_along_polys.xsd new file mode 100644 index 000000000000..33b69f5eb7a3 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/points_along_polys.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml index 32455c577d5e..1c38af1b8240 100755 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml @@ -1267,7 +1267,7 @@ tests: compare: geometry: precision: 6 - + - algorithm: qgis:pointstopath name: Points to path (non grouped) params: @@ -1819,5 +1819,75 @@ tests: name: expected/points_along_lines.gml type: vector + - algorithm: native:pointsalonglines + name: Points along lines with start offset + params: + DISTANCE: 1.0 + END_OFFSET: 0.0 + INPUT: + name: lines.gml|layername=lines + type: vector + START_OFFSET: 1.5 + results: + OUTPUT: + name: expected/points_along_lines_start_offset.gml + type: vector + + - algorithm: native:pointsalonglines + name: Points along lines with end offset + params: + DISTANCE: 1.0 + END_OFFSET: 2.2 + INPUT: + name: lines.gml|layername=lines + type: vector + START_OFFSET: 0.0 + results: + OUTPUT: + name: expected/points_along_lines_end_offset.gml + type: vector + + - algorithm: native:pointsalonglines + name: Points along polygons + params: + DISTANCE: 1.0 + END_OFFSET: 0.0 + INPUT: + name: polys.gml|layername=polys2 + type: vector + START_OFFSET: 0.0 + results: + OUTPUT: + name: expected/points_along_polys.gml + type: vector + + - algorithm: native:pointsalonglines + name: Points along lines with z + params: + DISTANCE: 1.0 + END_OFFSET: 0.0 + INPUT: + name: lines_z.shp + type: vector + START_OFFSET: 0.0 + results: + OUTPUT: + name: expected/points_along_lines_z.shp + type: vector + + - algorithm: native:pointsalonglines + name: Points along lines with m + params: + DISTANCE: 1.0 + END_OFFSET: 0.0 + INPUT: + name: lines_m.shp + type: vector + START_OFFSET: 0.0 + results: + OUTPUT: + name: expected/points_along_lines_m.shp + type: vector + # See ../README.md for a description of the file format diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 60935fa0f6c4..adcdf05e935d 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -80,6 +80,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmarrayoffsetlines.cpp processing/qgsalgorithmpolygonstolines.cpp processing/qgsalgorithmpointonsurface.cpp + processing/qgsalgorithmpointsalonggeometry.cpp processing/qgsalgorithmprojectpointcartesian.cpp processing/qgsalgorithmpromotetomultipart.cpp processing/qgsalgorithmrasterlayeruniquevalues.cpp diff --git a/src/analysis/processing/qgsalgorithmpointsalonggeometry.cpp b/src/analysis/processing/qgsalgorithmpointsalonggeometry.cpp new file mode 100644 index 000000000000..c36f00fb3415 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmpointsalonggeometry.cpp @@ -0,0 +1,221 @@ +/*************************************************************************** + qgsalgorithmpointsalonggeometry.cpp + --------------------- + begin : June 2019 + copyright : (C) 2019 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. * + * * + ***************************************************************************/ + +#include "qgsalgorithmpointsalonggeometry.h" +#include "qgsgeometrycollection.h" +#include "qgscurve.h" +#include "qgsapplication.h" + +///@cond PRIVATE + +QString QgsPointsAlongGeometryAlgorithm::name() const +{ + return QStringLiteral( "pointsalonglines" ); +} + +QString QgsPointsAlongGeometryAlgorithm::displayName() const +{ + return QObject::tr( "Points along geometry" ); +} + +QStringList QgsPointsAlongGeometryAlgorithm::tags() const +{ + return QObject::tr( "create,interpolate,points,lines,regular,distance,by" ).split( ',' ); +} + +QString QgsPointsAlongGeometryAlgorithm::group() const +{ + return QObject::tr( "Vector geometry" ); +} + +QString QgsPointsAlongGeometryAlgorithm::groupId() const +{ + return QStringLiteral( "vectorgeometry" ); +} + +QString QgsPointsAlongGeometryAlgorithm::outputName() const +{ + return QObject::tr( "Interpolated points" ); +} + +QString QgsPointsAlongGeometryAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm creates a points layer, with points distributed along the lines of an " + "input vector layer. The distance between points (measured along the line) is defined as a parameter.\n\n" + "Start and end offset distances can be defined, so the first and last point will not fall exactly on the line's " + "first and last nodes. These start and end offsets are defined as distances, measured along the line from the first and last " + "nodes of the lines." ); +} + +QString QgsPointsAlongGeometryAlgorithm::shortDescription() const +{ + return QObject::tr( "Creates regularly spaced points along line features." ); +} + +QList QgsPointsAlongGeometryAlgorithm::inputLayerTypes() const +{ + return QList() << QgsProcessing::TypeVectorLine << QgsProcessing::TypeVectorPolygon; +} + +QgsProcessing::SourceType QgsPointsAlongGeometryAlgorithm::outputLayerType() const +{ + return QgsProcessing::TypeVectorPoint; +} + +QgsWkbTypes::Type QgsPointsAlongGeometryAlgorithm::outputWkbType( QgsWkbTypes::Type inputType ) const +{ + QgsWkbTypes::Type out = QgsWkbTypes::Point; + if ( QgsWkbTypes::hasZ( inputType ) ) + out = QgsWkbTypes::addZ( out ); + if ( QgsWkbTypes::hasM( inputType ) ) + out = QgsWkbTypes::addM( out ); + return out; +} + +QgsFields QgsPointsAlongGeometryAlgorithm::outputFields( const QgsFields &inputFields ) const +{ + QgsFields output = inputFields; + output.append( QgsField( QStringLiteral( "distance" ), QVariant::Double ) ); + output.append( QgsField( QStringLiteral( "angle" ), QVariant::Double ) ); + return output; +} + +QgsPointsAlongGeometryAlgorithm *QgsPointsAlongGeometryAlgorithm::createInstance() const +{ + return new QgsPointsAlongGeometryAlgorithm(); +} + +void QgsPointsAlongGeometryAlgorithm::initParameters( const QVariantMap & ) +{ + std::unique_ptr< QgsProcessingParameterDistance> distance = qgis::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "DISTANCE" ), + QObject::tr( "Distance" ), 1.0, QStringLiteral( "INPUT" ), false, 0 ); + distance->setIsDynamic( true ); + distance->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "DISTANCE" ), QObject::tr( "Distance" ), QgsPropertyDefinition::DoublePositive ) ); + distance->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) ); + addParameter( distance.release() ); + + std::unique_ptr< QgsProcessingParameterDistance> startOffset = qgis::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "START_OFFSET" ), + QObject::tr( "Start offset" ), 0.0, QStringLiteral( "INPUT" ), false, 0 ); + startOffset->setIsDynamic( true ); + startOffset->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "START_OFFSET" ), QObject::tr( "Start offset" ), QgsPropertyDefinition::DoublePositive ) ); + startOffset->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) ); + addParameter( startOffset.release() ); + + std::unique_ptr< QgsProcessingParameterDistance> endOffset = qgis::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "END_OFFSET" ), + QObject::tr( "End offset" ), 0.0, QStringLiteral( "INPUT" ), false, 0 ); + endOffset->setIsDynamic( true ); + endOffset->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "END_OFFSET" ), QObject::tr( "End offset" ), QgsPropertyDefinition::DoublePositive ) ); + endOffset->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) ); + addParameter( endOffset.release() ); +} + +QIcon QgsPointsAlongGeometryAlgorithm::icon() const +{ + return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmExtractVertices.svg" ) ); +} + +QString QgsPointsAlongGeometryAlgorithm::svgIconPath() const +{ + return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmExtractVertices.svg" ) ); +} + +QgsProcessingFeatureSource::Flag QgsPointsAlongGeometryAlgorithm::sourceFlags() const +{ + // skip geometry checks - this algorithm doesn't care about invalid geometries + return QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks; +} + +QgsFeatureSink::SinkFlags QgsPointsAlongGeometryAlgorithm::sinkFlags() const +{ + return QgsFeatureSink::RegeneratePrimaryKey; +} + +bool QgsPointsAlongGeometryAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +{ + mDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context ); + mDynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) ); + if ( mDynamicDistance ) + mDistanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >(); + + mStartOffset = parameterAsDouble( parameters, QStringLiteral( "START_OFFSET" ), context ); + mDynamicStartOffset = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "START_OFFSET" ) ); + if ( mDynamicStartOffset ) + mStartOffsetProperty = parameters.value( QStringLiteral( "START_OFFSET" ) ).value< QgsProperty >(); + + mEndOffset = parameterAsDouble( parameters, QStringLiteral( "END_OFFSET" ), context ); + mDynamicEndOffset = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "END_OFFSET" ) ); + if ( mDynamicEndOffset ) + mEndOffsetProperty = parameters.value( QStringLiteral( "END_OFFSET" ) ).value< QgsProperty >(); + + return true; +} + +QgsFeatureList QgsPointsAlongGeometryAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + QgsFeature f = feature; + if ( f.hasGeometry() ) + { + const QgsGeometry geometry = f.geometry(); + + double distance = mDistance; + if ( mDynamicDistance ) + distance = mDistanceProperty.valueAsDouble( context.expressionContext(), distance ); + if ( distance <= 0 ) + return QgsFeatureList(); + + double startOffset = mStartOffset; + if ( mDynamicStartOffset ) + startOffset = mStartOffsetProperty.valueAsDouble( context.expressionContext(), startOffset ); + + double endOffset = mEndOffset; + if ( mDynamicEndOffset ) + endOffset = mEndOffsetProperty.valueAsDouble( context.expressionContext(), endOffset ); + + const double totalLength = geometry.type() == QgsWkbTypes::PolygonGeometry ? geometry.constGet()->perimeter() + : geometry.length() - endOffset; + + double currentDistance = startOffset; + QgsFeatureList out; + out.reserve( static_cast< int >( std::ceil( ( totalLength - startOffset ) / distance ) ) ); + while ( currentDistance <= totalLength ) + { + const QgsGeometry point = geometry.interpolate( currentDistance ); + const double angle = ( 180 / M_PI ) * geometry.interpolateAngle( currentDistance ); + QgsFeature outputFeature; + outputFeature.setGeometry( point ); + QgsAttributes outAttr = f.attributes(); + outAttr << currentDistance << angle; + outputFeature.setAttributes( outAttr ); + out.append( outputFeature ); + currentDistance += distance; + if ( feedback->isCanceled() ) // better check here -- a ridiculously small distance might take forever + break; + } + return out; + } + else + { + QgsAttributes outAttr = f.attributes(); + outAttr << QVariant() << QVariant(); + f.setAttributes( outAttr ); + return QgsFeatureList() << f; + } +} + +///@endcond + + diff --git a/src/analysis/processing/qgsalgorithmpointsalonggeometry.h b/src/analysis/processing/qgsalgorithmpointsalonggeometry.h new file mode 100644 index 000000000000..8b1883a16dfc --- /dev/null +++ b/src/analysis/processing/qgsalgorithmpointsalonggeometry.h @@ -0,0 +1,81 @@ +/*************************************************************************** + qgsalgorithmpointsalonggeometry.h + --------------------- + begin : June 2019 + copyright : (C) 2019 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. * + * * + ***************************************************************************/ + +#ifndef QGSPOINTSALONGGEOMETRYALGORITHM_H +#define QGSPOINTSALONGGEOMETRYALGORITHM_H + +#define SIP_NO_FILE + +#include "qgis_sip.h" +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +/** + * Native points along geometry algorithm. + */ +class QgsPointsAlongGeometryAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsPointsAlongGeometryAlgorithm() = default; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QString shortDescription() const override; + QList inputLayerTypes() const override; + QgsProcessing::SourceType outputLayerType() const override; + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override; + QgsFields outputFields( const QgsFields &inputFields ) const override; + QgsPointsAlongGeometryAlgorithm *createInstance() const override SIP_FACTORY; + void initParameters( const QVariantMap &configuration = QVariantMap() ) override; + QIcon icon() const override; + QString svgIconPath() const override; + + protected: + QString outputName() const override; + QgsProcessingFeatureSource::Flag sourceFlags() const override; + QgsFeatureSink::SinkFlags sinkFlags() const override; + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback *feedback ) override; + + private: + + double mDistance = 0.0; + bool mDynamicDistance = false; + QgsProperty mDistanceProperty; + + double mStartOffset = 0.0; + bool mDynamicStartOffset = false; + QgsProperty mStartOffsetProperty; + + double mEndOffset = 0.0; + bool mDynamicEndOffset = false; + QgsProperty mEndOffsetProperty; + +}; + + +///@endcond PRIVATE + +#endif // QGSPOINTSALONGGEOMETRYALGORITHM_H + + diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index fde088dce39a..a8c8b6263271 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -74,6 +74,7 @@ #include "qgsalgorithmpackage.h" #include "qgsalgorithmarrayoffsetlines.h" #include "qgsalgorithmpointonsurface.h" +#include "qgsalgorithmpointsalonggeometry.h" #include "qgsalgorithmprojectpointcartesian.h" #include "qgsalgorithmpromotetomultipart.h" #include "qgsalgorithmrasterlayeruniquevalues.h" @@ -214,6 +215,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsPackageAlgorithm() ); addAlgorithm( new QgsCreateArrayOffsetLinesAlgorithm() ); addAlgorithm( new QgsPointOnSurfaceAlgorithm() ); + addAlgorithm( new QgsPointsAlongGeometryAlgorithm() ); addAlgorithm( new QgsProjectPointCartesianAlgorithm() ); addAlgorithm( new QgsPromoteToMultipartAlgorithm() ); addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );