Real precipitation — styled plot and animation¶
A real MSWEP daily precipitation series (mm/day) over the Rhine basin, rendered with cleopatra's
total_precipitation style. A fixed 0…max scale keeps wet and dry days comparable across the animation.
A second animation shows the matching river discharge (Qtot) series.
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import cleopatra
from cleopatra.glyphs.base.animation import embed_gif
from cleopatra.glyphs.gridded.array_glyph import (
Animation,
ArrayGlyph,
Basemap,
ColorBar,
FrameLabel,
)
print("cleopatra", cleopatra.__version__)
DATA = Path("../../../examples/data") # kernel CWD is the notebook's own folder
def load_stack(name):
"""Load an .npz holding a (time, rows, cols) stack, its extent, date labels, and CRS."""
z = np.load(DATA / name, allow_pickle=True)
crs = (
int(z["crs"]) if "crs" in z.files else None
) # EPSG code, or None for lon/lat data
return (
z["stack"].astype(float),
[float(v) for v in z["extent"]],
list(z["labels"]),
crs,
)
precip, p_ext, p_labels, p_crs = load_stack("mswep_precip.npz")
print(
"MSWEP precip stack:",
precip.shape,
"|",
p_labels[0],
"->",
p_labels[-1],
"| max",
round(float(np.nanmax(precip)), 1),
"mm/day",
)
from cleopatra.styling.params import DataStyle
cleopatra 0.45.0 MSWEP precip stack: (6, 125, 93) | 1979-01-01 -> 1979-01-06 | max 18.1 mm/day
The wettest day¶
One frame with the total_precipitation style, ArrayGlyph.plot over the same EPSG:4647 basemap as the
animation — glyph.crs reprojects the dark tile basemap to match the projected data. The style ties opacity to the
rain, so dry cells stay transparent and the basemap shows through.
pcap = float(
np.nanpercentile(precip, 97)
) # scale to the top of ordinary rain (not the rare peak) so cells read clearly
day = int(np.nanargmax([np.nansum(f) for f in precip]))
pw, pe, ps, pn = p_ext
glyph = ArrayGlyph(precip[day], extent=[pw, ps, pe, pn])
glyph.crs = p_crs # EPSG:4647 -> add_tiles reprojects the dark basemap to match
glyph.add_tiles(
source="CartoDB.DarkMatter"
) # detailed dark web-tile basemap under the precip
glyph.plot(
vmin=0,
vmax=pcap,
full_bleed="black",
colorbar=ColorBar(label_color="white"),
data_style=DataStyle(style="total_precipitation"),
)
plt.show()
/home/runner/work/cleopatra/cleopatra/src/cleopatra/basemap/tiles.py:846: UserWarning: CartoDB tiles now require an API key. Please provide one to continue using the tiles. You can request the key at https://carto.com/basemaps/apikey/. url = provider.build_url(x=tile.x, y=tile.y, z=tile.z)
Animated — the daily precipitation series, over a projected-CRS basemap¶
The MSWEP raster is in EPSG:4647 (ETRS89 / UTM zone 32N), not lon/lat — so a plain lon/lat tile basemap would land
off-screen. Telling the glyph its CRS (glyph.crs = 4647, read straight from the .npz) makes
glyph.add_tiles(source="CartoDB.DarkMatter") reproject the dark tile basemap into that projected CRS (via pyproj)
so it lines up under the data — without reprojecting the data itself. full_bleed="black" fills the frame, and the
total_precipitation style ties opacity to the rain rate: dry cells stay transparent (the basemap shows through) and
wet cells build up opaque colour.
pw, pe, ps, pn = p_ext
glyph = ArrayGlyph(precip, extent=[pw, ps, pe, pn])
glyph.crs = p_crs # EPSG:4647 -> add_tiles reprojects the dark basemap to match
glyph.add_tiles(
source="CartoDB.DarkMatter"
) # dark web-tile basemap, drawn under the frames
anim = glyph.animate(
p_labels,
vmin=0,
vmax=pcap,
full_bleed="black",
colorbar=ColorBar(label_color="white"),
data_style=DataStyle(style="total_precipitation"),
playback=Animation(
frame_label=FrameLabel(
location=[pw + 0.03 * (pe - pw), ps + 0.05 * (pn - ps)], color="white"
),
interval=500,
),
)
plt.close(glyph.fig)
embed_gif(anim, fps=2)
/home/runner/work/cleopatra/cleopatra/src/cleopatra/basemap/tiles.py:846: UserWarning: CartoDB tiles now require an API key. Please provide one to continue using the tiles. You can request the key at https://carto.com/basemaps/apikey/. url = provider.build_url(x=tile.x, y=tile.y, z=tile.z)
<IPython.core.display.Image object>
Bonus — river discharge (Qtot) series¶
The matching Rhine runoff series (10 days) in the same EPSG:4647 grid, rendered with the flow_accumulation
style (symmetric-log, value-linked opacity). glyph.crs again warps the relief into the projected CRS, so the
wet channel network stands out over the terrain.
q, q_ext, q_labels, q_crs = load_stack("rhine_discharge.npz")
qmax = float(np.nanmax(q))
qw, qe, qs, qn = q_ext
glyph = ArrayGlyph(q, extent=[qw, qs, qe, qn])
glyph.crs = q_crs # EPSG:4647 -> warp the relief to match the projected data
anim2 = glyph.animate(
q_labels,
vmin=0,
vmax=qmax,
full_bleed="black",
basemap=Basemap(relief="medium"),
colorbar=ColorBar(label_color="black"),
data_style=DataStyle(style="flow_accumulation"),
playback=Animation(
frame_label=FrameLabel(
location=[qw + 0.03 * (qe - qw), qs + 0.05 * (qn - qs)], color="white"
),
interval=400,
),
)
plt.close(glyph.fig)
embed_gif(anim2, fps=3)
<IPython.core.display.Image object>