Flood forecasting with GloFAS — the EWDS endpoint¶
The earthlens.ecmwf backend reaches more than one Copernicus Data Store
through the same cdsapi client. Besides the Climate Data Store (CDS), it can
retrieve from the CEMS Early Warning Data Store (EWDS) — the home of
GloFAS (the Global Flood Awareness System). This notebook downloads a small
GloFAS river-discharge forecast and maps it.
What you will learn
- How a dataset's
endpointroutes a retrieve to the right store (CDS vs EWDS). - That a single Copernicus token authenticates against CDS and EWDS.
- How to pull
cems-glofas-forecastthrough the unifiedEarthLensfacade. - That GloFAS comes back on its native regular 0.05° grid as NetCDF, ready
to read and plot with
pyramids.
Prerequisites¶
- A Copernicus account (the same single sign-on as CDS) — register at https://ewds.climate.copernicus.eu/.
- A Personal Access Token in
~/.cdsapirc. The same CDS token works for EWDS — only the URL differs, and the backend handles that for you:url: https://cds.climate.copernicus.eu/api key: <YOUR-TOKEN> - The GloFAS licence accepted once on its dataset page: https://ewds.climate.copernicus.eu/datasets/cems-glofas-forecast (tick it at the bottom of the Download tab).
Setup¶
The imports this notebook needs, up front. earthlens provides the unified
EarthLens entry point and the ecmwf Catalog; pyramids provides NetCDF
for reading the file that lands on disk; matplotlib draws the map.
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from pyramids.netcdf import NetCDF
from earthlens import EarthLens
from earthlens.ecmwf import Catalog
from earthlens.ecmwf.endpoints import ENDPOINTS
Step 1 — one backend, several stores¶
Every dataset row in the catalog carries an endpoint slug that decides which
Copernicus store the retrieve is sent to. GloFAS lives on ewds; the ERA5
family stays on cds (the default). Let's read GloFAS's catalog row.
catalog = Catalog()
glofas = catalog.datasets["cems-glofas-forecast"]
print(f"endpoint: {glofas.endpoint}")
print(f"grid_resolution: {glofas.grid_resolution} # degrees (native GloFAS grid)")
print(f"provider: {glofas.provider}")
The backend maps each endpoint slug to a store URL. A single token
authenticates against all of them, so switching stores is just a routing
detail — you never manage a second credential.
| endpoint | store | used for |
|---|---|---|
cds |
Climate Data Store | ERA5, CARRA, seasonal, … |
ewds |
CEMS Early Warning Data Store | GloFAS floods, fire danger |
ads |
Atmosphere Data Store | (reserved) |
for slug, (url, _url_env, _key_env) in ENDPOINTS.items():
print(f"{slug:5} -> {url}")
Step 2 — verify the token¶
If ~/.cdsapirc is present, the backend reuses that token for EWDS
automatically. A missing token raises AuthenticationError pointing at the file.
rc = Path.home() / ".cdsapirc"
print(f"~/.cdsapirc found: {rc.is_file()} ({rc})")
Step 3 — request a GloFAS forecast¶
We ask for one day of river_discharge_in_the_last_24_hours over a small window
of the Ganges–Brahmaputra delta. GloFAS is requested with
temporal_resolution="daily" — the forecast is keyed to a reference date, so
the monthly path (which drops the day selector) is rejected with a clear error.
out_dir = Path("data/glofas")
out_dir.mkdir(parents=True, exist_ok=True)
request = dict(
data_source="ecmwf",
variables={"cems-glofas-forecast": ["river-discharge-in-the-last-24-hours"]},
start="2024-01-01",
end="2024-01-01",
temporal_resolution="daily",
lat_lim=[23.0, 25.0],
lon_lim=[89.0, 91.0],
path=str(out_dir),
)
request
The catalog row already carries GloFAS's request defaults (system_version,
hydrological_model, leadtime_hour), so the call stays short. download()
blocks while the EWDS queue processes the request (usually under a minute for a
window this small) and returns the written NetCDF path(s).
earthlens = EarthLens(**request)
out_paths = earthlens.download()
out_paths
Step 4 — read the result and confirm the native grid¶
The file is a plain NetCDF (dis24 = river discharge in the last 24 h, m³/s).
get_variable hands back a pyramids raster whose geotransform confirms
GloFAS's native 0.05° grid — the backend snapped the requested bbox to it
rather than to ERA5's 0.125°.
nc_path = out_paths[0]
nc = NetCDF.read_file(str(nc_path))
discharge_var = nc.get_variable("dis24")
print(f"file: {Path(nc_path).name}")
print(f"variables: {nc.variable_names}")
print(f"grid: {discharge_var.rows} x {discharge_var.columns} cells, "
f"EPSG:{discharge_var.epsg}")
geotransform = discharge_var.geotransform
cell_size = abs(geotransform[1])
discharge = np.squeeze(discharge_var.read_array())
print(f"cell size: {cell_size:.3f} deg # GloFAS native 0.05, not ERA5's 0.125")
print(f"discharge: {np.nanmin(discharge):.1f} .. {np.nanmax(discharge):.1f} m3/s")
Step 5 — map the river discharge¶
Plotting the field makes the river network visible: the high-discharge cells trace the main channels of the delta, while the surrounding land sits near zero. A log-scaled colour ramp keeps the big rivers from washing out the tributaries.
from matplotlib.colors import LogNorm
rows, cols = discharge.shape
x_min, y_max = geotransform[0], geotransform[3]
extent = [x_min, x_min + cols * cell_size, y_max - rows * cell_size, y_max]
field = np.ma.masked_less_equal(discharge, 0)
fig, ax = plt.subplots(figsize=(7, 6))
im = ax.imshow(field, cmap="Blues", origin="upper", extent=extent,
norm=LogNorm(vmin=1, vmax=float(field.max())))
ax.set_title("GloFAS river discharge (dis24) — Ganges-Brahmaputra delta")
ax.set_xlabel("longitude (deg E)")
ax.set_ylabel("latitude (deg N)")
fig.colorbar(im, ax=ax, label="discharge (m3/s, log scale)")
plt.show()
The bright ribbons are the river channels carrying thousands of m³/s; the dark background is off-river land at near-zero discharge — exactly the sparse, network-structured field GloFAS models on its 0.05° grid.
Options & edge cases¶
- Other GloFAS variables. The forecast row also exposes
runoff_water_equivalent,snow_depth_water_equivalent, andsoil_wetness_index— swap the variable code to retrieve them. - Lead time. The catalog default is a 24 h lead; longer horizons (up to
720 h) are selected via the row's
leadtime_hourextra. - Daily only. GloFAS must be requested with
temporal_resolution="daily"; a"monthly"request raisesValueErrorbecause the forecast reference date needs thedayselector the monthly path omits. - Bigger areas. Widen
lat_lim/lon_limfor a basin-scale map; the retrieve time grows with the area.
Takeaway¶
A GloFAS flood forecast is one EarthLens("ecmwf", …) call away: the dataset's
endpoint routes it to EWDS, your existing CDS token authenticates there, and
the result arrives as NetCDF on GloFAS's native 0.05° grid — ready to read and
map with pyramids + matplotlib. See the
EWDS reference page for the full dataset list
and credential setup.