diff --git a/python/core/auto_generated/qgsgeometrysimplifier.sip.in b/python/core/auto_generated/qgsgeometrysimplifier.sip.in index 24863e89ee47..19390affb921 100644 --- a/python/core/auto_generated/qgsgeometrysimplifier.sip.in +++ b/python/core/auto_generated/qgsgeometrysimplifier.sip.in @@ -25,6 +25,17 @@ Abstract base class for simplify geometries using a specific algorithm virtual QgsGeometry simplify( const QgsGeometry &geometry ) const = 0; %Docstring Returns a simplified version the specified geometry +%End + + virtual QgsAbstractGeometry *simplify( const QgsAbstractGeometry *geometry ) const = 0 /Factory/; +%Docstring +Returns a simplified version the specified ``geometry``. + +Will return ``None`` if no simplification is to be performed to the geometry. + +Caller takes ownership of the returned geometry. + +.. versionadded:: 3.18 %End public: @@ -61,6 +72,8 @@ is specified in layer units. virtual QgsGeometry simplify( const QgsGeometry &geometry ) const; + virtual QgsAbstractGeometry *simplify( const QgsAbstractGeometry *geometry ) const /Factory/; + protected: diff --git a/python/core/auto_generated/qgsmaptopixelgeometrysimplifier.sip.in b/python/core/auto_generated/qgsmaptopixelgeometrysimplifier.sip.in index daad16cf1dea..7a3e91477459 100644 --- a/python/core/auto_generated/qgsmaptopixelgeometrysimplifier.sip.in +++ b/python/core/auto_generated/qgsmaptopixelgeometrysimplifier.sip.in @@ -78,9 +78,8 @@ Sets the local simplification algorithm of the vector layer managed virtual QgsGeometry simplify( const QgsGeometry &geometry ) const; -%Docstring -Returns a simplified version the specified geometry -%End + virtual QgsAbstractGeometry *simplify( const QgsAbstractGeometry *geometry ) const /Factory/; + void setTolerance( double value ); %Docstring diff --git a/src/core/qgsgeometrysimplifier.cpp b/src/core/qgsgeometrysimplifier.cpp index a6817ef176f7..24467570aa18 100644 --- a/src/core/qgsgeometrysimplifier.cpp +++ b/src/core/qgsgeometrysimplifier.cpp @@ -18,6 +18,7 @@ #include "qgsgeometrysimplifier.h" #include "qgsrectangle.h" #include "qgsgeometry.h" +#include "qgsgeos.h" bool QgsAbstractGeometrySimplifier::isGeneralizableByDeviceBoundingBox( const QgsRectangle &envelope, float mapToPixelTol ) { @@ -47,3 +48,15 @@ QgsGeometry QgsTopologyPreservingSimplifier::simplify( const QgsGeometry &geomet return geometry.simplify( mTolerance ); } +QgsAbstractGeometry *QgsTopologyPreservingSimplifier::simplify( const QgsAbstractGeometry *geometry ) const +{ + if ( !geometry ) + { + return nullptr; + } + + QgsGeos geos( geometry ); + std::unique_ptr< QgsAbstractGeometry > simplifiedGeom( geos.simplify( mTolerance ) ); + return simplifiedGeom.release(); +} + diff --git a/src/core/qgsgeometrysimplifier.h b/src/core/qgsgeometrysimplifier.h index ae0e854ba10c..e08d98ea2589 100644 --- a/src/core/qgsgeometrysimplifier.h +++ b/src/core/qgsgeometrysimplifier.h @@ -22,8 +22,10 @@ class QgsGeometry; class QgsRectangle; +class QgsAbstractGeometry; #include "qgis_core.h" +#include "qgis_sip.h" /** * \ingroup core @@ -37,6 +39,17 @@ class CORE_EXPORT QgsAbstractGeometrySimplifier //! Returns a simplified version the specified geometry virtual QgsGeometry simplify( const QgsGeometry &geometry ) const = 0; + /** + * Returns a simplified version the specified \a geometry. + * + * Will return NULLPTR if no simplification is to be performed to the geometry. + * + * Caller takes ownership of the returned geometry. + * + * \since QGIS 3.18 + */ + virtual QgsAbstractGeometry *simplify( const QgsAbstractGeometry *geometry ) const = 0 SIP_FACTORY; + // MapToPixel simplification helper methods public: //! Returns whether the device-envelope can be replaced by its BBOX when is applied the specified tolerance @@ -65,6 +78,7 @@ class CORE_EXPORT QgsTopologyPreservingSimplifier : public QgsAbstractGeometrySi QgsTopologyPreservingSimplifier( double tolerance ); QgsGeometry simplify( const QgsGeometry &geometry ) const override; + QgsAbstractGeometry *simplify( const QgsAbstractGeometry *geometry ) const override SIP_FACTORY; protected: //! Distance tolerance for the simplification diff --git a/src/core/qgsmaptopixelgeometrysimplifier.cpp b/src/core/qgsmaptopixelgeometrysimplifier.cpp index c41470464c6d..c0b96e6145d0 100644 --- a/src/core/qgsmaptopixelgeometrysimplifier.cpp +++ b/src/core/qgsmaptopixelgeometrysimplifier.cpp @@ -415,3 +415,47 @@ QgsGeometry QgsMapToPixelSimplifier::simplify( const QgsGeometry &geometry ) con return QgsGeometry( simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, *geometry.constGet(), mTolerance, false ) ); } + +QgsAbstractGeometry *QgsMapToPixelSimplifier::simplify( const QgsAbstractGeometry *geometry ) const +{ + // + // IMPORTANT!!!!!!! + // We want to avoid any geometry cloning we possibly can here, which is why the + // "fail" paths always return nullptr + // + + if ( !geometry ) + { + return nullptr; + } + if ( mSimplifyFlags == QgsMapToPixelSimplifier::NoFlags ) + { + return nullptr; + } + + // Check whether the geometry can be simplified using the map2pixel context + const QgsWkbTypes::Type singleType = QgsWkbTypes::singleType( geometry->wkbType() ); + const QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( singleType ); + if ( flatType == QgsWkbTypes::Point ) + { + return nullptr; + } + + const bool isaLinearRing = flatType == QgsWkbTypes::Polygon; + const int numPoints = geometry->nCoordinates(); + + if ( numPoints <= ( isaLinearRing ? 6 : 3 ) ) + { + // No simplify simple geometries + return nullptr; + } + + const QgsRectangle envelope = geometry->boundingBox(); + if ( std::max( envelope.width(), envelope.height() ) / numPoints > mTolerance * 2.0 ) + { + //points are in average too far apart to lead to any significant simplification + return nullptr; + } + + return simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, *geometry, mTolerance, false ).release(); +} diff --git a/src/core/qgsmaptopixelgeometrysimplifier.h b/src/core/qgsmaptopixelgeometrysimplifier.h index d1364e22a673..b9b2f322d0f5 100644 --- a/src/core/qgsmaptopixelgeometrysimplifier.h +++ b/src/core/qgsmaptopixelgeometrysimplifier.h @@ -89,8 +89,8 @@ class CORE_EXPORT QgsMapToPixelSimplifier : public QgsAbstractGeometrySimplifier //! Sets the local simplification algorithm of the vector layer managed void setSimplifyAlgorithm( SimplifyAlgorithm simplifyAlgorithm ) { mSimplifyAlgorithm = simplifyAlgorithm; } - //! Returns a simplified version the specified geometry QgsGeometry simplify( const QgsGeometry &geometry ) const override; + QgsAbstractGeometry *simplify( const QgsAbstractGeometry *geometry ) const override SIP_FACTORY; //! Sets the tolerance of the vector layer managed void setTolerance( double value ) { mTolerance = value; } diff --git a/tests/src/core/testqgsmaptopixelgeometrysimplifier.cpp b/tests/src/core/testqgsmaptopixelgeometrysimplifier.cpp index bbf7f187cb07..6f5accebdf0d 100644 --- a/tests/src/core/testqgsmaptopixelgeometrysimplifier.cpp +++ b/tests/src/core/testqgsmaptopixelgeometrysimplifier.cpp @@ -77,6 +77,7 @@ class TestQgsMapToPixelGeometrySimplifier : public QObject void testCircularString(); void testVisvalingam(); void testRingValidity(); + void testAbstractGeometrySimplify(); }; @@ -217,5 +218,42 @@ void TestQgsMapToPixelGeometrySimplifier::testRingValidity() } +void TestQgsMapToPixelGeometrySimplifier::testAbstractGeometrySimplify() +{ + // test direct simplification of abstract geometries, especially the "no simplification required" paths + QgsMapToPixelSimplifier simplifier( QgsMapToPixelSimplifier::SimplifyGeometry, 5 ); + std::unique_ptr< QgsAbstractGeometry > simplified; + + // no input geometry + simplified.reset( simplifier.simplify( nullptr ) ); + QVERIFY( !simplified.get() ); + + // no simplification flag + simplifier.setSimplifyFlags( QgsMapToPixelSimplifier::NoFlags ); + simplified.reset( simplifier.simplify( nullptr ) ); + QVERIFY( !simplified.get() ); + + simplifier.setSimplifyFlags( QgsMapToPixelSimplifier::SimplifyGeometry ); + // point geometry = no simplification + simplified.reset( simplifier.simplify( QgsGeometry::fromWkt( QStringLiteral( "Point( 1 2 )" ) ).constGet() ) ); + QVERIFY( !simplified.get() ); + simplified.reset( simplifier.simplify( QgsGeometry::fromWkt( QStringLiteral( "PointZ( 1 2 3 )" ) ).constGet() ) ); + QVERIFY( !simplified.get() ); + simplified.reset( simplifier.simplify( QgsGeometry::fromWkt( QStringLiteral( "MultiPoint( 1 2, 3 4 )" ) ).constGet() ) ); + QVERIFY( !simplified.get() ); + + // triangle polygon = no simplification + simplified.reset( simplifier.simplify( QgsGeometry::fromWkt( QStringLiteral( "Polygon(( 1 1, 1 2, 2 2, 1 1))" ) ).constGet() ) ); + QVERIFY( !simplified.get() ); + + // too large bounding box vs tolerance + simplified.reset( simplifier.simplify( QgsGeometry::fromWkt( QStringLiteral( "LineString( 1 1, 50 1.5, 100 2, 100 200 )" ) ).constGet() ) ); + QVERIFY( !simplified.get() ); + + // should be simplified + simplified.reset( simplifier.simplify( QgsGeometry::fromWkt( QStringLiteral( "LineString( 1 1, 2 1.1, 2.1 1.09, 3 0.9, 4 1 )" ) ).constGet() ) ); + QCOMPARE( simplified->asWkt( 2 ), QStringLiteral( "LineString (1 1, 2 1.1, 3 0.9, 4 1)" ) ); +} + QGSTEST_MAIN( TestQgsMapToPixelGeometrySimplifier ) #include "testqgsmaptopixelgeometrysimplifier.moc"