バイナリベクトルタイルを作ってホストしてみた

最近WEB地図界隈で話題になっているバイナリベクトルタイルを作ってみました。

この記事は、[バイナリベクトルタイルの作り方(GUNMA GIS GEEK)][作り方]という記事にある作り方をそのままそっくりやってみたという内容の記事です。クックパッドで言うところの「つくれぽ」ですね。
なので、バイナリベクトルタイルや途中で出てくるMBTilesなどの仕様や仕組みについてはあまり理解していません。ご了承ください。

[Leafletでの表示例][デモ]

### 1.データをダウンロード

データは何でも良いのですが、とりあえず[国土数値情報]を使ってみます。
今回は点データである「学校」（平成25年度）のデータを利用しました。zipファイルをダウンロードし、解凍するとシェープファイルが手に入ります。
フォルダごと作業ディレクトリのどこかに放り込んでおきましょう。

### 2.シェープファイルをGeoJSONに変換

一般的にはGDALの[ogr2ogr]を使う場面だと思いますが、より楽な[shp2json]を使いました。npmでサクッとインストールできるので、D3.js、特にTopoJSON関連を使うなら導入して損はないと思います。

```Shell
  $ shp2json --encoding Shift-JIS source.shp > output.geojson
```

国土数値情報はShift-JISで提供されているので、`--encoding Shift-JIS`を忘れずに。

次の手順で使う[tippecanoe]では[ndjson]形式を扱うことができるので、`shp2json`で`-n`オプションをつけて、ndjsonで出力してもいいです。tippecanoeのドキュメントによると、ndjsonだとちょっとだけ早くなるとのこと。

### 3.GeoJSONからMBTilesを作成

[MBTiles]はSQLiteベースで作られたタイルセットを格納するためのファイル形式です。GeoJSONからMBTilesを作るために[tippecanoe]を手に入れます。macなのでhomebrewを使いました。

tippecanoeのオプションですが、基本的には「作り方」の例に従っています。`-r`オプションで設定するレートも、数値ではなく`-rg`オプションにしました。実用的なものを作る場合は適切なレートを設定した方がいいと思います。要検証。

入力が先述の[ndjson]形式の場合は`-P`オプションをつけます。

下記のように、`-L`オプションを使うことで複数のデータをレイヤーとして一つのmbtilesファイルにまとめることもできます。

```Shell
  $ tippecanoe -L hoge:./data/hoge.json -L fuga:./data/fuga.json -o output.mbtiles
```

`tippecanoe`の公式ドキュメントはもう少しちゃんと読んでおきたいです。

### 4.mbutilを使ってpbfファイルへ展開

[mbutil]は[MBTiles]とタイルを構成するディレクトリの相互変換を行うツールです。「作り方」ではeasy_installでしたがpipを使ってインストールしました。
拡張子は「作り方」でも言及されていたように、.pbfではなく**.mvt**としました。

```Shell
  $ find . -type f -exec mv -v '{}' '{}'.mvt \;
```

これで、mbutilで指定したディレクトリにmvtという拡張子がついたファイルが入ったフォルダが出来ているはずです。

### 5.表示してみる

[Leafletでの表示例][デモ]

「作り方」の記事に則って[Leaflet]を使用して表示します。
ここで注意点が一つ。バイナリベクトルタイルの表示に使用する[Leaflet.MapboxVectorTileプラグイン][mvtプラグイン]はLeaflet v1.0.0以降のバージョンでは動きません。ガーン。
また、上記のデモでも実証されるように、地物の数が多すぎるタイルは読み込んでくれませんでした（タイルの欠損があるわけではないはず）。この辺の閾値なども調べていきたいです。

```JavaScript
  const schools = new L.TileLayer.MVTSource({
    url: "./tile/schools/" + "{z}/{x}/{y}.mvt",
    maxZoom: 18,
    maxNativeZoom: 15,
    attribution: '<a href="http://nlftp.mlit.go.jp/ksj/" target="_blank">国土数値情報（学校）</a>',
    mutexToggle: true,
    getIDForLayerFeature: function(feature){
      return feature.properties.P29_001 + feature.properties.P29_005;
    },
    onClick: hoveronFeature, //別途設定した関数
    style: function(feature){
      let color = feature.properties.P29_004 === "16001" ? "yellow" :
                  feature.properties.P29_004 === "16002" ? "gold" :
                  feature.properties.P29_004 === "16003" ? "#aca777" :
                  feature.properties.P29_004 === "16004" ? "#ff963e" :
                  feature.properties.P29_004 === "16005" ? "#ac815c" :
                  feature.properties.P29_004 === "16006" ? "#78109d" :
                  feature.properties.P29_004 === "16007" ? "#d90096" : "#3f9386";

      return {color: color, radius: 6, selected: {color: "red", radius: 10}};
    }
  });
```

LeafletのMVTレイヤ設定の例です。[プラグインのドキュメント][mvtプラグインconfig]によると、必須となるオプションは`url`、`getIDForLayerFeature`、`filter`のようです（ただしfilterオプションはなくても動く）。重要なのが`getIDForLayerFeature`で、これを設定しないと地物のスタイリングやクリックイベント時に正しい挙動をしてくれません。地物のプロパティに固有のidがある場合はそれを設定すればいいのですが、国土数値情報の学校データには存在しないので、行政区域コード（P29_001）に名称（P29_005）を繋げたものを設定しました。

#### その他のライブラリ

[OpenLayersでの表示例][デモol]
（※2017/08/18 OpenLayersのバージョンをv4.2.0→v4.3.1に置き換え）

[OpenLayers]ではバイナリベクトルタイルを読み込む`ol.format.MVT()`が存在しているのですが、~~なぜか上手く動作してくれません。もしかしたら[Mapboxが配信しているタイル][ol4example]に特化しているかもしれません（あるいはラスタタイルとの併用を前提としていないか）。~~

※2017/08/17追記
2017/08/13にリリースされた[OpenLayers v4.3.0](https://github.com/openlayers/openlayers/releases/tag/v4.3.0)ではMVTソースの動作はめちゃくちゃ良くなってます。（最新版はv4.3.1）

ちゃんと調べてないのでアレなんですが、WEBGLベースの[Mapbox GL JS]が一番良いみたいですね。後々試してみます。

### 6.GitHub Pagesでホストしてみる

作成したバイナリベクトルタイルをGitHub Pagesを使ってホストしてみます。
2016年からの新機能でmasterブランチのdocsフォルダの中身をホストできるようなので(参考:[2016年新機能! GitHubのmasterブランチをWebページとして公開する手順])、それに則ってやってみました。

- レポジトリ: [https://github.com/cieloazul310/mvt-tiles][MVT-Repository]
- ホストしたタイル:  https://cieloazul310.github.io/mvt-tiles/tile/schools/{z}/{x}/{y}.mvt
- ホストしたページ: [https://cieloazul310.github.io/mvt-tiles][デモ]



### 課題

- tippecanoeの機能をもっと試していきたい。
- Leafletでのクリックイベントや複雑なスタイリングなどを試していきたい。
- LeafletのMapboxVectorTileプラグインは最終更新が2015年9月で、Leafletの最新バージョンに対応する可能性はなさそう。それでもLeafletを使うべきか悩みどころ。

### 感想

難易度が高い印象があったバイナリベクトルタイルが意外なほどあっさり作れてしまって驚きました。
レシピを公開してくれた[GUNMA GIS GEEK]の清水正行氏には感謝申し上げたいです。

### 参考リンク

- [VectorTilesSpecification] -mapbox
- [MbTiles] -mapbox
- [バイナリベクトルタイルの作り方][作り方] -GUNMA GIS GEEK
- [anyone can host vector tiles] -hfu's Qiita
- [Vector Tiles to start in Japan] -hfu's Qiita
- [地球地図バイナリベクトルタイルの整備][地球地図の整備] -国土地理院

[GUNMA GIS GEEK]: https://shimz.me/blog/
[作り方]: https://shimz.me/blog/leaflet-js/5354
[mvtプラグイン]: https://github.com/SpatialServer/Leaflet.MapboxVectorTile/
[mvtプラグインconfig]: https://github.com/SpatialServer/Leaflet.MapboxVectorTile/blob/master/docs/configuration.md
[ol4example]: http://openlayers.org/en/latest/examples/mapbox-vector-tiles.html
[VectorTilesSpecification]: https://www.mapbox.com/vector-tiles/specification/
[MBTiles]: https://www.mapbox.com/help/define-mbtiles/
[tippecanoe]: https://github.com/mapbox/tippecanoe
[mbutil]: https://github.com/mapbox/mbutil
[ndjson]: http://ndjson.org/
[国土数値情報]: http://nlftp.mlit.go.jp/ksj/
[地理院地図]: http://maps.gsi.go.jp/
[ogr2ogr]: http://www.gdal.org/ogr2ogr.html
[shp2json]: https://github.com/mbostock/shapefile#command-line-reference
[OpenLayers]: http://openlayers.org
[Leaflet]: http://leafletjs.com
[Mapbox GL JS]: https://www.mapbox.com/mapbox-gl-js/api/
[MVT-Repository]: https://github.com/cieloazul310/mvt-tiles/
[デモ]: https://cieloazul310.github.io/mvt-tiles
[デモol]: https://cieloazul310.github.io/mvt-tiles/openlayers.html

[2016年新機能! GitHubのmasterブランチをWebページとして公開する手順]: http://qiita.com/tonkotsuboy_com/items/f98667b89228b98bc096
[anyone can host vector tiles]: http://qiita.com/hfu/items/e3fab485fa90b4b89da5
[Vector Tiles to start in Japan]: http://qiita.com/hfu/items/c7040662af1fe21a9f56
[地球地図の整備]: http://ccpn.gsi.go.jp/meeting_partners/data/20170608/GSI_3.html