Skip to content

Commit 7ebe97e

Browse files
committed
Update docs and README with multi-backend pathfinding support
Add backend support table to docstring and user guide notebook, dask out-of-core example, and CPU-fallback symbol in README matrix.
1 parent 8d1fd01 commit 7ebe97e

File tree

3 files changed

+32
-12
lines changed

3 files changed

+32
-12
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e
131131

132132
#### Supported Spatial Functions with Supported Inputs
133133

134+
✅ = native backend    🔄 = accepted (CPU fallback)
135+
134136
-------
135137

136138
### **Classification**
@@ -183,7 +185,7 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e
183185

184186
| Name | Description | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
185187
|:----------:|:------------|:----------------------:|:--------------------:|:-------------------:|:------:|
186-
| [A* Pathfinding](xrspatial/pathfinding.py) | Finds the least-cost path between two cells on a cost surface | ✅️ | | | |
188+
| [A* Pathfinding](xrspatial/pathfinding.py) | Finds the least-cost path between two cells on a cost surface | ✅️ | | 🔄 | 🔄 |
187189

188190
----------
189191

docs/source/user_guide/pathfinding.ipynb

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,7 @@
33
{
44
"cell_type": "markdown",
55
"metadata": {},
6-
"source": [
7-
"## Pathfinding\n",
8-
"\n",
9-
"Xarray-spatial provides A\\* pathfinding for finding optimal routes through raster surfaces.\n",
10-
"Paths can be computed using geometric distance alone (unweighted) or weighted by a\n",
11-
"friction/cost surface, which makes the algorithm find the *least-cost* path rather\n",
12-
"than the geometrically shortest one.\n",
13-
"\n",
14-
"- [Unweighted A\\*](#Unweighted-A*): find the shortest geometric path through a line network\n",
15-
"- [Weighted A\\*](#Weighted-A*): find the least-cost path through a friction surface"
16-
]
6+
"source": "## Pathfinding\n\nXarray-spatial provides A\\* pathfinding for finding optimal routes through raster surfaces.\nPaths can be computed using geometric distance alone (unweighted) or weighted by a\nfriction/cost surface, which makes the algorithm find the *least-cost* path rather\nthan the geometrically shortest one.\n\nAll four backends are supported:\n\n| Backend | Strategy |\n|---------|----------|\n| **NumPy** | Numba-jitted kernel (fast, in-memory) |\n| **Dask** | Sparse Python A\\* with LRU chunk cache — loads chunks on demand so the full grid never needs to fit in RAM |\n| **CuPy** | CPU fallback (transfers to numpy, runs the numba kernel, transfers back) |\n| **Dask+CuPy** | Same sparse A\\* as Dask, with automatic cupy-to-numpy chunk conversion |\n\n**Note:** `snap_start` and `snap_goal` are not supported with Dask-backed arrays\nbecause the brute-force nearest-pixel scan is O(h*w). Ensure the start and goal\npixels are valid before calling `a_star_search`.\n\n- [Unweighted A\\*](#Unweighted-A*): find the shortest geometric path through a line network\n- [Weighted A\\*](#Weighted-A*): find the least-cost path through a friction surface\n- [Dask (out-of-core)](#Dask-(out-of-core)): pathfinding on datasets that don't fit in RAM"
177
},
188
{
199
"cell_type": "markdown",
@@ -272,6 +262,18 @@
272262
"- Red Blob Games — A\\* implementation: https://www.redblobgames.com/pathfinding/a-star/implementation.html\n",
273263
"- Cost distance (weighted proximity): `xrspatial.cost_distance`"
274264
]
265+
},
266+
{
267+
"cell_type": "markdown",
268+
"source": "### Dask (out-of-core)\n\nFor rasters that don't fit in memory, wrap your data in a Dask array.\nA\\* explores only the pixels it needs (typically O(path_length) to\nO(path_length^2)), loading chunks on demand through an LRU cache.\nThe output is a lazy Dask array of the same chunk structure.",
269+
"metadata": {}
270+
},
271+
{
272+
"cell_type": "code",
273+
"source": "import dask.array as da\n\n# Re-use the terrain and friction from above, but as dask arrays\nterrain_dask = terrain.copy()\nterrain_dask.data = da.from_array(terrain.data, chunks=(200, 300))\n\nfriction_dask = friction.copy()\nfriction_dask.data = da.from_array(friction.data, chunks=(200, 300))\n\n# Run A* — chunks are loaded on demand, not all at once\npath_dask = a_star_search(terrain_dask, start_coord, goal_coord, friction=friction_dask)\nprint(f\"Result type: {type(path_dask.data)}\")\nprint(f\"Chunks: {path_dask.data.chunks}\")\n\n# Compute to verify it matches the in-memory result\ndask_cost = float(path_dask.values[gpy, gpx])\nprint(f\"Dask A* cost at goal: {dask_cost:.4f}\")\nprint(f\"NumPy A* cost at goal: {astar_val:.4f}\")\nprint(f\"Match: {np.isclose(dask_cost, astar_val, rtol=1e-5)}\")",
274+
"metadata": {},
275+
"execution_count": null,
276+
"outputs": []
275277
}
276278
],
277279
"metadata": {

xrspatial/pathfinding.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,22 @@ def a_star_search(surface: xr.DataArray,
514514
The output is an equal-sized ``xr.DataArray`` with NaN for non-path
515515
pixels and the accumulated cost at each path pixel.
516516
517+
**Backend support**
518+
519+
============= ===========================================================
520+
Backend Strategy
521+
============= ===========================================================
522+
NumPy Numba-jitted kernel (fast, in-memory)
523+
Dask Sparse Python A* with LRU chunk cache — loads chunks on
524+
demand so the full grid never needs to fit in RAM
525+
CuPy CPU fallback (transfers to numpy, runs numba kernel,
526+
transfers back)
527+
Dask + CuPy Same sparse A* as Dask, with cupy→numpy chunk conversion
528+
============= ===========================================================
529+
530+
``snap_start`` and ``snap_goal`` are not supported with Dask-backed
531+
arrays (raises ``ValueError``).
532+
517533
Parameters
518534
----------
519535
surface : xr.DataArray

0 commit comments

Comments
 (0)