Skip to content

Commit

Permalink
[FEATURE][processing] New algorithm to extract specific nodes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nyalldawson committed Oct 30, 2016
1 parent 8dab2cd commit 82312e1
Show file tree
Hide file tree
Showing 8 changed files with 653 additions and 1 deletion.
7 changes: 7 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ qgis:extractnodes: >

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.

qgis:extractspecificnodes: >
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.

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.

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.

qgis:fieldcalculator: >
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.

Expand Down
118 changes: 118 additions & 0 deletions python/plugins/processing/algs/qgis/ExtractSpecificNodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-

"""
***************************************************************************
ExtractSpecificNodes.py
--------------------
Date : October 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__ = 'October 2016'
__copyright__ = '(C) 2016, Nyall Dawson'

# This will get replaced with a git SHA1 when you do a git archive323

__revision__ = '$Format:%H$'

import math
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector, ParameterString
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector

from qgis.core import QgsWkbTypes, QgsFeature, QgsGeometry, QgsField
from qgis.PyQt.QtCore import QVariant


class ExtractSpecificNodes(GeoAlgorithm):

INPUT_LAYER = 'INPUT_LAYER'
OUTPUT_LAYER = 'OUTPUT_LAYER'
NODES = 'NODES'

def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Extract specific nodes')
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')

self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_ANY]))
self.addParameter(ParameterString(self.NODES,
self.tr('Node indices'), default='0'))
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Nodes'), datatype=[dataobjects.TYPE_VECTOR_POINT]))

def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(
self.getParameterValue(self.INPUT_LAYER))

fields = layer.fields()
fields.append(QgsField('node_pos', QVariant.Int))
fields.append(QgsField('node_index', QVariant.Int))
fields.append(QgsField('distance', QVariant.Double))
fields.append(QgsField('angle', QVariant.Double))

writer = self.getOutputFromName(
self.OUTPUT_LAYER).getVectorWriter(
fields,
QgsWkbTypes.Point,
layer.crs())

node_indices_string = self.getParameterValue(self.NODES)
indices = []
for node in node_indices_string.split(','):
try:
indices.append(int(node))
except:
raise GeoAlgorithmExecutionException(
self.tr('\'{}\' is not a valid node index').format(node))

features = vector.features(layer)
total = 100.0 / len(features)

for current, f in enumerate(features):

input_geometry = f.geometry()
if not input_geometry:
writer.addFeature(f)
else:
total_nodes = input_geometry.geometry().nCoordinates()

for node in indices:
if node < 0:
node_index = total_nodes + node
else:
node_index = node

if node_index < 0 or node_index >= total_nodes:
continue

distance = input_geometry.distanceToVertex(node_index)
angle = math.degrees(input_geometry.angleAtVertex(node_index))

output_feature = QgsFeature()
attrs = f.attributes()
attrs.append(node)
attrs.append(node_index)
attrs.append(distance)
attrs.append(angle)
output_feature.setAttributes(attrs)

point = input_geometry.vertexAt(node_index)
output_feature.setGeometry(QgsGeometry.fromPoint(point))

writer.addFeature(output_feature)

progress.setPercentage(int(current * total))

del writer
4 changes: 3 additions & 1 deletion python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
from .ZonalStatisticsQgis import ZonalStatisticsQgis
from .RemoveNullGeometry import RemoveNullGeometry
from .ExtendLines import ExtendLines
from .ExtractSpecificNodes import ExtractSpecificNodes

pluginPath = os.path.normpath(os.path.join(
os.path.split(os.path.dirname(__file__))[0], os.pardir))
Expand Down Expand Up @@ -233,7 +234,8 @@ def __init__(self):
ReliefAuto(), ZonalStatisticsQgis(),
IdwInterpolationZValue(), IdwInterpolationAttribute(),
TinInterpolationZValue(), TinInterpolationAttribute(),
RemoveNullGeometry(), ExtractByExpression(), ExtendLines()
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
ExtractSpecificNodes()
]

if hasMatplotlib:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>extract_specific_nodes_lines</Name>
<ElementPath>extract_specific_nodes_lines</ElementPath>
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>21</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>11.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>5.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>node_pos</Name>
<ElementPath>node_pos</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>node_index</Name>
<ElementPath>node_index</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>distance</Name>
<ElementPath>distance</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>angle</Name>
<ElementPath>angle</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>0</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>11,5</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-1</ogr:node_pos>
<ogr:node_index>3</ogr:node_index>
<ogr:distance>6.82842712474619</ogr:distance>
<ogr:angle>45</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>2</ogr:node_pos>
<ogr:node_index>2</ogr:node_index>
<ogr:distance>4</ogr:distance>
<ogr:angle>22.5</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-2</ogr:node_pos>
<ogr:node_index>2</ogr:node_index>
<ogr:distance>4</ogr:distance>
<ogr:angle>22.5</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>-1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>0</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-1</ogr:node_pos>
<ogr:node_index>1</ogr:node_index>
<ogr:distance>2</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>-1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-2</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,0</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>0</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>0</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.9">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-1</ogr:node_pos>
<ogr:node_index>3</ogr:node_index>
<ogr:distance>4</ogr:distance>
<ogr:angle>0</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.10">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>2</ogr:node_pos>
<ogr:node_index>2</ogr:node_index>
<ogr:distance>3</ogr:distance>
<ogr:angle>45</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.11">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-2</ogr:node_pos>
<ogr:node_index>2</ogr:node_index>
<ogr:distance>3</ogr:distance>
<ogr:angle>45</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.12">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>0</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.13">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-1</ogr:node_pos>
<ogr:node_index>1</ogr:node_index>
<ogr:distance>2</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.14">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-2</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.15">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>0</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.16">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>10,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-1</ogr:node_pos>
<ogr:node_index>1</ogr:node_index>
<ogr:distance>3</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.17">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-2</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>90</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.18">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>0</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>45</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.19">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>10,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-1</ogr:node_pos>
<ogr:node_index>1</ogr:node_index>
<ogr:distance>5.65685424949238</ogr:distance>
<ogr:angle>45</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.20">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,-3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:node_pos>-2</ogr:node_pos>
<ogr:node_index>0</ogr:node_index>
<ogr:distance>0</ogr:distance>
<ogr:angle>45</ogr:angle>
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_specific_nodes_lines fid="lines.21">
</ogr:extract_specific_nodes_lines>
</gml:featureMember>
</ogr:FeatureCollection>
Loading

0 comments on commit 82312e1

Please sign in to comment.