Skip to content
Permalink
Browse files

[feature][expressions] Add "main_angle" function to return the estimated

main angle of a geometry

Returns the angle of the oriented minimum bounding box which covers the
geometry value.

Useful for data defined overrides in symbology of label expressions,
e.g. to rotate labels to match the overall angle of a polygon, and
similar for line pattern fills...
  • Loading branch information
nyalldawson committed Jul 15, 2020
1 parent 8858321 commit bd830ba2dd2a79f86d582de4fdf0f4bdea3bd8bc
@@ -0,0 +1,9 @@
{
"name": "main_angle",
"type": "function",
"groups": ["GeometryGroup"],
"description":"Returns the main angle of a geometry (clockwise, in degrees from North), which represents the angle of the oriented minimal bounding rectangle which completely covers the geometry.",
"arguments": [ {"arg":"geometry","description":"a geometry"} ],
"examples": [ { "expression":"main_angle(geom_from_wkt('Polygon ((321577 129614, 321581 129618, 321585 129615, 321581 129610, 321577 129614))'))", "returns":"38.66"}]
}

@@ -3,7 +3,7 @@
"type": "function",
"groups": ["GeometryGroup"],
"description":"Returns a geometry which represents the minimal oriented bounding box of an input geometry.",
"arguments": [ {"arg":"geom","description":"a geometry"} ],
"arguments": [ {"arg":"geometry","description":"a geometry"} ],
"examples": [ { "expression":"geom_to_wkt( oriented_bbox( geom_from_wkt( 'MULTIPOINT(1 2, 3 4, 3 2)' ) ) )", "returns":"Polygon ((1 4, 1 2, 3 2, 3 4, 1 4))"}]
}

@@ -3853,6 +3853,25 @@ static QVariant fcnOrientedBBox( const QVariantList &values, const QgsExpression
return result;
}

static QVariant fcnMainAngle( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );

// we use the angle of the oriented minimum bounding box to calculate the polygon main angle.
// While ArcGIS uses a different approach ("the angle of longest collection of segments that have similar orientation"), this
// yields similar results to OMBB approach under the same constraints ("this tool is meant for primarily orthogonal polygons rather than organically shaped ones.")

double area, angle, width, height;
const QgsGeometry geom = fGeom.orientedMinimumBoundingBox( area, angle, width, height );

if ( geom.isNull() )
{
parent->setEvalErrorString( QObject::tr( "Error calculating polygon main angle: %1" ).arg( geom.lastError() ) );
return QVariant();
}
return angle;
}

static QVariant fcnDifference( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
@@ -6178,6 +6197,9 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "oriented_bbox" ), QgsExpressionFunction::ParameterList()
<< QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
fcnOrientedBBox, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "main_angle" ), QgsExpressionFunction::ParameterList()
<< QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ),
fcnMainAngle, QStringLiteral( "GeometryGroup" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "minimal_circle" ), QgsExpressionFunction::ParameterList()
<< QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "segments" ), true, 36 ),
@@ -1240,6 +1240,12 @@ class TestQgsExpression: public QObject
QTest::newRow( "m_min line M NaN" ) << "m_min(make_line(geom_from_wkt('PointZM (0 0 0 nan)'),geom_from_wkt('PointZM (1 1 1 2)')))" << false << QVariant( 2.0 );
QTest::newRow( "m_min point" ) << "m_min(make_point_m(0,0,1))" << false << QVariant( 1.0 );
QTest::newRow( "m_min line" ) << "m_min(make_line(make_point_m(0,0,1),make_point_m(-1,-1,2),make_point_m(-2,-2,0)))" << false << QVariant( 0.0 );
QTest::newRow( "main angle polygon" ) << "round(main_angle( geom_from_wkt('POLYGON((0 0,2 9,9 2,0 0))')))" << false << QVariant( 77 );
QTest::newRow( "main angle multi polygon" ) << "round(main_angle( geom_from_wkt('MULTIPOLYGON(((0 0,3 10,1 10,1 6,0 0)))')))" << false << QVariant( 17 );
QTest::newRow( "main angle point" ) << "main_angle( geom_from_wkt('POINT (1.5 0.5)') )" << true << QVariant();
QTest::newRow( "main angle line" ) << "round(main_angle( geom_from_wkt('LINESTRING (-1 2, 9 12)') ))" << false << QVariant( 45 );
QTest::newRow( "main angle not geom" ) << "main_angle('g')" << true << QVariant();
QTest::newRow( "main angle null" ) << "main_angle(NULL)" << false << QVariant();

// string functions
QTest::newRow( "format_number" ) << "format_number(1999.567,2)" << false << QVariant( "1,999.57" );

0 comments on commit bd830ba

Please sign in to comment.
You can’t perform that action at this time.