The Great Aletsch Glacier retreating, 1986 → 2023¶
The Great Aletsch in the Swiss Alps is the largest glacier in the Alps — a 20 km river of ice, a
UNESCO World Heritage site, and one of the clearest icons of a warming climate. Like almost every Alpine
glacier it is retreating and thinning: its tongue creeps back up-valley and bare rock spreads where
ice used to be. This notebook builds a true-colour time-lapse — one satellite frame per year — from
the earthlens gee backend, and embeds it as an animation you can just watch.
The record spans four decades, so we reach across the Landsat archive: Landsat 5 carries us from 1986 through 1998, Landsat 7 covers 1999–2012, and Landsat 8 takes over from 2013 — the one catch being that their true-colour bands have different names.
Setup¶
pyramids reads and animates the yearly rasters (DatasetCollection), and IPython.display.HTML
embeds the GIF. Earth Engine needs a service account, read from the GEE_SERVICE_ACCOUNT /
GEE_SERVICE_KEY environment variables.
import base64
import os
from pathlib import Path
import matplotlib.pyplot as plt
from cleopatra.glyphs.gridded.array_glyph import FrameLabel
from IPython.display import HTML
from pyramids.dataset.collection import DatasetCollection
from earthlens.core import EarthLens
SERVICE_ACCOUNT = os.environ["GEE_SERVICE_ACCOUNT"]
SERVICE_KEY = os.environ["GEE_SERVICE_KEY"]
OUT = Path("out") / "aletsch"
OUT.mkdir(parents=True, exist_ok=True)
ALETSCH = dict(lat_lim=[46.38, 46.58], lon_lim=[7.95, 8.15]) # the glacier + its tongue
One true-colour frame per year¶
For each year we pull a late-summer median composite (July–September) over the glacier — median
throws out the odd cloud, and late summer is when the seasonal snow has melted back to expose the bare
glacier ice and the freshly uncovered rock. The only branch is the sensor era: Landsat 5 (≤1998) and
Landsat 7 (1999–2012) share the true-colour bands SR_B3 / SR_B2 / SR_B1, while Landsat 8
(≥2013) uses SR_B4 / SR_B3 / SR_B2.
frames = {}
for year in range(1986, 2024):
if year <= 1998:
asset, bands = "LANDSAT/LT05/C02/T1_L2", ["SR_B3", "SR_B2", "SR_B1"]
elif year <= 2012:
asset, bands = "LANDSAT/LE07/C02/T1_L2", ["SR_B3", "SR_B2", "SR_B1"]
else:
asset, bands = "LANDSAT/LC08/C02/T1_L2", ["SR_B4", "SR_B3", "SR_B2"]
job = EarthLens(
data_source="gee",
dataset=asset,
variables=bands,
start=f"{year}-07-01",
end=f"{year}-09-30",
temporal_resolution="raw",
reducer="median",
scale=60.0,
path=OUT / str(year),
export_via="url",
**ALETSCH,
)
job.authenticate(service_account=SERVICE_ACCOUNT, service_key=SERVICE_KEY)
paths = job.download(progress_bar=False)
frames[year] = paths[0]
len(frames)
Build the time-lapse¶
pyramids animates a stack of rasters natively — no hand-rolled frame loop. We load the yearly frames
into a DatasetCollection, and collection.plot(rgb_options={"rgb": [0, 1, 2], "surface_reflectance": …}) composites
the red/green/blue Landsat bands per year into a true-colour time-lapse. glyph.animate(...) stamps
each frame with its year — which we nudge up into the top margin so it reads like a title instead of
sitting on the ice — and glyph.save_animation(...) writes a larger, social-post-sized animated GIF
(which plays in any viewer — Jupyter, VS Code, GitHub, nbviewer — with no JavaScript).
timeline = sorted(frames)
cube = DatasetCollection.from_files([frames[year] for year in timeline])
glyph = cube.plot(
rgb_options={"rgb": [0, 1, 2], "surface_reflectance": 30000}, figsize=(8, 7.5)
)
# Float the year label above the frame, anchored top-left, instead of overlaying
# the ice, and enlarge it for a social post. An explicit FrameLabel location switches to
# data coordinates and is already non-clipping; [0, -10] sits just above the
# image's own top-left corner (baseline-anchored, so the glyphs render upward).
glyph.animate(
timeline,
interval=350,
frame_label=FrameLabel(location=[0, -10]),
)
gif_path = OUT / "aletsch_timelapse.gif"
glyph.save_animation(
str(gif_path), fps=3
) # save_animation() also accepts a Path directly; str() here just keeps the call explicit
plt.close("all")
Embed the GIF as a base64 data URI so the animation travels inside the notebook.
encoded = base64.b64encode(gif_path.read_bytes()).decode()
HTML(
f'<img src="data:image/gif;base64,{encoded}" alt="Great Aletsch Glacier 1986-2023 true-colour time-lapse" />'
)
What you are watching¶
- The bright ribbon curving through the scene is the glacier itself; the dark line running down its centre is the medial moraine — rock debris carried along where two ice streams merge.
- 1986 → 2023 — the tongue retreats up-valley (its lower end creeps back) and the ice thins, so bare grey-brown rock widens along the glacier's edges and the snowline climbs higher each decade.
- Warm years leave the glacier darker and more debris-covered; snowy years briefly whiten it again — but the long-term trend is unmistakable ice loss.
- The slight shift in tone around 1998→1999 and 2012→2013 is the handover between Landsat 5, 7 and 8 — different instruments, very slightly different colour, not a change on the ground.
Make it your own: move ALETSCH to another glacier (try the Rhône Glacier nearby, or the icefields
of Alaska and Patagonia — though those are cloudier), widen the year range, or drop scale for a
sharper, heavier movie.