From 389c256383a3eaf52785f6152029a2309e467b31 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Wed, 18 Dec 2024 16:46:20 -0800 Subject: [PATCH] Add functions for oriented Hausdorff distance --- .../distance/DiscreteFrechetDistance.java | 2 + .../distance/DiscreteHausdorffDistance.java | 135 +++++++++++++++++- .../DiscreteHausdorffDistanceTest.java | 114 +++++++++++---- 3 files changed, 218 insertions(+), 33 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteFrechetDistance.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteFrechetDistance.java index a12948950a..009e734ec0 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteFrechetDistance.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteFrechetDistance.java @@ -47,6 +47,8 @@ * @see Distance discrète de Fréchet optimisée * @see * Fast Discrete Fréchet Distance + * + * @see DiscreteHausdorffDistance */ public class DiscreteFrechetDistance { diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistance.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistance.java index a45cbcbae9..dfc2ecaf57 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistance.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistance.java @@ -17,6 +17,7 @@ import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.CoordinateSequenceFilter; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; /** * An algorithm for computing a distance metric @@ -26,7 +27,8 @@ * for one of the geometries. * The points can be either the vertices of the geometries (the default), * or the geometries with line segments densified by a given fraction. - * Also determines two points of the Geometries which are separated by the computed distance. + * The class can also determine two points of the geometries + * which are separated by the computed distance. *

* This algorithm is an approximation to the standard Hausdorff distance. * Specifically, @@ -53,15 +55,39 @@ * DHD(A, B) = 22.360679774997898 * HD(A, B) ~= 47.8 * + * The class can compute the oriented Hausdorff distance from A to B. + * This computes the distance to the farthest point on A from B. + * A use case is to test whether a geometry A lies completely within a given + * distance of another one B. + * This is more efficient than testing inclusion in a buffer of B. + * + * @see DiscreteFrechetDistance + * */ public class DiscreteHausdorffDistance { + /** + * Computes the Hausdorff distance between two geometries. + * + * @param g0 the first input + * @param g1 the second input + * @return the Hausdorff distance between g0 and g1 + */ public static double distance(Geometry g0, Geometry g1) { DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); return dist.distance(); } + /** + * Computes the Hausdorff distance between two geometries, + * densified by the given fraction. + * + * @param g0 the first input + * @param g1 the second input + * @param densifyFrac the densification fraction + * @return the Hausdorff distance between g0 and g1 + */ public static double distance(Geometry g0, Geometry g1, double densifyFrac) { DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); @@ -69,6 +95,101 @@ public static double distance(Geometry g0, Geometry g1, double densifyFrac) return dist.distance(); } + /** + * Computes a line containing points indicating + * the Hausdorff distance between two geometries. + * + * @param g0 the first input + * @param g1 the second input + * @return a 2-point line indicating the distance + */ + public static LineString distanceLine(Geometry g0, Geometry g1) + { + DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); + dist.distance(); + return g0.getFactory().createLineString(dist.getCoordinates()); + } + + /** + * Computes a line containing points indicating + * the Hausdorff distance between two geometries, + * densified by the given fraction. + * + * @param g0 the first input + * @param g1 the second input + * @param densifyFrac the densification fraction + * @return a 2-point line indicating the distance + */ + public static LineString distanceLine(Geometry g0, Geometry g1, double densifyFrac) + { + DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); + dist.setDensifyFraction(densifyFrac); + dist.distance(); + return g0.getFactory().createLineString(dist.getCoordinates()); + } + + /** + * Computes the oriented Hausdorff distance from one geometry to another. + * + * @param g0 the first input + * @param g1 the second input + * @return the oriented Hausdorff distance from g0 to g1 + */ + public static double orientedDistance(Geometry g0, Geometry g1) + { + DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); + return dist.orientedDistance(); + } + + /** + * Computes the oriented Hausdorff distance from one geometry to another, + * densified by the given fraction. + * + * @param g0 the first input + * @param g1 the second input + * @param densifyFrac the densification fraction + * @return the oriented Hausdorff distance from g0 to g1 + */ + public static double orientedDistance(Geometry g0, Geometry g1, double densifyFrac) + { + DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); + dist.setDensifyFraction(densifyFrac); + return dist.orientedDistance(); + } + + /** + * Computes a line containing points indicating + * the computed oriented Hausdorff distance from one geometry to another. + * + * @param g0 the first input + * @param g1 the second input + * @return a 2-point line indicating the distance + */ + public static LineString orientedDistanceLine(Geometry g0, Geometry g1) + { + DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); + dist.orientedDistance(); + return g0.getFactory().createLineString(dist.getCoordinates()); + } + + /** + * Computes a line containing points indicating + * the computed oriented Hausdorff distance from one geometry to another, + * densified by the given fraction. + * + * @param g0 the first input + * @param g1 the second input + * @param densifyFrac the densification fraction + * @return a 2-point line indicating the distance + */ + public static LineString orientedDistanceLine(Geometry g0, Geometry g1, double densifyFrac) + { + DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); + dist.setDensifyFraction(densifyFrac); + dist.orientedDistance(); + return g0.getFactory().createLineString(dist.getCoordinates()); + } + private Geometry g0; private Geometry g1; private PointPairDistance ptDist = new PointPairDistance(); @@ -90,7 +211,7 @@ public DiscreteHausdorffDistance(Geometry g0, Geometry g1) * subsegments, whose fraction of the total length is closest * to the given fraction. * - * @param densifyFrac + * @param densifyFrac a fraction in range (0, 1] */ public void setDensifyFraction(double densifyFrac) { @@ -101,12 +222,22 @@ public void setDensifyFraction(double densifyFrac) this.densifyFrac = densifyFrac; } + /** + * Computes the Hausdorff distance between A and B. + * + * @return the Hausdorff distance + */ public double distance() { compute(g0, g1); return ptDist.getDistance(); } + /** + * Computes the oriented Hausdorff distance from A to B. + * + * @return the oriented Hausdorff distance + */ public double orientedDistance() { computeOrientedDistance(g0, g1, ptDist); diff --git a/modules/core/src/test/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistanceTest.java b/modules/core/src/test/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistanceTest.java index ae99fb5e6d..122c509249 100644 --- a/modules/core/src/test/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistanceTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/algorithm/distance/DiscreteHausdorffDistanceTest.java @@ -9,19 +9,15 @@ * * http://www.eclipse.org/org/documents/edl-v10.php. */ - package org.locationtech.jts.algorithm.distance; import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.io.ParseException; -import junit.framework.TestCase; import junit.textui.TestRunner; -import test.jts.util.IOUtil; - +import test.jts.GeometryTestCase; public class DiscreteHausdorffDistanceTest -extends TestCase +extends GeometryTestCase { public static void main(String args[]) { TestRunner.run(DiscreteHausdorffDistanceTest.class); @@ -29,53 +25,109 @@ public static void main(String args[]) { public DiscreteHausdorffDistanceTest(String name) { super(name); } - public void testLineSegments() throws Exception + public void testLineSegments() { - runTest("LINESTRING (0 0, 2 1)", "LINESTRING (0 0, 2 0)", 1.0); + runTest("LINESTRING (0 0, 2 1)", "LINESTRING (0 0, 2 0)", "LINESTRING (2 0, 2 1)"); } - public void testLineSegments2() throws Exception + public void testLineSegments2() { - runTest("LINESTRING (0 0, 2 0)", "LINESTRING (0 1, 1 2, 2 1)", 2.0); + runTest("LINESTRING (0 0, 2 0)", "LINESTRING (0 1, 1 2, 2 1)", "LINESTRING (1 0, 1 2)"); } - public void testLinePoints() throws Exception + public void testLinePoints() { - runTest("LINESTRING (0 0, 2 0)", "MULTIPOINT (0 1, 1 0, 2 1)", 1.0); + runTest("LINESTRING (0 0, 2 0)", "MULTIPOINT (0 2, 1 0, 2 1)", "LINESTRING (0 0, 0 2)"); } /** - * Shows effects of limiting HD to vertices - * Answer is not true Hausdorff distance. - * - * @throws Exception + * Shows effects of limiting HD to vertices, + * which in this case does not produce the true Hausdorff distance. */ - public void testLinesShowingDiscretenessEffect() throws Exception + public void testLinesShowingDiscretenessEffect() { - runTest("LINESTRING (130 0, 0 0, 0 150)", "LINESTRING (10 10, 10 150, 130 10)", 14.142135623730951); + String wkt1 = "LINESTRING (130 0, 0 0, 0 150)"; + String wkt2 = "LINESTRING (10 10, 10 150, 130 10)"; + runTest(wkt1, wkt2, "LINESTRING (10 10, 0 0)"); + // densifying provides accurate HD + runTest(wkt1, wkt2, 0.5, "LINESTRING (0 80, 70 80)"); + + //-- oriented mode + runOriented(wkt1, wkt2, "LINESTRING (10 10, 0 0)"); // densifying provides accurate HD - runTest("LINESTRING (130 0, 0 0, 0 150)", "LINESTRING (10 10, 10 150, 130 10)", 0.5, 70.0); + runOriented(wkt1, wkt2, 0.1, "LINESTRING (107.41176470588235 36.352941176470594, 65 0)"); } + public void testOrientedLines() throws Exception + { + String wkt1 = "LINESTRING (1 6, 3 5, 1 4)"; + String wkt2 = "LINESTRING (1 9, 9 5, 1 1)"; + runOriented(wkt1, wkt2, "LINESTRING (2.2 8.4, 1 6)"); + runOriented(wkt2, wkt1, "LINESTRING (3 5, 9 5)"); + } + + public void testOrientedLines2() throws Exception + { + String wkt1 = "LINESTRING (1 6, 3 5, 1 4)"; + String wkt2 = "LINESTRING (1 3, 1 9, 9 5, 1 1)"; + runOriented(wkt1, wkt2, "LINESTRING (1 5, 3 5)"); + runOriented(wkt2, wkt1, "LINESTRING (3 5, 9 5)"); + } + private static final double TOLERANCE = 0.00001; - private void runTest(String wkt1, String wkt2, double expectedDistance) - throws ParseException + private void runTest(String wkt1, String wkt2, String wktExpected) { - Geometry g1 = IOUtil.readWKT(wkt1); - Geometry g2 = IOUtil.readWKT(wkt2); + Geometry g1 = read(wkt1); + Geometry g2 = read(wkt2); + + Geometry result = DiscreteHausdorffDistance.distanceLine(g1, g2); + Geometry expected = read(wktExpected); + checkEqual(expected, result, TOLERANCE); - double distance = DiscreteHausdorffDistance.distance(g1, g2); - assertEquals(distance, expectedDistance, TOLERANCE); + double resultDistance = DiscreteHausdorffDistance.distance(g1, g2); + double expectedDistance = expected.getLength(); + assertEquals(expectedDistance, resultDistance, TOLERANCE); } - private void runTest(String wkt1, String wkt2, double densifyFrac, double expectedDistance) - throws ParseException + + private void runTest(String wkt1, String wkt2, double densifyFrac, String wktExpected) { - Geometry g1 = IOUtil.readWKT(wkt1); - Geometry g2 = IOUtil.readWKT(wkt2); + Geometry g1 = read(wkt1); + Geometry g2 = read(wkt2); + + Geometry result = DiscreteHausdorffDistance.distanceLine(g1, g2, densifyFrac); + Geometry expected = read(wktExpected); + checkEqual(expected, result, TOLERANCE); + + double resultDistance = DiscreteHausdorffDistance.distance(g1, g2, densifyFrac); + double expectedDistance = expected.getLength(); + assertEquals(expectedDistance, resultDistance, TOLERANCE); + } + + private void runOriented(String wkt1, String wkt2, String wktExpected) { + Geometry g1 = read(wkt1); + Geometry g2 = read(wkt2); + + Geometry result = DiscreteHausdorffDistance.orientedDistanceLine(g1, g2); + Geometry expected = read(wktExpected); + checkEqual(expected, result, TOLERANCE); + + double resultDistance = DiscreteHausdorffDistance.orientedDistance(g1, g2); + double expectedDistance = expected.getLength(); + assertEquals(expectedDistance, resultDistance, TOLERANCE); + } + + private void runOriented(String wkt1, String wkt2, double densifyFrac, String wktExpected) { + Geometry g1 = read(wkt1); + Geometry g2 = read(wkt2); + + Geometry result = DiscreteHausdorffDistance.orientedDistanceLine(g1, g2, densifyFrac); + Geometry expected = read(wktExpected); + checkEqual(expected, result, TOLERANCE); - double distance = DiscreteHausdorffDistance.distance(g1, g2, densifyFrac); - assertEquals(distance, expectedDistance, TOLERANCE); + double resultDistance = DiscreteHausdorffDistance.orientedDistance(g1, g2, densifyFrac); + double expectedDistance = expected.getLength(); + assertEquals(expectedDistance, resultDistance, TOLERANCE); } }