Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 15 additions & 4 deletions libpysal/weights/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,22 @@ def _da2wsp(
ser = da.to_series()
dtype = np.int32 if (shape[0] * shape[1]) < 46340**2 else np.int64
nodata = get_nodata(da)
if nodata is not None:
mask = (ser != nodata).to_numpy()

values = ser.to_numpy()
mask = None

# Handle NaN masking for float rasters (explicit or implicit nodata)
if np.issubdtype(values.dtype, np.floating) and (
nodata is None or (isinstance(nodata, float) and np.isnan(nodata))
):
mask = ~np.isnan(values)
else:
mask = values != nodata

if mask is not None:
ids = np.where(mask)[0]
id_map = _idmap(ids, mask, dtype)
ser = ser[ser != nodata]
ser = ser[mask]
else:
ids = np.arange(len(ser), dtype=dtype)
id_map = ids.copy()
Expand Down Expand Up @@ -393,7 +404,7 @@ def _da2wsp(
else:
# Fallback method to build sparse matrix
sw = lat2SW(*shape, criterion)
if "nodatavals" in da.attrs and da.attrs["nodatavals"]:
if mask is not None:
sw = sw[mask]
sw = sw[:, mask]
return sw, ser
Expand Down
57 changes: 57 additions & 0 deletions libpysal/weights/tests/test_raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,60 @@ def test_dataarray(self):
)
w = Queen.from_xarray(da)
assert w.n == 97232


class TestNodata:
def setup_method(self):
self.xr = pytest.importorskip("xarray")

def test_nan_nodata_float_raster_not_masked(self):
"""
Float rasters with NaN nodata should exclude NaN cells from contiguity,
but current logic does not mask them since `ser != np.nan` is always True.
"""
data = np.array([[1.0, 1.0, 1.0], [1.0, np.nan, 1.0], [1.0, 1.0, 1.0]])
da = self.xr.DataArray(data, dims=("y", "x"))

# Expected: center NaN pixel excluded -> 8 valid cells
# Actual (current bug): NaN is not masked -> 9 cells
wsp = raster.da2WSP(da)
assert wsp.n == 8

def test_rio_nodata(self):
"""
Raster with da.rio.nodata should be correctly masked.
This guards existing behavior for rioxarray.
"""
pytest.importorskip("rioxarray")
import rioxarray # noqa: F401

data = np.array([[1, 1, 1], [1, -9999, 1], [1, 1, 1]])
# xarray with rioxarray needs coords to be valid usually
# for some ops, but write_nodata might be fine
da = self.xr.DataArray(data, dims=("y", "x"))
da = da.rio.write_nodata(-9999)

wsp = raster.da2WSP(da)
assert wsp.n == 8

def test_attrs_nodata(self):
"""
Raster with da.attrs['nodatavals'] should be correctly masked.
This guards backward compatibility.
"""
data = np.array([[1, 1, 1], [1, -1, 1], [1, 1, 1]], dtype=int)
da = self.xr.DataArray(data, dims=("y", "x"))
da.attrs["nodatavals"] = (-1,)

wsp = raster.da2WSP(da)
assert wsp.n == 8

def test_no_nodata(self):
"""
Raster without nodata should include all pixels.
"""
data = np.array([[1.0, 1.0, 1.0], [1.0, 2.0, 1.0], [1.0, 1.0, 1.0]])
da = self.xr.DataArray(data, dims=("y", "x"))

wsp = raster.da2WSP(da)
assert wsp.n == 9