From 27e1ef5c1caec54c3e439429e755d3b9241124e1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 6 Nov 2018 11:58:15 +1000 Subject: [PATCH] Add method to QgsCurvePolygon to force RHR, ensuring standard ring orientation --- .../geometry/qgscurvepolygon.sip.in | 11 ++++++++ src/core/geometry/qgscurvepolygon.cpp | 27 +++++++++++++++++++ src/core/geometry/qgscurvepolygon.h | 11 ++++++++ tests/src/core/testqgsgeometry.cpp | 19 +++++++++++++ 4 files changed, 68 insertions(+) diff --git a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in index f3fe9633ac4d..ac713c270cc4 100644 --- a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in +++ b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in @@ -136,6 +136,17 @@ For example, this removes unclosed rings and rings with less than 4 vertices. .. versionadded:: 3.0 %End + + void forceRHR(); +%Docstring +Forces the geometry to respect the Right-Hand-Rule, in which the area that is +bounded by the 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. + +.. versionadded:: 3.6 +%End + virtual void draw( QPainter &p ) const; virtual void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) throw( QgsCsException ); diff --git a/src/core/geometry/qgscurvepolygon.cpp b/src/core/geometry/qgscurvepolygon.cpp index c930f57d5562..db68f936c884 100644 --- a/src/core/geometry/qgscurvepolygon.cpp +++ b/src/core/geometry/qgscurvepolygon.cpp @@ -705,6 +705,33 @@ void QgsCurvePolygon::removeInvalidRings() mInteriorRings = validRings; } +void QgsCurvePolygon::forceRHR() +{ + if ( mExteriorRing && mExteriorRing->orientation() != QgsCurve::Clockwise ) + { + // flip exterior ring orientation + std::unique_ptr< QgsCurve > flipped( mExteriorRing->reversed() ); + mExteriorRing = std::move( flipped ); + } + + QVector validRings; + for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) ) + { + if ( curve && curve->orientation() != QgsCurve::CounterClockwise ) + { + // flip interior ring orientation + QgsCurve *flipped = curve->reversed(); + validRings << flipped; + delete curve; + } + else + { + validRings << curve; + } + } + mInteriorRings = validRings; +} + void QgsCurvePolygon::draw( QPainter &p ) const { if ( !mExteriorRing ) diff --git a/src/core/geometry/qgscurvepolygon.h b/src/core/geometry/qgscurvepolygon.h index 14b6010a5b10..d076e913c880 100644 --- a/src/core/geometry/qgscurvepolygon.h +++ b/src/core/geometry/qgscurvepolygon.h @@ -133,6 +133,17 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface */ void removeInvalidRings(); + + /** + * Forces the geometry to respect the Right-Hand-Rule, in which the area that is + * bounded by the 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. + * + * \since QGIS 3.6 + */ + void forceRHR(); + void draw( QPainter &p ) const override; void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) override SIP_THROW( QgsCsException ); void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 ) override; diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 4a4c1390e6d5..6054a1a816de 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -6596,6 +6596,25 @@ void TestQgsGeometry::polygon() invalidRingPolygon.addInteriorRing( removeInvalidPolygonRing.clone() ); invalidRingPolygon.removeInvalidRings(); QCOMPARE( invalidRingPolygon.asWkt( 2 ), QStringLiteral( "PolygonZM ((11 2 3 4, 4 12 13 14, 11 12 13 14, 11 22 23 24, 11 2 3 4),(1 2 5 6, 11.01 2.01 15 16, 11 2.01 25 26, 1 2 5 6))" ) ); + + // force RHR + QgsPolygon rhr; + rhr.forceRHR(); // no crash + rhr.fromWkt( QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 13 14, 100 100 13 14, 100 0 23 24, 0 0 3 4))" ) ); + rhr.forceRHR(); + QCOMPARE( rhr.asWkt( 2 ), QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 13 14, 100 100 13 14, 100 0 23 24, 0 0 3 4))" ) ); + rhr.fromWkt( QStringLiteral( "PolygonZM ((0 0 3 4, 100 0 13 14, 100 100 23 24, 0 100 23 24, 0 0 3 4))" ) ); + rhr.forceRHR(); + QCOMPARE( rhr.asWkt( 2 ), QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 23 24, 100 100 23 24, 100 0 13 14, 0 0 3 4))" ) ); + rhr.fromWkt( QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 13 14, 100 100 13 14, 100 0 23 24, 0 0 3 4),(10 10 1 2, 20 10 3 4, 20 20 4, 5, 10 20 6 8, 10 10 1 2))" ) ); + rhr.forceRHR(); + QCOMPARE( rhr.asWkt( 2 ), QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 13 14, 100 100 13 14, 100 0 23 24, 0 0 3 4),(10 10 1 2, 20 10 3 4, 10 20 6 8, 10 10 1 2))" ) ); + rhr.fromWkt( QStringLiteral( "PolygonZM ((0 0 3 4, 100 0 13 14, 100 100 13 14, 0 100 23 24, 0 0 3 4),(10 10 1 2, 20 10 3 4, 20 20 4, 5, 10 20 6 8, 10 10 1 2))" ) ); + rhr.forceRHR(); + QCOMPARE( rhr.asWkt( 2 ), QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 23 24, 100 100 13 14, 100 0 13 14, 0 0 3 4),(10 10 1 2, 20 10 3 4, 10 20 6 8, 10 10 1 2))" ) ); + rhr.fromWkt( QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 13 14, 100 100 13 14, 100 0 23 24, 0 0 3 4),(10 10 1 2, 10 20 3 4, 20 10 6 8, 10 10 1 2))" ) ); + rhr.forceRHR(); + QCOMPARE( rhr.asWkt( 2 ), QStringLiteral( "PolygonZM ((0 0 3 4, 0 100 13 14, 100 100 13 14, 100 0 23 24, 0 0 3 4),(10 10 1 2, 20 10 6 8, 10 20 3 4, 10 10 1 2))" ) ); } void TestQgsGeometry::triangle()