Skip to content

Commit 82312e1

Browse files
committed
[FEATURE][processing] New algorithm to extract specific nodes
This algorithm allows you to extract specific nodes from geometries. Eg you can extract the first or last node in the geometry. The algorithm accepts a comma separated list of node indices to extract, eg 0 = first node, 1 = second node, etc. Negative indices can be used to extract nodes from the end of the geometry. Eg -1 = last node, -2 = second last node.
1 parent 8dab2cd commit 82312e1

File tree

8 files changed

+653
-1
lines changed

8 files changed

+653
-1
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ qgis:extractnodes: >
177177

178178
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.
179179

180+
qgis:extractspecificnodes: >
181+
This algorithm takes a line or polygon layer and generates a point layer with points representing specific nodes in the input lines or polygons. For instance, this algorithm can be used to extract the first or last nodes in the geometry. The attributes associated to each point are the same ones associated to the line or polygon that the point belongs to.
182+
183+
The node indices parameter accepts a comma separated string specifying the indices of the nodes to extract. The first node corresponds to an index of 0, the second node has an index of 1, etc. Negative indices can be used to find nodes at the end of the geometry, eg an index of -1 corresponds to the last node, -2 corresponds to the second last node, etc.
184+
185+
Additional fields are added to the nodes indicating the specific node position (eg 0, -1, etc), the original node index, distance along the original geometry and bisector angle of node for the original geometry.
186+
180187
qgis:fieldcalculator: >
181188
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.
182189

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
ExtractSpecificNodes.py
6+
--------------------
7+
Date : October 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__ = 'October 2016'
22+
__copyright__ = '(C) 2016, Nyall Dawson'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive323
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import math
29+
from processing.core.GeoAlgorithm import GeoAlgorithm
30+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
31+
from processing.core.parameters import ParameterVector, ParameterString
32+
from processing.core.outputs import OutputVector
33+
from processing.tools import dataobjects, vector
34+
35+
from qgis.core import QgsWkbTypes, QgsFeature, QgsGeometry, QgsField
36+
from qgis.PyQt.QtCore import QVariant
37+
38+
39+
class ExtractSpecificNodes(GeoAlgorithm):
40+
41+
INPUT_LAYER = 'INPUT_LAYER'
42+
OUTPUT_LAYER = 'OUTPUT_LAYER'
43+
NODES = 'NODES'
44+
45+
def defineCharacteristics(self):
46+
self.name, self.i18n_name = self.trAlgorithm('Extract specific nodes')
47+
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
48+
49+
self.addParameter(ParameterVector(self.INPUT_LAYER,
50+
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_ANY]))
51+
self.addParameter(ParameterString(self.NODES,
52+
self.tr('Node indices'), default='0'))
53+
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Nodes'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
54+
55+
def processAlgorithm(self, progress):
56+
layer = dataobjects.getObjectFromUri(
57+
self.getParameterValue(self.INPUT_LAYER))
58+
59+
fields = layer.fields()
60+
fields.append(QgsField('node_pos', QVariant.Int))
61+
fields.append(QgsField('node_index', QVariant.Int))
62+
fields.append(QgsField('distance', QVariant.Double))
63+
fields.append(QgsField('angle', QVariant.Double))
64+
65+
writer = self.getOutputFromName(
66+
self.OUTPUT_LAYER).getVectorWriter(
67+
fields,
68+
QgsWkbTypes.Point,
69+
layer.crs())
70+
71+
node_indices_string = self.getParameterValue(self.NODES)
72+
indices = []
73+
for node in node_indices_string.split(','):
74+
try:
75+
indices.append(int(node))
76+
except:
77+
raise GeoAlgorithmExecutionException(
78+
self.tr('\'{}\' is not a valid node index').format(node))
79+
80+
features = vector.features(layer)
81+
total = 100.0 / len(features)
82+
83+
for current, f in enumerate(features):
84+
85+
input_geometry = f.geometry()
86+
if not input_geometry:
87+
writer.addFeature(f)
88+
else:
89+
total_nodes = input_geometry.geometry().nCoordinates()
90+
91+
for node in indices:
92+
if node < 0:
93+
node_index = total_nodes + node
94+
else:
95+
node_index = node
96+
97+
if node_index < 0 or node_index >= total_nodes:
98+
continue
99+
100+
distance = input_geometry.distanceToVertex(node_index)
101+
angle = math.degrees(input_geometry.angleAtVertex(node_index))
102+
103+
output_feature = QgsFeature()
104+
attrs = f.attributes()
105+
attrs.append(node)
106+
attrs.append(node_index)
107+
attrs.append(distance)
108+
attrs.append(angle)
109+
output_feature.setAttributes(attrs)
110+
111+
point = input_geometry.vertexAt(node_index)
112+
output_feature.setGeometry(QgsGeometry.fromPoint(point))
113+
114+
writer.addFeature(output_feature)
115+
116+
progress.setPercentage(int(current * total))
117+
118+
del writer

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
from .ZonalStatisticsQgis import ZonalStatisticsQgis
173173
from .RemoveNullGeometry import RemoveNullGeometry
174174
from .ExtendLines import ExtendLines
175+
from .ExtractSpecificNodes import ExtractSpecificNodes
175176

176177
pluginPath = os.path.normpath(os.path.join(
177178
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -233,7 +234,8 @@ def __init__(self):
233234
ReliefAuto(), ZonalStatisticsQgis(),
234235
IdwInterpolationZValue(), IdwInterpolationAttribute(),
235236
TinInterpolationZValue(), TinInterpolationAttribute(),
236-
RemoveNullGeometry(), ExtractByExpression(), ExtendLines()
237+
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
238+
ExtractSpecificNodes()
237239
]
238240

239241
if hasMatplotlib:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>extract_specific_nodes_lines</Name>
4+
<ElementPath>extract_specific_nodes_lines</ElementPath>
5+
<GeometryType>1</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>21</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>node_pos</Name>
16+
<ElementPath>node_pos</ElementPath>
17+
<Type>Integer</Type>
18+
</PropertyDefn>
19+
<PropertyDefn>
20+
<Name>node_index</Name>
21+
<ElementPath>node_index</ElementPath>
22+
<Type>Integer</Type>
23+
</PropertyDefn>
24+
<PropertyDefn>
25+
<Name>distance</Name>
26+
<ElementPath>distance</ElementPath>
27+
<Type>Real</Type>
28+
</PropertyDefn>
29+
<PropertyDefn>
30+
<Name>angle</Name>
31+
<ElementPath>angle</ElementPath>
32+
<Type>Real</Type>
33+
</PropertyDefn>
34+
</GMLFeatureClass>
35+
</GMLFeatureClassList>
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:extract_specific_nodes_lines fid="lines.1">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,2</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:node_pos>0</ogr:node_pos>
18+
<ogr:node_index>0</ogr:node_index>
19+
<ogr:distance>0</ogr:distance>
20+
<ogr:angle>90</ogr:angle>
21+
</ogr:extract_specific_nodes_lines>
22+
</gml:featureMember>
23+
<gml:featureMember>
24+
<ogr:extract_specific_nodes_lines fid="lines.2">
25+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>11,5</gml:coordinates></gml:Point></ogr:geometryProperty>
26+
<ogr:node_pos>-1</ogr:node_pos>
27+
<ogr:node_index>3</ogr:node_index>
28+
<ogr:distance>6.82842712474619</ogr:distance>
29+
<ogr:angle>45</ogr:angle>
30+
</ogr:extract_specific_nodes_lines>
31+
</gml:featureMember>
32+
<gml:featureMember>
33+
<ogr:extract_specific_nodes_lines fid="lines.3">
34+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9,3</gml:coordinates></gml:Point></ogr:geometryProperty>
35+
<ogr:node_pos>2</ogr:node_pos>
36+
<ogr:node_index>2</ogr:node_index>
37+
<ogr:distance>4</ogr:distance>
38+
<ogr:angle>22.5</ogr:angle>
39+
</ogr:extract_specific_nodes_lines>
40+
</gml:featureMember>
41+
<gml:featureMember>
42+
<ogr:extract_specific_nodes_lines fid="lines.4">
43+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9,3</gml:coordinates></gml:Point></ogr:geometryProperty>
44+
<ogr:node_pos>-2</ogr:node_pos>
45+
<ogr:node_index>2</ogr:node_index>
46+
<ogr:distance>4</ogr:distance>
47+
<ogr:angle>22.5</ogr:angle>
48+
</ogr:extract_specific_nodes_lines>
49+
</gml:featureMember>
50+
<gml:featureMember>
51+
<ogr:extract_specific_nodes_lines fid="lines.5">
52+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>-1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
53+
<ogr:node_pos>0</ogr:node_pos>
54+
<ogr:node_index>0</ogr:node_index>
55+
<ogr:distance>0</ogr:distance>
56+
<ogr:angle>90</ogr:angle>
57+
</ogr:extract_specific_nodes_lines>
58+
</gml:featureMember>
59+
<gml:featureMember>
60+
<ogr:extract_specific_nodes_lines fid="lines.6">
61+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
62+
<ogr:node_pos>-1</ogr:node_pos>
63+
<ogr:node_index>1</ogr:node_index>
64+
<ogr:distance>2</ogr:distance>
65+
<ogr:angle>90</ogr:angle>
66+
</ogr:extract_specific_nodes_lines>
67+
</gml:featureMember>
68+
<gml:featureMember>
69+
<ogr:extract_specific_nodes_lines fid="lines.7">
70+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>-1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
71+
<ogr:node_pos>-2</ogr:node_pos>
72+
<ogr:node_index>0</ogr:node_index>
73+
<ogr:distance>0</ogr:distance>
74+
<ogr:angle>90</ogr:angle>
75+
</ogr:extract_specific_nodes_lines>
76+
</gml:featureMember>
77+
<gml:featureMember>
78+
<ogr:extract_specific_nodes_lines fid="lines.8">
79+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,0</gml:coordinates></gml:Point></ogr:geometryProperty>
80+
<ogr:node_pos>0</ogr:node_pos>
81+
<ogr:node_index>0</ogr:node_index>
82+
<ogr:distance>0</ogr:distance>
83+
<ogr:angle>0</ogr:angle>
84+
</ogr:extract_specific_nodes_lines>
85+
</gml:featureMember>
86+
<gml:featureMember>
87+
<ogr:extract_specific_nodes_lines fid="lines.9">
88+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
89+
<ogr:node_pos>-1</ogr:node_pos>
90+
<ogr:node_index>3</ogr:node_index>
91+
<ogr:distance>4</ogr:distance>
92+
<ogr:angle>0</ogr:angle>
93+
</ogr:extract_specific_nodes_lines>
94+
</gml:featureMember>
95+
<gml:featureMember>
96+
<ogr:extract_specific_nodes_lines fid="lines.10">
97+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,2</gml:coordinates></gml:Point></ogr:geometryProperty>
98+
<ogr:node_pos>2</ogr:node_pos>
99+
<ogr:node_index>2</ogr:node_index>
100+
<ogr:distance>3</ogr:distance>
101+
<ogr:angle>45</ogr:angle>
102+
</ogr:extract_specific_nodes_lines>
103+
</gml:featureMember>
104+
<gml:featureMember>
105+
<ogr:extract_specific_nodes_lines fid="lines.11">
106+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,2</gml:coordinates></gml:Point></ogr:geometryProperty>
107+
<ogr:node_pos>-2</ogr:node_pos>
108+
<ogr:node_index>2</ogr:node_index>
109+
<ogr:distance>3</ogr:distance>
110+
<ogr:angle>45</ogr:angle>
111+
</ogr:extract_specific_nodes_lines>
112+
</gml:featureMember>
113+
<gml:featureMember>
114+
<ogr:extract_specific_nodes_lines fid="lines.12">
115+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,1</gml:coordinates></gml:Point></ogr:geometryProperty>
116+
<ogr:node_pos>0</ogr:node_pos>
117+
<ogr:node_index>0</ogr:node_index>
118+
<ogr:distance>0</ogr:distance>
119+
<ogr:angle>90</ogr:angle>
120+
</ogr:extract_specific_nodes_lines>
121+
</gml:featureMember>
122+
<gml:featureMember>
123+
<ogr:extract_specific_nodes_lines fid="lines.13">
124+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
125+
<ogr:node_pos>-1</ogr:node_pos>
126+
<ogr:node_index>1</ogr:node_index>
127+
<ogr:distance>2</ogr:distance>
128+
<ogr:angle>90</ogr:angle>
129+
</ogr:extract_specific_nodes_lines>
130+
</gml:featureMember>
131+
<gml:featureMember>
132+
<ogr:extract_specific_nodes_lines fid="lines.14">
133+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,1</gml:coordinates></gml:Point></ogr:geometryProperty>
134+
<ogr:node_pos>-2</ogr:node_pos>
135+
<ogr:node_index>0</ogr:node_index>
136+
<ogr:distance>0</ogr:distance>
137+
<ogr:angle>90</ogr:angle>
138+
</ogr:extract_specific_nodes_lines>
139+
</gml:featureMember>
140+
<gml:featureMember>
141+
<ogr:extract_specific_nodes_lines fid="lines.15">
142+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
143+
<ogr:node_pos>0</ogr:node_pos>
144+
<ogr:node_index>0</ogr:node_index>
145+
<ogr:distance>0</ogr:distance>
146+
<ogr:angle>90</ogr:angle>
147+
</ogr:extract_specific_nodes_lines>
148+
</gml:featureMember>
149+
<gml:featureMember>
150+
<ogr:extract_specific_nodes_lines fid="lines.16">
151+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>10,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
152+
<ogr:node_pos>-1</ogr:node_pos>
153+
<ogr:node_index>1</ogr:node_index>
154+
<ogr:distance>3</ogr:distance>
155+
<ogr:angle>90</ogr:angle>
156+
</ogr:extract_specific_nodes_lines>
157+
</gml:featureMember>
158+
<gml:featureMember>
159+
<ogr:extract_specific_nodes_lines fid="lines.17">
160+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
161+
<ogr:node_pos>-2</ogr:node_pos>
162+
<ogr:node_index>0</ogr:node_index>
163+
<ogr:distance>0</ogr:distance>
164+
<ogr:angle>90</ogr:angle>
165+
</ogr:extract_specific_nodes_lines>
166+
</gml:featureMember>
167+
<gml:featureMember>
168+
<ogr:extract_specific_nodes_lines fid="lines.18">
169+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
170+
<ogr:node_pos>0</ogr:node_pos>
171+
<ogr:node_index>0</ogr:node_index>
172+
<ogr:distance>0</ogr:distance>
173+
<ogr:angle>45</ogr:angle>
174+
</ogr:extract_specific_nodes_lines>
175+
</gml:featureMember>
176+
<gml:featureMember>
177+
<ogr:extract_specific_nodes_lines fid="lines.19">
178+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>10,1</gml:coordinates></gml:Point></ogr:geometryProperty>
179+
<ogr:node_pos>-1</ogr:node_pos>
180+
<ogr:node_index>1</ogr:node_index>
181+
<ogr:distance>5.65685424949238</ogr:distance>
182+
<ogr:angle>45</ogr:angle>
183+
</ogr:extract_specific_nodes_lines>
184+
</gml:featureMember>
185+
<gml:featureMember>
186+
<ogr:extract_specific_nodes_lines fid="lines.20">
187+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
188+
<ogr:node_pos>-2</ogr:node_pos>
189+
<ogr:node_index>0</ogr:node_index>
190+
<ogr:distance>0</ogr:distance>
191+
<ogr:angle>45</ogr:angle>
192+
</ogr:extract_specific_nodes_lines>
193+
</gml:featureMember>
194+
<gml:featureMember>
195+
<ogr:extract_specific_nodes_lines fid="lines.21">
196+
</ogr:extract_specific_nodes_lines>
197+
</gml:featureMember>
198+
</ogr:FeatureCollection>

0 commit comments

Comments
 (0)