Skip to content

Commit 409dfdf

Browse files
committed
[FEATURE] Expose GEOS linear referencing function to QgsGeometry
Adds a new QgsGeometry::lineLocatePoint() function for retrieving the distance along a linestring to the nearest position on the linestring to a given point.
1 parent 1df8474 commit 409dfdf

File tree

6 files changed

+108
-1
lines changed

6 files changed

+108
-1
lines changed

python/core/geometry/qgsgeometry.sip

+14-1
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,21 @@ class QgsGeometry
527527
/**
528528
* Return interpolated point on line at distance
529529
* @note added in 1.9
530+
* @see lineLocatePoint()
531+
*/
532+
QgsGeometry interpolate( double distance ) const;
533+
534+
/** Returns a distance representing the location along this linestring of the closest point
535+
* on this linestring geometry to the specified point. Ie, the returned value indicates
536+
* how far along this linestring you need to traverse to get to the closest location
537+
* where this linestring comes to the specified point.
538+
* @param point point to seek proximity to
539+
* @return distance along line, or -1 on error
540+
* @note only valid for linestring geometries
541+
* @see interpolate()
542+
* @note added in QGIS 3.0
530543
*/
531-
QgsGeometry interpolate( double distance );
544+
double lineLocatePoint( const QgsGeometry& point ) const;
532545

533546
/** Returns a geometry representing the points shared by this geometry and other. */
534547
QgsGeometry intersection( const QgsGeometry& geometry ) const;

src/core/geometry/qgsgeometry.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,24 @@ QgsGeometry QgsGeometry::interpolate( double distance ) const
14711471
return QgsGeometry( result );
14721472
}
14731473

1474+
double QgsGeometry::lineLocatePoint( const QgsGeometry& point ) const
1475+
{
1476+
if ( type() != QgsWkbTypes::LineGeometry )
1477+
return -1;
1478+
1479+
if ( QgsWkbTypes::flatType( point.wkbType() ) != QgsWkbTypes::Point )
1480+
return -1;
1481+
1482+
QgsGeometry segmentized = *this;
1483+
if ( QgsWkbTypes::isCurvedType( wkbType() ) )
1484+
{
1485+
segmentized = QgsGeometry( static_cast< QgsCurve* >( d->geometry )->segmentize() );
1486+
}
1487+
1488+
QgsGeos geos( d->geometry );
1489+
return geos.lineLocatePoint( *( static_cast< QgsPointV2* >( point.d->geometry ) ) );
1490+
}
1491+
14741492
QgsGeometry QgsGeometry::intersection( const QgsGeometry& geometry ) const
14751493
{
14761494
if ( !d->geometry || geometry.isEmpty() )

src/core/geometry/qgsgeometry.h

+13
Original file line numberDiff line numberDiff line change
@@ -564,9 +564,22 @@ class CORE_EXPORT QgsGeometry
564564
/**
565565
* Return interpolated point on line at distance
566566
* @note added in 1.9
567+
* @see lineLocatePoint()
567568
*/
568569
QgsGeometry interpolate( double distance ) const;
569570

571+
/** Returns a distance representing the location along this linestring of the closest point
572+
* on this linestring geometry to the specified point. Ie, the returned value indicates
573+
* how far along this linestring you need to traverse to get to the closest location
574+
* where this linestring comes to the specified point.
575+
* @param point point to seek proximity to
576+
* @return distance along line, or -1 on error
577+
* @note only valid for linestring geometries
578+
* @see interpolate()
579+
* @note added in QGIS 3.0
580+
*/
581+
double lineLocatePoint( const QgsGeometry& point ) const;
582+
570583
/** Returns a geometry representing the points shared by this geometry and other. */
571584
QgsGeometry intersection( const QgsGeometry& geometry ) const;
572585

src/core/geometry/qgsgeos.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -1911,6 +1911,36 @@ QgsGeometry QgsGeos::shortestLine( const QgsGeometry& other, QString* errorMsg )
19111911
return QgsGeometry( line );
19121912
}
19131913

1914+
double QgsGeos::lineLocatePoint( const QgsPointV2& point, QString* errorMsg ) const
1915+
{
1916+
if ( !mGeos )
1917+
{
1918+
return -1;
1919+
}
1920+
1921+
GEOSGeomScopedPtr otherGeom( asGeos( &point, mPrecision ) );
1922+
if ( !otherGeom )
1923+
{
1924+
return -1;
1925+
}
1926+
1927+
double distance = -1;
1928+
try
1929+
{
1930+
distance = GEOSProject_r( geosinit.ctxt, mGeos, otherGeom.get() );
1931+
}
1932+
catch ( GEOSException &e )
1933+
{
1934+
if ( errorMsg )
1935+
{
1936+
*errorMsg = e.what();
1937+
}
1938+
return -1;
1939+
}
1940+
1941+
return distance;
1942+
}
1943+
19141944

19151945
/** Extract coordinates of linestring's endpoints. Returns false on error. */
19161946
static bool _linestringEndpoints( const GEOSGeometry* linestring, double& x1, double& y1, double& x2, double& y2 )

src/core/geometry/qgsgeos.h

+11
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
128128
*/
129129
QgsGeometry shortestLine( const QgsGeometry& other, QString* errorMsg = nullptr ) const;
130130

131+
/** Returns a distance representing the location along this linestring of the closest point
132+
* on this linestring geometry to the specified point. Ie, the returned value indicates
133+
* how far along this linestring you need to traverse to get to the closest location
134+
* where this linestring comes to the specified point.
135+
* @param point point to seek proximity to
136+
* @param errorMsg error messages emitted, if any
137+
* @note only valid for linestring geometries
138+
* @return distance along line, or -1 on error
139+
*/
140+
double lineLocatePoint( const QgsPointV2& point, QString* errorMsg = nullptr ) const;
141+
131142
/** Create a geometry from a GEOSGeometry
132143
* @param geos GEOSGeometry. Ownership is NOT transferred.
133144
*/

tests/src/python/test_qgsgeometry.py

+22
Original file line numberDiff line numberDiff line change
@@ -3430,5 +3430,27 @@ def testMergeLines(self):
34303430
exp = 'MultiLineString((0 0, 10 10),(12 2, 14 4))'
34313431
self.assertTrue(compareWkt(result, exp, 0.00001), "Merge lines: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
34323432

3433+
def testLineLocatePoint(self):
3434+
""" test QgsGeometry.lineLocatePoint() """
3435+
3436+
# not a linestring
3437+
point = QgsGeometry.fromWkt('Point(1 2)')
3438+
self.assertEqual(point.lineLocatePoint(point), -1)
3439+
3440+
# not a point
3441+
linestring = QgsGeometry.fromWkt('LineString(0 0, 10 0)')
3442+
self.assertEqual(linestring.lineLocatePoint(linestring), -1)
3443+
3444+
# valid
3445+
self.assertEqual(linestring.lineLocatePoint(point), 1)
3446+
point = QgsGeometry.fromWkt('Point(9 -2)')
3447+
self.assertEqual(linestring.lineLocatePoint(point), 9)
3448+
3449+
# circular string
3450+
geom = QgsGeometry.fromWkt('CircularString (1 5, 6 2, 7 3)')
3451+
point = QgsGeometry.fromWkt('Point(9 -2)')
3452+
self.assertAlmostEqual(geom.lineLocatePoint(point), 7.372, places=3)
3453+
3454+
34333455
if __name__ == '__main__':
34343456
unittest.main()

0 commit comments

Comments
 (0)