An API-consistency audit of xrspatial/pathfinding.py (public functions: a_star_search, multi_stop_search) found four MEDIUM issues. They are bundled here because each fix is a few lines and none changes behavior for existing correct code.
1. multi_stop_search drops the input raster's attrs; a_star_search keeps them
a_star_search returns attrs=surface.attrs. multi_stop_search replaces them with its routing metadata, so crs, res, units, etc. are lost when you route through waypoints:
import numpy as np, xarray as xr
from xrspatial import a_star_search, multi_stop_search
data = np.array([[0., 1., 0., 0.],
[1., 1., 0., 0.],
[0., 1., 2., 2.],
[1., 0., 2., 0.],
[0., 2., 2., 2.]])
agg = xr.DataArray(data, dims=['y', 'x'],
attrs={'res': (1.0, 1.0), 'crs': 4326, 'units': 'm'})
agg['x'] = np.arange(4.); agg['y'] = np.arange(4., -1., -1.)
sorted(a_star_search(agg, (3, 0), (0, 1), barriers=[0]).attrs)
# ['crs', 'res', 'units']
sorted(multi_stop_search(agg, [(3, 0), (2, 1), (0, 1)], barriers=[0]).attrs)
# ['segment_costs', 'total_cost', 'waypoint_order']
Fix: merge, i.e. keep surface.attrs and add waypoint_order / segment_costs / total_cost on top.
2. a_star_search is missing @supports_dataset
Its sibling in the same module has it, and so do the analogous functions elsewhere (cost_distance, proximity, surface_distance, the hydro family). Result:
ds = xr.Dataset({'elev': agg})
multi_stop_search(ds, [(3, 0), (2, 1), (0, 1)], barriers=[0]) # returns Dataset
a_star_search(ds, (3, 0), (0, 1), barriers=[0])
# TypeError: a_star_search(): `surface` must be an xarray.DataArray, got ...Dataset
The Dataset accessor (accessor.py) exposes multi_stop_search but not a_star_search for the same reason. Fix: add the decorator and the accessor method.
3. Wrong type hints and docstring errors
start and goal are annotated Union[tuple, list, np.array]. np.array is a function, not a type; this should be np.ndarray.
x and y are annotated Optional[str] but None is not actually accepted (a_star_search(agg, start, goal, x=None, y=None) raises ValueError: 'surface.coords' should be named as coordinates:(None, None)). Siblings (cost_distance, proximity, surface_distance, balanced_allocation) all annotate plain str.
friction: xr.DataArray = None should be Optional[xr.DataArray].
- The
surface docstring entry says "2D array of values to bin", a leftover from some histogram-style function. The connectivity entry has no description at all.
4. Mutable default barriers: list = []
Both public functions use a mutable list literal as default. It is not mutated today (barriers = np.asarray(barriers) rebinds), so there is no live bug, but it is the standard Python trap and siblings use a None sentinel (cost_distance(target_values: list = None)). Fix: Optional[list] = None with the empty-list substitution inside.
Not fixed, documented for the record
The first parameter is named surface here, raster in cost_distance/proximity/surface_distance, and agg in the terrain/focal modules. Library naming is split three ways, so renaming pathfinding alone would churn a stable signature without converging anything. Left as a note in the sweep state.
Repro above executed on 2026-07-08 against main (numpy backend; Dataset and attrs checks also exercised on the dask path in the PR tests).
An API-consistency audit of
xrspatial/pathfinding.py(public functions:a_star_search,multi_stop_search) found four MEDIUM issues. They are bundled here because each fix is a few lines and none changes behavior for existing correct code.1.
multi_stop_searchdrops the input raster's attrs;a_star_searchkeeps thema_star_searchreturnsattrs=surface.attrs.multi_stop_searchreplaces them with its routing metadata, socrs,res,units, etc. are lost when you route through waypoints:Fix: merge, i.e. keep
surface.attrsand addwaypoint_order/segment_costs/total_coston top.2.
a_star_searchis missing@supports_datasetIts sibling in the same module has it, and so do the analogous functions elsewhere (
cost_distance,proximity,surface_distance, the hydro family). Result:The Dataset accessor (
accessor.py) exposesmulti_stop_searchbut nota_star_searchfor the same reason. Fix: add the decorator and the accessor method.3. Wrong type hints and docstring errors
startandgoalare annotatedUnion[tuple, list, np.array].np.arrayis a function, not a type; this should benp.ndarray.xandyare annotatedOptional[str]butNoneis not actually accepted (a_star_search(agg, start, goal, x=None, y=None)raisesValueError: 'surface.coords' should be named as coordinates:(None, None)). Siblings (cost_distance,proximity,surface_distance,balanced_allocation) all annotate plainstr.friction: xr.DataArray = Noneshould beOptional[xr.DataArray].surfacedocstring entry says "2D array of values to bin", a leftover from some histogram-style function. Theconnectivityentry has no description at all.4. Mutable default
barriers: list = []Both public functions use a mutable list literal as default. It is not mutated today (
barriers = np.asarray(barriers)rebinds), so there is no live bug, but it is the standard Python trap and siblings use aNonesentinel (cost_distance(target_values: list = None)). Fix:Optional[list] = Nonewith the empty-list substitution inside.Not fixed, documented for the record
The first parameter is named
surfacehere,rasterin cost_distance/proximity/surface_distance, andaggin the terrain/focal modules. Library naming is split three ways, so renaming pathfinding alone would churn a stable signature without converging anything. Left as a note in the sweep state.Repro above executed on 2026-07-08 against main (numpy backend; Dataset and attrs checks also exercised on the dask path in the PR tests).