21
19

More than 1 year has passed since last update.

Deck.gl の世界へようこそ! 3歩でわかる お手軽 データビジュアライゼーション

Posted at

今年も Advent Calendar の季節がやってきましたね。私も恒例の社内 Advent Calendar 向けに記事を書かせていただきます。2019年2020年 に引き続き,今年は Deck.gl をご紹介します。

Deck.glとは

  • https://deck.gl/
  • Uber 発のオープンソース
    • MITライセンス
  • データビジュアライゼーション フレームワーク
    • レイヤーを使ったアプローチ
  • React フレンドリー
    • リアクティブ プログラミング パラダイム
  • WebGL ベース
    • GPU で大量のデータを効率的に可視化
  • ベースマッププロバイダーとの統合
    • ArcGIS, CART, Google Map, Mapbox など

登場人物たちを見てみよう!

~ Deck.gl の世界に登場する人たちをご紹介するコーナー ~

Deck

  • Deck.gl のコアクラス
  • レイヤーやビューポートを受け取り,イベントを処理する

Layer / レイヤー

  • レイヤーは Deck.gl のコアコンセプト
  • データムのコレクションから各オブジェクトのプロパティへマッピング
    マップ上にレンダリングする
  • 複数のレイヤーを組み合わせ可能

Layer の種類

Core Layers

一般的なデータビジュアライゼーションを目的としたレイヤー

Aggregation Layers

入力データを集約し,グリッドによるヒートマップなどを表現するレイヤー

  • ContourLayer
    • Isoline
      • しきい値の上下を分ける線分
    • Isoband
      • しきい値の範囲を塗りつぶす
  • GridLayer
    • 3D 四角形グリッドのヒートマップ
    • GPU 集約のサポート有無によって GPUGridLayer または CPUGridLayer
  • HexagonLayer
    • 3D 六角形グリッドのヒートマップ
  • ScreenGridLayer
    • 2D グリッド
    • 座標の配列をヒストグラムビンに集約 (GPU または CPU)
    • GridLayer はマップを拡大縮小しても,オブジェクトは合わせて拡大縮小されるが,ScreenGridLayer は各ビンに分類される値の数が再集約される
  • HeatmapLayer
    • ヒートマップ
    • ガウスカーネル密度推定 を内部的に実装
      • 何を言ってるんだ...?
    • すべてのブラウザでサポートされているわけではない

Geo Layers

具体的な地理空間可視化ユースケースを実現するレイヤー

Mesh Layers

3D モデルを可視化するレイヤー

  • SimpleMeshLayer
    • 任意の数の 3D オブジェクト
    • e.g. マップ上の位置と方向を持つ 3D の車
  • ScenegraphLayer
    • シーングラフ
    • luma.gl (https://luma.gl/) を利用
      • PhiloGL の後継
      • MIT ライセンス
    • 通常は glTF ファイルをロードする
      • glTF (GL Transmission Format)
        • JSONによって3Dモデルやシーンを表現するフォーマット
    • プログラムでもシーングラフの作成が可能

Viewport / ビューポート

  • 典型的な 3D ライブラリの「カメラ」クラスの地理空間対応バージョン
  • 3D 座標のプロジェクションなどの数値計算を行う
  • 通常は Viewport インスタンスを直接操作することはない

View / ビュー

  • HTML の canvas 上の位置と範囲に対応
  • ViewState / ビューステート
    • ビューインスタンスの状態を表す
    • カメラの位置,向き,ズームやコントローラーなどの設定

View の種類
  • MapView
  • GlobeView (Experimental)
    • 地球を3D地球儀に投影する
  • FirstPersonView
    • 一人称視点
    • FPS (First-person shooter) みたいなやつ
    • e.g. 車両ログをドライバーの視点からレンダリング
  • OrthographicView
    • 平行投影/正投影
    • 地理空間以外のユースケースで 2D チャートをレンダリング
  • OrbitView
    • 透視投影
    • 地理空間以外のユースケースで 3D オブジェクトをレンダリング

Controller / コントローラー

  • ユーザーの操作とカメラをインタラクティブにする
  • ポインターイベントとタッチジェスチャーをリッスン
    ユーザーの入力をビューステートの変更に変換する

Controller の種類

その他

簡単 3 Step で体験してみよう!

~ サンプルを使って Deck.gl の世界を体験できるコーナー ~

今回は React を使わず,素の JavaScript で実装します。

Step 1

まずは,地図を表示してみましょう。

DeckGL (Scripting Interface) は Mapbox などの外部サービスとの統合をサポートする機能でコアの Deck クラスを拡張したクラスです。Step1 では,このクラスを利用してベースマップを表示します。

Mapbox のサービスを利用するには アクセストークン が必要になります。※ Mapbox GL JS は ver 2 からオープンソースではなくなりました。無料枠はあります。MapLibre (https://maplibre.org/) というプロジェクトが ver 1 系をフォークしてオープンソースとして引き継いでいるそうです。

それでは,コードを書いていきましょう。

Deck.gl ライブラリを読み込んでおく必要があります。HTML の Script タグに追加すれば OK です。今回は CDN から取得することにします。

<script src='https://unpkg.com/deck.gl@8.6.4/dist.min.js'></script>

Mapbox の API を利用するためのスタイルとスクリプトも読み込みましょう。1

<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css' rel='stylesheet' />

Deck が使う canvas 要素はプロパティで指定することもできますが,省略すると自動生成されます。自動生成される親要素は省略すると document.body になります。
画面全体にマップを表示するように CSS を設定します。

html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
}

DeckGL のインスタンスを生成します。

  • map
    • デフォルト window.mapboxgl
  • mapStyle
    • map のスタイル
  • mapboxApiAccessToken
    • Mapbox の API アクセストークン
  • initialViewState
    • 初期のビューステート
  • views
    • デフォルト MapView
  • controller
    • デフォルト null (無効)
    • true を指定すると,ビューのデフォルトコントローラー
    • デフォルトのビューが MapView なので,その場合は MapController
const deckgl = new deck.DeckGL({
  mapStyle: 'https://api.maptiler.com/maps/streets/style.json',
  initialViewState: {
    longitude: 137, // 経度
    latitude: 37,   // 緯度
    zoom: 5,        // ズーム
    minZoom: 2,     // 最小ズーム
    maxZoom: 8,     // 最大ズーム
    pitch: 30,      // 傾き
    bearing: 10,    // 回転
  },
  controller: true,
  layers: []
});

これで地図が表示されました!
step1.png

DeckGL オブジェクトの setProps メソッドを使えば,動的に viewStatelayers の値を変更することもできます。

deckgl.setProps({...});

Step 2

次に,ベースマップではなく TileLayer を使って OpenStreetMap の地図タイルを表示してみましょう。こちらの方法であればアクセストークンは不要です。

ライセンスの規約に則り,クレジット(出典)を表示するための HTML 要素を追加しましょう。

<div class="attribution">
  © <a href="http://www.openstreetmap.org/about/" target="_blank" rel="noopener noreferrer">OpenStreetMap</a> contributors
</div>


style
.attribution {
  position: absolute;
  bottom: 0;
  right: 0;
  z-index: 9;
  padding: 2px 4px;
  background-color: rgba(255, 255, 255, 0.5);
  font-size: 11px;
  pointer-events: auto;
}

.attribution a {
  color: rgba(0, 0, 0, 0.75);
  text-decoration: none;
}


地図タイルを表示するための TileLayer のインスタンスを生成します。

const tileLayer = new deck.TileLayer({
  data: "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png",
  minZoom: 0,
  maxZoom: 19,
  tileSize: 256,
  renderSubLayers: props => new deck.BitmapLayer(props, {
    data: null,
    image: props.data,
    bounds: (({ west, south, east, north }) => [west, south, east, north])(props.tile.bbox),
  }),
});

Step2 では,拡張版ではないコアの Deck オブジェクトを使用します。layers を指定してインスタンスを生成します。

const deckgl = new deck.Deck({
  initialViewState: {
    ... // Step1 と同じ
  },
  controller: true,
  layers: [
    tileLayer,
  ]
});

地図タイルが表示できましたね🗺 2
step2.png

Step 3

いよいよ地図上にデータを表示してみましょう。今回は ScatterplotLayer を使って,日本の都市の人口に比例したサイズの円を該当の座標位置にそれぞれ表示します。

まずは,適当なデータを用意します。

const data = [
  {p: [141.3521727, 43.0620842], v: 1952356}, // sapporo
  {p: [140.8677736, 38.2686018], v: 1082159}, // sendai
  {p: [139.5196066, 35.4937089], v: 3724844}, // yokohama
  {p: [136.9043778, 35.1814347], v: 2295638}, // nagoya
  {p: [135.7696242, 35.0118413], v: 1475183}, // kyouto
  {p: [135.5023064, 34.6936616], v: 2691185}, // oosaka
  {p: [135.1959010, 34.6894819], v: 1537272}, // koube
  {p: [132.4556497, 34.3849809], v: 1194034}, // hiroshima
  {p: [130.4017854, 33.5902214], v: 1538681}, // fukuoka
];

この data を使って ScatterplotLayer のインスタンスを生成します。

  • opacity
    • 不透明度
  • stroked
    • アウトラインを描画するかどうか
    • デフォルト false
  • filled
    • 領域を塗りつぶすかどうか
    • デフォルト true
  • getPosition
    • データムから座標を取得する関数
  • getRadius
    • データムから半径を取得する関数
  • getFillColor
    • データムから塗りつぶしの色を取得する関数
    • ドキュメントでは getColor となっているが古いプロパティらしく,警告がでる
      > `ScatterplotLayer: getColor` is deprecated and will be removed in a later version. Use `getFillColor/getLineColor` instead
const scatterplotLayer = new deck.ScatterplotLayer({
  data,
  opacity: 0.5,
  getPosition : ({p}) => p,
  getRadius   : ({v}) => Math.sqrt(v) * 12,
  getFillColor: ({v}) => [Math.sqrt(v) / 10, 20, 60],
});

Deck のインスタンスを生成します。先ほどの tileLayer に加えて scatterplotLayer も設定します。

const deckgl = new deck.Deck({
  initialViewState: {
    ... // Step1 と同じ
  },
  controller: true,
  layers: [
    tileLayer,
    scatterplotLayer,
  ]
});

データに合わせたサイズ・色の円が表示されました!
step2.png

ということで,簡単にデータビジュアライゼーションができましたね😉

サンプル

  • ドラッグ : 平行移動
  • CTRL + ドラッグ : で回転
  • スクロール : ズームイン・アウト

おしまい

はじめはオープンデータを使ったデータビジュアライゼーションを構想していたのですが,多くの場合は何かしらの加工が必要で,なかなかそのまますぐに使えそうなデータを見つけることができなかったため,今回は断念しました。。もう少し調べて再チャレンジしたいです。

Deck.gl の Examples では,GIS (Geographic Information System) 分野に留まらない幅広い魅力に触れることができますので,一度ご覧になってみてはいかがでしょうか。

参考

以下,大変参考にさせていただきました。


  1. 古いバージョンのライブラリだとアクセストークンなしでも表示できてしまうみたいですが... 

  2. ただ,カメラの位置判定で,ズームアウトしたときに一般的な世界地図(日本が右端にあるやつ)の区切りあたりで途切れてしまうみたいですね...  

21
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
19