【deck.gl】react-map-glを使って背景地図を表示する

## はじめに
react-map-glを使って背景地図を表示し、地図コントロール用のボタンを配置しました。
deck.glとの組み合わせでは、ハマりポイントもあって苦戦したので共有します。

## 環境構築
### Reactプロジェクト作成
deck.glはReactとの親和性がよいので、Reactを使います。

TypeScriptを使う場合は [こちら](https://qiita.com/asahina820/items/6ead76abf4fc7f187dfc) を参考にしてください（型チェックでハマりやすいのでJavaScriptがおすすめです。ちなみに公式ドキュメントに型が書いてないのでソースをたどる必要があります・・）

```powershell
npx create-react-app sample
```

### ライブラリインストール
deck.glをインストールします。

```powershell
npm install deck.gl --save
```

react-map-glをインストールします。
MapLibre GL JSを使う場合は、`mapbox-gl` を`maplibre-gl` に変えてください。
```powershell
npm install --save react-map-gl mapbox-gl
```

## 実装
### 背景地図表示
deck.glの公式ドキュメントの、[MapboxOverlay](https://deck.gl/docs/api-reference/mapbox/mapbox-overlay#using-with-react-map-gl)を参考に実装します。
deck.glを react-map-gl v7のコントロールで使用するには、MapboxOverlayを使用する必要があります。

```react:App.js
import React from 'react';
import { LineLayer } from '@deck.gl/layers';
import { MapboxOverlay } from '@deck.gl/mapbox';
import Map, { useControl } from 'react-map-gl';
// Mapbox GL JS
import 'mapbox-gl/dist/mapbox-gl.css';
import './style.css';

const App = () => {
    const DeckGLOverlay = (props) => {
        const overlay = useControl(() => new MapboxOverlay(props));
        overlay.setProps(props);
        return null;
    };
    // Mapboxアクセストークン
    const MAPBOX_ACCESS_TOKEN = 'your_access_token';
    // 初期ビューポートの設定
    const INITIAL_VIEW_STATE = {
        latitude: 38.875584,
        longitude: 139.7454316,
        bearing: 0,
        pitch: 0,
        zoom: 5,
    };
    // LineLayerで使うデータ
    const data = [{ sourcePosition: [139.7454316, 38.875584], targetPosition: [145.7454316, 40.875584] }];
    // LineLayer
    const layers = [new LineLayer({ id: 'line-layer', data })];
    return (
        <div className="App">
            <Map
                initialViewState={INITIAL_VIEW_STATE}
                mapStyle="mapbox://styles/mapbox/dark-v9"
                style={{ width: '100vw', height: '100vh' }}
                mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
            >
                <DeckGLOverlay layers={layers} />
            </Map>
        </div>
    );
};

export default App;
```

公式ドキュメントはTypeScriptで書かれていたのでJavaScriptで書き直しました。
公式ドキュメントにはMapの`style`を設定していませんが、設定しないと地図が表示されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/6ff3fc87-3ebb-ad20-8bad-3e8093a3eb47.png)

背景地図と、deck.glのLineLayerが表示できました！

### コントロール用のボタン配置
react-map-glには地図をコントロールするためのパーツがいくつか用意されています。
詳細は[APIリファレンス](https://visgl.github.io/react-map-gl/)をご覧ください。

今回は、現在地を表示できる`GeolocateControl`、全画面モードに切り替えることができる`FullscreenControl`、ズームイン・ズームアウトができる`NavigationControl`、スケールを表示できる`ScaleControl`を追加してみます。

実装は簡単です。インポートして、コンポーネントを追加するだけです。

```react:App.js
import React from 'react';
import { LineLayer } from '@deck.gl/layers';
import { MapboxOverlay } from '@deck.gl/mapbox';
import Map, { useControl, NavigationControl, FullscreenControl, ScaleControl, GeolocateControl } from 'react-map-gl';
// Mapbox GL JS
import 'mapbox-gl/dist/mapbox-gl.css';
import './style.css';

const App = () => {
    const DeckGLOverlay = (props) => {
        const overlay = useControl(() => new MapboxOverlay(props));
        overlay.setProps(props);
        return null;
    };
    // Mapboxアクセストークン
    const MAPBOX_ACCESS_TOKEN = 'your_access_token';
    // 初期ビューポートの設定
    const INITIAL_VIEW_STATE = {
        latitude: 38.875584,
        longitude: 139.7454316,
        bearing: 0,
        pitch: 0,
        zoom: 5,
    };
    // LineLayerで使うデータ
    const data = [{ sourcePosition: [139.7454316, 38.875584], targetPosition: [145.7454316, 40.875584] }];
    // LineLayer
    const layers = [new LineLayer({ id: 'line-layer', data })];
    return (
        <div className="App">
            <Map
                initialViewState={INITIAL_VIEW_STATE}
                mapStyle="mapbox://styles/mapbox/dark-v9"
                style={{ width: '100vw', height: '100vh' }}
                mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
            >
                <DeckGLOverlay layers={layers} />
                <GeolocateControl />
                <FullscreenControl />
                <NavigationControl />
                <ScaleControl />
            </Map>
        </div>
    );
};

export default App;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/073292f7-993f-b363-404f-f035c014bb28.png)

コントロール用のボタンなどを配置できました！
これらのパーツを簡単に導入できるのは便利ですね。

## 余談：ハマりポイント
deck.glの公式ドキュメントの、[Adding a Base Map](https://deck.gl/docs/get-started/using-with-react#adding-a-base-map)を参考に実装したらハマりました。
実はこれ、おすすめの方法ではありません・・

```react:App.js
import React from 'react';
import DeckGL from '@deck.gl/react';
import { LineLayer } from '@deck.gl/layers';
import { Map } from 'react-map-gl';
// Mapbox GL JS
import 'mapbox-gl/dist/mapbox-gl.css';
import './style.css';

const App = () => {
    // Mapboxアクセストークン
    const MAPBOX_ACCESS_TOKEN = 'your_access_token';
    // 初期ビューポートの設定
    const INITIAL_VIEW_STATE = {
        latitude: 38.875584,
        longitude: 139.7454316,
        bearing: 0,
        pitch: 0,
        zoom: 5,
    };
    // LineLayerで使うデータ
    const data = [{ sourcePosition: [139.7454316, 38.875584], targetPosition: [145.7454316, 40.875584] }];
    // LineLayer
    const layers = [new LineLayer({ id: 'line-layer', data })];
    return (
        <div className="App">
            <DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} layers={layers}>
                <Map mapboxAccessToken={MAPBOX_ACCESS_TOKEN} mapStyle="mapbox://styles/mapbox/dark-v9" />
            </DeckGL>
        </div>
    );
};

export default App;
```

公式ドキュメントには`import 'mapbox-gl/dist/mapbox-gl.css';` がありませんが、これがないとattributionなどのスタイルが崩れるのであったほうがいいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/9060a3b5-0c2a-2560-6003-1c338bfd0ed0.png)

こんな感じでdeck.glのレイヤとreact-map-glの背景地図を表示することができました！
しかし、これには問題点があります。
react-map-glで用意されている地図コントロール用のボタンを配置すると、ボタンを押すことができません。

```react:App.js
import React from 'react';
import DeckGL from '@deck.gl/react';
import { LineLayer } from '@deck.gl/layers';
import { Map, useControl, NavigationControl, FullscreenControl, ScaleControl, GeolocateControl } from 'react-map-gl';
// Mapbox GL JS
import 'mapbox-gl/dist/mapbox-gl.css';
import './style.css';

const App = () => {
    // Mapboxアクセストークン
    const MAPBOX_ACCESS_TOKEN = 'your_access_token';
    // 初期ビューポートの設定
    const INITIAL_VIEW_STATE = {
        latitude: 38.875584,
        longitude: 139.7454316,
        bearing: 0,
        pitch: 0,
        zoom: 5,
    };
    // LineLayerで使うデータ
    const data = [{ sourcePosition: [139.7454316, 38.875584], targetPosition: [145.7454316, 40.875584] }];
    // LineLayer
    const layers = [new LineLayer({ id: 'line-layer', data })];
    return (
        <div className="App">
            <DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} layers={layers}>
                <Map mapboxAccessToken={MAPBOX_ACCESS_TOKEN} mapStyle="mapbox://styles/mapbox/dark-v9">
                    <GeolocateControl />
                    <FullscreenControl />
                    <NavigationControl />
                    <ScaleControl />
                </Map>
            </DeckGL>
        </div>
    );
};

export default App;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/af56a536-1676-5022-a2e8-346c2ebc3b57.png)

ボタンを押しても無反応です。


開発者ツールを見てみると、
deck-wrapperのz-indexは0になっていますが、mapboxgl-mapのz-indexが-1になっています。
react-map-glのボタンたちは、重なり順が他の要素よりも下になっていたため、押せなかったということです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/1db31d36-fba1-8257-57d4-9674ae4b1a0b.png)

ためしにmapboxgl-mapのz-indexを0にしてみると、ボタンが押せるようになりました。
ですが、ズームイン・ズームアウトができません。
このようにdeck.gl>react-map-gl の並びは不都合があるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/df462648-cf3b-cc97-4b82-fcdf362a0881.png)


deck.glの公式ドキュメントの、[Using with Mapbox](https://deck.gl/docs/developer-guide/base-maps/using-with-mapbox#react-map-gl)にreact-map-glの言及がありました。


> To use deck.gl with react-map-gl v7's controls, you must use MapboxOverlay.

>日本語訳：
deck.gl を react-map-gl v7 のコントロールで使用するには、MapboxOverlay を使用する必要があります。

react-map-glの最新バージョンはv7なので、当てはまります。
これ、かなりハマりました。~~Get-Startedにも書いてほしかった~~

## おわりに
ボタンが押せない問題でかなり苦戦してしまいましたが、公式ドキュメントを漁ると解決策が書いてあったので無事に解決しました。
この手のトラップは多いです。。困ったら公式ドキュメントを見るクセをつけましょう。