mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-26 00:02:08 -05:00
[feature][expression] Add a "per_part" option to "rotate" expression
function If set to true and no explicit rotation center point is specified (ie rotation happens around the geometry's center), then the rotation is applied around the center of each part individually instead of the geometry as a whole. This is designed mostly to aid cases when rotate is used as a cartographic tool as part of a geometry generator symbol layer. Sponsored by North Road, thanks to SLYR
This commit is contained in:
parent
80ed176035
commit
c36a042828
@ -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"}]
|
||||
|
@ -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<QgsExpressionFunction *> &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" ) )
|
||||
|
@ -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)" );
|
||||
|
Loading…
x
Reference in New Issue
Block a user