Skip to content

Commit a676430

Browse files
authored
Add surface_distance module: 3D terrain distance via multi-source Dijkstra (#918)
* Add surface_distance module: 3D terrain distance via multi-source Dijkstra Adds surface_distance(), surface_allocation(), and surface_direction() which compute distance, nearest-target allocation, and compass direction along a 3D terrain surface (DEM). Unlike proximity (flat 2D Euclidean) or cost_distance (scalar friction), edge costs account for elevation relief: sqrt(horizontal_dist² + dz²). Backends: NumPy (Dijkstra), CuPy (parallel Bellman-Ford relaxation), Dask+NumPy bounded (map_overlap) and unbounded (iterative boundary Dijkstra), Dask+CuPy. Geodesic mode (lat/lon → great-circle horizontal distances) supported for NumPy. Includes 32 tests, ASV benchmarks, __init__ exports, and .xrs accessor methods on both DataArray and Dataset. * Add surface distance functions to README proximity table
1 parent c2ef6cc commit a676430

File tree

6 files changed

+2172
-0
lines changed

6 files changed

+2172
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e
206206
| [Cost Distance](xrspatial/cost_distance.py) | Computes minimum accumulated cost to the nearest source through a friction surface | ✅️ || ✅️ | ✅️ |
207207
| [Direction](xrspatial/proximity.py) | Computes the direction from each cell to the nearest source feature | ✅️ || ✅️ | ✅️ |
208208
| [Proximity](xrspatial/proximity.py) | Computes the distance from each cell to the nearest source feature | ✅️ || ✅️ | ✅️ |
209+
| [Surface Distance](xrspatial/surface_distance.py) | Computes distance along the 3D terrain surface to the nearest source | ✅️ || ✅️ | ✅️ |
210+
| [Surface Allocation](xrspatial/surface_distance.py) | Assigns each cell to the nearest source by terrain surface distance | ✅️ || ✅️ | ✅️ |
211+
| [Surface Direction](xrspatial/surface_distance.py) | Computes compass direction to the nearest source by terrain surface distance | ✅️ || ✅️ | ✅️ |
209212

210213
--------
211214

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import numpy as np
2+
3+
from xrspatial.surface_distance import (
4+
surface_distance, surface_allocation, surface_direction,
5+
)
6+
7+
from .common import get_xr_dataarray
8+
9+
10+
class SurfaceDistance:
11+
params = ([100, 300, 1000], ["numpy", "dask"])
12+
param_names = ("nx", "type")
13+
14+
def setup(self, nx, type):
15+
ny = nx // 2
16+
self.agg = get_xr_dataarray((ny, nx), type, is_int=True)
17+
self.elev = get_xr_dataarray((ny, nx), type)
18+
19+
def time_surface_distance(self, nx, type):
20+
surface_distance(self.agg, self.elev)
21+
22+
def time_surface_allocation(self, nx, type):
23+
surface_allocation(self.agg, self.elev)
24+
25+
def time_surface_direction(self, nx, type):
26+
surface_direction(self.agg, self.elev)

xrspatial/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
from xrspatial.proximity import manhattan_distance # noqa
3333
from xrspatial.proximity import proximity # noqa
3434
from xrspatial.slope import slope # noqa
35+
from xrspatial.surface_distance import surface_allocation # noqa
36+
from xrspatial.surface_distance import surface_direction # noqa
37+
from xrspatial.surface_distance import surface_distance # noqa
3538
from xrspatial.terrain import generate_terrain # noqa
3639
from xrspatial.polygonize import polygonize # noqa
3740
from xrspatial.viewshed import viewshed # noqa

xrspatial/accessor.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ def cost_distance(self, friction, **kwargs):
9797
from .cost_distance import cost_distance
9898
return cost_distance(self._obj, friction, **kwargs)
9999

100+
def surface_distance(self, elevation, **kwargs):
101+
from .surface_distance import surface_distance
102+
return surface_distance(self._obj, elevation, **kwargs)
103+
104+
def surface_allocation(self, elevation, **kwargs):
105+
from .surface_distance import surface_allocation
106+
return surface_allocation(self._obj, elevation, **kwargs)
107+
108+
def surface_direction(self, elevation, **kwargs):
109+
from .surface_distance import surface_direction
110+
return surface_direction(self._obj, elevation, **kwargs)
111+
100112
# ---- Pathfinding ----
101113

102114
def a_star_search(self, start, goal, **kwargs):
@@ -259,6 +271,18 @@ def cost_distance(self, friction, **kwargs):
259271
from .cost_distance import cost_distance
260272
return cost_distance(self._obj, friction, **kwargs)
261273

274+
def surface_distance(self, elevation, **kwargs):
275+
from .surface_distance import surface_distance
276+
return surface_distance(self._obj, elevation, **kwargs)
277+
278+
def surface_allocation(self, elevation, **kwargs):
279+
from .surface_distance import surface_allocation
280+
return surface_allocation(self._obj, elevation, **kwargs)
281+
282+
def surface_direction(self, elevation, **kwargs):
283+
from .surface_distance import surface_direction
284+
return surface_direction(self._obj, elevation, **kwargs)
285+
262286
# ---- Multispectral (band mapping via kwargs) ----
263287

264288
def ndvi(self, nir, red, **kwargs):

0 commit comments

Comments
 (0)