Skip to content
Permalink
Browse files

Merge pull request #3435 from nyalldawson/interpolate_angle

Expression functions to Interpolate angle (+ related processing improvements)
  • Loading branch information
nyalldawson committed Aug 29, 2016
2 parents 9ba41e9 + da78dde commit 7300cda86b9f0919dd9c43a2a344f6b1c0afd3a3
Showing with 1,701 additions and 33 deletions.
  1. +19 −0 python/core/geometry/qgsgeometry.sip
  2. +7 −0 python/plugins/processing/algs/help/qgis.yaml
  3. +26 −15 python/plugins/processing/algs/qgis/ExtractNodes.py
  4. +113 −0 python/plugins/processing/algs/qgis/PointsAlongGeometry.py
  5. +3 −1 python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
  6. +25 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_lines.gfs
  7. +146 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_lines.gml
  8. +25 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_multilines.gfs
  9. +130 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_multilines.gml
  10. +41 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_multipolys.gfs
  11. +249 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_multipolys.gml
  12. +41 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_polys.gfs
  13. +357 −0 python/plugins/processing/tests/testdata/expected/extract_nodes_polys.gml
  14. +59 −13 python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
  15. +8 −0 resources/function_help/json/angle_at_vertex
  16. +8 −0 resources/function_help/json/distance_to_vertex
  17. +8 −0 resources/function_help/json/line_interpolate_angle
  18. +100 −1 src/core/geometry/qgsgeometry.cpp
  19. +19 −0 src/core/geometry/qgsgeometry.h
  20. +55 −3 src/core/geometry/qgsgeometryutils.cpp
  21. +15 −0 src/core/geometry/qgsgeometryutils.h
  22. +31 −0 src/core/qgsexpression.cpp
  23. +12 −0 tests/src/core/testqgsexpression.cpp
  24. +101 −0 tests/src/core/testqgsgeometryutils.cpp
  25. +103 −0 tests/src/python/test_qgsgeometry.py
@@ -194,6 +194,15 @@ class QgsGeometry
*/
double distanceToVertex( int vertex ) const;

/**
* Returns the bisector angle for this geometry at the specified vertex.
* @param vertex vertex index to calculate bisector angle at
* @returns bisector angle, in radians clockwise from north
* @note added in QGIS 3.0
* @see interpolateAngle()
*/
double angleAtVertex( int vertex ) const;

/**
* Returns the indexes of the vertices before and after the given vertex index.
*
@@ -545,6 +554,16 @@ class QgsGeometry
*/
double lineLocatePoint( const QgsGeometry& point ) const;

/** Returns the angle parallel to the linestring or polygon boundary at the specified distance
* along the geometry. Angles are in radians, clockwise from north.
* If the distance coincides precisely at a node then the average angle from the segment either side
* of the node is returned.
* @param distance distance along geometry
* @note added in QGIS 3.0
* @see angleAtVertex()
*/
double interpolateAngle( double distance ) const;

/** Returns a geometry representing the points shared by this geometry and other. */
QgsGeometry intersection( const QgsGeometry& geometry ) const;

@@ -166,6 +166,8 @@ qgis:extractbylocation: >
qgis:extractnodes: >
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.

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

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

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.

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

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.

qgis:pointsdisplacement:


@@ -26,10 +26,12 @@
__revision__ = '$Format:%H$'

import os
import math

from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtCore import QVariant

from qgis.core import Qgis, QgsFeature, QgsGeometry, QgsWkbTypes
from qgis.core import QgsFeature, QgsGeometry, QgsWkbTypes, QgsField

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

writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
layer.fields().toList(), QgsWkbTypes.Point, layer.crs())
fields = layer.fields()
fields.append(QgsField('node_index', QVariant.Int))
fields.append(QgsField('distance', QVariant.Double))
fields.append(QgsField('angle', QVariant.Double))

outFeat = QgsFeature()
inGeom = QgsGeometry()
outGeom = QgsGeometry()
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
fields, QgsWkbTypes.Point, layer.crs())

features = vector.features(layer)
total = 100.0 / len(features)
for current, f in enumerate(features):
inGeom = f.geometry()
attrs = f.attributes()

points = vector.extractPoints(inGeom)
outFeat.setAttributes(attrs)

for i in points:
outFeat.setGeometry(outGeom.fromPoint(i))
writer.addFeature(outFeat)
input_geometry = f.geometry()
if not input_geometry:
writer.addFeature(f)
else:
points = vector.extractPoints(input_geometry)

for i, point in enumerate(points):
distance = input_geometry.distanceToVertex(i)
angle = math.degrees(input_geometry.angleAtVertex(i))
attrs = f.attributes()
attrs.append(i)
attrs.append(distance)
attrs.append(angle)
output_feature = QgsFeature()
output_feature.setAttributes(attrs)
output_feature.setGeometry(QgsGeometry.fromPoint(point))
writer.addFeature(output_feature)

progress.setPercentage(int(current * total))

@@ -0,0 +1,113 @@
# -*- 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'

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

__revision__ = '$Format:%H$'

import os
import math

from qgis.PyQt.QtCore import QVariant
from qgis.PyQt.QtGui import QIcon

from qgis.core import QgsFeature, QgsGeometry, QgsWkbTypes, QgsField

from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.parameters import ParameterVector, ParameterNumber
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]


class PointsAlongGeometry(GeoAlgorithm):

INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
DISTANCE = 'DISTANCE'
START_OFFSET = 'START_OFFSET'
END_OFFSET = 'END_OFFSET'

def getIcon(self):
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'extract_nodes.png'))

def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Points along lines')
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')

self.addParameter(ParameterVector(self.INPUT,
self.tr('Input layer'),
[ParameterVector.VECTOR_TYPE_POLYGON, ParameterVector.VECTOR_TYPE_LINE]))
self.addParameter(ParameterNumber(self.DISTANCE,
self.tr('Distance'), default=1.0))
self.addParameter(ParameterNumber(self.START_OFFSET,
self.tr('Start offset'), default=0.0))
self.addParameter(ParameterNumber(self.END_OFFSET,
self.tr('End offset'), default=0.0))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Points')))

def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(
self.getParameterValue(self.INPUT))
distance = self.getParameterValue(self.DISTANCE)
start_offset = self.getParameterValue(self.START_OFFSET)
end_offset = self.getParameterValue(self.END_OFFSET)

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

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

features = vector.features(layer)
total = 100.0 / len(features)
for current, input_feature in enumerate(features):
input_geometry = input_feature.geometry()
if not input_geometry:
writer.addFeature(input_feature)
else:
if input_geometry.type == QgsWkbTypes.PolygonGeometry:
length = input_geometry.geometry().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)
writer.addFeature(output_feature)

current_distance += distance

progress.setPercentage(int(current * total))

del writer
@@ -154,6 +154,7 @@
from .PolygonCentroids import PolygonCentroids
from .Translate import Translate
from .SingleSidedBuffer import SingleSidedBuffer
from .PointsAlongGeometry import PointsAlongGeometry

pluginPath = os.path.normpath(os.path.join(
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -208,7 +209,8 @@ def __init__(self):
RectanglesOvalsDiamondsFixed(), MergeLines(),
BoundingBox(), Boundary(), PointOnSurface(),
OffsetLine(), PolygonCentroids(),
Translate(), SingleSidedBuffer()
Translate(), SingleSidedBuffer(),
PointsAlongGeometry()
]

if hasMatplotlib:
@@ -0,0 +1,25 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>extract_nodes_lines</Name>
<ElementPath>extract_nodes_lines</ElementPath>
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>17</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>11.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>5.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>distance</Name>
<ElementPath>distance</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>angle</Name>
<ElementPath>angle</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

0 comments on commit 7300cda

Please sign in to comment.
You can’t perform that action at this time.