フリーダムの日記

GIS(地理情報システム)を中心に技術的なことを書いています。

ArcGIS API for JavaScript で deck.gl を利用してみる

はじめに

deck.gl はご存知でしょうか。
大規模なデータの分析や可視化に最適な WebGL を利用したフレームワークです。 Uber が開発しています。背景地図に Mapbox を利用しているため、Mapbox 製と誤解されることがありますが、開発は Uber です。 ただし、deck.gl のなかで Mapbox GL JS を呼び出して使用することもできます。 日本でも利用されているケースは多いかと思います。

deck.gl

さて、今回は、なんと Esri が開発している ArcGIS API for JavaScript でも deck.gl が利用できるようになりましたので、幾つか簡単なサンプルを例にして紹介したいと思います。

deck.gl の Whats-new でもArcGIS API for JavaScript との連携についてはしっかりと紹介されています。

ESRI + deck.gl

f:id:freedom0625:20200314145742j:plain

 

 

f:id:freedom0625:20200523153030g:plain

Tile3DLayer および I3S フォーマット

さらに Deck.gl が提供している Tile3DLayer に OGCI3S もサポートされたとのことです。これによって、Esri が提供している 3d Scene (I3S) のサービスも利用することができます。詳細は、tile-3d-layer にも紹介されています。

ArcGIS API for JavaScript で deck.gl を利用

ArcGIS API for JavaScript で deck.gl を利用する方法は以下にも詳しく紹介されています。 ここでは簡単な手順について紹介したいと思います。 developers.arcgis.com

ArcGIS API for JavaScript で deck.gl の TripsLayer を使用した例になります。

valuecreation.github.io

大まかな手順は以下のとおりです。

ヘッダーにそれぞれのライブラリを定義します。

<!--  deck.gl -->
<script src="https://unpkg.com/deck.gl@8.1.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/geo-layers@8.1.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/arcgis@8.1.0/dist.min.js"></script>

<!-- arcgis js api css 4.15 --> 
<link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />

<!-- arcgis js api 4.15 -->
<script src="https://js.arcgis.com/4.15/"></script>

ArcGIS API for JavaScript で利用するクラスをロードし、deck.gl で利用するレイヤーを定義します。 deck.gl で ArcGIS API for JavaScript を利用するための deck.loadArcGISModules() モジュールをロードします。

// We use require to load "esri/*" modules
    require([
      "esri/Map",
      "esri/views/MapView"
    ], function(Map, MapView) {
      // Most deck.gl features are available on the global "deck" object
      const GeoJsonLayer = deck.GeoJsonLayer;
      const ArcLayer = deck.ArcLayer;
      const TripsLayer = deck.TripsLayer;

      // The ArcGIS/deck.gl interface code is located in a separate module
      // that must be loaded asynchronously
      deck.loadArcGISModules().then(function(arcGIS) {
        // Now we can use the arcGIS module
        ...

MapView オブジェクトの map プロパティに Map オブジェクトを設定します。 そして、Map オブジェクトの layers プロパティに dock.gl のレイヤーを設定します。

   const layer = new DeckLayer({
      "deck.layers": [
        new deck.GeoJsonLayer({
          ...
        }),
        new deck.ArcLayer({
          ...
        })
      ]
    });

    const mapView = new MapView({
      container: "viewDiv",
      map: new Map({
        basemap: "dark-gray-vector",
        layers: [layer]
      }),
      ...
    });

以下の例では、deck.gl の TripsLayer を使用しています。TripsLayer はアニメーションが可能なポリライン レイヤーになります。 TripsLayer は、50 ミリ秒ごとに新しいインスタンスを作成して、DeckLayer として設定しています。

const layer = new arcGIS.DeckLayer();

    setInterval(function () {
      layer.deck.layers = [
        new deck.TripsLayer({
          id: "trips",
          data: "https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/trips/trips-v7.json",
          getPath: function (d) { return d.path; },
          getTimestamps: function (d) { return d.timestamps; },
          getColor: function (d) { return (d.vendor === 0 ? [253, 128, 93] : [23, 184, 190]); },
          opacity: 1.0,
          widthMinPixels: 4,
          rounded: true,
          trailLength: 180,
          currentTime: (performance.now() % 20000) / 10,
          shadowEnabled: false
        })
      ];
    }, 50);

実際に私が利用してみた感じですが、deck.gl のレイヤー定義がそのまま利用できるため、deck.gl ですでに開発されている方は簡単に利用することができるのではないのでしょうか。ArcGIS API for JavaScript でも Esri が提供しているサービスやコンテンツを表示したり分析したりすることができるため、deck.gl のサービスと組み合わせることで、より高度な分析や可視化に特化したアプリケーションの開発ができるかと思います!

上記の例では、MapView を使用した例ですが、3D の場合は SceneView を使用します。SceneView を使用した例は以下になります。

valuecreation.github.io

いずれも GitHub に登録していますので確認していただけます。 github.com

1kmメッシュ別将来推計人口 2050年 のデータを表示

最後に国土数値情報で提供されている「5. 各種統計」の1 kmメッシュ別将来推計人口(H30 国政局推計)(shape 形式版) のデータを利用してみました。

国土数値情報とは、国土に関する電子地図用のデータのことで、地形や土地利用など、国土に関する基礎的かつ汎用的なデータを GIS データとして整備して提供しています。汎用的なデータに関しては無償データとして公開されているため、利用用途にもよりますが、無償で利用することができます。

1km メッシュ別将来推計人口(H30国政局推計)のデータは、GeoJSON に変換して利用しました。通常 GIS データで利用されるフォーマットは、Shape ファイルや座標が入った CSV 等の利用が多いですが、Webアプリケーションで GIS データを利用する場合は GeoJSON が一番手軽に利用することができます。 GeoJSON は、JSON の拡張版のフォーマットで地理空間情報を手軽に利用することが可能です。

作成したアプリは以下になります。

valuecreation.github.io

作成したアプリのソースは以下になり、deck.gl の GeoJsonLayer レイヤーを利用しました。

<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <!--
    ArcGIS API for JavaScript, https://js.arcgis.com
    For more information about the custom-lv-deckgl sample, read the original sample description at developers.arcgis.com.
    https://developers.arcgis.com/javascript/latest/sample-code/custom-lv-deckgl/index.html
    -->
    <title>Render deck.gl layers in SceneView - 4.15</title>

    <!--  deck.gl -->
    <script src="https://unpkg.com/deck.gl@8.1.0/dist.min.js"></script>
    <script src="https://unpkg.com/@deck.gl/layers@8.1.0/dist.min.js"></script>
    <script src="https://unpkg.com/@deck.gl/geo-layers@8.1.0/dist.min.js"></script>
    <script src="https://unpkg.com/@deck.gl/arcgis@8.1.0/dist.min.js"></script>

    <!-- arcgis js api css 4.15 -->
    <link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />

    <!-- arcgis js api 4.15 -->
    <script src="https://js.arcgis.com/4.15/"></script>

    <style>
        html,
        body,
        #viewDiv {
            padding: 0;
            margin: 0;
            width: 100%;
            height: 100%;
        }

        html,
        body {
            height: 100%;
        }

        .legend {
            background-color: #fff;
            border-radius: 3px;
            bottom: 30px;
            box-shadow: 0 1px 2px rgba(0,0,0,0.10);
            font: 15px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
            padding: 10px;
            position: absolute;
            right: 10px;
            z-index: 1;
        }
        .legend h4 {
            margin: 0 0 10px;
        }
        .legend div span {
            border-radius: 50%;
            display: inline-block;
            height: 15px;
            margin-right: 10px;
            width: 15px;
            color:orangered
        }
        #tooltip:empty {
            display: none;
        }
        #tooltip {
            font-family: Helvetica, Arial, sans-serif;
            font-size: 11px;
            position: absolute;
            padding: 4px;
            margin: 8px;
            background: rgba(0, 0, 0, 0.8);
            color: #fff;
            max-width: 300px;
            font-size: 10px;
            z-index: 9;
            pointer-events: none;
        }
    </style>

    <script>
        // We use the module we just defined, together with the other "standard" ArcGIS modules
        require([
            "esri/Map",
            "esri/views/SceneView",
            "esri/views/3d/externalRenderers"
        ], function (Map, SceneView, externalRenderers) {
            const GeoJsonLayer = deck.GeoJsonLayer;
            const ArcLayer = deck.ArcLayer;

            const mesh_1km = "https://raw.githubusercontent.com/valuecreation/mapbox-prj/b014b62e2c4db92726ca35ca8ec9a52b2acd5f28/data/1km_mesh_2018_13.geojson";

            const COLOR_SCALE = [
                [215, 25, 28], // 0 - 4000
                [232, 91, 58], // 4000 - 8000
                [249, 158, 89], // 8000 - 12000
                [254, 201, 128], // 12000 - 16000
                [255, 237, 170], // 16000 - 20000
                [237, 247, 201], // 20000 - 24000
                [199, 230, 219], // 24000 - 28000
                [157, 207, 228], // 28000 - 32000
                [100, 165, 205], // 32000 - 36000
                [44, 123, 182] // 36000 - 40000
            ];

            function getLayers() {
                return [
                    new GeoJsonLayer({
                        id: "1km_mesh_2018_13",
                        data: mesh_1km,
                        opacity: 0.7,
                        stroked: false,
                        filled: true,
                        extruded: true,
                        wireframe: true,
                        fp64: true,
                        getElevation: f => f.properties.PT0_2050,
                        getFillColor: f => colorScale(f.properties.PT0_2050),
                        getLineColor: [255, 255, 255],
                        pickable: true,
                        onHover: updateTooltip
                    })
                ];
            }

            deck.loadArcGISModules().then(function ({
                DeckRenderer
            }) {
                const sceneView = new SceneView({
                    container: "viewDiv",
                    map: new Map({
                        basemap: "dark-gray-vector"
                    }),
                    environment: {
                        atmosphereEnabled: false
                    },
                    camera: {
                        position: {
                            x: 139.767125,
                            y: 35.081236,
                            z: 100000
                        },
                        tilt: 45,
                        //heading: 35
                    },
                    viewingMode: "local"
                });

                const extren = new DeckRenderer(sceneView, {
                    layers: getLayers()
                });

                externalRenderers.add(sceneView, extren);
            });

            function colorScale(x) {

                if (0 >= x && x < 4000) {
                    return COLOR_SCALE[0];
                } else if (4000 >= x && x < 8000) {
                    return COLOR_SCALE[1];
                } else if (8000 >= x && x < 8000) {
                    return COLOR_SCALE[2];
                } else if (12000 >= x && x < 16000) {
                    return COLOR_SCALE[3];
                } else if (16000 >= x && x < 20000) {
                    return COLOR_SCALE[4];
                } else if (20000 >= x && x < 24000) {
                    return COLOR_SCALE[5];
                } else if (24000 >= x && x < 28000) {
                    return COLOR_SCALE[6];
                } else if (28000 >= x && x < 32000) {
                    return COLOR_SCALE[7];
                } else if (32000 >= x && x < 36000) {
                    return COLOR_SCALE[8];
                } else if (36000 >= x && x < 40000) {
                    return COLOR_SCALE[9];
                } else if (40000 > x) {
                    return COLOR_SCALE[9];
                }

            }

            function updateTooltip({x, y, object}) {
                const tooltip = document.getElementById('tooltip');
                if (object) {
                    tooltip.style.top = `${y}px`;
                    tooltip.style.left = `${x}px`;
                    tooltip.innerHTML = `
                    <div><b>市区町村コード &nbsp;</b></div>
                    <div><div>${object.properties.SHICODE}</div></div>
                    <div><b>2050年 総人口数(男女計)</b></div>
                    <div>${Math.round(object.properties.PT0_2050)}人</div>
                    `;
                } else { 
                    tooltip.innerHTML = '';
                }
            }

        });
    </script>
</head>

<body>
    <div id="tooltip"></div>
    <div id="viewDiv"></div>
    <div id='county-legend' class='legend'>
        <h4>1kmメッシュ別将来推計人口 2050年</h4>
        <div><span style='background-color: rgb(215, 25, 28)'></span>0 - 4,000</div>
        <div><span style='background-color: rgb(232, 91, 58)'></span>4,000 - 8,000</div>
        <div><span style='background-color: rgb(249, 158, 89)'></span>8,000 - 12,000</div>
        <div><span style='background-color: rgb(254, 201, 128)'></span>12,000 - 16,000</div>
        <div><span style='background-color: rgb(255, 237, 170)'></span>16,000 - 20,000</div>
        <div><span style='background-color: rgb(237, 247, 201)'></span>20,000 - 24,000</div>
        <div><span style='background-color: rgb(199, 230, 219)'></span>24,000 - 28,000</div>
        <div><span style='background-color: rgb(157, 207, 228)'></span>28,000 - 32,000</div>
        <div><span style='background-color: rgb(100, 165, 205)'></span>32,000 - 36,000</div>
        <div><span style='background-color: rgb(44, 123, 182)'></span>36,000 - 40,000</div>
    </div>
</body>

</html>

さいごに

ArcGIS API for JavaScript で deck.gl を利用した例を幾つか紹介しました。 deck.gl もバージョンアップが多く常に進化していますので、今後もキャッチアップをしていきたいと思います。次回は、deck.gl で I3S を使用した例が紹介できればと思います。