Description
The CPU sweep path of viewshed crashes on certain NaN/nodata input positions. A single NaN cell placed on the observer's row to the right of the observer raises ValueError: node not found.
Reproduction:
import numpy as np, xarray as xa
from xrspatial import viewshed
arr = np.full((5, 5), 2.0)
arr[2, 4] = np.nan # observer is at (2, 2)
xs = np.arange(5.0); ys = np.arange(5.0)
raster = xa.DataArray(arr, coords=dict(x=xs, y=ys), dims=["y", "x"])
viewshed(raster, x=2.0, y=2.0, observer_elev=5) # ValueError: node not found
Root cause
_init_event_list generates the three sweep events (ENTER / CENTER / EXIT) for every cell, including NaN cells. There is no nodata check, despite the comment claiming the code path is "not NODATA". When the sweep processes a NaN cell, the ENTER event inserts a node whose key is computed from the NaN elevation, and the matching EXIT event then fails to find that node in the red-black status tree, raising ValueError: node not found from _delete_from_tree.
The initial sweepline insertion loop already guards with if not np.isnan(...), so the asymmetry lives in event generation.
Proposed policy
Skip invalid cells in event generation: do not create ENTER/CENTER/EXIT events for a NaN cell, so it is never inserted into or deleted from the status tree. The visibility grid is already initialized to INVISIBLE, so a skipped NaN cell stays INVISIBLE. This applies across the numpy, dask, and cupy-CPU-fallback paths, which all route through the CPU sweep.
A strict xfail at xrspatial/tests/test_viewshed.py::test_viewshed_nan_input_crashing_position already documents this crash; the fix flips it into a passing test.
Note: supersedes closed issue #2693, which tracked the same crash.
Description
The CPU sweep path of
viewshedcrashes on certain NaN/nodata input positions. A single NaN cell placed on the observer's row to the right of the observer raisesValueError: node not found.Reproduction:
Root cause
_init_event_listgenerates the three sweep events (ENTER / CENTER / EXIT) for every cell, including NaN cells. There is no nodata check, despite the comment claiming the code path is "not NODATA". When the sweep processes a NaN cell, the ENTER event inserts a node whose key is computed from the NaN elevation, and the matching EXIT event then fails to find that node in the red-black status tree, raisingValueError: node not foundfrom_delete_from_tree.The initial sweepline insertion loop already guards with
if not np.isnan(...), so the asymmetry lives in event generation.Proposed policy
Skip invalid cells in event generation: do not create ENTER/CENTER/EXIT events for a NaN cell, so it is never inserted into or deleted from the status tree. The visibility grid is already initialized to INVISIBLE, so a skipped NaN cell stays INVISIBLE. This applies across the numpy, dask, and cupy-CPU-fallback paths, which all route through the CPU sweep.
A strict xfail at
xrspatial/tests/test_viewshed.py::test_viewshed_nan_input_crashing_positionalready documents this crash; the fix flips it into a passing test.Note: supersedes closed issue #2693, which tracked the same crash.