LoginSignup
4
4

Maplibre GL JS の addProtocol の使い方を調べる

Last updated at Posted at 2023-02-12

はじめに

Maplibre GL JS は、Mapbox GL JS から派生したウェブ地図用のライブラリです。

Mapbox GL JS の v2 系のライセンス変更(2020年12月)をきっかけに、Mapbox GL JS の v1 系からフォークされましたが、その Maplibre GL JS も既に v2 となって久しく、もともとの Mapbox GL JS にはない機能も追加されています。

そのひとつが、addProtocol という機能であり、タイルをロードする際に、実行する処理を追加することができます。ただ、maplibre.org から出されている addProtocol の説明はあっさりしていたので、使ってみた挙動やこの機能を活用している参照実装等を通して、理解を深めてみます。

この記事は、MapLibre GL JS v3 までを対象としています。MapLibre GL JS v4 ではaddProtocol に破壊的変更が入りましたのでご注意ください。この破壊的変更の内容と既存コードの流用可否については、非常に簡易的ですが、以下の記事で調べています。

使い方

本機能は、Style JSON 内の source で指定する URL のプロトコル部分にオリジナルのプロトコルを設定でき、そのプロトコルが設定されたファイルがロードされる際に、処理を追加することができます。

たとえば、custom:// というオリジナルプロトコルを設定してみます。ベクトルタイルの場合、Style JSON の記述は、以下の通りになるでしょう。設定のうち、tiles 内の URL に注目です。

Style JSON
"sources": {
  "vector": {
    "type": "vector",
    "tiles": ["custom://localhost/xyz/vector/{z}/{x}/{y}.pbf"], 
    "minzoom": 4,
    "maxzoom": 16
  }
}

この custom:// プロトコルが設定された URL からタイルをロードする際に行う処理を addProtocol で記載します。上記の maplibre.org から出されている例にならい、単純に https プロトコルで fetch する例を記載すると以下の通りです。

addProtcolの記述
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: () => { } };
  
});

まず、addProtocol の第1引数がオリジナルで設定したいプロトコル名になります。今回は custom ですね。

第2引数が、実行したい処理の関数になります。この関数は、params と callback を受け取ります。まず、params の内容は、source の type によって異なりますが、オリジナルのプロトコルを含んだ URL となります。この際、タイル番号は、${z}${x}${y} ではなく、既に実際の数字に置き換えられています。この URL をもとに、処理を行い、最終的なデータを callback に渡してあげると処理は終了になる、という形に見えます。

上記の例では、単純に、オリジナルプロトコルの custom://https:// へ置き換えて、その URL のデータを fetch API で受け取り、buffer array として、callback へ渡しています。(渡すときは、callback の第2引数に渡すようです。他の引数が何を意味しているのかは詳細に調査しておりませんが、このソースResponseCallback の型を見ると、第1引数から、エラー、データ、Cache-Control?、Expires? と思われます。)

上記はベクトルタイルの例ですが、callback へ渡すときのデータ形式は、Source の type に応じて、変更すべきと理解しています。

params の内容例

Source の type 別の params の中身を以下に記載してみます。

raster
{
  "url": "custom://localhost/xyz/raster/13/7275/3227.png",
  "headers": {
      "accept": "image/webp,*/*"
  },
  "type": "arrayBuffer"
}
vector
{
  "url": "custom://localhost/xyz/vector/13/7275/3227.pbf",
  "type": "arrayBuffer"
}
geojson
{
  "url": "custom://localhost/data/example.geojson",
  "type": "json"
}

活用例

先人の addProtocol 活用例を以下紹介します。

国土地理院の標高タイルをデコード

国土地理院から公開されている標高タイルですが、こちらの標高のエンコード方式は、Maplibre GL JS(や Mapbox GL JS)では対応していません。しかし、addProtocol を使うことで、タイルロード時に、デコードを行い、Maplibre GL JS が解釈できる形式に変換することで、type が raster-dem のSource 等として利用することが可能になります。

PMTiles の読み込み

PMTiles は、一つのファイルの中にタイルデータ一式が格納されており、タイル座標に対応する位置のデータを HTTP 範囲リクエストで取得します。利用するにはクライアント側での実装が必要ですが、Maplibre GL JS で利用できるような環境が提供されています。この実装においては、 addProtocol を利用して、タイル座標から PMTiles で取得すべきデータの範囲を割り出し、タイルのデータを取得してきて、Maplibre GL JS へ渡しています。

少し使いづらいところ

色々試す中で addProtocol で使いづらいと思われるポイントを共有してみます。

タイル座標は直接受け取れない

受け取れる情報に、タイル座標(x, y, z)はなく、URL だけとなります。そのため、タイル座標を取り出そうとすると、URL をパースする必要があります。そのため、変則的なURLだと難儀しそうです。以下は、PMTiles の参照実装におけるパース例となります。

  const re = new RegExp(/pmtiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/);
  const result = params.url.match(re);
  // (中略)
  const z = result[2];
  const x = result[3];
  const y = result[4];

このパース方法だと、以下のような URL のタイルは、誤ったパースを行うことになります。

custom://localhost/xyz/{y}/folder/test-10{z}/{x}/2/3/1/image.png

この URL のタイルに対して、addProtocl で受け取れる params は以下の通りですが、params.url のどの数字がタイル座標の z, x, y を示すのか判別できません。

{
  "url": "custom://localhost/xyz/806/folder/test-1011/1819/2/3/1/image.png",
  "headers": {
    "accept": "image/webp,*/*"
  },
  "type": "arrayBuffer"
}

もともとの Source の type の影響を受ける

もともとの Style の Source に影響を受けているので、例えば、タイル形式の GeoJSON をそのまま GeoJSON Source として使おうとすると無理があります。

呼び出すファイルと Maplibre GL JS へ渡すデータを1対1で変換するということを基本に考える必要があります。GeoJSON タイルの場合は、Source の type が geojson の場合は tiles の設定はできませんので、Source の type を vectorraster に指定し、addProtocol 内で、GeoJSON を pbf や 画像等の buffer array に変換する必要がありそうです。

(勉強不足なので、もしかしたら、もう少し機能を紐解けばできる可能性もありますが……。)

感想

この機能により、色々なことができそうで期待が膨らみます。Mapbox GL JS ではなく、Maplibre GL JS を使う大きなモチベーションになりそうな機能です。Contributor の方々に感謝と敬意を表します。

今まで自分は Mapbox GL JS の v1 系を使い続けていましたが、Maplibre GL JS へのマイグレーションを本格的に考えていきたいですね。

余談

Mapbox GL JS には、Mapbox 社のサービスで利用される mapbox:// というプロトコルが実装されていますが、Maplibre GL JS の v2 では削除されています。最初、addProtocol を聞いたときに、このプロトコルを一般化するためのものかと想像しました。

実際のところを調べようとして、GitHub レポジトリの issue や discussion を探ってみました。「addProtocol」をキーワードに検索して、色々と見ていくと、v2 をリリースするまでに、mapbox:// プロトコルへの対応を残すかどうかは議論があり、コードを残すとMapbox 社のライセンスに抵触する可能性があるという懸念があった様子です。結局、mapbox:// プロトコルへの対応は v2.0 で削除されて、どうしても使いたければ、「同時に追加された addProtocol を用いて、自己責任で対応することは可能では?」という旨のコメントが残っています。

しかし、addProtocol の機能を加えた直接のきっかけやモチベーションは見つけることができませんでした。(もちろん、私が見つけられていないだけの可能性は高いです。だれかご存じであれば、ご教授願います……。)

【追記】
@shizu 様から、addProtocol の機能を加えたきっかけと思われる issue を紹介いただきました。こちらの issue を見ると、addProtocol は、mapbox:// プロトコルの取り扱いとは別に提案されたものなのかもしれません。なお、この issue は MapLibre v1.15.0 の時点でリリースされていますので、addProtocol は MapLibre が v2 になる前から実装されていたのですね。

4
4
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4