MapLibre GL JS + deck.gl + Google Photorealistic 3D Tilesを試す

# はじめに

- [deck.gl](https://deck.gl/)を用いて[GoogleのPhotorealistic 3D Tiles](https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja#work_with_deckgl)を表示する方法は下記の記事に書きました。
- 今回の記事では、[MapLibre GL JS](https://maplibre.org/)と[deck.gl](https://deck.gl/)を用いて、[GoogleのPhotorealistic 3D Tiles](https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja#work_with_deckgl)を表示する方法について説明します。

https://qiita.com/syanseto/items/16f6184890bdc2b4cd3e

# 前提条件
- Google Maps PlatformのAPIキーを取得済みであること。
>※Google Maps Platform APIキーの取得・発行についてはこちらを参照してください。
https://www.zenrin-datacom.net/business/gmapsapi/api_key/index.html

# deck.glのレイヤーをMapLibre GL JSに組み込む方法
- MapLibre GL JSでGoogleのPhotorealistic 3D Tilesを表示するためには、deck.glのTile3DLayerを使用する必要があります。
- deck.glのTile3DLayerをMapLibre GL JSに組み込むには主に3つの方法があるようです。
- 下記のブログ記事に詳しく書かれています（おすすめは方法２とのこと📝）。
- 方法１、方法２及び方法３について紹介します。

https://nagix.hatenablog.com/entry/2022/12/31/011441

方法１：MapboxOverlayを使ったオーバーレイ
方法２：MapboxOverlayを使ったインターリーブ（おすすめ📝）

https://deck.gl/docs/api-reference/mapbox/mapbox-overlay

- オーバーレイ、インターリーブの違いは[こちらのページの図](https://deck.gl/docs/get-started/using-with-map)がわかりやすいです。
- オーバーレイは、2つのライブラリが相互に独立してレンダリングを管理する仕様のようです。
- 一方で、インターリーブは、deck.glのレイヤーとMapLibre GL JSのレイヤーのオクルージョンが可能になるようです（方法２がおすすめな理由はここにありそうです📝）。

https://deck.gl/docs/get-started/using-with-map

方法３：MapboxLayerを使ったインターリーブ
- 方法３は、上記の方法２に近い仕様のようです（オクルージョンが可能かなどは試して確認したいところです）。

https://deck.gl/docs/api-reference/mapbox/mapbox-layer

https://docs.maptiler.com/deck-gl/interleaved/

# MapboxOverlayを使ったオーバーレイ
- Google Maps PlatformのAPIキーは、ご自身で用意してください。
- GoogleのPhotorealistic 3D Tilesの表示には、deck.glのMapboxOverlay及びTile3DLayerを使用します。
- MapboxOverlayのオーバーレイは、interleavedをfalseにすることで機能するようです。
- map.addControlでdeck.glのレイヤーをMapLibre GL JSのコントロールとして追加できるようです。

## コード
```html
<html>

<head>
  <title>Google 3D tiles example</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
  <link href="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet">
  <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    #credits {
      position: absolute;
      bottom: 0;
      right: 0;
      padding: 2px;
      font-size: 15px;
      color: white;
      text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
      z-index: 1000;
    }

    #credits a {
      color: white;
      pointer-events: auto;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <div id="credits"></div>
  <script type="text/javascript">
    // 【参考】https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja
    const GOOGLE_API_KEY = 'YOUR_API_KEY';
    const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
    const creditsElement = document.getElementById('credits');

    const map = new maplibregl.Map({
      container: 'map',
      style: {
        version: 8,
        sources: {},
        layers: []
      },
      hash: true,
      zoom: 16,
      center: [139.7660988, 35.6808658],
      pitch: 60,
      bearing: 0,
      attributionControl: false,
    })

    // ズーム・回転
    map.addControl(new maplibregl.NavigationControl());

    // フルスクリーンモードのオンオフ
    map.addControl(new maplibregl.FullscreenControl());

    // 現在位置表示
    map.addControl(new maplibregl.GeolocateControl({
      positionOptions: {
        enableHighAccuracy: false
      },
      fitBoundsOptions: { maxZoom: 18 },
      trackUserLocation: true,
      showUserLocation: true
    }));

    // スケール表示
    map.addControl(new maplibregl.ScaleControl({
      maxWidth: 200,
      unit: 'metric'
    }));

    map.on('load', () => {
      // MapboxOverlayを使ったオーバーレイ
      const overlay = new deck.MapboxOverlay({
        interleaved: false,
        layers: [
          new deck.Tile3DLayer({
            id: 'google-3d-tiles',
            data: TILESET_URL,
            loadOptions: {
              fetch: {
                headers: {
                  'X-GOOG-API-KEY': GOOGLE_API_KEY
                }
              }
            },
            onTilesetLoad: tileset3d => {
              tileset3d.options.onTraversalComplete = selectedTiles => {
                const credits = new Set();
                selectedTiles.forEach(tile => {
                  const { copyright } = tile.content.gltf.asset;
                  copyright.split(';').forEach(credits.add, credits);
                  creditsElement.innerHTML = [...credits].join('; ');
                });
                return selectedTiles;
              }
            }
          })
        ]
      });

      map.addControl(overlay);

    })
  </script>
</body>

</html>
```

# MapboxOverlayを使ったインターリーブ
- Google Maps PlatformのAPIキーは、ご自身で用意してください。
- GoogleのPhotorealistic 3D Tilesの表示には、deck.glのMapboxOverlay及びTile3DLayerを使用します。
- MapboxOverlayのインターリーブは、interleavedをtrueにすることで機能するようです。
- map.addControlでdeck.glのレイヤーをMapLibre GL JSのコントロールとして追加できるようです。

## コード
```html
<html>

<head>
  <title>Google 3D tiles example</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
  <link href="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet">
  <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    #credits {
      position: absolute;
      bottom: 0;
      right: 0;
      padding: 2px;
      font-size: 15px;
      color: white;
      text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
      z-index: 1;
    }

    #credits a {
      color: white;
      pointer-events: auto;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <div id="credits"></div>
  <script type="text/javascript">
    // 【参考】https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja
    const GOOGLE_API_KEY = 'YOUR_API_KEY';
    const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
    const creditsElement = document.getElementById('credits');

    const map = new maplibregl.Map({
      container: 'map',
      style: {
        version: 8,
        sources: {},
        layers: []
      },
      hash: true,
      zoom: 16,
      center: [139.7660988, 35.6808658],
      pitch: 60,
      bearing: 0,
      attributionControl: false,
    })

    // ズーム・回転
    map.addControl(new maplibregl.NavigationControl());

    // フルスクリーンモードのオンオフ
    map.addControl(new maplibregl.FullscreenControl());

    // 現在位置表示
    map.addControl(new maplibregl.GeolocateControl({
      positionOptions: {
        enableHighAccuracy: false
      },
      fitBoundsOptions: { maxZoom: 18 },
      trackUserLocation: true,
      showUserLocation: true
    }));

    // スケール表示
    map.addControl(new maplibregl.ScaleControl({
      maxWidth: 200,
      unit: 'metric'
    }));

    map.on('load', () => {
      // MapboxOverlayを使ったインターリーブ
      const overlay = new deck.MapboxOverlay({
        interleaved: true,
        layers: [
          new deck.Tile3DLayer({
            id: 'google-3d-tiles',
            data: TILESET_URL,
            loadOptions: {
              fetch: {
                headers: {
                  'X-GOOG-API-KEY': GOOGLE_API_KEY
                }
              }
            },
            onTilesetLoad: tileset3d => {
              tileset3d.options.onTraversalComplete = selectedTiles => {
                const credits = new Set();
                selectedTiles.forEach(tile => {
                  const { copyright } = tile.content.gltf.asset;
                  copyright.split(';').forEach(credits.add, credits);
                  creditsElement.innerHTML = [...credits].join('; ');
                });
                return selectedTiles;
              }
            },
            beforeId: 'admin_labels'
          })
        ]
      });

      map.addControl(overlay);

    })
  </script>
</body>

</html>
```

# MapboxLayerを使ったインターリーブ
- Google Maps PlatformのAPIキーは、ご自身で用意してください。
- GoogleのPhotorealistic 3D Tilesの表示には、deck.glのMapboxLayer及びTile3DLayerを使用します。
- map.addLayerでdeck.glのレイヤーがMapLibre GL JSのカスタムスタイルレイヤー（よくわかっていません・・・）として追加されるようです。

## コード
```html
<html>

<head>
  <title>Google 3D tiles example</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
  <link href="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet">
  <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    #credits {
      position: absolute;
      bottom: 0;
      right: 0;
      padding: 2px;
      font-size: 15px;
      color: white;
      text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
      z-index: 1;
    }

    #credits a {
      color: white;
      pointer-events: auto;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <div id="credits"></div>
  <script type="text/javascript">
    // 【参考】https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja
    const GOOGLE_API_KEY = 'YOUR_API_KEY';
    const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
    const creditsElement = document.getElementById('credits');

    const map = new maplibregl.Map({
      container: 'map',
      style: {
        version: 8,
        sources: {},
        layers: []
      },
      hash: true,
      zoom: 16,
      center: [139.7660988, 35.6808658],
      pitch: 60,
      bearing: 0,
      attributionControl: false,
    })

    // ズーム・回転
    map.addControl(new maplibregl.NavigationControl());

    // フルスクリーンモードのオンオフ
    map.addControl(new maplibregl.FullscreenControl());

    // 現在位置表示
    map.addControl(new maplibregl.GeolocateControl({
      positionOptions: {
        enableHighAccuracy: false
      },
      fitBoundsOptions: { maxZoom: 18 },
      trackUserLocation: true,
      showUserLocation: true
    }));

    // スケール表示
    map.addControl(new maplibregl.ScaleControl({
      maxWidth: 200,
      unit: 'metric'
    }));

    map.on('load', () => {

      const { MapboxLayer, Tile3DLayer } = deck;

      // MapboxLayerを使ったインターリーブ
      const tile3dLayer = new MapboxLayer({
        id: 'google-3d-tiles',
        type: Tile3DLayer,
        data: TILESET_URL,
        loadOptions: {
          fetch: {
            headers: {
              'X-GOOG-API-KEY': GOOGLE_API_KEY
            }
          }
        },
        onTilesetLoad: tileset3d => {
          tileset3d.options.onTraversalComplete = selectedTiles => {
            const credits = new Set();
            selectedTiles.forEach(tile => {
              const { copyright } = tile.content.gltf.asset;
              copyright.split(';').forEach(credits.add, credits);
              creditsElement.innerHTML = [...credits].join('; ');
            });
            return selectedTiles;
          }
        }
      });

      map.addLayer(tile3dLayer);

    })
  </script>
</body>

</html>
```

# 参考文献
https://developers.google.com/maps/documentation/tile/use-renderer?hl=ja#work_with_deckgl

https://nagix.hatenablog.com/entry/2022/12/31/011441

https://deck.gl/docs/api-reference/mapbox/mapbox-overlay

https://deck.gl/docs/api-reference/mapbox/mapbox-layer