Terrain & Hydrology
Purpose: terrain products whose numbers are physically meaningful. The two
recurring failure modes: unit mismatch (degree coordinates with meter
elevations silently corrupts every derivative) and unconditioned DEMs
(flow routed into spurious pits produces fragmented, fictional streams).
DEM hygiene first
| Check |
Rule |
| Surface type |
DTM (bare earth) for hydrology/slope; DSM (with canopy/buildings) for viewshed/solar. Using a DSM for watersheds routes rivers over treetops. |
| Source |
Copernicus GLO-30 > SRTM for most global work; national LiDAR DTMs when available (see point-cloud-lidar to make your own). Record source + acquisition date. |
| Nodata |
Identify the nodata value (-9999, -32768, 3.4e38) and mask it — never let it enter statistics or fill algorithms as "very deep hole". |
| Voids |
Fill data voids (interpolation from edges) BEFORE hydrological conditioning; document filled areas. |
| CRS + units |
Reproject to a projected CRS so horizontal units = vertical units (meters). Slope from a 4326 DEM without z-factor correction is the classic silent error. If staying geographic, apply a latitude-dependent z-factor — better: don't. |
Derivatives
import whitebox
wbt = whitebox.WhiteboxTools()
wbt.slope("dem.tif", "slope_deg.tif", units="degrees")
wbt.aspect("dem.tif", "aspect_deg.tif")
wbt.plan_curvature("dem.tif", "plan_curv.tif")
- Slope: state units (degrees vs percent — 45° = 100%); Horn's method
(3×3) is the standard; steeper terrain → consider resolution effects
(slope flattens as cell size grows — report cell size with every slope
statistic).
- Aspect: circular variable — never average it arithmetically; use vector
(sin/cos) averaging; flat cells have undefined aspect (mask, don't zero).
- Curvature: plan (flow convergence) vs profile (flow acceleration) —
pick per question.
- Hillshade is for cartography (see
cartography-geoviz), never analysis
input.
- Ruggedness/position: TRI, TPI (radius-dependent — report the radius),
geomorphons for landform classification.
Hydrological conditioning — order matters
voids filled → breach depressions (preferred) → fill remaining pits
→ flow direction → flow accumulation → streams → watersheds
- Breaching before filling (WhiteboxTools
BreachDepressionsLeastCost): carves through barriers (road embankments
over culverts) instead of flooding upstream areas flat. Pure fill on
flat/embanked terrain creates large artificial lakes with arbitrary flow
paths.
- Real depressions exist (karst, prairie potholes, reservoirs). If the
landscape genuinely holds water, don't condition it away — model with
explicit sink handling and say so.
- Flow direction: D8 for stream networks/watersheds (discrete,
standard); D-infinity/MFD for dispersal quantities (wetness index,
erosion) on hillslopes.
Streams and watersheds
- Stream extraction threshold (min. accumulation) is a MODELING choice:
derive from a mapped reference network (match total stream length) or
report the threshold and show two alternatives — never present one
threshold's network as "the" rivers.
- Pour point snapping: outlet coordinates rarely fall on the modeled
stream cell. Snap to the highest-accumulation cell within a search
radius (
wbt.jenson_snap_pour_points) — an unsnapped pour point yields
a tiny, wrong watershed silently.
- Verify delineation: watershed area vs authoritative basin data (±5-10%),
and the modeled network overlaid on imagery/topo maps at 3 locations.
- Wetness index (TWI), stream power (SPA): compute from conditioned DEM +
MFD accumulation; they are relative indices — don't read absolute
thresholds across regions.
Viewshed
- Use a DSM (or DTM + feature heights) — bare-earth viewsheds
overstate visibility wherever trees/buildings exist; state which surface
was used.
- Set observer height (~1.7 m person, tower height for infrastructure) and
target height explicitly; defaults differ across tools.
- Account for earth curvature + refraction beyond ~5 km
(
wbt.viewshed handles it; verify the flag).
- Deliver binary visible/not plus the observer point(s) and parameters in
the metadata; for siting problems, cumulative viewsheds from candidate
sets feed
mcda-suitability-analysis.
Tooling
WhiteboxTools (conditioning, full hydrology suite, fast) · pysheds
(lightweight Python watersheds) · richdem (derivatives) · GDAL
(gdaldem) for quick slope/hillshade · GRASS (r.watershed) for very
large DEMs (no explicit fill needed — least-cost routing).
Verification protocol
- Derivative histograms: slope > 60° over large areas or negative
accumulation = unit/nodata bug.
- Stream network overlay on imagery at 3 locations, including one flat
area (where artifacts concentrate).
- Watershed area cross-check vs authoritative basin polygons.
- Report: DEM source/date/resolution, conditioning method, flow
algorithm, stream threshold, all in the deliverable.
Pitfalls checklist
- Slope from a geographic-CRS DEM without z-factor (values ~100× off).
- DSM used for watershed delineation (rivers over treetops).
- Fill-only conditioning across road embankments → phantom lakes.
- Unsnapped pour point → 3-cell "watershed".
- Arithmetic mean of aspect (350° and 10° average to south, not north).
- Nodata treated as elevation in fill/statistics.
- One arbitrary stream threshold presented as the drainage network.
Execution contract
- Workflow: inspect DEM source, CRS, vertical units, datum, resolution, and nodata; condition terrain; derive gradients and flow; delineate products; test thresholds; validate against imagery and controls.
- Decision rules: use terrain workflows on raster elevation products, point-cloud workflows before DEM generation, and choose conditioning and flow algorithms from landscape and scale.
- Verification protocol: inspect derivative distributions, hillshade artifacts, stream overlays, watershed area, pour-point snapping, threshold sensitivity, and elevation-control residuals.
- Failure modes: reject products from DSM misuse, geographic-unit slope, vertical datum mismatch, unconditioned barriers, nodata contamination, unsnapped outlets, or resolution unsupported by source data.
- Deliverables: conditioned DEM, derivatives and hydrologic products, parameter and threshold record, CRS and vertical datum, QA maps, validation metrics, and limitations.
- Source freshness: consult the authoritative source registry before applying tool algorithms or product rules and record the checked date.
1---2name: terrain-hydrology3description: Always invoke for terrain, drainage, viewshed, or visibility analysis from elevation, even before the DEM or correct surface is chosen. Covers DTM-versus-DSM selection, slope, aspect, curvature, hillshade, conditioning, flow direction/accumulation, streams, watersheds, and catchments. Use point-cloud-lidar first only when an elevation surface must be created from LiDAR or photogrammetric points.4license: MIT5---67# Terrain & Hydrology89Purpose: terrain products whose numbers are physically meaningful. The two10recurring failure modes: **unit mismatch** (degree coordinates with meter11elevations silently corrupts every derivative) and **unconditioned DEMs**12(flow routed into spurious pits produces fragmented, fictional streams).1314## DEM hygiene first1516| Check | Rule |17|---|---|18| Surface type | **DTM** (bare earth) for hydrology/slope; **DSM** (with canopy/buildings) for viewshed/solar. Using a DSM for watersheds routes rivers over treetops. |19| Source | Copernicus GLO-30 > SRTM for most global work; national LiDAR DTMs when available (see `point-cloud-lidar` to make your own). Record source + acquisition date. |20| Nodata | Identify the nodata value (-9999, -32768, 3.4e38) and mask it — never let it enter statistics or fill algorithms as "very deep hole". |21| Voids | Fill data voids (interpolation from edges) BEFORE hydrological conditioning; document filled areas. |22| **CRS + units** | Reproject to a projected CRS so horizontal units = vertical units (meters). Slope from a 4326 DEM without z-factor correction is the classic silent error. If staying geographic, apply a latitude-dependent z-factor — better: don't. |2324## Derivatives2526```python27import whitebox28wbt = whitebox.WhiteboxTools()29wbt.slope("dem.tif", "slope_deg.tif", units="degrees")30wbt.aspect("dem.tif", "aspect_deg.tif")31wbt.plan_curvature("dem.tif", "plan_curv.tif")32```3334- Slope: state units (degrees vs percent — 45° = 100%); Horn's method35 (3×3) is the standard; steeper terrain → consider resolution effects36 (slope flattens as cell size grows — report cell size with every slope37 statistic).38- Aspect: circular variable — never average it arithmetically; use vector39 (sin/cos) averaging; flat cells have undefined aspect (mask, don't zero).40- Curvature: plan (flow convergence) vs profile (flow acceleration) —41 pick per question.42- Hillshade is for cartography (see `cartography-geoviz`), never analysis43 input.44- Ruggedness/position: TRI, TPI (radius-dependent — report the radius),45 geomorphons for landform classification.4647## Hydrological conditioning — order matters4849```50voids filled → breach depressions (preferred) → fill remaining pits51→ flow direction → flow accumulation → streams → watersheds52```5354- **Breaching before filling** (WhiteboxTools55 `BreachDepressionsLeastCost`): carves through barriers (road embankments56 over culverts) instead of flooding upstream areas flat. Pure fill on57 flat/embanked terrain creates large artificial lakes with arbitrary flow58 paths.59- Real depressions exist (karst, prairie potholes, reservoirs). If the60 landscape genuinely holds water, don't condition it away — model with61 explicit sink handling and say so.62- Flow direction: **D8** for stream networks/watersheds (discrete,63 standard); **D-infinity/MFD** for dispersal quantities (wetness index,64 erosion) on hillslopes.6566## Streams and watersheds6768- Stream extraction threshold (min. accumulation) is a MODELING choice:69 derive from a mapped reference network (match total stream length) or70 report the threshold and show two alternatives — never present one71 threshold's network as "the" rivers.72- **Pour point snapping**: outlet coordinates rarely fall on the modeled73 stream cell. Snap to the highest-accumulation cell within a search74 radius (`wbt.jenson_snap_pour_points`) — an unsnapped pour point yields75 a tiny, wrong watershed silently.76- Verify delineation: watershed area vs authoritative basin data (±5-10%),77 and the modeled network overlaid on imagery/topo maps at 3 locations.78- Wetness index (TWI), stream power (SPA): compute from conditioned DEM +79 MFD accumulation; they are relative indices — don't read absolute80 thresholds across regions.8182## Viewshed8384- Use a **DSM** (or DTM + feature heights) — bare-earth viewsheds85 overstate visibility wherever trees/buildings exist; state which surface86 was used.87- Set observer height (~1.7 m person, tower height for infrastructure) and88 target height explicitly; defaults differ across tools.89- Account for earth curvature + refraction beyond ~5 km90 (`wbt.viewshed` handles it; verify the flag).91- Deliver binary visible/not plus the observer point(s) and parameters in92 the metadata; for siting problems, cumulative viewsheds from candidate93 sets feed `mcda-suitability-analysis`.9495## Tooling9697WhiteboxTools (conditioning, full hydrology suite, fast) · `pysheds`98(lightweight Python watersheds) · `richdem` (derivatives) · GDAL99(`gdaldem`) for quick slope/hillshade · GRASS (`r.watershed`) for very100large DEMs (no explicit fill needed — least-cost routing).101102## Verification protocol1031041. Derivative histograms: slope > 60° over large areas or negative105 accumulation = unit/nodata bug.1062. Stream network overlay on imagery at 3 locations, including one flat107 area (where artifacts concentrate).1083. Watershed area cross-check vs authoritative basin polygons.1094. Report: DEM source/date/resolution, conditioning method, flow110 algorithm, stream threshold, all in the deliverable.111112## Pitfalls checklist113114- Slope from a geographic-CRS DEM without z-factor (values ~100× off).115- DSM used for watershed delineation (rivers over treetops).116- Fill-only conditioning across road embankments → phantom lakes.117- Unsnapped pour point → 3-cell "watershed".118- Arithmetic mean of aspect (350° and 10° average to south, not north).119- Nodata treated as elevation in fill/statistics.120- One arbitrary stream threshold presented as the drainage network.121122## Execution contract123124- **Workflow:** inspect DEM source, CRS, vertical units, datum, resolution, and nodata; condition terrain; derive gradients and flow; delineate products; test thresholds; validate against imagery and controls.125- **Decision rules:** use terrain workflows on raster elevation products, point-cloud workflows before DEM generation, and choose conditioning and flow algorithms from landscape and scale.126- **Verification protocol:** inspect derivative distributions, hillshade artifacts, stream overlays, watershed area, pour-point snapping, threshold sensitivity, and elevation-control residuals.127- **Failure modes:** reject products from DSM misuse, geographic-unit slope, vertical datum mismatch, unconditioned barriers, nodata contamination, unsnapped outlets, or resolution unsupported by source data.128- **Deliverables:** conditioned DEM, derivatives and hydrologic products, parameter and threshold record, CRS and vertical datum, QA maps, validation metrics, and limitations.129- **Source freshness:** consult [the authoritative source registry](references/authoritative-sources.md) before applying tool algorithms or product rules and record the checked date.