MapLibre GL JSにdeck.glレイヤーを組み込む方法

# はじめに
:::note info
この記事は [MapLibre Advent Calendar 2023](https://qiita.com/advent-calendar/2023/maplibre) 20日目の記事です。
:::
[MapLibre GL JS](https://maplibre.org/maplibre-gl-js/docs/)は2.5次元のインタラクティブな地図を描画するライブラリですが、一方で、3Dのデータ表現が弱いというところがあります。そこで、便利な方法として、3Dのデータ表現に長けている[deck.gl](https://deck.gl/)のレイヤーをMapLibre GL JSに組み込む方法を紹介したいと思います。具体には、[3D都市モデルPLATEAU 3D Tiles](https://github.com/Project-PLATEAU/plateau-streaming-tutorial/blob/e4f5708633d0a62233b8b02a6f9340f50e120556/3d-tiles/plateau-3dtiles-streaming.md)や[3次元点群データ](https://opennagasaki.nerc.or.jp/)をdeck.glの[Tile3DLayer](https://deck.gl/docs/api-reference/geo-layers/tile-3d-layer)を用いて、MapLibre GL JSにレイヤーとして組み込む方法について紹介します。

# MapLibre GL JSにdeck.glレイヤーを組み込む方法
MapLibre GL JSにdeck.glレイヤーを組み込む方法には主に3つの方法があるようです。詳細については、下記の記事に詳しく書かれています。

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

**1. [MapboxOverlay](https://deck.gl/docs/api-reference/mapbox/mapbox-overlay)を使ったオーバーレイ**
**2. [MapboxOverlay](https://deck.gl/docs/api-reference/mapbox/mapbox-overlay)を使ったインターリーブ**
**3. [MapboxLayer](https://deck.gl/docs/api-reference/mapbox/mapbox-layer)を使ったインターリーブ**

上記の記事の中で紹介されている、3つの方法におけるオーバーレイ、インターリーブの違いは[こちらのページの図](https://deck.gl/docs/get-started/using-with-map)がわかりやすいです。

:::note info
**オーバーレイ**は、**2つのライブラリが相互に独立してレンダリング**する仕様のようで、2Dマップに適しているようです。一方で、**インターリーブ**は、deck.glのレイヤーとMapLibre GL JSのレイヤーの**オクルージョン（手前にある物体が後ろにある物体を隠す状態）が可能になる**ようで、3Dマップに適しているようです。
:::

今回は、上記の記事で紹介されている方法のうち、deck.glの[**MapboxLayerを使ったインターリーブ**](https://deck.gl/docs/api-reference/mapbox/mapbox-layer)を使用して、MapLibre GL JSで3D都市モデルPLATEAU 3D Tilesや3次元点群データを表示します。

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

なお、3つの方法については、下記の記事でも[Google Photorealistic 3D Tiles](https://developers.google.com/maps/documentation/tile/3d-tiles?hl=ja)を用いて、MapLibre GL JSにdeck.glレイヤーを組み込む方法を試しているのであわせてご覧ください。

https://qiita.com/shi-works/items/3f918d84b3ff9b2db580

# 3D都市モデルPLATEAU 3D TilesをMapLibre GL JSで表示する
[3D都市モデルPLATEAU 3D Tiles](https://github.com/Project-PLATEAU/plateau-streaming-tutorial/blob/e4f5708633d0a62233b8b02a6f9340f50e120556/3d-tiles/plateau-3dtiles-streaming.md)をdeck.glの[Tile3DLayer](https://deck.gl/docs/api-reference/geo-layers/tile-3d-layer)を用いて、MapLibre GL JSにレイヤーとして組み込みます。

# デモサイト

https://shi-works.github.io/PLATEAU-3DTiles-on-MapLibre-GL-JS-Demo/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/9057b5f4-8c69-523d-bd4d-69299c855f6a.png)

**東京駅周辺**

# ソースコードと解説
- 全体のソースコードは、下記のGitHubから入手してください。

https://github.com/shi-works/PLATEAU-3DTiles-on-MapLibre-GL-JS-Demo

- 解説は下記のとおりになります。
- 3D Tilesのローダーには、[loaders.glのTiles3DLoader](https://loaders.gl/docs/modules/3d-tiles/api-reference/tiles-3d-loader)を使用します。
```html:html
<html>

<head>
  <title>3D都市モデルPLATEAU（3D Tiles）</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js"></script>
  <link href="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css" rel="stylesheet">
  <script src="https://unpkg.com/pmtiles@2.10.0/dist/index.js"></script>
  <script src="https://unpkg.com/deck.gl@8.9.0/dist.min.js"></script>
  <script src="https://unpkg.com/@loaders.gl/3d-tiles@^2.3.0/dist/dist.min.js"></script>
```

- `MapboxLayer`で3D都市モデルPLATEAU 3D Tilesを定義します。
- `loader`に`Tiles3DLoader`を指定します。
- `data`に3D都市モデルPLATEAU 3D TilesのURL（tileset.jsonのURL）を指定します。
- 3D Tilesの配信URLは下記より確認できます。

https://github.com/Project-PLATEAU/plateau-streaming-tutorial/blob/e4f5708633d0a62233b8b02a6f9340f50e120556/3d-tiles/plateau-3dtiles-streaming.md

:::note info
今回、3D都市モデルPLATEAU 3D Tilesは[**2021年度末時点**で公開されている3D Tilesデータ](https://github.com/Project-PLATEAU/plateau-streaming-tutorial/blob/e4f5708633d0a62233b8b02a6f9340f50e120556/3d-tiles/plateau-3dtiles-streaming.md)を使用しています。今回の方法で最新の3D Tilesデータ（2022年度末時点）を使用すると3Dモデルが表示されませんでした。ちなみに、[QGIS3.34で3D Tilesを読み込めるようになりました](https://www.lutraconsulting.co.uk/blog/2023/11/16/3d-tiles-in-QGIS/)が、最新の3D Tilesデータは表示できませんでした。現時点の最新の3D Tilesデータは下記になります。
https://github.com/Project-PLATEAU/plateau-streaming-tutorial/blob/main/3d-tiles/plateau-3dtiles-streaming.md
:::

- `onTileLoad`以下は、3Dモデルの高さをマップ平面に合うようにするための調整です。
- 詳細については、下記の記事が詳しいです。

https://nagix.hatenablog.com/entry/2022/12/27/203825

- 最終的に、`content.cartographicOrigin.z`で建築物の基礎の標高に加えて東京駅付近のジオイド高 36.6641m を引いて、3Dモデルの高さをマップ平面に合うように調整しています。
- `map.addLayer(tile3dLayer)`で3D都市モデルPLATEAU 3D Tilesレイヤを追加します。

```javascript:javascript
    map.on('load', () => {
      // MapboxLayerを使ったインターリーブ
      const tile3dLayer = new deck.MapboxLayer({
        id: '13101_chiyoda-ku',
        type: deck.Tile3DLayer,
        // 3D都市モデルPLATEAU 3DTilesソース
        data: 'https://plateau.geospatial.jp/main/data/3d-tiles/bldg/13100_tokyo/13101_chiyoda-ku/low_resolution/tileset.json', // 千代田区
        loader: Tiles3DLoader,
        opacity: 1,
        // 【参考】https://nagix.hatenablog.com/entry/2022/12/27/203825
        onTileLoad: d => {
          const { content } = d;
          const buffer = content.batchTableBinary.buffer;
          const key = content.batchTableJson;
          const len = key._gml_id.length;
          const zMinView = new DataView(buffer, key._zmin.byteOffset, len * 8);
          const zMins = [];

          for (let i = 0; i < len; i++) {
            zMins.push(zMinView.getFloat64(i * 8, true));
          }
          zMins.sort((a, b) => a - b);
          content.cartographicOrigin.z -= 36.6641 + zMins[Math.floor(len / 2)];
        }
      });

      // 3D都市モデルPLATEAU 3DTilesレイヤ
      map.addLayer(tile3dLayer);

    })
```

# 3次元点群データ（3D Tiles）をMapLibre GL JSで表示する
[3次元点群データ](https://opennagasaki.nerc.or.jp/)をdeck.glの[Tile3DLayer](https://deck.gl/docs/api-reference/geo-layers/tile-3d-layer)を用いて、MapLibre GL JSにレイヤーとして組み込みます。

:::note info
[長崎県がオープンデータとして公開している3次元点群データ（LAS形式）](https://opennagasaki.nerc.or.jp/)から3D Tilesを生成して、MapLibre GL JS+deck.glで表示しています。3次元点群データから3D Tilesを生成する方法については、Zennで記事を書いていますので、あわせてご覧ください。
:::

https://zenn.dev/shi_works/articles/b01b67be8b4702

# デモサイト

https://shi-works.github.io/nagasaki-point-cloud-map-on-maplibre-gl-js-demo/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/7c9db183-7757-3b62-184f-2cce71e95794.png)

**長崎駅周辺**

# ソースコードと解説
- 全体のソースコードは、下記のGitHubから入手してください。

https://github.com/shi-works/nagasaki-point-cloud-map-on-maplibre-gl-js-demo

- 解説は下記のとおりになります。
- 3D Tilesのローダーには、[loaders.glのTiles3DLoader](https://loaders.gl/docs/modules/3d-tiles/api-reference/tiles-3d-loader)を使用します。
```html:html
<html>

<head>
  <title>長崎県 3次元点群データ（3DTiles）</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src='https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js'></script>
  <link href='https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css' rel='stylesheet' />
  <script src="https://unpkg.com/pmtiles@2.10.0/dist/index.js"></script>
  <script src="https://unpkg.com/deck.gl@8.9.0/dist.min.js"></script>
  <script src="https://unpkg.com/@loaders.gl/3d-tiles@^2.3.0/dist/dist.min.js"></script>
```

- `MapboxLayer`で3次元点群データ（3D Tiles）を定義します。
- `data`に3次元点群データ（3D Tiles）のURL（tileset.jsonのURL）を指定します。
- `loader`に`Tiles3DLoader`を指定します。
- `map.addLayer(tile3dLayer)`でレイヤを追加します。

```javascript:javascript
    map.on('load', () => {
      // MapboxLayerを使ったインターリーブ
      const tile3dLayer = new deck.MapboxLayer({
        id: "nagasaki-station",
        type: deck.Tile3DLayer,
        pointSize: 2,
        // 3次元点群データ（3DTiles）ソース
        data: 'https://xs489works.xsrv.jp/3dtiles/open_nagasaki/nagasaki_station/tileset.json',
        loader: Tiles3DLoader
      });

      // 3次元点群データ（3DTiles）レイヤ
      map.addLayer(tile3dLayer);

    })
```

# 参考文献
https://deck.gl/docs/api-reference/mapbox/mapbox-layer

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

https://qiita.com/Kanahiro/items/4ae702c575176501a157

https://nagix.hatenablog.com/entry/2022/12/27/203825

# おわりに
MapLibre GL JSにdeck.glのレイヤーを組み込む方法についてご紹介しました。deck.glのTile3DLayerをMapLibre GL JSにレイヤとして組み込むことで、MapLibre GL JSで3D都市モデルPLATEAU 3D Tilesや3次元点群データ（3D Tiles）を表示することができます。deck.glには、Tile3DLayer以外にも可視化のための様々な[レイヤー](https://deck.gl/examples)が用意されています。例えば、地点間の繋がりを3Dの弧で表現できる[ArcLayer](https://deck.gl/docs/api-reference/layers/arc-layer)やデータをヘキサゴン（六角形）で集約してヒートマップとして表現できる[HexagonLayer](https://deck.gl/docs/api-reference/aggregation-layers/hexagon-layer)など表現力の高いレイヤーを利用することもできます。