From aaede28b4366fd3f639e45581b88b9c07913fbf0 Mon Sep 17 00:00:00 2001 From: lbartoletti Date: Wed, 16 Jan 2019 17:42:20 +0100 Subject: [PATCH] [feature] Add expression for square by diagonal and rectangle from 3 points With the new class QgsQuadrilateral, we can add expressions to create a square by a diagonal and rectangles by 3 points --- .../function_help/json/make_rectangle_3points | 17 +++++ resources/function_help/json/make_square | 14 ++++ src/core/expression/qgsexpressionfunction.cpp | 68 ++++++++++++++++++- tests/src/core/testqgsexpression.cpp | 15 ++++ 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 resources/function_help/json/make_rectangle_3points create mode 100644 resources/function_help/json/make_square diff --git a/resources/function_help/json/make_rectangle_3points b/resources/function_help/json/make_rectangle_3points new file mode 100644 index 00000000000..40f46e491f9 --- /dev/null +++ b/resources/function_help/json/make_rectangle_3points @@ -0,0 +1,17 @@ +{ + "name": "make_rectangle_3points", + "type": "function", + "description": "Creates a rectangle from 3 points.", + "variableLenArguments": true, + "arguments": [ + {"arg":"point1", "description": "First point."}, + {"arg":"point2", "description": "Second point."}, + {"arg":"point3", "description": "Third point."}, + {"arg":"option", "optional": true, "default": "0", "description": + "An optional argument to construct the rectangle. By default this value is 0. Value can be 0 (distance) or 1 (projected). Option distance: Second distance is equal to the distance between 2nd and 3rd point. Option projected: Second distance is equal to the distance of the perpendicular projection of the 3rd point on the segment or its extension."} + ], + "examples": [ + { "expression":"geom_to_wkt(make_rectangle(make_point(0, 0), make_point(0,5), make_point(5, 5), 0)))", "returns":"'Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))'"}, + { "expression":"geom_to_wkt(make_rectangle(make_point(0, 0), make_point(0,5), make_point(5, 3), 1)))", "returns":"'Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))'"} + ] +} diff --git a/resources/function_help/json/make_square b/resources/function_help/json/make_square new file mode 100644 index 00000000000..4668df07770 --- /dev/null +++ b/resources/function_help/json/make_square @@ -0,0 +1,14 @@ +{ + "name": "make_square", + "type": "function", + "description": "Creates a square from a diagonal.", + "variableLenArguments": true, + "arguments": [ + {"arg":"point1", "description": "First point of the regular polygon"}, + {"arg":"point2", "description": "Second point"} + ], + "examples": [ + { "expression":"geom_to_wkt(make_square( make_point(0,0), make_point(5,5)))", "returns":"'Polygon ((0 0, -0 5, 5 5, 5 0, 0 0))'"}, + { "expression":"geom_to_wkt(make_square( make_point(5,0), make_point(5,5)))", "returns":"'Polygon ((5 0, 2.5 2.5, 5 5, 7.5 2.5, 5 0))'"} + ] +} diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index c3e19ea12bc..db3b4a00253 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -30,6 +30,7 @@ #include "qgstriangle.h" #include "qgscurve.h" #include "qgsregularpolygon.h" +#include "qgsquadrilateral.h" #include "qgsmultipolygon.h" #include "qgsogcutils.h" #include "qgsdistancearea.h" @@ -2487,6 +2488,60 @@ static QVariant fcnMakeRegularPolygon( const QVariantList &values, const QgsExpr } +static QVariant fcnMakeSquare( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry pt1 = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + if ( pt1.isNull() ) + return QVariant(); + if ( pt1.type() != QgsWkbTypes::PointGeometry || pt1.isMultipart() ) + return QVariant(); + + QgsGeometry pt2 = QgsExpressionUtils::getGeometry( values.at( 1 ), parent ); + if ( pt2.isNull() ) + return QVariant(); + if ( pt2.type() != QgsWkbTypes::PointGeometry || pt2.isMultipart() ) + return QVariant(); + + const QgsPoint *point1 = qgsgeometry_cast< const QgsPoint *>( pt1.constGet() ); + const QgsPoint *point2 = qgsgeometry_cast< const QgsPoint *>( pt2.constGet() ); + QgsQuadrilateral square = QgsQuadrilateral::squareFromDiagonal( *point1, *point2 ); + + return QVariant::fromValue( QgsGeometry( square.toPolygon() ) ); +} + +static QVariant fcnMakeRectangleFrom3Points( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry pt1 = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + if ( pt1.isNull() ) + return QVariant(); + if ( pt1.type() != QgsWkbTypes::PointGeometry || pt1.isMultipart() ) + return QVariant(); + + QgsGeometry pt2 = QgsExpressionUtils::getGeometry( values.at( 1 ), parent ); + if ( pt2.isNull() ) + return QVariant(); + if ( pt2.type() != QgsWkbTypes::PointGeometry || pt2.isMultipart() ) + return QVariant(); + + QgsGeometry pt3 = QgsExpressionUtils::getGeometry( values.at( 2 ), parent ); + if ( pt3.isNull() ) + return QVariant(); + if ( pt3.type() != QgsWkbTypes::PointGeometry || pt3.isMultipart() ) + return QVariant(); + + QgsQuadrilateral::ConstructionOption option = static_cast< QgsQuadrilateral::ConstructionOption >( QgsExpressionUtils::getIntValue( values.at( 3 ), parent ) ); + if ( ( option < QgsQuadrilateral::Distance ) || ( option > QgsQuadrilateral::Projected ) ) + { + parent->setEvalErrorString( QObject::tr( "Option can be 0 (distance) or 1 (projected)" ) ); + return QVariant(); + } + const QgsPoint *point1 = qgsgeometry_cast< const QgsPoint *>( pt1.constGet() ); + const QgsPoint *point2 = qgsgeometry_cast< const QgsPoint *>( pt2.constGet() ); + const QgsPoint *point3 = qgsgeometry_cast< const QgsPoint *>( pt3.constGet() ); + QgsQuadrilateral rect = QgsQuadrilateral::rectangleFrom3Points( *point1, *point2, *point3, option ); + return QVariant::fromValue( QgsGeometry( rect.toPolygon() ) ); +} + static QVariant pointAt( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) // helper function { FEAT_FROM_CONTEXT( context, f ); @@ -4888,8 +4943,17 @@ const QList &QgsExpression::Functions() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "number_sides" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "circle" ), true, 0 ), - fcnMakeRegularPolygon, QStringLiteral( "GeometryGroup" ) ); - + fcnMakeRegularPolygon, QStringLiteral( "GeometryGroup" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "make_square" ), QgsExpressionFunction::ParameterList() + << QgsExpressionFunction::Parameter( QStringLiteral( "point1" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "point2" ) ), + fcnMakeSquare, QStringLiteral( "GeometryGroup" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "make_rectangle_3points" ), QgsExpressionFunction::ParameterList() + << QgsExpressionFunction::Parameter( QStringLiteral( "point1" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "point2" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "point3" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "option" ), true, 0 ), + fcnMakeRectangleFrom3Points, QStringLiteral( "GeometryGroup" ) ); QgsStaticExpressionFunction *xAtFunc = new QgsStaticExpressionFunction( QStringLiteral( "$x_at" ), 1, fcnXat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet(), false, QStringList() << QStringLiteral( "xat" ) << QStringLiteral( "x_at" ) ); xAtFunc->setIsStatic( false ); sFunctions << xAtFunc; diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index ab2a0df748d..1492e78c426 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -944,6 +944,21 @@ class TestQgsExpression: public QObject QTest::newRow( "make_regular_polygon bad (numEdges < 3)" ) << "make_regular_polygon(make_point(0,0), make_point(0,5), 2)" << true << QVariant(); QTest::newRow( "make_regular_polygon" ) << "geom_to_wkt(make_regular_polygon(make_point(0,0), make_point(0,5), 5), 2)" << false << QVariant( "Polygon ((0 5, 4.76 1.55, 2.94 -4.05, -2.94 -4.05, -4.76 1.55, 0 5))" ); QTest::newRow( "make_regular_polygon" ) << "geom_to_wkt(make_regular_polygon(make_point(0,0), project(make_point(0,0), 4.0451, radians(36)), 5, 1), 2)" << false << QVariant( "Polygon ((0 5, 4.76 1.55, 2.94 -4.05, -2.94 -4.05, -4.76 1.55, 0 5))" ); + QTest::newRow( "make_square not geom (point 1)" ) << "make_square(make_line(make_point(1,2), make_point(3,4)), make_point(5,5))" << false << QVariant(); + QTest::newRow( "make_square not geom (point 2)" ) << "make_square(make_point(0,0), make_line(make_point(1,2), make_point(3,4)))" << false << QVariant(); + QTest::newRow( "make_square bad (point 1)" ) << "make_square('a', make_point(5,5))" << true << QVariant(); + QTest::newRow( "make_square bad (point 2)" ) << "make_square(make_point(0,0), 'a')" << true << QVariant(); + QTest::newRow( "make_square" ) << "geom_to_wkt(make_square(make_point(5, 5), make_point(1, 1)))" << false << QVariant( "Polygon ((5 5, 5 1, 1 1, 1 5, 5 5))" ); + QTest::newRow( "make_rectangle_3points not geom (point 1)" ) << "make_rectangle_3points( make_line(make_point(1,2), make_point(3,4)), make_point(0,5), make_point(5,5))" << false << QVariant(); + QTest::newRow( "make_rectangle_3points not geom (point 2)" ) << "make_rectangle_3points(make_point(0,0), make_line(make_point(1,2), make_point(3,4)), make_point(5,5))" << false << QVariant(); + QTest::newRow( "make_rectangle_3points not geom (point 3)" ) << "make_rectangle_3points(make_point(0,0), make_point(0,5), make_line(make_point(1,2), make_point(3,4)))" << false << QVariant(); + QTest::newRow( "make_rectangle_3points bad (point 1)" ) << "make_rectangle_3points('a', make_point(0,5), make_point(5,5))" << true << QVariant(); + QTest::newRow( "make_rectangle_3points bad (point 2)" ) << "make_rectangle_3points(make_point(0,0), 'a', make_point(5,5))" << true << QVariant(); + QTest::newRow( "make_rectangle_3points bad (point 3)" ) << "make_rectangle_3points(make_point(0,0), make_point(0,5), 'a')" << true << QVariant(); + QTest::newRow( "make_rectangle_3points bad (invalid option)" ) << "make_rectangle_3points(make_point(0,0), make_point(0,5), make_point(5,5), 2)" << true << QVariant(); + QTest::newRow( "make_rectangle_3points (distance default)" ) << "geom_to_wkt(make_rectangle_3points(make_point(0, 0), make_point(0,5), make_point(5, 5)))" << false << QVariant( "Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))" ); + QTest::newRow( "make_rectangle_3points (distance)" ) << "geom_to_wkt(make_rectangle_3points(make_point(0, 0), make_point(0,5), make_point(5, 5), 0))" << false << QVariant( "Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))" ); + QTest::newRow( "make_rectangle_3points (projected)" ) << "geom_to_wkt(make_rectangle_3points(make_point(0, 0), make_point(0,5), make_point(5, 3), 1))" << false << QVariant( "Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))" ); QTest::newRow( "x point" ) << "x(make_point(2.2,4.4))" << false << QVariant( 2.2 ); QTest::newRow( "y point" ) << "y(make_point(2.2,4.4))" << false << QVariant( 4.4 ); QTest::newRow( "z point" ) << "z(make_point(2.2,4.4,6.6))" << false << QVariant( 6.6 );