|
| 1 | +# Puppeteer animation skills (reference examples) |
| 2 | + |
| 3 | +These are **reference skills** — illustrative, copy-and-adapt examples that show how to |
| 4 | +drive the MapControl web map with [Puppeteer](https://pptr.dev) to produce animations |
| 5 | +for different scenarios. They are documentation, not a shipped/tested package; treat each |
| 6 | +`SKILL.md` as a recipe and each `animate.mjs` as a starting point. |
| 7 | + |
| 8 | +Each skill drives a **live map page** the same way a browser user would: it navigates to a |
| 9 | +map URL, waits for the map to be ready, then scripts camera moves. Nothing here reaches |
| 10 | +into private server internals — animation goes through the in-page MapLibre map object the |
| 11 | +page already publishes. |
| 12 | + |
| 13 | +## What the page gives you |
| 14 | + |
| 15 | +The served map page publishes two hooks the moment it is ready (see |
| 16 | +[`server/mapcontrol_server/static/esip-contract.js`](../../server/mapcontrol_server/static/esip-contract.js)): |
| 17 | + |
| 18 | +| Hook | What it is | Use it for | |
| 19 | +|---|---|---| |
| 20 | +| `window.__esipInternals.map` | the raw **MapLibre GL JS** `Map` instance | camera animation — `flyTo`, `easeTo`, `rotateTo`, `setBearing`, `setPitch` | |
| 21 | +| `window.ESIPMap` | the **public command surface** | basemap, visibility, `zoomToAssets`, reading the asset registry | |
| 22 | +| `esip:ready` event | fired once the contract is live | knowing when the hooks exist | |
| 23 | + |
| 24 | +Because animation just calls MapLibre's own camera methods, everything MapLibre supports |
| 25 | +is available — including the smooth van Wijk `flyTo` and 3D globe + terrain (the same |
| 26 | +terrain/sky path fixed in the server shell). |
| 27 | + |
| 28 | +## Prerequisites |
| 29 | + |
| 30 | +```bash |
| 31 | +npm install puppeteer |
| 32 | +``` |
| 33 | + |
| 34 | +You also need a **map to point at**. Create one first (any of the usual ways) and grab its |
| 35 | +`map_id`: |
| 36 | + |
| 37 | +```bash |
| 38 | +# Minimal: create a map over REST and read back the id |
| 39 | +curl -s -X POST http://localhost:8000/api/maps | python3 -c "import sys,json; print(json.load(sys.stdin)['map_id'])" |
| 40 | +``` |
| 41 | + |
| 42 | +or from the Python SDK: |
| 43 | + |
| 44 | +```python |
| 45 | +from mapcontrol import MapControl |
| 46 | +session = MapControl("http://localhost:8000").create_map() |
| 47 | +print(session.map_id) # feed this to MAP_ID below |
| 48 | +``` |
| 49 | + |
| 50 | +The map URL every skill opens is: |
| 51 | + |
| 52 | +``` |
| 53 | +http://localhost:8000/map/<MAP_ID>?ui=none |
| 54 | +``` |
| 55 | + |
| 56 | +`ui=none` serves the **naked canvas** (no picker, no draw tools) — the cleanest frame for a |
| 57 | +recording. Drop it if you want the chrome. If `user_session` is omitted the page |
| 58 | +auto-creates one, which is fine for a throwaway animation. |
| 59 | + |
| 60 | +## Shared helper |
| 61 | + |
| 62 | +All skills import [`lib/esip-map.mjs`](lib/esip-map.mjs), a tiny helper that launches a |
| 63 | +browser, opens a map URL, and resolves once `window.__esipInternals.map` exists and the |
| 64 | +style has loaded. Read it once; the per-skill scripts stay short. |
| 65 | + |
| 66 | +## The skills |
| 67 | + |
| 68 | +| Skill | Scenario | |
| 69 | +|---|---| |
| 70 | +| [`flyto-tour/`](flyto-tour/SKILL.md) | Ballistic **city-to-city tour** — smooth `flyTo` between waypoints | |
| 71 | +| [`terrain-orbit/`](terrain-orbit/SKILL.md) | **3D globe orbit** around a peak (Matterhorn) with terrain + sky | |
| 72 | +| [`keyframe-screenshots/`](keyframe-screenshots/SKILL.md) | Capture **PNG stills** at scripted keyframes | |
| 73 | +| [`record-frames/`](record-frames/SKILL.md) | Capture a **frame sequence** during an animation (→ GIF/MP4) | |
| 74 | + |
| 75 | +Each folder has a `SKILL.md` (when to use it + the recipe) and a runnable `animate.mjs`. |
0 commit comments