| """Pre-compute TWI (Topographic Wetness Index) and HAND (Height Above |
| Nearest Drainage) for the cached NYC DEM. |
| |
| These are standard hydrology indices used by InfoWorks ICM, HEC-RAS, |
| and the Forest Service / USGS. They give the microtopo specialist new |
| per-address signal beyond elevation percentile + relief: |
| |
| - **TWI** = ln(specific_catchment_area / tan(slope)). HIGH values mean |
| a cell is saturation-prone (large upslope drainage area + low slope = |
| water accumulates here). |
| - **HAND** = vertical distance from each cell to the nearest channel. |
| LOW values (sub-meter) mean the address sits at or near drainage |
| level — flood-vulnerable. HIGH values mean it's perched on dry ground. |
| |
| Output: data/twi.tif and data/hand.tif, aligned with data/nyc_dem_30m.tif. |
| |
| Run: python scripts/compute_hydrology_indices.py |
| """ |
| from __future__ import annotations |
|
|
| import sys |
| import warnings |
| from pathlib import Path |
|
|
| warnings.filterwarnings("ignore") |
|
|
| ROOT = Path(__file__).resolve().parent.parent |
| DEM_PATH = ROOT / "data" / "nyc_dem_30m.tif" |
| TWI_OUT = ROOT / "data" / "twi.tif" |
| HAND_OUT = ROOT / "data" / "hand.tif" |
|
|
|
|
| def main() -> int: |
| if not DEM_PATH.exists(): |
| print(f"missing {DEM_PATH}; run scripts/fetch_nyc_dem.py first", |
| file=sys.stderr) |
| return 1 |
| if TWI_OUT.exists() and HAND_OUT.exists(): |
| print(f"already exist: {TWI_OUT.name}, {HAND_OUT.name}", file=sys.stderr) |
| return 0 |
|
|
| import whitebox_workflows as wbw |
| wbe = wbw.WbEnvironment() |
| wbe.verbose = True |
| wbe.working_directory = str(ROOT / "data") |
|
|
| print("loading DEM...", file=sys.stderr) |
| dem = wbe.read_raster(str(DEM_PATH)) |
|
|
| |
| |
| print("filling depressions (Wang & Liu)...", file=sys.stderr) |
| dem_filled = wbe.fill_depressions_wang_and_liu(dem) |
|
|
| |
| print("D-infinity flow accumulation...", file=sys.stderr) |
| sca = wbe.dinf_flow_accum(dem_filled, out_type="specific contributing area", |
| log_transform=False) |
|
|
| |
| print("slope...", file=sys.stderr) |
| slope = wbe.slope(dem_filled, units="degrees") |
|
|
| |
| print("TWI...", file=sys.stderr) |
| twi = wbe.wetness_index(sca, slope) |
| wbe.write_raster(twi, str(TWI_OUT.name), compress=True) |
|
|
| |
| print("D8 flow accumulation for stream extraction...", file=sys.stderr) |
| d8_accum = wbe.d8_flow_accum(dem_filled, out_type="cells", |
| log_transform=False) |
|
|
| |
| |
| |
| print("extracting streams...", file=sys.stderr) |
| streams = wbe.extract_streams(d8_accum, threshold=1500.0) |
|
|
| |
| print("HAND (elevation_above_stream)...", file=sys.stderr) |
| hand = wbe.elevation_above_stream(dem_filled, streams) |
| wbe.write_raster(hand, str(HAND_OUT.name), compress=True) |
|
|
| print(f"\nwrote:\n {TWI_OUT} ({TWI_OUT.stat().st_size // 1024} KB)\n" |
| f" {HAND_OUT} ({HAND_OUT.stat().st_size // 1024} KB)", |
| file=sys.stderr) |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|