Add method to find distance from a point to a poylgon's boundary

This commit is contained in:
Nyall Dawson 2016-11-10 15:58:18 +10:00
parent 1d3f1f07e8
commit d5c307eb05
4 changed files with 73 additions and 0 deletions

View File

@ -35,4 +35,12 @@ class QgsPolygonV2: public QgsCurvePolygon
virtual void setExteriorRing( QgsCurve* ring /Transfer/ ); virtual void setExteriorRing( QgsCurve* ring /Transfer/ );
virtual QgsAbstractGeometry* boundary() const /Factory/; virtual QgsAbstractGeometry* boundary() const /Factory/;
/**
* Returns the distance from a point to the boundary of the polygon (either the
* exterior ring or any closer interior rings). The returned distance will be
* negative if the point lies outside the polygon.
* @note added in QGIS 3.0
*/
double pointDistanceToBoundary( double x, double y ) const;
}; };

View File

@ -261,6 +261,40 @@ QgsAbstractGeometry* QgsPolygonV2::boundary() const
} }
} }
double QgsPolygonV2::pointDistanceToBoundary( double x, double y ) const
{
if ( !mExteriorRing )
return std::numeric_limits< double >::quiet_NaN();
bool inside = false;
double minimumDistance = DBL_MAX;
double minDistX = 0.0;
double minDistY = 0.0;
int numRings = mInteriorRings.size() + 1;
for ( int ringIndex = 0; ringIndex < numRings; ++ringIndex )
{
const QgsLineString* ring = static_cast< const QgsLineString* >( ringIndex == 0 ? mExteriorRing : mInteriorRings.at( ringIndex - 1 ) );
int len = ring->numPoints() - 1; //assume closed
for ( int i = 0, j = len - 1; i < len; j = i++ )
{
double aX = ring->xAt( i );
double aY = ring->yAt( i );
double bX = ring->xAt( j );
double bY = ring->yAt( j );
if ((( aY > y ) != ( bY > y ) ) &&
( x < ( bX - aX ) * ( y - aY ) / ( bY - aY ) + aX ) )
inside = !inside;
minimumDistance = qMin( minimumDistance, QgsGeometryUtils::sqrDistToLine( x, y, aX, aY, bX, bY, minDistX, minDistY, 4 * DBL_EPSILON ) );
}
}
return ( inside ? 1 : -1 ) * sqrt( minimumDistance );
}
QgsPolygonV2* QgsPolygonV2::surfaceToPolygon() const QgsPolygonV2* QgsPolygonV2::surfaceToPolygon() const
{ {
return clone(); return clone();

View File

@ -60,5 +60,13 @@ class CORE_EXPORT QgsPolygonV2: public QgsCurvePolygon
virtual QgsAbstractGeometry* boundary() const override; virtual QgsAbstractGeometry* boundary() const override;
/**
* Returns the distance from a point to the boundary of the polygon (either the
* exterior ring or any closer interior rings). The returned distance will be
* negative if the point lies outside the polygon.
* @note added in QGIS 3.0
*/
double pointDistanceToBoundary( double x, double y ) const;
}; };
#endif // QGSPOLYGONV2_H #endif // QGSPOLYGONV2_H

View File

@ -3026,6 +3026,29 @@ void TestQgsGeometry::polygon()
QCOMPARE( lineBoundary->zAt( 3 ), 10.0 ); QCOMPARE( lineBoundary->zAt( 3 ), 10.0 );
delete boundary; delete boundary;
// point distance to boundary
QgsLineString pd1;
pd1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0, 0 ) << QgsPointV2( 1, 0 ) << QgsPointV2( 1, 1 ) << QgsPointV2( 0, 1 ) << QgsPointV2( 0, 0 ) );
QgsPolygonV2 pd;
// no meaning, but let's not crash
( void )pd.pointDistanceToBoundary( 0, 0 );
pd.setExteriorRing( pd1.clone() );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0, 0.5 ), 0.0, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.1, 0.5 ), 0.1, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( -0.1, 0.5 ), -0.1, 0.0000000001 );
// with a ring
QgsLineString pdRing1;
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 ) );
pd.setInteriorRings( QList< QgsCurve* >() << pdRing1.clone() );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0, 0.5 ), 0.0, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.1, 0.5 ), 0.0, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.01, 0.5 ), 0.01, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.08, 0.5 ), 0.02, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.12, 0.5 ), -0.02, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( -0.1, 0.5 ), -0.1, 0.0000000001 );
} }
void TestQgsGeometry::multiPoint() void TestQgsGeometry::multiPoint()