add a flag to coerceToType() to control whether duplicated nodes should

be kept
This commit is contained in:
Alexander Bruy 2025-05-05 10:16:32 +01:00 committed by Nyall Dawson
parent 559844d7d3
commit 3cf314fcdd
6 changed files with 78 additions and 9 deletions

View File

@ -2310,7 +2310,7 @@ Exports the geometry to a GeoJSON string.
%End %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 %Docstring
Attempts to coerce this geometry into the specified destination Attempts to coerce this geometry into the specified destination
``type``. ``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 the dimension value added when promoting geometries to Z, M or ZM
versions. By default 0.0 is used for Z and M. 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:: .. note::
This method is much stricter than :py:func:`~QgsGeometry.convertToType`, as it considers the exact WKB type This method is much stricter than :py:func:`~QgsGeometry.convertToType`, as it considers the exact WKB type

View File

@ -2310,7 +2310,7 @@ Exports the geometry to a GeoJSON string.
%End %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 %Docstring
Attempts to coerce this geometry into the specified destination Attempts to coerce this geometry into the specified destination
``type``. ``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 the dimension value added when promoting geometries to Z, M or ZM
versions. By default 0.0 is used for Z and M. 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:: .. note::
This method is much stricter than :py:func:`~QgsGeometry.convertToType`, as it considers the exact WKB type This method is much stricter than :py:func:`~QgsGeometry.convertToType`, as it considers the exact WKB type

View File

@ -168,7 +168,7 @@ const QVector< QgsGeometry > QgsConvertGeometryTypeAlgorithm::convertGeometry( c
} }
else else
{ {
geometries = geom.coerceToType( outputWkbType ); geometries = geom.coerceToType( outputWkbType, 0, 0, false );
} }
return geometries; return geometries;

View File

@ -1695,7 +1695,7 @@ json QgsGeometry::asJsonObject( int precision ) const
} }
QVector<QgsGeometry> QgsGeometry::coerceToType( const Qgis::WkbType type, double defaultZ, double defaultM ) const QVector<QgsGeometry> QgsGeometry::coerceToType( const Qgis::WkbType type, double defaultZ, double defaultM, bool avoidDuplicates ) const
{ {
QVector< QgsGeometry > res; QVector< QgsGeometry > res;
if ( isNull() ) if ( isNull() )
@ -1768,7 +1768,7 @@ QVector<QgsGeometry> QgsGeometry::coerceToType( const Qgis::WkbType type, double
QSet< QgsPoint > added; QSet< QgsPoint > added;
for ( auto vertex = source.vertices_begin(); vertex != source.vertices_end(); ++vertex ) 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 continue; // avoid duplicate points, e.g. start/end of rings
mp->addGeometry( ( *vertex ).clone() ); mp->addGeometry( ( *vertex ).clone() );
added.insert( *vertex ); added.insert( *vertex );

View File

@ -2223,6 +2223,10 @@ class CORE_EXPORT QgsGeometry
* to Z, M or ZM versions. * to Z, M or ZM versions.
* By default 0.0 is used for Z and M. * 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 * \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 * 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 * 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 * \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 * Try to convert the geometry to the requested type

View File

@ -12002,15 +12002,22 @@ class TestQgsGeometry(QgisTestCase):
def testCoerce(self): def testCoerce(self):
"""Test coerce function""" """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) geom = QgsGeometry.fromWkt(wkt)
if defaultZ is not None or defaultM is not None: if defaultZ is not None or defaultM is not None:
return [ return [
g.asWkt(2) 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: 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( self.assertEqual(
coerce_to_wkt("Point (1 1)", QgsWkbTypes.Type.Point), ["Point (1 1)"] 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), coerce_to_wkt("Polygon((1 1, 2 2, 1 2, 1 1))", QgsWkbTypes.Type.Point),
["Point (1 1)", "Point (2 2)", "Point (1 2)"], ["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( self.assertEqual(
coerce_to_wkt("Polygon((1 1, 2 2, 1 2, 1 1))", QgsWkbTypes.Type.LineString), coerce_to_wkt("Polygon((1 1, 2 2, 1 2, 1 1))", QgsWkbTypes.Type.LineString),
["LineString (1 1, 2 2, 1 2, 1 1)"], ["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), coerce_to_wkt("Polygon ((1 1, 1 2, 2 2, 1 1))", QgsWkbTypes.Type.Point),
["Point (1 1)", "Point (1 2)", "Point (2 2)"], ["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( self.assertEqual(
coerce_to_wkt( coerce_to_wkt(
@ -12297,6 +12321,14 @@ class TestQgsGeometry(QgisTestCase):
), ),
["MultiPoint ((1 1),(1 2),(2 2))"], ["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( self.assertEqual(
coerce_to_wkt( coerce_to_wkt(
@ -12312,6 +12344,23 @@ class TestQgsGeometry(QgisTestCase):
"Point (4 4)", "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( self.assertEqual(
coerce_to_wkt( coerce_to_wkt(
@ -12320,6 +12369,14 @@ class TestQgsGeometry(QgisTestCase):
), ),
["MultiPoint ((1 1),(1 2),(2 2),(3 3),(4 3),(4 4))"], ["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 # polygon -> lines
self.assertEqual( self.assertEqual(