Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

floodpath

PyPI versiontestsPython 3.10+License: MIT

A modular Python pipeline for HAND-based flood inundation and damage estimation.

floodpath chains together everything you need to go from a (lat, lon) point to a per-cell flood damage estimate. As of v0.2 the pipeline is end-to-end physically grounded — it accepts precipitation directly, runs SCS-CN runoff partitioning + Manning channel hydraulics, and produces a rainfall-driven flood map (the static-water-level path remains supported):

 Precipitation (uniform synthetic, or your own grid)
↓ SCS-CN
runoff Q (mm/cell)
↓ flow accumulation (pyflwdir)
accumulated upstream volume + peak discharge
↓ Manning normal-depth at streams
stream water levels h (m)
↓ HAND broadcast (per-stream → per-cell)
DEM → flow direction → streams → HAND → flood depth (m) per cell
↓
+ GHSL built-up + WorldPop + OSM buildings
↓
+ JRC Huizinga 2017 depth-damage curves
↓
→ 2D damage map

Each layer is a small, well-tested module. Plug in the parts you need, swap in your own data, or extend with new sources.

Install

pip install floodpath

floodpath depends on rasterio and pyflwdir, both of which install cleanly via pip on Linux. On macOS arm64, conda-forge is the smoother path:

conda install -c conda-forge rasterio pyflwdir numpy
pip install floodpath

Quickstart — static water-level scenario

The original v0.1 pipeline. Useful as a what-if tool ("if water rose to 5 m everywhere, where would it go?").

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.damageimport (
JRC_AFRICA_RESIDENTIAL,
compute_inundation_depth,
compute_damage,
)
# 1. Fetch a DEM patch (Copernicus GLO-30, ~30 m, no auth)dem=get_dem(lat=11.805, lon=37.5625, buffer_deg=0.0375)
# 2. Terrain hydrologygrid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 3. Exposure (GHS-BUILT-S, ~90 m built-up surface per cell)exposure=get_ghsl_built(lat=11.805, lon=37.5625, buffer_deg=0.0375, epoch=2020)
# 4. Damage at a 5 m water leveldepth=compute_inundation_depth(hand, water_level=5.0)
damage=compute_damage(depth, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Total damaged built-up: {damage.values.sum():,.0f} m²")

Quickstart — rainfall-driven scenario (new in v0.2)

Drives the same HAND machinery from a real rainfall event. Replaces the user-supplied "5 m water level" with a per-cell water depth field computed from precipitation → SCS-CN → flow accumulation → Manning normal-depth.

fromfloodpath.demimportget_demfromfloodpath.hydrologyimportbuild_flow_grid, extract_streams, compute_handfromfloodpath.exposureimportget_ghsl_builtfromfloodpath.landuseimportget_worldcover_landuse, landuse_to_roughnessfromfloodpath.soilimportget_soilgrids_texture, texture_to_hsgfromfloodpath.precipimportuniform_precip_likefromfloodpath.runoffimportcompute_curve_number, apply_scs_cnfromfloodpath.routingimport (
accumulate_runoff,
peak_discharge,
compute_water_level,
compute_rainfall_inundation,
)
fromfloodpath.damageimportJRC_AFRICA_RESIDENTIAL, compute_damageLAT, LON, BUF=11.805, 37.5625, 0.0375# 1. Terrain + hydrologydem=get_dem(lat=LAT, lon=LON, buffer_deg=BUF)
grid=build_flow_grid(dem)
streams=extract_streams(grid, threshold=200)
hand=compute_hand(grid, streams, dem)
# 2. Land surface inputslanduse=get_worldcover_landuse(lat=LAT, lon=LON, buffer_deg=BUF, year=2021)
roughness=landuse_to_roughness(landuse)
texture=get_soilgrids_texture(lat=LAT, lon=LON, buffer_deg=BUF)
hsg=texture_to_hsg(texture)
exposure=get_ghsl_built(lat=LAT, lon=LON, buffer_deg=BUF, epoch=2020)
# 3. Rainfall → runoff (any PrecipGrid works; uniform 100 mm here)cn=compute_curve_number(landuse, hsg)
precip=uniform_precip_like(cn, depth_mm=100.0)
runoff=apply_scs_cn(cn, precip)
# 4. Steady-state routing → discharge → Manning water levelacc=accumulate_runoff(runoff, grid)
discharge=peak_discharge(acc, duration_s=6*3600.0) # 6-hour design stormwater_level=compute_water_level(discharge, roughness, grid, streams, dem)
# 5. Rainfall-driven flood + damageflood=compute_rainfall_inundation(water_level, hand, grid, streams)
damage=compute_damage(flood, exposure, JRC_AFRICA_RESIDENTIAL)
print(f"Flooded fraction: {100*flood.flooded_fraction():.1f}% of patch")
print(f"Outlet peak Q: {discharge.outlet_peak():.1f} m³/s")
print(f"Total rainfall-driven damage: {damage.values.sum():,.0f} m² built-up")

Quickstart — interactive outlet selection (ArcSWAT-style)

For an ArcSWAT-style workflow — fetch a DEM patch, render flow accumulation and the stream network on a slippy map, then click the pixel you want to use as the watershed outlet — install the optional interactive extras and use floodpath.interactive.pick_outlet from a Jupyter notebook:

pip install floodpath[interactive] # adds leafmap, ipyleaflet, matplotlib
fromfloodpath.interactiveimportpick_outletpicker=pick_outlet(lat=11.805, lon=37.5625, buffer_deg=0.0375)
picker.show() # renders the leafmap widget — click a pixel on a stream# In a follow-up cell, after clicking:selection=picker.selectionprint(f"Outlet snapped to: {selection.outlet}")
print(f"Upstream basin: {selection.basin.cell_count} cells")

The picker auto-snaps each click to the nearest downstream stream cell (via D8 trace) and overlays the delineated upstream basin. The returned OutletSelection bundles the snapped outlet, the basin mask, and the DEM / flow grid / streams used to compute it — feed those straight into the rest of the pipeline:

fromfloodpath.hydrologyimportcompute_handfromfloodpath.damageimportcompute_inundation_depth, compute_damage, JRC_AFRICA_RESIDENTIALhand=compute_hand(
grid=selection.flow_grid,
streams=selection.streams,
dem=selection.dem,
)
depth=compute_inundation_depth(hand, water_level=5.0)
# ...

For headless / scripted use (no map widget), call picker.select(lat, lon) directly — it returns the same OutletSelection.

For an end-to-end demo that wires the picker into the full pipeline (DEM → flow → streams → outlet → HAND → flood → population affected → damage), see examples/pick_outlet.ipynb on GitHub.

Modules

ModuleSourceWhat it provides
floodpath.demCopernicus GLO-30 (AWS Open Data, COG)Elevation patch around any (lat, lon)
floodpath.hydrologyderived from DEM via pyflwdirFlow direction + accumulation, stream networks (with Strahler order), basin delineation, snap-to-stream, HAND
floodpath.exposureGHSL R2023A, WorldPop, OpenStreetMap (Overpass)Built-up surface, population, building footprints
floodpath.landuseESA WorldCover (10 m, AWS Open Data, COG)11-class land-cover raster (2020 v100, 2021 v200), Manning's roughness derivation
floodpath.soilISRIC SoilGrids 2.0 (250 m, COG)Sand/silt/clay topsoil composition + USDA texture-triangle classification + NEH 630 Ch7 hydrologic soil group (A/B/C/D)
floodpath.precipSynthetic uniform (real fetchers later: ERA5 / IMERG / CHIRPS)Precipitation depth raster (mm) — pluggable input to the runoff equation
floodpath.runoffNEH 630 Ch9 + Ch10 + landuse + HSG + precipSCS Curve Number raster + SCS-CN runoff equation Q = (P-0.2S)²/(P+0.8S)
floodpath.routingrunoff + flow direction (pyflwdir) + roughness + HANDHydrologic routing (accumulation + peak discharge) + hydraulic closure (Manning normal-depth at streams, Leopold-Maddock width) + rainfall-driven HAND flood depth
floodpath.damageJRC Huizinga 2017 + DEM/HAND/GHSL/routingPer-cell flood depth and damage in m² of built-up surface — accepts either a static water-level scenario or a rainfall-driven flood from floodpath.routing
floodpath.interactiveleafmap + ipyleaflet + matplotlib (optional extras)Jupyter-based ArcSWAT-style outlet picker: hillshade + streams + click-to-snap + basin delineation

Depth-damage curves

floodpath.damage ships 26 continental-average curves from JRC's Huizinga et al. 2017 Global flood depth-damage functions report — covering residential, commerce, industry, transport, infrastructure and agriculture asset classes across up to six continents.

fromfloodpath.damageimportjrc_curvecurve=jrc_curve(asset_class="residential", continent="north_america")
fractions=curve(depths_m=np.array([0.0, 0.5, 1.0, 2.0, 5.0]))

Coverage gaps from the original report are preserved: jrc_curve("commerce", "africa") raises KeyError rather than fabricating data.

Test fixtures and offline development

floodpath ships with a small set of committed test fixtures (Robit Bata watershed, northern Ethiopia) so contributors can iterate without hitting the network:

pytest -m "not integration"# ~0.1 s, no network
pytest # full suite, ~1 minute (downloads ~25 MB)

The fixtures (committed binaries totalling ~330 KB) are regenerated by scripts under tests/fixtures/_generate_*.py whenever an upstream source changes.

Status

floodpath is beta (v0.2). The pipeline produces sensible flood/damage maps for both:

  • Static water-level scenarios (the v0.1 path)
  • Rainfall-driven scenarios with SCS-CN runoff partitioning, steady-state flow accumulation, and Manning normal-depth at stream cells (new in v0.2)

It does not yet model:

  • Time-resolved hydraulics or hydrographs (no unit hydrograph or kinematic-wave routing — steady-state only; planned for v0.3)
  • 2D shallow-water dynamics or Saint-Venant solver (not planned)
  • Subgrid stochastic uncertainty / ensemble flood mapping (not planned)

The steady-state routing assumption is appropriate for small basins under intense storms; larger basins where peak attenuation along the channel matters will see biased-high peak Q and biased-high flood depths. If you need full physics, look at LISFLOOD-FP, HEC-RAS 2D, or WFlow.

What's new in v0.2.1

  • New optional module floodpath.interactive — ArcSWAT-style outlet picker on a leafmap widget. pick_outlet(lat, lon) shows a hillshaded DEM + Strahler-coloured stream network on a Carto Positron basemap; clicks auto-snap downstream to the nearest stream cell, the upstream basin is delineated and overlaid, and the marker is draggable for fine-tuning. Install via pip install floodpath[interactive].
  • New floodpath.hydrology.snap_to_stream helper underpins the picker; surfaces outside-DEM-bbox clicks as a clean ValueError so callers handle one branch.
  • New end-to-end example notebook at examples/pick_outlet.ipynb, walking the full DEM → flow → streams → outlet → HAND → flood → population → damage chain at Kigali, Rwanda.

What's new in v0.2

  • New modules: floodpath.landuse (ESA WorldCover + Manning's roughness), floodpath.soil (SoilGrids 2.0 + NEH 630 Ch7 hydrologic soil group), floodpath.precip (uniform synthetic; pluggable for any user-supplied grid), floodpath.runoff (NEH 630 Ch9 SCS Curve Number + Ch10 SCS-CN equation), floodpath.routing (steady-state hydrologic + Manning hydraulic closure)
  • compute_damage now accepts either kind of inundation depth (static or rainfall-driven) — same numerics, different scenario metadata
  • 332 offline unit tests + 16 integration tests; smoke test runs 19 stages from DEM through rainfall-driven damage

Citation

If you use floodpath in academic work, please cite the underlying datasets too:

  • DEM: Copernicus DEM GLO-30, ESA / Airbus, doi:10.5270/ESA-c5d3d65
  • Built-up surface: GHSL Data Package 2023, JRC, doi:10.2760/098587
  • Population: WorldPop, University of Southampton, doi:10.5258/SOTON/WP00674
  • Land cover: ESA WorldCover 2020/2021, ESA, doi:10.5281/zenodo.7254221
  • Soil texture: ISRIC SoilGrids 2.0, doi:10.5194/soil-7-217-2021
  • Hydrologic soil group + Curve Number: USDA NRCS National Engineering Handbook Part 630, Chapter 7 (Hydrologic Soil Groups, 2009) and Chapter 9 (Hydrologic Soil-Cover Complexes, 2009)
  • Channel hydraulic geometry: Leopold, L. B. and Maddock, T. (1953). The hydraulic geometry of stream channels and some physiographic implications. USGS Professional Paper 252
  • Damage curves: Huizinga, J., de Moel, H. and Szewczyk, W. (2017). Global flood depth-damage functions: Methodology and the database with guidelines. JRC Technical Report EUR 28552 EN, doi:10.2760/16510

License

MIT — see LICENSE.

About

Modular Python pipeline for HAND-based flood inundation and damage estimation — from a (lat, lon) point and rainfall to a per-cell flood depth and damage map.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages