diff --git a/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in b/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in index ea18493110a..b3e0deda411 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2310,7 +2310,7 @@ Exports the geometry to a GeoJSON string. %End - QVector< QgsGeometry > coerceToType( Qgis::WkbType type, double defaultZ = 0, double defaultM = 0 ) const; + QVector< QgsGeometry > coerceToType( Qgis::WkbType type, double defaultZ = 0, double defaultM = 0, bool avoidDuplicates = true ) const; %Docstring Attempts to coerce this geometry into the specified destination ``type``. @@ -2333,6 +2333,10 @@ Since QGIS 3.24, the parameters ``defaultZ`` and ``defaultM`` control the dimension value added when promoting geometries to Z, M or ZM versions. By default 0.0 is used for Z and M. +Since QGIS 3.44, the parameters ``avoidDuplicates`` controls whether to +keep duplicated nodes (e.g. start/end of rings) when promoting polygon +geometries to points. By default duplicated nodes are ignored. + .. note:: This method is much stricter than :py:func:`~QgsGeometry.convertToType`, as it considers the exact WKB type diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index 6b7d61efc5a..7246d322268 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2310,7 +2310,7 @@ Exports the geometry to a GeoJSON string. %End - QVector< QgsGeometry > coerceToType( Qgis::WkbType type, double defaultZ = 0, double defaultM = 0 ) const; + QVector< QgsGeometry > coerceToType( Qgis::WkbType type, double defaultZ = 0, double defaultM = 0, bool avoidDuplicates = true ) const; %Docstring Attempts to coerce this geometry into the specified destination ``type``. @@ -2333,6 +2333,10 @@ Since QGIS 3.24, the parameters ``defaultZ`` and ``defaultM`` control the dimension value added when promoting geometries to Z, M or ZM versions. By default 0.0 is used for Z and M. +Since QGIS 3.44, the parameters ``avoidDuplicates`` controls whether to +keep duplicated nodes (e.g. start/end of rings) when promoting polygon +geometries to points. By default duplicated nodes are ignored. + .. note:: This method is much stricter than :py:func:`~QgsGeometry.convertToType`, as it considers the exact WKB type diff --git a/src/analysis/processing/qgsalgorithmconvertgeometrytype.cpp b/src/analysis/processing/qgsalgorithmconvertgeometrytype.cpp index 9a8d28152e3..7cdb1e71894 100644 --- a/src/analysis/processing/qgsalgorithmconvertgeometrytype.cpp +++ b/src/analysis/processing/qgsalgorithmconvertgeometrytype.cpp @@ -168,7 +168,7 @@ const QVector< QgsGeometry > QgsConvertGeometryTypeAlgorithm::convertGeometry( c } else { - geometries = geom.coerceToType( outputWkbType ); + geometries = geom.coerceToType( outputWkbType, 0, 0, false ); } return geometries; diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 94d3016c70d..c6b0f1cf3d9 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -1695,7 +1695,7 @@ json QgsGeometry::asJsonObject( int precision ) const } -QVector QgsGeometry::coerceToType( const Qgis::WkbType type, double defaultZ, double defaultM ) const +QVector QgsGeometry::coerceToType( const Qgis::WkbType type, double defaultZ, double defaultM, bool avoidDuplicates ) const { QVector< QgsGeometry > res; if ( isNull() ) @@ -1768,7 +1768,7 @@ QVector QgsGeometry::coerceToType( const Qgis::WkbType type, double QSet< QgsPoint > added; for ( auto vertex = source.vertices_begin(); vertex != source.vertices_end(); ++vertex ) { - if ( added.contains( *vertex ) ) + if ( avoidDuplicates && added.contains( *vertex ) ) continue; // avoid duplicate points, e.g. start/end of rings mp->addGeometry( ( *vertex ).clone() ); added.insert( *vertex ); diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index d2b1d1a2612..9b2826c4530 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2223,6 +2223,10 @@ class CORE_EXPORT QgsGeometry * to Z, M or ZM versions. * By default 0.0 is used for Z and M. * + * Since QGIS 3.44, the parameters \a avoidDuplicates controls whether to keep duplicated nodes (e.g. start/end of rings) + * when promoting polygon geometries to points. + * By default duplicated nodes are ignored. + * * \note This method is much stricter than convertToType(), as it considers the exact WKB type * of geometries instead of the geometry family (point/line/polygon), and tries more exhaustively * to coerce geometries to the desired \a type. It also correctly maintains curves and z/m values @@ -2230,7 +2234,7 @@ class CORE_EXPORT QgsGeometry * * \since QGIS 3.14 */ - QVector< QgsGeometry > coerceToType( Qgis::WkbType type, double defaultZ = 0, double defaultM = 0 ) const; + QVector< QgsGeometry > coerceToType( Qgis::WkbType type, double defaultZ = 0, double defaultM = 0, bool avoidDuplicates = true ) const; /** * Try to convert the geometry to the requested type diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index 78b6169ffd9..2386da98d9c 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -12002,15 +12002,22 @@ class TestQgsGeometry(QgisTestCase): def testCoerce(self): """Test coerce function""" - def coerce_to_wkt(wkt, type, defaultZ=None, defaultM=None): + def coerce_to_wkt( + wkt, type, defaultZ=None, defaultM=None, avoidDuplicates=True + ): geom = QgsGeometry.fromWkt(wkt) if defaultZ is not None or defaultM is not None: return [ g.asWkt(2) - for g in geom.coerceToType(type, defaultZ or 0, defaultM or 0) + for g in geom.coerceToType( + type, defaultZ or 0, defaultM or 0, avoidDuplicates + ) ] else: - return [g.asWkt(2) for g in geom.coerceToType(type)] + return [ + g.asWkt(2) + for g in geom.coerceToType(type, avoidDuplicates=avoidDuplicates) + ] self.assertEqual( coerce_to_wkt("Point (1 1)", QgsWkbTypes.Type.Point), ["Point (1 1)"] @@ -12036,6 +12043,15 @@ class TestQgsGeometry(QgisTestCase): coerce_to_wkt("Polygon((1 1, 2 2, 1 2, 1 1))", QgsWkbTypes.Type.Point), ["Point (1 1)", "Point (2 2)", "Point (1 2)"], ) + # keep duplicated nodes + self.assertEqual( + coerce_to_wkt( + "Polygon((1 1, 2 2, 1 2, 1 1))", + QgsWkbTypes.Type.Point, + avoidDuplicates=False, + ), + ["Point (1 1)", "Point (2 2)", "Point (1 2)", "Point (1 1)"], + ) self.assertEqual( coerce_to_wkt("Polygon((1 1, 2 2, 1 2, 1 1))", QgsWkbTypes.Type.LineString), ["LineString (1 1, 2 2, 1 2, 1 1)"], @@ -12290,6 +12306,14 @@ class TestQgsGeometry(QgisTestCase): coerce_to_wkt("Polygon ((1 1, 1 2, 2 2, 1 1))", QgsWkbTypes.Type.Point), ["Point (1 1)", "Point (1 2)", "Point (2 2)"], ) + self.assertEqual( + coerce_to_wkt( + "Polygon ((1 1, 1 2, 2 2, 1 1))", + QgsWkbTypes.Type.Point, + avoidDuplicates=False, + ), + ["Point (1 1)", "Point (1 2)", "Point (2 2)", "Point (1 1)"], + ) self.assertEqual( coerce_to_wkt( @@ -12297,6 +12321,14 @@ class TestQgsGeometry(QgisTestCase): ), ["MultiPoint ((1 1),(1 2),(2 2))"], ) + self.assertEqual( + coerce_to_wkt( + "Polygon ((1 1, 1 2, 2 2, 1 1))", + QgsWkbTypes.Type.MultiPoint, + avoidDuplicates=False, + ), + ["MultiPoint ((1 1),(1 2),(2 2),(1 1))"], + ) self.assertEqual( coerce_to_wkt( @@ -12312,6 +12344,23 @@ class TestQgsGeometry(QgisTestCase): "Point (4 4)", ], ) + self.assertEqual( + coerce_to_wkt( + "MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))", + QgsWkbTypes.Type.Point, + avoidDuplicates=False, + ), + [ + "Point (1 1)", + "Point (1 2)", + "Point (2 2)", + "Point (1 1)", + "Point (3 3)", + "Point (4 3)", + "Point (4 4)", + "Point (3 3)", + ], + ) self.assertEqual( coerce_to_wkt( @@ -12320,6 +12369,14 @@ class TestQgsGeometry(QgisTestCase): ), ["MultiPoint ((1 1),(1 2),(2 2),(3 3),(4 3),(4 4))"], ) + self.assertEqual( + coerce_to_wkt( + "MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))", + QgsWkbTypes.Type.MultiPoint, + avoidDuplicates=False, + ), + ["MultiPoint ((1 1),(1 2),(2 2),(1 1),(3 3),(4 3),(4 4),(3 3))"], + ) # polygon -> lines self.assertEqual(