[expressions][feature] Add bearing function

This commit is contained in:
Andrea Giudiceandrea 2023-08-20 11:21:16 +02:00 committed by Nyall Dawson
parent 44e9942e22
commit b98c758539
3 changed files with 102 additions and 0 deletions

View File

@ -0,0 +1,27 @@
{
"name": "bearing",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Returns the north-based bearing as the angle in radians measured clockwise on the ellipsoid from the vertical on point_a to point_b.",
"arguments": [{
"arg": "point_a",
"description": "point geometry"
}, {
"arg": "point_b",
"description": "point geometry"
}, {
"arg": "source_auth_id",
"description": "the source auth CRS ID of the points"
}, {
"arg": "ellipsoid",
"description": "the acronym or the auth ID of the ellipsoid"
}],
"examples": [{
"expression": "degrees( bearing( make_point(16198544, -4534850), make_point(18736872, -1877769), 'EPSG:3857', 'EPSG:7030') )",
"returns": "49.980071"
}, {
"expression": "degrees( bearing( make_point(18736872, -1877769), make_point(16198544, -4534850), 'EPSG:3857', 'EPSG:7030') )",
"returns": "219.282386"
}],
"tags": ["measured", "clockwise", "points", "angle", "vertical", "north", "azimuth", "bearing", "radians"]
}

View File

@ -5387,6 +5387,76 @@ static QVariant fcnAzimuth( const QVariantList &values, const QgsExpressionConte
}
}
static QVariant fcnBearing( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom1 = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
const QgsGeometry geom2 = QgsExpressionUtils::getGeometry( values.at( 1 ), parent );
const QString sAuthId = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );
const QString ellipsoid = QgsExpressionUtils::getStringValue( values.at( 3 ), parent );
if ( geom1.isNull() || geom2.isNull() || geom1.type() != Qgis::GeometryType::Point || geom2.type() != Qgis::GeometryType::Point )
{
parent->setEvalErrorString( QObject::tr( "Function `bearing` requires two valid point geometries." ) );
return QVariant();
}
QgsPointXY point1 = geom1.asPoint();
if ( geom1.isMultipart() )
{
QgsMultiPointXY multiPoint = geom1.asMultiPoint();
if ( multiPoint.count() == 1 )
{
point1 = multiPoint[0];
}
}
QgsPointXY point2 = geom2.asPoint();
if ( geom2.isMultipart() )
{
QgsMultiPointXY multiPoint = geom2.asMultiPoint();
if ( multiPoint.count() == 1 )
{
point2 = multiPoint[0];
}
}
if ( point1.isEmpty() || point2.isEmpty() )
{
parent->setEvalErrorString( QObject::tr( "Function `bearing` requires point geometries or multi point geometries with a single part." ) );
return QVariant();
}
const QgsCoordinateReferenceSystem sCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( sAuthId );
if ( !sCrs.isValid() )
{
parent->setEvalErrorString( QObject::tr( "Function `bearing` requires a valid source auth CRS ID." ) );
return QVariant();
}
QgsCoordinateTransformContext tContext;
if ( context )
{
tContext = context->variable( QStringLiteral( "_project_transform_context" ) ).value<QgsCoordinateTransformContext>();
}
QgsDistanceArea da;
da.setSourceCrs( sCrs, tContext );
if ( !da.setEllipsoid( ellipsoid ) )
{
parent->setEvalErrorString( QObject::tr( "Function `bearing` requires a valid ellipsoid acronym or ellipsoid auth ID." ) );
return QVariant();
}
try
{
const double bearing = std::fmod( da.bearing( point1, point2 ) + 2 * M_PI, 2 * M_PI );
return bearing;
}
catch ( QgsCsException &cse )
{
QgsMessageLog::logMessage( QObject::tr( "Error caught in bearing() function: %1" ).arg( cse.what() ) );
return QVariant();
}
}
static QVariant fcnProject( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
@ -8029,6 +8099,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "radians" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "degrees" ) ), fcnRadians, QStringLiteral( "Math" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "degrees" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "radians" ) ), fcnDegrees, QStringLiteral( "Math" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "azimuth" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "point_a" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "point_b" ) ), fcnAzimuth, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "bearing" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "point_a" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "point_b" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "source_auth_id" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "ellipsoid" ) ), fcnBearing, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "inclination" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "point_a" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "point_b" ) ), fcnInclination, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "project" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "point" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "distance" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "azimuth" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "elevation" ), true, M_PI_2 ), fcnProject, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "abs" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnAbs, QStringLiteral( "Math" ) )

View File

@ -1395,6 +1395,10 @@ class TestQgsExpression: public QObject
QTest::newRow( "relate pattern false" ) << "relate( geom_from_wkt( 'LINESTRING(40 40,120 120)' ), geom_from_wkt( 'LINESTRING(40 40,60 120)' ), '**1F002**' )" << false << QVariant( false );
QTest::newRow( "azimuth" ) << "toint(degrees(azimuth( point_a := make_point(25, 45), point_b := make_point(75, 100)))*1000000)" << false << QVariant( 42273689 );
QTest::newRow( "azimuth" ) << "toint(degrees( azimuth( make_point(75, 100), make_point(25,45) ) )*1000000)" << false << QVariant( 222273689 );
QTest::newRow( "bearing 1" ) << "to_int(bearing( make_point(16198544, -4534850), make_point(18736872, -1877769), 'EPSG:3857', 'EPSG:7030')*1000000)" << false << QVariant( 872317 );
QTest::newRow( "bearing 2" ) << "to_int(bearing( make_point(-2074453, 9559553), make_point(-55665, 6828252), 'EPSG:3857', 'EPSG:7030')*1000000)" << false << QVariant( 2356910 );
QTest::newRow( "bearing 3" ) << "to_int(degrees( bearing( make_point(16198544, -4534850), make_point(18736872, -1877769), 'EPSG:3857', 'EPSG:7030'))*1000000)" << false << QVariant( 49980071 );
QTest::newRow( "bearing 4" ) << "to_int(degrees( bearing( make_point(18736872, -1877769), make_point(16198544, -4534850), 'EPSG:3857', 'EPSG:7030'))*1000000)" << false << QVariant( 219282386 );
QTest::newRow( "project not geom" ) << "project( 'asd', 1, 2 )" << true << QVariant();
QTest::newRow( "project not point" ) << "project( geom_from_wkt('LINESTRING(2 0,2 2, 3 2, 3 0)'), 1, 2 )" << true << QVariant();
QTest::newRow( "project x" ) << "toint(x(project( make_point( 1, 2 ), 3, radians(270)))*1000000)" << false << QVariant( -2 * 1000000 );