tech-book-labs
可視化(チャート / 図 / 表) · 最終検証 2026-07-26 · deck.gl 9.3.7 · 初公開 2026-07-26

deck.gl で数万件の移動データを WebGL アニメ描画する

deck.gl の TripsLayer で「時刻つき経路」を動く軌跡として再生するパターン。Canvas/SVG が数千要素で頭打ちになる領域を WebGL で突破する仕組み、React(@deck.gl/react)での層構成、毎フレーム currentTime だけを更新して GPU にデータを載せたままにする設計、trailLength の演出、MapLibre 併用まで、架空路線網を再生する触れる demo 付きで整理する実装メモ。

deckgl webgl trips-layer animation gis react

検証日: 2026-07-26

使用バージョン: @deck.gl/core@9.3.7 / @deck.gl/layers / @deck.gl/geo-layers / @deck.gl/react / react@19.2

対象: 点や線が数万件を超えて Canvas/SVG の描画が破綻し始めた人。移動・フロー(交通、物流、アクセスログ)を「動き」として見せたい人

deck.gl(Uber 発・現 OpenJS Foundation 配下の WebGL 可視化フレームワーク)の TripsLayer で、「時刻つき経路」の集合を動く軌跡として再生する。SVG の DOM 数千要素・Canvas 2D の数万描画で頭打ちになる領域を、GPU 描画で数十万点まで引き上げるのが deck.gl の役割。動く demo 付き(データは架空の放射状路線網)。

触って試す

数百本の「列車」が同時に走っている。静的な路線ラインは 1 本も描いておらず、軌跡の尾(trail)の重なりだけで路線網が浮かび上がる。「尾の長さ」を最短にすると粒の流れに、最長にすると残像の網になる。ドラッグで回転(pitch 45°)、スクロールでズーム。

1. なぜ deck.gl か — 描画手段の使い分け

手段実用上限の目安使いどころ
SVG(D3 等)〜数千要素軸・ラベル・少数の図形。DOM なのでイベントが楽
Canvas 2D〜数万描画/frameヒートマップ、静的な大量点
deck.gl(WebGL)数十万〜大量点・線・3D・時間アニメーション

deck.gl は「レイヤーの宣言」だけを書く。ScatterplotLayer(点)、ArcLayer(2 点間の弧)、PathLayer(線)、TripsLayer(時刻つき線)などをデータと accessor(データ 1 件から座標や色を取り出す関数)で構成すると、GPU バッファへの転送・シェーダ・再描画はフレームワークが持つ。

2. 最小サンプル — ScatterplotLayer を React で出す

pnpm add @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/react

次のコードは 1 万点を WebGL で描く最小構成。背景地図なしでも動く(deck.gl 単体で 1 つの WebGL キャンバス):

import DeckGL from "@deck.gl/react";
import { ScatterplotLayer } from "@deck.gl/layers";

const layer = new ScatterplotLayer({
  id: "points",
  data: points,                          // [{position: [lng, lat]}, ...] × 10,000
  getPosition: (d) => d.position,        // accessor: データ → [経度, 緯度]
  getFillColor: [80, 210, 255, 180],
  radiusMinPixels: 2,
});

export function Map() {
  return (
    <div style={{ position: "relative", height: 400 }}>
      <DeckGL
        initialViewState={{ longitude: 139.7, latitude: 35.68, zoom: 10 }}
        controller                        // ドラッグ/ズーム操作を有効化
        layers={[layer]}
      />
    </div>
  );
}

ポイント:

  • DeckGL は absolute 配置なので、position: relative + 高さ指定の親で包む
  • レイヤーは毎レンダーで new してよい。deck.gl が前回インスタンスと props を diff し、変わっていなければ GPU 側は何もしない(React の仮想 DOM と同じ発想)
  • 色は [r, g, b, a] の 0-255 配列

3. TripsLayer — 「時刻つき経路」を再生する

TripsLayer のデータは 経路(座標列)+ 各点の通過時刻 のペア。currentTime を進めると、各経路上の「いま」の位置までが描かれ、trailLength 分の過去が尾として残る:

import { TripsLayer } from "@deck.gl/geo-layers";

new TripsLayer({
  id: "trips",
  data: trips,   // [{path: [[lng,lat],...], timestamps: [0, 4, 8, ...]}, ...]
  getPath: (d) => d.path,
  getTimestamps: (d) => d.timestamps,    // path と同じ長さの時刻配列
  getColor: (d) => d.color,
  currentTime: time,                     // ← これだけを毎フレーム更新する
  trailLength: 18,                       // 何時間ぶんの尾を残すか(timestamps と同じ単位)
  widthMinPixels: 2.5,
  fadeTrail: true,                       // 尾を透明に減衰
});

アニメーションは requestAnimationFrametime を進めてループさせるだけ:

useEffect(() => {
  let last = performance.now();
  let raf = 0;
  const tick = (now: number) => {
    setTime((t) => (t + (now - last) / 1000 * 60) % LOOP);  // 実 1 秒 = 仮想 60 分
    last = now;
    raf = requestAnimationFrame(tick);
  };
  raf = requestAnimationFrame(tick);
  return () => cancelAnimationFrame(raf);
}, []);

性能の鍵は「座標データを毎フレーム触らない」ことdata が同一参照なら GPU バッファは載せたまま、currentTime(ただの uniform 値)だけが更新される。逆に毎フレーム data を作り直すと、全経路の再転送が走ってフレームレートが崩壊する。

4. 「描かずに見せる」— trailLength の演出

demo で静的な路線ラインを描いていないのは意図的で、TripsLayer だけで密度が表現できるため:

  • 尾が重なる場所 = 本数が多い区間が自然に明るくなる(加算的な視覚効果)
  • 運行が少ない区間は流れては消え、運行終了後は画面が静かになる — 「動と静」がデータそのものを語る
  • trailLength 短 → 粒の流れ(個々の便が見える)/ 長 → 残像の網(路線網の形が見える)

この「軌跡の重なりで構造を浮かび上がらせる」手法は、交通に限らずアクセスログ・物流・人流でも同じ形で使える。

5. 背景地図と重ねる(MapLibre 併用)

demo は抽象背景(単色)だが、実データでは背景地図が欲しくなる。deck.gl は MapLibre GL JS と公式に統合できる(pnpm add @deck.gl/mapbox を追加。名前は mapbox だが MapLibre 対応):

import { MapboxOverlay } from "@deck.gl/mapbox";
import maplibregl from "maplibre-gl";

const map = new maplibregl.Map({ /* 通常の MapLibre 初期化 */ });
const overlay = new MapboxOverlay({ interleaved: true, layers: [tripsLayer] });
map.addControl(overlay);   // MapLibre のカメラと deck.gl が同期する

interleaved: true にすると deck.gl のレイヤーが MapLibre の描画パイプラインに割り込み、地図ラベルの下に軌跡を描くような重ね順制御ができる。背景タイルの配信をサーバーレスにしたい場合は MapLibre + PMTiles の記事と組み合わせる。

つまずいたポイント

  • 画面が真っ黒のままエラーも出ない: 親要素の高さ 0 が定番。DeckGL は absolute 配置なので、親に明示的な heightposition: relative が必須
  • アニメーションがカクつく(数千 trip 程度なのに): 毎フレーム data 配列を再生成していた。座標は useMemo で固定し、フレームで動かすのは currentTime だけにする(§3)
  • trailLength を変えても何も変わらないように見える: timestamps の単位と混同していた。trailLength は timestamps と同じ単位(demo は「分」)。秒とミリ秒が混ざると 1000 倍ずれて見た目が変わらない/全部消える
  • React 19 の StrictMode で WebGL context が 2 個作られる: dev では mount が 2 回走るため。unmount 側の cleanup は deck.gl が処理するので実害は dev のみだが、console.warn が出るのは仕様
  • ズームすると点が消える(ScatterplotLayer): getRadius がメートル単位のため、引きの表示で 1px 未満になっていた。radiusMinPixels で下限を張る

向くケース / 向かないケース

判定
移動・フローの時系列再生(交通、物流、人流)◎ TripsLayer の本領
数万〜数十万点の散布・密度表示◎ ScatterplotLayer / HexagonLayer
軸・凡例つきの業務チャート✖ Recharts / D3 の領分(deck.gl に軸はない)
描画対象が数百件以下△ WebGL の初期化コストの方が高い。SVG で十分

関連書籍

tech-book.net /books/9784873118086

Python と JavaScriptではじめるデータビジュアライゼーション | tech-book.net

Kyran Dale/嶋田 健志/木下 哲也

関連テーマ・学習ロードマップから次の一冊へ。

詳細を tech-book.net で見る
tech-book.net /books/9784873119380

Reactハンズオンラーニング 第2版 : Webアプリケーション開発のベストプラクティス | tech-book.net

Alex Banks/Eve Porcello/宮崎 空

関連テーマ・学習ロードマップから次の一冊へ。

詳細を tech-book.net で見る