diff --git a/resources/function_help/json/force_rhr b/resources/function_help/json/force_rhr new file mode 100644 index 00000000000..f024a5a1b08 --- /dev/null +++ b/resources/function_help/json/force_rhr @@ -0,0 +1,8 @@ +{ + "name": "force_rhr", + "type": "function", + "description": "Forces a geometry to respect the Right-Hand-Rule, in which the area that is bounded by a polygon is to the right of the boundary. In particular, the exterior ring is oriented in a clockwise direction and the interior rings in a counter-clockwise direction.", + "arguments": [ {"arg":"geom","description":"a geometry. Any non-polygon geometries are returned unchanged."}], + "examples": [ { "expression":"geom_to_wkt(force_rhr(geometry:=geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))')))", "returns":"Polygon ((-1 -1, 0 2, 4 2, 4 0, -1 -1))"}] +} + diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 29b5567aed8..7b3cab0d3eb 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -2692,6 +2692,46 @@ static QVariant fcnBuffer( const QVariantList &values, const QgsExpressionContex return result; } +static QVariant fcnForceRHR( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); + if ( fGeom.isMultipart() ) + { + const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( fGeom.constGet() ); + std::unique_ptr< QgsGeometryCollection > newCollection( collection->createEmptyWithSameType() ); + for ( int i = 0; i < collection->numGeometries(); ++i ) + { + const QgsAbstractGeometry *g = collection->geometryN( i ); + if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( g ) ) + { + std::unique_ptr< QgsCurvePolygon > corrected( cp->clone() ); + corrected->forceRHR(); + newCollection->addGeometry( corrected.release() ); + } + else + { + newCollection->addGeometry( g->clone() ); + } + } + QgsGeometry geom( std::move( newCollection ) ); + return !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); + } + else + { + if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( fGeom.constGet() ) ) + { + std::unique_ptr< QgsCurvePolygon > corrected( cp->clone() ); + corrected->forceRHR(); + QgsGeometry geom( std::move( corrected ) ); + return !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); + } + else + { + return fGeom; + } + } +} + static QVariant fcnWedgeBuffer( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QgsGeometry fGeom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); @@ -4687,6 +4727,8 @@ const QList &QgsExpression::Functions() << new QgsStaticExpressionFunction( QStringLiteral( "within" ), 2, fcnWithin, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "translate" ), 3, fcnTranslate, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "buffer" ), -1, fcnBuffer, QStringLiteral( "GeometryGroup" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "force_rhr" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "geometry" ) ), + fcnForceRHR, QStringLiteral( "GeometryGroup" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "wedge_buffer" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "center" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "azimuth" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "width" ) ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index a0ad2ae6dc6..950416094bd 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -816,6 +816,12 @@ class TestQgsExpression: public QObject QTest::newRow( "geometry_n collection" ) << "geom_to_wkt(geometry_n(geom_from_wkt('GEOMETRYCOLLECTION(POINT(0 1), POINT(0 0), POINT(1 0), POINT(1 1))'),3))" << false << QVariant( QStringLiteral( "Point (1 0)" ) ); QTest::newRow( "geometry_n collection bad index 1" ) << "geometry_n(geom_from_wkt('GEOMETRYCOLLECTION(POINT(0 1), POINT(0 0), POINT(1 0), POINT(1 1))'),0)" << false << QVariant(); QTest::newRow( "geometry_n collection bad index 2" ) << "geometry_n(geom_from_wkt('GEOMETRYCOLLECTION(POINT(0 1), POINT(0 0), POINT(1 0), POINT(1 1))'),5)" << false << QVariant(); + QTest::newRow( "force_rhr not geom" ) << "force_rhr('g')" << true << QVariant(); + QTest::newRow( "force_rhr null" ) << "force_rhr(NULL)" << false << QVariant(); + QTest::newRow( "force_rhr point" ) << "geom_to_wkt(force_rhr(geom_from_wkt('POINT(1 2)')))" << false << QVariant( "Point (1 2)" ); + QTest::newRow( "force_rhr polygon" ) << "geom_to_wkt(force_rhr(geometry:=geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))')))" << false << QVariant( "Polygon ((-1 -1, 0 2, 4 2, 4 0, -1 -1))" ); + QTest::newRow( "force_rhr multipolygon" ) << "geom_to_wkt(force_rhr(geometry:=geom_from_wkt('MULTIPOLYGON(Polygon((-1 -1, 4 0, 4 2, 0 2, -1 -1)),Polygon((100 100, 200 100, 200 200, 100 200, 100 100)))')))" << false << QVariant( "MultiPolygon (((-1 -1, 0 2, 4 2, 4 0, -1 -1)),((100 100, 100 200, 200 200, 200 100, 100 100)))" ); + QTest::newRow( "force_rhr line" ) << "geom_to_wkt(force_rhr(geom_from_wkt('LINESTRING(0 0, 1 1, 2 2)')))" << false << QVariant( "LineString (0 0, 1 1, 2 2)" ); QTest::newRow( "boundary not geom" ) << "boundary('g')" << true << QVariant(); QTest::newRow( "boundary null" ) << "boundary(NULL)" << false << QVariant(); QTest::newRow( "boundary point" ) << "boundary(geom_from_wkt('POINT(1 2)'))" << false << QVariant(); diff --git a/tests/testdata/zip/landsat_b1.tif.gz.properties b/tests/testdata/zip/landsat_b1.tif.gz.properties new file mode 100644 index 00000000000..652402913f1 --- /dev/null +++ b/tests/testdata/zip/landsat_b1.tif.gz.properties @@ -0,0 +1,2 @@ +compressed_size=12008 +uncompressed_size=40684