Skip to content

Commit c674acf

Browse files
authored
Expose multi_stop_search on the .xrs DataArray and Dataset accessors (#3564)
1 parent a6357aa commit c674acf

4 files changed

Lines changed: 66 additions & 4 deletions

File tree

docs/source/reference/pathfinding.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ A* Pathfinding
2121
:toctree: _autosummary
2222

2323
xrspatial.pathfinding.a_star_search
24+
25+
Multi-Stop Routing
26+
==================
27+
.. autosummary::
28+
:toctree: _autosummary
29+
30+
xrspatial.pathfinding.multi_stop_search

xrspatial/accessor.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,10 @@ def a_star_search(self, start, goal, **kwargs):
12831283
from .pathfinding import a_star_search
12841284
return a_star_search(self._obj, start, goal, **kwargs)
12851285

1286+
def multi_stop_search(self, waypoints, **kwargs):
1287+
from .pathfinding import multi_stop_search
1288+
return multi_stop_search(self._obj, waypoints, **kwargs)
1289+
12861290
# ---- Zonal ----
12871291

12881292
def zonal_stats(self, zones, **kwargs):
@@ -2059,6 +2063,12 @@ def surface_direction(self, elevation, **kwargs):
20592063
from .surface_distance import surface_direction
20602064
return surface_direction(self._obj, elevation, **kwargs)
20612065

2066+
# ---- Pathfinding ----
2067+
2068+
def multi_stop_search(self, waypoints, **kwargs):
2069+
from .pathfinding import multi_stop_search
2070+
return multi_stop_search(self._obj, waypoints, **kwargs)
2071+
20622072
# ---- Preview ----
20632073

20642074
def preview(self, **kwargs):

xrspatial/pathfinding.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
dask = None
1616

1717
from xrspatial.cost_distance import _heap_push, _heap_pop
18+
from xrspatial.dataset_support import supports_dataset
1819
from xrspatial.utils import (
1920
_validate_raster,
2021
get_dataarray_resolution, ngjit,
@@ -1324,6 +1325,7 @@ def _optimize_waypoint_order(surface, waypoints, barriers, x, y,
13241325
return [waypoints[i] for i in order]
13251326

13261327

1328+
@supports_dataset
13271329
def multi_stop_search(surface: xr.DataArray,
13281330
waypoints: list,
13291331
barriers: list = [],
@@ -1344,8 +1346,10 @@ def multi_stop_search(surface: xr.DataArray,
13441346
13451347
Parameters
13461348
----------
1347-
surface : xr.DataArray
1348-
2-D elevation / cost surface.
1349+
surface : xr.DataArray or xr.Dataset
1350+
2-D elevation / cost surface. A Dataset routes each data
1351+
variable through the same waypoints independently and returns
1352+
a Dataset of the per-variable results.
13491353
waypoints : list of array-like
13501354
Sequence of ``(y, x)`` coordinate pairs to visit. Must contain
13511355
at least two points.
@@ -1368,9 +1372,10 @@ def multi_stop_search(surface: xr.DataArray,
13681372
13691373
Returns
13701374
-------
1371-
xr.DataArray
1375+
xr.DataArray or xr.Dataset
13721376
Cumulative path cost surface. Attributes include
13731377
``waypoint_order``, ``segment_costs``, and ``total_cost``.
1378+
A Dataset input returns a Dataset of per-variable results.
13741379
13751380
Raises
13761381
------

xrspatial/tests/test_accessor.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_dataarray_accessor_has_expected_methods(elevation):
9191
'morph_erode', 'morph_dilate', 'morph_opening', 'morph_closing',
9292
'morph_gradient', 'morph_white_tophat', 'morph_black_tophat',
9393
'proximity', 'allocation', 'direction', 'cost_distance',
94-
'a_star_search',
94+
'a_star_search', 'multi_stop_search',
9595
'zonal_stats', 'zonal_apply', 'zonal_crosstab', 'crop', 'trim',
9696
'regions',
9797
'generate_terrain', 'perlin',
@@ -118,6 +118,7 @@ def test_dataset_accessor_has_expected_methods():
118118
'morph_erode', 'morph_dilate', 'morph_opening', 'morph_closing',
119119
'morph_gradient', 'morph_white_tophat', 'morph_black_tophat',
120120
'proximity', 'allocation', 'direction', 'cost_distance',
121+
'multi_stop_search',
121122
'ndvi', 'evi', 'arvi', 'savi', 'nbr', 'sipi',
122123
'rasterize',
123124
'validate',
@@ -272,6 +273,45 @@ def test_ds_morph_gradient(elevation):
272273
xr.testing.assert_identical(result, expected)
273274

274275

276+
# ---------------------------------------------------------------------------
277+
# 4c. DataArray pathfinding — accessor matches direct call
278+
# ---------------------------------------------------------------------------
279+
280+
def test_da_a_star_search(elevation):
281+
from xrspatial.pathfinding import a_star_search
282+
start, goal = (0, 0), (9, 9)
283+
expected = a_star_search(elevation, start, goal)
284+
result = elevation.xrs.a_star_search(start, goal)
285+
xr.testing.assert_identical(result, expected)
286+
287+
288+
def test_da_multi_stop_search(elevation):
289+
from xrspatial.pathfinding import multi_stop_search
290+
waypoints = [(0, 0), (5, 5), (9, 9)]
291+
expected = multi_stop_search(elevation, waypoints)
292+
result = elevation.xrs.multi_stop_search(waypoints)
293+
xr.testing.assert_identical(result, expected)
294+
295+
296+
def test_da_multi_stop_search_kwargs(elevation):
297+
from xrspatial.pathfinding import multi_stop_search
298+
waypoints = [(0, 0), (9, 0), (9, 9)]
299+
expected = multi_stop_search(elevation, waypoints, optimize_order=True)
300+
result = elevation.xrs.multi_stop_search(waypoints, optimize_order=True)
301+
xr.testing.assert_identical(result, expected)
302+
303+
304+
def test_ds_multi_stop_search(elevation):
305+
from xrspatial.pathfinding import multi_stop_search
306+
ds = xr.Dataset({'a': elevation, 'b': elevation + 100})
307+
waypoints = [(0, 0), (5, 5), (9, 9)]
308+
expected = multi_stop_search(ds, waypoints)
309+
result = ds.xrs.multi_stop_search(waypoints)
310+
xr.testing.assert_identical(result, expected)
311+
# supports_dataset routes each variable through its own surface
312+
assert set(result.data_vars) == {'a', 'b'}
313+
314+
275315
# ---------------------------------------------------------------------------
276316
# 5. Dataset single-input — accessor matches direct call
277317
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)