Geoloniaでは地理空間データを作ったり変形したりした上、ほとんどの場合は表示するためにベクトルタイルとして配信します。一般的にベクトルタイルを作るには元のデータから各ズームレベルのベクトルタイルを予め生成ツールを使います。例えば、GeoJSONからベクトルタイルを作りたい場合は tippecanoe を使ったり、 Postgis を使う場合は ST_AsMVT という関数を使います。また、OpenStreetMapのデータをベクトルタイルとして配信するケースも多々ありますが、こちらは専用のツールを使います。Geolonia では tilemaker を使っておりますが、他にも OpenMapTiles もあります。
今回は、あらかじめタイルを作るという用途よりは上流のデータをもとに動的に作る必要があったので、今まで使ったツールでは不十分でした。Mapbox Vector Tile形式は protobuf でエンコードされた情報なので、もしエンコーディングなど自分でできれば、ベクトルタイル専用のライブラリとか使わずに、そのまま Mapbox Vector Tile の protobuf 定義を使って自力で作ることができます。
ここからの説明は TypeScript の基本的な設定が行なっていることを前提としています。
まずは、 Mapbox Vector Tile の protobuf 定義を組み込みます。私は git submodule で組み込みましたが、 vector_tile.proto
のファイルさえあれば大丈夫です。
git submodule add https://github.com/mapbox/vector-tile-spec.git
そして、こちらのファイルを読み込んでエンコーティングするためのコードや TypeScript の定義を作るコマンドを package.json
に登録します。
"scripts": {
...
"build:protobuf": "pbjs -t static-module -w es6 -o src/libs/protobuf.js vector-tile-spec/2.1/vector_tile.proto && pbts -o src/libs/protobuf.d.ts src/libs/protobuf.js"
}
このコマンドに pbjs
と pbts
というものを呼び出していますが、 protobufjs
パッケージに入っています。
npm install --save protobufjs
さて、コードを生成しましょう。
npm run build:protobuf
生成後、 src/libs/protobuf.js
と src/libs/protobuf.d.ts
に結果が入っています。
MapboxのドキュメンテーションにMVTの内容について説明するページがあります。今回特に重要な点をリストします:
さて、基本情報が揃ったのでコードを書いてみましょう。
import { vector_tile } from "../libs/protobuf";
// ZigZag エンコーディング
const zz = (value: number) => (value << 1) ^ (value >> 31);
const tile = vector_tile.Tile.create({
layers: [
{
version: 2, // 個体値
name: "test", // レイヤー名
extent: 256, // このタイルの相対座標の範囲を指定する。256に設定すると、このタイルが256ピクセル四角で作られていることを示します。 256×256=65536 ピクセル。範囲は: (0,0) から (256,256) まで
features: [
{
// レイヤー内、idがユニークである必要があります。
id: 1,
type: vector_tile.Tile.GeomType.POLYGON,
geometry: [
// (0,0) で始めます
((1 & 0x7) | (1 << 3)), // MoveTo (1) 命令を 1 回実行
zz(5), zz(5), // (5,5) に移動
((2 & 0x7) | (3 << 3)), // LineTo (2) 命令を 3 回実行
zz(1), zz(0), // 線を (5,5) から (6,5) まで引く
zz(0), zz(1), // 線を (6,5) から (6,6) まで引く
zz(-1), zz(0), // 線を (6,6) から (5,6) まで引く
15, // パスを閉じる(暗黙的に最後の点 (5,6) から最初の点 (5,5) に線を引きます)
],
tags: [
0, // test-property-key-1
0, // value of key 1
1, // test-property-key-2
1, // value of key 2 and 3
2, // test-property-key-3
1, // value of key 2 and 3
],
}
],
keys: [
"test-property-key-1",
"test-property-key-2",
"test-property-key-3"
],
values: [
{stringValue: "value of key 1"},
{stringValue: "value of key 2 and 3"},
],
}
]
});
この Feature はどの形を作るかわかりますか?
こちらのタイルをバイナリに変換するには:
const buffer: Buffer = vector_tile.Tile.encode(tile).finish();
ここからそのままファイルに保存したり、 Lambda を使う場合は return { body: buffer.toString('base64'), isBase64Encoded: true }
などで配信できます。
当初、ベクトルタイルがブラックボックスであり、必ず他のツールを使って生成したりGeoJSONに変換してからデバッグや、QGISで開いたりしましたが、今回実際作ってみたら慣れた後(最初、絶対・相対の変換計算や命令の相対座標変換とか結構ハマりました。。)は意外とスムーズに行けました。