Skip to content

Commit 34a6cb3

Browse files
committed
Add standalone basin module, fix BoundaryStore temp file leaks
Extract basin delineation from watershed.py into its own basin.py module with a basin() public function. The old basins() stays as a backward-compatible wrapper. Add basin to __init__.py and both xarray accessors. Add BoundarySnapshot to _boundary_store.py -- a read-only in-memory copy of converged boundary strips. BoundaryStore.snapshot() copies strip data to plain numpy arrays and closes the underlying memmap temp files. Apply this pattern across all hydrology dask backends (flow_accumulation, watershed, fill, stream_order) so temp directories are cleaned up before the lazy dask result is returned. Previously, BoundaryStore objects captured in map_blocks closures kept temp files alive indefinitely.
1 parent a601433 commit 34a6cb3

File tree

10 files changed

+678
-410
lines changed

10 files changed

+678
-410
lines changed

xrspatial/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from xrspatial.terrain_metrics import tri # noqa
4747
from xrspatial.polygonize import polygonize # noqa
4848
from xrspatial.viewshed import viewshed # noqa
49+
from xrspatial.basin import basin # noqa
4950
from xrspatial.watershed import basins # noqa
5051
from xrspatial.watershed import watershed # noqa
5152
from xrspatial.zonal import apply as zonal_apply # noqa

xrspatial/_boundary_store.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,48 @@ def close(self):
9797
def __del__(self):
9898
self.close()
9999

100+
def snapshot(self):
101+
"""Return a lightweight in-memory copy and close this store.
102+
103+
The returned ``BoundarySnapshot`` has the same ``.get()``
104+
interface but holds plain numpy arrays instead of memmaps,
105+
so no temp files remain referenced.
106+
"""
107+
snap = BoundarySnapshot(self)
108+
self.close()
109+
return snap
110+
100111
def __enter__(self):
101112
return self
102113

103114
def __exit__(self, *exc):
104115
self.close()
116+
117+
118+
class BoundarySnapshot:
119+
"""Read-only in-memory copy of converged boundary strips.
120+
121+
Created via ``BoundaryStore.snapshot()``; exposes the same
122+
``.get(side, iy, ix)`` interface so callers need no changes.
123+
"""
124+
125+
def __init__(self, store):
126+
self._cum_x = store._cum_x
127+
self._cum_y = store._cum_y
128+
# Copy memmap data to plain numpy arrays
129+
for name in ('top', 'bottom', 'left', 'right'):
130+
src = getattr(store, f'_{name}')
131+
setattr(self, f'_{name}', np.array(src))
132+
133+
def get(self, side, iy, ix):
134+
"""Return a numpy view of the boundary strip for tile (iy, ix)."""
135+
if side == 'top':
136+
return self._top[iy, self._cum_x[ix]:self._cum_x[ix + 1]]
137+
elif side == 'bottom':
138+
return self._bottom[iy, self._cum_x[ix]:self._cum_x[ix + 1]]
139+
elif side == 'left':
140+
return self._left[ix, self._cum_y[iy]:self._cum_y[iy + 1]]
141+
elif side == 'right':
142+
return self._right[ix, self._cum_y[iy]:self._cum_y[iy + 1]]
143+
else:
144+
raise ValueError(f"Unknown side: {side!r}")

xrspatial/accessor.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ def watershed(self, pour_points, **kwargs):
6767
from .watershed import watershed
6868
return watershed(self._obj, pour_points, **kwargs)
6969

70+
def basin(self, **kwargs):
71+
from .basin import basin
72+
return basin(self._obj, **kwargs)
73+
7074
def basins(self, **kwargs):
7175
from .watershed import basins
7276
return basins(self._obj, **kwargs)
@@ -293,6 +297,10 @@ def watershed(self, pour_points, **kwargs):
293297
from .watershed import watershed
294298
return watershed(self._obj, pour_points, **kwargs)
295299

300+
def basin(self, **kwargs):
301+
from .basin import basin
302+
return basin(self._obj, **kwargs)
303+
296304
def basins(self, **kwargs):
297305
from .watershed import basins
298306
return basins(self._obj, **kwargs)

0 commit comments

Comments
 (0)