Skip to content

Commit 7300cda

Browse files
authored
Merge pull request #3435 from nyalldawson/interpolate_angle
Expression functions to Interpolate angle (+ related processing improvements)
2 parents 9ba41e9 + da78dde commit 7300cda

25 files changed

+1701
-33
lines changed

python/core/geometry/qgsgeometry.sip

+19
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ class QgsGeometry
194194
*/
195195
double distanceToVertex( int vertex ) const;
196196

197+
/**
198+
* Returns the bisector angle for this geometry at the specified vertex.
199+
* @param vertex vertex index to calculate bisector angle at
200+
* @returns bisector angle, in radians clockwise from north
201+
* @note added in QGIS 3.0
202+
* @see interpolateAngle()
203+
*/
204+
double angleAtVertex( int vertex ) const;
205+
197206
/**
198207
* Returns the indexes of the vertices before and after the given vertex index.
199208
*
@@ -545,6 +554,16 @@ class QgsGeometry
545554
*/
546555
double lineLocatePoint( const QgsGeometry& point ) const;
547556

557+
/** Returns the angle parallel to the linestring or polygon boundary at the specified distance
558+
* along the geometry. Angles are in radians, clockwise from north.
559+
* If the distance coincides precisely at a node then the average angle from the segment either side
560+
* of the node is returned.
561+
* @param distance distance along geometry
562+
* @note added in QGIS 3.0
563+
* @see angleAtVertex()
564+
*/
565+
double interpolateAngle( double distance ) const;
566+
548567
/** Returns a geometry representing the points shared by this geometry and other. */
549568
QgsGeometry intersection( const QgsGeometry& geometry ) const;
550569

python/plugins/processing/algs/help/qgis.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ qgis:extractbylocation: >
166166
qgis:extractnodes: >
167167
This algorithm takes a line or polygon layer and generates a point layer with points representing the nodes in the input lines or polygons. The attributes associated to each point are the same ones associated to the line or polygon that the point belongs to.
168168

169+
Additional fields are added to the nodes indicating the node index (beginning at 0), distance along original geometry and bisector angle of node for original geometry.
170+
169171
qgis:fieldcalculator: >
170172
This algorithm computes a new vector layer with the same features of the input layer, but with an additional attribute. The values of this new attribute are computed from each feature using a mathematical formula, based on te properties and attributes of the feature.
171173

@@ -284,6 +286,11 @@ qgis:orientedminimumboundingbox: >
284286

285287
As an alternative, the output layer can contain not just a single rectangle, but one for each input feature, representing the minimum rectangle that covers each of them.
286288

289+
qgis:pointsalonglines: >
290+
Creates points at regular intervals along line or polygon geometries. Created points will have new attributes added for the distance along the geometry and the angle of the line at the point.
291+
292+
An optional start and end offset can be specified, which controls how far from the start and end of the geometry the points should be created.
293+
287294
qgis:pointsdisplacement:
288295

289296

python/plugins/processing/algs/qgis/ExtractNodes.py

+26-15
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
__revision__ = '$Format:%H$'
2727

2828
import os
29+
import math
2930

3031
from qgis.PyQt.QtGui import QIcon
32+
from qgis.PyQt.QtCore import QVariant
3133

32-
from qgis.core import Qgis, QgsFeature, QgsGeometry, QgsWkbTypes
34+
from qgis.core import QgsFeature, QgsGeometry, QgsWkbTypes, QgsField
3335

3436
from processing.core.GeoAlgorithm import GeoAlgorithm
3537
from processing.core.parameters import ParameterVector
@@ -61,25 +63,34 @@ def processAlgorithm(self, progress):
6163
layer = dataobjects.getObjectFromUri(
6264
self.getParameterValue(self.INPUT))
6365

64-
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
65-
layer.fields().toList(), QgsWkbTypes.Point, layer.crs())
66+
fields = layer.fields()
67+
fields.append(QgsField('node_index', QVariant.Int))
68+
fields.append(QgsField('distance', QVariant.Double))
69+
fields.append(QgsField('angle', QVariant.Double))
6670

67-
outFeat = QgsFeature()
68-
inGeom = QgsGeometry()
69-
outGeom = QgsGeometry()
71+
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
72+
fields, QgsWkbTypes.Point, layer.crs())
7073

7174
features = vector.features(layer)
7275
total = 100.0 / len(features)
7376
for current, f in enumerate(features):
74-
inGeom = f.geometry()
75-
attrs = f.attributes()
76-
77-
points = vector.extractPoints(inGeom)
78-
outFeat.setAttributes(attrs)
79-
80-
for i in points:
81-
outFeat.setGeometry(outGeom.fromPoint(i))
82-
writer.addFeature(outFeat)
77+
input_geometry = f.geometry()
78+
if not input_geometry:
79+
writer.addFeature(f)
80+
else:
81+
points = vector.extractPoints(input_geometry)
82+
83+
for i, point in enumerate(points):
84+
distance = input_geometry.distanceToVertex(i)
85+
angle = math.degrees(input_geometry.angleAtVertex(i))
86+
attrs = f.attributes()
87+
attrs.append(i)
88+
attrs.append(distance)
89+
attrs.append(angle)
90+
output_feature = QgsFeature()
91+
output_feature.setAttributes(attrs)
92+
output_feature.setGeometry(QgsGeometry.fromPoint(point))
93+
writer.addFeature(output_feature)
8394

8495
progress.setPercentage(int(current * total))
8596

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
PointsAlongGeometry.py
6+
---------------------
7+
Date : August 2016
8+
Copyright : (C) 2016 by Nyall Dawson
9+
Email : nyall dot dawson at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Nyall Dawson'
21+
__date__ = 'August 2016'
22+
__copyright__ = '(C) 2016, Nyall Dawson'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import os
29+
import math
30+
31+
from qgis.PyQt.QtCore import QVariant
32+
from qgis.PyQt.QtGui import QIcon
33+
34+
from qgis.core import QgsFeature, QgsGeometry, QgsWkbTypes, QgsField
35+
36+
from processing.core.GeoAlgorithm import GeoAlgorithm
37+
from processing.core.parameters import ParameterVector, ParameterNumber
38+
from processing.core.outputs import OutputVector
39+
from processing.tools import dataobjects, vector
40+
41+
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
42+
43+
44+
class PointsAlongGeometry(GeoAlgorithm):
45+
46+
INPUT = 'INPUT'
47+
OUTPUT = 'OUTPUT'
48+
DISTANCE = 'DISTANCE'
49+
START_OFFSET = 'START_OFFSET'
50+
END_OFFSET = 'END_OFFSET'
51+
52+
def getIcon(self):
53+
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'extract_nodes.png'))
54+
55+
def defineCharacteristics(self):
56+
self.name, self.i18n_name = self.trAlgorithm('Points along lines')
57+
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
58+
59+
self.addParameter(ParameterVector(self.INPUT,
60+
self.tr('Input layer'),
61+
[ParameterVector.VECTOR_TYPE_POLYGON, ParameterVector.VECTOR_TYPE_LINE]))
62+
self.addParameter(ParameterNumber(self.DISTANCE,
63+
self.tr('Distance'), default=1.0))
64+
self.addParameter(ParameterNumber(self.START_OFFSET,
65+
self.tr('Start offset'), default=0.0))
66+
self.addParameter(ParameterNumber(self.END_OFFSET,
67+
self.tr('End offset'), default=0.0))
68+
self.addOutput(OutputVector(self.OUTPUT, self.tr('Points')))
69+
70+
def processAlgorithm(self, progress):
71+
layer = dataobjects.getObjectFromUri(
72+
self.getParameterValue(self.INPUT))
73+
distance = self.getParameterValue(self.DISTANCE)
74+
start_offset = self.getParameterValue(self.START_OFFSET)
75+
end_offset = self.getParameterValue(self.END_OFFSET)
76+
77+
fields = layer.fields()
78+
fields.append(QgsField('distance', QVariant.Double))
79+
fields.append(QgsField('angle', QVariant.Double))
80+
81+
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
82+
fields, QgsWkbTypes.Point, layer.crs())
83+
84+
features = vector.features(layer)
85+
total = 100.0 / len(features)
86+
for current, input_feature in enumerate(features):
87+
input_geometry = input_feature.geometry()
88+
if not input_geometry:
89+
writer.addFeature(input_feature)
90+
else:
91+
if input_geometry.type == QgsWkbTypes.PolygonGeometry:
92+
length = input_geometry.geometry().perimeter()
93+
else:
94+
length = input_geometry.length() - end_offset
95+
current_distance = start_offset
96+
97+
while current_distance <= length:
98+
point = input_geometry.interpolate(current_distance)
99+
angle = math.degrees(input_geometry.interpolateAngle(current_distance))
100+
101+
output_feature = QgsFeature()
102+
output_feature.setGeometry(point)
103+
attrs = input_feature.attributes()
104+
attrs.append(current_distance)
105+
attrs.append(angle)
106+
output_feature.setAttributes(attrs)
107+
writer.addFeature(output_feature)
108+
109+
current_distance += distance
110+
111+
progress.setPercentage(int(current * total))
112+
113+
del writer

python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
from .PolygonCentroids import PolygonCentroids
155155
from .Translate import Translate
156156
from .SingleSidedBuffer import SingleSidedBuffer
157+
from .PointsAlongGeometry import PointsAlongGeometry
157158

158159
pluginPath = os.path.normpath(os.path.join(
159160
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -208,7 +209,8 @@ def __init__(self):
208209
RectanglesOvalsDiamondsFixed(), MergeLines(),
209210
BoundingBox(), Boundary(), PointOnSurface(),
210211
OffsetLine(), PolygonCentroids(),
211-
Translate(), SingleSidedBuffer()
212+
Translate(), SingleSidedBuffer(),
213+
PointsAlongGeometry()
212214
]
213215

214216
if hasMatplotlib:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>extract_nodes_lines</Name>
4+
<ElementPath>extract_nodes_lines</ElementPath>
5+
<GeometryType>1</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>17</FeatureCount>
9+
<ExtentXMin>-1.00000</ExtentXMin>
10+
<ExtentXMax>11.00000</ExtentXMax>
11+
<ExtentYMin>-3.00000</ExtentYMin>
12+
<ExtentYMax>5.00000</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
<PropertyDefn>
15+
<Name>distance</Name>
16+
<ElementPath>distance</ElementPath>
17+
<Type>Real</Type>
18+
</PropertyDefn>
19+
<PropertyDefn>
20+
<Name>angle</Name>
21+
<ElementPath>angle</ElementPath>
22+
<Type>Real</Type>
23+
</PropertyDefn>
24+
</GMLFeatureClass>
25+
</GMLFeatureClassList>

0 commit comments

Comments
 (0)