This notebook fetches recent flood data from Montandon, the global crisis data bank, filtering for severity, and visualizes them on a map.
import os
import pandas as pd
from pystac_client import Client
import geopandas as gpd
from shapely.geometry import Point, Polygon, shape
from lonboard import viz
from datetime import datetime, timedelta, timezone## Connect to Montandon STAC
Montandon is exposed as a STAC collection, but requires authentication.
STAC_API_URL = "https://montandon-eoapi-stage.ifrc.org/stac"
API_TOKEN = os.getenv('MONTANDON_API_TOKEN')
if not API_TOKEN:
raise RuntimeError("MONTANDON_API_TOKEN is not set — required to authenticate with the Montandon STAC API.")auth_headers = {"Authorization": f"Bearer {API_TOKEN}"}Check Auth¶
# Connect to STAC API with authentication
try:
client = Client.open(STAC_API_URL, headers=auth_headers)
print(f"\n[OK] Connected to: {STAC_API_URL}")
print(f"[OK] API Title: {client.title}")
print(f"[OK] Authentication: Bearer Token (OpenID Connect)")
except Exception as e:
print(f"\n[ERROR] Authentication failed: {e}")
[OK] Connected to: https://montandon-eoapi-stage.ifrc.org/stac
[OK] API Title: Montandon STAC API
[OK] Authentication: Bearer Token (OpenID Connect)
clientFetch Recent Flood Impacts¶
We search for flood impact records directly, and then fetch hazard data based on the monty correlation ID.
Floods are searched for by the UNDRR hazard type MH0600, which are mapped to GDACS flood event FL.
now = datetime.now(timezone.utc)
start = now - timedelta(days=90) # adjust to change the search window
items_max = 5000 # Keeps max items consistent across searchesNote that this search takes several minutes to run with items_max = 5000.
search = client.search(
collections=["gdacs-impacts"],
datetime=f"{start.isoformat()}/{now.isoformat()}",
max_items=items_max,
)
# MH0600 is the standard UNDRR flood hazard code
flood_impacts = [
item for item in search.items()
if 'MH0600' in item.properties.get('monty:hazard_codes', [])
]
print(f"Found {len(flood_impacts)} flood impact records")Found 593 flood impact records
Each impact below returns a row for impact type and its value.
def parse_impact(item):
detail = item.properties.get('monty:impact_detail', {})
return {
'monty_corr_id': item.properties.get('monty:corr_id'),
'title': item.properties.get('title'),
'country_codes': ', '.join(item.properties.get('monty:country_codes', [])),
'impact_type': detail.get('type'),
'impact_value': detail.get('value'),
}
impacts_df = pd.DataFrame([parse_impact(item) for item in flood_impacts])
impacts_dfWe pivot the rows, so that each event, matched with the Monty correlation id monty_corr_id, is a single row, with columns being the values of potential impacts.
# Pivot: one row per event, impact types as columns
impacts_pivot = impacts_df.pivot_table(
index='monty_corr_id',
columns='impact_type',
values='impact_value',
aggfunc='sum'
).reset_index()
# Re-attach metadata — take first value per corr_id
metadata = (
impacts_df[['monty_corr_id', 'title', 'country_codes']]
.drop_duplicates('monty_corr_id')
)
impacts_pivot = impacts_pivot.merge(metadata, on='monty_corr_id')
print(f"{len(impacts_pivot)} flood events with impact data")
impacts_pivot74 flood events with impact data
Get Flood Footprints¶
Use the correlation IDs from the impact records to fetch the matching flood footprint polygons from GDACS hazards.
corr_id_set = set(impacts_pivot['monty_corr_id'])
search = client.search(
collections=["gdacs-hazards"],
max_items=items_max,
)
hazard_items = [
item for item in search.items()
if item.properties.get('monty:corr_id') in corr_id_set
]
print(f"Matched {len(hazard_items)} hazard footprints")Matched 74 hazard footprints
hazards_gdf = gpd.GeoDataFrame(
[{
'monty_corr_id': item.properties['monty:corr_id'],
'severity_label': item.properties.get('monty:hazard_detail', {}).get('severity_label'),
'geometry': shape(item.geometry),
} for item in hazard_items],
geometry='geometry',
crs='EPSG:4326',
)
# Combine footprints with impact data
floods = hazards_gdf.merge(impacts_pivot, on='monty_corr_id', how='left')
floodsviz(floods)Manywidgets¶
from manywidgets import Stat, GridGrid(
Stat(label="Population affected", value=impacts_pivot.affected_total.sum()),
Stat(label="People Killed", value=impacts_pivot.death.sum()),
Stat(label="People Injured", value=impacts_pivot.injured.sum()),
Stat(label="People Displaced", value=impacts_pivot.relocated.sum()),
Stat(label="Buildings Damaged", value=impacts_pivot.damaged.sum()),
# Stat(label="Buildings Destroyed", value=impacts_pivot.destroyed.sum()),
Stat(label="Countries Affected", value=impacts_pivot.country_codes.count()),
columns=3, gap="6px",
)