Tracing Typhoon Bavi (2020) in true colour¶
In late August 2020, Typhoon Bavi spun up east of Taiwan, curved north through the East China Sea
past Okinawa and Jeju, and made landfall near the China–North Korea border. From space its cloud spiral
is unmistakable, and a day-by-day true-colour time-lapse traces the whole track. We build one from
the earthlens gee backend and embed it as an animation.
The West Pacific's geostationary eye (Himawari, 10-minute imagery) isn't on Earth Engine, so we use the
next best thing: MODIS Terra (MOD09GA) daily surface reflectance — one true-colour snapshot per
day (~10:30 local overpass) to follow the storm north. (Aqua's MYD09GA reflectance is masked over this
region for the whole week, so Terra alone carries the time-lapse.)
Setup¶
pyramids reads the frames and animates the stack (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 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") / "bavi2020"
OUT.mkdir(parents=True, exist_ok=True)
BAVI = dict(
lat_lim=[18.0, 43.0], lon_lim=[116.0, 138.0]
) # East China Sea -> Korea, the whole track
One true-colour frame per day¶
For each day of Bavi's life we pull a daily true-colour composite from Terra (MOD09GA) — the
visible bands b01/b04/b03 (red/green/blue) — so the animation gets one clean frame a day to follow
the spiral. We keep them in chronological order.
days = [f"2020-08-{day:02d}" for day in range(21, 29)] # 21–28 Aug 2020
frames = [] # (date_label, path), one Terra composite per day, in chronological order
for day in days:
job = EarthLens(
data_source="gee",
dataset="MODIS/061/MOD09GA",
variables=["sur_refl_b01", "sur_refl_b04", "sur_refl_b03"],
start=day,
end=day,
temporal_resolution="daily",
scale=3000.0,
path=OUT / day,
export_via="url",
**BAVI,
)
job.authenticate(service_account=SERVICE_ACCOUNT, service_key=SERVICE_KEY)
got = job.download(progress_bar=False)
frames.append((day, got[0]))
len(frames)
Build the time-lapse¶
Load the ordered frames into a DatasetCollection, composite the true colour with
collection.plot(rgb_options={"rgb": [0, 1, 2], …}), label each frame with its date via glyph.animate(...), and write
the GIF with glyph.save_animation(...) — pyramids' native animation, no hand-rolled frame loop. The
GIF plays in any viewer with no JavaScript.
labels = [label for label, _ in frames]
paths = [path for _, path in frames]
cube = DatasetCollection.from_files(paths)
glyph = cube.plot(
rgb_options={"rgb": [0, 1, 2], "surface_reflectance": 3200}, figsize=(6, 5.5)
)
glyph.animate(labels, interval=350) # stamp each frame with its date
gif_path = OUT / "bavi_2020_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="Typhoon Bavi 2020 true-colour time-lapse" />'
)
What you are watching¶
- 21–23 Aug — the storm organises east of Taiwan and the Ryukyus, its spiral tightening as it drifts north-northwest.
- 24–25 Aug — Bavi peaks over the warm East China Sea, a broad, cloud-filled pinwheel passing west of Kyushu and Jeju.
- 26–27 Aug — it races north and makes landfall near the China–North Korea border, the cloud mass smearing over the Korean peninsula.
- The black diagonal wedges that flit through some frames are MODIS orbital swath gaps — a normal artefact of stitching a once-daily polar-orbiting sensor into a single map.
Make it your own: move BAVI and days to any storm and window (MODIS has near-real-time data, so
a current typhoon works too), or drop scale for a sharper, heavier movie.