diff --git a/python/core/auto_generated/geometry/qgscircularstring.sip.in b/python/core/auto_generated/geometry/qgscircularstring.sip.in index 19474037c71..81412294354 100644 --- a/python/core/auto_generated/geometry/qgscircularstring.sip.in +++ b/python/core/auto_generated/geometry/qgscircularstring.sip.in @@ -158,6 +158,7 @@ Sets the circular string's points virtual double yAt( int index ) const; + virtual QgsCircularString *createEmptyWithSameType() const /Factory/; diff --git a/python/core/auto_generated/geometry/qgscompoundcurve.sip.in b/python/core/auto_generated/geometry/qgscompoundcurve.sip.in index c3fe276e2ef..637882c33da 100644 --- a/python/core/auto_generated/geometry/qgscompoundcurve.sip.in +++ b/python/core/auto_generated/geometry/qgscompoundcurve.sip.in @@ -162,6 +162,7 @@ Appends first point if not already closed. virtual double yAt( int index ) const; + virtual QgsCompoundCurve *createEmptyWithSameType() const /Factory/; diff --git a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in index 4e8b850533d..13ad8fd8dd7 100644 --- a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in +++ b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in @@ -201,6 +201,7 @@ Returns approximate rotation angle for a vertex. Usually average angle between a virtual QgsCurvePolygon *toCurveType() const /Factory/; + virtual QgsCurvePolygon *createEmptyWithSameType() const /Factory/; diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index 38d0fb82d36..67d0bcb066a 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -1588,6 +1588,7 @@ was performed on the geometry. .. versionadded:: 3.0 %End + static QgsGeometry fromQPointF( QPointF point ); %Docstring Construct geometry from a QPointF diff --git a/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in b/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in index 050075b6316..28db6f391c0 100644 --- a/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in +++ b/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in @@ -345,6 +345,8 @@ class QgsShapeburstFillSymbolLayer : QgsFillSymbolLayer ~QgsShapeburstFillSymbolLayer(); + + static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; diff --git a/src/core/geometry/qgsabstractgeometry.cpp b/src/core/geometry/qgsabstractgeometry.cpp index 4191f7ad3a4..16926250480 100644 --- a/src/core/geometry/qgsabstractgeometry.cpp +++ b/src/core/geometry/qgsabstractgeometry.cpp @@ -246,6 +246,11 @@ bool QgsAbstractGeometry::convertTo( QgsWkbTypes::Type type ) return true; } +void QgsAbstractGeometry::filterVertices( const std::function & ) +{ + // Ideally this would be pure virtual, but SIP has issues with that +} + QgsVertexIterator QgsAbstractGeometry::vertices() const { return QgsVertexIterator( this ); diff --git a/src/core/geometry/qgsabstractgeometry.h b/src/core/geometry/qgsabstractgeometry.h index 68900709790..d982c0d80e7 100644 --- a/src/core/geometry/qgsabstractgeometry.h +++ b/src/core/geometry/qgsabstractgeometry.h @@ -569,6 +569,18 @@ class CORE_EXPORT QgsAbstractGeometry #ifndef SIP_RUN + /** + * Filters the vertices from the geometry in place, removing any which do not return true for the \a filter function + * check. Has no meaning when called on a single point geometry. + * + * Depending on the \a filter used, this may result in an invalid geometry. However, CurvePolygon rings which are no longer + * valid rings will be automatically removed after filtering. + * + * \since QGIS 3.2 + * \note Not available in Python bindings + */ + virtual void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ); + /** * \ingroup core * The vertex_iterator class provides STL-style iterator for vertices. diff --git a/src/core/geometry/qgscircularstring.cpp b/src/core/geometry/qgscircularstring.cpp index 1e52412fdc8..edb6823f535 100644 --- a/src/core/geometry/qgscircularstring.cpp +++ b/src/core/geometry/qgscircularstring.cpp @@ -546,6 +546,52 @@ double QgsCircularString::yAt( int index ) const return 0.0; } +void QgsCircularString::filterVertices( const std::function &filter ) +{ + bool hasZ = is3D(); + bool hasM = isMeasure(); + int size = mX.size(); + + double *srcX = mX.data(); // clazy:exclude=detaching-member + double *srcY = mY.data(); // clazy:exclude=detaching-member + double *srcM = hasM ? mM.data() : nullptr; // clazy:exclude=detaching-member + double *srcZ = hasZ ? mZ.data() : nullptr; // clazy:exclude=detaching-member + + double *destX = srcX; + double *destY = srcY; + double *destM = srcM; + double *destZ = srcZ; + + int filteredPoints = 0; + for ( int i = 0; i < size; ++i ) + { + double x = *srcX++; + double y = *srcY++; + double z = hasZ ? *srcZ++ : std::numeric_limits::quiet_NaN(); + double m = hasM ? *srcM++ : std::numeric_limits::quiet_NaN(); + + if ( filter( QgsPoint( x, y, z, m ) ) ) + { + filteredPoints++; + *destX++ = x; + *destY++ = y; + if ( hasM ) + *destM++ = m; + if ( hasZ ) + *destZ++ = z; + } + } + + mX.resize( filteredPoints ); + mY.resize( filteredPoints ); + if ( hasZ ) + mZ.resize( filteredPoints ); + if ( hasM ) + mM.resize( filteredPoints ); + + clearCache(); +} + void QgsCircularString::points( QgsPointSequence &pts ) const { pts.clear(); diff --git a/src/core/geometry/qgscircularstring.h b/src/core/geometry/qgscircularstring.h index 84d3a60e160..3e8f3a932bc 100644 --- a/src/core/geometry/qgscircularstring.h +++ b/src/core/geometry/qgscircularstring.h @@ -125,7 +125,9 @@ class CORE_EXPORT QgsCircularString: public QgsCurve void swapXy() override; double xAt( int index ) const override; double yAt( int index ) const override; + #ifndef SIP_RUN + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override; /** * Cast the \a geom to a QgsCircularString. diff --git a/src/core/geometry/qgscompoundcurve.cpp b/src/core/geometry/qgscompoundcurve.cpp index af17a9bd0b4..c809ab4a2d7 100644 --- a/src/core/geometry/qgscompoundcurve.cpp +++ b/src/core/geometry/qgscompoundcurve.cpp @@ -773,6 +773,15 @@ double QgsCompoundCurve::yAt( int index ) const return 0.0; } +void QgsCompoundCurve::filterVertices( const std::function &filter ) +{ + for ( QgsCurve *curve : qgis::as_const( mCurves ) ) + { + curve->filterVertices( filter ); + } + clearCache(); +} + void QgsCompoundCurve::sumUpArea( double &sum ) const { for ( const QgsCurve *curve : mCurves ) diff --git a/src/core/geometry/qgscompoundcurve.h b/src/core/geometry/qgscompoundcurve.h index a3e82f43acd..7430382ea90 100644 --- a/src/core/geometry/qgscompoundcurve.h +++ b/src/core/geometry/qgscompoundcurve.h @@ -125,7 +125,9 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve double xAt( int index ) const override; double yAt( int index ) const override; + #ifndef SIP_RUN + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override; /** * Cast the \a geom to a QgsCompoundCurve. diff --git a/src/core/geometry/qgscurvepolygon.cpp b/src/core/geometry/qgscurvepolygon.cpp index 0f8934908ab..d122db6c088 100644 --- a/src/core/geometry/qgscurvepolygon.cpp +++ b/src/core/geometry/qgscurvepolygon.cpp @@ -1174,6 +1174,31 @@ QgsCurvePolygon *QgsCurvePolygon::toCurveType() const return clone(); } +void QgsCurvePolygon::filterVertices( const std::function &filter ) +{ + if ( mExteriorRing ) + mExteriorRing->filterVertices( filter ); + + QVector filteredRings; + filteredRings.reserve( mInteriorRings.size() ); + for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) ) + { + curve->filterVertices( filter ); + + if ( !curve->isRing() ) + { + // remove invalid rings + delete curve; + } + else + { + filteredRings << curve; + } + } + mInteriorRings = filteredRings; + clearCache(); +} + int QgsCurvePolygon::childCount() const { return 1 + mInteriorRings.count(); diff --git a/src/core/geometry/qgscurvepolygon.h b/src/core/geometry/qgscurvepolygon.h index ade65289dd0..8a57ec70ef3 100644 --- a/src/core/geometry/qgscurvepolygon.h +++ b/src/core/geometry/qgscurvepolygon.h @@ -153,7 +153,9 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface void swapXy() override; QgsCurvePolygon *toCurveType() const override SIP_FACTORY; + #ifndef SIP_RUN + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override; /** * Cast the \a geom to a QgsCurvePolygon. diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 9da72e4f581..92262a5dd22 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -2574,6 +2574,16 @@ QString QgsGeometry::lastError() const return mLastError; } +void QgsGeometry::filterVertices( const std::function &filter ) +{ + if ( !d->geometry ) + return; + + detach(); + + d->geometry->filterVertices( filter ); +} + void QgsGeometry::convertPointList( const QVector &input, QgsPointSequence &output ) { output.clear(); diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 6124b01b6b5..9c8d9315099 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -1529,6 +1529,18 @@ class CORE_EXPORT QgsGeometry */ QString lastError() const; + /** + * Filters the vertices from the geometry in place, removing any which do not return true for the \a filter function + * check. Has no effect when called on a single point geometry. + * + * Depending on the \a filter used, this may result in an invalid geometry. However, CurvePolygon rings which are no longer + * valid rings will be automatically removed after filtering. + * + * \since QGIS 3.2 + * \note Not available in Python bindings + */ + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) SIP_SKIP; + /** * Construct geometry from a QPointF * \param point source QPointF diff --git a/src/core/geometry/qgsgeometrycollection.cpp b/src/core/geometry/qgsgeometrycollection.cpp index 0c672aa3348..f52131cbd62 100644 --- a/src/core/geometry/qgsgeometrycollection.cpp +++ b/src/core/geometry/qgsgeometrycollection.cpp @@ -830,6 +830,16 @@ bool QgsGeometryCollection::dropMValue() return true; } +void QgsGeometryCollection::filterVertices( const std::function &filter ) +{ + for ( QgsAbstractGeometry *geom : qgis::as_const( mGeometries ) ) + { + if ( geom ) + geom->filterVertices( filter ); + } + clearCache(); +} + void QgsGeometryCollection::swapXy() { for ( QgsAbstractGeometry *geom : qgis::as_const( mGeometries ) ) diff --git a/src/core/geometry/qgsgeometrycollection.h b/src/core/geometry/qgsgeometrycollection.h index bd66ac408c4..a1fcd7c8988 100644 --- a/src/core/geometry/qgsgeometrycollection.h +++ b/src/core/geometry/qgsgeometrycollection.h @@ -143,6 +143,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry QgsGeometryCollection *toCurveType() const override SIP_FACTORY; #ifndef SIP_RUN + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override; /** * Cast the \a geom to a QgsGeometryCollection. diff --git a/src/core/geometry/qgslinestring.cpp b/src/core/geometry/qgslinestring.cpp index e8c5a1aa739..250b9468d41 100644 --- a/src/core/geometry/qgslinestring.cpp +++ b/src/core/geometry/qgslinestring.cpp @@ -1329,3 +1329,49 @@ bool QgsLineString::convertTo( QgsWkbTypes::Type type ) return QgsCurve::convertTo( type ); } } + +void QgsLineString::filterVertices( const std::function &filter ) +{ + bool hasZ = is3D(); + bool hasM = isMeasure(); + int size = mX.size(); + + double *srcX = mX.data(); + double *srcY = mY.data(); + double *srcM = hasM ? mM.data() : nullptr; + double *srcZ = hasZ ? mZ.data() : nullptr; + + double *destX = srcX; + double *destY = srcY; + double *destM = srcM; + double *destZ = srcZ; + + int filteredPoints = 0; + for ( int i = 0; i < size; ++i ) + { + double x = *srcX++; + double y = *srcY++; + double z = hasZ ? *srcZ++ : std::numeric_limits::quiet_NaN(); + double m = hasM ? *srcM++ : std::numeric_limits::quiet_NaN(); + + if ( filter( QgsPoint( x, y, z, m ) ) ) + { + filteredPoints++; + *destX++ = x; + *destY++ = y; + if ( hasM ) + *destM++ = m; + if ( hasZ ) + *destZ++ = z; + } + } + + mX.resize( filteredPoints ); + mY.resize( filteredPoints ); + if ( hasZ ) + mZ.resize( filteredPoints ); + if ( hasM ) + mM.resize( filteredPoints ); + + clearCache(); +} diff --git a/src/core/geometry/qgslinestring.h b/src/core/geometry/qgslinestring.h index 52feeb91964..dc38acd42bc 100644 --- a/src/core/geometry/qgslinestring.h +++ b/src/core/geometry/qgslinestring.h @@ -268,6 +268,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve bool convertTo( QgsWkbTypes::Type type ) override; #ifndef SIP_RUN + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override; /** * Cast the \a geom to a QgsLineString. diff --git a/src/core/geometry/qgspoint.cpp b/src/core/geometry/qgspoint.cpp index 58216b40ee7..572bc99adaa 100644 --- a/src/core/geometry/qgspoint.cpp +++ b/src/core/geometry/qgspoint.cpp @@ -592,6 +592,10 @@ bool QgsPoint::convertTo( QgsWkbTypes::Type type ) return false; } +void QgsPoint::filterVertices( const std::function & ) +{ + // no meaning for points +} QPointF QgsPoint::toQPointF() const { diff --git a/src/core/geometry/qgspoint.h b/src/core/geometry/qgspoint.h index f3519c39c30..2fa4e617cc3 100644 --- a/src/core/geometry/qgspoint.h +++ b/src/core/geometry/qgspoint.h @@ -440,6 +440,8 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry #ifndef SIP_RUN + void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override; + /** * Cast the \a geom to a QgsPoint. * Should be used by qgsgeometry_cast( geometry ).