Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #5986 from nyalldawson/geometry_equals
Refine geometry equals checks
  • Loading branch information
nyalldawson committed Jan 5, 2018
2 parents df95536 + 164c439 commit 63cc124
Show file tree
Hide file tree
Showing 31 changed files with 267 additions and 85 deletions.
2 changes: 2 additions & 0 deletions doc/api_break.dox
Expand Up @@ -1354,6 +1354,8 @@ maintains Z or M dimensions from the input points and is more efficient.
- exportToWkt() was renamed to asWkt()
- exportToGeoJSON() was renamed to asJson()
- closestSegmentWithContext() now returns an extra value, indicating whether the point is to the left of the geometry
- equals() now performs a fast, strict equality check, where geometries are considered equal only if they have the
exact same type, vertices and order. The slower topological test can be performed by calling isGeosEqual instead.


QgsGeometryAnalyzer {#qgis_api_break_3_0_QgsGeometryAnalyzer}
Expand Down
3 changes: 3 additions & 0 deletions python/core/geometry/qgsabstractgeometry.sip
Expand Up @@ -72,6 +72,9 @@ Constructor for QgsAbstractGeometry.
virtual ~QgsAbstractGeometry();
QgsAbstractGeometry( const QgsAbstractGeometry &geom );

virtual bool operator==( const QgsAbstractGeometry &other ) const = 0;
virtual bool operator!=( const QgsAbstractGeometry &other ) const = 0;

virtual QgsAbstractGeometry *clone() const = 0 /Factory/;
%Docstring
Clones the geometry by performing a deep copy
Expand Down
4 changes: 1 addition & 3 deletions python/core/geometry/qgscircularstring.sip
Expand Up @@ -25,9 +25,7 @@ class QgsCircularString: QgsCurve
public:
QgsCircularString();

virtual bool operator==( const QgsCurve &other ) const;

virtual bool operator!=( const QgsCurve &other ) const;
virtual bool equals( const QgsCurve &other ) const;


virtual QString geometryType() const;
Expand Down
4 changes: 1 addition & 3 deletions python/core/geometry/qgscompoundcurve.sip
Expand Up @@ -25,9 +25,7 @@ class QgsCompoundCurve: QgsCurve
QgsCompoundCurve( const QgsCompoundCurve &curve );
~QgsCompoundCurve();

virtual bool operator==( const QgsCurve &other ) const;

virtual bool operator!=( const QgsCurve &other ) const;
virtual bool equals( const QgsCurve &other ) const;


virtual QString geometryType() const;
Expand Down
13 changes: 11 additions & 2 deletions python/core/geometry/qgscurve.sip
Expand Up @@ -28,8 +28,17 @@ class QgsCurve: QgsAbstractGeometry
Constructor for QgsCurve.
%End

virtual bool operator==( const QgsCurve &other ) const = 0;
virtual bool operator!=( const QgsCurve &other ) const = 0;
virtual bool equals( const QgsCurve &other ) const = 0;
%Docstring
Checks whether this curve exactly equals another curve.

.. versionadded:: 3.0
%End

virtual bool operator==( const QgsAbstractGeometry &other ) const;

virtual bool operator!=( const QgsAbstractGeometry &other ) const;


virtual QgsCurve *clone() const = 0 /Factory/;

Expand Down
6 changes: 4 additions & 2 deletions python/core/geometry/qgscurvepolygon.sip
Expand Up @@ -25,8 +25,10 @@ class QgsCurvePolygon: QgsSurface
QgsCurvePolygon();
QgsCurvePolygon( const QgsCurvePolygon &p );

bool operator==( const QgsCurvePolygon &other ) const;
bool operator!=( const QgsCurvePolygon &other ) const;
virtual bool operator==( const QgsAbstractGeometry &other ) const;

virtual bool operator!=( const QgsAbstractGeometry &other ) const;


~QgsCurvePolygon();

Expand Down
42 changes: 34 additions & 8 deletions python/core/geometry/qgsgeometry.sip
Expand Up @@ -247,13 +247,46 @@ return true for isEmpty().
bool isMultipart() const;
%Docstring
Returns true if WKB of the geometry is of WKBMulti* type
%End

bool equals( const QgsGeometry &geometry ) const;
%Docstring
Test if this geometry is exactly equal to another ``geometry``.

This is a strict equality check, where the underlying geometries must
have exactly the same type, component vertices and vertex order.

Calling this method is dramatically faster than the topological
equality test performed by isGeosEqual().

.. note::

Comparing two null geometries will return false.

.. versionadded:: 1.5

.. seealso:: :py:func:`isGeosEqual()`
%End

bool isGeosEqual( const QgsGeometry & ) const;
%Docstring
Compares the geometry with another geometry using GEOS
Compares the geometry with another geometry using GEOS.

This method performs a slow, topological check, where geometries
are considered equal if all of the their component edges overlap. E.g.
lines with the same vertex locations but opposite direction will be
considered equal by this method.

Consider using the much faster, stricter equality test performed
by equals() instead.

.. note::

Comparing two null geometries will return false.

.. versionadded:: 1.5

.. seealso:: :py:func:`equals()`
%End

bool isGeosValid() const;
Expand Down Expand Up @@ -749,13 +782,6 @@ Tests for if geometry is contained in another (uses GEOS)
%Docstring
Tests for if geometry is disjoint of another (uses GEOS)

.. versionadded:: 1.5
%End

bool equals( const QgsGeometry &geometry ) const;
%Docstring
Test for if geometry equals another (uses GEOS)

.. versionadded:: 1.5
%End

Expand Down
5 changes: 5 additions & 0 deletions python/core/geometry/qgsgeometrycollection.sip
Expand Up @@ -27,6 +27,11 @@ class QgsGeometryCollection: QgsAbstractGeometry
QgsGeometryCollection( const QgsGeometryCollection &c );
~QgsGeometryCollection();

virtual bool operator==( const QgsAbstractGeometry &other ) const;

virtual bool operator!=( const QgsAbstractGeometry &other ) const;


virtual QgsGeometryCollection *clone() const /Factory/;


Expand Down
4 changes: 1 addition & 3 deletions python/core/geometry/qgslinestring.sip
Expand Up @@ -57,9 +57,7 @@ or repeatedly calling addVertex()
.. versionadded:: 3.0
%End

virtual bool operator==( const QgsCurve &other ) const;

virtual bool operator!=( const QgsCurve &other ) const;
virtual bool equals( const QgsCurve &other ) const;


QgsPoint pointN( int i ) const;
Expand Down
6 changes: 4 additions & 2 deletions python/core/geometry/qgspoint.sip
Expand Up @@ -85,8 +85,10 @@ Construct a QgsPoint from a QPointF
%End


bool operator==( const QgsPoint &pt ) const;
bool operator!=( const QgsPoint &pt ) const;
virtual bool operator==( const QgsAbstractGeometry &other ) const;

virtual bool operator!=( const QgsAbstractGeometry &other ) const;


double x() const;
%Docstring
Expand Down
2 changes: 1 addition & 1 deletion src/analysis/processing/qgsalgorithmsplitwithlines.cpp
Expand Up @@ -202,7 +202,7 @@ QVariantMap QgsSplitWithLinesAlgorithm::processAlgorithm( const QVariantMap &par
// between geometry and splitLine, only the first one is considered.
if ( result == QgsGeometry::Success ) // split occurred
{
if ( inGeom.equals( before ) )
if ( inGeom.isGeosEqual( before ) )
{
// bug in splitGeometry: sometimes it returns 0 but
// the geometry is unchanged
Expand Down
3 changes: 3 additions & 0 deletions src/core/geometry/qgsabstractgeometry.h
Expand Up @@ -110,6 +110,9 @@ class CORE_EXPORT QgsAbstractGeometry
QgsAbstractGeometry( const QgsAbstractGeometry &geom );
QgsAbstractGeometry &operator=( const QgsAbstractGeometry &geom );

virtual bool operator==( const QgsAbstractGeometry &other ) const = 0;
virtual bool operator!=( const QgsAbstractGeometry &other ) const = 0;

/**
* Clones the geometry by performing a deep copy
*/
Expand Down
7 changes: 1 addition & 6 deletions src/core/geometry/qgscircularstring.cpp
Expand Up @@ -33,7 +33,7 @@ QgsCircularString::QgsCircularString()
mWkbType = QgsWkbTypes::CircularString;
}

bool QgsCircularString::operator==( const QgsCurve &other ) const
bool QgsCircularString::equals( const QgsCurve &other ) const
{
const QgsCircularString *otherLine = dynamic_cast< const QgsCircularString * >( &other );
if ( !otherLine )
Expand Down Expand Up @@ -61,11 +61,6 @@ bool QgsCircularString::operator==( const QgsCurve &other ) const
return true;
}

bool QgsCircularString::operator!=( const QgsCurve &other ) const
{
return !operator==( other );
}

QgsCircularString *QgsCircularString::createEmptyWithSameType() const
{
auto result = qgis::make_unique< QgsCircularString >();
Expand Down
3 changes: 1 addition & 2 deletions src/core/geometry/qgscircularstring.h
Expand Up @@ -36,8 +36,7 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
public:
QgsCircularString();

bool operator==( const QgsCurve &other ) const override;
bool operator!=( const QgsCurve &other ) const override;
bool equals( const QgsCurve &other ) const override;

QString geometryType() const override;
int dimension() const override;
Expand Down
7 changes: 1 addition & 6 deletions src/core/geometry/qgscompoundcurve.cpp
Expand Up @@ -35,7 +35,7 @@ QgsCompoundCurve::~QgsCompoundCurve()
clear();
}

bool QgsCompoundCurve::operator==( const QgsCurve &other ) const
bool QgsCompoundCurve::equals( const QgsCurve &other ) const
{
const QgsCompoundCurve *otherCurve = qgsgeometry_cast< const QgsCompoundCurve * >( &other );
if ( !otherCurve )
Expand All @@ -56,11 +56,6 @@ bool QgsCompoundCurve::operator==( const QgsCurve &other ) const
return true;
}

bool QgsCompoundCurve::operator!=( const QgsCurve &other ) const
{
return !operator==( other );
}

QgsCompoundCurve *QgsCompoundCurve::createEmptyWithSameType() const
{
auto result = qgis::make_unique< QgsCompoundCurve >();
Expand Down
3 changes: 1 addition & 2 deletions src/core/geometry/qgscompoundcurve.h
Expand Up @@ -36,8 +36,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
QgsCompoundCurve &operator=( const QgsCompoundCurve &curve );
~QgsCompoundCurve() override;

bool operator==( const QgsCurve &other ) const override;
bool operator!=( const QgsCurve &other ) const override;
bool equals( const QgsCurve &other ) const override;

QString geometryType() const override;
int dimension() const override;
Expand Down
14 changes: 14 additions & 0 deletions src/core/geometry/qgscurve.cpp
Expand Up @@ -22,6 +22,20 @@
#include "qgspoint.h"
#include "qgsmultipoint.h"

bool QgsCurve::operator==( const QgsAbstractGeometry &other ) const
{
const QgsCurve *otherCurve = qgsgeometry_cast< const QgsCurve * >( &other );
if ( !otherCurve )
return false;

return equals( *otherCurve );
}

bool QgsCurve::operator!=( const QgsAbstractGeometry &other ) const
{
return !operator==( other );
}

bool QgsCurve::isClosed() const
{
if ( numPoints() == 0 )
Expand Down
10 changes: 8 additions & 2 deletions src/core/geometry/qgscurve.h
Expand Up @@ -41,8 +41,14 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry
*/
QgsCurve() = default;

virtual bool operator==( const QgsCurve &other ) const = 0;
virtual bool operator!=( const QgsCurve &other ) const = 0;
/**
* Checks whether this curve exactly equals another curve.
* \since QGIS 3.0
*/
virtual bool equals( const QgsCurve &other ) const = 0;

bool operator==( const QgsAbstractGeometry &other ) const override;
bool operator!=( const QgsAbstractGeometry &other ) const override;

QgsCurve *clone() const override = 0 SIP_FACTORY;

Expand Down
26 changes: 15 additions & 11 deletions src/core/geometry/qgscurvepolygon.cpp
Expand Up @@ -90,40 +90,44 @@ QgsCurvePolygon &QgsCurvePolygon::operator=( const QgsCurvePolygon &p )
return *this;
}

bool QgsCurvePolygon::operator==( const QgsCurvePolygon &other ) const
bool QgsCurvePolygon::operator==( const QgsAbstractGeometry &other ) const
{
const QgsCurvePolygon *otherPolygon = qgsgeometry_cast< const QgsCurvePolygon * >( &other );
if ( !otherPolygon )
return false;

//run cheap checks first
if ( mWkbType != other.mWkbType )
if ( mWkbType != otherPolygon->mWkbType )
return false;

if ( ( !mExteriorRing && other.mExteriorRing ) || ( mExteriorRing && !other.mExteriorRing ) )
if ( ( !mExteriorRing && otherPolygon->mExteriorRing ) || ( mExteriorRing && !otherPolygon->mExteriorRing ) )
return false;

if ( mInteriorRings.count() != other.mInteriorRings.count() )
if ( mInteriorRings.count() != otherPolygon->mInteriorRings.count() )
return false;

// compare rings
if ( mExteriorRing && other.mExteriorRing )
if ( mExteriorRing && otherPolygon->mExteriorRing )
{
if ( *mExteriorRing != *other.mExteriorRing )
if ( *mExteriorRing != *otherPolygon->mExteriorRing )
return false;
}

for ( int i = 0; i < mInteriorRings.count(); ++i )
{
if ( ( !mInteriorRings.at( i ) && other.mInteriorRings.at( i ) ) ||
( mInteriorRings.at( i ) && !other.mInteriorRings.at( i ) ) )
if ( ( !mInteriorRings.at( i ) && otherPolygon->mInteriorRings.at( i ) ) ||
( mInteriorRings.at( i ) && !otherPolygon->mInteriorRings.at( i ) ) )
return false;

if ( mInteriorRings.at( i ) && other.mInteriorRings.at( i ) &&
*mInteriorRings.at( i ) != *other.mInteriorRings.at( i ) )
if ( mInteriorRings.at( i ) && otherPolygon->mInteriorRings.at( i ) &&
*mInteriorRings.at( i ) != *otherPolygon->mInteriorRings.at( i ) )
return false;
}

return true;
}

bool QgsCurvePolygon::operator!=( const QgsCurvePolygon &other ) const
bool QgsCurvePolygon::operator!=( const QgsAbstractGeometry &other ) const
{
return !operator==( other );
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/geometry/qgscurvepolygon.h
Expand Up @@ -38,8 +38,8 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
QgsCurvePolygon( const QgsCurvePolygon &p );
QgsCurvePolygon &operator=( const QgsCurvePolygon &p );

bool operator==( const QgsCurvePolygon &other ) const;
bool operator!=( const QgsCurvePolygon &other ) const;
bool operator==( const QgsAbstractGeometry &other ) const override;
bool operator!=( const QgsAbstractGeometry &other ) const override;

~QgsCurvePolygon() override;

Expand Down
9 changes: 6 additions & 3 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -1147,9 +1147,12 @@ bool QgsGeometry::equals( const QgsGeometry &geometry ) const
return false;
}

QgsGeos geos( d->geometry.get() );
mLastError.clear();
return geos.isEqual( geometry.d->geometry.get(), &mLastError );
// fast check - are they shared copies of the same underlying geometry?
if ( d == geometry.d )
return true;

// slower check - actually test the geometries
return *d->geometry.get() == *geometry.d->geometry.get();
}

bool QgsGeometry::touches( const QgsGeometry &geometry ) const
Expand Down

0 comments on commit 63cc124

Please sign in to comment.