Skip to content
Permalink
Browse files
Expose GEOS Hausdorff distance calculations to QgsGeometry
  • 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.
@@ -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
@@ -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 )
@@ -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.
@@ -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;
}
@@ -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;
@@ -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.