Skip to content

Commit d5c307e

Browse files
committed
Add method to find distance from a point to a poylgon's boundary
1 parent 1d3f1f0 commit d5c307e

File tree

4 files changed

+73
-0
lines changed

4 files changed

+73
-0
lines changed

python/core/geometry/qgspolygon.sip

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,12 @@ class QgsPolygonV2: public QgsCurvePolygon
3535
virtual void setExteriorRing( QgsCurve* ring /Transfer/ );
3636

3737
virtual QgsAbstractGeometry* boundary() const /Factory/;
38+
39+
/**
40+
* Returns the distance from a point to the boundary of the polygon (either the
41+
* exterior ring or any closer interior rings). The returned distance will be
42+
* negative if the point lies outside the polygon.
43+
* @note added in QGIS 3.0
44+
*/
45+
double pointDistanceToBoundary( double x, double y ) const;
3846
};

src/core/geometry/qgspolygon.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,40 @@ QgsAbstractGeometry* QgsPolygonV2::boundary() const
261261
}
262262
}
263263

264+
double QgsPolygonV2::pointDistanceToBoundary( double x, double y ) const
265+
{
266+
if ( !mExteriorRing )
267+
return std::numeric_limits< double >::quiet_NaN();
268+
269+
bool inside = false;
270+
double minimumDistance = DBL_MAX;
271+
double minDistX = 0.0;
272+
double minDistY = 0.0;
273+
274+
int numRings = mInteriorRings.size() + 1;
275+
for ( int ringIndex = 0; ringIndex < numRings; ++ringIndex )
276+
{
277+
const QgsLineString* ring = static_cast< const QgsLineString* >( ringIndex == 0 ? mExteriorRing : mInteriorRings.at( ringIndex - 1 ) );
278+
279+
int len = ring->numPoints() - 1; //assume closed
280+
for ( int i = 0, j = len - 1; i < len; j = i++ )
281+
{
282+
double aX = ring->xAt( i );
283+
double aY = ring->yAt( i );
284+
double bX = ring->xAt( j );
285+
double bY = ring->yAt( j );
286+
287+
if ((( aY > y ) != ( bY > y ) ) &&
288+
( x < ( bX - aX ) * ( y - aY ) / ( bY - aY ) + aX ) )
289+
inside = !inside;
290+
291+
minimumDistance = qMin( minimumDistance, QgsGeometryUtils::sqrDistToLine( x, y, aX, aY, bX, bY, minDistX, minDistY, 4 * DBL_EPSILON ) );
292+
}
293+
}
294+
295+
return ( inside ? 1 : -1 ) * sqrt( minimumDistance );
296+
}
297+
264298
QgsPolygonV2* QgsPolygonV2::surfaceToPolygon() const
265299
{
266300
return clone();

src/core/geometry/qgspolygon.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,13 @@ class CORE_EXPORT QgsPolygonV2: public QgsCurvePolygon
6060

6161
virtual QgsAbstractGeometry* boundary() const override;
6262

63+
/**
64+
* Returns the distance from a point to the boundary of the polygon (either the
65+
* exterior ring or any closer interior rings). The returned distance will be
66+
* negative if the point lies outside the polygon.
67+
* @note added in QGIS 3.0
68+
*/
69+
double pointDistanceToBoundary( double x, double y ) const;
70+
6371
};
6472
#endif // QGSPOLYGONV2_H

tests/src/core/testqgsgeometry.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,6 +3026,29 @@ void TestQgsGeometry::polygon()
30263026
QCOMPARE( lineBoundary->zAt( 3 ), 10.0 );
30273027
delete boundary;
30283028

3029+
// point distance to boundary
3030+
3031+
QgsLineString pd1;
3032+
pd1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0, 0 ) << QgsPointV2( 1, 0 ) << QgsPointV2( 1, 1 ) << QgsPointV2( 0, 1 ) << QgsPointV2( 0, 0 ) );
3033+
QgsPolygonV2 pd;
3034+
// no meaning, but let's not crash
3035+
( void )pd.pointDistanceToBoundary( 0, 0 );
3036+
3037+
pd.setExteriorRing( pd1.clone() );
3038+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0, 0.5 ), 0.0, 0.0000000001 );
3039+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.1, 0.5 ), 0.1, 0.0000000001 );
3040+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( -0.1, 0.5 ), -0.1, 0.0000000001 );
3041+
// with a ring
3042+
QgsLineString pdRing1;
3043+
pdRing1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0.1, 0.1 ) << QgsPointV2( 0.2, 0.1 ) << QgsPointV2( 0.2, 0.6 ) << QgsPointV2( 0.1, 0.6 ) << QgsPointV2( 0.1, 0.1 ) );
3044+
pd.setInteriorRings( QList< QgsCurve* >() << pdRing1.clone() );
3045+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0, 0.5 ), 0.0, 0.0000000001 );
3046+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.1, 0.5 ), 0.0, 0.0000000001 );
3047+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.01, 0.5 ), 0.01, 0.0000000001 );
3048+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.08, 0.5 ), 0.02, 0.0000000001 );
3049+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.12, 0.5 ), -0.02, 0.0000000001 );
3050+
QGSCOMPARENEAR( pd.pointDistanceToBoundary( -0.1, 0.5 ), -0.1, 0.0000000001 );
3051+
30293052
}
30303053

30313054
void TestQgsGeometry::multiPoint()

0 commit comments

Comments
 (0)