From 9e38e43876551ffebe9f543e3e91b07c8ed5a46f Mon Sep 17 00:00:00 2001 From: Antoine Facchini <37629967+Koyaani@users.noreply.github.com> Date: Thu, 2 Sep 2021 02:57:50 +0200 Subject: [PATCH] Add affine transform expression (#44771) --- resources/function_help/json/affine_transform | 23 +++++++++ src/core/expression/qgsexpressionfunction.cpp | 51 +++++++++++++++++++ tests/src/core/testqgsexpression.cpp | 7 +++ 3 files changed, 81 insertions(+) create mode 100644 resources/function_help/json/affine_transform diff --git a/resources/function_help/json/affine_transform b/resources/function_help/json/affine_transform new file mode 100644 index 00000000000..c15a6e247d6 --- /dev/null +++ b/resources/function_help/json/affine_transform @@ -0,0 +1,23 @@ +{ + "name": "affine_transform", + "type": "function", + "groups": ["GeometryGroup"], + "description": "Returns the geometry after an affine transformation. Calculations are in the Spatial Reference System of this geometry. The operations are performed in a scale, rotation, translation order. If there is a Z or M offset but the coordinate is not present in the geometry, it will be added.", + "arguments": [ + {"arg":"geometry","description":"a geometry"}, + {"arg":"deltaX","description":"x-axis translation"}, + {"arg":"deltaY","description":"y-axis translation"}, + {"arg":"rotationZ","description":"rotation around z-axis in degrees counter-clockwise"}, + {"arg":"scaleX","description":"x-axis scale factor"}, + {"arg":"scaleY","description":"y-axis scale factor"}, + {"arg":"deltaZ","optional":true,"default":"0","description":"z-axis translation"}, + {"arg":"deltaM","optional":true,"default":"0","description":"m-axis translation"}, + {"arg":"scaleZ","optional":true,"default":"1","description":"z-axis scale factor"}, + {"arg":"scaleM","optional":true,"default":"1","description":"m-axis scale factor"} + ], + "examples": [ + { "expression":"geom_to_wkt(affine_transform(geom_from_wkt('LINESTRING(1 1, 2 2)'), 2, 2, 0, 1, 1))", "returns":"'LineString (3 3, 4 4)'"}, + { "expression":"geom_to_wkt(affine_transform(geom_from_wkt('POLYGON((0 0, 0 3, 2 2, 0 0))'), 0, 0, -90, 1, 2))", "returns":"'Polygon ((0 0, 6 0, 4 -2, 0 0))'"}, + { "expression":"geom_to_wkt(affine_transform(geom_from_wkt('POINT(3 1)'), 0, 0, 0, 1, 1, 5, 0))", "returns":"'PointZ (3 1 5)'"} + ] +} diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 2c91d6f212a..7260b4ec323 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -4006,6 +4006,46 @@ static QVariant fcnRotate( const QVariantList &values, const QgsExpressionContex return QVariant::fromValue( fGeom ); } +static QVariant fcnAffineTransform( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + if ( fGeom.isNull() ) + { + return QVariant(); + } + + const double deltaX = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); + const double deltaY = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent ); + + const double rotationZ = QgsExpressionUtils::getDoubleValue( values.at( 3 ), parent ); + + const double scaleX = QgsExpressionUtils::getDoubleValue( values.at( 4 ), parent ); + const double scaleY = QgsExpressionUtils::getDoubleValue( values.at( 5 ), parent ); + + const double deltaZ = QgsExpressionUtils::getDoubleValue( values.at( 6 ), parent ); + const double deltaM = QgsExpressionUtils::getDoubleValue( values.at( 7 ), parent ); + const double scaleZ = QgsExpressionUtils::getDoubleValue( values.at( 8 ), parent ); + const double scaleM = QgsExpressionUtils::getDoubleValue( values.at( 9 ), parent ); + + if ( deltaZ != 0.0 && !fGeom.constGet()->is3D() ) + { + fGeom.get()->addZValue( 0 ); + } + if ( deltaM != 0.0 && !fGeom.constGet()->isMeasure() ) + { + fGeom.get()->addMValue( 0 ); + } + + QTransform transform; + transform.translate( deltaX, deltaY ); + transform.rotate( rotationZ ); + transform.scale( scaleX, scaleY ); + fGeom.transform( transform, deltaZ, scaleZ, deltaM, scaleM ); + + return QVariant::fromValue( fGeom ); +} + + static QVariant fcnCentroid( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); @@ -6972,6 +7012,17 @@ const QList &QgsExpression::Functions() << QgsExpressionFunction::Parameter( QStringLiteral( "rotation" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "center" ), true ), fcnRotate, QStringLiteral( "GeometryGroup" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "affine_transform" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "deltaX" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "deltaY" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "rotationZ" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "scaleX" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "scaleY" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "deltaZ" ), true, 0 ) + << QgsExpressionFunction::Parameter( QStringLiteral( "deltaM" ), true, 0 ) + << QgsExpressionFunction::Parameter( QStringLiteral( "scaleZ" ), true, 1 ) + << QgsExpressionFunction::Parameter( QStringLiteral( "scaleM" ), true, 1 ), + fcnAffineTransform, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "buffer" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "distance" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "segments" ), true, 8 ), diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 30cd8eef45e..46201b9d612 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1305,6 +1305,13 @@ class TestQgsExpression: public QObject QTest::newRow( "rotate line fixed multi point" ) << "geom_to_wkt(rotate(geom_from_wkt('LineString(0 0, 10 0, 10 10)'),90, geom_from_wkt('MULTIPOINT((-5 -3))')))" << false << QVariant( "LineString (-2 -8, -2 -18, 8 -18)" ); QTest::newRow( "rotate line fixed multi point multiple" ) << "geom_to_wkt(rotate(geom_from_wkt('LineString(0 0, 10 0, 10 10)'),90, geom_from_wkt('MULTIPOINT(-5 -3,1 2)')))" << true << QVariant(); QTest::newRow( "rotate polygon centroid" ) << "geom_to_wkt(rotate(geom_from_wkt('Polygon((0 0, 10 0, 10 10, 0 0))'),-90))" << false << QVariant( "Polygon ((10 0, 10 10, 0 10, 10 0))" ); + QTest::newRow( "affine_transform not geom" ) << "affine_transform('g', 0, 0, 0, 0, 0, 0)" << true << QVariant(); + QTest::newRow( "affine_transform null" ) << "affine_transform(NULL, 0, 0, 0, 0, 0, 0)" << false << QVariant(); + QTest::newRow( "affine_transform point XYZM" ) << "geom_to_wkt(affine_transform(geom_from_wkt('POINT(2 2 2 2)'), 2, 2, 180, 0, 1, 1, 1, 2, 2))" << false << QVariant( "PointZM (2 0 5 5)" ); + QTest::newRow( "affine_transform point with negative scale" ) << "geom_to_wkt(affine_transform(geom_from_wkt('POINT(1 1)'), 0, 0, 90, -2, -2))" << false << QVariant( "Point (2 -2)" ); + QTest::newRow( "affine_transform line XY" ) << "geom_to_wkt(affine_transform(geom_from_wkt('LINESTRING(1 0, 2 0)'), 0, 0, 90, 2, 1))" << false << QVariant( "LineString (0 2, 0 4)" ); + QTest::newRow( "affine_transform polygon XYZ" ) << "geom_to_wkt(affine_transform(geom_from_wkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'), 0, 0, -90, 0.5, 0.5))" << false << QVariant( "Polygon ((0 0, 0.5 0, 0.5 -0.5, 0 -0.5, 0 0))" ); + QTest::newRow( "affine_transform point XY with translation on ZM" ) << "geom_to_wkt(affine_transform(geom_from_wkt('POINT(1 1)'), 0, 0, 0, 1, 1, 3, 4))" << false << QVariant( "PointZM (1 1 3 4)" ); QTest::newRow( "is_multipart true" ) << "is_multipart(geom_from_wkt('MULTIPOINT ((0 0),(1 1),(2 2))'))" << false << QVariant( true ); QTest::newRow( "is_multipart false" ) << "is_multipart(geom_from_wkt('POINT (0 0)'))" << false << QVariant( false ); QTest::newRow( "is_multipart false empty geometry" ) << "is_multipart(geom_from_wkt('POINT EMPTY'))" << false << QVariant( false );