今年も 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
一般的なデータビジュアライゼーションを目的としたレイヤー
-
ArcLayer
- From - To の座標を結ぶ 3D のアーチ
-
BitmapLayer
- 境界に合わせたビットマップ
-
GeoJsonLayer
- ポイント,ライン,ポリゴン,テキストなど
- GeoJSON 形式のデータを元にする
-
IconLayer
- ラスターアイコン
-
LineLayer
- From - To の座標を結ぶフラットライン
-
PathLayer
- 座標リストの各要素をつなぐパス
- Miter joint
-
PointCloudLayer
- 3D の球体
-
PolygonLayer
- ポリゴン
-
ScatterplotLayer
- 2D の円
- 散布図を表現
-
TextLayer
- テキストラベル
- IconLayer の拡張
Aggregation Layers
入力データを集約し,グリッドによるヒートマップなどを表現するレイヤー
-
ContourLayer
- Isoline
- しきい値の上下を分ける線分
- Isoband
- しきい値の範囲を塗りつぶす
- Isoline
-
GridLayer
- 3D 四角形グリッドのヒートマップ
- GPU 集約のサポート有無によって GPUGridLayer または CPUGridLayer
-
HexagonLayer
- 3D 六角形グリッドのヒートマップ
-
ScreenGridLayer
- 2D グリッド
- 座標の配列をヒストグラムビンに集約 (GPU または CPU)
- GridLayer はマップを拡大縮小しても,オブジェクトは合わせて拡大縮小されるが,ScreenGridLayer は各ビンに分類される値の数が再集約される
-
HeatmapLayer
- ヒートマップ
-
ガウスカーネル密度推定 を内部的に実装
- 何を言ってるんだ...?
- すべてのブラウザでサポートされているわけではない
Geo Layers
具体的な地理空間可視化ユースケースを実現するレイヤー
-
H3ClusterLayer / H3HexagonLayer
- 3D 六角形グリッド
- H3 (https://h3geo.org/) を利用
- Apache2.0 ライセンス
- 六角形のグリッドに分割・集計するための地理空間インデックスシステム
-
S2Layer
- ポリゴン
- S2 Geometry Library (http://s2geometry.io/) を利用
- Apache2.0 ライセンス
- データを 2D ではなく,3D の球体で表現する
-
TileLayer
- 分割されたタイル
- 現在の Viewport に表示されているものだけをロードする
- Google Map みたいな感じ
-
Trips Layer
- 車両の移動を表すアニメーション化されたパス
- Example
-
TerrainLayer
- 立体的な地形
- Example
-
MVTLayer
- 分割されたタイル
- MVTs (Mapbox Vector Tiles) を利用
Mesh Layers
3D モデルを可視化するレイヤー
-
SimpleMeshLayer
- 任意の数の 3D オブジェクト
- e.g. マップ上の位置と方向を持つ 3D の車
-
ScenegraphLayer
- シーングラフ
- luma.gl (https://luma.gl/) を利用
- PhiloGL の後継
- MIT ライセンス
- 通常は glTF ファイルをロードする
- glTF (GL Transmission Format)
- JSONによって3Dモデルやシーンを表現するフォーマット
- glTF (GL Transmission Format)
- プログラムでもシーングラフの作成が可能
Viewport / ビューポート
- 典型的な 3D ライブラリの「カメラ」クラスの地理空間対応バージョン
- 3D 座標のプロジェクションなどの数値計算を行う
- 通常は Viewport インスタンスを直接操作することはない
View / ビュー
- HTML の canvas 上の位置と範囲に対応
-
ViewState / ビューステート
- ビューインスタンスの状態を表す
- カメラの位置,向き,ズームやコントローラーなどの設定
View の種類
-
MapView
- Webメルカトル図法を実装する
- デフォルトのビュー
-
GlobeView (Experimental)
- 地球を3D地球儀に投影する
-
FirstPersonView
- 一人称視点
- FPS (First-person shooter) みたいなやつ
- e.g. 車両ログをドライバーの視点からレンダリング
-
OrthographicView
- 平行投影/正投影
- 地理空間以外のユースケースで 2D チャートをレンダリング
-
OrbitView
- 透視投影
- 地理空間以外のユースケースで 3D オブジェクトをレンダリング
Controller / コントローラー
- ユーザーの操作とカメラをインタラクティブにする
- ポインターイベントとタッチジェスチャーをリッスン
ユーザーの入力をビューステートの変更に変換する
Controller の種類
※ View の種類にそれぞれ対応
その他
- Point Light などのライト
- Lighting Effect などの視覚効果
簡単 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: []
});
DeckGL オブジェクトの setProps
メソッドを使えば,動的に viewState
や layers
の値を変更することもできます。
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
Step 3
いよいよ地図上にデータを表示してみましょう。今回は ScatterplotLayer を使って,日本の都市の人口に比例したサイズの円を該当の座標位置にそれぞれ表示します。
まずは,適当なデータを用意します。
- 人口は Wikipedia から
- 座標は Google Map で調べました...
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,
]
});
ということで,簡単にデータビジュアライゼーションができましたね😉
- ドラッグ : 平行移動
- CTRL + ドラッグ : で回転
- スクロール : ズームイン・アウト
おしまい
はじめはオープンデータを使ったデータビジュアライゼーションを構想していたのですが,多くの場合は何かしらの加工が必要で,なかなかそのまますぐに使えそうなデータを見つけることができなかったため,今回は断念しました。。もう少し調べて再チャレンジしたいです。
Deck.gl の Examples では,GIS (Geographic Information System) 分野に留まらない幅広い魅力に触れることができますので,一度ご覧になってみてはいかがでしょうか。
参考
以下,大変参考にさせていただきました。
- Uber社の全部盛りデータ可視化ツールスイート「Vis.gl」一覧
https://gunmagisgeek.com/blog/deck-gl/6435 - deck.glでmapboxサービス以外のベースマップを使用する
https://gunmagisgeek.com/blog/deck-gl/6431 - Mapbox GL JS ver.2はオープンソースではなくなってしまいました。
https://gunmagisgeek.com/blog/mapboxgl/7029