Skip to content

Commit

Permalink
Add functions for oriented Hausdorff distance
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-jts committed Dec 19, 2024
1 parent 33fd2a9 commit 389c256
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
* @see <a href="https://hal.archives-ouvertes.fr/hal-02110055/document">Distance discrète de Fréchet optimisée</a>
* @see <a href="https://towardsdatascience.com/fast-discrete-fr%C3%A9chet-distance-d6b422a8fb77">
* Fast Discrete Fréchet Distance</a>
*
* @see DiscreteHausdorffDistance
*/
public class DiscreteFrechetDistance {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
* <p>
* This algorithm is an approximation to the standard Hausdorff distance.
* Specifically,
Expand All @@ -53,22 +55,141 @@
* DHD(A, B) = 22.360679774997898
* HD(A, B) ~= 47.8
* </pre>
* 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);
dist.setDensifyFraction(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();
Expand All @@ -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)
{
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,73 +9,125 @@
*
* 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);
}

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);
}

}

0 comments on commit 389c256

Please sign in to comment.