Skip to content

Commit

Permalink
Expose GEOS Hausdorff distance calculations to QgsGeometry
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Aug 30, 2017
1 parent 6fe6394 commit d860722
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 3 deletions.
41 changes: 41 additions & 0 deletions python/core/geometry/qgsgeometry.sip
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,47 @@ Returns true if WKB of the geometry is of WKBMulti* type
:rtype: float
%End

double hausdorffDistance( const QgsGeometry &geom ) const;
%Docstring
Returns the Hausdorff distance between this geometry and ``geom``. This is basically a measure of how similar or dissimilar 2 geometries are.

This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
subset of useful cases. Examples of these are:

- computing distance between Linestrings that are roughly parallel to each other,
and roughly equal in length. This occurs in matching linear networks.
- Testing similarity of geometries.

If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.

In case of error -1 will be returned.

.. versionadded:: 3.0
.. seealso:: hausdorffDistanceDensify()
:rtype: float
%End

double hausdorffDistanceDensify( const QgsGeometry &geom, double densifyFraction ) const;
%Docstring
Returns the Hausdorff distance between this geometry and ``geom``. This is basically a measure of how similar or dissimilar 2 geometries are.

This function accepts a ``densifyFraction`` argument. The function performs a segment
densification before computing the discrete Hausdorff distance. The ``densifyFraction`` parameter
sets the fraction by which to densify each segment. Each segment will be split into a
number of equal-length subsegments, whose fraction of the total length is
closest to the given fraction.

This method can be used when the default approximation provided by hausdorffDistance()
is not sufficient. Decreasing the ``densifyFraction`` parameter will make the
distance returned approach the true Hausdorff distance for the geometries.

In case of error -1 will be returned.

.. versionadded:: 3.0
.. seealso:: hausdorffDistance()
:rtype: float
%End

QgsPointXY closestVertex( const QgsPointXY &point, int &atVertex /Out/, int &beforeVertex /Out/, int &afterVertex /Out/, double &sqrDist /Out/ ) const;
%Docstring
:rtype: QgsPointXY
Expand Down
22 changes: 22 additions & 0 deletions src/core/geometry/qgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,28 @@ double QgsGeometry::distance( const QgsGeometry &geom ) const
return g.distance( geom.d->geometry );
}

double QgsGeometry::hausdorffDistance( const QgsGeometry &geom ) const
{
if ( !d->geometry || !geom.d->geometry )
{
return -1.0;
}

QgsGeos g( d->geometry );
return g.hausdorffDistance( geom.d->geometry );
}

double QgsGeometry::hausdorffDistanceDensify( const QgsGeometry &geom, double densifyFraction ) const
{
if ( !d->geometry || !geom.d->geometry )
{
return -1.0;
}

QgsGeos g( d->geometry );
return g.hausdorffDistanceDensify( geom.d->geometry, densifyFraction );
}

QgsGeometry QgsGeometry::buffer( double distance, int segments ) const
{
if ( !d->geometry )
Expand Down
39 changes: 39 additions & 0 deletions src/core/geometry/qgsgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,45 @@ class CORE_EXPORT QgsGeometry
*/
double distance( const QgsGeometry &geom ) const;

/**
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
*
* This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
* subset of useful cases. Examples of these are:
*
* - computing distance between Linestrings that are roughly parallel to each other,
* and roughly equal in length. This occurs in matching linear networks.
* - Testing similarity of geometries.
*
* If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.
*
* In case of error -1 will be returned.
*
* \since QGIS 3.0
* \see hausdorffDistanceDensify()
*/
double hausdorffDistance( const QgsGeometry &geom ) const;

/**
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
*
* This function accepts a \a densifyFraction argument. The function performs a segment
* densification before computing the discrete Hausdorff distance. The \a densifyFraction parameter
* sets the fraction by which to densify each segment. Each segment will be split into a
* number of equal-length subsegments, whose fraction of the total length is
* closest to the given fraction.
*
* This method can be used when the default approximation provided by hausdorffDistance()
* is not sufficient. Decreasing the \a densifyFraction parameter will make the
* distance returned approach the true Hausdorff distance for the geometries.
*
* In case of error -1 will be returned.
*
* \since QGIS 3.0
* \see hausdorffDistance()
*/
double hausdorffDistanceDensify( const QgsGeometry &geom, double densifyFraction ) const;

/**
* Returns the vertex closest to the given point, the corresponding vertex index, squared distance snap point / target point
* and the indices of the vertices before and after the closest vertex.
Expand Down
50 changes: 47 additions & 3 deletions src/core/geometry/qgsgeos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,19 +369,63 @@ double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) c
return distance;
}

GEOSGeometry *otherGeosGeom = asGeos( geom, mPrecision );
GEOSGeomScopedPtr otherGeosGeom( asGeos( geom, mPrecision ) );
if ( !otherGeosGeom )
{
return distance;
}

try
{
GEOSDistance_r( geosinit.ctxt, mGeos, otherGeosGeom, &distance );
GEOSDistance_r( geosinit.ctxt, mGeos, otherGeosGeom.get(), &distance );
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )

GEOSGeom_destroy_r( geosinit.ctxt, otherGeosGeom );
return distance;
}

double QgsGeos::hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
double distance = -1.0;
if ( !mGeos )
{
return distance;
}

GEOSGeomScopedPtr otherGeosGeom( asGeos( geom, mPrecision ) );
if ( !otherGeosGeom )
{
return distance;
}

try
{
GEOSHausdorffDistance_r( geosinit.ctxt, mGeos, otherGeosGeom.get(), &distance );
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )

return distance;
}

double QgsGeos::hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
{
double distance = -1.0;
if ( !mGeos )
{
return distance;
}

GEOSGeomScopedPtr otherGeosGeom( asGeos( geom, mPrecision ) );
if ( !otherGeosGeom )
{
return distance;
}

try
{
GEOSHausdorffDistanceDensify_r( geosinit.ctxt, mGeos, otherGeosGeom.get(), densifyFraction, &distance );
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )

return distance;
}
Expand Down
36 changes: 36 additions & 0 deletions src/core/geometry/qgsgeos.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
QgsPoint *pointOnSurface( QString *errorMsg = nullptr ) const override;
QgsAbstractGeometry *convexHull( QString *errorMsg = nullptr ) const override;
double distance( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;

/**
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
*
* This algorithm is an approximation to the standard Hausdorff distance. This approximation is exact or close enough for a large
* subset of useful cases. Examples of these are:
*
* - computing distance between Linestrings that are roughly parallel to each other,
* and roughly equal in length. This occurs in matching linear networks.
* - Testing similarity of geometries.
*
* If the default approximate provided by this method is insufficient, use hausdorffDistanceDensify() instead.
*
* \since QGIS 3.0
* \see hausdorffDistanceDensify()
*/
double hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const;

/**
* Returns the Hausdorff distance between this geometry and \a geom. This is basically a measure of how similar or dissimilar 2 geometries are.
*
* This function accepts a \a densifyFraction argument. The function performs a segment
* densification before computing the discrete Hausdorff distance. The \a densifyFraction parameter
* sets the fraction by which to densify each segment. Each segment will be split into a
* number of equal-length subsegments, whose fraction of the total length is
* closest to the given fraction.
*
* This method can be used when the default approximation provided by hausdorffDistance()
* is not sufficient. Decreasing the \a densifyFraction parameter will make the
* distance returned approach the true Hausdorff distance for the geometries.
*
* \since QGIS 3.0
* \see hausdorffDistance()
*/
double hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg = nullptr ) const;

bool intersects( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
bool touches( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
bool crosses( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
Expand Down
28 changes: 28 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4223,6 +4223,34 @@ def testClipped(self):
self.assertTrue(compareWkt(result, exp, 0.00001),
"clipped: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

def testHausdorff(self):
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],
["LINESTRING (0 0, 2 1)", "LINESTRING (0 0, 2 0)", 1],
["LINESTRING (0 0, 2 0)", "LINESTRING (0 1, 1 2, 2 1)", 2],
["LINESTRING (0 0, 2 0)", "MULTIPOINT (0 1, 1 0, 2 1)", 1],
["LINESTRING (130 0, 0 0, 0 150)", "LINESTRING (10 10, 10 150, 130 10)", 14.142135623730951]
]
for t in tests:
g1 = QgsGeometry.fromWkt(t[0])
g2 = QgsGeometry.fromWkt(t[1])
o = g1.hausdorffDistance(g2)
exp = t[2]
self.assertAlmostEqual(o, exp, 5,
"mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], exp, o))

def testHausdorffDensify(self):
tests = [
["LINESTRING (130 0, 0 0, 0 150)", "LINESTRING (10 10, 10 150, 130 10)", 0.5, 70.0]
]
for t in tests:
g1 = QgsGeometry.fromWkt(t[0])
g2 = QgsGeometry.fromWkt(t[1])
densify = t[2]
o = g1.hausdorffDistanceDensify(g2, densify)
exp = t[3]
self.assertAlmostEqual(o, exp, 5,
"mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], exp, o))


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

0 comments on commit d860722

Please sign in to comment.