Skip to content

Commit 4b6f3a3

Browse files
committed
[FEATURE][processing] New algorithm to orthagonalize geometries
Adds a new QgsGeometry::orthagonalize method which tries to make angles in geometries either right angles or straight lines Also adds a processing algorithm exposing this feature.
1 parent 379e7a4 commit 4b6f3a3

18 files changed

+689
-1
lines changed

python/core/geometry/qgsgeometry.sip

+10
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,16 @@ class QgsGeometry
420420
*/
421421
QgsGeometry orientedMinimumBoundingBox( double& area /Out/, double &angle /Out/, double& width /Out/, double& height /Out/ ) const;
422422

423+
/**
424+
* Attempts to orthagonalize a line or polygon geometry by shifting vertices to make the geometries
425+
* angles either right angles or flat lines. This is an iterative algorithm which will loop until
426+
* either the vertices are within a specified tolerance of right angles or a set number of maximum
427+
* iterations is reached. The angle threshold parameter specifies how close to a right angle or
428+
* straight line an angle must be before it is attempted to be straightened.
429+
* @note added in QGIS 3.0
430+
*/
431+
QgsGeometry orthagonalize( double tolerance = 1.0E-8, int maxIterations = 1000, double angleThreshold = 15.0 ) const;
432+
423433
/** Test for intersection with a rectangle (uses GEOS) */
424434
bool intersects( const QgsRectangle& r ) const;
425435

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

+7
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,13 @@ qgis:orientedminimumboundingbox: >
320320

321321
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.
322322

323+
qgis:orthagonalize: >
324+
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.
325+
326+
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.
327+
328+
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.
329+
323330
qgis:pointsalonglines: >
324331
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.
325332

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
Orthagonalize.py
6+
----------------
7+
Date : December 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__ = 'December 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+
29+
from processing.core.GeoAlgorithm import GeoAlgorithm
30+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
31+
from processing.core.parameters import ParameterVector, ParameterNumber
32+
from processing.core.outputs import OutputVector
33+
from processing.tools import dataobjects, vector
34+
35+
36+
class Orthagonalize(GeoAlgorithm):
37+
38+
INPUT_LAYER = 'INPUT_LAYER'
39+
OUTPUT_LAYER = 'OUTPUT_LAYER'
40+
MAX_ITERATIONS = 'MAX_ITERATIONS'
41+
DISTANCE_THRESHOLD = 'DISTANCE_THRESHOLD'
42+
ANGLE_TOLERANCE = 'ANGLE_TOLERANCE'
43+
44+
def defineCharacteristics(self):
45+
self.name, self.i18n_name = self.trAlgorithm('Orthagonalize')
46+
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
47+
self.tags = self.tr('rectangle,perpendicular,right,angles,square,quadrilateralise')
48+
49+
self.addParameter(ParameterVector(self.INPUT_LAYER,
50+
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE,
51+
dataobjects.TYPE_VECTOR_POLYGON]))
52+
self.addParameter(ParameterNumber(self.ANGLE_TOLERANCE,
53+
self.tr('Maximum angle tolerance (degrees)'),
54+
0.0, 45.0, 15.0))
55+
56+
max_iterations = ParameterNumber(self.MAX_ITERATIONS,
57+
self.tr('Maximum algorithm iterations'),
58+
1, 10000, 1000)
59+
max_iterations.isAdvanced = True
60+
self.addParameter(max_iterations)
61+
62+
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Orthagonalized')))
63+
64+
def processAlgorithm(self, progress):
65+
layer = dataobjects.getObjectFromUri(
66+
self.getParameterValue(self.INPUT_LAYER))
67+
max_iterations = self.getParameterValue(self.MAX_ITERATIONS)
68+
angle_tolerance = self.getParameterValue(self.ANGLE_TOLERANCE)
69+
writer = self.getOutputFromName(
70+
self.OUTPUT_LAYER).getVectorWriter(
71+
layer.fields(),
72+
layer.wkbType(),
73+
layer.crs())
74+
75+
features = vector.features(layer)
76+
total = 100.0 / len(features)
77+
78+
for current, input_feature in enumerate(features):
79+
output_feature = input_feature
80+
input_geometry = input_feature.geometry()
81+
if input_geometry:
82+
output_geometry = input_geometry.orthagonalize(1.0e-8, max_iterations, angle_tolerance)
83+
if not output_geometry:
84+
raise GeoAlgorithmExecutionException(
85+
self.tr('Error orthagonalizing geometry'))
86+
87+
output_feature.setGeometry(output_geometry)
88+
89+
writer.addFeature(output_feature)
90+
progress.setPercentage(int(current * total))
91+
92+
del writer

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
from .DropGeometry import DropGeometry
184184
from .BasicStatistics import BasicStatisticsForField
185185
from .Heatmap import Heatmap
186+
from .Orthagonalize import Orthagonalize
186187

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

253255
if hasMatplotlib:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>lines_to_orth</Name>
4+
<ElementPath>lines_to_orth</ElementPath>
5+
<GeometryType>2</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>4</FeatureCount>
9+
<ExtentXMin>-1.17586</ExtentXMin>
10+
<ExtentXMax>0.68704</ExtentXMax>
11+
<ExtentYMin>-0.96433</ExtentYMin>
12+
<ExtentYMax>1.11582</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
<PropertyDefn>
15+
<Name>intval</Name>
16+
<ElementPath>intval</ElementPath>
17+
<Type>Integer</Type>
18+
</PropertyDefn>
19+
</GMLFeatureClass>
20+
</GMLFeatureClassList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.175859979156524</gml:X><gml:Y>-0.9643318489227042</gml:Y></gml:coord>
10+
<gml:coord><gml:X>0.6870430854602452</gml:X><gml:Y>1.115824129342566</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:lines_to_orth fid="lines_to_orth.0">
16+
<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>
17+
<ogr:intval>0</ogr:intval>
18+
</ogr:lines_to_orth>
19+
</gml:featureMember>
20+
<gml:featureMember>
21+
<ogr:lines_to_orth fid="lines_to_orth.1">
22+
<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>
23+
<ogr:intval>1</ogr:intval>
24+
</ogr:lines_to_orth>
25+
</gml:featureMember>
26+
<gml:featureMember>
27+
<ogr:lines_to_orth fid="lines_to_orth.2">
28+
<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>
29+
<ogr:intval>2</ogr:intval>
30+
</ogr:lines_to_orth>
31+
</gml:featureMember>
32+
<gml:featureMember>
33+
<ogr:lines_to_orth fid="lines_to_orth.3">
34+
<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>
35+
<ogr:intval>3</ogr:intval>
36+
</ogr:lines_to_orth>
37+
</gml:featureMember>
38+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>polys_to_orth</Name>
4+
<ElementPath>polys_to_orth</ElementPath>
5+
<GeometryType>3</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>4</FeatureCount>
9+
<ExtentXMin>-0.70300</ExtentXMin>
10+
<ExtentXMax>0.92360</ExtentXMax>
11+
<ExtentYMin>-1.55335</ExtentYMin>
12+
<ExtentYMax>0.89200</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
<PropertyDefn>
15+
<Name>intval</Name>
16+
<ElementPath>intval</ElementPath>
17+
<Type>Integer</Type>
18+
</PropertyDefn>
19+
</GMLFeatureClass>
20+
</GMLFeatureClassList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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>-0.703</gml:X><gml:Y>-1.553346855983773</gml:Y></gml:coord>
10+
<gml:coord><gml:X>0.923603171676194</gml:X><gml:Y>0.892</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:polys_to_orth fid="polys_to_orth.0">
16+
<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>
17+
<ogr:intval>3</ogr:intval>
18+
</ogr:polys_to_orth>
19+
</gml:featureMember>
20+
<gml:featureMember>
21+
<ogr:polys_to_orth fid="polys_to_orth.1">
22+
<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>
23+
<ogr:intval>1</ogr:intval>
24+
</ogr:polys_to_orth>
25+
</gml:featureMember>
26+
<gml:featureMember>
27+
<ogr:polys_to_orth fid="polys_to_orth.2">
28+
<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>
29+
<ogr:intval>2</ogr:intval>
30+
</ogr:polys_to_orth>
31+
</gml:featureMember>
32+
<gml:featureMember>
33+
<ogr:polys_to_orth fid="polys_to_orth.3">
34+
<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>
35+
<ogr:intval>4</ogr:intval>
36+
</ogr:polys_to_orth>
37+
</gml:featureMember>
38+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>orthagonal_lines</Name>
4+
<ElementPath>orthagonal_lines</ElementPath>
5+
<GeometryType>2</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>4</FeatureCount>
9+
<ExtentXMin>-1.17356</ExtentXMin>
10+
<ExtentXMax>0.68704</ExtentXMax>
11+
<ExtentYMin>-0.96433</ExtentYMin>
12+
<ExtentYMax>1.12603</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
<PropertyDefn>
15+
<Name>intval</Name>
16+
<ElementPath>intval</ElementPath>
17+
<Type>Integer</Type>
18+
</PropertyDefn>
19+
</GMLFeatureClass>
20+
</GMLFeatureClassList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.173559243727069</gml:X><gml:Y>-0.9643318490464938</gml:Y></gml:coord>
10+
<gml:coord><gml:X>0.687043085460245</gml:X><gml:Y>1.126027868738941</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:orthagonal_lines fid="lines_to_orth.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.074456310482982,-0.916199588298252 0.048128551164703,-0.964331848922704 0.062280009502849,-0.634278538511395 0.687043085460245,-0.661066056858086</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
<ogr:intval>0</ogr:intval>
18+
</ogr:orthagonal_lines>
19+
</gml:featureMember>
20+
<gml:featureMember>
21+
<ogr:orthagonal_lines fid="lines_to_orth.1">
22+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.074456310482982,-0.916199588298252 0.048128551278313,-0.964331849046494 0.062280009603929,-0.634278538621531 0.687043085460245,-0.661066056858086</gml:coordinates></gml:LineString></ogr:geometryProperty>
23+
<ogr:intval>1</ogr:intval>
24+
</ogr:orthagonal_lines>
25+
</gml:featureMember>
26+
<gml:featureMember>
27+
<ogr:orthagonal_lines fid="lines_to_orth.2">
28+
<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>
29+
<ogr:intval>2</ogr:intval>
30+
</ogr:orthagonal_lines>
31+
</gml:featureMember>
32+
<gml:featureMember>
33+
<ogr:orthagonal_lines fid="lines_to_orth.3">
34+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1.1679544331119,0.483380445772577 -1.173559243727069,1.112790863745332 0.312933083255933,1.126027868738941 0.319179574408909,0.42455808473015 -1.065182334531776,0.412230531370954</gml:coordinates></gml:LineString></ogr:geometryProperty>
35+
<ogr:intval>3</ogr:intval>
36+
</ogr:orthagonal_lines>
37+
</gml:featureMember>
38+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>orthagonal_polys</Name>
4+
<ElementPath>orthagonal_polys</ElementPath>
5+
<GeometryType>3</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>4</FeatureCount>
9+
<ExtentXMin>-0.72569</ExtentXMin>
10+
<ExtentXMax>0.92360</ExtentXMax>
11+
<ExtentYMin>-1.55335</ExtentYMin>
12+
<ExtentYMax>0.89200</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
<PropertyDefn>
15+
<Name>intval</Name>
16+
<ElementPath>intval</ElementPath>
17+
<Type>Integer</Type>
18+
</PropertyDefn>
19+
</GMLFeatureClass>
20+
</GMLFeatureClassList>

0 commit comments

Comments
 (0)