PMTiles  ベクトルタイル読み込みを観察する

# はじめに
PMTilesを使ったウェブ地図を作成することをためすこと、またタイルのZXYの範囲がどのようにリクエストされているかを確認すること、が必要だったので実験してみました。

記録をメモしておきます。


# 環境
* GitHub ページ
* Google Chrome
* Windows Powershell

# やったこと
## 1. PMTilesを使ったWeb地図をつくる（MapLibre GL JS）
通常のMapLibre GL JSでのウェブ地図作成に、以下のような手順を加えます。

htmlファイルのheadに以下を追加 
```
<script src="https://unpkg.com/pmtiles@2.5.0/dist/index.js"></script>
```

そして、htmlファイルのbody、script内に以下を追加
```
let protocol = new pmtiles.Protocol();
maplibregl.addProtocol("pmtiles",protocol.tile);
```

スタイルJSON中のソースの書き方をURLで以下のように書く（以下の例は https://ubukawa.github.io/109/test123.pmtiles を使う場合 ）
```
"url": "pmtiles://https://ubukawa.github.io/109/test123.pmtiles"
```

実際に作ったhtmlファイルはこちら：　https://github.com/ubukawa/109/blob/main/map/map.html
実際に作ったスタイルファイルはこちら：　https://github.com/ubukawa/109/blob/main/map/style1.json

これでPMTilesのベクトルタイルが見られます。
https://ubukawa.github.io/109/map/map.html
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/ce2bfa32-a4eb-cc4e-7db3-9c735cb19b8a.png)

## 2. PMTilesへのアクセスを観察

作ったマップをGoogle Chromeでひらき、F12の開発者モードでPMTilesへのアクセスを観察します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/20abd244-758d-3571-f6f0-08d8ab5d11e3.png)

* 発見 １：pmtilesの取得は206のステータス。これはリクエストヘッダに書いてある通りにPartial Contentの取得が成功したということらしい。なるほど。
* 発見 ２：プロトコルはh2を使えている。

さらに、リクエストヘッダを見てみる。if-rangeとrangeというものがあるようだ。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/bcf248d9-3f9c-7944-fd00-047415bd13fe.png)

* 発見 ３：if rangeは共通で、rangeが所定のタイルに相当するようだ。if-rangeについて調べると、前に断片を受け取ってからデータが変わったかを確認するための情報らしい。なので、データベースが更新されていなければそのままなのだろう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/e52787e4-c38a-a350-f602-685d53ba9592.png)

* 疑問 １： あるタイルのデータ範囲がどこからどこにあるというのはどこに書いてあるのだろう？？？

* 発見 ４： リクエストの中にはif-range がないものもある。一番最初に読んだものにはif-rangeがないのかもしれない。これは0からでないrangeなので理由がよくわからない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/b0dc1daf-e557-666a-3f29-3806546f33ff.png)

* 発見 ５： もう一度やってみたところ、最初のリクエストは、rangeのバイトが0から始まっている。if-rangeはない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/1aa6369b-2e8b-b5fb-cf45-bccd7158a1b7.png)

なお、二番目になると、if-rangeがついている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/b4ac36a5-8e12-227a-3049-9d19ef8cd8d0.png)

* 発見 ６： 最初のリクエストで得られたデータはほかのリクエストに比べてデータサイズが小さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/4525d050-e334-5d2f-4687-76647fe3de43.png)

初期ズームレベルが１の地図で、最初によまれる5つのPMTilesの断片について、rengeとコンテンツ長をみたら以下の通りであった：
1. bytes=0-16383, 16384
1. bytes=356261-765489, 409229
1. bytes=24447-260732, 236286
1. bytes=308376-356260, 47885
1. bytes=260733-308375, 47643

* 発見 7： 上記、２～５のPMTilesの断片は、もともとのデータの 1/x/y.pbf シリーズ（4つ）にサイズがよく似ている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/97f510f0-3aa4-c4c0-786e-06c90a597953.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/2d23be2c-c16f-747f-854b-d2481b64d7b8.png)


* 推測 １： そうすると、bytes=0-16383 の範囲が、タイル分布のマッピングというか、どのタイルがどのデータ長かを書いてあるものだと思われる。WindowsPowershell の curl.exeで見てみた。
```
curl.exe https://ubukawa.github.io/109/test123.pmtiles -i -H "Range: bytes=0-16383"
```
↓バイナリーなのでファイルが見られない。（--outputオプションでテキストに出力しても読めない。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/6273cde4-aedf-fe59-8630-449d313d476d.png)

最初に読まれるPMtilesの断片（bytes=0-16383）は、PMTiles共通で、各タイルのデータ長が含まれるという仮説を強化すべく、別のPMTiles地図を観察した。
@hfu  さんの https://x.optgeo.org/zoomstack/ を開く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/e441a9b2-6300-6f12-eba5-251a1800c3f4.png)
* 発見 8： ここでも最初に読まれているPMTiles の rangeが bytes=0-16383 であることが分かった。ヘッダ情報というか、メタデータというか、この部分に各タイルのデータ長が記載されているのではないかと思われる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/42666f35-9f37-114d-a165-a9487d37b610.png)


# まとめ
* GitHubページで静的にホストしているPMtilesは、MapLibre GL JSで簡単に読めることが分かった。
* PMTilesを読み込むとき：
  * range をしてしたリクエストで部分的に読み込みをしている（206）。if-range を使って、データが更新されていないかもチェックしている。
  * リクエストヘッダのなかに、x,y,zのタイル番号はない。あるのはrangeだけ。
  * タイルごとのデータ長は、おそらく最初のPMTilesへのアクセス （bytes=0-16383）で情報を得ているようだ。**16384**という数字がキーポイント。



# 参考ページ
https://www.npmjs.com/package/pmtiles

https://developer.mozilla.org/ja/docs/Web/HTTP/Range_requests

https://dev.to/hfu/serve-os-open-zoomstack-2022-12-through-pmtiles-1di1