Skip to content
Merged
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
5 changes: 3 additions & 2 deletions xrspatial/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ def _marching_squares_kernel(data, level, seg_rows, seg_cols, seg_count):
bl = data[r + 1, c]
br = data[r + 1, c + 1]

# Skip quads with any NaN corner.
if tl != tl or tr != tr or bl != bl or br != br:
# Skip quads with any non-finite corner (NaN or +/-inf).
if not (np.isfinite(tl) and np.isfinite(tr) and
np.isfinite(bl) and np.isfinite(br)):
continue

# Build 4-bit case index.
Expand Down
44 changes: 28 additions & 16 deletions xrspatial/tests/test_contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,18 +538,11 @@ def test_inf_far_level_no_crossing(self):
assert np.isfinite(coords).all(), (
"level 0.5 ring should not include the inf quad")

@pytest.mark.xfail(
reason="contours() emits NaN coordinates near an inf corner; "
"see https://github.com/xarray-contrib/xarray-spatial/"
"issues/2704",
strict=True,
)
def test_inf_corner_no_nan_coords(self):
"""A finite level near a +inf cell must not leak NaN coordinates.

The NaN-skip guard in the kernel uses ``x != x`` which does not
catch infinity, so the inf quad is interpolated and produces NaN.
Tracked as a source bug in #2704.
Regression for #2704: the NaN-skip guard in the kernel used ``x != x``
which does not catch infinity; fixed by using ``np.isfinite``.
"""
data = self._inf_peak(np.inf)
agg = create_test_raster(data, backend='numpy')
Expand All @@ -558,21 +551,40 @@ def test_inf_corner_no_nan_coords(self):
assert np.isfinite(coords).all(), (
f"non-finite coordinate in contour at level {level}: {coords}")

@pytest.mark.xfail(
reason="contours() emits NaN coordinates near a -inf corner; "
"see https://github.com/xarray-contrib/xarray-spatial/"
"issues/2704",
strict=True,
)
def test_neg_inf_corner_no_nan_coords(self):
"""Same NaN leak for a -inf corner. Tracked in #2704."""
"""A finite level near a -inf cell must not leak NaN coordinates.

Regression for #2704: same fix as test_inf_corner_no_nan_coords.
"""
data = self._inf_peak(-np.inf)
agg = create_test_raster(data, backend='numpy')
result = contours(agg, levels=[0.5])
for level, coords in result:
assert np.isfinite(coords).all(), (
f"non-finite coordinate in contour at level {level}: {coords}")

def test_mixed_inf(self):
"""Multiple infinities of opposite signs must not produce NaN."""
data = np.array([
[0., 0., 0., 0., 0.],
[0., np.inf, 1., -np.inf, 0.],
[0., 1., 1., 1., 0.],
[0., -np.inf, 1., np.inf, 0.],
[0., 0., 0., 0., 0.],
], dtype=np.float64)
agg = create_test_raster(data, backend='numpy')
result = contours(agg, levels=[0.5])
for level, coords in result:
assert np.isfinite(coords).all(), \
f"Non-finite coordinates found at level {level}"

def test_all_inf_quad(self):
"""A 2x2 raster with all corners infinite produces no contours."""
data = np.full((2, 2), np.inf, dtype=np.float64)
agg = create_test_raster(data, backend='numpy')
result = contours(agg, levels=[1.0])
assert result == []


# ---------------------------------------------------------------------------
# CRS propagation to GeoDataFrame output (#2704 audit, Cat 5)
Expand Down
Loading