Skip to content
Permalink
Browse files

Merge pull request #3848 from nyalldawson/orthag

[FEATURE][processing] New algorithm to orthagonalize geometries
  • Loading branch information
nyalldawson committed Dec 9, 2016
2 parents e41c2a7 + 4b6f3a3 commit 5aa1539418d88d6979888c50e43846f46276d2ed
Showing with 884 additions and 3 deletions.
  1. +10 −0 python/core/geometry/qgsgeometry.sip
  2. +31 −0 python/core/geometry/qgspointv2.sip
  3. +30 −0 python/core/qgspoint.sip
  4. +7 −0 python/plugins/processing/algs/help/qgis.yaml
  5. +92 −0 python/plugins/processing/algs/qgis/Orthagonalize.py
  6. +3 −1 python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
  7. +20 −0 python/plugins/processing/tests/testdata/custom/lines_to_orth.gfs
  8. +38 −0 python/plugins/processing/tests/testdata/custom/lines_to_orth.gml
  9. +20 −0 python/plugins/processing/tests/testdata/custom/polys_to_orth.gfs
  10. +38 −0 python/plugins/processing/tests/testdata/custom/polys_to_orth.gml
  11. +20 −0 python/plugins/processing/tests/testdata/expected/orthagonal_lines.gfs
  12. +38 −0 python/plugins/processing/tests/testdata/expected/orthagonal_lines.gml
  13. +20 −0 python/plugins/processing/tests/testdata/expected/orthagonal_polys.gfs
  14. +38 −0 python/plugins/processing/tests/testdata/expected/orthagonal_polys.gml
  15. +41 −0 python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
  16. +7 −0 src/core/geometry/qgsgeometry.cpp
  17. +10 −0 src/core/geometry/qgsgeometry.h
  18. +233 −0 src/core/geometry/qgsinternalgeometryengine.cpp
  19. +10 −0 src/core/geometry/qgsinternalgeometryengine.h
  20. +30 −0 src/core/geometry/qgspointv2.h
  21. +34 −0 src/core/qgspoint.cpp
  22. +33 −2 src/core/qgspoint.h
  23. +15 −0 tests/src/core/testqgsgeometry.cpp
  24. +22 −0 tests/src/core/testqgspoint.cpp
  25. +44 −0 tests/src/python/test_qgsgeometry.py
@@ -420,6 +420,16 @@ class QgsGeometry
*/
QgsGeometry orientedMinimumBoundingBox( double& area /Out/, double &angle /Out/, double& width /Out/, double& height /Out/ ) const;

/**
* Attempts to orthagonalize a line or polygon geometry by shifting vertices to make the geometries
* angles either right angles or flat lines. This is an iterative algorithm which will loop until
* either the vertices are within a specified tolerance of right angles or a set number of maximum
* iterations is reached. The angle threshold parameter specifies how close to a right angle or
* straight line an angle must be before it is attempted to be straightened.
* @note added in QGIS 3.0
*/
QgsGeometry orthagonalize( double tolerance = 1.0E-8, int maxIterations = 1000, double angleThreshold = 15.0 ) const;

/** Test for intersection with a rectangle (uses GEOS) */
bool intersects( const QgsRectangle& r ) const;

@@ -179,6 +179,37 @@ class QgsPointV2: public QgsAbstractGeometry
*/
double azimuth( const QgsPointV2& other ) const;

/**
* Calculates the vector obtained by subtracting a point from this point.
* @note added in QGIS 3.0
*/
QgsVector operator-( const QgsPointV2& p ) const;

/**
* Adds a vector to this point in place.
* @note added in QGIS 3.0
*/
QgsPointV2 &operator+=( QgsVector v );

/**
* Subtracts a vector from this point in place.
* @note added in QGIS 3.0
*/
QgsPointV2 &operator-=( QgsVector v );

/**
* Adds a vector to this point.
* @note added in QGIS 3.0
*/
QgsPointV2 operator+( QgsVector v ) const;

/**
* Subtracts a vector from this point.
* @note added in QGIS 3.0
*/
QgsPointV2 operator-( QgsVector v ) const;


//implementation of inherited methods
virtual QgsRectangle boundingBox() const;
virtual QString geometryType() const;
@@ -39,6 +39,30 @@ class QgsVector
*/
double operator*( QgsVector v ) const;

/**
* Adds another vector to this vector.
* @node added in QGIS 3.0
*/
QgsVector operator+( QgsVector other ) const;

/**
* Adds another vector to this vector in place.
* @node added in QGIS 3.0
*/
QgsVector& operator+=( QgsVector other );

/**
* Subtracts another vector to this vector.
* @node added in QGIS 3.0
*/
QgsVector operator-( QgsVector other ) const;

/**
* Subtracts another vector to this vector in place.
* @node added in QGIS 3.0
*/
QgsVector& operator-=( QgsVector other );

/** Returns the length of the vector.
*/
double length() const;
@@ -74,6 +98,12 @@ class QgsVector
* if called on a vector with length of 0.
*/
QgsVector normalized() const;

//! Equality operator
bool operator==( QgsVector other ) const;

//! Inequality operator
bool operator!=( QgsVector other ) const;
};


@@ -320,6 +320,13 @@ 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:orthagonalize: >
This algorithm takes a line or polygon layer and attempts to orthagonalize all the geometries in the layer. This process shifts the nodes in the geometries to try to make every angle in the geometry either a right angle or a straight line.

The angle tolerance parameter is used to specify the maximum deviation from a right angle or straight line a node can have for it to be adjusted. Smaller tolerances mean that only nodes which are already closer to right angles will be adjusted, and larger tolerances mean that nodes which deviate further from right angles will also be adjusted.

The algorithm is iterative. Setting a larger number for the maximum iterations will result in a more orthagonal geometry at the cost of extra processing time.

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.

@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-

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

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

__revision__ = '$Format:%H$'


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


class Orthagonalize(GeoAlgorithm):

INPUT_LAYER = 'INPUT_LAYER'
OUTPUT_LAYER = 'OUTPUT_LAYER'
MAX_ITERATIONS = 'MAX_ITERATIONS'
DISTANCE_THRESHOLD = 'DISTANCE_THRESHOLD'
ANGLE_TOLERANCE = 'ANGLE_TOLERANCE'

def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Orthagonalize')
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
self.tags = self.tr('rectangle,perpendicular,right,angles,square,quadrilateralise')

self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE,
dataobjects.TYPE_VECTOR_POLYGON]))
self.addParameter(ParameterNumber(self.ANGLE_TOLERANCE,
self.tr('Maximum angle tolerance (degrees)'),
0.0, 45.0, 15.0))

max_iterations = ParameterNumber(self.MAX_ITERATIONS,
self.tr('Maximum algorithm iterations'),
1, 10000, 1000)
max_iterations.isAdvanced = True
self.addParameter(max_iterations)

self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Orthagonalized')))

def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(
self.getParameterValue(self.INPUT_LAYER))
max_iterations = self.getParameterValue(self.MAX_ITERATIONS)
angle_tolerance = self.getParameterValue(self.ANGLE_TOLERANCE)
writer = self.getOutputFromName(
self.OUTPUT_LAYER).getVectorWriter(
layer.fields(),
layer.wkbType(),
layer.crs())

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

for current, input_feature in enumerate(features):
output_feature = input_feature
input_geometry = input_feature.geometry()
if input_geometry:
output_geometry = input_geometry.orthagonalize(1.0e-8, max_iterations, angle_tolerance)
if not output_geometry:
raise GeoAlgorithmExecutionException(
self.tr('Error orthagonalizing geometry'))

output_feature.setGeometry(output_geometry)

writer.addFeature(output_feature)
progress.setPercentage(int(current * total))

del writer
@@ -183,6 +183,7 @@
from .DropGeometry import DropGeometry
from .BasicStatistics import BasicStatisticsForField
from .Heatmap import Heatmap
from .Orthagonalize import Orthagonalize

pluginPath = os.path.normpath(os.path.join(
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -247,7 +248,8 @@ def __init__(self):
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer(),
PoleOfInaccessibility(), CreateAttributeIndex(), DropGeometry(),
BasicStatisticsForField(), RasterCalculator(), Heatmap()
BasicStatisticsForField(), RasterCalculator(), Heatmap(),
Orthagonalize()
]

if hasMatplotlib:
@@ -0,0 +1,20 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>lines_to_orth</Name>
<ElementPath>lines_to_orth</ElementPath>
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
<ExtentXMin>-1.17586</ExtentXMin>
<ExtentXMax>0.68704</ExtentXMax>
<ExtentYMin>-0.96433</ExtentYMin>
<ExtentYMax>1.11582</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,38 @@
<?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.175859979156524</gml:X><gml:Y>-0.9643318489227042</gml:Y></gml:coord>
<gml:coord><gml:X>0.6870430854602452</gml:X><gml:Y>1.115824129342566</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:lines_to_orth fid="lines_to_orth.0">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.074456310482982,-0.916199588298252 0.040225681809122,-0.955727318521376 0.04741254184969,-0.617944896614678 0.687043085460245,-0.661066056858086</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:intval>0</ogr:intval>
</ogr:lines_to_orth>
</gml:featureMember>
<gml:featureMember>
<ogr:lines_to_orth fid="lines_to_orth.1">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.074456310482982,-0.916199588298252 0.048128551164702,-0.964331848922704 0.062280009502849,-0.634278538511395 0.687043085460245,-0.661066056858086</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:intval>1</ogr:intval>
</ogr:lines_to_orth>
</gml:featureMember>
<gml:featureMember>
<ogr:lines_to_orth fid="lines_to_orth.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.027472879898916,-0.278453309909787 -0.290913154921217,0.321316751857768 0.531737966482446,-0.156968783842037</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:intval>2</ogr:intval>
</ogr:lines_to_orth>
</gml:featureMember>
<gml:featureMember>
<ogr:lines_to_orth fid="lines_to_orth.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.1679544331119,0.483380445772577 -1.175859979156524,1.115824129342566 0.231327216786701,1.044674214940942 0.215516124697451,0.522908175995701 -1.065182334531776,0.412230531370954</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:intval>3</ogr:intval>
</ogr:lines_to_orth>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,20 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>polys_to_orth</Name>
<ElementPath>polys_to_orth</ElementPath>
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
<ExtentXMin>-0.70300</ExtentXMin>
<ExtentXMax>0.92360</ExtentXMax>
<ExtentYMin>-1.55335</ExtentYMin>
<ExtentYMax>0.89200</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,38 @@
<?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>-0.703</gml:X><gml:Y>-1.553346855983773</gml:Y></gml:coord>
<gml:coord><gml:X>0.923603171676194</gml:X><gml:Y>0.892</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:polys_to_orth fid="polys_to_orth.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-0.48158215010142,0.086166328600406 -0.182150101419878,0.081257606490872 -0.201784989858012,-0.12 0.568884381338742,-0.203448275862069 0.568884381338742,-0.772860040567951 -0.545395537525355,-0.689411764705882 -0.48158215010142,0.086166328600406</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>-0.266466849320251,-0.285441896123125 -0.286067376703618,-0.546782261234689 0.132077207474883,-0.605583843384791 0.132077207474883,-0.46184664257343 0.347683008691924,-0.455313133445642 0.341149499564134,-0.305042423506492 -0.266466849320251,-0.285441896123125</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>3</ogr:intval>
</ogr:polys_to_orth>
</gml:featureMember>
<gml:featureMember>
<ogr:polys_to_orth fid="polys_to_orth.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-0.550304259634889,-1.553346855983773 -0.182150101419878,-0.95448275862069 -0.182150101419878,-0.95448275862069 0.186004056795132,-1.538620689655172 -0.550304259634889,-1.553346855983773</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>1</ogr:intval>
</ogr:polys_to_orth>
</gml:featureMember>
<gml:featureMember>
<ogr:polys_to_orth fid="polys_to_orth.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>0.506859671768394,-1.376348884381339 0.433099760280288,-1.081309238428914 0.765019361976765,-0.900597455283054 0.923603171676194,-1.132941176470588 0.923603171676194,-1.39110086667896 0.506859671768394,-1.376348884381339</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>2</ogr:intval>
</ogr:polys_to_orth>
</gml:featureMember>
<gml:featureMember>
<ogr:polys_to_orth fid="polys_to_orth.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-0.699,0.892 -0.703,0.405 -0.022,0.361 0.014,0.851 -0.699,0.892</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>-0.619341074030194,0.777531730269581 -0.619341074030194,0.574992947308119 -0.515804608307302,0.5674228478321 -0.517697133176307,0.516324676368964 -0.411715740512025,0.499291952547919 -0.37965636923108,0.76721669825296 -0.619341074030194,0.777531730269581</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>-0.322407491943678,0.506161817822407 -0.185010186453913,0.735157326972015 -0.046855871016547,0.428568298193201 -0.322407491943678,0.506161817822407</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>4</ogr:intval>
</ogr:polys_to_orth>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,20 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>orthagonal_lines</Name>
<ElementPath>orthagonal_lines</ElementPath>
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
<ExtentXMin>-1.17356</ExtentXMin>
<ExtentXMax>0.68704</ExtentXMax>
<ExtentYMin>-0.96433</ExtentYMin>
<ExtentYMax>1.12603</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

0 comments on commit 5aa1539

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