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.
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
import cleopatra
from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, Basemap, ColorBar, FrameLabel
from cleopatra.glyphs.base.animation import embed_gif
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.32.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()
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, frame_label=FrameLabel(location=[pw + 0.03 * (pe - pw),
ps + 0.05 * (pn - ps)],
color="white"), interval=500, full_bleed="black", colorbar=ColorBar(label_color="white"), data_style=DataStyle(style="total_precipitation"))
plt.close(glyph.fig)
embed_gif(anim, fps=2)
<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, frame_label=FrameLabel(location=[qw + 0.03 * (qe - qw),
qs + 0.05 * (qn - qs)],
color="white"), interval=400, full_bleed="black", basemap=Basemap(relief="medium"), colorbar=ColorBar(label_color="black"), data_style=DataStyle(style="flow_accumulation"))
plt.close(glyph.fig)
embed_gif(anim2, fps=3)
<IPython.core.display.Image object>