Skip to content

Commit d860722

Browse files
committed
Expose GEOS Hausdorff distance calculations to QgsGeometry
1 parent 6fe6394 commit d860722

File tree

6 files changed

+213
-3
lines changed

6 files changed

+213
-3
lines changed

python/core/geometry/qgsgeometry.sip

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,47 @@ Returns true if WKB of the geometry is of WKBMulti* type
241241
:rtype: float
242242
%End
243243

244+
double hausdorffDistance( const QgsGeometry &geom ) const;
245+
%Docstring
246+
Returns the Hausdorff distance between this geometry and ``geom``. This is basically a measure of how similar or dissimilar 2 geometries are.
247+
248+
This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
249+
subset of useful cases. Examples of these are:
250+
251+
- computing distance between Linestrings that are roughly parallel to each other,
252+
and roughly equal in length. This occurs in matching linear networks.
253+
- Testing similarity of geometries.
254+
255+
If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.
256+
257+
In case of error -1 will be returned.
258+
259+
.. versionadded:: 3.0
260+
.. seealso:: hausdorffDistanceDensify()
261+
:rtype: float
262+
%End
263+
264+
double hausdorffDistanceDensify( const QgsGeometry &geom, double densifyFraction ) const;
265+
%Docstring
266+
Returns the Hausdorff distance between this geometry and ``geom``. This is basically a measure of how similar or dissimilar 2 geometries are.
267+
268+
This function accepts a ``densifyFraction`` argument. The function performs a segment
269+
densification before computing the discrete Hausdorff distance. The ``densifyFraction`` parameter
270+
sets the fraction by which to densify each segment. Each segment will be split into a
271+
number of equal-length subsegments, whose fraction of the total length is
272+
closest to the given fraction.
273+
274+
This method can be used when the default approximation provided by hausdorffDistance()
275+
is not sufficient. Decreasing the ``densifyFraction`` parameter will make the
276+
distance returned approach the true Hausdorff distance for the geometries.
277+
278+
In case of error -1 will be returned.
279+
280+
.. versionadded:: 3.0
281+
.. seealso:: hausdorffDistance()
282+
:rtype: float
283+
%End
284+
244285
QgsPointXY closestVertex( const QgsPointXY &point, int &atVertex /Out/, int &beforeVertex /Out/, int &afterVertex /Out/, double &sqrDist /Out/ ) const;
245286
%Docstring
246287
:rtype: QgsPointXY

src/core/geometry/qgsgeometry.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,28 @@ double QgsGeometry::distance( const QgsGeometry &geom ) const
14471447
return g.distance( geom.d->geometry );
14481448
}
14491449

1450+
double QgsGeometry::hausdorffDistance( const QgsGeometry &geom ) const
1451+
{
1452+
if ( !d->geometry || !geom.d->geometry )
1453+
{
1454+
return -1.0;
1455+
}
1456+
1457+
QgsGeos g( d->geometry );
1458+
return g.hausdorffDistance( geom.d->geometry );
1459+
}
1460+
1461+
double QgsGeometry::hausdorffDistanceDensify( const QgsGeometry &geom, double densifyFraction ) const
1462+
{
1463+
if ( !d->geometry || !geom.d->geometry )
1464+
{
1465+
return -1.0;
1466+
}
1467+
1468+
QgsGeos g( d->geometry );
1469+
return g.hausdorffDistanceDensify( geom.d->geometry, densifyFraction );
1470+
}
1471+
14501472
QgsGeometry QgsGeometry::buffer( double distance, int segments ) const
14511473
{
14521474
if ( !d->geometry )

src/core/geometry/qgsgeometry.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,45 @@ class CORE_EXPORT QgsGeometry
271271
*/
272272
double distance( const QgsGeometry &geom ) const;
273273

274+
/**
275+
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
276+
*
277+
* This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
278+
* subset of useful cases. Examples of these are:
279+
*
280+
* - computing distance between Linestrings that are roughly parallel to each other,
281+
* and roughly equal in length. This occurs in matching linear networks.
282+
* - Testing similarity of geometries.
283+
*
284+
* If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.
285+
*
286+
* In case of error -1 will be returned.
287+
*
288+
* \since QGIS 3.0
289+
* \see hausdorffDistanceDensify()
290+
*/
291+
double hausdorffDistance( const QgsGeometry &geom ) const;
292+
293+
/**
294+
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
295+
*
296+
* This function accepts a \a densifyFraction argument. The function performs a segment
297+
* densification before computing the discrete Hausdorff distance. The \a densifyFraction parameter
298+
* sets the fraction by which to densify each segment. Each segment will be split into a
299+
* number of equal-length subsegments, whose fraction of the total length is
300+
* closest to the given fraction.
301+
*
302+
* This method can be used when the default approximation provided by hausdorffDistance()
303+
* is not sufficient. Decreasing the \a densifyFraction parameter will make the
304+
* distance returned approach the true Hausdorff distance for the geometries.
305+
*
306+
* In case of error -1 will be returned.
307+
*
308+
* \since QGIS 3.0
309+
* \see hausdorffDistance()
310+
*/
311+
double hausdorffDistanceDensify( const QgsGeometry &geom, double densifyFraction ) const;
312+
274313
/**
275314
* Returns the vertex closest to the given point, the corresponding vertex index, squared distance snap point / target point
276315
* and the indices of the vertices before and after the closest vertex.

src/core/geometry/qgsgeos.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,19 +369,63 @@ double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) c
369369
return distance;
370370
}
371371

372-
GEOSGeometry *otherGeosGeom = asGeos( geom, mPrecision );
372+
GEOSGeomScopedPtr otherGeosGeom( asGeos( geom, mPrecision ) );
373373
if ( !otherGeosGeom )
374374
{
375375
return distance;
376376
}
377377

378378
try
379379
{
380-
GEOSDistance_r( geosinit.ctxt, mGeos, otherGeosGeom, &distance );
380+
GEOSDistance_r( geosinit.ctxt, mGeos, otherGeosGeom.get(), &distance );
381381
}
382382
CATCH_GEOS_WITH_ERRMSG( -1.0 )
383383

384-
GEOSGeom_destroy_r( geosinit.ctxt, otherGeosGeom );
384+
return distance;
385+
}
386+
387+
double QgsGeos::hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
388+
{
389+
double distance = -1.0;
390+
if ( !mGeos )
391+
{
392+
return distance;
393+
}
394+
395+
GEOSGeomScopedPtr otherGeosGeom( asGeos( geom, mPrecision ) );
396+
if ( !otherGeosGeom )
397+
{
398+
return distance;
399+
}
400+
401+
try
402+
{
403+
GEOSHausdorffDistance_r( geosinit.ctxt, mGeos, otherGeosGeom.get(), &distance );
404+
}
405+
CATCH_GEOS_WITH_ERRMSG( -1.0 )
406+
407+
return distance;
408+
}
409+
410+
double QgsGeos::hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
411+
{
412+
double distance = -1.0;
413+
if ( !mGeos )
414+
{
415+
return distance;
416+
}
417+
418+
GEOSGeomScopedPtr otherGeosGeom( asGeos( geom, mPrecision ) );
419+
if ( !otherGeosGeom )
420+
{
421+
return distance;
422+
}
423+
424+
try
425+
{
426+
GEOSHausdorffDistanceDensify_r( geosinit.ctxt, mGeos, otherGeosGeom.get(), densifyFraction, &distance );
427+
}
428+
CATCH_GEOS_WITH_ERRMSG( -1.0 )
385429

386430
return distance;
387431
}

src/core/geometry/qgsgeos.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,42 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
8484
QgsPoint *pointOnSurface( QString *errorMsg = nullptr ) const override;
8585
QgsAbstractGeometry *convexHull( QString *errorMsg = nullptr ) const override;
8686
double distance( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
87+
88+
/**
89+
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
90+
*
91+
* This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
92+
* subset of useful cases. Examples of these are:
93+
*
94+
* - computing distance between Linestrings that are roughly parallel to each other,
95+
* and roughly equal in length. This occurs in matching linear networks.
96+
* - Testing similarity of geometries.
97+
*
98+
* If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.
99+
*
100+
* \since QGIS 3.0
101+
* \see hausdorffDistanceDensify()
102+
*/
103+
double hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const;
104+
105+
/**
106+
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
107+
*
108+
* This function accepts a \a densifyFraction argument. The function performs a segment
109+
* densification before computing the discrete Hausdorff distance. The \a densifyFraction parameter
110+
* sets the fraction by which to densify each segment. Each segment will be split into a
111+
* number of equal-length subsegments, whose fraction of the total length is
112+
* closest to the given fraction.
113+
*
114+
* This method can be used when the default approximation provided by hausdorffDistance()
115+
* is not sufficient. Decreasing the \a densifyFraction parameter will make the
116+
* distance returned approach the true Hausdorff distance for the geometries.
117+
*
118+
* \since QGIS 3.0
119+
* \see hausdorffDistance()
120+
*/
121+
double hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg = nullptr ) const;
122+
87123
bool intersects( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
88124
bool touches( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
89125
bool crosses( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;

tests/src/python/test_qgsgeometry.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4223,6 +4223,34 @@ def testClipped(self):
42234223
self.assertTrue(compareWkt(result, exp, 0.00001),
42244224
"clipped: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
42254225

4226+
def testHausdorff(self):
4227+
tests = [["POLYGON((0 0, 0 2, 1 2, 2 2, 2 0, 0 0))", "POLYGON((0.5 0.5, 0.5 2.5, 1.5 2.5, 2.5 2.5, 2.5 0.5, 0.5 0.5))", 0.707106781186548],
4228+
["LINESTRING (0 0, 2 1)", "LINESTRING (0 0, 2 0)", 1],
4229+
["LINESTRING (0 0, 2 0)", "LINESTRING (0 1, 1 2, 2 1)", 2],
4230+
["LINESTRING (0 0, 2 0)", "MULTIPOINT (0 1, 1 0, 2 1)", 1],
4231+
["LINESTRING (130 0, 0 0, 0 150)", "LINESTRING (10 10, 10 150, 130 10)", 14.142135623730951]
4232+
]
4233+
for t in tests:
4234+
g1 = QgsGeometry.fromWkt(t[0])
4235+
g2 = QgsGeometry.fromWkt(t[1])
4236+
o = g1.hausdorffDistance(g2)
4237+
exp = t[2]
4238+
self.assertAlmostEqual(o, exp, 5,
4239+
"mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], exp, o))
4240+
4241+
def testHausdorffDensify(self):
4242+
tests = [
4243+
["LINESTRING (130 0, 0 0, 0 150)", "LINESTRING (10 10, 10 150, 130 10)", 0.5, 70.0]
4244+
]
4245+
for t in tests:
4246+
g1 = QgsGeometry.fromWkt(t[0])
4247+
g2 = QgsGeometry.fromWkt(t[1])
4248+
densify = t[2]
4249+
o = g1.hausdorffDistanceDensify(g2, densify)
4250+
exp = t[3]
4251+
self.assertAlmostEqual(o, exp, 5,
4252+
"mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], exp, o))
4253+
42264254

42274255
if __name__ == '__main__':
42284256
unittest.main()

0 commit comments

Comments
 (0)