diff --git a/python/PyQt6/core/auto_generated/geometry/qgsabstractgeometry.sip.in b/python/PyQt6/core/auto_generated/geometry/qgsabstractgeometry.sip.in index 46dd402fa9ec..c6d3333e10b2 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgsabstractgeometry.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgsabstractgeometry.sip.in @@ -604,7 +604,7 @@ E.g. :py:class:`QgsLineString` -> :py:class:`QgsCompoundCurve`, :py:class:`QgsPo :return: the converted geometry. Caller takes ownership %End - virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const = 0 /Factory/; + virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const = 0 /Factory/; %Docstring Makes a new geometry with all the points or vertices snapped to the closest point of the grid. Ownership is transferred to the caller. @@ -626,6 +626,7 @@ In this case, it can be thought like rounding the x and y of all the points/vert :param vSpacing: Vertical spacing of the grid (y axis). 0 to disable. :param dSpacing: Depth spacing of the grid (z axis). 0 (default) to disable. :param mSpacing: Custom dimension spacing of the grid (m axis). 0 (default) to disable. +:param removeRedundantPoints: if ``True``, then points which are redundant (e.g. they represent mid points on a straight line segment) will be skipped (since QGIS 3.38) %End virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) = 0; diff --git a/python/PyQt6/core/auto_generated/geometry/qgscircularstring.sip.in b/python/PyQt6/core/auto_generated/geometry/qgscircularstring.sip.in index df661bd14cea..45907d1c04e0 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgscircularstring.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgscircularstring.sip.in @@ -151,7 +151,7 @@ Appends the contents of another circular ``string`` to the end of this circular virtual QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/; - virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/PyQt6/core/auto_generated/geometry/qgscompoundcurve.sip.in b/python/PyQt6/core/auto_generated/geometry/qgscompoundcurve.sip.in index bc7256ac6460..72926351545c 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgscompoundcurve.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgscompoundcurve.sip.in @@ -81,7 +81,7 @@ of the curve. :param toleranceType: maximum segmentation angle or maximum difference between approximation and curve %End - virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/PyQt6/core/auto_generated/geometry/qgscurvepolygon.sip.in b/python/PyQt6/core/auto_generated/geometry/qgscurvepolygon.sip.in index 03c288a5c6c2..b5137c053304 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgscurvepolygon.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgscurvepolygon.sip.in @@ -68,7 +68,7 @@ Curve polygon geometry type virtual QgsAbstractGeometry *boundary() const /Factory/; - virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/PyQt6/core/auto_generated/geometry/qgsgeometrycollection.sip.in b/python/PyQt6/core/auto_generated/geometry/qgsgeometrycollection.sip.in index 9f21b5c174dc..ca1c257a90ec 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgsgeometrycollection.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgsgeometrycollection.sip.in @@ -95,7 +95,7 @@ Returns a geometry from within the collection. virtual void clear(); - virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in index 61ce1397087c..10c95a6cfcf5 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in @@ -594,7 +594,7 @@ segment in the line. int indexOf( const QgsPoint &point ) const final; virtual bool isValid( QString &error /Out/, Qgis::GeometryValidityFlags flags = Qgis::GeometryValidityFlags() ) const; - virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/PyQt6/core/auto_generated/geometry/qgspoint.sip.in b/python/PyQt6/core/auto_generated/geometry/qgspoint.sip.in index c7fa57bd4b54..9e87b2448699 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgspoint.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgspoint.sip.in @@ -355,7 +355,7 @@ Example virtual QgsPoint *clone() const /Factory/; - virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in b/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in index 53b200c9ba01..da295d423ad1 100644 --- a/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in @@ -604,7 +604,7 @@ E.g. :py:class:`QgsLineString` -> :py:class:`QgsCompoundCurve`, :py:class:`QgsPo :return: the converted geometry. Caller takes ownership %End - virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const = 0 /Factory/; + virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const = 0 /Factory/; %Docstring Makes a new geometry with all the points or vertices snapped to the closest point of the grid. Ownership is transferred to the caller. @@ -626,6 +626,7 @@ In this case, it can be thought like rounding the x and y of all the points/vert :param vSpacing: Vertical spacing of the grid (y axis). 0 to disable. :param dSpacing: Depth spacing of the grid (z axis). 0 (default) to disable. :param mSpacing: Custom dimension spacing of the grid (m axis). 0 (default) to disable. +:param removeRedundantPoints: if ``True``, then points which are redundant (e.g. they represent mid points on a straight line segment) will be skipped (since QGIS 3.38) %End virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) = 0; diff --git a/python/core/auto_generated/geometry/qgscircularstring.sip.in b/python/core/auto_generated/geometry/qgscircularstring.sip.in index df661bd14cea..45907d1c04e0 100644 --- a/python/core/auto_generated/geometry/qgscircularstring.sip.in +++ b/python/core/auto_generated/geometry/qgscircularstring.sip.in @@ -151,7 +151,7 @@ Appends the contents of another circular ``string`` to the end of this circular virtual QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/; - virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/core/auto_generated/geometry/qgscompoundcurve.sip.in b/python/core/auto_generated/geometry/qgscompoundcurve.sip.in index bc7256ac6460..72926351545c 100644 --- a/python/core/auto_generated/geometry/qgscompoundcurve.sip.in +++ b/python/core/auto_generated/geometry/qgscompoundcurve.sip.in @@ -81,7 +81,7 @@ of the curve. :param toleranceType: maximum segmentation angle or maximum difference between approximation and curve %End - virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in index 03c288a5c6c2..b5137c053304 100644 --- a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in +++ b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in @@ -68,7 +68,7 @@ Curve polygon geometry type virtual QgsAbstractGeometry *boundary() const /Factory/; - virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in b/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in index 11e70dcbc45b..d05ede7b98b8 100644 --- a/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in @@ -95,7 +95,7 @@ Returns a geometry from within the collection. virtual void clear(); - virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/core/auto_generated/geometry/qgslinestring.sip.in b/python/core/auto_generated/geometry/qgslinestring.sip.in index 61ce1397087c..10c95a6cfcf5 100644 --- a/python/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/core/auto_generated/geometry/qgslinestring.sip.in @@ -594,7 +594,7 @@ segment in the line. int indexOf( const QgsPoint &point ) const final; virtual bool isValid( QString &error /Out/, Qgis::GeometryValidityFlags flags = Qgis::GeometryValidityFlags() ) const; - virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/python/core/auto_generated/geometry/qgspoint.sip.in b/python/core/auto_generated/geometry/qgspoint.sip.in index c7fa57bd4b54..9e87b2448699 100644 --- a/python/core/auto_generated/geometry/qgspoint.sip.in +++ b/python/core/auto_generated/geometry/qgspoint.sip.in @@ -355,7 +355,7 @@ Example virtual QgsPoint *clone() const /Factory/; - virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; + virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/; virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); diff --git a/src/core/geometry/qgsabstractgeometry.h b/src/core/geometry/qgsabstractgeometry.h index 4f1e0d7fb323..4a2c382759c4 100644 --- a/src/core/geometry/qgsabstractgeometry.h +++ b/src/core/geometry/qgsabstractgeometry.h @@ -644,8 +644,9 @@ class CORE_EXPORT QgsAbstractGeometry * \param vSpacing Vertical spacing of the grid (y axis). 0 to disable. * \param dSpacing Depth spacing of the grid (z axis). 0 (default) to disable. * \param mSpacing Custom dimension spacing of the grid (m axis). 0 (default) to disable. + * \param removeRedundantPoints if TRUE, then points which are redundant (e.g. they represent mid points on a straight line segment) will be skipped (since QGIS 3.38) */ - virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const = 0 SIP_FACTORY; + virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const = 0 SIP_FACTORY; /** * Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a diff --git a/src/core/geometry/qgscircularstring.cpp b/src/core/geometry/qgscircularstring.cpp index 22a6ed99a8bb..b77ebff87c97 100644 --- a/src/core/geometry/qgscircularstring.cpp +++ b/src/core/geometry/qgscircularstring.cpp @@ -624,7 +624,7 @@ QgsLineString *QgsCircularString::curveToLine( double tolerance, SegmentationTol return line; } -QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const +QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool ) const { // prepare result std::unique_ptr result { createEmptyWithSameType() }; diff --git a/src/core/geometry/qgscircularstring.h b/src/core/geometry/qgscircularstring.h index 81fa3aa2a3c8..9b2dae93dcc1 100644 --- a/src/core/geometry/qgscircularstring.h +++ b/src/core/geometry/qgscircularstring.h @@ -259,7 +259,7 @@ class CORE_EXPORT QgsCircularString: public QgsCurve QgsPoint startPoint() const override SIP_HOLDGIL; QgsPoint endPoint() const override SIP_HOLDGIL; QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY; - QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; + QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits::epsilon(), bool useZValues = false ) override; void draw( QPainter &p ) const override; diff --git a/src/core/geometry/qgscompoundcurve.cpp b/src/core/geometry/qgscompoundcurve.cpp index f66646bba8a3..724c610cd09e 100644 --- a/src/core/geometry/qgscompoundcurve.cpp +++ b/src/core/geometry/qgscompoundcurve.cpp @@ -465,13 +465,13 @@ QgsLineString *QgsCompoundCurve::curveToLine( double tolerance, SegmentationTole return line; } -QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const +QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const { std::unique_ptr result( createEmptyWithSameType() ); for ( QgsCurve *curve : mCurves ) { - std::unique_ptr gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ) ); + std::unique_ptr gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) ); if ( gridified ) { result->mCurves.append( gridified.release() ); diff --git a/src/core/geometry/qgscompoundcurve.h b/src/core/geometry/qgscompoundcurve.h index 8fc1c5e76807..6793e2c5fa23 100644 --- a/src/core/geometry/qgscompoundcurve.h +++ b/src/core/geometry/qgscompoundcurve.h @@ -121,7 +121,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve */ QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY; - QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; + QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits::epsilon(), bool useZValues = false ) override; bool boundingBoxIntersects( const QgsBox3D &box3d ) const override SIP_HOLDGIL; const QgsAbstractGeometry *simplifiedTypeRef() const override SIP_HOLDGIL; diff --git a/src/core/geometry/qgscurvepolygon.cpp b/src/core/geometry/qgscurvepolygon.cpp index d68fe2f387c0..f12974806ea8 100644 --- a/src/core/geometry/qgscurvepolygon.cpp +++ b/src/core/geometry/qgscurvepolygon.cpp @@ -551,7 +551,7 @@ QgsAbstractGeometry *QgsCurvePolygon::boundary() const } } -QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const +QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const { if ( !mExteriorRing ) return nullptr; @@ -560,7 +560,7 @@ QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacin std::unique_ptr< QgsCurvePolygon > polygon( createEmptyWithSameType() ); // exterior ring - auto exterior = std::unique_ptr { static_cast< QgsCurve *>( mExteriorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ) }; + auto exterior = std::unique_ptr { static_cast< QgsCurve *>( mExteriorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) }; if ( !exterior ) return nullptr; @@ -573,7 +573,7 @@ QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacin if ( !interior ) continue; - QgsCurve *gridifiedInterior = static_cast< QgsCurve * >( interior->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ); + QgsCurve *gridifiedInterior = static_cast< QgsCurve * >( interior->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ); if ( !gridifiedInterior ) continue; diff --git a/src/core/geometry/qgscurvepolygon.h b/src/core/geometry/qgscurvepolygon.h index e609b3339e23..7bb7dd6d3230 100644 --- a/src/core/geometry/qgscurvepolygon.h +++ b/src/core/geometry/qgscurvepolygon.h @@ -137,7 +137,7 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface double perimeter() const override SIP_HOLDGIL; QgsPolygon *surfaceToPolygon() const override SIP_FACTORY; QgsAbstractGeometry *boundary() const override SIP_FACTORY; - QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; + QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits::epsilon(), bool useZValues = false ) override; bool boundingBoxIntersects( const QgsBox3D &box3d ) const override SIP_HOLDGIL; diff --git a/src/core/geometry/qgsgeometrycollection.cpp b/src/core/geometry/qgsgeometrycollection.cpp index 058db833ba51..20cf15ed7338 100644 --- a/src/core/geometry/qgsgeometrycollection.cpp +++ b/src/core/geometry/qgsgeometrycollection.cpp @@ -92,13 +92,13 @@ void QgsGeometryCollection::clear() clearCache(); //set bounding box invalid } -QgsGeometryCollection *QgsGeometryCollection::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const +QgsGeometryCollection *QgsGeometryCollection::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const { std::unique_ptr result; for ( auto geom : mGeometries ) { - std::unique_ptr gridified { geom->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) }; + std::unique_ptr gridified { geom->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) }; if ( gridified ) { if ( !result ) diff --git a/src/core/geometry/qgsgeometrycollection.h b/src/core/geometry/qgsgeometrycollection.h index e83e04a79e83..4e5e009e9bfe 100644 --- a/src/core/geometry/qgsgeometrycollection.h +++ b/src/core/geometry/qgsgeometrycollection.h @@ -184,7 +184,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry int dimension() const override SIP_HOLDGIL; QString geometryType() const override SIP_HOLDGIL; void clear() override; - QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; + QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits::epsilon(), bool useZValues = false ) override; QgsAbstractGeometry *boundary() const override SIP_FACTORY; void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex SIP_OUT, QgsVertexId &nextVertex SIP_OUT ) const override; diff --git a/src/core/geometry/qgslinestring.cpp b/src/core/geometry/qgslinestring.cpp index b4cfa2497e69..ec63bb953f6b 100644 --- a/src/core/geometry/qgslinestring.cpp +++ b/src/core/geometry/qgslinestring.cpp @@ -326,7 +326,7 @@ bool QgsLineString::isValid( QString &error, Qgis::GeometryValidityFlags flags ) return QgsCurve::isValid( error, flags ); } -QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const +QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const { const int length = numPoints(); if ( length < 2 ) @@ -355,6 +355,7 @@ QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, d double previousY = 0; double previousZ = 0; double previousM = 0; + int outSize = 0; for ( int i = 0; i < length; ++i ) { const double currentX = *xIn++; @@ -375,14 +376,43 @@ QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, d && ( !hasM || mSpacing <= 0 || qgsDoubleNear( roundedM, previousM ) ); if ( isPointEqual ) continue; - } - outX.append( roundedX ); - outY.append( roundedY ); - if ( hasZ ) - outZ.append( roundedZ ); - if ( hasM ) - outM.append( roundedM ); + // maybe previous point is redundant and is just a midpoint on a straight line -- let's check + bool previousPointRedundant = false; + if ( outSize > 1 && !hasZ && !hasM ) + { + previousPointRedundant = QgsGeometryUtilsBase::leftOfLine( outX.at( outSize - 1 ), + outY.at( outSize - 1 ), + outX.at( outSize - 2 ), + outY.at( outSize - 2 ), + roundedX, roundedY ) == 0; + } + if ( previousPointRedundant ) + { + outX[ outSize - 1 ] = roundedX; + outY[ outSize - 1 ] = roundedY; + } + else + { + outX.append( roundedX ); + outY.append( roundedY ); + if ( hasZ ) + outZ.append( roundedZ ); + if ( hasM ) + outM.append( roundedM ); + outSize++; + } + } + else + { + outX.append( roundedX ); + outY.append( roundedY ); + if ( hasZ ) + outZ.append( roundedZ ); + if ( hasM ) + outM.append( roundedM ); + outSize++; + } previousX = roundedX; previousY = roundedY; @@ -390,7 +420,7 @@ QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, d previousM = roundedM; } - if ( outX.length() < 2 || ( isClosed() && outX.length() < 4 ) ) + if ( outSize < 2 || ( isClosed() && outSize < 4 ) ) return nullptr; return new QgsLineString( outX, outY, outZ, outM ); diff --git a/src/core/geometry/qgslinestring.h b/src/core/geometry/qgslinestring.h index 0079a33a3f6d..0c32ee61dda1 100644 --- a/src/core/geometry/qgslinestring.h +++ b/src/core/geometry/qgslinestring.h @@ -954,7 +954,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve bool isEmpty() const override SIP_HOLDGIL; int indexOf( const QgsPoint &point ) const final; bool isValid( QString &error SIP_OUT, Qgis::GeometryValidityFlags flags = Qgis::GeometryValidityFlags() ) const override; - QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; + QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits::epsilon(), bool useZValues = false ) override; bool isClosed() const override SIP_HOLDGIL; bool isClosed2D() const override SIP_HOLDGIL; diff --git a/src/core/geometry/qgspoint.cpp b/src/core/geometry/qgspoint.cpp index 9b6cebf23312..cf0090577db4 100644 --- a/src/core/geometry/qgspoint.cpp +++ b/src/core/geometry/qgspoint.cpp @@ -107,7 +107,7 @@ QgsPoint *QgsPoint::clone() const return new QgsPoint( *this ); } -QgsPoint *QgsPoint::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const +QgsPoint *QgsPoint::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool ) const { // helper function auto gridifyValue = []( double value, double spacing, bool extraCondition = true ) -> double diff --git a/src/core/geometry/qgspoint.h b/src/core/geometry/qgspoint.h index 67523cad2de0..149e5e39fcb4 100644 --- a/src/core/geometry/qgspoint.h +++ b/src/core/geometry/qgspoint.h @@ -562,7 +562,7 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry QString geometryType() const override SIP_HOLDGIL; int dimension() const override SIP_HOLDGIL; QgsPoint *clone() const override SIP_FACTORY; - QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; + QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits::epsilon(), bool useZValues = false ) override; void clear() override; bool fromWkb( QgsConstWkbPtr &wkb ) override; diff --git a/tests/src/core/geometry/testqgsgeometry.cpp b/tests/src/core/geometry/testqgsgeometry.cpp index 18e410c7a9af..63d50c140dd1 100644 --- a/tests/src/core/geometry/testqgsgeometry.cpp +++ b/tests/src/core/geometry/testqgsgeometry.cpp @@ -2595,6 +2595,15 @@ void TestQgsGeometry::snappedToGrid() QCOMPARE( snapped->asWkt( 5 ), QStringLiteral( "LineStringZM (68 415 11 57, 27 505 24 49, 27 406 40 32)" ) ); snapped.reset( curve.constGet()->snappedToGrid( 1, 1, 10, 10 ) ); QCOMPARE( snapped->asWkt( 5 ), QStringLiteral( "LineStringZM (68 415 10 60, 27 505 20 50, 27 406 40 30)" ) ); + + // with removal of redundant vertices + curve = QgsGeometry::fromWkt( "LineString( 68.1 415.2, 27.1 505.2, 27.1 406.2 )" ); + curve.densifyByCount( 10 ); + snapped.reset( curve.constGet()->snappedToGrid( 1, 1, 0, 0, true ) ); + QCOMPARE( snapped->asWkt( 5 ), QStringLiteral( "LineString (68 415, 27 505, 27 406)" ) ); + curve.densifyByCount( 1000 ); + snapped.reset( curve.constGet()->snappedToGrid( 1, 1, 0, 0, true ) ); + QCOMPARE( snapped->asWkt( 5 ), QStringLiteral( "LineString (68 415, 27 505, 27 406)" ) ); } //compound curve