はじめに
昨今のGIS界隈ではCloud Optimizedが重要なキーワードとなっています。Cloud Optimizedの概要については下記のリンクが詳しいです。
このタイプのデータ形式ではラスターデータのCloud Optimized GeoTIFF(COG)が既に広く使われていて、ベクター形式は群雄割拠という様相です(2023年1月現在)。ここで変わり種として、タイルデータ(ラスター・ベクター含む)に関するCloud Optimized形式としてPMTilesが台頭してきました。本記事ではPMTilesの概要・生成・消費を一貫して紹介します。
PMTilesとは
簡単に言うと、特別なサーバー実装なしに、必要最小限のタイルデータを配信することを可能とするファイル形式です。この説明で「?」となるのは正常です。
たとえばラスターデータのCloud Optimized形式であるCOGについて考えてみると、大きなラスターデータであっても必要最小限の部分だけを「切り出して」配信できるので、確かに便利そうです。しかしタイルデータはそもそも切られているものであって、単にタイルデータを1タイル:1ファイルとして配信すればよいだけではないかと、疑問に思う方がいるはずです。
これはある意味で正しいです。手持ちのちょっとした航空写真データがあるとして、一度タイルに分割してサーバーに配置すればよいので、このケースでは必ずしもPMTilesは必要ないでしょう。しかしタイル数が膨大なケースを考慮する必要があります。たとえば全球タイルでズームレベル14だと、タイル数は3億を超えます。3億ファイルをサーバーへアップロードするコストは小さくないでしょう。配置するのが一度切りならば我慢できる痛みだと思いますが、そうではないことが多いはずです。
ここで、MBTilesが既にこのコストを解決しているのでは、と思われるかもしれません。これも確かにそうですが、MBTilesは実体がSQLiteであり、タイルデータの配信にはサーバー実装が必要です。PMTilesは、①多数のタイルデータを1ファイルとして扱える②Cloud Optimizedの仕組みでサーバー実装を要しない、という点で優れています。ただし、③クライアント側に特別な実装を要するという点は理解しておく必要があります。
技術的な詳細については開発者自身による下記のブログポストが詳しいです。
https://protomaps.com/blog/pmtiles-v3-hilbert-tile-ids
https://protomaps.com/blog/pmtiles-v3-layout-compression
PMTilesを生成する
PMTilesの生成には、以下のいずれかを利用できます。
https://github.com/protomaps/go-pmtiles
https://github.com/felt/tippecanoe
前者、protomaps/go-pmtilesがPMTilesの開発者によるリポジトリです。また、tippecanoeがv2.17.0でPMTilesの出力に対応したことは特筆すべき点です。手持ちのベクターデータをPMTilesにしたいならtippecanoeがシンプルですし、ラスタデータもしくは手持ちのMBTilesをPMTilesにするならgo-pmtilesを用いるのがよいでしょう。本記事ではgo-pmtilesを用いてPMTilesを生成してみます。
検証用データ
go-pmtilesでPMTilesを生成するにはMBTilesが必要です。今回はMapTiler Dataの評価版データを利用します。
非商用・個人プロジェクトなら無料で試用することができます。50GBくらいあります。ファイル名はmaptiler-osm-2017-07-03-v3.6.1-planet.mbtiles
となりました。
go-pmtilesでPMTilesを生成する
RAMが16GB載っているc5.2xlarge
で実行しました。c5.xlarge
だと途中で落ちました。
go-pmtilesはビルド済みバイナリが配布されているので、コンパイル等は不要です。実行環境にあったファイルをダウンロード・展開しておきましょう。
./pmtiles convert maptiler-osm-2017-07-03-v3.6.1-planet.mbtiles output.pmtiles
2023/01/05 14:09:02 convert.go:366: Root dir bytes: 9664
2023/01/05 14:09:02 convert.go:367: Leaves dir bytes: 59911110
2023/01/05 14:09:02 convert.go:368: Num leaf dirs: 2024
2023/01/05 14:09:02 convert.go:369: Total dir bytes: 59920774
2023/01/05 14:09:02 convert.go:370: Average leaf dir bytes: 29600
2023/01/05 14:09:02 convert.go:371: Average bytes per addressed tile: 0.48
2023/01/05 14:14:03 convert.go:344: Finished in 2h14m6.206522602s
real 134m7.881s
user 28m18.093s
sys 8m56.547s
変換前のMBTilesは54GBでしたが、PMTilesにすると35GBに減少しています。
ls -l
-rw-rw-r-- 1 ubuntu ubuntu 54776152064 Aug 31 2017 maptiler-osm-2017-07-03-v3.6.1-planet.mbtiles
-rw-rw-r-- 1 ubuntu ubuntu 35728910401 Jan 5 14:14 output.pmtiles
これはおそらく、repeated tiles(繰り返し登場する同じ内容のタイル)のデータ取り扱いの効率化によるものと考えられます。
以下、生成したPMTilesはhttps://<sampleurl>/output.pmtiles
として配信されているものとします。なお、PMTilesを適切に配信するには、HTTP Range-Requestに対応したウェブサーバーである必要があります。現代のウェブサーバーなら普通に対応している機能です。今回の検証ではS3にアップロードしてみました。
おまけ:tippecanoeでPMTilesを生成する
v2.17.0以降のtippecanoeでもPMTilesを生成出来ます。
tippecanoe --version
# tippecanoe v2.17.0
# 単に、拡張子をpmtilesとすればよい
tippecanoe -o output.pmtiles input.geojsonl
# MBTilesの生成処理が走り、完了後にPMTilesへの変換処理が走る。前者に比べて後者の処理時間は短い
PMTilesのデータをブラウザで表示する
PMTilesはクライアント側に特殊な実装を要求しますが、protomaps/PMTiles
以下で、その実装が公開されています。今回はMapLibre GL JS
で表示します。
npm install pmtiles
npm install maplibre-gl
import { Map } from 'maplibre-gl';
import * as pmtiles from 'pmtiles';
const protocol = new pmtiles.Protocol();
maplibregl.addProtocol("pmtiles",protocol.tile);
const map = new Map({
container: 'map',
style: {
version: 8,
sources:{
pmtiles: {
type:'vector',
url: 'pmtiles://https://<sampleurl>/output.pmtile',
attribution: '© MapTiler © OpenStreetMap contributors'
}
},
layers:[
{
id: 'water',
source: 'pmtiles',
'source-layer': 'water',
type: 'fill',
paint: {
'fill-color': '#0000ff',
},
},
{
id: 'landcover',
source: 'pmtiles',
'source-layer': 'landcover',
type: 'fill',
paint: {
'fill-color': '#00ff00',
},
},
{
id: 'boundary',
source: 'pmtiles',
'source-layer': 'boundary',
type: 'line',
paint: {
'line-color': '#ff0000',
},
},
{
id: 'place',
source: 'pmtiles',
'source-layer': 'place',
type: 'circle',
paint: {
'circle-color': '#ff9900',
},
},
],
}
});
たったこれだけのコードで、PMTilesのクライアント実装が完了しました。速度も一般のタイルデータの場合と遜色なく、高速に動作します。
metadataの読み取り
PMTilesは、MBTilesに含まれるmetadataも内包しています。下記のように読み取ることができ、非常に便利です。
const _pmtiles = new pmtiles.PMTiles(
'https://<sampleurl>/output.pmtiles',
);
_pmtiles.getMetadata().then((res) => console.log(res));
/**
{
"attribution": "<a href=\"http://www.openmaptiles.org/\" target=\"_blank\">© OpenMapTiles</a> <a href=\"http://www.openstreetmap.org/about/\" target=\"_blank\">© OpenStreetMap contributors</a>",
"description": "A tileset showcasing all layers in OpenMapTiles. http://openmaptiles.org",
"format": "pbf",
"id": "openmaptiles",
"maskLevel": "8",
"maxzoom": "14",
"minzoom": "0",
"mtime": "1499626373833",
"name": "OpenMapTiles",
"pixel_scale": "256",
"planettime": "1499040000000",
"vector_layers": [
{
"description": "",
"fields": {
"class": "String"
},
"id": "water",
"maxzoom": 14,
"minzoom": 0
},
// 以下略
],
"version": "3.6.1"
}
*/
終わりに
結構前からPMTilesは追っているのですが、パフォーマンスが向上している(たぶん)など、成熟していきている感があります。とても便利なフォーマットなのでどんどん取り入れていきたいところです。タイル系の形式としてデファクトと考えてよいでしょう。あとはQGISがネイティブ対応することがあれば、とてもうれしいですね。