mcpField
M
map-animation

map-animation

github.com/iart-aiクリエイティブ・メディア21824 回インストール18 週間前に更新
リポジトリ

map-animation について

map-animationは、映像制作者やデータを用いた解説向けのエディトリアルな地図アニメーションを作るスキルです。Google Earth StudioとAfter Effectsのカメラ移動、GeoJSON/SVGマップのオーバーレイ、12fpsのグラフィック表現を組み合わせます。

主な機能

  1. 01Google Earth Studioでカメラ移動を作成
  2. 02GeoJSONまたはSVGからベクターマップを構築
  3. 03ピン、ルート、地域、ラベルをアニメーション化
  4. 04グラフィック動作を12fpsに量子化
  5. 05座標配列からシーケンスを生成

活用シーン

  1. 01ドキュメンタリーで場所を示す
  2. 02ルート、移住、サプライチェーンを可視化
  3. 03地理的な地域を強調してラベル付け
  4. 04イベントデータから地図動画を繰り返し生成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iart-ai/map-animation-skills --skill map-animation

インストール後、Claude Code で/map-animationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
references/
earth-studio-to-ae.md
vector-maps.md

詳細

マップアニメーション

エディトリアルな「Vox風」のアニメーションマップを作成します。ベースマップの上をカメラがズーム、周回、パンしながら、位置ピンが落ち、ルートが描画され、地域がハイライトされ、ラベルが表示されます。動きは意図的な12fpsのカクつきに揃え、写真のようではなくグラフィックとして見せます。制作方法は2つあります。Google Earth Studioでフォトリアルなベース映像を作成してAfter Effectsで合成する方法と、RemotionやD3で座標配列を使って動かす、コードでレンダリング可能なベクターマップ(GeoJSON/SVG)です。

使用する場面

  • 場所を示すドキュメンタリー/解説映像のパート:地球から都市へズームする、ランドマークを周回する、国境に沿ってパンする。
  • 地理上の移動を示す:2地点間にルートを描く、サプライチェーン、移住、飛行経路を示す。
  • 国、州、地域をハイライトしてラベルを付ける。地理を段階的に解説する。
  • 座標やイベントのリストを、繰り返し使えるデータ駆動型の動画用マップシーケンスに変換する。

制作方法を選ぶ

必要なもの制作方法出力
フォトリアルな地形、実際の衛星画像/3D建物、シネマティックなカメラGoogle Earth Studio → After Effects画像シーケンス+カメラ.jsx。AEで合成
すっきりとしたエディトリアルなベクター調、完全な制御、データ駆動型RemotionまたはD3のベクター(GeoJSON/SVG)コードでレンダリングしたMP4/GIF、またはウェブSVG
どちらの上にもピン/ルート/ハイライト/ラベルを重ねるオーバーレイレイヤーAEのシェイプ/ヌルレイヤー、またはReact/SVGコンポーネント

多くの「Vox風」作品では、これらを組み合わせます。ベースのカメラ移動にはEarth Studioを使い、その上にベクターオーバーレイでピン、ルート、ラベルを重ねます。

特徴的な表現:12fpsのカクつき

このスタイルを最もよく特徴づけるのは、24fpsの編集の中でマップグラフィックを12fpsでアニメーションさせることです。ベース映像と音声は24fpsで滑らかに再生し、グラフィックレイヤー(カメラに連動するピン、描画されるルート、ポップするラベル)は12fpsで段階的に変化させます。その結果、滑らかな動画ではなく、意図的に手作りした「インフォグラフィック」のような動きになります。

  • **計算方法:**24fpsでは、グラフィックレイヤーの各値を2フレーム保持します(偶数フレームでのみ更新)。30fpsでは、約2~3フレーム保持します(約12.5/10fps)。
  • **After Effects:**レイヤー/コンポにposterizeTime(12)を適用するか、グラフィックを12fpsのプリコンポジションにまとめて、24fpsのマスター内に配置します。
  • **Remotion/コード:**アニメーション値を計算する前にフレームをスナップします。
// Quantize the timeline to 12fps "steps" while the comp renders at 24fps.
const STEP = Math.round(fps / 12);              // 2 at 24fps
const f = Math.floor(frame / STEP) * STEP;       // use `f`, not `frame`, for graphic motion
const progress = interpolate(f, [0, 48], [0, 1], { extrapolateRight: "clamp" });

ベース映像/カメラは滑らかな状態(24fps)に保ち、カクつかせるのはグラフィックオーバーレイだけにします。すべてをカクつかせると、スタイルではなくフレーム落ちのように見えます。

制作方法A — Google Earth Studio → After Effects

Earth Studioは、Googleの3D地球上でカメラにキーフレームを設定できるブラウザツールです。AEに読み込む2つのファイルをエクスポートします。レンダリングされたフレームの画像シーケンスと、After Effectsで正確な3Dカメラを再構築する**カメラトラック.jsx**スクリプトです。

  1. Earth Studioでキーフレームを設定する(ブラウザ):カメラ位置(緯度/経度/高度)、傾き、方位、視野角にキーフレームを設定します。自動イージングを使うか、キーフレームごとにカスタムイージングを設定します。動きはゆっくりと意図的にし、急旋回ではなく場所を見せる動きにします。
  2. エクスポート → レンダリング:画像シーケンス(JPG/PNG)を選び、解像度とfpsを設定します。「3D Camera Export」/After Effectsのチェックボックスを有効にして、フレームと一緒に.jsxおよびトラックポイントのファイルも出力します。
  3. **After Effectsで:**画像シーケンスをフッテージとして読み込み、**File → Scripts → Run Script File…**を選択して、エクスポートした.jsxを実行します。Earth Studioの動きとフレーム単位で一致するキーフレームを持つ3Dカメラとヌルレイヤーを含むコンポが作成されます。
  4. **オーバーレイをトラックに親子付けする:**ピン/ラベルのヌルレイヤーを、読み込んだ3Dカメラ/トラックのヌルレイヤーに親子付けします。これにより、カメラの移動中もマップ上の正しい位置に固定されます(.jsxには、Earth Studioで指定した座標のトラックポイントが含まれます)。
  5. **カクつきを適用する:**オーバーレイのプリコンポジションのみに適用し(posterizeTime(12))、ベース映像のカラーグレーディングを行ってからレンダリングします。

正確なキーフレーム/エクスポート設定、.jsxの読み込み手順、トラックポイントをスクリーン空間から緯度/経度に対応付ける方法については、references/earth-studio-to-ae.mdを参照してください。

パス B — ベクターマップ(GeoJSON / SVG)

すっきりとしたエディトリアルな見た目にするには、GeoJSON を SVG パスに投影してアニメーションさせます。座標が正しく配置されるように、実際の投影法を使います。

import { geoMercator, geoPath } from "d3-geo";
const projection = geoMercator().fitSize([1920, 1080], geojson);  // fit map to frame
const toPath = geoPath(projection);
const [x, y] = projection([lng, lat]);          // any lat/long → screen pixel

geojson.features を <path d={toPath(f)} /> として描画します。同じ projection([lng,lat]) で、各ピンの座標を画面上の位置に変換するため、カメラ(scale/translate 変換)が動いても、ピン、ルート、ラベルはすべて同じ座標空間を共有します。

ベクター上のカメラ移動は、対象の緯度・経度に向けて CSS/transform でズームとパンを行います。

const [tx, ty] = projection([targetLng, targetLat]);
const scale = interpolate(f, [0, 36], [1, 4], { extrapolateRight: "clamp" });
// transform-origin at the target so the zoom homes in on it
const transform = `translate(${960 - tx*scale}px, ${540 - ty*scale}px) scale(${scale})`;

オーバーレイ:ピン、ルート、ハイライト、ラベル

ピンのドロップ(落として、静止させ、必要に応じてパルスさせる)— 座標の配列に沿って時間差で表示します。

const pins = [{ name:"Kyiv", lng:30.52, lat:50.45 }, { name:"Lviv", lng:24.03, lat:49.84 }];
pins.map((p, i) => {
  const local = f - i * STEP * 4;                 // stagger, quantized to the 12fps step
  const drop = spring({ frame: local, fps, config:{ damping: 12 } });   // overshoot = settle
  const [x, y] = projection([p.lng, p.lat]);
  return <Pin key={p.name} x={x} y={y - 40*(1-drop)} opacity={local > 0 ? 1 : 0} />;
});

ルートの描画 — 投影法を使ってパスを作成し、stroke-dashoffset で線を描画します。

// route = [[lng,lat], ...]; project each point, join into an SVG path
const d = route.map((c,i) => (i ? "L" : "M") + projection(c).join(" ")).join(" ");
// in the component: pathLength=1, strokeDashoffset = 1 - drawProgress
const draw = interpolate(f, [12, 60], [0, 1], { extrapolateLeft:"clamp", extrapolateRight:"clamp" });

pathLength="1" を指定すると、ルートの長さが正規化されるため、1 つの進捗値(0→1)で描画できます。線の先頭に沿って移動するドットを、offset-distance で追加します。大圏航路/曲線の飛行ルートには、d3.geoInterpolate(a, b) で中間点をサンプリングします。

地域のハイライト — 該当する GeoJSON フィーチャーを塗りつぶす/線で強調し、その他は暗くして(フォーカス+コンテキスト)表示します。

const isTarget = (f) => f.properties.name === "France";
// target: fill accent, opacity 1, stroke up; others: opacity .35

塗りの不透明度を補間してハイライトを表示するか、クリップパスマスクでワイプ表示します。色は意味と結び付け、同じシーンの途中で決して割り当て直さないでください。

ラベルコールアウト — ピンのラベル、または引き出し線付きのボックスです。ピンが配置された後にフェード/スライド表示し、projection([lng,lat]) に固定してカメラを追従させます。ラベルは水平に保ち、安全領域内に配置してください。地図と一緒に回転させないでください。

データ駆動のシーケンス

順序付き配列を 1 つ使ってシーケンス全体を制御し、地図自体が物語を語るようにデータから再レンダリングします。

const beats = [
  { lng: 30.52, lat: 50.45, label: "Kyiv",  hold: 48 },
  { lng: 24.03, lat: 49.84, label: "Lviv",  hold: 36, route: true },
];
// Each beat = a Sequence: camera flies to (lng,lat), pin drops, label pops, route draws if set.
// Total duration = sum(holds); compute it in calculateMetadata, not by hand.

1 つの配列で、カメラのターゲット、ピンの順序、ルートの区間、ラベルを同期させます。データを入れ替えるだけで新しい地図シーケンスを作成でき、キーフレームを設定し直す必要はありません。

出力チェックリスト

  • カメラの動きはゆっくりと意図的に(急激に動かさず、状況を示す)し、すべてのキーフレームでイージングを使います。
  • グラフィックオーバーレイは 12fps でカクつかせ、ベース映像/カメラは滑らかに保ちます(24fps)。
  • カメラの移動中も、ピン/ルート/ラベルを緯度・経度に固定します(トラックに親子付けする/投影法を共有する)。
  • ルートは読み進める順序で描画し、ハイライトはフォーカス+コンテキストを使います(対象を明るくし、その他を暗くする)。
  • ラベルは水平で読みやすく、安全領域内に配置し、色のルールに一貫性を持たせます。
  • データ駆動のパスでは、座標/ビート配列を唯一の信頼できる情報源にします。

納品と検証(レンダリングした静止画 → MP4)

パッケージ化されたヘルパー(scripts/):scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png で静止画をタイル状に並べ、scripts/probe-mp4.sh out.mp4 [WxH] [fps] でエンコード結果を検証します。詳しくは scripts/README.md を参照してください。

コードでレンダリング可能な方法は Remotion のコンポジションです。フレームが決定的なので、シーク用ハーネスなしで任意の正確なフレームをヘッドレスでレンダリングできます。納品物が MP4/GIF(ベクターマップ、データ駆動、正確なラベル)の場合や、データセットごとにバッチ処理する場合は、この方法を使います。Earth Studio + AE のコンポジションの場合、納品物は AE プロジェクトとレンダリング済み映像になり、マスターコンポジションをスクラブして検証します。

出力要件:

  • コンポジションが登録された Remotion プロジェクト(<Composition> + zod schema + defaultProps)。すべての動きはフレーム駆動にします(タイマー/Date.now()/Math.random() は使用しません)。
  • 納品物 = レンダリング済みの out/*.mp4(新しいデータからマップを再レンダリングできるよう、プロジェクトと beats/座標の配列も含めます)。
  • 12fps のスタッターはハードコードせず、fps から計算します(フレームを量子化します)。これにより、fps を変更しても維持されます。
  • 再生時間がデータに依存する(ビートの保持時間の合計など)場合は、手動で設定せず、calculateMetadata で計算します。

検証ループ — 静止画をレンダリング → 確認 → エンコード。 まず単一フレームをレンダリングします(低コストで、エンコード不要)。それらを確認し、フレームが正しくなってから一度だけエンコードします。

# Frame-exact stills at start / each beat / end — render with the SHIPPED props (the real beats array)
npx remotion still MapSeq out/f-start.png --frame=0   --props='{...}'
npx remotion still MapSeq out/f-beat2.png --frame=N   --props='{...}'   # mid a beat's hold
npx remotion still MapSeq out/f-end.png   --frame=L   --props='{...}'   # L = durationInFrames - 1

# Inspect each PNG — FIDELITY: every pin sits on the correct lat/long, the route connects the right
# cities, the highlighted region is the intended one, labels are spelled right and readable. And land
# on TWO adjacent frames inside a move to confirm the overlay holds for 2 frames (the 12fps step),
# while the base camera is still advancing every frame.
# ARTIFACTS: labels off-canvas/clipped, pins drifting off their coordinate, route overshooting,
# missing map tiles/fonts, projection cutoff at the antimeridian.

# Only after the stills check out, encode:
npx remotion render MapSeq out/map.mp4 --props='{...}'
  • npx remotion compositions は durationInFrames/fps を読み取り、最終フレームとビートごとのサンプルフレームを選びます。
  • データ駆動/バッチ処理: すべてのデータセットをバッチレンダリングする前に、代表的な beats 配列を1つ、静止画で検証します。投影やラベルの不具合は N 回ではなく、一度で見つけられます。
  • README 用デモ GIF を手軽に作成: npx remotion render MapSeq out/demo.gif --codec=gif。

完了前の確認事項:

  1. npx remotion still でフレーム 0、各ビートの中間、最終フレームをエラーなくレンダリングできること(エラーや、タイル/フォントの欠落がないこと)。
  2. すべてのピン/ルート/ラベルが、確認したすべてのフレームで正しい投影座標に配置されていること(既知の都市でスポットチェックします)。
  3. 隣り合う2つのオーバーレイフレームが同一であること(12fps のステップで保持される)。同時にベースカメラは進んでいること。スタッターが実際に発生し、グラフィックレイヤーのみに適用されていることを確認します。
  4. フレーム駆動のみであること。Date.now()/Math.random()/タイマーは使用せず、出荷する beats が正しくレンダリングされること(defaultProps だけでなく)。
  5. MP4 全体がエンコードされ、再生できること。(任意)README 用の GIF もレンダリングします。

参考ファイル

  • references/earth-studio-to-ae.md — Google Earth Studio のキーフレームとエクスポート設定、After Effects の .jsx カメラインポートワークフロー、オーバーレイをトラックポイントにペアレント化する方法、緯度/経度 ↔ 画面座標のマッピング、オーバーレイのプリコンポジションへの 12fps スタッターの適用。
  • references/vector-maps.md — GeoJSON の投影設定、ベクターカメラを緯度/経度にズーム/パンする方法、ピンのドロップ/ルートの描画/領域のハイライト/ラベルコールアウトのレシピ、データ駆動の beats パターン、コードと AE 向けの 12fps スタッター量子化の完全なレシピ。

コメント