diff --git a/resources/function_help/json/rotate b/resources/function_help/json/rotate index e90b4d2377a..04fdaeb7947 100644 --- a/resources/function_help/json/rotate +++ b/resources/function_help/json/rotate @@ -5,7 +5,8 @@ "description": "Returns a rotated version of a geometry. Calculations are in the Spatial Reference System of this geometry.", "arguments": [ {"arg":"geometry","description":"a geometry"}, {"arg":"rotation","description":"clockwise rotation in degrees"}, - {"arg":"center", "optional":true,"description":"rotation center point. If not specified, the center of the geometry's bounding box is used."} + {"arg":"center", "optional":true,"default":"NULL", "description":"rotation center point. If not specified, the center of the geometry's bounding box is used."}, + {"arg":"per_part", "optional":true, "default":"false","description": "apply rotation per part. If true, then rotation will apply around the center of each part's bounding box when the input geometry is multipart and an explicit rotation center point is not specified."} ], "examples": [ { "expression":"rotate($geometry, 45, make_point(4, 5))", "returns":"geometry rotated 45 degrees clockwise around the (4, 5) point"}, { "expression":"rotate($geometry, 45)", "returns":"geometry rotated 45 degrees clockwise around the center of its bounding box"}] diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index b0b5280ac15..1a6270c4eb4 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -4194,38 +4194,45 @@ static QVariant fcnRotate( const QVariantList &values, const QgsExpressionContex const double rotation = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); const QgsGeometry center = values.at( 2 ).isValid() ? QgsExpressionUtils::getGeometry( values.at( 2 ), parent ) : QgsGeometry(); + const bool perPart = values.value( 3 ).toBool(); - QgsPointXY pt; - if ( center.isNull() ) + if ( center.isNull() && perPart && fGeom.isMultipart() ) { - // if center wasn't specified, use bounding box centroid - pt = fGeom.boundingBox().center(); - } - else if ( center.type() != QgsWkbTypes::PointGeometry ) - { - parent->setEvalErrorString( QObject::tr( "Function 'rotate' requires a point value for the center" ) ); - return QVariant(); - } - else if ( center.isMultipart() ) - { - QgsMultiPointXY multiPoint = center.asMultiPoint(); - if ( multiPoint.count() == 1 ) + // no explicit center, rotating per part + // (note that we only do this branch for multipart geometries -- for singlepart geometries + // the result is equivalent to setting perPart as false anyway) + std::unique_ptr< QgsGeometryCollection > collection( qgsgeometry_cast< QgsGeometryCollection * >( fGeom.constGet()->clone() ) ); + for ( auto it = collection->parts_begin(); it != collection->parts_end(); ++it ) { - pt = multiPoint[0]; + const QgsPointXY partCenter = ( *it )->boundingBox().center(); + QTransform t = QTransform::fromTranslate( partCenter.x(), partCenter.y() ); + t.rotate( -rotation ); + t.translate( -partCenter.x(), -partCenter.y() ); + ( *it )->transform( t ); } - else + return QVariant::fromValue( QgsGeometry( std::move( collection ) ) ); + } + else + { + QgsPointXY pt; + if ( center.isEmpty() ) + { + // if center wasn't specified, use bounding box centroid + pt = fGeom.boundingBox().center(); + } + else if ( QgsWkbTypes::flatType( center.constGet()->simplifiedTypeRef()->wkbType() ) != QgsWkbTypes::Point ) { parent->setEvalErrorString( QObject::tr( "Function 'rotate' requires a point value for the center" ) ); return QVariant(); } - } - else - { - pt = center.asPoint(); - } + else + { + pt = QgsPointXY( *qgsgeometry_cast< const QgsPoint * >( center.constGet()->simplifiedTypeRef() ) ); + } - fGeom.rotate( rotation, pt ); - return QVariant::fromValue( fGeom ); + fGeom.rotate( rotation, pt ); + return QVariant::fromValue( fGeom ); + } } static QVariant fcnScale( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) @@ -7263,7 +7270,8 @@ const QList &QgsExpression::Functions() fcnTranslate, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "rotate" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "rotation" ) ) - << QgsExpressionFunction::Parameter( QStringLiteral( "center" ), true ), + << QgsExpressionFunction::Parameter( QStringLiteral( "center" ), true ) + << QgsExpressionFunction::Parameter( QStringLiteral( "per_part" ), true, false ), fcnRotate, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "scale" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "x_scale" ) ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 244b16d1b54..5e27c806277 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1323,6 +1323,8 @@ 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( "rotate multiline centroid, not per part" ) << "geom_to_wkt(rotate(geom_from_wkt('MultiLineString((0 0, 10 0, 10 10), (12 0, 12 12))'),90))" << false << QVariant( "MultiLineString ((0 12, 0 2, 10 2),(0 0, 12 0))" ); + QTest::newRow( "rotate multiline centroid, per part" ) << "geom_to_wkt(rotate(geom_from_wkt('MultiLineString((0 0, 10 0, 10 10), (12 0, 12 12))'),90, per_part:=true))" << false << QVariant( "MultiLineString ((0 10, 0 0, 10 0),(6 6, 18 6))" ); QTest::newRow( "scale not geom" ) << "scale('g', 1.2, 0.8)" << true << QVariant(); QTest::newRow( "scale null" ) << "scale(NULL, 1.2, 0.8)" << false << QVariant(); QTest::newRow( "scale point" ) << "geom_to_wkt(scale(geom_from_wkt('POINT( 20 10)'), 1.2, 0.8, geom_from_wkt('POINT( 30 15)')))" << false << QVariant( "Point (18 11)" );