diff --git a/python/core/geometry/qgsabstractgeometry.sip b/python/core/geometry/qgsabstractgeometry.sip index 07c63db7654..4c70db7e695 100644 --- a/python/core/geometry/qgsabstractgeometry.sip +++ b/python/core/geometry/qgsabstractgeometry.sip @@ -463,6 +463,56 @@ Returns the centroid of the geometry :rtype: bool %End + + QgsVertexIterator vertices() const; +%Docstring + Returns Java-style iterator for traversal of vertices of the geometry +.. versionadded:: 3.0 + :rtype: QgsVertexIterator +%End + + protected: + + virtual bool hasChildGeometries() const; +%Docstring + Returns whether the geometry has any child geometries (false for point / curve, true otherwise) +.. note:: + + used for vertex_iterator implementation +.. versionadded:: 3.0 + :rtype: bool +%End + + virtual int childCount() const; +%Docstring + Returns number of child geometries (for geometries with child geometries) or child points (for geometries without child geometries - i.e. curve / point) +.. note:: + + used for vertex_iterator implementation +.. versionadded:: 3.0 + :rtype: int +%End + + virtual QgsAbstractGeometry *childGeometry( int index ) const; +%Docstring + Returns pointer to child geometry (for geometries with child geometries - i.e. geom. collection / polygon) +.. note:: + + used for vertex_iterator implementation +.. versionadded:: 3.0 + :rtype: QgsAbstractGeometry +%End + + virtual QgsPoint childPoint( int index ) const; +%Docstring + Returns point at index (for geometries without child geometries - i.e. curve / point) +.. note:: + + used for vertex_iterator implementation +.. versionadded:: 3.0 + :rtype: QgsPoint +%End + protected: void setZMTypeFromSubGeometry( const QgsAbstractGeometry *subggeom, QgsWkbTypes::Type baseGeomType ); @@ -531,6 +581,54 @@ struct QgsVertexId +class QgsVertexIterator +{ +%Docstring + Java-style iterator for traversal of vertices of a geometry +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsabstractgeometry.h" +%End + public: + QgsVertexIterator(); + + QgsVertexIterator( const QgsAbstractGeometry *geometry ); +%Docstring +Constructs iterator for the given geometry +%End + + bool hasNext() const; +%Docstring +Find out whether there are more vertices + :rtype: bool +%End + + QgsPoint next(); +%Docstring +Return next vertex of the geometry (undefined behavior if hasNext() returns false before calling next()) + :rtype: QgsPoint +%End + + QgsVertexIterator *__iter__(); +%Docstring + :rtype: QgsVertexIterator +%End +%MethodCode + sipRes = sipCpp; +%End + + SIP_PYOBJECT __next__(); +%MethodCode + if ( sipCpp->hasNext() ) + sipRes = sipConvertFromType( new QgsPoint( sipCpp->next() ), sipType_QgsPoint, Py_None ); + else + PyErr_SetString( PyExc_StopIteration, "" ); +%End + +}; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/core/geometry/qgscurve.sip b/python/core/geometry/qgscurve.sip index e5de2c54bd7..5c639760030 100644 --- a/python/core/geometry/qgscurve.sip +++ b/python/core/geometry/qgscurve.sip @@ -177,6 +177,9 @@ class QgsCurve: QgsAbstractGeometry virtual void clearCache() const; + virtual int childCount() const; + virtual QgsPoint childPoint( int index ) const; + }; /************************************************************************ diff --git a/python/core/geometry/qgscurvepolygon.sip b/python/core/geometry/qgscurvepolygon.sip index 2cac48215d1..e4136949778 100644 --- a/python/core/geometry/qgscurvepolygon.sip +++ b/python/core/geometry/qgscurvepolygon.sip @@ -192,6 +192,10 @@ Adds an interior ring to the geometry (takes ownership) virtual QgsCurvePolygon *toCurveType() const /Factory/; + protected: + virtual int childCount() const; + virtual QgsAbstractGeometry *childGeometry( int index ) const; + protected: diff --git a/python/core/geometry/qgsgeometry.sip b/python/core/geometry/qgsgeometry.sip index 28548e110da..d2b46d6ddc2 100644 --- a/python/core/geometry/qgsgeometry.sip +++ b/python/core/geometry/qgsgeometry.sip @@ -241,6 +241,14 @@ Returns true if WKB of the geometry is of WKBMulti* type :rtype: float %End + + QgsVertexIterator vertices() const; +%Docstring + Returns Java-style iterator for traversal of vertices of the geometry +.. versionadded:: 3.0 + :rtype: QgsVertexIterator +%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/geometry/qgsgeometrycollection.sip b/python/core/geometry/qgsgeometrycollection.sip index 8051012377c..bf31cd4caba 100644 --- a/python/core/geometry/qgsgeometrycollection.sip +++ b/python/core/geometry/qgsgeometrycollection.sip @@ -168,6 +168,10 @@ Adds a geometry and takes ownership. Returns true in case of success. + protected: + virtual int childCount() const; + virtual QgsAbstractGeometry *childGeometry( int index ) const; + protected: virtual bool wktOmitChildType() const; diff --git a/python/core/geometry/qgspoint.sip b/python/core/geometry/qgspoint.sip index eb6964a2140..382ee68e5ae 100644 --- a/python/core/geometry/qgspoint.sip +++ b/python/core/geometry/qgspoint.sip @@ -410,6 +410,11 @@ class QgsPoint: QgsAbstractGeometry virtual bool convertTo( QgsWkbTypes::Type type ); + + protected: + virtual int childCount() const; + virtual QgsPoint childPoint( int index ) const; + }; diff --git a/src/core/geometry/qgsabstractgeometry.cpp b/src/core/geometry/qgsabstractgeometry.cpp index 614e1c333bf..4191f7ad3a4 100644 --- a/src/core/geometry/qgsabstractgeometry.cpp +++ b/src/core/geometry/qgsabstractgeometry.cpp @@ -246,6 +246,22 @@ bool QgsAbstractGeometry::convertTo( QgsWkbTypes::Type type ) return true; } +QgsVertexIterator QgsAbstractGeometry::vertices() const +{ + return QgsVertexIterator( this ); +} + +bool QgsAbstractGeometry::hasChildGeometries() const +{ + return QgsWkbTypes::isMultiType( wkbType() ) || dimension() == 2; +} + +QgsPoint QgsAbstractGeometry::childPoint( int index ) const +{ + Q_UNUSED( index ); + return QgsPoint(); +} + bool QgsAbstractGeometry::isEmpty() const { QgsVertexId vId; @@ -265,3 +281,112 @@ QgsAbstractGeometry *QgsAbstractGeometry::segmentize( double tolerance, Segmenta return clone(); } + +QgsAbstractGeometry::vertex_iterator::vertex_iterator( const QgsAbstractGeometry *g, int index ) + : depth( 0 ) +{ + ::memset( levels, 0, sizeof( Level ) * 3 ); // make sure we clean up also the padding areas (for memcmp test in operator==) + levels[0].g = g; + levels[0].index = index; + + digDown(); // go to the leaf level of the first vertex +} + +QgsAbstractGeometry::vertex_iterator &QgsAbstractGeometry::vertex_iterator::operator++() +{ + if ( depth == 0 && levels[0].index >= levels[0].g->childCount() ) + return *this; // end of geometry - nowhere else to go + + Q_ASSERT( !levels[depth].g->hasChildGeometries() ); // we should be at a leaf level + + ++levels[depth].index; + + // traverse up if we are at the end in the current level + while ( depth > 0 && levels[depth].index >= levels[depth].g->childCount() ) + { + --depth; + ++levels[depth].index; + } + + digDown(); // go to the leaf level again + + return *this; +} + +QgsAbstractGeometry::vertex_iterator QgsAbstractGeometry::vertex_iterator::operator++( int ) +{ + vertex_iterator it( *this ); + ++*this; + return it; +} + +QgsPoint QgsAbstractGeometry::vertex_iterator::operator*() const +{ + Q_ASSERT( !levels[depth].g->hasChildGeometries() ); + return levels[depth].g->childPoint( levels[depth].index ); +} + +QgsVertexId QgsAbstractGeometry::vertex_iterator::vertexId() const +{ + int part = 0, ring = 0, vertex = levels[depth].index; + if ( depth == 0 ) + { + // nothing else to do + } + else if ( depth == 1 ) + { + if ( QgsWkbTypes::isMultiType( levels[0].g->wkbType() ) ) + part = levels[0].index; + else + ring = levels[0].index; + } + else if ( depth == 2 ) + { + part = levels[0].index; + ring = levels[1].index; + } + else + { + Q_ASSERT( false ); + return QgsVertexId(); + } + + // get the vertex type: find out from the leaf geometry + QgsVertexId::VertexType vertexType = QgsVertexId::SegmentVertex; + if ( const QgsCurve *curve = dynamic_cast( levels[depth].g ) ) + { + QgsPoint p; + curve->pointAt( vertex, p, vertexType ); + } + + return QgsVertexId( part, ring, vertex, vertexType ); +} + +bool QgsAbstractGeometry::vertex_iterator::operator==( const QgsAbstractGeometry::vertex_iterator &other ) const +{ + if ( depth != other.depth ) + return false; + int res = ::memcmp( levels, other.levels, sizeof( Level ) * ( depth + 1 ) ); + return res == 0; +} + +void QgsAbstractGeometry::vertex_iterator::digDown() +{ + if ( levels[depth].g->hasChildGeometries() && levels[depth].index >= levels[depth].g->childCount() ) + return; // first check we are not already at the end + + // while not "final" depth for the geom: go one level down. + while ( levels[depth].g->hasChildGeometries() ) + { + ++depth; + Q_ASSERT( depth < 3 ); // that's capacity of the levels array + levels[depth].index = 0; + levels[depth].g = levels[depth - 1].g->childGeometry( levels[depth - 1].index ); + } +} + +QgsPoint QgsVertexIterator::next() +{ + n = i++; + return *n; +} diff --git a/src/core/geometry/qgsabstractgeometry.h b/src/core/geometry/qgsabstractgeometry.h index 6569995be2c..65ca9119cc6 100644 --- a/src/core/geometry/qgsabstractgeometry.h +++ b/src/core/geometry/qgsabstractgeometry.h @@ -30,6 +30,7 @@ class QgsMultiCurve; class QgsMultiPointV2; class QgsPoint; struct QgsVertexId; +class QgsVertexIterator; class QPainter; class QDomDocument; class QDomElement; @@ -463,6 +464,114 @@ class CORE_EXPORT QgsAbstractGeometry */ virtual bool convertTo( QgsWkbTypes::Type type ); +#ifndef SIP_RUN + + /** + * \ingroup core + * The vertex_iterator class provides STL-style iterator for vertices. + * \since QGIS 3.0 + */ + class CORE_EXPORT vertex_iterator + { + private: + + /** + * A helper structure to keep track of vertex traversal within one level within a geometry. + * For example, linestring geometry will have just one level, while multi-polygon has three levels + * (part index, ring index, vertex index). + */ + struct Level + { + const QgsAbstractGeometry *g; //!< Current geometry + int index; //!< Ptr in the current geometry + }; + + Level levels[3]; //!< Stack of levels - three levels should be sufficient (e.g. part index, ring index, vertex index) + int depth; //!< At what depth level are we right now + + void digDown(); //!< Prepare the stack of levels so that it points to a leaf child geometry + + public: + //! Create invalid iterator + vertex_iterator() : depth( -1 ) {} + + //! Create vertex iterator for a geometry + vertex_iterator( const QgsAbstractGeometry *g, int index ); + + /** + * The prefix ++ operator (++it) advances the iterator to the next vertex and returns an iterator to the new current vertex. + * Calling this function on iterator that is already past the last item leads to undefined results. + */ + vertex_iterator &operator++(); + + //! The postfix ++ operator (it++) advances the iterator to the next vertex and returns an iterator to the previously current vertex. + vertex_iterator operator++( int ); + + //! Returns the current item. + QgsPoint operator*() const; + + //! Returns vertex ID of the current item. + QgsVertexId vertexId() const; + + bool operator==( const vertex_iterator &other ) const; + bool operator!=( const vertex_iterator &other ) const { return !( *this == other ); } + }; + + /** + * Returns STL-style iterator pointing to the first vertex of the geometry + * \since QGIS 3.0 + */ + vertex_iterator vertices_begin() const + { + return vertex_iterator( this, 0 ); + } + + /** + * Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry + * \since QGIS 3.0 + */ + vertex_iterator vertices_end() const + { + return vertex_iterator( this, childCount() ); + } +#endif + + /** + * Returns Java-style iterator for traversal of vertices of the geometry + * \since QGIS 3.0 + */ + QgsVertexIterator vertices() const; + + protected: + + /** + * Returns whether the geometry has any child geometries (false for point / curve, true otherwise) + * \note used for vertex_iterator implementation + * \since QGIS 3.0 + */ + virtual bool hasChildGeometries() const; + + /** + * Returns number of child geometries (for geometries with child geometries) or child points (for geometries without child geometries - i.e. curve / point) + * \note used for vertex_iterator implementation + * \since QGIS 3.0 + */ + virtual int childCount() const { return 0; } + + /** + * Returns pointer to child geometry (for geometries with child geometries - i.e. geom. collection / polygon) + * \note used for vertex_iterator implementation + * \since QGIS 3.0 + */ + virtual QgsAbstractGeometry *childGeometry( int index ) const { Q_UNUSED( index ); return nullptr; } + + /** + * Returns point at index (for geometries without child geometries - i.e. curve / point) + * \note used for vertex_iterator implementation + * \since QGIS 3.0 + */ + virtual QgsPoint childPoint( int index ) const; + protected: QgsWkbTypes::Type mWkbType = QgsWkbTypes::Unknown; @@ -554,4 +663,51 @@ inline T qgsgeometry_cast( const QgsAbstractGeometry *geom ) // clazy:excludeall=qstring-allocations +/** + * \ingroup core + * \brief Java-style iterator for traversal of vertices of a geometry + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsVertexIterator +{ + public: + QgsVertexIterator(): g( nullptr ) {} + + //! Constructs iterator for the given geometry + QgsVertexIterator( const QgsAbstractGeometry *geometry ) + : g( geometry ) + , i( g->vertices_begin() ) + , n( g->vertices_end() ) + { + } + + //! Find out whether there are more vertices + bool hasNext() const + { + return g && g->vertices_end() != i; + } + + //! Return next vertex of the geometry (undefined behavior if hasNext() returns false before calling next()) + QgsPoint next(); + +#ifdef SIP_RUN + QgsVertexIterator *__iter__(); + % MethodCode + sipRes = sipCpp; + % End + + SIP_PYOBJECT __next__(); + % MethodCode + if ( sipCpp->hasNext() ) + sipRes = sipConvertFromType( new QgsPoint( sipCpp->next() ), sipType_QgsPoint, Py_None ); + else + PyErr_SetString( PyExc_StopIteration, "" ); + % End +#endif + + private: + const QgsAbstractGeometry *g; + QgsAbstractGeometry::vertex_iterator i, n; +}; + #endif //QGSABSTRACTGEOMETRYV2 diff --git a/src/core/geometry/qgscurve.cpp b/src/core/geometry/qgscurve.cpp index 55088b839e5..73e75a2a74c 100644 --- a/src/core/geometry/qgscurve.cpp +++ b/src/core/geometry/qgscurve.cpp @@ -156,3 +156,16 @@ void QgsCurve::clearCache() const QgsAbstractGeometry::clearCache(); } +int QgsCurve::childCount() const +{ + return numPoints(); +} + +QgsPoint QgsCurve::childPoint( int index ) const +{ + QgsPoint point; + QgsVertexId::VertexType type; + bool res = pointAt( index, point, type ); + Q_ASSERT( res ); + return point; +} diff --git a/src/core/geometry/qgscurve.h b/src/core/geometry/qgscurve.h index a3509cb401d..300ef08a9df 100644 --- a/src/core/geometry/qgscurve.h +++ b/src/core/geometry/qgscurve.h @@ -184,6 +184,9 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry void clearCache() const override; + virtual int childCount() const override; + virtual QgsPoint childPoint( int index ) const override; + private: mutable QgsRectangle mBoundingBox; diff --git a/src/core/geometry/qgscurvepolygon.cpp b/src/core/geometry/qgscurvepolygon.cpp index dd138be2ab2..d92cc908751 100644 --- a/src/core/geometry/qgscurvepolygon.cpp +++ b/src/core/geometry/qgscurvepolygon.cpp @@ -989,3 +989,16 @@ QgsCurvePolygon *QgsCurvePolygon::toCurveType() const { return clone(); } + +int QgsCurvePolygon::childCount() const +{ + return 1 + mInteriorRings.count(); +} + +QgsAbstractGeometry *QgsCurvePolygon::childGeometry( int index ) const +{ + if ( index == 0 ) + return mExteriorRing.get(); + else + return mInteriorRings.at( index - 1 ); +} diff --git a/src/core/geometry/qgscurvepolygon.h b/src/core/geometry/qgscurvepolygon.h index 4ffff2cc105..64d36fa4da7 100644 --- a/src/core/geometry/qgscurvepolygon.h +++ b/src/core/geometry/qgscurvepolygon.h @@ -171,6 +171,10 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface return nullptr; } #endif + protected: + virtual int childCount() const override; + virtual QgsAbstractGeometry *childGeometry( int index ) const override; + protected: std::unique_ptr< QgsCurve > mExteriorRing; diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index ca0b0020e40..f51baa11847 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -1551,6 +1551,27 @@ double QgsGeometry::hausdorffDistanceDensify( const QgsGeometry &geom, double de return g.hausdorffDistanceDensify( geom.d->geometry.get(), densifyFraction, &mLastError ); } +QgsAbstractGeometry::vertex_iterator QgsGeometry::vertices_begin() const +{ + if ( !d->geometry ) + return QgsAbstractGeometry::vertex_iterator(); + return d->geometry->vertices_begin(); +} + +QgsAbstractGeometry::vertex_iterator QgsGeometry::vertices_end() const +{ + if ( !d->geometry ) + return QgsAbstractGeometry::vertex_iterator(); + return d->geometry->vertices_end(); +} + +QgsVertexIterator QgsGeometry::vertices() const +{ + if ( !d->geometry ) + return QgsVertexIterator(); + return QgsVertexIterator( 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 046ae9b97ec..bfc94dfa00d 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -281,6 +281,27 @@ class CORE_EXPORT QgsGeometry */ double distance( const QgsGeometry &geom ) const; +#ifndef SIP_RUN + + /** + * Returns STL-style iterator pointing to the first vertex of the geometry + * \since QGIS 3.0 + */ + QgsAbstractGeometry::vertex_iterator vertices_begin() const; + + /** + * Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry + * \since QGIS 3.0 + */ + QgsAbstractGeometry::vertex_iterator vertices_end() const; +#endif + + /** + * Returns Java-style iterator for traversal of vertices of the geometry + * \since QGIS 3.0 + */ + QgsVertexIterator vertices() 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.cpp b/src/core/geometry/qgsgeometrycollection.cpp index c708c7bf208..48471386984 100644 --- a/src/core/geometry/qgsgeometrycollection.cpp +++ b/src/core/geometry/qgsgeometrycollection.cpp @@ -733,3 +733,13 @@ bool QgsGeometryCollection::wktOmitChildType() const { return false; } + +int QgsGeometryCollection::childCount() const +{ + return mGeometries.count(); +} + +QgsAbstractGeometry *QgsGeometryCollection::childGeometry( int index ) const +{ + return mGeometries.at( index ); +} diff --git a/src/core/geometry/qgsgeometrycollection.h b/src/core/geometry/qgsgeometrycollection.h index a6d02c911a2..0f0c954ad5d 100644 --- a/src/core/geometry/qgsgeometrycollection.h +++ b/src/core/geometry/qgsgeometrycollection.h @@ -157,6 +157,10 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry } #endif + protected: + virtual int childCount() const override; + virtual QgsAbstractGeometry *childGeometry( int index ) const override; + protected: QVector< QgsAbstractGeometry * > mGeometries; diff --git a/src/core/geometry/qgspoint.cpp b/src/core/geometry/qgspoint.cpp index db47b5d1817..4c40c95ca24 100644 --- a/src/core/geometry/qgspoint.cpp +++ b/src/core/geometry/qgspoint.cpp @@ -644,3 +644,14 @@ int QgsPoint::dimension() const { return 0; } + +int QgsPoint::childCount() const +{ + return 1; +} + +QgsPoint QgsPoint::childPoint( int index ) const +{ + Q_ASSERT( index == 0 ); + return *this; +} diff --git a/src/core/geometry/qgspoint.h b/src/core/geometry/qgspoint.h index b6cc3c4b02b..d5c209187c5 100644 --- a/src/core/geometry/qgspoint.h +++ b/src/core/geometry/qgspoint.h @@ -449,6 +449,11 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry return nullptr; } #endif + + protected: + virtual int childCount() const override; + virtual QgsPoint childPoint( int index ) const override; + private: double mX; double mY; diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 0bae440156f..ab294050098 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -70,6 +70,7 @@ class TestQgsGeometry : public QObject void asVariant(); //test conversion to and from a QVariant void isEmpty(); void operatorBool(); + void vertexIterator(); // geometry types void point(); //test QgsPointV2 @@ -415,6 +416,23 @@ void TestQgsGeometry::operatorBool() QVERIFY( !geom ); } +void TestQgsGeometry::vertexIterator() +{ + QgsGeometry geom; + QgsVertexIterator it = geom.vertices(); + QVERIFY( !it.hasNext() ); + + QgsPolyline polyline; + polyline << QgsPoint( 1, 2 ) << QgsPoint( 3, 4 ); + QgsGeometry geom2 = QgsGeometry::fromPolyline( polyline ); + QgsVertexIterator it2 = geom2.vertices(); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 1, 2 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 3, 4 ) ); + QVERIFY( !it2.hasNext() ); +} + void TestQgsGeometry::point() { //test QgsPointV2 @@ -769,6 +787,20 @@ void TestQgsGeometry::point() QCOMPARE( p22, p21 ); QCOMPARE( v, QgsVertexId( 1, 0, 0 ) ); + // vertex iterator + QgsAbstractGeometry::vertex_iterator it1 = p21.vertices_begin(); + QgsAbstractGeometry::vertex_iterator it1end = p21.vertices_end(); + QCOMPARE( *it1, p21 ); + QCOMPARE( it1.vertexId(), QgsVertexId( 0, 0, 0 ) ); + ++it1; + QCOMPARE( it1, it1end ); + + // Java-style iterator + QgsVertexIterator it2( &p21 ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), p21 ); + QVERIFY( !it2.hasNext() ); + //vertexAt - will always be same as point QCOMPARE( p21.vertexAt( QgsVertexId() ), p21 ); QCOMPARE( p21.vertexAt( QgsVertexId( 0, 0, 0 ) ), p21 ); @@ -1895,6 +1927,7 @@ void TestQgsGeometry::circularString() QVERIFY( !l32.nextVertex( v, p ) ); v = QgsVertexId( 0, 0, 10 ); QVERIFY( !l32.nextVertex( v, p ) ); + //CircularString l32.setPoints( QgsPointSequence() << QgsPoint( 1, 2 ) << QgsPoint( 11, 12 ) ); v = QgsVertexId( 0, 0, 2 ); //out of range @@ -3568,6 +3601,15 @@ void TestQgsGeometry::lineString() QVERIFY( !l32.nextVertex( v, p ) ); v = QgsVertexId( 0, 0, 10 ); QVERIFY( !l32.nextVertex( v, p ) ); + + // vertex iterator on empty linestring + QgsAbstractGeometry::vertex_iterator it1 = l32.vertices_begin(); + QCOMPARE( it1, l32.vertices_end() ); + + // Java-style iterator on empty linetring + QgsVertexIterator it1x( &l32 ); + QVERIFY( !it1x.hasNext() ); + //LineString l32.setPoints( QgsPointSequence() << QgsPoint( 1, 2 ) << QgsPoint( 11, 12 ) ); v = QgsVertexId( 0, 0, 2 ); //out of range @@ -3591,6 +3633,24 @@ void TestQgsGeometry::lineString() QCOMPARE( v, QgsVertexId( 1, 0, 1 ) ); //test that part number is maintained QCOMPARE( p, QgsPoint( 11, 12 ) ); + // vertex iterator + QgsAbstractGeometry::vertex_iterator it2 = l32.vertices_begin(); + QCOMPARE( *it2, QgsPoint( 1, 2 ) ); + QCOMPARE( it2.vertexId(), QgsVertexId( 0, 0, 0 ) ); + ++it2; + QCOMPARE( *it2, QgsPoint( 11, 12 ) ); + QCOMPARE( it2.vertexId(), QgsVertexId( 0, 0, 1 ) ); + ++it2; + QCOMPARE( it2, l32.vertices_end() ); + + // Java-style iterator + QgsVertexIterator it2x( &l32 ); + QVERIFY( it2x.hasNext() ); + QCOMPARE( it2x.next(), QgsPoint( 1, 2 ) ); + QVERIFY( it2x.hasNext() ); + QCOMPARE( it2x.next(), QgsPoint( 11, 12 ) ); + QVERIFY( !it2x.hasNext() ); + //LineStringZ l32.setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointZ, 1, 2, 3 ) << QgsPoint( QgsWkbTypes::PointZ, 11, 12, 13 ) ); v = QgsVertexId( 0, 0, -1 ); @@ -3601,6 +3661,7 @@ void TestQgsGeometry::lineString() QCOMPARE( v, QgsVertexId( 0, 0, 1 ) ); QCOMPARE( p, QgsPoint( QgsWkbTypes::PointZ, 11, 12, 13 ) ); QVERIFY( !l32.nextVertex( v, p ) ); + //LineStringM l32.setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointM, 1, 2, 0, 4 ) << QgsPoint( QgsWkbTypes::PointM, 11, 12, 0, 14 ) ); v = QgsVertexId( 0, 0, -1 ); @@ -10336,6 +10397,25 @@ void TestQgsGeometry::multiPoint() QgsVertexId after; // return error - points have no segments QVERIFY( boundaryMP.closestSegment( QgsPoint( 0.5, 0.5 ), closest, after ) < 0 ); + + // vertex iterator + QgsAbstractGeometry::vertex_iterator it = boundaryMP.vertices_begin(); + QgsAbstractGeometry::vertex_iterator itEnd = boundaryMP.vertices_end(); + QCOMPARE( *it, QgsPoint( 0, 0 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 1, 1 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 0 ) ); + ++it; + QCOMPARE( it, itEnd ); + + // Java-style iterator + QgsVertexIterator it2( &boundaryMP ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 0, 0 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 1, 1 ) ); + QVERIFY( !it2.hasNext() ); } void TestQgsGeometry::multiLineString() @@ -10831,6 +10911,45 @@ void TestQgsGeometry::multiLineString() QCOMPARE( static_cast< QgsPoint *>( mpBoundary->geometryN( 3 ) )->y(), 11.0 ); delete boundary; + // vertex iterator: 2 linestrings with 3 points each + QgsAbstractGeometry::vertex_iterator it = multiLine1.vertices_begin(); + QgsAbstractGeometry::vertex_iterator itEnd = multiLine1.vertices_end(); + QCOMPARE( *it, QgsPoint( 0, 0 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 1, 0 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 1 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 1, 1 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 2 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10, 10 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 11, 10 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 1 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 11, 11 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 2 ) ); + ++it; + QCOMPARE( it, itEnd ); + + // Java-style iterator + QgsVertexIterator it2( &multiLine1 ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 0, 0 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 1, 0 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 1, 1 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10, 10 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 11, 10 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 11, 11 ) ); + QVERIFY( !it2.hasNext() ); + // add a closed string = no boundary QgsLineString boundaryLine3; boundaryLine3.setPoints( QList() << QgsPoint( 20, 20 ) << QgsPoint( 21, 20 ) << QgsPoint( 21, 21 ) << QgsPoint( 20, 20 ) ); @@ -12726,6 +12845,102 @@ void TestQgsGeometry::multiPolygon() QCOMPARE( dynamic_cast< QgsLineString * >( multiLineBoundary->geometryN( 3 ) )->yAt( 1 ), 10.8 ); QCOMPARE( dynamic_cast< QgsLineString * >( multiLineBoundary->geometryN( 3 ) )->yAt( 2 ), 10.9 ); QCOMPARE( dynamic_cast< QgsLineString * >( multiLineBoundary->geometryN( 3 ) )->yAt( 3 ), 10.8 ); + + // vertex iterator: 2 polygons (one with just exterior ring, other with two interior rings) + QgsAbstractGeometry::vertex_iterator it = multiPolygon1.vertices_begin(); + QgsAbstractGeometry::vertex_iterator itEnd = multiPolygon1.vertices_end(); + QCOMPARE( *it, QgsPoint( 0, 0 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 1, 0 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 1 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 1, 1 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 2 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 0, 0 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 0, 0, 3 ) ); + ++it; + // 2nd polygon - exterior ring + QCOMPARE( *it, QgsPoint( 10, 10 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 11, 10 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 1 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 11, 11 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 2 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10, 10 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 0, 3 ) ); + ++it; + // 2nd polygon - 1st interior ring + QCOMPARE( *it, QgsPoint( 10.1, 10.1 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 1, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10.2, 10.1 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 1, 1 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10.2, 10.2 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 1, 2 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10.1, 10.1 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 1, 3 ) ); + ++it; + // 2nd polygon - 2nd interior ring + QCOMPARE( *it, QgsPoint( 10.8, 10.8 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 2, 0 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10.9, 10.8 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 2, 1 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10.9, 10.9 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 2, 2 ) ); + ++it; + QCOMPARE( *it, QgsPoint( 10.8, 10.8 ) ); + QCOMPARE( it.vertexId(), QgsVertexId( 1, 2, 3 ) ); + ++it; + // done! + QCOMPARE( it, itEnd ); + + // Java-style iterator + QgsVertexIterator it2( &multiPolygon1 ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 0, 0 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 1, 0 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 1, 1 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 0, 0 ) ); + QVERIFY( it2.hasNext() ); + // 2nd polygon - exterior ring + QCOMPARE( it2.next(), QgsPoint( 10, 10 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 11, 10 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 11, 11 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10, 10 ) ); + QVERIFY( it2.hasNext() ); + // 2nd polygon - 1st interior ring + QCOMPARE( it2.next(), QgsPoint( 10.1, 10.1 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10.2, 10.1 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10.2, 10.2 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10.1, 10.1 ) ); + QVERIFY( it2.hasNext() ); + // 2nd polygon - 2nd interior ring + QCOMPARE( it2.next(), QgsPoint( 10.8, 10.8 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10.9, 10.8 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10.9, 10.9 ) ); + QVERIFY( it2.hasNext() ); + QCOMPARE( it2.next(), QgsPoint( 10.8, 10.8 ) ); + QVERIFY( !it2.hasNext() ); } void TestQgsGeometry::geometryCollection() diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index 66bee7b73d5..19709cebd03 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -85,6 +85,14 @@ class TestQgsGeometry(unittest.TestCase): g = QgsGeometry.fromWkt('MultiPoint ()') self.assertTrue(g.isEmpty()) + def testVertexIterator(self): + g = QgsGeometry.fromWkt('Linestring(11 12, 13 14)') + it = g.vertices() + self.assertEqual(next(it), QgsPoint(11, 12)) + self.assertEqual(next(it), QgsPoint(13, 14)) + with self.assertRaises(StopIteration): + next(it) + def testWktPointLoading(self): myWKT = 'Point (10 10)' myGeometry = QgsGeometry.fromWkt(myWKT)