diff --git a/python/core/geometry/qgsgeometry.sip b/python/core/geometry/qgsgeometry.sip index 1c18e2b12677..759cd0ca5263 100644 --- a/python/core/geometry/qgsgeometry.sip +++ b/python/core/geometry/qgsgeometry.sip @@ -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 diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 203d1ad68ed9..2a9b4f323a08 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -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 ) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 30c433f544e5..76f2fce8315a 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -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. diff --git a/src/core/geometry/qgsgeos.cpp b/src/core/geometry/qgsgeos.cpp index b7f754d2d226..477ff4d24560 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -369,7 +369,7 @@ 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; @@ -377,11 +377,55 @@ double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) c 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; } diff --git a/src/core/geometry/qgsgeos.h b/src/core/geometry/qgsgeos.h index cf07861c02c5..e20346368f0e 100644 --- a/src/core/geometry/qgsgeos.h +++ b/src/core/geometry/qgsgeos.h @@ -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; diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index 8a2a214c711d..ccaf8affc63c 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -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()