Skip to content

pathfinding: multi_stop_search drops input attrs, a_star_search lacks Dataset support, type hint and default fixes #3644

Description

@brendancol

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiAPI design and consistencyarea:pathfindingArea: pathfindingbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions