deck.glを使ってGoogle Mapに大量のポリゴンを描画してみる

Google Mapはとても優れたプロダクトです。地図と言ったらGoogle Map、と思われる方も多いはずです。
地図を使ったアプリケーション開発においても、ユーザーに広く認知されたGoogle Mapを使いたいという要望を聞く機会が良くあります。

Google Mapを使ったアプリケーション開発を行う際に、Maps JavaScript APIを使って開発を行うことが多いと思います。Maps JavaScript APIはGoogle Mapを使ったWebアプリケーション開発を行う際に使用するJavaScriptライブラリです。
Maps JavaScript APIを使ったアプリケーション開発を進める中で、loadGeoJson()やaddGeoJson()を使って大量のデータを描画した際にパフォーマンスの問題が出てくることがあります。

この記事では、deck.glを使ってGoogle Mapに大量のデータを描画してパフォーマンスがどうなるか見てみます。

## deck.gl
[deck.gl](https://deck.gl/)はGPUを使って大規模なデータセットも高速に表現することができるデータビジュアライゼーションフレームワークです。
地図上に様々な方法でデータを表現することができます。

## 使用するデータ：半径50km円の町丁目ポリゴン
今回は大量のポリゴンを含むGeoJSONのデータをdeck.glで描画してみます。
東京駅付近を中心とした半径50km円の範囲の町丁目ポリゴンを使用します。約18,000町丁目の地物数で50MBほどのGeoJSONのデータになります。描画パフォーマンスには地物の数だけではなくポリゴンの頂点数も関わってきますが、今回は頂点数は考慮しません。
このGeoJSONのデータは[TerraMap API](https://www.mapmarketing.co.jp/terramap-api?grid=qiita&utm_source=qiita.com&utm_medium=referral&utm_campaign=qiita_deckgl_geojson_da2546c2f2bd4f996bf0_01 "エリアマーケティングシステム開発APIのTerraMap API")を使用して東京駅から半径50km円の範囲で取得しました。

# deck.glを使ってGoogle MapにGeoJSONを描画する
## deck.glとGoogle Mapの使用例
Google Mapでのdeck.glの使用方法や使用例はこちらをご確認ください。

https://cloud.google.com/blog/ja/products/maps-platform/richer-data-visualization-google-maps-platform-using-deckgl

https://developers.google.com/maps/documentation/javascript/deckgl-overlay-view?hl=ja

https://deck.gl/examples/google-maps

## deck.glを使ってGoogle MapにGeoJSONを描画してみる
``` js
// 事前に npm install deck.gl でdeck.glをインストール
// deck.glのGeoJsonLayerとGoogleMapsOverlayを使用する
import { GeoJsonLayer } from '@deck.gl/layers';
import { GoogleMapsOverlay } from "@deck.gl/google-maps";

(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
        ({key: "YOUR-GOOGLE-MAP-API-KEY",
        });

let map;
async function initMap() {
    const { Map } = await google.maps.importLibrary("maps");
    map = new Map(document.getElementById("map"), {
      center: { lat: 35.68437, lng: 139.75247 },
      zoom: 10,
    });  

    // GeoJSONレイヤーを作成
    const polygonLayer = new GeoJsonLayer({
        id: 'PolygonLayer',
        data: 'http://localhost:5173/area_50km.json',
        getFillColor: [120, 150, 180, 230],
        getLineColor: [220, 220, 220],
        getLineWidth: 0.3,
        lineWidthUnits: 'pixels',
        pickable: true,
    });

    // オーバーレイにレイヤーをセット
    const overlay = new GoogleMapsOverlay({
        layers: [polygonLayer],
    });
    
    overlay.setMap(map);
}

initMap();
```
実行結果
ズームレベル変更、地図移動ともにスムーズです。
![deckgl-googlemap2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/4c05f63d-c7a1-777e-7563-1053c31a032c.gif)

### googlemaps/js-api-loaderを使ってGoogle Mapを呼び出した場合
Google Mapの呼び出し方にはいくつか方法があります。（[Maps JavaScript API を読み込む](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#javascript)）
推奨は最初に提示したDynamic Library Importのようです。
以下はgooglemaps/js-api-loaderを使った場合です。「[Maps JavaScript API を読み込む](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#javascript)」のgooglemaps/js-api-loaderの説明に記載されているloader.load()は非推奨になっているため、以下のコードでは、load()ではなくimportLibrary()を使用しています。

``` js
// deck.glに加えて、npm install @googlemaps/js-api-loader しておく
import { Loader } from "@googlemaps/js-api-loader"
import { GeoJsonLayer } from '@deck.gl/layers';
import { GoogleMapsOverlay } from "@deck.gl/google-maps";

const loader = new Loader({
  apiKey: "YOUR-GOOGLE-MAP-API-KEY",
});

let map;
loader
  .importLibrary('maps')
  .then(({ Map }) => {
      map = new Map(document.getElementById("map"), {
        center: { lat: 35.68437, lng: 139.75247 },
        zoom: 10,
      });
      
      const polygonLayer = new GeoJsonLayer({
        id: 'PolygonLayer',
        data: 'http://localhost:5173/area_50km.json',
        getFillColor: [120, 150, 180, 230],
        getLineColor: [220, 220, 220],
        getLineWidth: 0.3,
        lineWidthUnits: 'pixels',
        pickable: true,
      });
    
      const overlay = new GoogleMapsOverlay({
        layers: [polygonLayer],
      });
      overlay.setMap(map);
  });
```
## deck.glじゃないとダメ？
deck.gl以外でも、GeoJSONやベクトルタイルなどの地理情報データをWebGLを活用して表示できるライブラリでも可能だと思います。
Google MapはWebGLのカスタムオーバーレイをサポートしていますので自分でコードを書くという手もあると思います。しかし、deck.glを使うとすごく簡単にできます。

また、余談ですが、Google Mapでは通常の地図とWebGL対応の地図があります。今回の記事のコードでは通常の地図を使用しました。[コード](https://github.com/visgl/deck.gl/blob/master/modules/google-maps/src/google-maps-overlay.ts)を見た感じdeck.glはGoogle Mapの通常地図/WebGL地図どちらのオーバーレイにも対応しているようです。

## まとめ
deck.glを使うと地図上に描画する地物が多い場合でもスムーズに操作することができます。大量のポリゴンやポイントの描画を行う際に地図の操作性が課題となっているようなケースではdeck.glは有効な方法だと思います。