MapLibre GL JSのaddProtocolとは何なのか

![mug-jp_logo_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/8d017f83-8e52-aee7-f6b2-8c75ccd7df34.png)

## はじめに

### MapLibreとは

MapLibre Official WebSite
https://maplibre.org

- Web地図に関するプロダクトをOSSとして開発・メンテナンスしているOrganizationです
- ブラウザ・モバイル向けの地図ライブラリおよび地図配信に関するツール群があります


### MapLibre User Group Japan (MUG-JP) とは

MUG-JP Official Web Site
https://mug-jp.org/

- MapLibreプロダクトの開発者によるユーザーコミュニティです
- ナレッジの共有やイベントの開催による普及活動を行なっています

## v2で突如生えてきた`addProtocol()`関数

`addProtocol()`の経緯などについては以下の記事が詳しいです。そういえば昔は`mapbox://`というプロトコルが使えたっけ…。

https://qiita.com/mg_kudo/items/cc88be1ed46fa77871fd

MapLibre Styleでは、`source`でタイル等のURLを定義しますが、その際のプレフィックスを`protocol`と呼びます（http/https...）。`https://`ならもちろんデフォルトの挙動（単にデータをダウンロードする）となりますが、ここに独自のprotocolを定義するのが`addProtocol`という訳です。

`addProtocol()`という言葉だけだと意味がわかりづらいので、ユーザーがタイルデータを自由に生成出来る`custom source`と理解するのが良いと思います。

## addProtocolで定義するカスタムプロトコル

割となんでも出来て、ルールはたったひとつ＝<b>「タイルデータをreturnする」</b>ことだけです。

以下の例では、クライアントサイドでオンメモリで生成したPNG画像をreturnしています（fetchせずcanvasで画像生成しているのがポイント）。

https://svelte-maplibre-gl.mierune.dev/examples/custom-protocol より引用

```typescript
maplibregl.addProtocol('myprotocol', async (params) => {
    const zxy = params.url.replace('myprotocol://', '');
    const [z, x, y] = zxy.split('/').map((v) => parseInt(v, 10));

    const png = await new Promise((resolve) => {
      const canvas = document.createElement('canvas');
      canvas.width = 512;
      canvas.height = 512;
      const context = canvas.getContext('2d')!;

      // checkered pattern
      context.fillStyle = (z + x - y) % 2 === 0 ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.4)';
      context.fillRect(0, 0, canvas.width, canvas.height);
      context.fillStyle = 'white';
      context.font = '32px sans-serif';
      context.fillText(`${z}/${x}/${y}`, 32, 64);

      // canvas to blob (png) to arraybuffer
      canvas.toBlob(async (blob) => {
        resolve(await blob!.arrayBuffer());
      });
    });

    return { data: png };
});
```

また、以下の例ではベクトルタイルのバイナリをオンメモリで生成しています。

https://qiita.com/frogcat/items/b38b5e37535b65c5464b

## addProtocolを活用しているライブラリ群

https://github.com/mug-jp/maplibre-gl-gsi-terrain

https://github.com/onthegomap/maplibre-contour

https://github.com/geomatico/maplibre-cog-protocol

https://github.com/jimmyrocks/maplibre-gl-vector-text-protocol

https://github.com/protomaps/PMTiles

## 余談：`addProtocol()`が実装されたあたりにあった会話

筆者所属のMIERUNEがMapLibre Orgにスポンサーしたときに、MIERUNEがMapLibreをどう利用しているか的なインタビューがあり：

https://maplibre.org/news/2022-06-10-mierune-announcement/#have-you-ever-used-maplibre-in-a-way-you-never-thought-you-would

![Screenshot 2024-12-07 at 10.55.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/8b95df45-3ab6-586e-9ab2-4d0a22da3a0f.png)

メンテナーたちも、現状のような使われ方は予想していなかったのかもしれませんね。`addProtocol()`がユーザー定義のsourceの作成を可能としたことで、図らずもいわゆる「プラグイン機構」を提供するものとなったということでしょう。当初、[関数のシグネチャもおよそサードパーティに利用させるような作りではなかった](https://qiita.com/Kanahiro/items/70b3b8b11bd26cbaf30e#typescript)のも思い出されます（値を返すのが`callback(null, png, null, null)`という関数だった。v4で綺麗になった）。

## 余談：MapLibre Nativeではどうなっているのか

MapLibre Nativeでは、そもそもデータへのリクエストは全て自分で定義する必要があります。以下は筆者が開発している`chiitiler`のコードです。

https://github.com/Kanahiro/chiitiler/blob/877d53ba7c60566c65aeb12f7cf97c4b63b74681/src/render/pool.ts#L58

データフェッチングは全てこの関数を通るので、プロトコルをチェックして条件分岐させています。

ここで、GL JSとNativeは違う仕組みで動いていることに注意が必要です。つまり`addProtocol`に過度に依存したstyleはNativeでは利用出来ないということです。