From 5cd1e31b82bdf1c473cf9f9df11228826f19fa46 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Mon, 26 Aug 2024 11:16:33 +0700 Subject: [PATCH 1/2] [expression] Add line_interpolate_point_by_m and line_locate_m expressions --- .../json/line_interpolate_point_by_m | 22 ++++++++ resources/function_help/json/line_locate_m | 22 ++++++++ src/core/expression/qgsexpressionfunction.cpp | 51 +++++++++++++++++++ tests/src/core/testqgsexpression.cpp | 9 ++++ 4 files changed, 104 insertions(+) create mode 100644 resources/function_help/json/line_interpolate_point_by_m create mode 100644 resources/function_help/json/line_locate_m diff --git a/resources/function_help/json/line_interpolate_point_by_m b/resources/function_help/json/line_interpolate_point_by_m new file mode 100644 index 00000000000..eaf9a810a99 --- /dev/null +++ b/resources/function_help/json/line_interpolate_point_by_m @@ -0,0 +1,22 @@ +{ + "name": "line_interpolate_point_by_m", + "type": "function", + "groups": ["GeometryGroup"], + "description": "Returns the point interpolated by a matching M value along a linestring geometry.", + "arguments": [{ + "arg": "geometry", + "description": "a linestring geometry" + }, { + "arg": "m", + "description": "an M value" + }, { + "arg": "use_3d_distance", + "optional": true, + "description": "controls whether 2D or 3D distances between vertices should be used during interpolation (this option is only considered for lines with z values)" + }], + "examples": [{ + "expression": "geom_to_wkt(line_interpolate_point_by_m(geom_from_wkt('LineStringM(0 0 0, 10 10 10)'), m:=5))", + "returns": "'Point (5 5)'" + }], + "tags": ["distance", "interpolated", "linestring", "point", "specified", "along"] +} diff --git a/resources/function_help/json/line_locate_m b/resources/function_help/json/line_locate_m new file mode 100644 index 00000000000..0e9be06018e --- /dev/null +++ b/resources/function_help/json/line_locate_m @@ -0,0 +1,22 @@ +{ + "name": "line_locate_m", + "type": "function", + "groups": ["GeometryGroup"], + "description": "Returns the distance along a linestring corresponding to the first matching interpolated M value.", + "arguments": [{ + "arg": "geometry", + "description": "a linestring geometry" + }, { + "arg": "m", + "description": "an M value" + }, { + "arg": "use_3d_distance", + "optional": true, + "description": "controls whether 2D or 3D distances between vertices should be used during interpolation (this option is only considered for lines with z values)" + }], + "examples": [{ + "expression": "line_locate_m(geometry:=geom_from_wkt('LineStringM(0 0 0, 10 10 10)'),m:=5)", + "returns": "7.07106" + }], + "tags": ["distance", "linestring", "interpolated", "corresponding", "along"] +} diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 87d1f202990..1a21d9e80cf 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -5653,6 +5653,33 @@ static QVariant fcnLineInterpolatePoint( const QVariantList &values, const QgsEx return result; } +static QVariant fcnLineInterpolatePointByM( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + const double m = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); + const bool use3DDistance = values.at( 2 ).toBool(); + + double x, y, z, distance; + + const QgsLineString *line = qgsgeometry_cast( lineGeom.constGet() ); + if ( !line ) + { + return QVariant(); + } + + if ( line->lineLocatePointByM( m, x, y, z, distance, use3DDistance ) ) + { + QgsPoint point( x, y ); + if ( use3DDistance && QgsWkbTypes::hasZ( lineGeom.wkbType() ) ) + { + point.addZValue( z ); + } + return QVariant::fromValue( QgsGeometry( point.clone() ) ); + } + + return QVariant(); +} + static QVariant fcnLineSubset( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); @@ -5732,6 +5759,24 @@ static QVariant fcnLineLocatePoint( const QVariantList &values, const QgsExpress return distance >= 0 ? distance : QVariant(); } +static QVariant fcnLineLocateM( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + const double m = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); + const bool use3DDistance = values.at( 2 ).toBool(); + + double x, y, z, distance; + + const QgsLineString *line = qgsgeometry_cast( lineGeom.constGet() ); + if ( !line ) + { + return QVariant(); + } + + const bool found = line->lineLocatePointByM( m, x, y, z, distance, use3DDistance ); + return found ? distance : QVariant(); +} + static QVariant fcnRound( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { if ( values.length() == 2 && values.at( 1 ).toInt() != 0 ) @@ -9083,10 +9128,16 @@ const QList &QgsExpression::Functions() fcnShortestLine, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "line_interpolate_point" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "distance" ) ), fcnLineInterpolatePoint, QStringLiteral( "GeometryGroup" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "line_interpolate_point_by_m" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "m" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "use_3d_distance" ), true, false ), + fcnLineInterpolatePointByM, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "line_interpolate_angle" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "distance" ) ), fcnLineInterpolateAngle, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "line_locate_point" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "point" ) ), fcnLineLocatePoint, QStringLiteral( "GeometryGroup" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "line_locate_m" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "m" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "use_3d_distance" ), true, false ), + fcnLineLocateM, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "angle_at_vertex" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "vertex" ) ), fcnAngleAtVertex, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "distance_to_vertex" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index f6c2c3e1c2d..eadc4a4b7d3 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1438,10 +1438,19 @@ class TestQgsExpression: public QObject 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_interpolate_point_by_m not geom" ) << "line_interpolate_point_by_m('g', 5)" << true << QVariant(); + QTest::newRow( "line_interpolate_point_by_m null" ) << "line_interpolate_point_by_m(NULL, 5)" << false << QVariant(); + QTest::newRow( "line_interpolate_point_by_m point" ) << "line_interpolate_point_by_m(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant(); + QTest::newRow( "line_interpolate_point_by_m line" ) << "geom_to_wkt(line_interpolate_point_by_m(geometry:=geom_from_wkt('LineStringM(0 0 0, 10 10 10)'),m:=5))" << false << QVariant( "Point (5 5)" ); 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 ); + QTest::newRow( "line_locate_m not geom" ) << "line_locate_m('g', 0)" << true << QVariant(); + QTest::newRow( "line_locate_m null" ) << "line_locate_m(NULL, 0)" << false << QVariant(); + QTest::newRow( "line_locate_m point" ) << "line_locate_m(geom_from_wkt('POINT(1 2)'),0)" << false << QVariant(); + QTest::newRow( "line_locate_m line out of range" ) << "line_locate_m(geom_from_wkt('LineStringM(0 0 0, 10 10 10)'),m:=20)" << false << QVariant(); + QTest::newRow( "line_locate_m line" ) << "line_locate_m(geom_from_wkt('LineStringM(0 0 0, 10 10 10)'),m:=0)" << false << QVariant( 0.0 ); QTest::newRow( "line_interpolate_angle not geom" ) << "line_interpolate_angle('g', 5)" << true << QVariant(); QTest::newRow( "line_interpolate_angle null" ) << "line_interpolate_angle(NULL, 5)" << false << QVariant(); QTest::newRow( "line_interpolate_angle point" ) << "line_interpolate_angle(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant( 0.0 ); From e1709a3655abc76926d796f7f89f09ffc8b11e4d Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sun, 8 Sep 2024 10:56:58 +0700 Subject: [PATCH 2/2] Address review --- resources/function_help/json/line_interpolate_point_by_m | 1 + resources/function_help/json/line_locate_m | 1 + src/core/expression/qgsexpressionfunction.cpp | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/function_help/json/line_interpolate_point_by_m b/resources/function_help/json/line_interpolate_point_by_m index eaf9a810a99..d2483df0624 100644 --- a/resources/function_help/json/line_interpolate_point_by_m +++ b/resources/function_help/json/line_interpolate_point_by_m @@ -12,6 +12,7 @@ }, { "arg": "use_3d_distance", "optional": true, + "default": "false", "description": "controls whether 2D or 3D distances between vertices should be used during interpolation (this option is only considered for lines with z values)" }], "examples": [{ diff --git a/resources/function_help/json/line_locate_m b/resources/function_help/json/line_locate_m index 0e9be06018e..984c089ca8c 100644 --- a/resources/function_help/json/line_locate_m +++ b/resources/function_help/json/line_locate_m @@ -12,6 +12,7 @@ }, { "arg": "use_3d_distance", "optional": true, + "default": "false", "description": "controls whether 2D or 3D distances between vertices should be used during interpolation (this option is only considered for lines with z values)" }], "examples": [{ diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 1a21d9e80cf..beb311ac399 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -5655,7 +5655,7 @@ static QVariant fcnLineInterpolatePoint( const QVariantList &values, const QgsEx static QVariant fcnLineInterpolatePointByM( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { - QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + const QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); const double m = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); const bool use3DDistance = values.at( 2 ).toBool(); @@ -5761,7 +5761,7 @@ static QVariant fcnLineLocatePoint( const QVariantList &values, const QgsExpress static QVariant fcnLineLocateM( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { - QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + const QgsGeometry lineGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); const double m = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); const bool use3DDistance = values.at( 2 ).toBool();