Prototype tool for composing camera animations on a VersaTiles map and previewing them in the browser. Long-term goal: feed the same data model into a headless renderer for TV-grade map animations.
Status: prototype. Hosted but not publicly linked. The JSON schema is the durable contract; the editor and renderer talk it.
colorful or satellite.Share by URL — the entire animation (style, terrain, sky, annotations, scale) is encoded into the URL hash with a bit-packed binary codec; any tester with the link sees the same animation.
Embed in a third-party page via a generated <iframe> snippet (16:9, fluid width). The viewer is served from /view and carries the same URL-hash payload. See embed-demo.html for the iframe in real-page context with a copy-paste snippet.
Local persistence — the latest animation is mirrored to localStorage, so a reload without a hash restores it.
Export / import as JSON for archival or hand-off.
Render to MP4 via the published Docker image (button in the editor copies the command):
docker run --rm --pull always -v "$PWD:/out" \
ghcr.io/versatiles-org/versatiles-map-animation:latest \
--hash '<URL-hash payload>' --width 1920 --fps 30 --output /out/animation.mp4
npm install
npm run dev # http://localhost:5173
npm run build
Output in build/. Deploy to any static host (GitHub Pages, Cloudflare Pages, etc.). URL-hash state means no server-side support is needed.
npm run check # format + svelte-check + eslint + vitest
npm run test:coverage # vitest with v8 coverage
The same shape ships in the downloadable JSON and (in a more compact bit-packed form) the URL hash. JSON schema version 1:
{
"version": 1,
"style": "colorful", // "colorful" | "satellite"
"labels": true, // place names (colorful) or overlay (satellite)
"terrain": false,
"sky": false,
"annotationScale": 1, // global multiplier on per-annotation iconSize/labelSize
"keyframes": [
{
"t": 0.0, // seconds
"lng": 13.405,
"lat": 52.52,
"zoom": 10,
"pitch": 0, // 0..90
"bearing": 0, // -180..180
"roll": 0, // -180..180
"path": "arc" // optional: "arc" | "linear" — trajectory to this keyframe
}
],
"annotations": [
{
"lng": 13.405,
"lat": 52.52,
"icon": "symbol-marker",
"color": "#cc0000",
"label": "Berlin",
"labelColor": "#111111", // optional
"labelPosition": "bottom", // optional: top-left … bottom-right
"labelDistance": 1.5, // optional: em from the geo point
"iconSize": 1, // optional
"labelSize": 1, // optional
"rotation": 0, // optional, degrees clockwise (mainly for arrow icons)
"visibleFrom": 0, // optional: hide before this time
"visibleUntil": 5, // optional: hide at/after this time
"fadeIn": 0.5, // optional: seconds; ramp 0→1 before visibleFrom
"fadeOut": 0.5, // optional: seconds; ramp 1→0 after visibleUntil
"labelHaloColor": "#ffffff", // optional: explicit override (else auto-flip for legibility)
"labelHaloWidth": 1.5,
"iconHaloColor": "#ffffff",
"iconHaloWidth": 0
}
]
}
version is the JSON-format compat marker; new optional fields are added without bumping it. Files written before a feature was added still load — every additional field is optional and falls back to a sensible default.
The URL hash uses a separate layered binary format with five tags (V1..V5); the encoder picks the smallest version that round-trips the current animation losslessly, so simple animations stay short and existing share links remain stable as new features land.
MIT — see LICENSE.