feature: add x_at, y_at, z_at and m_at expressions

This commit is contained in:
Antoine Facchini 2022-11-09 17:11:28 +01:00
parent ec689c52f1
commit d8131a0791
6 changed files with 354 additions and 40 deletions

View File

@ -0,0 +1,18 @@
{
"name": "m_at",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Retrieves a m coordinate of the geometry, or NULL if the geometry has no m value.",
"arguments": [{
"arg": "geometry",
"description": "geometry object"
}, {
"arg": "i",
"description": "index of the vertex of the geometry (indices start at 0; negative values apply from the last index, starting at -1)"
}],
"examples": [{
"expression": "m_at(geom_from_wkt('LineStringZM(0 0 0 0, 10 10 0 5, 10 10 0 0)'), 1)",
"returns": "5"
}],
"tags": ["retrieves", "coordinate", "measure"]
}

View File

@ -0,0 +1,18 @@
{
"name": "x_at",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Retrieves a x coordinate of the geometry.",
"arguments": [{
"arg": "geometry",
"description": "geometry object"
}, {
"arg": "i",
"description": "index of the vertex of the geometry (indices start at 0; negative values apply from the last index, starting at -1)"
}],
"examples": [{
"expression": "x_at( geom_from_wkt( 'POINT(4 5)' ), 0 )",
"returns": "4"
}],
"tags": ["retrieves", "coordinate"]
}

View File

@ -0,0 +1,18 @@
{
"name": "y_at",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Retrieves a y coordinate of the geometry.",
"arguments": [{
"arg": "geometry",
"description": "geometry object"
}, {
"arg": "i",
"description": "index of the vertex of the geometry (indices start at 0; negative values apply from the last index, starting at -1)"
}],
"examples": [{
"expression": "y_at( geom_from_wkt( 'POINT(4 5)' ), 0 )",
"returns": "5"
}],
"tags": ["retrieves", "coordinate"]
}

View File

@ -0,0 +1,18 @@
{
"name": "z_at",
"type": "function",
"groups": ["GeometryGroup"],
"description": "Retrieves a z coordinate of the geometry, or NULL if the geometry has no z value.",
"arguments": [{
"arg": "geometry",
"description": "geometry object"
}, {
"arg": "i",
"description": "index of the vertex of the geometry (indices start at 0; negative values apply from the last index, starting at -1)"
}],
"examples": [{
"expression": "z_at(geom_from_wkt('LineStringZ(0 0 0, 10 10 5, 10 10 0)'), 1)",
"returns": "5"
}],
"tags": ["retrieves", "coordinate", "3D"]
}

View File

@ -3778,44 +3778,134 @@ static QVariant fcnMakeRectangleFrom3Points( const QVariantList &values, const Q
return QVariant::fromValue( QgsGeometry( rect.toPolygon() ) );
}
static QVariant pointAt( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) // helper function
static QVariant pointAt( const QgsGeometry &geom, int idx, QgsExpression *parent ) // helper function
{
FEAT_FROM_CONTEXT( context, f )
int idx = QgsExpressionUtils::getNativeIntValue( values.at( 0 ), parent );
QgsGeometry g = f.geometry();
if ( g.isNull() )
if ( geom.isNull() )
return QVariant();
if ( idx < 0 )
{
idx += g.constGet()->nCoordinates();
idx += geom.constGet()->nCoordinates();
}
if ( idx < 0 || idx >= g.constGet()->nCoordinates() )
if ( idx < 0 || idx >= geom.constGet()->nCoordinates() )
{
parent->setEvalErrorString( QObject::tr( "Index is out of range" ) );
return QVariant();
}
QgsPointXY p = g.vertexAt( idx );
return QVariant( QPointF( p.x(), p.y() ) );
return QVariant::fromValue( geom.vertexAt( idx ) );
}
static QVariant fcnXat( const QVariantList &values, const QgsExpressionContext *f, QgsExpression *parent, const QgsExpressionNodeFunction * )
// function used for the old $ style
static QVariant fcnOldXat( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QVariant v = pointAt( values, f, parent );
if ( v.type() == QVariant::PointF )
return QVariant( v.toPointF().x() );
FEAT_FROM_CONTEXT( context, feature )
const QgsGeometry geom = feature.geometry();
int idx = QgsExpressionUtils::getNativeIntValue( values.at( 0 ), parent );
QVariant v = pointAt( geom, idx, parent );
if ( !v.isNull() )
return QVariant( v.value<QgsPoint>().x() );
else
return QVariant();
}
static QVariant fcnYat( const QVariantList &values, const QgsExpressionContext *f, QgsExpression *parent, const QgsExpressionNodeFunction * )
static QVariant fcnXat( const QVariantList &values, const QgsExpressionContext *f, QgsExpression *parent, const QgsExpressionNodeFunction *node )
{
QVariant v = pointAt( values, f, parent );
if ( v.type() == QVariant::PointF )
return QVariant( v.toPointF().y() );
if ( values.at( 1 ).isNull() || values.at( 0 ).isNull() ) // the case where the alias x_at function is called like a $ function (x_at(i))
{
return fcnOldXat( values, f, parent, node );
}
else if ( values.at( 0 ).isNull() && !values.at( 1 ).isNull() ) // same as above with x_at(i:=0) (this values is at the second position)
{
return fcnOldXat( QVariantList() << values[1], f, parent, node );
}
QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
int vertexNumber = QgsExpressionUtils::getNativeIntValue( values.at( 1 ), parent ); // Should be one or 0 ???
if ( geom.isNull() )
{
return QVariant();
}
QVariant v = pointAt( geom, vertexNumber, parent );
if ( !v.isNull() )
return QVariant( v.value<QgsPoint>().x() );
else
return QVariant();
}
// function used for the old $ style
static QVariant fcnOldYat( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
FEAT_FROM_CONTEXT( context, feature )
const QgsGeometry geom = feature.geometry();
int idx = QgsExpressionUtils::getNativeIntValue( values.at( 0 ), parent );
QVariant v = pointAt( geom, idx, parent );
if ( !v.isNull() )
return QVariant( v.value<QgsPoint>().y() );
else
return QVariant();
}
static QVariant fcnYat( const QVariantList &values, const QgsExpressionContext *f, QgsExpression *parent, const QgsExpressionNodeFunction *node )
{
if ( values.at( 1 ).isNull() ) // the case where the alias y_at function is called like a $ function (y_at(i))
{
return fcnOldYat( values, f, parent, node );
}
else if ( values.at( 0 ).isNull() && !values.at( 1 ).isNull() ) // same as above with x_at(i:=0) (this values is at the second position)
{
return fcnOldYat( QVariantList() << values[1], f, parent, node );
}
QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
int vertexNumber = QgsExpressionUtils::getNativeIntValue( values.at( 1 ), parent );
if ( geom.isNull() )
{
return QVariant();
}
QVariant v = pointAt( geom, vertexNumber, parent );
if ( !v.isNull() )
return QVariant( v.value<QgsPoint>().y() );
else
return QVariant();
}
static QVariant fcnZat( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
int vertexNumber = QgsExpressionUtils::getNativeIntValue( values.at( 1 ), parent );
if ( geom.isNull() )
{
return QVariant();
}
QVariant v = pointAt( geom, vertexNumber, parent );
if ( !v.isNull() && v.value<QgsPoint>().is3D() )
return QVariant( v.value<QgsPoint>().z() );
else
return QVariant();
}
static QVariant fcnMat( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
int vertexNumber = QgsExpressionUtils::getNativeIntValue( values.at( 1 ), parent );
if ( geom.isNull() )
{
return QVariant();
}
QVariant v = pointAt( geom, vertexNumber, parent );
if ( !v.isNull() && v.value<QgsPoint>().isMeasure() )
return QVariant( v.value<QgsPoint>().m() );
else
return QVariant();
}
static QVariant fcnGeometry( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * )
{
if ( !context )
@ -8022,11 +8112,18 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
#endif
QgsExpressionFunction::Parameter( QStringLiteral( "keep_collapsed" ), true, false )
}, fcnGeomMakeValid, QStringLiteral( "GeometryGroup" ) );
QgsStaticExpressionFunction *xAtFunc = new QgsStaticExpressionFunction( QStringLiteral( "$x_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "i" ) ), fcnXat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet<QString>(), false, QStringList() << QStringLiteral( "xat" ) << QStringLiteral( "x_at" ) );
functions << new QgsStaticExpressionFunction( QStringLiteral( "x_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ), true ) << QgsExpressionFunction::Parameter( QStringLiteral( "i" ), true ), fcnXat, QStringLiteral( "GeometryGroup" ) );
functions << new QgsStaticExpressionFunction( QStringLiteral( "y_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ), true ) << QgsExpressionFunction::Parameter( QStringLiteral( "i" ), true ), fcnYat, QStringLiteral( "GeometryGroup" ) );
functions << new QgsStaticExpressionFunction( QStringLiteral( "z_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "i" ), true ), fcnZat, QStringLiteral( "GeometryGroup" ) );
functions << new QgsStaticExpressionFunction( QStringLiteral( "m_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "i" ), true ), fcnMat, QStringLiteral( "GeometryGroup" ) );
QgsStaticExpressionFunction *xAtFunc = new QgsStaticExpressionFunction( QStringLiteral( "$x_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "i" ) ), fcnOldXat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet<QString>(), false, QStringList() << QStringLiteral( "xat" ) );
xAtFunc->setIsStatic( false );
functions << xAtFunc;
QgsStaticExpressionFunction *yAtFunc = new QgsStaticExpressionFunction( QStringLiteral( "$y_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "i" ) ), fcnYat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet<QString>(), false, QStringList() << QStringLiteral( "yat" ) << QStringLiteral( "y_at" ) );
QgsStaticExpressionFunction *yAtFunc = new QgsStaticExpressionFunction( QStringLiteral( "$y_at" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "i" ) ), fcnOldYat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet<QString>(), false, QStringList() << QStringLiteral( "yat" ) );
yAtFunc->setIsStatic( false );
functions << yAtFunc;

View File

@ -3233,21 +3233,51 @@ class TestQgsExpression: public QObject
void eval_geometry_calc()
{
QgsPolylineXY polyline, polygon_ring;
QgsPolyline polylineZ;
polyline << QgsPointXY( 0, 0 ) << QgsPointXY( 10, 0 );
polylineZ << QgsPoint( 0, 0, 0 ) << QgsPoint( 3, 0, 4 );
QgsPolylineXY polygon_ring;
polygon_ring << QgsPointXY( 2, 1 ) << QgsPointXY( 10, 1 ) << QgsPointXY( 10, 6 ) << QgsPointXY( 2, 6 ) << QgsPointXY( 2, 1 );
QgsPolygonXY polygon;
polygon << polygon_ring;
QgsFeature fPolygon, fPolyline, fPolylineZ;
QgsGeometry polylineGeom = QgsGeometry::fromPolylineXY( polyline );
fPolyline.setGeometry( polylineGeom );
QgsGeometry polylineZGeom = QgsGeometry::fromPolyline( polylineZ );
fPolylineZ.setGeometry( polylineZGeom );
QgsGeometry polygonGeom = QgsGeometry::fromPolygonXY( polygon );
QgsPolygonXY polygonXY;
polygonXY << polygon_ring;
QgsGeometry polygonGeom = QgsGeometry::fromPolygonXY( polygonXY );
QgsFeature fPolygon;
fPolygon.setGeometry( polygonGeom );
QgsPolylineXY polyline;
polyline << QgsPointXY( 0, 0 ) << QgsPointXY( 10, 0 );
QgsGeometry polylineGeom = QgsGeometry::fromPolylineXY( polyline );
QgsFeature fPolyline;
fPolyline.setGeometry( polylineGeom );
QgsPolyline polylineZ;
polylineZ << QgsPoint( 0, 0, 0 ) << QgsPoint( 3, 0, 4 );
QgsGeometry polylineZGeom = QgsGeometry::fromPolyline( polylineZ );
QgsFeature fPolylineZ;
fPolylineZ.setGeometry( polylineZGeom );
QgsPolyline polylineM;
polylineM << QgsPoint( QgsWkbTypes::PointM, 0, 0, 0, 0 ) << QgsPoint( QgsWkbTypes::PointM, 3, 0, 0, 8 );
QgsGeometry polylineMGeom = QgsGeometry::fromPolyline( polylineM );
QgsFeature fPolylineM;
fPolylineM.setGeometry( polylineMGeom );
QgsPolyline polylineZM;
polylineZM << QgsPoint( QgsWkbTypes::PointZM, 0, 0, 0, 0 ) << QgsPoint( QgsWkbTypes::PointZM, 3, 0, 4, 8 );
QgsGeometry polylineZMGeom = QgsGeometry::fromPolyline( polylineZM );
QgsFeature fPolylineZM;
fPolylineZM.setGeometry( polylineZMGeom );
QgsMultiLineString mls;
QgsLineString part;
part.setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointZM, 10, 10, 10, 10 )
<< QgsPoint( QgsWkbTypes::PointZM, 20, 20, 20, 20 ) );
mls.addGeometry( part.clone() );
part.setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointZM, 30, 30, 30, 30 )
<< QgsPoint( QgsWkbTypes::PointZM, 40, 40, 40, 40 ) );
mls.addGeometry( part.clone() );
QgsGeometry multiStringZMGeom;
multiStringZMGeom.set( mls.clone() );
QgsFeature fMultiLineStringZM;
fMultiLineStringZM.setGeometry( multiStringZMGeom );
QgsExpressionContext context;
QgsExpression exp1( QStringLiteral( "$area" ) );
@ -3266,59 +3296,174 @@ class TestQgsExpression: public QObject
QCOMPARE( vPerimeter.toDouble(), 26. );
QgsExpression deprecatedExpXAt( QStringLiteral( "$x_at(1)" ) );
QgsExpression expXAt( QStringLiteral( "x_at(@geometry, 1)" ) );
context.setFeature( fPolygon );
QVariant xAt = deprecatedExpXAt.evaluate( &context );
QCOMPARE( xAt.toDouble(), 10.0 );
xAt = expXAt.evaluate( &context );
QCOMPARE( xAt.toDouble(), 10.0 );
context.setFeature( fPolyline );
xAt = deprecatedExpXAt.evaluate( &context );
QCOMPARE( xAt.toDouble(), 10.0 );
xAt = expXAt.evaluate( &context );
QCOMPARE( xAt.toDouble(), 10.0 );
QgsExpression deprecatedExpXAtNeg( QStringLiteral( "$x_at(-2)" ) );
QgsExpression expXAtNeg( QStringLiteral( "x_at(@geometry, -2)" ) );
context.setFeature( fPolygon );
xAt = deprecatedExpXAtNeg.evaluate( &context );
QCOMPARE( xAt.toDouble(), 2.0 );
xAt = expXAtNeg.evaluate( &context );
QCOMPARE( xAt.toDouble(), 2.0 );
QgsExpression deprecatedExpYAt( QStringLiteral( "$y_at(2)" ) );
QgsExpression expYAt( QStringLiteral( "y_at(@geometry, 2)" ) );
context.setFeature( fPolygon );
QVariant yAt = deprecatedExpYAt.evaluate( &context );
QCOMPARE( yAt.toDouble(), 6.0 );
yAt = expYAt.evaluate( &context );
QCOMPARE( yAt.toDouble(), 6.0 );
QgsExpression deprecatedExpYAt2( QStringLiteral( "$y_at(1)" ) );
QgsExpression expYAt2( QStringLiteral( "y_at(@geometry, 1)" ) );
context.setFeature( fPolyline );
yAt = deprecatedExpYAt2.evaluate( &context );
QCOMPARE( yAt.toDouble(), 0.0 );
QgsExpression deprecatedExpYAtNeg( QStringLiteral( "$y_at(-2)" ) );
QgsExpression expYAtNeg( QStringLiteral( "y_at(@geometry, -2)" ) );
context.setFeature( fPolygon );
yAt = deprecatedExpYAtNeg.evaluate( &context );
QCOMPARE( yAt.toDouble(), 6.0 );
yAt = expYAtNeg.evaluate( &context );
QCOMPARE( yAt.toDouble(), 6.0 );
QgsExpression expXAt( QStringLiteral( "x_at(1)" ) );
QgsExpression deprecatedAliasexpXAt( QStringLiteral( "x_at(1)" ) );
context.setFeature( fPolygon );
xAt = expXAt.evaluate( &context );
xAt = deprecatedAliasexpXAt.evaluate( &context );
QCOMPARE( xAt.toDouble(), 10.0 );
QgsExpression deprecatedAliasexpXAt2( QStringLiteral( "x_at(1)" ) );
context.setFeature( fPolyline );
xAt = expXAt.evaluate( &context );
xAt = deprecatedAliasexpXAt2.evaluate( &context );
QCOMPARE( xAt.toDouble(), 10.0 );
QgsExpression expXAtNeg( QStringLiteral( "x_at(-2)" ) );
QgsExpression deprecatedAliasexpYAt( QStringLiteral( "y_at(1)" ) );
context.setFeature( fPolygon );
yAt = deprecatedAliasexpYAt.evaluate( &context );
QCOMPARE( yAt.toDouble(), 1.0 );
QgsExpression deprecatedAliasexpYAt2( QStringLiteral( "y_at(1)" ) );
context.setFeature( fPolyline );
yAt = deprecatedAliasexpYAt2.evaluate( &context );
QCOMPARE( yAt.toDouble(), 0.0 );
// with a ZM geometry
expXAt = QgsExpression( QStringLiteral( "x_at(@geometry, 2)" ) );
context.setFeature( fMultiLineStringZM );
xAt = expXAt.evaluate( &context );
QCOMPARE( xAt.toDouble(), 30.0 );
QgsExpression expXAtNeg2( QStringLiteral( "x_at(@geometry, -2)" ) );
context.setFeature( fPolygon );
xAt = expXAtNeg.evaluate( &context );
QCOMPARE( xAt.toDouble(), 2.0 );
QgsExpression expYAt( QStringLiteral( "y_at(2)" ) );
QgsExpression expYAt3( QStringLiteral( "y_at(@geometry, 2)" ) );
context.setFeature( fPolygon );
yAt = expYAt.evaluate( &context );
QCOMPARE( yAt.toDouble(), 6.0 );
QgsExpression expYAt2( QStringLiteral( "$y_at(1)" ) );
QgsExpression expYAt4( QStringLiteral( "y_at(@geometry, 1)" ) );
context.setFeature( fPolyline );
yAt = expYAt2.evaluate( &context );
QCOMPARE( yAt.toDouble(), 0.0 );
QgsExpression expYAtNeg( QStringLiteral( "y_at(-2)" ) );
// with a ZM geometry
expYAt2 = QgsExpression( QStringLiteral( "y_at(@geometry, 2)" ) );
context.setFeature( fMultiLineStringZM );
yAt = expYAt2.evaluate( &context );
QCOMPARE( yAt.toDouble(), 30.0 );
QgsExpression expYAtNeg2( QStringLiteral( "y_at(@geometry, -2)" ) );
context.setFeature( fPolygon );
yAt = expYAtNeg.evaluate( &context );
yAt = expYAtNeg2.evaluate( &context );
QCOMPARE( yAt.toDouble(), 6.0 );
// Test z_at
// a basic case
QgsExpression expZAt( QStringLiteral( "z_at(@geometry, 1)" ) );
context.setFeature( fPolylineZ );
QVariant zAt = expZAt.evaluate( &context );
QCOMPARE( zAt.toDouble(), 4.0 );
// with a negative range
expZAt = QgsExpression( QStringLiteral( "z_at(@geometry, -1)" ) );
context.setFeature( fPolylineZ );
zAt = expZAt.evaluate( &context );
QCOMPARE( zAt.toDouble(), 4.0 );
// with an index out of range
expZAt = QgsExpression( QStringLiteral( "z_at(@geometry, 3)" ) );
// even with a no Z geometry, an eval error should be raised.
context.setFeature( fPolyline );
zAt = expZAt.evaluate( &context );
QVERIFY( expZAt.hasEvalError() );
// with a geom with no Z
expZAt = QgsExpression( QStringLiteral( "z_at(@geometry, 1)" ) );
context.setFeature( fPolyline );
zAt = expZAt.evaluate( &context );
QVERIFY( zAt.isNull() );
// with a geom with no Z but with M
expZAt = QStringLiteral( "z_at(@geometry, 1)" );
context.setFeature( fPolylineM );
zAt = expZAt.evaluate( &context );
QVERIFY( zAt.isNull() );
// with a multi geom
expZAt = QStringLiteral( "z_at(@geometry, 2)" );
context.setFeature( fMultiLineStringZM );
zAt = expZAt.evaluate( &context );
QCOMPARE( zAt.toDouble(), 30.0 );
// Test m_at
// a basic case
QgsExpression expMAt( QStringLiteral( "m_at(@geometry, 1)" ) );
context.setFeature( fPolylineM );
QVariant mAt = expMAt.evaluate( &context );
QCOMPARE( mAt.toDouble(), 8.0 );
// with a negative range
expMAt = QgsExpression( QStringLiteral( "m_at(@geometry, -1)" ) );
context.setFeature( fPolylineM );
mAt = expMAt.evaluate( &context );
QCOMPARE( mAt.toDouble(), 8.0 );
// with an index out of range
expMAt = QgsExpression( QStringLiteral( "m_at(@geometry, 3)" ) );
// even with a no M geometry, an eval error should be raised.
context.setFeature( fPolyline );
mAt = expMAt.evaluate( &context );
QVERIFY( expMAt.hasEvalError() );
// with a geom with no M
expMAt = QgsExpression( QStringLiteral( "m_at(@geometry, 1)" ) );
context.setFeature( fPolyline );
mAt = expMAt.evaluate( &context );
QVERIFY( mAt.isNull() );
// with a geom with no M but with Z
expMAt = QStringLiteral( "m_at(@geometry, 1)" );
context.setFeature( fPolylineZ );
mAt = expMAt.evaluate( &context );
QVERIFY( mAt.isNull() );
// with a multi geom
expMAt = QStringLiteral( "m_at(@geometry, 3)" );
context.setFeature( fMultiLineStringZM );
mAt = expMAt.evaluate( &context );
QCOMPARE( mAt.toDouble(), 40.0 );
QgsExpression exp4( QStringLiteral( "bounds_width($geometry)" ) );
context.setFeature( fPolygon );
QVariant vBoundsWidth = exp4.evaluate( &context );