Mapbox Vector Tiles のデータ構造

## はじめに

こんにちは。株式会社 MIERUNEの鈴木です。

ここ最近、Web地図システムのデータ配信に使われているベクトルタイルについて、中身をよく知らないまま使っていたこともあり、ベクトルタイルで最も広く使われているであろう Mapbox Vector Tiles の中身について今回調べてみました。

## Mapbox Vector Tiles とは

Mapbox Vector Tiles は Mapbox 社が中心となって仕様を作成しているベクトルタイル実装です。
あるズーム値・x座標・y座標の場所に表示するベクターデータを Protocol Buffers でパッケージして提供します。ちなみに複数のベクトルタイルを sqlite3 データベースにまとめて提供するファイルが mbtiles です。

Mapbox Vector Tiles は仕様が公開されており、そのおかげか、MVT データに変換・展開するツールも多く入手できます。

## Mapbox Vector Tiles の仕様

Mapbox Vector Tiles は次のリンク先で仕様が公開されています。

- https://docs.mapbox.com/vector-tiles/specification/
- https://github.com/mapbox/vector-tile-spec

mbtiles の仕様も公開されています。 

- https://github.com/mapbox/mbtiles-spec


## Mapbox Vector Tiles のデータ構造

Mapbox Vector Tiles の仕様から、どのようなデータ構造を持っているか読み取ります。

### 全体の構造

![20201204_MVTについて.008.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/334543e4-5c0e-3721-c390-418c7000593e.jpeg)

Mapbox Vector Tiles はこのような入れ子のデータ構造を持ちます。

- Tiles
    - Layers
        - Features
        - Keys
        - Values

### Tiles

Tiles は、複数の Layers をもつコンテナです。

### Layers

Layers は複数の Features や、Features の属性を表現する Keys, Values を含む事ができるコンテナです。

Layers とは画像編集ツールの PhotoShop でいうレイヤ、QGIS のレイヤ、それぞれと同じ意味あいをもちます。つまり、表現するデータを任意の種類ごとに分別し、描写する際に、他の種類のデータと混らないように物理的に分ける層として機能します。

### Features

Features はこのデータが配置されたタイル座標に描くベクタデータを内包します。

Features が描くベクタデータの種類を `GeomType` 属性で指定し、`geometry` 属性に描くベクタデータ命令を数値に変換した値を保持します。どの Keys, Values を利用するか示す tags を持ちます。

Features で表現するベクタデータは具体的な緯度経度の値をデータに含みません。データ構造の外部から与えられたタイル座標の領域に対し、どのような絵を描くかのデータしか持ちません。このおかげでタイル座標をずらして配置してもデータの内容自体に手をいれる必要がなくなるメリットを持ちます。

GeomTypeは

- 0: UNKNOWN
- 1: POINT
- 2: LINESTRING
- 3: POLYGON

の4種類です。

### Features.geometry

Features.geometry について、以下の3種類の命令でベクタデータを描きます。

- MoveTo: 原点から指定した相対座標位置にカーソルを動かす。
- LineTo: 現在の座標から、指定した相対座標の位置に線を引く。
- ClosePath: 複数の LineTo を繋いだ最後の座標から、最初の座標に向かって線を引いて、形状を閉じます。

これらの命令を組み合わせる事で、POINT, LINESTRING, POLYGON それぞれを表現します。
また、命令の組み合わせをさらに数珠つなぎにする事で、MULTIPOINT, MULTILINESTRING, MULTIPOLYGON を表現することも可能です。

命令の具体例について、いくつかの画像で説明します。

![20201204_MVTについて.012.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/35f391d9-3e53-df08-ff7a-afa5f0a4147e.jpeg)

まず領域を用意します。

![20201204_MVTについて.013.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/895f930f-aa61-e105-bbbc-938f70f905b4.jpeg)

`MoveTo` 命令で、左上の原点からX座標を1、Y座標を2ずらした `(1, 2)` の位置にカーソルを移動します。（X座標は右から左、Y座標は上から下に向かってカウントアップされます）

![20201204_MVTについて.014.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/708aa149-21d6-b379-3376-a60571bcda92.jpeg)

`LineTo` 命令で `(1, 2)` からX座標を3、Y座標を-1ずらした座標に線をひきます。

![20201204_MVTについて.015.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/c3f09856-3502-37f9-8dd0-5e616bc9f857.jpeg)

`LineTo` 命令で最後の位置からX座標を3、Y座標を4ずらした座標に線をひきます。

![20201204_MVTについて.016.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/cd401697-68a8-a45c-f325-b79e65a7a3fb.jpeg)

`LineTo` 命令で最後の位置からX座標を-4、Y座標を2ずらした座標に線をひきます。

![20201204_MVTについて.017.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/4a8a0110-84e3-c8e9-060a-6a8887b017a7.jpeg)

`ClosePath` 命令でここまで描いてきたパスを閉じます。これでポリゴンが一つできました。

![20201204_MVTについて.018.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/1231a209-f54d-ac19-0c85-b7e0ae5a0bcb.jpeg)

さらに命令を繰り返すことで、穴あきポリゴンを表現する事ができました。

## ツールを使って mvt->geojson 変換

mvtから別のデータ、例えばGeoJSONに変換するには`tippecanoe-decode` などのツールを使う事で可能です。また、pbf自体は仕様がオープンになっているので、展開ツールを自作する事もできます。

![20201204_MVTについて.023.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/27c54832-8401-7811-1060-34aae4f283d9.jpeg)

ここで以下の国土地理院ベクトルタイルのベクトルタイルデータを展開してみます。
URL: https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/14/14625/6015.pbf

![20201204_MVTについて.024.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/6391aa2c-f763-f8e0-7da3-827343d19b12.jpeg)

`tippecanoe-decode` の引数に、ダウンロードした pbf ファイルの名前、ズーム値、x座標、y座標のタイル位置を指定してみます。まずは、ファイル取得元の URL で表される通りに指定してみます。

![20201204_MVTについて.025.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/e860ab5e-27d0-4c27-05e8-0a6c0550ee8a.jpeg)

その結果、このようなGeoJSONを取得する事ができました。Mapbox Vector Tilesのベクトルタイルのデータ構造の通り、Tiles, 複数の Layers,複数の Features を持っているようです。

![20201204_MVTについて.026.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/ee6aa8fb-5ce7-7a21-0a3d-4f4ba1afc8fe.jpeg)

Layer の一部を切り取って、QGIS で表示してみました。しかし、ちょっと背景地図の建物と、描いた絵の位置が合っていないようです。

![20201204_MVTについて.027.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/4f611879-f409-4c8e-c4ff-418126dd1b83.jpeg)

そこで、同じファイルについて、タイル座標を変更してもう一度 GeoJSON を生成してみます。

![20201204_MVTについて.028.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/a18b88d1-6f5e-d152-2e81-f32ac551755c.jpeg)

今度は背景地図の建物と、描いた絵の位置が合いました。

![20201204_MVTについて.030.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/98e9a4fd-e334-557b-eb94-8d5c1233ab28.jpeg)

さらに同じデータに対し、ズーム値・X座標・Y座標全てのタイル座標を変更して GeoJSON を出力するなんてこともできます。実用上の意味はありませんが、Features.geometry が内部で具体的な緯度経度情報をもたないからこそ、異なる緯度経度への数値変換が柔軟にできる事を示しています。


## 最後に

弊社こと、株式会社MIERUNE は今回ご紹介した Mapbox Vector Tiles を利用するシステムなど、地理位置情報の取り扱いに長けた会社です。

これまでも多くのお客様の多様なニーズに沿った機能を受注・開発してきております。もしご興味がございましたら、[弊社ウェブサイト](https://mierune.co.jp/#ja) よりご気軽にお問い合わせください。