---
name: map_animation_video
description: "Animated maps for video: route and journey animations, fly-to moves, area and border highlights, labels that stay readable at phone size, and timing the map to narration. Provider-neutral, with route data and attribution checks before rendering. Use when a travel, news, or history video needs an animated map, a route draw, a zoom to a place, or a border or area highlight."
---

# Map Animation for Video

## Purpose
Build an animated map shot that a viewer can follow on a phone and that holds up as a factual claim. The method is provider-neutral: the same beat sheet, data checks, and label rules work in a web map library such as MapLibre GL JS, in a GIS tool such as QGIS fed by Natural Earth or OpenStreetMap data, or in a video editor with keyframed map frames. The tool changes. The checks do not.

## Workflow
1. **Write the beat sheet first.** One beat per idea: context, move, arrival, highlight. Record name, start, move time, hold time, centre as longitude and latitude, zoom, and labels in a plan file shaped like `assets/sample-plan.json`. Detail in `references/camera-and-timing.md`.
2. **Pick and record the data source.** Choose Natural Earth for world and country views, OpenStreetMap for city and road detail, or another provider after reading its current video terms. Write the source and the exact attribution text into the plan. Rules per source in `references/data-and-attribution.md`.
3. **Verify the geography before animating it.** Coordinates are longitude, latitude. Check every named place against a second source, take road routes from routing or track geometry rather than straight lines, and build flights and long sea legs as great circles with `scripts/great_circle.py --from LON,LAT --to LON,LAT --points 64`. Split any route that crosses longitude 180. Full checks in `references/data-and-attribution.md`.
4. **Check the projection.** Web Mercator distorts area as latitude rises and cannot show the poles past about 85.05 degrees. Any shot that compares the size of places across latitudes uses a globe or equal area view, or carries a note that sizes are distorted.
5. **Set the camera move by distance.** Short moves get a plain eased pan. Long moves get a flyTo style arc that zooms out, pans, and lands. Unrelated places get a cut, not a move. Fit the full route with padding when the shape of the journey is the point. Timing and narration sync in `references/camera-and-timing.md`.
6. **Style labels and highlights for a phone.** Primary labels at least 32 pixels in a 1080 tall frame, secondary at least 28, every label backed by a chip or halo, one highlight colour per meaning, route lines with a casing. Rules in `references/labels-and-style.md`.
7. **Run the checker.** Run `scripts/plan_check.py plan.json` and fix every FAIL before rendering. It tests coordinate ranges and swapped pairs, beat overlaps, label size and contrast, route length and antimeridian crossings, attribution for sources that require it, and the border note when disputed borders are shown.
8. **Time the map to the narration.** Mark the time each place is named, end each move just before its name lands, hold at least 1 second after a move and at least 2 seconds after a named label or highlight appears. Watch once muted and once listening only.
9. **Render and QA at final size.** Export at the plan size and frame rate, H.264 with yuv420p and even dimensions for delivery copies. Read every label on a real phone width frame, trace every border, confirm the attribution is on screen per the source rules, and compare any distance in the narration against the route data one last time.

## Output Contract
- A plan file per map shot, in the shape of `assets/sample-plan.json`: output size and frame rate, data source, attribution text, border note when needed, beats with timing and labels, and the route points.
- Route geometry as GeoJSON with longitude, latitude coordinates, great circle for flights and long sea legs, produced or checked with the bundled scripts.
- One rendered shot per plan at the planned size, plus a checker report from `scripts/plan_check.py` with no FAIL lines.
- Attribution delivered the way the data source requires for video: on the map corner while it is a major component for OpenStreetMap, in credits or description with the copyright URL where the guidelines call for it, and per current terms for any commercial provider.
- A delivery note naming the data source, its border view when borders are shown, and any projection note the shot carries.

## Operating Rules
1. Data before decoration. No route, border, or distance ships on how it looks. Each one traces to named data that was checked in step 3.
2. Never invent geography. No made up stops, borders, distances, or dates. A number the data does not support gets cut or gets checked, never rounded into existence.
3. Attribution is part of the render, not an afterthought. A shot with a missing or wrong credit does not ship, and credits alone do not cover a source that requires attribution while its content is shown.
4. Disputed borders are stated, not smoothed over. The plan names whose border view is shown, and the video says so when the dispute is part of the story.
5. Historical stories use historical data. Modern borders and names on a period map are errors. When a modern reference map is used on purpose, the shot says so.
6. One move, one colour meaning, one base style at a time. Consistency inside a video is what lets a viewer learn the map language in the first shot and read the rest without help.
7. Numbers marked as rules of this skill, such as label sizes, hold times, and line widths, are working rules set for phone viewing. Vendor and legal numbers, such as attribution terms and contrast ratios, come from the sources in `references/` and get rechecked when a provider changes its terms.
8. Test before shipping. After any change to `scripts/`, rerun the plan checker on `assets/sample-plan.json` (must PASS) and on a plan with a known bad label and missing attribution (must FAIL).

## Verification
Tested on 2026-10-21. `scripts/plan_check.py` passes `assets/sample-plan.json` (3 beats, route about 3,249 kilometres by segment math, 0 fails, 0 warnings), fails a doctored plan on four counts (missing OpenStreetMap attribution, a 14 pixel label, label contrast 1.26, and a beat overlap) with exit 1, and exits 2 on a missing file. `scripts/great_circle.py` builds the Lisbon to Istanbul arc at 3,237.6 kilometres and writes valid GeoJSON to `assets/sample-route.geojson`.
