Describe the bug
viewshed() on a single-row or single-column raster silently returns wrong results. The CPU sweep helper _viewshed_cpu computes the grid resolution as:
ew_res = (x_range[1] - x_range[0]) / (width - 1)
ns_res = (y_range[1] - y_range[0]) / (height - 1)
When width == 1 or height == 1, the denominator is zero. With float coordinates this gives NaN (a divide-by-zero RuntimeWarning, not an exception). That NaN resolution then flows through every distance and gradient calculation, so every non-observer cell comes back INVISIBLE, even on flat terrain where they should all be visible. Nothing raises, so the caller gets a plausible-looking but wrong array.
The sibling helpers _viewshed_windowed and _viewshed_dask already guard this with if width > 1 else 1.0 / if height > 1 else 1.0. _viewshed_cpu is missing the same guard. And since _viewshed_windowed routes a degenerate window back through _viewshed_cpu, the max_distance path is affected too.
Reproduce
import numpy as np, xarray as xr
from xrspatial import viewshed
arr = np.zeros((1, 5))
r = xr.DataArray(arr, coords=dict(x=np.arange(5.0), y=np.arange(1.0)), dims=["y", "x"])
print(viewshed(r, x=2.0, y=0.0, observer_elev=5).values)
# [[ -1. -1. 180. -1. -1.]] -- every cell but the observer is INVISIBLE
Expected behavior
On flat terrain with an elevated observer, all cells should be visible. The fix is to fall back to unit resolution when an axis has length 1, matching what the windowed and dask helpers already do.
Additional context
Found during a robustness sweep of the viewshed module. Silent wrong output in an analysis function, so worth treating as high priority.
Describe the bug
viewshed()on a single-row or single-column raster silently returns wrong results. The CPU sweep helper_viewshed_cpucomputes the grid resolution as:When
width == 1orheight == 1, the denominator is zero. With float coordinates this gives NaN (a divide-by-zero RuntimeWarning, not an exception). That NaN resolution then flows through every distance and gradient calculation, so every non-observer cell comes back INVISIBLE, even on flat terrain where they should all be visible. Nothing raises, so the caller gets a plausible-looking but wrong array.The sibling helpers
_viewshed_windowedand_viewshed_daskalready guard this withif width > 1 else 1.0/if height > 1 else 1.0._viewshed_cpuis missing the same guard. And since_viewshed_windowedroutes a degenerate window back through_viewshed_cpu, themax_distancepath is affected too.Reproduce
Expected behavior
On flat terrain with an elevated observer, all cells should be visible. The fix is to fall back to unit resolution when an axis has length 1, matching what the windowed and dask helpers already do.
Additional context
Found during a robustness sweep of the viewshed module. Silent wrong output in an analysis function, so worth treating as high priority.