Skip to content

Commit d6f09c0

Browse files
committed
[FEATURE] Add method to calculate pole of inaccessibility for polygons
Implements a method in QgsGeometry and a processing algorithm to calculate the pole of inaccessibility for a surface, which is the most distant internal point from the boundary of the surface. This function uses the 'polylabel' algorithm (Vladimir Agafonkin, 2016), which is an iterative approach guaranteed to find the true pole of inaccessibility within a specified tolerance. More precise tolerances require more iterations and will take longer to calculate.
1 parent d5c307e commit d6f09c0

File tree

13 files changed

+538
-7
lines changed

13 files changed

+538
-7
lines changed

python/core/geometry/qgsgeometry.sip

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,15 +510,37 @@ class QgsGeometry
510510
/** Returns a simplified version of this geometry using a specified tolerance value */
511511
QgsGeometry simplify( double tolerance ) const;
512512

513-
/** Returns the center of mass of a geometry
513+
/**
514+
* Returns the center of mass of a geometry.
514515
* @note for line based geometries, the center point of the line is returned,
515516
* and for point based geometries, the point itself is returned
517+
* @see pointOnSurface()
518+
* @see poleOfInaccessibility()
516519
*/
517520
QgsGeometry centroid() const;
518521

519-
/** Returns a point within a geometry */
522+
/**
523+
* Returns a point guaranteed to lie on the surface of a geometry. While the centroid()
524+
* of a geometry may be located outside of the geometry itself (eg for concave shapes),
525+
* the point on surface will always be inside the geometry.
526+
* @see centroid()
527+
* @see poleOfInaccessibility()
528+
*/
520529
QgsGeometry pointOnSurface() const;
521530

531+
/**
532+
* Calculates the approximate pole of inaccessibility for a surface, which is the
533+
* most distant internal point from the boundary of the surface. This function
534+
* uses the 'polylabel' algorithm (Vladimir Agafonkin, 2016), which is an iterative
535+
* approach guaranteed to find the true pole of inaccessibility within a specified
536+
* tolerance. More precise tolerances require more iterations and will take longer
537+
* to calculate.
538+
* @see centroid()
539+
* @see pointOnSurface()
540+
* @note added in QGIS 3.0
541+
*/
542+
QgsGeometry poleOfInaccessibility( double precision ) const;
543+
522544
/** Returns the smallest convex polygon that contains all the points in the geometry. */
523545
QgsGeometry convexHull() const;
524546

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ qgis:polarplot: >
337337

338338
Two fields must be entered as parameters: one that define the category each feature two (to group features) and another one with the variable to plot (this has to be a numeric one)
339339

340+
qgis:poleofinaccessibility: >
341+
This algorithm calculates the pole of inaccessibility for a polygon layer, which is the most distant internal point from the boundary of the surface. This algorithm uses the 'polylabel' algorithm (Vladimir Agafonkin, 2016), which is an iterative approach guaranteed to find the true pole of inaccessibility within a specified tolerance (in layer units). More precise tolerances require more iterations and will take longer to calculate.
342+
340343
qgis:polygoncentroids: >
341344
This algorithm creates a new point layer, with points representing the centroid of polygons of an input layer.
342345

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class PointOnSurface(GeoAlgorithm):
4545
INPUT_LAYER = 'INPUT_LAYER'
4646
OUTPUT_LAYER = 'OUTPUT_LAYER'
4747

48+
def getIcon(self):
49+
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'centroids.png'))
50+
4851
def defineCharacteristics(self):
4952
self.name, self.i18n_name = self.trAlgorithm('Point on surface')
5053
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
PoleOfInaccessibility.py
6+
------------------------
7+
Date : November 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__ = 'November 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 os
29+
30+
from qgis.core import QgsGeometry, QgsWkbTypes
31+
32+
from qgis.PyQt.QtGui import QIcon
33+
34+
from processing.core.GeoAlgorithm import GeoAlgorithm
35+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
36+
from processing.core.parameters import ParameterVector, ParameterNumber
37+
from processing.core.outputs import OutputVector
38+
from processing.tools import dataobjects, vector
39+
40+
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
41+
42+
43+
class PoleOfInaccessibility(GeoAlgorithm):
44+
45+
INPUT_LAYER = 'INPUT_LAYER'
46+
TOLERANCE = 'TOLERANCE'
47+
OUTPUT_LAYER = 'OUTPUT_LAYER'
48+
49+
def getIcon(self):
50+
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'centroids.png'))
51+
52+
def defineCharacteristics(self):
53+
self.name, self.i18n_name = self.trAlgorithm('Pole of Inaccessibility')
54+
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
55+
56+
self.addParameter(ParameterVector(self.INPUT_LAYER,
57+
self.tr('Input layer'),
58+
[dataobjects.TYPE_VECTOR_POLYGON]))
59+
self.addParameter(ParameterNumber(self.TOLERANCE,
60+
self.tr('Tolerance (layer units)'), default=1.0, minValue=0.0))
61+
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Point'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
62+
63+
def processAlgorithm(self, progress):
64+
layer = dataobjects.getObjectFromUri(
65+
self.getParameterValue(self.INPUT_LAYER))
66+
tolerance = self.getParameterValue(self.TOLERANCE)
67+
68+
writer = self.getOutputFromName(
69+
self.OUTPUT_LAYER).getVectorWriter(
70+
layer.fields(),
71+
QgsWkbTypes.Point,
72+
layer.crs())
73+
74+
features = vector.features(layer)
75+
total = 100.0 / len(features)
76+
77+
for current, input_feature in enumerate(features):
78+
output_feature = input_feature
79+
input_geometry = input_feature.geometry()
80+
if input_geometry:
81+
output_geometry = input_geometry.poleOfInaccessibility(tolerance)
82+
if not output_geometry:
83+
raise GeoAlgorithmExecutionException(
84+
self.tr('Error calculating pole of inaccessibility'))
85+
86+
output_feature.setGeometry(output_geometry)
87+
88+
writer.addFeature(output_feature)
89+
progress.setPercentage(int(current * total))
90+
91+
del writer

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
from .ExtractSpecificNodes import ExtractSpecificNodes
177177
from .GeometryByExpression import GeometryByExpression
178178
from .SnapGeometries import SnapGeometriesToLayer
179+
from .PoleOfInaccessibility import PoleOfInaccessibility
179180

180181
pluginPath = os.path.normpath(os.path.join(
181182
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -238,7 +239,8 @@ def __init__(self):
238239
IdwInterpolationZValue(), IdwInterpolationAttribute(),
239240
TinInterpolationZValue(), TinInterpolationAttribute(),
240241
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
241-
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer()
242+
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer(),
243+
PoleOfInaccessibility()
242244
]
243245

244246
if hasMatplotlib:
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>pole_inaccessibility_polys</Name>
4+
<ElementPath>pole_inaccessibility_polys</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>6</FeatureCount>
10+
<ExtentXMin>0.50000</ExtentXMin>
11+
<ExtentXMax>6.58578</ExtentXMax>
12+
<ExtentYMin>-2.41422</ExtentYMin>
13+
<ExtentYMax>5.50000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>name</Name>
17+
<ElementPath>name</ElementPath>
18+
<Type>String</Type>
19+
<Width>5</Width>
20+
</PropertyDefn>
21+
<PropertyDefn>
22+
<Name>intval</Name>
23+
<ElementPath>intval</ElementPath>
24+
<Type>Integer</Type>
25+
</PropertyDefn>
26+
<PropertyDefn>
27+
<Name>floatval</Name>
28+
<ElementPath>floatval</ElementPath>
29+
<Type>Real</Type>
30+
</PropertyDefn>
31+
</GMLFeatureClass>
32+
</GMLFeatureClassList>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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.5</gml:X><gml:Y>-2.414215087890625</gml:Y></gml:coord>
10+
<gml:coord><gml:X>6.585784912109375</gml:X><gml:Y>5.5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:pole_inaccessibility_polys fid="polys.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.5,0.5</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:name>aaaaa</ogr:name>
18+
<ogr:intval>33</ogr:intval>
19+
<ogr:floatval>44.123456</ogr:floatval>
20+
</ogr:pole_inaccessibility_polys>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:pole_inaccessibility_polys fid="polys.1">
24+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4.999996185302734,4.414211273193359</gml:coordinates></gml:Point></ogr:geometryProperty>
25+
<ogr:name>Aaaaa</ogr:name>
26+
<ogr:intval>-33</ogr:intval>
27+
<ogr:floatval>0</ogr:floatval>
28+
</ogr:pole_inaccessibility_polys>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:pole_inaccessibility_polys fid="polys.2">
32+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2.5,5.5</gml:coordinates></gml:Point></ogr:geometryProperty>
33+
<ogr:name>bbaaa</ogr:name>
34+
<ogr:floatval>0.123</ogr:floatval>
35+
</ogr:pole_inaccessibility_polys>
36+
</gml:featureMember>
37+
<gml:featureMember>
38+
<ogr:pole_inaccessibility_polys fid="polys.3">
39+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6.585784912109375,-2.414215087890625</gml:coordinates></gml:Point></ogr:geometryProperty>
40+
<ogr:name>ASDF</ogr:name>
41+
<ogr:intval>0</ogr:intval>
42+
</ogr:pole_inaccessibility_polys>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:pole_inaccessibility_polys fid="polys.4">
46+
<ogr:intval>120</ogr:intval>
47+
<ogr:floatval>-100291.43213</ogr:floatval>
48+
</ogr:pole_inaccessibility_polys>
49+
</gml:featureMember>
50+
<gml:featureMember>
51+
<ogr:pole_inaccessibility_polys fid="polys.5">
52+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4.289703369140625,-0.232696533203125</gml:coordinates></gml:Point></ogr:geometryProperty>
53+
<ogr:name>elim</ogr:name>
54+
<ogr:intval>2</ogr:intval>
55+
<ogr:floatval>3.33</ogr:floatval>
56+
</ogr:pole_inaccessibility_polys>
57+
</gml:featureMember>
58+
</ogr:FeatureCollection>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,3 +1464,15 @@ tests:
14641464
OUTPUT:
14651465
name: expected/snap_point_to_lines_prefer_closest.gml
14661466
type: vector
1467+
1468+
- algorithm: qgis:poleofinaccessibility
1469+
name: Pole of inaccessibility (polygons)
1470+
params:
1471+
INPUT_LAYER:
1472+
name: polys.gml
1473+
type: vector
1474+
TOLERANCE: 1.0e-05
1475+
results:
1476+
OUTPUT_LAYER:
1477+
name: expected/pole_inaccessibility_polys.gml
1478+
type: vector

src/core/geometry/qgsgeometry.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,13 @@ QgsGeometry QgsGeometry::pointOnSurface() const
14801480
return QgsGeometry( pt.clone() );
14811481
}
14821482

1483+
QgsGeometry QgsGeometry::poleOfInaccessibility( double precision ) const
1484+
{
1485+
QgsInternalGeometryEngine engine( *this );
1486+
1487+
return engine.poleOfInaccessibility( precision );
1488+
}
1489+
14831490
QgsGeometry QgsGeometry::convexHull() const
14841491
{
14851492
if ( !d->geometry )

src/core/geometry/qgsgeometry.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,15 +563,37 @@ class CORE_EXPORT QgsGeometry
563563
//! Returns a simplified version of this geometry using a specified tolerance value
564564
QgsGeometry simplify( double tolerance ) const;
565565

566-
/** Returns the center of mass of a geometry
566+
/**
567+
* Returns the center of mass of a geometry.
567568
* @note for line based geometries, the center point of the line is returned,
568569
* and for point based geometries, the point itself is returned
570+
* @see pointOnSurface()
571+
* @see poleOfInaccessibility()
569572
*/
570573
QgsGeometry centroid() const;
571574

572-
//! Returns a point within a geometry
575+
/**
576+
* Returns a point guaranteed to lie on the surface of a geometry. While the centroid()
577+
* of a geometry may be located outside of the geometry itself (eg for concave shapes),
578+
* the point on surface will always be inside the geometry.
579+
* @see centroid()
580+
* @see poleOfInaccessibility()
581+
*/
573582
QgsGeometry pointOnSurface() const;
574583

584+
/**
585+
* Calculates the approximate pole of inaccessibility for a surface, which is the
586+
* most distant internal point from the boundary of the surface. This function
587+
* uses the 'polylabel' algorithm (Vladimir Agafonkin, 2016), which is an iterative
588+
* approach guaranteed to find the true pole of inaccessibility within a specified
589+
* tolerance. More precise tolerances require more iterations and will take longer
590+
* to calculate.
591+
* @see centroid()
592+
* @see pointOnSurface()
593+
* @note added in QGIS 3.0
594+
*/
595+
QgsGeometry poleOfInaccessibility( double precision ) const;
596+
575597
//! Returns the smallest convex polygon that contains all the points in the geometry.
576598
QgsGeometry convexHull() const;
577599

0 commit comments

Comments
 (0)