Skip to content

Commit

Permalink
[feature][expressions] Add "main_angle" function to return the estimated
Browse files Browse the repository at this point in the history
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 bd830ba
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 1 deletion.
9 changes: 9 additions & 0 deletions resources/function_help/json/main_angle
Original file line number Diff line number Diff line change
@@ -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"}]
}

2 changes: 1 addition & 1 deletion resources/function_help/json/oriented_bbox
Original file line number Diff line number Diff line change
Expand Up @@ -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))"}]
}

22 changes: 22 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 ),
Expand Down
6 changes: 6 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" );
Expand Down

0 comments on commit bd830ba

Please sign in to comment.