Description
Several xrspatial.hydro public functions accept scalar parameters that are not range-checked. Non-finite values (NaN, Inf) or out-of-domain values pass the existing checks and silently produce wrong output (all-NaN rasters, no-op behavior, or undefined comparisons).
| Function |
Parameter |
Current check |
Bad input that slips through |
flow_direction_mfd |
p (exponent) |
p <= 0 |
p=NaN (NaN <= 0 is False), p=Inf |
snap_pour_point_d8 |
search_radius |
none |
0, negative, 5.5, NaN |
hand_d8 / hand_dinf / hand_mfd |
threshold |
none |
NaN, Inf (silent all-NaN output) |
fill_d8 |
z_limit |
none |
NaN (revert never fires), negative (reverts everything) |
Examples:
flow_direction_mfd(agg, p=float('nan')) -> all weights become NaN, output is silently all-NaN.
snap_pour_point_d8(fa, pp, search_radius=0) -> no-op, output equals input.
hand_d8(fd, fa, el, threshold=float('nan')) -> fa >= NaN always False, returns NaN everywhere.
fill_d8(dem, z_limit=float('nan')) -> out - dem > NaN always False, behaves as if z_limit=None.
Expected behavior
Each parameter is validated up front. Non-finite values and out-of-range values raise a clean ValueError with a message that names the parameter.
Proposed fix
flow_direction_mfd: replace if p <= 0 with if not (np.isfinite(p) and p > 0).
snap_pour_point_d8: enforce isinstance(search_radius, (int, np.integer)) and search_radius >= 1.
hand_*: enforce np.isfinite(threshold) (semantic range allows 0).
fill_d8: when z_limit is not None, enforce np.isfinite(z_limit) and z_limit >= 0.
Description
Several
xrspatial.hydropublic functions accept scalar parameters that are not range-checked. Non-finite values (NaN,Inf) or out-of-domain values pass the existing checks and silently produce wrong output (all-NaN rasters, no-op behavior, or undefined comparisons).flow_direction_mfdp(exponent)p <= 0p=NaN(NaN <= 0 is False),p=Infsnap_pour_point_d8search_radius0, negative,5.5,NaNhand_d8/hand_dinf/hand_mfdthresholdNaN,Inf(silent all-NaN output)fill_d8z_limitNaN(revert never fires), negative (reverts everything)Examples:
flow_direction_mfd(agg, p=float('nan'))-> all weights become NaN, output is silently all-NaN.snap_pour_point_d8(fa, pp, search_radius=0)-> no-op, output equals input.hand_d8(fd, fa, el, threshold=float('nan'))->fa >= NaNalways False, returns NaN everywhere.fill_d8(dem, z_limit=float('nan'))->out - dem > NaNalways False, behaves as ifz_limit=None.Expected behavior
Each parameter is validated up front. Non-finite values and out-of-range values raise a clean
ValueErrorwith a message that names the parameter.Proposed fix
flow_direction_mfd: replaceif p <= 0withif not (np.isfinite(p) and p > 0).snap_pour_point_d8: enforceisinstance(search_radius, (int, np.integer)) and search_radius >= 1.hand_*: enforcenp.isfinite(threshold)(semantic range allows 0).fill_d8: whenz_limit is not None, enforcenp.isfinite(z_limit) and z_limit >= 0.