diff --git a/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in b/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in index ded61e7a955b..d03ab0547773 100644 --- a/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsabstractgeometry.sip.in @@ -610,9 +610,64 @@ Converts the geometry to a specified type. %End + QgsGeometryPartIterator parts(); +%Docstring +Returns Java-style iterator for traversal of parts of the geometry. This iterator +can safely be used to modify parts of the geometry. + +* Example: +.. code-block:: python + + # print the WKT representation of each part in a multi-point geometry + geometry = QgsMultiPoint.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + for part in geometry.parts(): + print(part.asWkt()) + + # single part geometries only have one part - this loop will iterate once only + geometry = QgsLineString.fromWkt( 'LineString( 0 0, 10 10 )' ) + for part in geometry.parts(): + print(part.asWkt()) + + # parts can be modified during the iteration + geometry = QgsMultiPoint.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + for part in geometry.parts(): + part.transform(ct) + + # part iteration can also be combined with vertex iteration + geometry = QgsMultiPolygon.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + for part in geometry.parts(): + for v in part.vertices(): + print(v.x(), v.y()) + +.. seealso:: :py:func:`vertices` + +.. versionadded:: 3.6 +%End + + QgsVertexIterator vertices() const; %Docstring -Returns Java-style iterator for traversal of vertices of the geometry +Returns a read-only, Java-style iterator for traversal of vertices of all the geometry, including all geometry parts and rings. + +.. warning:: + + The iterator returns a copy of individual vertices, and accordingly geometries cannot be + modified using the iterator. See transformVertices() for a safe method to modify vertices "in-place". + +* Example: +.. code-block:: python + + # print the x and y coordinate for each vertex in a LineString + geometry = QgsLineString.fromWkt( 'LineString( 0 0, 1 1, 2 2)' ) + for v in geometry.vertices(): + print(v.x(), v.y()) + + # vertex iteration includes all parts and rings + geometry = QgsMultiPolygon.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + for v in geometry.vertices(): + print(v.x(), v.y()) + +.. seealso:: :py:func:`parts` .. versionadded:: 3.0 %End @@ -771,6 +826,101 @@ Returns next vertex of the geometry (undefined behavior if hasNext() returns fal }; +class QgsGeometryPartIterator +{ +%Docstring +Java-style iterator for traversal of parts of a geometry + +.. versionadded:: 3.6 +%End + +%TypeHeaderCode +#include "qgsabstractgeometry.h" +%End + public: + QgsGeometryPartIterator(); +%Docstring +Constructor for QgsGeometryPartIterator +%End + + QgsGeometryPartIterator( QgsAbstractGeometry *geometry ); +%Docstring +Constructs iterator for the given geometry +%End + + bool hasNext() const; +%Docstring +Find out whether there are more parts +%End + + QgsAbstractGeometry *next(); +%Docstring +Returns next part of the geometry (undefined behavior if hasNext() returns false before calling next()) +%End + + QgsGeometryPartIterator *__iter__(); +%MethodCode + sipRes = sipCpp; +%End + + SIP_PYOBJECT __next__(); +%MethodCode + if ( sipCpp->hasNext() ) + sipRes = sipConvertFromType( sipCpp->next(), sipType_QgsAbstractGeometry, NULL ); + else + PyErr_SetString( PyExc_StopIteration, "" ); +%End + +}; + + +class QgsGeometryConstPartIterator +{ +%Docstring +Java-style iterator for const traversal of parts of a geometry + +.. versionadded:: 3.6 +%End + +%TypeHeaderCode +#include "qgsabstractgeometry.h" +%End + public: + QgsGeometryConstPartIterator(); +%Docstring +Constructor for QgsGeometryConstPartIterator +%End + + QgsGeometryConstPartIterator( const QgsAbstractGeometry *geometry ); +%Docstring +Constructs iterator for the given geometry +%End + + bool hasNext() const; +%Docstring +Find out whether there are more parts +%End + + const QgsAbstractGeometry *next(); +%Docstring +Returns next part of the geometry (undefined behavior if hasNext() returns false before calling next()) +%End + + QgsGeometryConstPartIterator *__iter__(); +%MethodCode + sipRes = sipCpp; +%End + + SIP_PYOBJECT __next__(); +%MethodCode + if ( sipCpp->hasNext() ) + sipRes = sipConvertFromType( const_cast< QgsAbstractGeometry * >( sipCpp->next() ), sipType_QgsAbstractGeometry, NULL ); + else + PyErr_SetString( PyExc_StopIteration, "" ); +%End + +}; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index c380fc6172c6..f5f57d3d205c 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -353,11 +353,105 @@ Will return a negative value if a geometry is missing. QgsVertexIterator vertices() const; %Docstring -Returns Java-style iterator for traversal of vertices of the geometry +Returns a read-only, Java-style iterator for traversal of vertices of all the geometry, including all geometry parts and rings. + +.. warning:: + + The iterator returns a copy of individual vertices, and accordingly geometries cannot be + modified using the iterator. See transformVertices() for a safe method to modify vertices "in-place". + +* Example: +.. code-block:: python + + # print the x and y coordinate for each vertex in a LineString + geometry = QgsGeometry.fromWkt( 'LineString( 0 0, 1 1, 2 2)' ) + for v in geometry.vertices(): + print(v.x(), v.y()) + + # vertex iteration includes all parts and rings + geometry = QgsGeometry.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + for v in geometry.vertices(): + print(v.x(), v.y()) + +.. seealso:: :py:func:`parts` .. versionadded:: 3.0 %End + + QgsGeometryPartIterator parts(); +%Docstring +Returns Java-style iterator for traversal of parts of the geometry. This iterator +can safely be used to modify parts of the geometry. + +This method forces a detach. Use constParts() to avoid the detach +if the parts are not going to be modified. + +* Example: +.. code-block:: python + + # print the WKT representation of each part in a multi-point geometry + geometry = QgsGeometry.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + for part in geometry.parts(): + print(part.asWkt()) + + # single part geometries only have one part - this loop will iterate once only + geometry = QgsGeometry.fromWkt( 'LineString( 0 0, 10 10 )' ) + for part in geometry.parts(): + print(part.asWkt()) + + # parts can be modified during the iteration + geometry = QgsGeometry.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + for part in geometry.parts(): + part.transform(ct) + + # part iteration can also be combined with vertex iteration + geometry = QgsGeometry.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + for part in geometry.parts(): + for v in part.vertices(): + print(v.x(), v.y()) + +.. seealso:: :py:func:`constParts` + +.. seealso:: :py:func:`vertices` + +.. versionadded:: 3.6 +%End + + QgsGeometryConstPartIterator constParts() const; +%Docstring +Returns Java-style iterator for traversal of parts of the geometry. This iterator +returns read-only references to parts and cannot be used to modify the parts. + +Unlike parts(), this method does not force a detach and is more efficient if read-only +iteration only is required. + +* Example: +.. code-block:: python + + # print the WKT representation of each part in a multi-point geometry + geometry = QgsGeometry.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + for part in geometry.parts(): + print(part.asWkt()) + + # single part geometries only have one part - this loop will iterate once only + geometry = QgsGeometry.fromWkt( 'LineString( 0 0, 10 10 )' ) + for part in geometry.parts(): + print(part.asWkt()) + + # part iteration can also be combined with vertex iteration + geometry = QgsGeometry.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + for part in geometry.parts(): + for v in part.vertices(): + print(v.x(), v.y()) + +.. seealso:: :py:func:`parts` + +.. seealso:: :py:func:`vertices` + +.. versionadded:: 3.6 +%End + double hausdorffDistance( const QgsGeometry &geom ) const; %Docstring Returns the Hausdorff distance between this geometry and ``geom``. This is basically a measure of how similar or dissimilar 2 geometries are. diff --git a/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in b/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in index 79840cc3eb09..55c61b3afc5c 100644 --- a/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometrycollection.sip.in @@ -58,7 +58,7 @@ Returns the number of geometries within the collection. - SIP_PYOBJECT geometryN( int n ) const; + SIP_PYOBJECT geometryN( int n ); %Docstring Returns a geometry from within the collection. diff --git a/src/core/geometry/qgsabstractgeometry.cpp b/src/core/geometry/qgsabstractgeometry.cpp index 912bb64a1e71..2a02a5216fb6 100644 --- a/src/core/geometry/qgsabstractgeometry.cpp +++ b/src/core/geometry/qgsabstractgeometry.cpp @@ -19,6 +19,7 @@ email : marco.hugentobler at sourcepole dot com #include "qgsgeos.h" #include "qgsmaptopixel.h" #include "qgspoint.h" +#include "qgsgeometrycollection.h" #include #include @@ -245,6 +246,28 @@ void QgsAbstractGeometry::transformVertices( const std::function( this ); + return part_iterator( this, collection ? collection->partCount() : 1 ); +} + +QgsGeometryPartIterator QgsAbstractGeometry::parts() +{ + return QgsGeometryPartIterator( this ); +} + +QgsGeometryConstPartIterator QgsAbstractGeometry::parts() const +{ + return QgsGeometryConstPartIterator( this ); +} + +QgsAbstractGeometry::const_part_iterator QgsAbstractGeometry::const_parts_end() const +{ + const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( this ); + return const_part_iterator( this, collection ? collection->partCount() : 1 ); +} + QgsVertexIterator QgsAbstractGeometry::vertices() const { return QgsVertexIterator( this ); @@ -389,3 +412,117 @@ QgsPoint QgsVertexIterator::next() n = i++; return *n; } + +QgsAbstractGeometry::part_iterator::part_iterator( QgsAbstractGeometry *g, int index ) + : mIndex( index ) + , mGeometry( g ) +{ +} + +QgsAbstractGeometry::part_iterator &QgsAbstractGeometry::part_iterator::operator++() +{ + const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry ); + if ( !collection ) + { + mIndex = 1; + return *this; // end of geometry -- nowhere else to go + } + + if ( mIndex >= collection->partCount() ) + return *this; // end of geometry - nowhere else to go + + mIndex++; + return *this; +} + +QgsAbstractGeometry::part_iterator QgsAbstractGeometry::part_iterator::operator++( int ) +{ + part_iterator it( *this ); + ++*this; + return it; +} + +QgsAbstractGeometry *QgsAbstractGeometry::part_iterator::operator*() const +{ + QgsGeometryCollection *collection = qgsgeometry_cast< QgsGeometryCollection * >( mGeometry ); + if ( !collection ) + { + return mGeometry; + } + + return collection->geometryN( mIndex ); +} + +int QgsAbstractGeometry::part_iterator::partNumber() const +{ + return mIndex; +} + +bool QgsAbstractGeometry::part_iterator::operator==( QgsAbstractGeometry::part_iterator other ) const +{ + return mGeometry == other.mGeometry && mIndex == other.mIndex; +} + +QgsAbstractGeometry *QgsGeometryPartIterator::next() +{ + n = i++; + return *n; +} + + + +QgsAbstractGeometry::const_part_iterator::const_part_iterator( const QgsAbstractGeometry *g, int index ) + : mIndex( index ) + , mGeometry( g ) +{ +} + +QgsAbstractGeometry::const_part_iterator &QgsAbstractGeometry::const_part_iterator::operator++() +{ + const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry ); + if ( !collection ) + { + mIndex = 1; + return *this; // end of geometry -- nowhere else to go + } + + if ( mIndex >= collection->partCount() ) + return *this; // end of geometry - nowhere else to go + + mIndex++; + return *this; +} + +QgsAbstractGeometry::const_part_iterator QgsAbstractGeometry::const_part_iterator::operator++( int ) +{ + const_part_iterator it( *this ); + ++*this; + return it; +} + +const QgsAbstractGeometry *QgsAbstractGeometry::const_part_iterator::operator*() const +{ + const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry ); + if ( !collection ) + { + return mGeometry; + } + + return collection->geometryN( mIndex ); +} + +int QgsAbstractGeometry::const_part_iterator::partNumber() const +{ + return mIndex; +} + +bool QgsAbstractGeometry::const_part_iterator::operator==( QgsAbstractGeometry::const_part_iterator other ) const +{ + return mGeometry == other.mGeometry && mIndex == other.mIndex; +} + +const QgsAbstractGeometry *QgsGeometryConstPartIterator::next() +{ + n = i++; + return *n; +} diff --git a/src/core/geometry/qgsabstractgeometry.h b/src/core/geometry/qgsabstractgeometry.h index 08e51fc5024f..40c067f0d6a9 100644 --- a/src/core/geometry/qgsabstractgeometry.h +++ b/src/core/geometry/qgsabstractgeometry.h @@ -36,6 +36,8 @@ class QgsVertexIterator; class QPainter; class QDomDocument; class QDomElement; +class QgsGeometryPartIterator; +class QgsGeometryConstPartIterator; typedef QVector< QgsPoint > QgsPointSequence; #ifndef SIP_RUN @@ -604,6 +606,136 @@ class CORE_EXPORT QgsAbstractGeometry */ virtual void transformVertices( const std::function< QgsPoint( const QgsPoint & ) > &transform ); + /** + * \ingroup core + * The part_iterator class provides STL-style iterator for geometry parts. + * \since QGIS 3.6 + */ + class CORE_EXPORT part_iterator + { + private: + + int mIndex = 0; //!< Current part in the geometry + QgsAbstractGeometry *mGeometry = nullptr; + + public: + //! Create invalid iterator + part_iterator() = default; + + //! Create part iterator for a geometry + part_iterator( QgsAbstractGeometry *g, int index ); + + /** + * The prefix ++ operator (++it) advances the iterator to the next part and returns an iterator to the new current part. + * Calling this function on iterator that is already past the last item leads to undefined results. + */ + part_iterator &operator++(); + + //! The postfix ++ operator (it++) advances the iterator to the next part and returns an iterator to the previously current part. + part_iterator operator++( int ); + + //! Returns the current item. + QgsAbstractGeometry *operator*() const; + + //! Returns the part number of the current item. + int partNumber() const; + + bool operator==( part_iterator other ) const; + bool operator!=( part_iterator other ) const { return !( *this == other ); } + }; + + /** + * Returns STL-style iterator pointing to the first part of the geometry. + * + * \see parts_end() + * \see parts() + * + * \since QGIS 3.6 + */ + part_iterator parts_begin() + { + return part_iterator( this, 0 ); + } + + /** + * Returns STL-style iterator pointing to the imaginary part after the last part of the geometry. + * + * \see parts_begin() + * \see parts() + * + * \since QGIS 3.6 + */ + part_iterator parts_end(); + + /** + * Returns Java-style iterator for traversal of parts of the geometry. This iterator + * returns read-only references to parts and cannot be used to modify the parts. + * + * \note Not available in Python bindings + * \since QGIS 3.6 + */ + QgsGeometryConstPartIterator parts() const; + + /** + * \ingroup core + * The part_iterator class provides STL-style iterator for const references to geometry parts. + * \since QGIS 3.6 + */ + class CORE_EXPORT const_part_iterator + { + private: + + int mIndex = 0; //!< Current part in the geometry + const QgsAbstractGeometry *mGeometry = nullptr; + + public: + //! Create invalid iterator + const_part_iterator() = default; + + //! Create part iterator for a geometry + const_part_iterator( const QgsAbstractGeometry *g, int index ); + + /** + * The prefix ++ operator (++it) advances the iterator to the next part and returns an iterator to the new current part. + * Calling this function on iterator that is already past the last item leads to undefined results. + */ + const_part_iterator &operator++(); + + //! The postfix ++ operator (it++) advances the iterator to the next part and returns an iterator to the previously current part. + const_part_iterator operator++( int ); + + //! Returns the current item. + const QgsAbstractGeometry *operator*() const; + + //! Returns the part number of the current item. + int partNumber() const; + + bool operator==( const_part_iterator other ) const; + bool operator!=( const_part_iterator other ) const { return !( *this == other ); } + }; + + /** + * Returns STL-style iterator pointing to the const first part of the geometry. + * + * \see const_parts_end() + * + * \since QGIS 3.6 + */ + const_part_iterator const_parts_begin() const + { + return const_part_iterator( this, 0 ); + } + + /** + * Returns STL-style iterator pointing to the imaginary const part after the last part of the geometry. + * + * \see const_parts_begin() + * + * \since QGIS 3.6 + */ + const_part_iterator const_parts_end() const; + + /** * \ingroup core * The vertex_iterator class provides STL-style iterator for vertices. @@ -656,7 +788,11 @@ class CORE_EXPORT QgsAbstractGeometry }; /** - * Returns STL-style iterator pointing to the first vertex of the geometry + * Returns STL-style iterator pointing to the first vertex of the geometry. + * + * \see vertices_end() + * \see vertices() + * * \since QGIS 3.0 */ vertex_iterator vertices_begin() const @@ -665,7 +801,11 @@ class CORE_EXPORT QgsAbstractGeometry } /** - * Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry + * Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry. + * + * \see vertices_begin() + * \see vertices() + * * \since QGIS 3.0 */ vertex_iterator vertices_end() const @@ -675,7 +815,60 @@ class CORE_EXPORT QgsAbstractGeometry #endif /** - * Returns Java-style iterator for traversal of vertices of the geometry + * Returns Java-style iterator for traversal of parts of the geometry. This iterator + * can safely be used to modify parts of the geometry. + * + * * Example: + * \code{.py} + * # print the WKT representation of each part in a multi-point geometry + * geometry = QgsMultiPoint.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + * for part in geometry.parts(): + * print(part.asWkt()) + * + * # single part geometries only have one part - this loop will iterate once only + * geometry = QgsLineString.fromWkt( 'LineString( 0 0, 10 10 )' ) + * for part in geometry.parts(): + * print(part.asWkt()) + * + * # parts can be modified during the iteration + * geometry = QgsMultiPoint.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + * for part in geometry.parts(): + * part.transform(ct) + * + * # part iteration can also be combined with vertex iteration + * geometry = QgsMultiPolygon.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + * for part in geometry.parts(): + * for v in part.vertices(): + * print(v.x(), v.y()) + * + * \endcode + * + * \see vertices() + * \since QGIS 3.6 + */ + QgsGeometryPartIterator parts(); + + + /** + * Returns a read-only, Java-style iterator for traversal of vertices of all the geometry, including all geometry parts and rings. + * + * \warning The iterator returns a copy of individual vertices, and accordingly geometries cannot be + * modified using the iterator. See transformVertices() for a safe method to modify vertices "in-place". + * + * * Example: + * \code{.py} + * # print the x and y coordinate for each vertex in a LineString + * geometry = QgsLineString.fromWkt( 'LineString( 0 0, 1 1, 2 2)' ) + * for v in geometry.vertices(): + * print(v.x(), v.y()) + * + * # vertex iteration includes all parts and rings + * geometry = QgsMultiPolygon.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + * for v in geometry.vertices(): + * print(v.x(), v.y()) + * \endcode + * + * \see parts() * \since QGIS 3.0 */ QgsVertexIterator vertices() const; @@ -861,4 +1054,103 @@ class CORE_EXPORT QgsVertexIterator }; +/** + * \ingroup core + * \brief Java-style iterator for traversal of parts of a geometry + * \since QGIS 3.6 + */ +class CORE_EXPORT QgsGeometryPartIterator +{ + public: + //! Constructor for QgsGeometryPartIterator + QgsGeometryPartIterator() = default; + + //! Constructs iterator for the given geometry + QgsGeometryPartIterator( QgsAbstractGeometry *geometry ) + : g( geometry ) + , i( g->parts_begin() ) + , n( g->parts_end() ) + { + } + + //! Find out whether there are more parts + bool hasNext() const + { + return g && g->parts_end() != i; + } + + //! Returns next part of the geometry (undefined behavior if hasNext() returns false before calling next()) + QgsAbstractGeometry *next(); + +#ifdef SIP_RUN + QgsGeometryPartIterator *__iter__(); + % MethodCode + sipRes = sipCpp; + % End + + SIP_PYOBJECT __next__(); + % MethodCode + if ( sipCpp->hasNext() ) + sipRes = sipConvertFromType( sipCpp->next(), sipType_QgsAbstractGeometry, NULL ); + else + PyErr_SetString( PyExc_StopIteration, "" ); + % End +#endif + + private: + QgsAbstractGeometry *g = nullptr; + QgsAbstractGeometry::part_iterator i, n; + +}; + + +/** + * \ingroup core + * \brief Java-style iterator for const traversal of parts of a geometry + * \since QGIS 3.6 + */ +class CORE_EXPORT QgsGeometryConstPartIterator +{ + public: + //! Constructor for QgsGeometryConstPartIterator + QgsGeometryConstPartIterator() = default; + + //! Constructs iterator for the given geometry + QgsGeometryConstPartIterator( const QgsAbstractGeometry *geometry ) + : g( geometry ) + , i( g->const_parts_begin() ) + , n( g->const_parts_end() ) + { + } + + //! Find out whether there are more parts + bool hasNext() const + { + return g && g->const_parts_end() != i; + } + + //! Returns next part of the geometry (undefined behavior if hasNext() returns false before calling next()) + const QgsAbstractGeometry *next(); + +#ifdef SIP_RUN + QgsGeometryConstPartIterator *__iter__(); + % MethodCode + sipRes = sipCpp; + % End + + SIP_PYOBJECT __next__(); + % MethodCode + if ( sipCpp->hasNext() ) + sipRes = sipConvertFromType( const_cast< QgsAbstractGeometry * >( sipCpp->next() ), sipType_QgsAbstractGeometry, NULL ); + else + PyErr_SetString( PyExc_StopIteration, "" ); + % End +#endif + + private: + const QgsAbstractGeometry *g = nullptr; + QgsAbstractGeometry::const_part_iterator i, n; + +}; + #endif //QGSABSTRACTGEOMETRYV2 diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 0fa99bc996fc..f4164c1a48d2 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -1676,6 +1676,53 @@ QgsVertexIterator QgsGeometry::vertices() const return QgsVertexIterator( d->geometry.get() ); } +QgsAbstractGeometry::part_iterator QgsGeometry::parts_begin() +{ + if ( !d->geometry ) + return QgsAbstractGeometry::part_iterator(); + + detach(); + return d->geometry->parts_begin(); +} + +QgsAbstractGeometry::part_iterator QgsGeometry::parts_end() +{ + if ( !d->geometry ) + return QgsAbstractGeometry::part_iterator(); + return d->geometry->parts_end(); +} + +QgsAbstractGeometry::const_part_iterator QgsGeometry::const_parts_begin() const +{ + if ( !d->geometry ) + return QgsAbstractGeometry::const_part_iterator(); + return d->geometry->const_parts_begin(); +} + +QgsAbstractGeometry::const_part_iterator QgsGeometry::const_parts_end() const +{ + if ( !d->geometry ) + return QgsAbstractGeometry::const_part_iterator(); + return d->geometry->const_parts_end(); +} + +QgsGeometryPartIterator QgsGeometry::parts() +{ + if ( !d->geometry ) + return QgsGeometryPartIterator(); + + detach(); + return QgsGeometryPartIterator( d->geometry.get() ); +} + +QgsGeometryConstPartIterator QgsGeometry::constParts() const +{ + if ( !d->geometry ) + return QgsGeometryConstPartIterator(); + + return QgsGeometryConstPartIterator( d->geometry.get() ); +} + QgsGeometry QgsGeometry::buffer( double distance, int segments ) const { if ( !d->geometry ) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 4420e31e876e..c1aeca7bc8ce 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -377,6 +377,9 @@ class CORE_EXPORT QgsGeometry #ifndef SIP_RUN + // TODO QGIS 4: consider renaming vertices_begin, vertices_end, parts_begin, parts_end, etc + // to camelCase + /** * Returns STL-style iterator pointing to the first vertex of the geometry * \since QGIS 3.0 @@ -391,11 +394,143 @@ class CORE_EXPORT QgsGeometry #endif /** - * Returns Java-style iterator for traversal of vertices of the geometry + * Returns a read-only, Java-style iterator for traversal of vertices of all the geometry, including all geometry parts and rings. + * + * \warning The iterator returns a copy of individual vertices, and accordingly geometries cannot be + * modified using the iterator. See transformVertices() for a safe method to modify vertices "in-place". + * + * * Example: + * \code{.py} + * # print the x and y coordinate for each vertex in a LineString + * geometry = QgsGeometry.fromWkt( 'LineString( 0 0, 1 1, 2 2)' ) + * for v in geometry.vertices(): + * print(v.x(), v.y()) + * + * # vertex iteration includes all parts and rings + * geometry = QgsGeometry.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + * for v in geometry.vertices(): + * print(v.x(), v.y()) + * \endcode + * + * \see parts() * \since QGIS 3.0 */ QgsVertexIterator vertices() const; +#ifndef SIP_RUN + + /** + * Returns STL-style iterator pointing to the first part of the geometry. + * + * This method forces a detach. Use const_parts_begin() to avoid the detach + * if the parts are not going to be modified. + * + * \since QGIS 3.6 + */ + QgsAbstractGeometry::part_iterator parts_begin(); + + /** + * Returns STL-style iterator pointing to the imaginary part after the last part of the geometry. + * + * This method forces a detach. Use const_parts_begin() to avoid the detach + * if the parts are not going to be modified. + * + * \since QGIS 3.6 + */ + QgsAbstractGeometry::part_iterator parts_end(); + + /** + * Returns STL-style const iterator pointing to the first part of the geometry. + * + * This method avoids a detach and is more efficient then parts_begin() for read + * only iteration. + * + * \since QGIS 3.6 + */ + QgsAbstractGeometry::const_part_iterator const_parts_begin() const; + + /** + * Returns STL-style iterator pointing to the imaginary part after the last part of the geometry. + * + * This method avoids a detach and is more efficient then parts_end() for read + * only iteration. + * + * \since QGIS 3.6 + */ + QgsAbstractGeometry::const_part_iterator const_parts_end() const; +#endif + + /** + * Returns Java-style iterator for traversal of parts of the geometry. This iterator + * can safely be used to modify parts of the geometry. + * + * This method forces a detach. Use constParts() to avoid the detach + * if the parts are not going to be modified. + * + * * Example: + * \code{.py} + * # print the WKT representation of each part in a multi-point geometry + * geometry = QgsGeometry.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + * for part in geometry.parts(): + * print(part.asWkt()) + * + * # single part geometries only have one part - this loop will iterate once only + * geometry = QgsGeometry.fromWkt( 'LineString( 0 0, 10 10 )' ) + * for part in geometry.parts(): + * print(part.asWkt()) + * + * # parts can be modified during the iteration + * geometry = QgsGeometry.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + * for part in geometry.parts(): + * part.transform(ct) + * + * # part iteration can also be combined with vertex iteration + * geometry = QgsGeometry.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + * for part in geometry.parts(): + * for v in part.vertices(): + * print(v.x(), v.y()) + * + * \endcode + * + * \see constParts() + * \see vertices() + * \since QGIS 3.6 + */ + QgsGeometryPartIterator parts(); + + /** + * Returns Java-style iterator for traversal of parts of the geometry. This iterator + * returns read-only references to parts and cannot be used to modify the parts. + * + * Unlike parts(), this method does not force a detach and is more efficient if read-only + * iteration only is required. + * + * * Example: + * \code{.py} + * # print the WKT representation of each part in a multi-point geometry + * geometry = QgsGeometry.fromWkt( 'MultiPoint( 0 0, 1 1, 2 2)' ) + * for part in geometry.parts(): + * print(part.asWkt()) + * + * # single part geometries only have one part - this loop will iterate once only + * geometry = QgsGeometry.fromWkt( 'LineString( 0 0, 10 10 )' ) + * for part in geometry.parts(): + * print(part.asWkt()) + * + * # part iteration can also be combined with vertex iteration + * geometry = QgsGeometry.fromWkt( 'MultiPolygon((( 0 0, 0 10, 10 10, 10 0, 0 0 ),( 5 5, 5 6, 6 6, 6 5, 5 5)),((20 2, 22 2, 22 4, 20 4, 20 2)))' ) + * for part in geometry.parts(): + * for v in part.vertices(): + * print(v.x(), v.y()) + * + * \endcode + * + * \see parts() + * \see vertices() + * \since QGIS 3.6 + */ + QgsGeometryConstPartIterator constParts() const; + /** * Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are. * diff --git a/src/core/geometry/qgsgeometrycollection.h b/src/core/geometry/qgsgeometrycollection.h index 16583e5bfe63..2020dd8b1c49 100644 --- a/src/core/geometry/qgsgeometrycollection.h +++ b/src/core/geometry/qgsgeometrycollection.h @@ -88,7 +88,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry #ifndef SIP_RUN QgsAbstractGeometry *geometryN( int n ); #else - SIP_PYOBJECT geometryN( int n ) const; + SIP_PYOBJECT geometryN( int n ); % MethodCode if ( a0 < 0 || a0 >= sipCpp->numGeometries() ) { diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 4e21b36aaca2..7a1759112ff0 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -82,6 +82,7 @@ class TestQgsGeometry : public QObject void isEmpty(); void equality(); void vertexIterator(); + void partIterator(); // geometry types @@ -477,6 +478,33 @@ void TestQgsGeometry::vertexIterator() QVERIFY( !it2.hasNext() ); } +void TestQgsGeometry::partIterator() +{ + QgsGeometry geom; + QgsGeometryPartIterator it = geom.parts(); + QVERIFY( !it.hasNext() ); + + geom = QgsGeometry::fromWkt( QStringLiteral( "Point( 1 2 )" ) ); + QgsGeometryConstPartIterator it2 = geom.constParts(); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next()->asWkt(), QStringLiteral( "Point (1 2)" ) ); + QVERIFY( !it2.hasNext() ); + + // test that non-const iterator detaches + QgsGeometry geom2 = geom; + it = geom2.parts(); + QVERIFY( it.hasNext() ); + QgsAbstractGeometry *part = it.next(); + QCOMPARE( part->asWkt(), QStringLiteral( "Point (1 2)" ) ); + static_cast< QgsPoint * >( part )->setX( 100 ); + QCOMPARE( geom2.asWkt(), QStringLiteral( "Point (100 2)" ) ); + QVERIFY( !it.hasNext() ); + // geom2 should hve adetached, geom should be unaffected by change + QCOMPARE( geom.asWkt(), QStringLiteral( "Point (1 2)" ) ); + + // See test_qgsgeometry.py for geometry-type specific checks! +} + void TestQgsGeometry::point() { //test QgsPointV2 diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index efff83ea79f6..d957091f4a57 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -95,6 +95,104 @@ def testVertexIterator(self): with self.assertRaises(StopIteration): next(it) + def testPartIterator(self): + g = QgsGeometry() + it = g.parts() + with self.assertRaises(StopIteration): + next(it) + with self.assertRaises(StopIteration): + next(it) + + # single point geometry + g = QgsGeometry.fromWkt('Point (10 10)') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'Point (10 10)') + with self.assertRaises(StopIteration): + next(it) + + it = g.get().parts() + self.assertEqual(next(it).asWkt(), 'Point (10 10)') + with self.assertRaises(StopIteration): + next(it) + + # multi point geometry + g = QgsGeometry.fromWkt('MultiPoint (10 10, 20 20, 10 20)') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'Point (10 10)') + self.assertEqual(next(it).asWkt(), 'Point (20 20)') + self.assertEqual(next(it).asWkt(), 'Point (10 20)') + with self.assertRaises(StopIteration): + next(it) + + it = g.get().parts() + self.assertEqual(next(it).asWkt(), 'Point (10 10)') + self.assertEqual(next(it).asWkt(), 'Point (20 20)') + self.assertEqual(next(it).asWkt(), 'Point (10 20)') + with self.assertRaises(StopIteration): + next(it) + + # empty multi point geometry + g = QgsGeometry.fromWkt('MultiPoint ()') + it = g.parts() + with self.assertRaises(StopIteration): + next(it) + + # single line geometry + g = QgsGeometry.fromWkt('LineString (10 10, 20 10, 30 10)') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'LineString (10 10, 20 10, 30 10)') + with self.assertRaises(StopIteration): + next(it) + + # multi line geometry + g = QgsGeometry.fromWkt('MultiLineString ((10 10, 20 20, 10 20),(5 7, 8 9))') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'LineString (10 10, 20 20, 10 20)') + self.assertEqual(next(it).asWkt(), 'LineString (5 7, 8 9)') + with self.assertRaises(StopIteration): + next(it) + + # empty multi line geometry + g = QgsGeometry.fromWkt('MultiLineString ()') + it = g.parts() + with self.assertRaises(StopIteration): + next(it) + + # single polygon geometry + g = QgsGeometry.fromWkt('Polygon ((10 10, 100 10, 100 100, 10 100, 10 10),(50 50, 55 50, 55 55, 50 55, 50 50))') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'Polygon ((10 10, 100 10, 100 100, 10 100, 10 10),(50 50, 55 50, 55 55, 50 55, 50 50))') + with self.assertRaises(StopIteration): + next(it) + + # multi polygon geometry + g = QgsGeometry.fromWkt('MultiPolygon (((10 10, 100 10, 100 100, 10 100, 10 10),(50 50, 55 50, 55 55, 50 55, 50 50)),((20 2, 20 4, 22 4, 22 2, 20 2)))') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'Polygon ((10 10, 100 10, 100 100, 10 100, 10 10),(50 50, 55 50, 55 55, 50 55, 50 50))') + self.assertEqual(next(it).asWkt(), 'Polygon ((20 2, 20 4, 22 4, 22 2, 20 2))') + with self.assertRaises(StopIteration): + next(it) + + # empty multi polygon geometry + g = QgsGeometry.fromWkt('MultiPolygon ()') + it = g.parts() + with self.assertRaises(StopIteration): + next(it) + + # geometry collection + g = QgsGeometry.fromWkt('GeometryCollection( Point( 1 2), LineString( 4 5, 8 7 ))') + it = g.parts() + self.assertEqual(next(it).asWkt(), 'Point (1 2)') + self.assertEqual(next(it).asWkt(), 'LineString (4 5, 8 7)') + with self.assertRaises(StopIteration): + next(it) + + # empty geometry collection + g = QgsGeometry.fromWkt('GeometryCollection()') + it = g.parts() + with self.assertRaises(StopIteration): + next(it) + def testWktPointLoading(self): myWKT = 'Point (10 10)' myGeometry = QgsGeometry.fromWkt(myWKT)