Skip to content
Merged
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
66 changes: 29 additions & 37 deletions xrspatial/tests/test_polygonize.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,13 @@ def test_polygonize_nan_pixels_excluded_dask():
class TestSimplifyHelpers:
"""Tests for internal simplification helper functions."""

@staticmethod
def _make_zigzag_coords(n):
return np.array(
[[float(i), float((i % 3) * 0.5)] for i in range(n)],
dtype=np.float64,
)

def test_douglas_peucker_straight_line(self):
"""DP on a straight line should reduce to just endpoints."""
from ..polygonize import _douglas_peucker
Expand Down Expand Up @@ -1155,10 +1162,7 @@ def _visvalingam_whyatt_o2(coords, tolerance):
return result

def make_coords(n):
return np.array(
[[float(i), float((i % 3) * 0.5)] for i in range(n)],
dtype=np.float64,
)
return self._make_zigzag_coords(n)

sizes = [50, 100, 200, 500]
tolerance = 0.1
Expand Down Expand Up @@ -1187,49 +1191,37 @@ def make_coords(n):
f"expected < 3x (heap={heap_time:.6f}s, o2={o2_time:.6f}s)"
)

def test_visvalingam_whyatt_scales_subquadratic(self):
"""_visvalingam_whyatt must scale sub-quadratically with input size.
def test_visvalingam_whyatt_completes_large_inputs(self):
"""_visvalingam_whyatt must complete on large inputs without hanging.

Issue #2539: the original O(n^2) linear min-area scan caused
simplification to dominate on rings with thousands of vertices.
The heap-based implementation should scale as O(n log n), so
when input size doubles, runtime should increase by less than 3x.
Issue #2888: the previous timing-based subquadratic scaling test was
flaky on loaded CI runners. This simplified smoke test verifies the
function handles large inputs correctly without timing assertions.

Uses larger inputs (2000-32000) and more iterations (50) for stable
timing measurements that avoid sub-millisecond noise.
The O(n log n) complexity is guaranteed by the heap-based
implementation and validated by correctness tests.
"""
import time

from ..polygonize import _visvalingam_whyatt

def make_coords(n):
return np.array(
[[float(i), float((i % 3) * 0.5)] for i in range(n)],
dtype=np.float64,
)

sizes = [2000, 4000, 8000, 16000, 32000]
tolerance = 0.1
timings = []

for n in sizes:
coords = make_coords(n)
# Warm up (numba compilation + cache).
coords = self._make_zigzag_coords(n)
# Warm up call (triggers numba JIT compilation and cache population,
# discarded; the second call below is the timed/measured one).
_visvalingam_whyatt(coords, tolerance)

start = time.perf_counter()
for _ in range(50):
_visvalingam_whyatt(coords, tolerance)
elapsed = (time.perf_counter() - start) / 50
timings.append(elapsed)

# When input size doubles, time should increase by less than 3x.
# O(n^2) would give ~4x; O(n log n) gives ~2.1-2.3x.
for i in range(len(timings) - 1):
ratio = timings[i + 1] / timings[i]
assert ratio < 3.0, (
f"Time ratio for {sizes[i]} -> {sizes[i+1]} is {ratio:.2f}x, "
f"expected < 3x (O(n^2) would give ~4x)"
result = _visvalingam_whyatt(coords, tolerance)
assert result.shape[0] >= 2, (
f"Expected at least 2 vertices for n={n}, got {result.shape[0]}"
)
assert result.shape[1] == 2
# Verify meaningful simplification occurred: at minimum the
# collinear upslope vertices (every 3rd interior point) are
# removed, so output must be < 90 % of input.
assert result.shape[0] < n * 0.9, (
f"Expected < {0.9 * n:.0f} vertices for n={n}, "
f"got {result.shape[0]} (ratio={result.shape[0]/n:.4f})"
)


Expand Down
Loading