Skip to content

Commit 2d43dac

Browse files
committedJan 4, 2018
Refine behavior of QgsGeometry equals tests
Before we had two checks - equals() and isGeosEqual() which performed the exact same check (since equals() called the geos equality test) Since the geos equality test is a slow, topological test, which considers two geometries equal if their component edges overlap, but disregards ordering of vertices this is not always what we want. There's also the issue that geos cannot consider m values when testing the geometries, so two geometries with different m values would be reported equal. So, now calling QgsGeometry::equals performs a very fast, strict equality test where geometries are only equal if the have exactly the same vertices, type, and order. And swap most code which was calling the slow geos test to instead use the fast strict native test.
1 parent 13aa521 commit 2d43dac

File tree

6 files changed

+111
-19
lines changed

6 files changed

+111
-19
lines changed
 

‎doc/api_break.dox

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,8 @@ maintains Z or M dimensions from the input points and is more efficient.
13541354
- exportToWkt() was renamed to asWkt()
13551355
- exportToGeoJSON() was renamed to asJson()
13561356
- closestSegmentWithContext() now returns an extra value, indicating whether the point is to the left of the geometry
1357+
- equals() now performs a fast, strict equality check, where geometries are considered equal only if they have the
1358+
exact same type, vertices and order. The slower topological test can be performed by calling isGeosEqual instead.
13571359

13581360

13591361
QgsGeometryAnalyzer {#qgis_api_break_3_0_QgsGeometryAnalyzer}

‎python/core/geometry/qgsgeometry.sip

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,46 @@ return true for isEmpty().
247247
bool isMultipart() const;
248248
%Docstring
249249
Returns true if WKB of the geometry is of WKBMulti* type
250+
%End
251+
252+
bool equals( const QgsGeometry &geometry ) const;
253+
%Docstring
254+
Test if this geometry is exactly equal to another ``geometry``.
255+
256+
This is a strict equality check, where the underlying geometries must
257+
have exactly the same type, component vertices and vertex order.
258+
259+
Calling this method is dramatically faster than the topological
260+
equality test performed by isGeosEqual().
261+
262+
.. note::
263+
264+
Comparing two null geometries will return false.
265+
266+
.. versionadded:: 1.5
267+
268+
.. seealso:: :py:func:`isGeosEqual()`
250269
%End
251270

252271
bool isGeosEqual( const QgsGeometry & ) const;
253272
%Docstring
254-
Compares the geometry with another geometry using GEOS
273+
Compares the geometry with another geometry using GEOS.
274+
275+
This method performs a slow, topological check, where geometries
276+
are considered equal if all of the their component edges overlap. E.g.
277+
lines with the same vertex locations but opposite direction will be
278+
considered equal by this method.
279+
280+
Consider using the much faster, stricter equality test performed
281+
by equals() instead.
282+
283+
.. note::
284+
285+
Comparing two null geometries will return false.
255286

256287
.. versionadded:: 1.5
288+
289+
.. seealso:: :py:func:`equals()`
257290
%End
258291

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

752-
.. versionadded:: 1.5
753-
%End
754-
755-
bool equals( const QgsGeometry &geometry ) const;
756-
%Docstring
757-
Test for if geometry equals another (uses GEOS)
758-
759785
.. versionadded:: 1.5
760786
%End
761787

‎src/core/geometry/qgsgeometry.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,9 +1147,12 @@ bool QgsGeometry::equals( const QgsGeometry &geometry ) const
11471147
return false;
11481148
}
11491149

1150-
QgsGeos geos( d->geometry.get() );
1151-
mLastError.clear();
1152-
return geos.isEqual( geometry.d->geometry.get(), &mLastError );
1150+
// fast check - are they shared copies of the same underlying geometry?
1151+
if ( d == geometry.d )
1152+
return true;
1153+
1154+
// slower check - actually test the geometries
1155+
return *d->geometry.get() == *geometry.d->geometry.get();
11531156
}
11541157

11551158
bool QgsGeometry::touches( const QgsGeometry &geometry ) const

‎src/core/geometry/qgsgeometry.h

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,36 @@ class CORE_EXPORT QgsGeometry
307307
bool isMultipart() const;
308308

309309
/**
310-
* Compares the geometry with another geometry using GEOS
310+
* Test if this geometry is exactly equal to another \a geometry.
311+
*
312+
* This is a strict equality check, where the underlying geometries must
313+
* have exactly the same type, component vertices and vertex order.
314+
*
315+
* Calling this method is dramatically faster than the topological
316+
* equality test performed by isGeosEqual().
317+
*
318+
* \note Comparing two null geometries will return false.
319+
*
320+
* \since QGIS 1.5
321+
* \see isGeosEqual()
322+
*/
323+
bool equals( const QgsGeometry &geometry ) const;
324+
325+
/**
326+
* Compares the geometry with another geometry using GEOS.
327+
*
328+
* This method performs a slow, topological check, where geometries
329+
* are considered equal if all of the their component edges overlap. E.g.
330+
* lines with the same vertex locations but opposite direction will be
331+
* considered equal by this method.
332+
*
333+
* Consider using the much faster, stricter equality test performed
334+
* by equals() instead.
335+
*
336+
* \note Comparing two null geometries will return false.
337+
*
311338
* \since QGIS 1.5
339+
* \see equals()
312340
*/
313341
bool isGeosEqual( const QgsGeometry & ) const;
314342

@@ -790,12 +818,6 @@ class CORE_EXPORT QgsGeometry
790818
*/
791819
bool disjoint( const QgsGeometry &geometry ) const;
792820

793-
/**
794-
* Test for if geometry equals another (uses GEOS)
795-
* \since QGIS 1.5
796-
*/
797-
bool equals( const QgsGeometry &geometry ) const;
798-
799821
/**
800822
* Test for if geometry touch another (uses GEOS)
801823
* \since QGIS 1.5

‎src/core/qgsvectorlayer.cpp

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ bool QgsVectorLayer::updateFeature( const QgsFeature &updatedFeature, bool skipD
985985
bool hasChanged = false;
986986
bool hasError = false;
987987

988-
if ( ( updatedFeature.hasGeometry() || currentFeature.hasGeometry() ) && !updatedFeature.geometry().isGeosEqual( currentFeature.geometry() ) )
988+
if ( ( updatedFeature.hasGeometry() || currentFeature.hasGeometry() ) && !updatedFeature.geometry().equals( currentFeature.geometry() ) )
989989
{
990990
if ( changeGeometry( updatedFeature.id(), updatedFeature.geometry(), true ) )
991991
{

‎tests/src/core/testqgsgeometry.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ class TestQgsGeometry : public QObject
8080
void asVariant(); //test conversion to and from a QVariant
8181
void isEmpty();
8282
void operatorBool();
83+
void equality();
8384
void vertexIterator();
8485

86+
8587
// geometry types
8688
void point(); //test QgsPointV2
8789
void lineString(); //test QgsLineString
@@ -431,6 +433,43 @@ void TestQgsGeometry::operatorBool()
431433
QVERIFY( !geom );
432434
}
433435

436+
void TestQgsGeometry::equality()
437+
{
438+
// null geometries
439+
QVERIFY( !QgsGeometry().equals( QgsGeometry() ) );
440+
441+
// compare to null
442+
QgsGeometry g1( qgis::make_unique< QgsPoint >( 1.0, 2.0 ) );
443+
QVERIFY( !g1.equals( QgsGeometry() ) );
444+
QVERIFY( !QgsGeometry().equals( g1 ) );
445+
446+
// compare implicitly shared copies
447+
QgsGeometry g2( g1 );
448+
QVERIFY( g2.equals( g1 ) );
449+
QVERIFY( g1.equals( g2 ) );
450+
QVERIFY( g1.equals( g1 ) );
451+
452+
// equal geometry, but different internal data
453+
g2 = QgsGeometry::fromWkt( "Point( 1.0 2.0 )" );
454+
QVERIFY( g2.equals( g1 ) );
455+
QVERIFY( g1.equals( g2 ) );
456+
457+
// different dimensionality
458+
g2 = QgsGeometry::fromWkt( "PointM( 1.0 2.0 3.0)" );
459+
QVERIFY( !g2.equals( g1 ) );
460+
QVERIFY( !g1.equals( g2 ) );
461+
462+
// different type
463+
g2 = QgsGeometry::fromWkt( "LineString( 1.0 2.0, 3.0 4.0 )" );
464+
QVERIFY( !g2.equals( g1 ) );
465+
QVERIFY( !g1.equals( g2 ) );
466+
467+
// different direction
468+
g1 = QgsGeometry::fromWkt( "LineString( 3.0 4.0, 1.0 2.0 )" );
469+
QVERIFY( !g2.equals( g1 ) );
470+
QVERIFY( !g1.equals( g2 ) );
471+
}
472+
434473
void TestQgsGeometry::vertexIterator()
435474
{
436475
QgsGeometry geom;

0 commit comments

Comments
 (0)
Please sign in to comment.