FOSS4G TOKAI 2019

ざっくり理解するDeck.gl


Created by Masayuki Shimizu / @_shimizu

自己紹介



  • 清水正行
  • 群馬県高崎市在住
  • 日経編集局 メディア戦略部
  • DataViz / GIS エンジニア

日経:Visual Data

Blog

Deck.glについての記事書いてます

Deck.gl

Deck.glとは


世界最大の配車アプリを開発・運営するUber社がオープンソースとして公開しているデータビジュアライゼーションフレームワーク。 同じくUber社が公開している可視化スイート「Vis.gl」のツールの一つ。

vis.gl


Deck.glを含むUber社が公開しているデータビジュアライゼーションスイート可視化ツール群。スイート(ひとそろいの、一式の)と名がつく通り、それぞれのライブラリやフレームワークは組み合わせて使えるようにコンポーネント化されている。
Uber社の全部盛りデータ可視化ツールスイート「Vis.gl」一覧 – GUNMA GIS GEEK

Deck.glの特徴


deck.glは、大規模なデータセットの視覚化をWebGlを用いて簡単に作成できるように設計されています。 ポイントやライン、ポリゴンなどのデータを地図上に可視化するための様々なレイヤーコンポーネントが提供されており、複数のレイヤーを組み合わせることにより、少ない労力で労力で印象的なデータビジュアライゼーションを作成することができます。

Deck.glの問題点

モダンなWeb開発ツールチェインを採用しているため、なんとなくハードルが高いフレームワークだと思われがち。今回はDeck.glの誤解を解くために初めに知っておくと理解が楽になる概念について紹介します。

押さえておきたい前提知識

  • React
    • コンポーネント
    • ビルドツール

  • Deck.gl
    • レイヤー
    • ビュー/ビューステイト

React

Reactとは何か?


ReactはFacebook社によって開発された、ユーザーインターフェイスを構築するためのJavaScriptフレームワークです。
ReactはJavaScriptのコードをコンポーネントという単位で分割し、再利用可能な部品として使うことを想定してシステムが設計されています。
Uber社のvis.glスイートもReactコンポーネントとして作成されており、各ツールを簡単に組み合わせて使えるように設計されています。

Reactに関する誤解

  • Reactは難しい
    • Reactだけ使う分には結構簡単
    • Fluxの概念だとか、Reduxを使い始めると難易度が上がる
  • ビルドツールよくわからない。
    • とりあえず create-react-appコマンドを使っておけばOK
    • webpackの詳細とかは後回し

コンポーネント


Reactはハードルの高いフレームワークと思われがちですが、 Deck.glを使ったアプリケーションの開発では、コンポーネントの基本である、コンポーネント作り方、インポートの仕方、propsを使った値の受け渡しの仕方、3点を抑えておけばほ問題ありません。

コンポーネントの基本形

//MyComponent
import React from 'react';

export default (props) => {
  const { text } = props; //引数を受け取る
  return <h1>{text}</h1>; //引数を適用したjsxを返す
};

Reactのコンポーネントは基本、単なる関数。値を受け取って(ない場合もある)、値を返すだけのものでしかない。

以前はクラスが主流だったが、フックという新機能の登場でステートレスなファンクションが基本となりつつある。

コンポーネントの埋め込み

//App.js
import React from 'react';
//コンポーネントの読み込み
import MyComponent from './MyComponent.js';

export default () => {
  return (
    <div className="App">
      <!--コンポーネントを使う-->
      <MyComponent text="ここの値がコンポーネントに渡されます"></MyComponent>
    </div>
  );
};
		

Deck.glコンポーネント

//App.js
import React from "react";
import DeckGL from "deck.gl";

export default () => {

  return (
    <DeckGL
      layers={renderLayers({ data: data })}
      controller={true}
      initialViewState={viewport}]
    >
    </DeckGL>
);
};

				

ビルドツール


Reactは開発環境の構築やビルドツールの設定など、Reactを取り巻く周辺のツールチェインが多様で複雑な印象がありますが、 Facebookが提供しているCLIツール「create-rect-app」ツールを使うとここら辺のめんどくさい作業をすべてスキップできます。

ツールのインストール

node.jsとnpmをインストール

$ apt install nodejs
$ apt install npm

npmを使ってcreate-react-appコマンドをインストトール

$ npm install create-react-app -g

パーミッションエラーが出る場合はsudoをつけて実行してください。

開発環境の構築

プロジェクトの作成

$ create-react-app myproject
$ cd myproject
$ npm install deck.gl --save	#deck.glを追加する

開発とビルド

開発サーバーの起動

$ npm start

ビルド

$ npm run build

buildディレクトリが生成され、コードが出力されます。

とりあえずこれだけわかっておけば、ビルドツールの細かい設定等がわからなくても開発はできます。

Reactは怖くない!


Windowsの場合は、WSL(Windows Subsystem for Linux)を利用すると、React&Deck.glの開発がとても快適に行えます。
ちなみに私はWSL-Ubuntuで開発しています。

Layer

レイヤーとは


「レイヤー」はDeck.glの中核をなす機能で、データムのコレクションを取得し、各データを位置、色、3Dポリゴンなどに関連付けてマップ上にレンダリングする可視化コレクションです。
複数のレイヤーを重ね合わせたり、ホバーやクリックなどのインタラクションを管理する機能があります。

かっこいいやつ

レイヤーの基本形

//RennderLayer.js
//散布図レイヤークラスをインポート
import { ScatterplotLayer } from "deck.gl";

export default props => {
  const { data } = props;

  //インスタンス化
  const scatter = new ScatterplotLayer({
    id: "scatter-layer",
    data: data,
    getFillColor: d => d.color,
    getPosition: d => [d.lng, d.lat]
  })

  //レイヤーを配列にまとめる
  const layes = [scatter];

  return layes;
};

		

Deck.glライブラリから必要なレイヤークラスをインポートし、プロパティを設定してインスタンス化します。

レイヤーの使い方

//App.js
import React from "react";
import DeckGL from "deck.gl";
import renderLayers from "./RenderLayers.js";

export default () => {
  const data = {
    { name: 'Colma', lng:-122.466233, lat:37.684638 },
    { name: 'Civic Center', lng:-122.413756, lat:37.779528 },
    ...
  };

  return (
    <DeckGL
      layers={renderLayers({ //レイヤーをDeckコンポーネントに設置
        data: data
      })}
      controller={true}
      initialViewState={ViewState}
    />
  );
};
		
			

Deckコンポーネントのlayerプロパティにレイヤーオブジェクトを渡す。

レイヤーのプロパティ

  • id
    • 任意のレイヤー名を指定。レンダリングの基盤となるためユニークなIDを指定する
  • data
    • レイヤーで描画するデータセットを渡すプロパティ
    • JSONの場合はURLで指定することもできる
  • get~(アクセサ)
    • getから始まるプロパティ群。getFillColor,getRadiusなど
    • データにアクセスするコールバックを指定する。コールバックの戻り値がプロパティの値として適用される

Deck.glとD3.jsのアクセサ

$ //deck.gl 
import { GeoJsonLayer } from 'deck.gl';

const data = loadJson("example.geojson");

const layer = new GeoJsonLayer({
  id: 'geojson-layer',
  data:data,
  getFillColor: d => d.properties.color,
  getRadius: d => d.properties.population,
});

return [layer];
//d3.js 
d3.json("example.geojson").then(data =>{
  svg.selectALL("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("fill", d => d.properties.color)
    .attr("r", d => d.properties.population)
});




指定したコールバックがデータストリームをトラバースする仕組みはD3を使ったことがある人には理解しやすい

レイヤーの種類によって指定できるプロパティの種類は変わりますが、dataプロパティにデータセットを渡し、アクセサを使って配色や線の太さなど表現形態を指定するという仕組みはすべて共通です。

レイヤーカタログ


とりあえず最初はScatterplotLayerを使って任意の場所にサークルを表示するというのに挑戦するのがお勧めです。
ひとつでもレイヤーの使い方が理解できてしまえば他のレイヤーを使うのは難しくありません。

View/ViewState

ビュー/ビューステイトとは


「view」はdeck.glが可視化したレイヤーを映し出すカメラとカメラコントロールロジックをパッケージ化したクラスです。
データセットの座標(地理空間座標、2次元座標など)および、カメラの視点(トップダウン、一人称など)に基づいて、1つまたは複数のviewクラスを選択できます。
「viewState」は、viewの状態を保存するオブジェクトです。

Deck.glを単体の地図としのみ利用する場合はviewを意識する必要はありません。
viewStateは、カメラ位置の初期設定やDeck.glと連動させる他のvis.glスイートコンポーネント間で受け渡す必要があります。

viewStateオブジェクト

//App.js

const viewState = {
  width: window.innerWidth,
  height: window.innerHeight,
  longitude: -3.2943888952729092,
  latitude: 53.63605986631115,
  zoom: 6,
  maxZoom: 16,
  pitch: 65
};

export default () => {
  return (
    <DeckGL
      controller={true}
      initialViewState={viewState}
    />
  );
};
		
					

Deckコンポーネントに渡されたviewStateオブジェクトは、ユーザーの操作を検出したコントローラーによって更新されます。
viewStateオブジェクトの受け渡しには、viewStateプロパティかinitialViewStateプロパティを使います。

レイヤーのプロパティ

  • initialViewState
    • このプロパティに初期状態のviewStateオブジェクトが渡された場合、Deckコンポーネントはコントローラーによって更新されたステートをもとに再描画を自動的に行います。
  • viewState
    • このプロパティでは、viewStateオブジェクトの更新のみが行われビューの再描画は行いません。
    • ビューの再描画が行われるためには、onViewStateChangeプロパティにコールバックを渡し、明示的に親コンポーネントのviewStateを更新する必要があります。

initialViewStateとviewState

 //initialViewState
const viewState = { … };

export default () => {
  return (
    <DeckGL
      controller={true}
      initialViewState={viewState}
    />
  );
};


		
//viewState
const viewState = { … };

export default () => {
  return (
    <DeckGL
      controller={true}
      viewState={viewState}
      onViewStateChange={v => setViewState(v.state) } 
    />
  );
};
	
		

他コンポーネントとviewStateを共有する場合には、onViewStateChangeを使って明示的にviewStateの更新を行う必要がある。

viewStateにまつわる問題

vis.glスイートでは、現在のviewStateをviewportと呼んでいた時期があるらしく、メソッド名やドキュメント、サンプルコードに混乱がみられます。
例えば、react-map-glでは、onViewStateChangeと同様の機能を持つ、onViewportChangeプロパティが今も存在している。

viewStateオブジェクト

//App.js
export default () => {
  const [viewState, setViewState] = useState({ … });

  return (
    <div>
      <MapGL
        {...viewState}
        mapStyle={"mapbox://styles/mapbox/light-v9"}
        mapboxApiAccessToken={MAPBOX_TOKEN}
        onViewportChange={v => setViewState(v)}
      >
       <DeckGL
          layers={renderLayers({
            data: data
          })}
          controller={true}
          viewState={viewState}
          onViewStateChange={v => setViewState(v.viewState)}
       />
      </MapGL>
    </div>
  );
};
				
				

onViewportChangeとonViewStateChangeでコールバックの引数として渡される値にも微妙に違いがあって悩ましい

とりあえず,徐々にviewStateで統一されつつあるみたいです。
(deckコンポーネントにはonViewportChangeは無い)
Deck.glを使っていて「なぜか地図が動かない!」って時は、viewState周辺を調べてみるのがお勧めです。

viewについて


Deck.glは地図ライブラリだと思われがちですが、viewを変更することで様々なデータセットの可視化にも利用できます。

viewの指定の仕方

//App.js
//ビュークラスを読み込む					
import DeckGL, { OrbitView } from "deck.gl";

//ビュークラスをインスタンス化
const view = new OrbitView({ fov: 50 });
const viewState = { … };

export default () => {
  return (
	<DeckGL
      views={view} //ビューを指定する
      controller={true}
      initialViewState={viewState}
    />
  );
};
				
							

ビューカタログ

クラス 座標系 詳細
View 生のveiwクラス。通常使用することはないが、投影行列を指定することで独自の座標系を利用できる。
MapView
(デフォルト)
地理空間 Web Meractor Projectionを用いてデータをレンダリングする。mapbox-glやGoogle Mapsなどの外部ベースマップライブラリの座標と一致するように設計されている。
FirstPersonView 地理空間 カメラは指定されたジオロケーションに配置され、一人称視点でデータをレンダリングする
ThirdPersonView 地理空間 カメラは指定されたジオロケーションに配置され、三人称視点でデータをレンダリングする
OrthographicView info-vis(2D) カメラは2Dの座標に対して垂直(見下ろし)に設置されターゲットポイントを映します。回転(チルト)は変更されません。
OrbitView info-vis(3D) カメラは指定された方向から2D座標をターゲットポイントを映します。指定されたポイントを中心に回転します。
PerspectiveView info-vis(3D) カメラは指定された方向から2D座標をターゲットポイントを映します。またカメラの位置を中心に回転します。

OrthographicView + D3.js

OrbitView + LiDARデータ


Viewを使うと、Deck.glでできることが広がり、単なる地図ライブラリ以上の活用ができて楽しくなります。

この辺、もっと詳しく知りたい方はブログにサンプルコードを載せているので是非ご覧ください。

まとめ


Deck.glは、日本ではまだ活用事例の少ないライブラリですが、Uber社によって十分にテストされた実用的なライブラリです。
公式サイトのドキュメントも非常に充実しているので、ぜひ実際に触って遊んでみてください。