Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ jobs:
- name: Set up micromamba environment
uses: mamba-org/setup-micromamba@v2
with:
environment-name: flood_inundation
environment-name: floodpath
create-args: >-
python=3.12
init-shell: bash

- name: Install conda packages
run: micromamba install -y -n flood_inundation -c conda-forge --file 1a_conda_requirements.txt
run: micromamba install -y -n floodpath -c conda-forge --file 1a_conda_requirements.txt

- name: Install pip packages
run: pip install -r 1b_pip_requirements.txt
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ htmlcov/
# Smoke-test outputs
plots/

# Local-only experimentation script (not part of the published package).
smoke_test.py

# Per-machine Claude Code settings.
.claude/

# Local virtual envs (the project uses a named conda env, not a local one,
# but guard against accidental commits if a contributor sets up differently).
.venv/
Expand Down
2 changes: 1 addition & 1 deletion 1c_setup_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

set -e

ENV_NAME="flood_inundation"
ENV_NAME="floodpath"
PYTHON_VERSION="3.12.3"
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
CONDA_REQUIREMENTS="${SCRIPT_DIR}/1a_conda_requirements.txt"
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Reza Ehsani

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
121 changes: 121 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# floodpath

[![tests](https://github.com/rehsani/floodpath/actions/workflows/tests.yml/badge.svg)](https://github.com/rehsani/floodpath/actions/workflows/tests.yml)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

**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:

```
DEM → flow direction → streams → HAND
+ 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

```bash
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:

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

## Quickstart

```python
from floodpath.dem import get_dem
from floodpath.hydrology import build_flow_grid, extract_streams, compute_hand
from floodpath.exposure import get_ghsl_built
from floodpath.damage import (
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 hydrology
grid = 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 level
depth = 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²")
```

## Modules

| Module | Source | What it provides |
|---|---|---|
| `floodpath.dem` | Copernicus GLO-30 (AWS Open Data, COG) | Elevation patch around any (lat, lon) |
| `floodpath.hydrology` | derived from DEM via `pyflwdir` | Flow direction + accumulation, stream networks (with Strahler order), basin delineation, HAND |
| `floodpath.exposure` | GHSL R2023A, WorldPop, OpenStreetMap (Overpass) | Built-up surface, population, building footprints |
| `floodpath.damage` | JRC Huizinga 2017 + DEM/HAND/GHSL | Per-cell flood depth and damage in m² of built-up surface |

## 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.

```python
from floodpath.damage import jrc_curve

curve = 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:

```bash
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 **alpha**. The pipeline produces sensible flood/damage maps for static water-level scenarios, but does not yet model:

- Time-resolved hydraulics (no Saint-Venant solver)
- Rainfall → runoff routing (planned for v0.2)
- Soil moisture / infiltration (planned for v0.2)
- 2D shallow-water dynamics (not planned yet!)

If you need those, look at [LISFLOOD-FP](https://www.bristol.ac.uk/geography/research/hydrology/models/lisflood/), [HEC-RAS 2D](https://www.hec.usace.army.mil/software/hec-ras/), or [WFlow](https://github.com/Deltares/Wflow.jl).

## 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
- **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](LICENSE).
3 changes: 3 additions & 0 deletions floodpath/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""floodpath — modular Python pipeline for HAND-based flood inundation and damage estimation."""

__version__ = "0.1.0"
File renamed without changes.
2 changes: 1 addition & 1 deletion damage/compute.py → floodpath/damage/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np

from exposure.models import ExposureGrid
from floodpath.exposure.models import ExposureGrid

from .constants import RATIO_TOLERANCE
from .curves import DepthDamageCurve
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion damage/inundation.py → floodpath/damage/inundation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np

from hydrology.models import Hand
from floodpath.hydrology.models import Hand

from .models import InundationDepth

Expand Down
2 changes: 1 addition & 1 deletion damage/models.py → floodpath/damage/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy as np
from rasterio.transform import Affine

from dem.models import BoundingBox
from floodpath.dem.models import BoundingBox


@dataclass
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions exposure/constants.py → floodpath/exposure/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
GHSL_UNITS: str = "m^2 of built-up surface per cell"

# Per-user cache directory; siblings to other potential exposure sources later.
DEFAULT_CACHE_DIR: Path = Path.home() / ".cache" / "flood_inundation" / "ghsl"
DEFAULT_CACHE_DIR: Path = Path.home() / ".cache" / "floodpath" / "ghsl"


# --- WorldPop (constrained 100 m population, R2020 maxar_v1) ---------------
Expand All @@ -49,7 +49,7 @@
# windowed reads are not viable. Download whole country files (~tens of MB)
# on first hit and read windows from the local cache.
WORLDPOP_CACHE_DIR: Path = (
Path.home() / ".cache" / "flood_inundation" / "worldpop"
Path.home() / ".cache" / "floodpath" / "worldpop"
)


Expand All @@ -58,4 +58,4 @@
OVERPASS_URL: str = "https://overpass-api.de/api/interpreter"
OSM_BUFFER_DEG: float = 0.1
OSM_DEFAULT_TIMEOUT_S: int = 60
OSM_USER_AGENT: str = "flood_inundation/0.1 (research; geospatial pipeline)"
OSM_USER_AGENT: str = "floodpath/0.1 (research; geospatial pipeline)"
6 changes: 3 additions & 3 deletions exposure/ghsl.py → floodpath/exposure/ghsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import rasterio
from rasterio.merge import merge

from dem.models import BoundingBox
from dem.utils import bbox_from_point
from floodpath.dem.models import BoundingBox
from floodpath.dem.utils import bbox_from_point

from .constants import (
DEFAULT_CACHE_DIR,
Expand Down Expand Up @@ -84,7 +84,7 @@ def get_ghsl_built(
buffer_deg: Half-width of the square bbox around the point, degrees.
epoch: Year of the GHSL product (one of GHSL_AVAILABLE_EPOCHS).
cache_dir: Where to store extracted tiles. Defaults to
~/.cache/flood_inundation/ghsl/.
~/.cache/floodpath/ghsl/.

Returns:
ExposureGrid carrying the m^2 built-up array, georef, and metadata.
Expand Down
2 changes: 1 addition & 1 deletion exposure/models.py → floodpath/exposure/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
from rasterio.transform import Affine

from dem.models import BoundingBox
from floodpath.dem.models import BoundingBox


@dataclass
Expand Down
4 changes: 2 additions & 2 deletions exposure/osm.py → floodpath/exposure/osm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from collections.abc import Iterable
from types import MappingProxyType

from dem.models import BoundingBox
from dem.utils import bbox_from_point
from floodpath.dem.models import BoundingBox
from floodpath.dem.utils import bbox_from_point

from .constants import (
OSM_BUFFER_DEG,
Expand Down
2 changes: 1 addition & 1 deletion exposure/utils.py → floodpath/exposure/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import math

from dem.models import BoundingBox
from floodpath.dem.models import BoundingBox

from .constants import (
GHSL_BASE_URL,
Expand Down
6 changes: 3 additions & 3 deletions exposure/worldpop.py → floodpath/exposure/worldpop.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import rasterio
from rasterio.windows import from_bounds, transform as window_transform

from dem.models import BoundingBox
from dem.utils import bbox_from_point
from floodpath.dem.models import BoundingBox
from floodpath.dem.utils import bbox_from_point

from .constants import (
WORLDPOP_AVAILABLE_YEARS,
Expand Down Expand Up @@ -106,7 +106,7 @@ def get_worldpop_population(
buffer_deg: Half-width of the square bbox around the point, degrees.
year: One of WORLDPOP_AVAILABLE_YEARS (2000-2020 inclusive).
cache_dir: Where to store country files. Defaults to
~/.cache/flood_inundation/worldpop/.
~/.cache/floodpath/worldpop/.

Returns:
ExposureGrid with population per cell.
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion hydrology/flow.py → floodpath/hydrology/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pyflwdir

from dem.models import DEM
from floodpath.dem.models import DEM

from .constants import DEM_NODATA_SENTINEL
from .models import FlowGrid
Expand Down
2 changes: 1 addition & 1 deletion hydrology/hand.py → floodpath/hydrology/hand.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Compute Height Above Nearest Drainage (HAND) per cell."""

from dem.models import DEM
from floodpath.dem.models import DEM

from .models import FlowGrid, Hand, StreamNetwork

Expand Down
2 changes: 1 addition & 1 deletion hydrology/models.py → floodpath/hydrology/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pyflwdir import FlwdirRaster
from rasterio.transform import Affine

from dem.models import BoundingBox
from floodpath.dem.models import BoundingBox


@dataclass
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import numpy as np

from dem import get_dem
from floodpath.dem import get_dem


def _summarize_dem(lat: float, lon: float) -> None:
Expand Down
43 changes: 43 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"

[project]
name = "floodpath"
version = "0.1.0"
description = "Modular Python pipeline for HAND-based flood inundation and damage estimation."
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "Reza Ehsani", email = "rezaa.ehsaani@gmail.com" },
]
keywords = ["flood", "hydrology", "geospatial", "DEM", "HAND", "inundation", "damage", "remote-sensing"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: GIS",
"Topic :: Scientific/Engineering :: Hydrology",
"Typing :: Typed",
]
dependencies = [
"numpy>=1.24",
"rasterio>=1.3",
"pyflwdir>=0.5",
]

[project.urls]
Homepage = "https://github.com/rehsani/floodpath"
Repository = "https://github.com/rehsani/floodpath"
Issues = "https://github.com/rehsani/floodpath/issues"

[tool.setuptools.packages.find]
where = ["."]
include = ["floodpath*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
Expand Down
Loading
Loading