Skip to content

Commit

Permalink
Implement QgsGeometry::isEmpty()
Browse files Browse the repository at this point in the history
Faster than QgsGeometry::isGeosEmpty() because it avoids the
conversion to GEOS geometries and just uses the QgsAbstractGeometry
subclasses directly.

Also implements faster isEmpty() overrides for specific
QgsAbstractGeometry subclasses.
  • Loading branch information
nyalldawson committed Jan 30, 2017
1 parent 49aae6e commit af0d68b
Show file tree
Hide file tree
Showing 31 changed files with 113 additions and 11 deletions.
2 changes: 1 addition & 1 deletion python/core/geometry/qgsabstractgeometry.sip
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ class QgsAbstractGeometry

/** Returns true if the geometry is empty
*/
bool isEmpty() const;
virtual bool isEmpty() const;

/** Returns true if the geometry contains curved segments
*/
Expand Down
1 change: 1 addition & 0 deletions python/core/geometry/qgscircularstring.sip
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class QgsCircularString: public QgsCurve
QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const;
QString asJSON( int precision = 17 ) const;

bool isEmpty() const;
int numPoints() const;

/** Returns the point at index i within the circular string.
Expand Down
2 changes: 2 additions & 0 deletions python/core/geometry/qgscompoundcurve.sip
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class QgsCompoundCurve: public QgsCurve
virtual QgsPointV2 endPoint() const;
virtual void points( QList<QgsPointV2>& pts ) const;
virtual int numPoints() const;
bool isEmpty() const;

/** Returns a new line string geometry corresponding to a segmentized approximation
* of the curve.
* @param tolerance segmentation tolerance
Expand Down
1 change: 1 addition & 0 deletions python/core/geometry/qgscurvepolygon.sip
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class QgsCurvePolygon: public QgsSurface

virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
virtual int nCoordinates() const;
bool isEmpty() const;
double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt, QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const;
bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const;

Expand Down
2 changes: 2 additions & 0 deletions python/core/geometry/qgsgeometry.sip
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ class QgsGeometry
*/
QgsWkbTypes::GeometryType type() const;

bool isEmpty() const;

/** Returns true if WKB of the geometry is of WKBMulti* type */
bool isMultipart() const;

Expand Down
1 change: 1 addition & 0 deletions python/core/geometry/qgsgeometrycollection.sip
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class QgsGeometryCollection: public QgsAbstractGeometry
QgsAbstractGeometry* geometryN( int n );

//methods inherited from QgsAbstractGeometry
bool isEmpty() const;
virtual int dimension() const;
virtual QString geometryType() const;
virtual void clear();
Expand Down
2 changes: 1 addition & 1 deletion python/core/geometry/qgslinestring.sip
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class QgsLineString: public QgsCurve
virtual int dimension() const;
virtual QgsLineString* clone() const /Factory/;
virtual void clear();

bool isEmpty() const;
virtual bool fromWkb( QgsConstWkbPtr& wkb );
virtual bool fromWkt( const QString& wkt );

Expand Down
1 change: 1 addition & 0 deletions python/core/geometry/qgspointv2.sip
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ class QgsPointV2: public QgsAbstractGeometry
virtual int dimension() const;
virtual QgsPointV2* clone() const /Factory/;
void clear();
bool isEmpty() const;
virtual bool fromWkb( QgsConstWkbPtr& wkb );
virtual bool fromWkt( const QString& wkt );
QByteArray asWkb() const;
Expand Down
2 changes: 1 addition & 1 deletion python/plugins/processing/algs/qgis/CheckValidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def doCheck(self, feedback):
attrs = inFeat.attributes()

valid = True
if not geom.isNull() and not geom.isGeosEmpty():
if not geom.isNull() and not geom.isEmpty():
errors = list(geom.validateGeometry())
if errors:
# QGIS method return a summary at the end
Expand Down
4 changes: 2 additions & 2 deletions python/plugins/processing/algs/qgis/Dissolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def processAlgorithm(self, feedback):
first = False

tmpInGeom = inFeat.geometry()
if tmpInGeom.isNull() or tmpInGeom.isGeosEmpty():
if tmpInGeom.isNull() or tmpInGeom.isEmpty():
continue

errors = tmpInGeom.validateGeometry()
Expand Down Expand Up @@ -137,7 +137,7 @@ def processAlgorithm(self, feedback):
index_attrs = tuple([attrs[i] for i in field_indexes])

tmpInGeom = QgsGeometry(inFeat.geometry())
if tmpInGeom.isGeosEmpty():
if tmpInGeom and tmpInGeom.isEmpty():
continue
errors = tmpInGeom.validateGeometry()
if len(errors) != 0:
Expand Down
2 changes: 1 addition & 1 deletion python/plugins/processing/algs/qgis/HypsometricCurves.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def processAlgorithm(self, feedback):
geom = f.geometry()
intersectedGeom = rasterGeom.intersection(geom)

if intersectedGeom.isGeosEmpty():
if intersectedGeom.isEmpty():
feedback.pushInfo(
self.tr('Feature %d does not intersect raster or '
'entirely located in NODATA area' % f.id()))
Expand Down
2 changes: 1 addition & 1 deletion python/plugins/processing/algs/qgis/Intersection.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def processAlgorithm(self, feedback):
if int_com:
int_sym = geom.symDifference(tmpGeom)
int_geom = QgsGeometry(int_com.difference(int_sym))
if int_geom.isGeosEmpty() or not int_geom.isGeosValid():
if int_geom.isEmpty() or not int_geom.isGeosValid():
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
self.tr('GEOS geoprocessing error: One or '
'more input features have invalid '
Expand Down
2 changes: 1 addition & 1 deletion src/app/qgsmaptooladdfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ void QgsMapToolAddFeature::cadCanvasReleaseEvent( QgsMapMouseEvent* e )
{
//not a polygon type. Impossible to get there
}
if ( f->geometry().isGeosEmpty() ) //avoid intersection might have removed the whole geometry
if ( f->geometry().isEmpty() ) //avoid intersection might have removed the whole geometry
{
emit messageEmitted( tr( "The feature cannot be added because it's geometry collapsed due to intersection avoidance" ), QgsMessageBar::CRITICAL );
stopCapturing();
Expand Down
2 changes: 1 addition & 1 deletion src/app/qgsmaptoolreshape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ void QgsMapToolReshape::cadCanvasReleaseEvent( QgsMapMouseEvent * e )
return;
}

if ( geom.isGeosEmpty() ) //intersection removal might have removed the whole geometry
if ( geom.isEmpty() ) //intersection removal might have removed the whole geometry
{
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgsabstractgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ class CORE_EXPORT QgsAbstractGeometry

/** Returns true if the geometry is empty
*/
bool isEmpty() const;
virtual bool isEmpty() const;

/** Returns true if the geometry contains curved segments
*/
Expand Down
5 changes: 5 additions & 0 deletions src/core/geometry/qgscircularstring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ QString QgsCircularString::asJSON( int precision ) const
return json;
}

bool QgsCircularString::isEmpty() const
{
return mX.isEmpty();
}

//curve interface
double QgsCircularString::length() const
{
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgscircularstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const override;
QString asJSON( int precision = 17 ) const override;

bool isEmpty() const override;
int numPoints() const override;

/** Returns the point at index i within the circular string.
Expand Down
13 changes: 13 additions & 0 deletions src/core/geometry/qgscompoundcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,19 @@ int QgsCompoundCurve::numPoints() const
return nPoints;
}

bool QgsCompoundCurve::isEmpty() const
{
if ( mCurves.isEmpty() )
return true;

Q_FOREACH ( QgsCurve* curve, mCurves )
{
if ( !curve->isEmpty() )
return false;
}
return true;
}

QgsLineString* QgsCompoundCurve::curveToLine( double tolerance, SegmentationToleranceType toleranceType ) const
{
QList< QgsCurve* >::const_iterator curveIt = mCurves.constBegin();
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgscompoundcurve.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
virtual QgsPointV2 endPoint() const override;
virtual void points( QgsPointSequence &pts ) const override;
virtual int numPoints() const override;
bool isEmpty() const override;

/** Returns a new line string geometry corresponding to a segmentized approximation
* of the curve.
Expand Down
8 changes: 8 additions & 0 deletions src/core/geometry/qgscurvepolygon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,14 @@ int QgsCurvePolygon::nCoordinates() const
return count;
}

bool QgsCurvePolygon::isEmpty() const
{
if ( !mExteriorRing )
return true;

return mExteriorRing->isEmpty();
}

double QgsCurvePolygon::closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt, QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const
{
if ( !mExteriorRing )
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgscurvepolygon.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface

virtual QgsCoordinateSequence coordinateSequence() const override;
virtual int nCoordinates() const override;
bool isEmpty() const override;
double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt, QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const override;
bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const override;

Expand Down
10 changes: 10 additions & 0 deletions src/core/geometry/qgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ QgsWkbTypes::GeometryType QgsGeometry::type() const
return static_cast< QgsWkbTypes::GeometryType >( QgsWkbTypes::geometryType( d->geometry->wkbType() ) );
}

bool QgsGeometry::isEmpty() const
{
if ( !d->geometry )
{
return true;
}

return d->geometry->isEmpty();
}

bool QgsGeometry::isMultipart() const
{
if ( !d->geometry )
Expand Down
10 changes: 10 additions & 0 deletions src/core/geometry/qgsgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class CORE_EXPORT QgsGeometry
* accessible via @link geometry @endlink).
* @see geometry
* @note added in QGIS 2.10
* @see isEmpty()
*/
bool isNull() const;

Expand Down Expand Up @@ -174,6 +175,14 @@ class CORE_EXPORT QgsGeometry
*/
QgsWkbTypes::GeometryType type() const;

/**
* Returns true if the geometry is empty (eg a linestring with no vertices,
* or a collection with no geometries).
* @see isNull()
* @see isGeosEmpty()
*/
bool isEmpty() const;

//! Returns true if WKB of the geometry is of WKBMulti* type
bool isMultipart() const;

Expand All @@ -189,6 +198,7 @@ class CORE_EXPORT QgsGeometry

/** Check if the geometry is empty using GEOS
@note added in 1.5
@see isEmpty()
*/
bool isGeosEmpty() const;

Expand Down
13 changes: 13 additions & 0 deletions src/core/geometry/qgsgeometrycollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ QgsAbstractGeometry* QgsGeometryCollection::geometryN( int n )
return mGeometries.value( n );
}

bool QgsGeometryCollection::isEmpty() const
{
if ( mGeometries.isEmpty() )
return true;

Q_FOREACH ( QgsAbstractGeometry* geometry, mGeometries )
{
if ( !geometry->isEmpty() )
return false;
}
return true;
}

bool QgsGeometryCollection::addGeometry( QgsAbstractGeometry* g )
{
if ( !g )
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgsgeometrycollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
QgsAbstractGeometry* geometryN( int n );

//methods inherited from QgsAbstractGeometry
bool isEmpty() const override;
virtual int dimension() const override;
virtual QString geometryType() const override { return QStringLiteral( "GeometryCollection" ); }
virtual void clear() override;
Expand Down
5 changes: 5 additions & 0 deletions src/core/geometry/qgslinestring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ void QgsLineString::clear()
clearCache();
}

bool QgsLineString::isEmpty() const
{
return mX.isEmpty();
}

bool QgsLineString::fromWkb( QgsConstWkbPtr& wkbPtr )
{
if ( !wkbPtr )
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgslinestring.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve
virtual int dimension() const override { return 1; }
virtual QgsLineString* clone() const override;
virtual void clear() override;
bool isEmpty() const override;

virtual bool fromWkb( QgsConstWkbPtr& wkb ) override;
virtual bool fromWkt( const QString& wkt ) override;
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgspointv2.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ class CORE_EXPORT QgsPointV2: public QgsAbstractGeometry
QgsPointV2 operator-( QgsVector v ) const { QgsPointV2 r = *this; r.rx() -= v.x(); r.ry() -= v.y(); return r; }

//implementation of inherited methods
bool isEmpty() const override { return false; }
virtual QgsRectangle boundingBox() const override { return QgsRectangle( mX, mY, mX, mY ); }
virtual QString geometryType() const override { return QStringLiteral( "Point" ); }
virtual int dimension() const override { return 0; }
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/spatialquery/qgsspatialquery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ bool QgsSpatialQuery::hasValidGeometry( QgsFeature &feature )

QgsGeometry geom = feature.geometry();

if ( geom.isNull() || geom.isGeosEmpty() )
if ( geom.isNull() || geom.isEmpty() )
return false;

return true;
Expand Down
12 changes: 12 additions & 0 deletions tests/src/core/testqgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2348,6 +2348,18 @@ void TestQgsGeometry::polygon()
QVERIFY( !p1.interiorRing( 0 ) );
QCOMPARE( p1.wkbType(), QgsWkbTypes::Polygon );

// empty exterior ring
ext = new QgsLineString();
p1.setExteriorRing( ext );
QVERIFY( p1.isEmpty() );
QCOMPARE( p1.numInteriorRings(), 0 );
QCOMPARE( p1.nCoordinates(), 0 );
QCOMPARE( p1.ringCount(), 1 );
QCOMPARE( p1.partCount(), 1 );
QVERIFY( p1.exteriorRing() );
QVERIFY( !p1.interiorRing( 0 ) );
QCOMPARE( p1.wkbType(), QgsWkbTypes::Polygon );

//valid exterior ring
ext = new QgsLineString();
ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) << QgsPointV2( 10, 10 )
Expand Down
12 changes: 12 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ def testBool(self):
g.setGeometry(None)
self.assertFalse(g)

def testIsEmpty(self):
"""
the bulk of these tests are in testqgsgeometry.cpp for each QgsAbstractGeometry subclass
this test just checks the QgsGeometry wrapper
"""
g = QgsGeometry()
self.assertTrue(g.isEmpty())
g = QgsGeometry.fromWkt('Point(10 10 )')
self.assertFalse(g.isEmpty())
g = QgsGeometry.fromWkt('MultiPoint ()')
self.assertTrue(g.isEmpty())

def testWktPointLoading(self):
myWKT = 'Point (10 10)'
myGeometry = QgsGeometry.fromWkt(myWKT)
Expand Down

0 comments on commit af0d68b

Please sign in to comment.