Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,30 +95,58 @@ private void simplifyRingEndpoint(CoordinateList pts) {

private LineSegment seg = new LineSegment();

private void simplifySection(int i, int j)
/**
* Douglas-Peucker section simplification using an explicit stack.
* <p>
* The classic recursive form can throw {@link StackOverflowError} on long,
* highly oscillatory linework where the furthest vertex is always adjacent
* to one endpoint (depth O(n)). An explicit stack keeps the same left-then-right
* split order while bounding memory by heap rather than the JVM call stack
* (see JTS #1127).
*/
private void simplifySection(int i0, int j0)
{
if((i+1) == j) {
return;
}
seg.p0 = pts[i];
seg.p1 = pts[j];
double maxDistance = -1.0;
int maxIndex = i;
for (int k = i + 1; k < j; k++) {
double distance = seg.distance(pts[k]);
if (distance > maxDistance) {
maxDistance = distance;
maxIndex = k;
// Pair stack: [i, j, i, j, ...]. Worst-case depth is O(n).
int[] stack = new int[Math.max(4, pts.length * 2)];
int top = 0;
stack[top++] = i0;
stack[top++] = j0;

while (top > 0) {
int j = stack[--top];
int i = stack[--top];
if ((i + 1) == j) {
continue;
}
}
if (maxDistance <= distanceTolerance) {
for(int k = i + 1; k < j; k++) {
usePt[k] = false;
seg.p0 = pts[i];
seg.p1 = pts[j];
double maxDistance = -1.0;
int maxIndex = i;
for (int k = i + 1; k < j; k++) {
double distance = seg.distance(pts[k]);
if (distance > maxDistance) {
maxDistance = distance;
maxIndex = k;
}
}
if (maxDistance <= distanceTolerance) {
for (int k = i + 1; k < j; k++) {
usePt[k] = false;
}
}
else {
// Grow if needed (should not for size 2*n, but stay safe).
if (top + 4 > stack.length) {
int[] grown = new int[stack.length * 2];
System.arraycopy(stack, 0, grown, 0, top);
stack = grown;
}
// Push right then left so left is processed first (same as recursion).
stack[top++] = maxIndex;
stack[top++] = j;
stack[top++] = i;
stack[top++] = maxIndex;
}
}
else {
simplifySection(i, maxIndex);
simplifySection(maxIndex, j);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,25 @@ public void testPolygonEndpointCollapse() {
"POLYGON EMPTY"
);
}

/**
* High-frequency zig-zag forces O(n) recursion depth in classic recursive
* Douglas-Peucker (each split peels one vertex). Matches the failure mode
* in JTS #1127 (noisy GPS tracks). Explicit-stack implementation must
* complete without StackOverflowError and keep every vertex at tolerance 0.
*/
public void testLongUnbalancedLineDoesNotStackOverflow() {
int n = 5000;
StringBuilder sb = new StringBuilder("LINESTRING (0 0");
for (int i = 1; i < n; i++) {
// alternate height so the furthest vertex is always adjacent to an endpoint
sb.append(", ").append(i).append(i % 2 == 0 ? " 0" : " 1");
}
sb.append(")");
Geometry geom = read(sb.toString());
Geometry result = DouglasPeuckerSimplifier.simplify(geom, 0.0);
assertEquals(n, result.getNumPoints());
}

private void checkDP(String wkt, double tolerance, String wktExpected) {
Geometry geom = read(wkt);
Expand Down
Loading