PMTiles 消費における MapLibre GL JS v4 の破壊的変更への対応

## はじめに

今更ですが、MapLibre GL JS の v4.0.0 が2月にリリースされました。[変更点](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#400)の中には、タイルをロードする際に実行する処理を追加することができる `addProtocol` の破壊的変更が含まれています。

> Changes `addProtocol` to be promise-based without the usage of callbacks and cancelable

MapLibre GL JS において、`addProtocol` は [PMTiles](https://github.com/protomaps/PMTiles) の消費などにおいて、他のウェブ地図ライブラリには代替できない機能となっていると認識しています。

そのため、今回の破壊的変更が、これまでのコードにおいてどのように影響を及ぼすのか、簡単に確認していきます。

### 参考：過去の関連記事
* addProtocol の解説記事（v3 の場合）

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

* PMTiles の解説記事

https://qiita.com/mg_kudo/items/3179639c79c1734fd373

## v3 と v4 での記述方法の違い

例として、単純に https プロトコルで fetch する `custom://` というオリジナルプロトコルの設定方法を v3 と v4 で比較してみます。

```JavaScript: v3 の場合
maplibregl.addProtocol('custom', (params, callback) => {
  console.log(params);
  fetch(`https://${params.url.split("://")[1]}`)
  .then(t => {
    if (t.status == 200) {
      t.arrayBuffer().then(arr => {
        callback(null, arr, null, null);
      });
    } else {
      callback(new Error(`Tile fetch error: ${t.statusText}`));
    }
  })
  .catch(e => {
    callback(new Error(e));
  });
  return { cancel: () => { } };
});
```
```JavaScript: v4 の場合
maplibregl.addProtocol('custom', async (params, abortController) => {
  console.log(params);
  const t = await fetch(`https://${params.url.split("://")[1]}`);
  if (t.status == 200) {
    const buffer = await t.arrayBuffer();
    return {data: buffer}
  } else {
    throw new Error(`Tile fetch error: ${t.statusText}`);
  }
});
```

変更点として、v3 の場合は、引数に設定した callback へ、変換したタイルデータを渡していましたが、v4 の場合は、Promise の履行結果としてタイルデータが渡されるようになっています。

なお、ベクトルタイル（pbf）の場合ですが、params の中身の主となる部分（タイルの URL やデータの type ）の形式は変更ないようです。

```json:params の例（pbf の場合）
{
  "url": "custom://localhost/xyz/vector/13/7275/3227.pbf",
  "type": "arrayBuffer"
}
```

## PMTiles の消費方法

PMTiles は、一つのファイルの中にタイルデータ一式が格納されており、HTTP 範囲リクエストにより、目的のタイルを取り出します。

MapLibre GL JS 単独で PMTiles へ対応しているわけではなく、別途ライブラリが必要です。[PMTiles レポジトリの参照実装](https://github.com/protomaps/PMTiles/tree/main/js)/[PMTiles package](https://www.npmjs.com/package/pmtiles) が利用されることが多いと感じています。

[PMTiles package](https://www.npmjs.com/package/pmtiles) は [v3.0.1 から MapLibre GL JS v4.x へ対応](https://github.com/protomaps/PMTiles/blob/main/js/CHANGELOG.md)しているようです。以下のように MapLibre GL JS v4.1 と PMTiles package v3.0 の組み合わせで、特にコードを変更することもなく PMTiles を読み込めることを確認しました。

```HTML:ライブラリ等の読み込み
<script src='https://unpkg.com/maplibre-gl@4.1/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@4.1/dist/maplibre-gl.css' rel='stylesheet' />
<script src='https://unpkg.com/pmtiles@3.0/dist/pmtiles.js'></script>
```

```JavaScript:addProtocol への pmtiles プロトコルの登録（v3/v4 共通）
let protocol = new pmtiles.Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
```

## 複数の PMTiles を読み込む方法

以前の記事で複数の PMTiles を addProtocol を利用して読み込む方法を検討しました。

https://qiita.com/mg_kudo/items/6fa899f4532ba2fe879b

こちらも改めて試してみましたが、以下の記述のまま変更なしで対応できそうです。

```JavaScript:複数の PMTiles を読み込む方法（v3/v4 共通）
let protocol = new pmtiles.Protocol();
const urlConvert = (url) => {
  // convert url
  // ここに URL の変換プログラムを記載
  return url;
}
const myProtocol = (param, callback) => {
  console.log(param);
  // URL を変換
  if(param.type != "json"){
    param.url = urlConvert(param.url);
  }
  return protocol.tile(param, callback);
}
maplibregl.addProtocol("multi-pmtiles", myProtocol);
```

:::note warn
ただし、`myProtocol()` へ渡す２つ目の引数名は、 callback ではなく、abortController と書き直した方が良いかもしれません。
:::

## おわりに

MapLibre GL JS も v4 まで開発が進みました。開発者の皆様に敬意を表します。PMTiles の利用も広がっている中、今回の破壊的変更で PMTiles の利用に影響が出るかと少し心配しましたが、特に大きな修正もなく、対応できそうで良かったです（PMTiles package 側の対応にも感謝です）。

今回の変更で、パフォーマンスへどの程度影響が出るかどうかは把握していません。今後、機会があれば、検証できるといいなと思っています。