]> granicus.if.org Git - postgis/commitdiff
Add support for hausdorff distance calculations. Requires GEOS 3.2+. (#209) From...
authorPaul Ramsey <pramsey@cleverelephant.ca>
Wed, 24 Jun 2009 22:08:33 +0000 (22:08 +0000)
committerPaul Ramsey <pramsey@cleverelephant.ca>
Wed, 24 Jun 2009 22:08:33 +0000 (22:08 +0000)
If you are working off of GEOS trunk, svn up, compile and install!

git-svn-id: http://svn.osgeo.org/postgis/trunk@4208 b70326c6-7e19-0410-871a-916f4a2858ee

doc/reference.xml
postgis/lwgeom_geos.c
postgis/postgis.sql.in.c
postgis/uninstall_postgis.sql.in.c
regress/Makefile.in
regress/hausdorff.sql [new file with mode: 0644]
regress/hausdorff_expected [new file with mode: 0644]

index 5a6862f52cd64c36571cc0fa9cdbc16c001e4354..1e9db089408d4c78e9925e40ed40561d26b8ef61 100644 (file)
@@ -9818,6 +9818,83 @@ SELECT ST_Disjoint('POINT(0 0)'::geometry, 'LINESTRING ( 0 0, 0 2 )'::geometry);
          </refsection>
        </refentry>
 
+       <refentry id="ST_HausdorffDistance">
+         <refnamediv>
+               <refname>ST_HausdorffDistance</refname>
+
+               <refpurpose>Returns the Hausdorff distance between two geometries.</refpurpose>
+         </refnamediv>
+
+         <refsynopsisdiv>
+               <funcsynopsis>
+                 <funcprototype>
+                       <funcdef>float <function>ST_HausdorffDistance</function></funcdef>
+
+                       <paramdef><type>geometry </type>
+                       <parameter>g1</parameter></paramdef>
+
+                       <paramdef><type>geometry </type>
+                       <parameter>g2</parameter></paramdef>
+                 </funcprototype>
+                 <funcprototype>
+                       <funcdef>float <function>ST_HausdorffDistance</function></funcdef>
+
+                       <paramdef><type>geometry </type>
+                       <parameter>g1</parameter></paramdef>
+
+                       <paramdef><type>geometry </type>
+                       <parameter>g2</parameter></paramdef>
+
+                       <paramdef><type>float</type>
+                       <parameter>densifyFrac</parameter></paramdef>
+                 </funcprototype>
+               </funcsynopsis>
+         </refsynopsisdiv>
+
+         <refsection>
+               <title>Description</title>
+
+               <para>Implements algorithm for computing a distance metric which can be thought of as the "Discrete Hausdorff Distance".
+This is the Hausdorff distance restricted to discrete points for one of the geometries.</para>
+               <para>
+When densifyFrac is specified, this function performs a segment densification before computing the discrete hausdorff distance. The densifyFrac 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.
+               </para>
+
+               <note>
+                       <para>
+The current implementation supports only vertices as the discrete locations. This could be extended to allow an arbitrary density of points to be used.        
+                       </para>
+               </note>
+               <note>
+                       <para>
+                               This algorithm is NOT equivalent to the standard Hausdorff distance. However, it computes an approximation that is correct for a large subset of useful cases.
+                       One important part of this subset is Linestrings that are roughly parallel to each other, and roughly equal in length.  This is a useful metric for line matching.
+                       </para>
+               </note>
+
+         </refsection>
+
+         <refsection>
+               <title>Examples</title>
+
+                       <programlisting>postgis=# SELECT st_HausdorffDistance(
+                'LINESTRING (0 0, 2 0)'::geometry,
+                'MULTIPOINT (0 1, 1 0, 2 1)'::geometry);
+ st_hausdorffdistance
+ ----------------------
+                     1
+(1 row)
+                       </programlisting>
+                       <programlisting>postgis=# SELECT st_hausdorffdistance('LINESTRING (130 0, 0 0, 0 150)'::geometry, 'LINESTRING (10 10, 10 150, 130 10)'::geometry, 0.5);
+ st_hausdorffdistance
+ ----------------------
+                    70
+(1 row)
+                       </programlisting>
+
+         </refsection>
+       </refentry>
+
        <refentry id="ST_Distance_Sphere">
          <refnamediv>
                <refname>ST_Distance_Sphere</refname>
index 11e3ce456d1b2a6e2344a302add3ecfc56ed0d77..3420bdc294a4281e834287efe9c2fd9c2f09c60c 100644 (file)
@@ -62,6 +62,8 @@ Datum LWGEOM_buildarea(PG_FUNCTION_ARGS); /* TODO: rename to match others
 */
 Datum linemerge(PG_FUNCTION_ARGS);
 Datum coveredby(PG_FUNCTION_ARGS);
+Datum hausdorffdistance(PG_FUNCTION_ARGS);
+Datum hausdorffdistancedensify(PG_FUNCTION_ARGS);
 
 Datum pgis_union_geometry_array_old(PG_FUNCTION_ARGS);
 Datum pgis_union_geometry_array(PG_FUNCTION_ARGS);
@@ -90,6 +92,91 @@ Datum postgis_geos_version(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(result);
 }
 
+/**
+ *  @brief Compute the Hausdorff distance thanks to the corresponding GEOS function
+ *  @example hausdorffdistance {@link #hausdorffdistance} - SELECT st_hausdorffdistance(
+ *      'POLYGON((0 0, 0 2, 1 2, 2 2, 2 0, 0 0))'::geometry,
+ *      '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))'::geometry);
+ */
+
+PG_FUNCTION_INFO_V1(hausdorffdistance);
+Datum hausdorffdistance(PG_FUNCTION_ARGS)
+{
+       PG_LWGEOM *geom1;
+       PG_LWGEOM *geom2;
+       GEOSGeometry *g1;
+       GEOSGeometry *g2;
+       double result;
+       int retcode;
+
+       POSTGIS_DEBUG(2, "hausdorff_distance called");
+
+       geom1 = (PG_LWGEOM *)  PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+       geom2 = (PG_LWGEOM *)  PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
+
+       initGEOS(lwnotice, lwnotice);
+
+       g1 = (GEOSGeometry *)POSTGIS2GEOS(geom1);
+       g2 = (GEOSGeometry *)POSTGIS2GEOS(geom2);
+       retcode = GEOSHausdorffDistance(g1, g2, &result);
+       GEOSGeom_destroy(g1);
+       GEOSGeom_destroy(g2);
+
+       if (retcode == 0)
+       {
+               elog(ERROR,"GEOS HausdorffDistance() threw an error!");
+               PG_RETURN_NULL(); /*never get here */
+       }
+
+       PG_FREE_IF_COPY(geom1, 0);
+       PG_FREE_IF_COPY(geom2, 0);
+
+       PG_RETURN_FLOAT8(result);
+}
+
+/**
+ *  @brief Compute the Hausdorff distance with densification thanks to the corresponding GEOS function
+ *  @example hausdorffdistancedensify {@link #hausdorffdistancedensify} - SELECT st_hausdorffdistancedensify(
+ *      'POLYGON((0 0, 0 2, 1 2, 2 2, 2 0, 0 0))'::geometry,
+ *      '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))'::geometry, 0.5);
+ */
+
+PG_FUNCTION_INFO_V1(hausdorffdistancedensify);
+Datum hausdorffdistancedensify(PG_FUNCTION_ARGS)
+{
+       PG_LWGEOM *geom1;
+       PG_LWGEOM *geom2;
+       GEOSGeometry *g1;
+       GEOSGeometry *g2;
+       double densifyFrac;
+       double result;
+       int retcode;
+
+
+       geom1 = (PG_LWGEOM *)  PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+       geom2 = (PG_LWGEOM *)  PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
+       densifyFrac = *(double *)  PG_DETOAST_DATUM(PG_GETARG_DATUM(2));
+
+       initGEOS(lwnotice, lwnotice);
+
+       g1 = (GEOSGeometry *)POSTGIS2GEOS(geom1);
+       g2 = (GEOSGeometry *)POSTGIS2GEOS(geom2);
+       retcode = GEOSHausdorffDistanceDensify(g1, g2, densifyFrac, &result);
+       GEOSGeom_destroy(g1);
+       GEOSGeom_destroy(g2);
+
+       if (retcode == 0)
+       {
+               elog(ERROR,"GEOS HausdorffDistanceDensify() threw an error!");
+               PG_RETURN_NULL(); /*never get here */
+       }
+
+       PG_FREE_IF_COPY(geom1, 0);
+       PG_FREE_IF_COPY(geom2, 0);
+
+       PG_RETURN_FLOAT8(result);
+}
+
 /**
  * @brief This is the final function for GeomUnion
  *                     aggregate. Will have as input an array of Geometries.
index 86b64ed601ef6ce737044f495a85e318a6c8a732..fb982e0b4946660a1f0af0acc97d56cb79235d9c 100644 (file)
@@ -3918,6 +3918,24 @@ CREATE OR REPLACE FUNCTION ST_IsValidReason(geometry)
        LANGUAGE 'C' IMMUTABLE STRICT; 
 #endif
 
+#if POSTGIS_GEOS_VERSION >= 32
+-- Requires GEOS >= 3.2.0
+-- Availability: 1.5.0 
+CREATE OR REPLACE FUNCTION ST_HausdorffDistance(geometry, geometry)
+       RETURNS FLOAT8
+       AS 'MODULE_PATHNAME', 'hausdorffdistance'
+       LANGUAGE 'C' IMMUTABLE STRICT; 
+#endif
+
+#if POSTGIS_GEOS_VERSION >= 32
+-- Requires GEOS >= 3.2.0
+-- Availability: 1.5.0 
+CREATE OR REPLACE FUNCTION ST_HausdorffDistance(geometry, geometry, float8)
+       RETURNS FLOAT8
+       AS 'MODULE_PATHNAME', 'hausdorffdistancedensify'
+       LANGUAGE 'C' IMMUTABLE STRICT;
+#endif
+
 -- Deprecation in 1.2.3
 CREATE OR REPLACE FUNCTION difference(geometry,geometry)
        RETURNS geometry
index 8fd9dac511f35eaebee7a8cb8376cad685543a1b..85d56540b17fb14450d0d943e8e012a2ce5ce4e4 100644 (file)
@@ -399,6 +399,12 @@ DROP FUNCTION ST_buffer(geometry,float8,text);
 DROP FUNCTION _ST_buffer(geometry,float8,cstring);
 DROP FUNCTION ST_Intersection(geometry,geometry);
 DROP FUNCTION intersection(geometry,geometry);
+#if POSTGIS_GEOS_VERSION >= 32
+DROP FUNCTION ST_HausdorffDistance(geometry, geometry)
+#endif
+#if POSTGIS_GEOS_VERSION >= 32
+DROP FUNCTION ST_HausdorffDistance(geometry, geometry, float8)
+#endif
 
 
 ---------------------------------------------------------------
index f548b8c44115f5f29334ea018e7581a544212986..4d4c6eb51e5dc62d28b0501fb335893f8f090aec 100644 (file)
@@ -52,7 +52,8 @@ TESTS = \
        kml \
        regress_ogc \
        regress_bdpoly \
-       regress_proj
+       regress_proj \
+       hausdorff
 
 # Covers/CoveredBy only if GEOS >= 3.0
 ifeq ($(shell expr $(POSTGIS_GEOS_VERSION) ">=" 30),1)
diff --git a/regress/hausdorff.sql b/regress/hausdorff.sql
new file mode 100644 (file)
index 0000000..8c733bd
--- /dev/null
@@ -0,0 +1,37 @@
+-- tests for Hausdorff distances
+
+-- polygon and polygon
+SELECT 'hausdorff_poly_poly', st_hausdorffdistance(
+       'POLYGON((0 0, 0 2, 1 2, 2 2, 2 0, 0 0))'::geometry,
+       '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))'::geometry);
+-- 0.707106781186548
+
+-- linestring and linestring
+SELECT 'hausdorff_ls_ls', st_hausdorffdistance(
+       'LINESTRING (0 0, 2 1)'::geometry
+       , 'LINESTRING (0 0, 2 0)'::geometry);
+-- 1.0
+
+-- other linestrings
+SELECT 'hausdorff_ls_ls_2', st_hausdorffdistance(
+       'LINESTRING (0 0, 2 0)'::geometry, 
+       'LINESTRING (0 1, 1 2, 2 1)'::geometry);
+-- 2.0
+
+-- linestring and multipoint
+SELECT 'hausdorff_ls_mp', st_hausdorffdistance(
+       'LINESTRING (0 0, 2 0)'::geometry, 
+       'MULTIPOINT (0 1, 1 0, 2 1)'::geometry);
+-- 1.0
+
+-- another linestring and linestring
+SELECT 'hausdorff_ls_ls_3', st_hausdorffdistance(
+       'LINESTRING (130 0, 0 0, 0 150)'::geometry, 
+       'LINESTRING (10 10, 10 150, 130 10)'::geometry);
+-- 14.142135623730951
+
+-- hausdorf with densification
+SELECT 'hausdorffdensify_ls_ls', st_hausdorffdistance(
+       'LINESTRING (130 0, 0 0, 0 150)'::geometry
+       , 'LINESTRING (10 10, 10 150, 130 10)'::geometry, 0.5);
+-- 70.0
diff --git a/regress/hausdorff_expected b/regress/hausdorff_expected
new file mode 100644 (file)
index 0000000..1cc32c0
--- /dev/null
@@ -0,0 +1,6 @@
+hausdorff_poly_poly|0.707106781186548
+hausdorff_ls_ls|1
+hausdorff_ls_ls_2|2
+hausdorff_ls_mp|1
+hausdorff_ls_ls_3|14.142135623731
+hausdorffdensify_ls_ls|70