Skip to content

Commit a0b5e05

Browse files
committed
Support back-paths in simple-frechet error
1 parent 65cf706 commit a0b5e05

File tree

4 files changed

+41
-16
lines changed

4 files changed

+41
-16
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/spatial/GeometrySimplificationBenchmark.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
@OutputTimeUnit(TimeUnit.MILLISECONDS)
4242
@State(Scope.Thread)
4343
public class GeometrySimplificationBenchmark {
44-
@Param({ "cartesiantrianglearea", "triangleArea", "triangleheight", "frechetError" })
44+
@Param({ "cartesiantrianglearea", "triangleArea", "triangleheight", "simplefrechetError" })
4545
public String calculatorName;
4646

4747
@Param({ "10", "100", "1000", "10000", "20000" })

libs/geo/src/main/java/org/elasticsearch/geometry/simplify/SimplificationErrorCalculator.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Registry {
3030
add("cartesiantrianglearea", new CartesianTriangleAreaCalculator());
3131
add("trianglearea", new TriangleAreaCalculator());
3232
add("triangleheight", new TriangleHeightCalculator());
33-
add("frecheterror", new FrechetErrorCalculator());
33+
add("simplefrecheterror", new SimpleFrechetErrorCalculator());
3434
}
3535

3636
static void add(String name, SimplificationErrorCalculator calculator) {
@@ -60,7 +60,8 @@ static SimplificationErrorCalculator byName(String calculatorName) {
6060
}
6161

6262
/**
63-
* Calculate the triangle area using cartesian coordinates as described at https://en.wikipedia.org/wiki/Area_of_a_triangle
63+
* Calculate the triangle area using cartesian coordinates as described at
64+
* <a href="https://en.wikipedia.org/wiki/Area_of_a_triangle">Area of a triangle</a>
6465
*/
6566
class CartesianTriangleAreaCalculator implements SimplificationErrorCalculator {
6667

@@ -75,8 +76,8 @@ public double calculateError(PointLike left, PointLike middle, PointLike right)
7576
}
7677

7778
/**
78-
* Calculate the triangle area using geographic coordinates and Herons formula (side lengths)
79-
* as described at https://en.wikipedia.org/wiki/Area_of_a_triangle
79+
* Calculate the triangle area using geographic coordinates and Herons formula (side lengths) as described at
80+
* <a href="https://en.wikipedia.org/wiki/Area_of_a_triangle">Area of a triangle</a>
8081
*/
8182
class TriangleAreaCalculator implements SimplificationErrorCalculator {
8283

@@ -106,8 +107,8 @@ private double distance(PointLike a, PointLike b) {
106107
}
107108

108109
/**
109-
* Calculate the triangle area using geographic coordinates and Herons formula (side lengths)
110-
* as described at https://en.wikipedia.org/wiki/Area_of_a_triangle, but scale the area down
110+
* Calculate the triangle area using geographic coordinates and Herons formula (side lengths) as described at
111+
* <a href="https://en.wikipedia.org/wiki/Area_of_a_triangle">Area of a triangle</a>, but scale the area down
111112
* by the inverse of the length of the base (left-right), which estimates the height of the triangle.
112113
*/
113114
class TriangleHeightCalculator implements SimplificationErrorCalculator {
@@ -124,7 +125,7 @@ public double calculateError(PointLike left, PointLike middle, PointLike right)
124125
double db = s - b;
125126
double dc = s - c;
126127
if (da >= 0 && db >= 0 && dc >= 0) {
127-
// Herons formula, scaled by 1/a
128+
// Herons formula, scaled by 2/a to estimate height
128129
return 2.0 * Math.sqrt(s * da * db * dc) / a;
129130
} else {
130131
// rounding errors can cause flat triangles to have negative values, leading to NaN areas
@@ -137,7 +138,24 @@ private double distance(PointLike a, PointLike b) {
137138
}
138139
}
139140

140-
class FrechetErrorCalculator implements SimplificationErrorCalculator {
141+
/**
142+
* Estimate the error as the height of the point above the base, but including support for back-paths
143+
* in the sense that of the point to be removed is father from either end than the height, we take that distance instead.
144+
* <p>
145+
* Rotate all three points such that left-right are a horizontal line on the x-axis. Then the numbers of interest are:
146+
* <ol>
147+
* <li>height: y-value of middle point</li>
148+
* <li>deltaL: -min(0, middleX - leftX)</li>
149+
* <li>deltaR: max(0, middleX - rightX)</li>
150+
* </ol>
151+
* And the final error is: error = max(height, max(deltaL, deltaR))
152+
* <p>
153+
* This is not a full Frechet error calculation as it does not maintain state of all removed points,
154+
* only calculating the error incurred by removal of the current point, as if the current simplified line is
155+
* a good enough approximation of the original line. This restriction is currently true of all the
156+
* calculations implemented so far.
157+
*/
158+
class SimpleFrechetErrorCalculator implements SimplificationErrorCalculator {
141159

142160
@Override
143161
public double calculateError(PointLike left, PointLike middle, PointLike right) {
@@ -157,8 +175,11 @@ public double calculateError(PointLike left, PointLike middle, PointLike right)
157175
double rightYrotated = rightY * cos - rightX * sin;
158176
assert Math.abs(rightYrotated) < 1e-10;
159177
assert Math.abs(rightXrotated - len) < len / 1e10;
160-
// Return distance to x-axis TODO: also include back-paths for Frechet distance calculation
161-
return Math.abs(middleYrotated);
178+
double height = Math.abs(middleYrotated);
179+
double deltaL = -Math.min(0, middleXrotated);
180+
double deltaR = Math.max(0, middleXrotated - rightXrotated);
181+
double backDistance = Math.max(deltaR, deltaL);
182+
return Math.max(height, backDistance);
162183
} else {
163184
// If left and right points are co-located, we assume no consequence to removing the middle point
164185
return 0.0;

libs/geo/src/test/java/org/elasticsearch/geometry/simplify/GeometrySimplifierFrechetTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import org.elasticsearch.geometry.Line;
1212

1313
public class GeometrySimplifierFrechetTests extends GeometrySimplifierTests {
14-
private SimplificationErrorCalculator calculator = new SimplificationErrorCalculator.FrechetErrorCalculator();
14+
private SimplificationErrorCalculator calculator = new SimplificationErrorCalculator.SimpleFrechetErrorCalculator();
1515

1616
@Override
1717
protected SimplificationErrorCalculator calculator() {

libs/geo/src/test/java/org/elasticsearch/geometry/simplify/SimplificationErrorCalculatorTests.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,20 @@ public void testCartesianAreaCalculation() {
2323
}
2424

2525
public void testFrechetCalculation() {
26-
var calculator = new SimplificationErrorCalculator.FrechetErrorCalculator();
26+
var calculator = new SimplificationErrorCalculator.SimpleFrechetErrorCalculator();
2727
var ao = new TestPoint(0, 0);
2828
var co = new TestPoint(1, 0);
2929
for (double degrees = 0; degrees < 360; degrees += 45) {
3030
TestPoint c = co.rotated(degrees, ao);
31-
for (double x = -1; x <= 2; x += 0.5) {
31+
for (double x = -2; x <= 3; x += 0.5) {
3232
var b = new TestPoint(x, 1).rotated(degrees, ao);
3333
double error = calculator.calculateError(ao, b, c);
34-
// TODO: change test once Frechet calculation includes back-paths
35-
assertThat("Expect a unit offset when bx=" + x + " rotated " + degrees, error, closeTo(1.0, 1e-10));
34+
double expected = 1.0; // triangle height is 1.0
35+
if (x < -1 || x > 2) {
36+
// Back-paths dominate, so assert on that, otherwise assert on triangle height
37+
expected = x < 0 ? -x : x - 1;
38+
}
39+
assertThat("Expect a unit offset when bx=" + x + " rotated " + degrees, error, closeTo(expected, 1e-10));
3640
}
3741
}
3842
}

0 commit comments

Comments
 (0)