Skip to content

Commit

Permalink
Merge pull request #3413 from nyalldawson/line_locate_point
Browse files Browse the repository at this point in the history
Linear referencing functions
  • Loading branch information
nyalldawson committed Aug 21, 2016
2 parents f64adb9 + e110ba7 commit 77750ad
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 1 deletion.
15 changes: 14 additions & 1 deletion python/core/geometry/qgsgeometry.sip
Expand Up @@ -527,8 +527,21 @@ class QgsGeometry
/**
* Return interpolated point on line at distance
* @note added in 1.9
* @see lineLocatePoint()
*/
QgsGeometry interpolate( double distance ) const;

/** Returns a distance representing the location along this linestring of the closest point
* on this linestring geometry to the specified point. Ie, the returned value indicates
* how far along this linestring you need to traverse to get to the closest location
* where this linestring comes to the specified point.
* @param point point to seek proximity to
* @return distance along line, or -1 on error
* @note only valid for linestring geometries
* @see interpolate()
* @note added in QGIS 3.0
*/
QgsGeometry interpolate( double distance );
double lineLocatePoint( const QgsGeometry& point ) const;

/** Returns a geometry representing the points shared by this geometry and other. */
QgsGeometry intersection( const QgsGeometry& geometry ) const;
Expand Down
8 changes: 8 additions & 0 deletions resources/function_help/json/line_interpolate_point
@@ -0,0 +1,8 @@
{
"name": "line_interpolate_point",
"type": "function",
"description": "Returns the point interpolated by a specified distance along a linestring geometry.",
"arguments": [ {"arg":"geometry","description":"a linestring geometry"},
{"arg":"distance","description":"distance along line to interpolate"}],
"examples": [ { "expression":"geom_to_wkt(line_interpolate_point(geometry:=geom_from_wkt('LineString(0 0, 10 0)'),distance:=5))", "returns":"'Point (5 0)'"}]
}
8 changes: 8 additions & 0 deletions resources/function_help/json/line_locate_point
@@ -0,0 +1,8 @@
{
"name": "line_locate_point",
"type": "function",
"description": "Returns the distance along a linestring corresponding to the closest position the linestring comes to a specified point geometry.",
"arguments": [ {"arg":"geometry","description":"a linestring geometry"},
{"arg":"point","description":"point geometry to locate closest position on linestring to"}],
"examples": [ { "expression":"line_locate_point(geometry:=geom_from_wkt('LineString(0 0, 10 0)'),point:=geom_from_wkt('Point(5 0)'))", "returns":"5.0"}]
}
18 changes: 18 additions & 0 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -1471,6 +1471,24 @@ QgsGeometry QgsGeometry::interpolate( double distance ) const
return QgsGeometry( result );
}

double QgsGeometry::lineLocatePoint( const QgsGeometry& point ) const
{
if ( type() != QgsWkbTypes::LineGeometry )
return -1;

if ( QgsWkbTypes::flatType( point.wkbType() ) != QgsWkbTypes::Point )
return -1;

QgsGeometry segmentized = *this;
if ( QgsWkbTypes::isCurvedType( wkbType() ) )
{
segmentized = QgsGeometry( static_cast< QgsCurve* >( d->geometry )->segmentize() );
}

QgsGeos geos( d->geometry );
return geos.lineLocatePoint( *( static_cast< QgsPointV2* >( point.d->geometry ) ) );
}

QgsGeometry QgsGeometry::intersection( const QgsGeometry& geometry ) const
{
if ( !d->geometry || geometry.isEmpty() )
Expand Down
13 changes: 13 additions & 0 deletions src/core/geometry/qgsgeometry.h
Expand Up @@ -564,9 +564,22 @@ class CORE_EXPORT QgsGeometry
/**
* Return interpolated point on line at distance
* @note added in 1.9
* @see lineLocatePoint()
*/
QgsGeometry interpolate( double distance ) const;

/** Returns a distance representing the location along this linestring of the closest point
* on this linestring geometry to the specified point. Ie, the returned value indicates
* how far along this linestring you need to traverse to get to the closest location
* where this linestring comes to the specified point.
* @param point point to seek proximity to
* @return distance along line, or -1 on error
* @note only valid for linestring geometries
* @see interpolate()
* @note added in QGIS 3.0
*/
double lineLocatePoint( const QgsGeometry& point ) const;

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

Expand Down
30 changes: 30 additions & 0 deletions src/core/geometry/qgsgeos.cpp
Expand Up @@ -1911,6 +1911,36 @@ QgsGeometry QgsGeos::shortestLine( const QgsGeometry& other, QString* errorMsg )
return QgsGeometry( line );
}

double QgsGeos::lineLocatePoint( const QgsPointV2& point, QString* errorMsg ) const
{
if ( !mGeos )
{
return -1;
}

GEOSGeomScopedPtr otherGeom( asGeos( &point, mPrecision ) );
if ( !otherGeom )
{
return -1;
}

double distance = -1;
try
{
distance = GEOSProject_r( geosinit.ctxt, mGeos, otherGeom.get() );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return -1;
}

return distance;
}


/** Extract coordinates of linestring's endpoints. Returns false on error. */
static bool _linestringEndpoints( const GEOSGeometry* linestring, double& x1, double& y1, double& x2, double& y2 )
Expand Down
11 changes: 11 additions & 0 deletions src/core/geometry/qgsgeos.h
Expand Up @@ -128,6 +128,17 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
*/
QgsGeometry shortestLine( const QgsGeometry& other, QString* errorMsg = nullptr ) const;

/** Returns a distance representing the location along this linestring of the closest point
* on this linestring geometry to the specified point. Ie, the returned value indicates
* how far along this linestring you need to traverse to get to the closest location
* where this linestring comes to the specified point.
* @param point point to seek proximity to
* @param errorMsg error messages emitted, if any
* @note only valid for linestring geometries
* @return distance along line, or -1 on error
*/
double lineLocatePoint( const QgsPointV2& point, QString* errorMsg = nullptr ) const;

/** Create a geometry from a GEOSGeometry
* @param geos GEOSGeometry. Ownership is NOT transferred.
*/
Expand Down
26 changes: 26 additions & 0 deletions src/core/qgsexpression.cpp
Expand Up @@ -2543,6 +2543,27 @@ static QVariant fcnShortestLine( const QVariantList& values, const QgsExpression
return result;
}

static QVariant fcnLineInterpolatePoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry lineGeom = getGeometry( values.at( 0 ), parent );
double distance = getDoubleValue( values.at( 1 ), parent );

QgsGeometry geom = lineGeom.interpolate( distance );

QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
return result;
}

static QVariant fcnLineLocatePoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry lineGeom = getGeometry( values.at( 0 ), parent );
QgsGeometry pointGeom = getGeometry( values.at( 1 ), parent );

double distance = lineGeom.lineLocatePoint( pointGeom );

return distance >= 0 ? distance : QVariant();
}

static QVariant fcnRound( const QVariantList& values, const QgsExpressionContext *, QgsExpression* parent )
{
if ( values.length() == 2 && values.at( 1 ).toInt() != 0 )
Expand Down Expand Up @@ -3121,6 +3142,7 @@ const QStringList& QgsExpression::BuiltinFunctions()
<< "bounds_width" << "bounds_height" << "is_closed" << "convex_hull" << "difference"
<< "distance" << "intersection" << "sym_difference" << "combine"
<< "extrude" << "azimuth" << "project" << "closest_point" << "shortest_line"
<< "line_locate_point" << "line_interpolate_point"
<< "union" << "geom_to_wkt" << "geomToWKT" << "geometry"
<< "transform" << "get_feature" << "getFeature"
<< "levenshtein" << "longest_common_substring" << "hamming_distance"
Expand Down Expand Up @@ -3345,6 +3367,10 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "order_parts", 3, fcnOrderParts, "GeometryGroup", QString() )
<< new StaticFunction( "closest_point", 2, fcnClosestPoint, "GeometryGroup" )
<< new StaticFunction( "shortest_line", 2, fcnShortestLine, "GeometryGroup" )
<< new StaticFunction( "line_interpolate_point", ParameterList() << Parameter( "geometry" )
<< Parameter( "distance" ), fcnLineInterpolatePoint, "GeometryGroup" )
<< new StaticFunction( "line_locate_point", ParameterList() << Parameter( "geometry" )
<< Parameter( "point" ), fcnLineLocatePoint, "GeometryGroup" )
<< new StaticFunction( "$id", 0, fcnFeatureId, "Record" )
<< new StaticFunction( "$currentfeature", 0, fcnFeature, "Record" )
<< new StaticFunction( "uuid", 0, fcnUuid, "Record", QString(), false, QStringList(), false, QStringList() << "$uuid" )
Expand Down
8 changes: 8 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -772,6 +772,14 @@ class TestQgsExpression: public QObject
QTest::newRow( "shortest_line geom" ) << "geom_to_wkt(shortest_line( geom_from_wkt('LineString( 1 1, 5 1, 5 5 )'),geom_from_wkt('Point( 6 3 )')))" << false << QVariant( "LineString (5 3, 6 3)" );
QTest::newRow( "shortest_line not geom" ) << "shortest_line('g','a')" << true << QVariant();
QTest::newRow( "shortest_line null" ) << "shortest_line(NULL,NULL)" << false << QVariant();
QTest::newRow( "line_interpolate_point not geom" ) << "line_interpolate_point('g', 5)" << true << QVariant();
QTest::newRow( "line_interpolate_point null" ) << "line_interpolate_point(NULL, 5)" << false << QVariant();
QTest::newRow( "line_interpolate_point point" ) << "line_interpolate_point(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant();
QTest::newRow( "line_interpolate_point line" ) << "geom_to_wkt(line_interpolate_point(geometry:=geom_from_wkt('LineString(0 0, 10 0)'),distance:=5))" << false << QVariant( "Point (5 0)" );
QTest::newRow( "line_locate_point not geom" ) << "line_locate_point('g', geom_from_wkt('Point 5 0'))" << false << QVariant();
QTest::newRow( "line_locate_point null" ) << "line_locate_point(NULL, geom_from_wkt('Point 5 0'))" << false << QVariant();
QTest::newRow( "line_locate_point point" ) << "line_locate_point(geom_from_wkt('POINT(1 2)'),geom_from_wkt('Point 5 0'))" << false << QVariant();
QTest::newRow( "line_locate_point line" ) << "line_locate_point(geometry:=geom_from_wkt('LineString(0 0, 10 0)'),point:=geom_from_wkt('Point(5 0)'))" << false << QVariant( 5.0 );

// string functions
QTest::newRow( "lower" ) << "lower('HeLLo')" << false << QVariant( "hello" );
Expand Down
22 changes: 22 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -3430,5 +3430,27 @@ def testMergeLines(self):
exp = 'MultiLineString((0 0, 10 10),(12 2, 14 4))'
self.assertTrue(compareWkt(result, exp, 0.00001), "Merge lines: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

def testLineLocatePoint(self):
""" test QgsGeometry.lineLocatePoint() """

# not a linestring
point = QgsGeometry.fromWkt('Point(1 2)')
self.assertEqual(point.lineLocatePoint(point), -1)

# not a point
linestring = QgsGeometry.fromWkt('LineString(0 0, 10 0)')
self.assertEqual(linestring.lineLocatePoint(linestring), -1)

# valid
self.assertEqual(linestring.lineLocatePoint(point), 1)
point = QgsGeometry.fromWkt('Point(9 -2)')
self.assertEqual(linestring.lineLocatePoint(point), 9)

# circular string
geom = QgsGeometry.fromWkt('CircularString (1 5, 6 2, 7 3)')
point = QgsGeometry.fromWkt('Point(9 -2)')
self.assertAlmostEqual(geom.lineLocatePoint(point), 7.372, places=3)


if __name__ == '__main__':
unittest.main()

0 comments on commit 77750ad

Please sign in to comment.