メモ:Rでバイナリベクトルタイル(.mvt)を生成してGitHub Pagesで見る

ふとsfパッケージ(というかGDAL/OGR)でMVTファイルを書き出せることに気付いたので、試したときのメモ。 素人なのでなにか変なこと書いてたらコメントいただけるとありがたいです。

バイナリベクトルタイルとは

あんまり説明できないのでこの辺を読んでください。

MVTとは

Mapbox Vector Tilesの略、というところから知れるように、Mapboxがつくったバイナリベクトルタイルの仕様です。 仕様自体はオープンなもの(参考:2020年始に確認した、Mapbox さんがベクトルタイル仕様のオープン性について言っていること - Qiita)なので安心です。

sf で MVT を生成

いくつかオプションを指定する必要がありますが、基本的にはこんな感じで書き出すと、

library(sf)

# とりあえず緯度経度で指定したいのでまずは EPSG 4326 で
# 書き出されるときは EPSG 3857に変換される
linestring <- st_sfc(
  st_linestring(rbind(c(135, 40), c(142, 35))),
  st_linestring(rbind(c(135, 36), c(141, 39))),
  crs = 4326
)

write_sf(
  linestring,
  dsn = "./data", 
  layer = "foo",           # 書き出すレイヤー名、あとで Mapbox GL JS からはこれを参照することになる。
  driver = "MVT",
  dataset_options = c(
    "MINZOOM=4",           # 最小のズームレベル
    "MAXZOOM=9",           # 最大のズームレベル
    "TILE_EXTENSION=mvt",  # デフォルトの .pbf でも使用上問題ないけど、一応 .mvt にしておく
    "COMPRESS=NO"          # (重要)デフォルトだと gzip 圧縮が ON になっているが、Mapbox GL JS は非圧縮のファイルしか扱えない
  )
)

こういう感じの出力結果になります。 MINZOOMMAXZOOMで指定したズームレベル(4〜9)のタイルしか生成されません。

> tree ./data
./data
├── 4
│   ├── 13
│   │   └── 6.mvt
│   └── 14
│       └── 6.mvt
├── 5
│   ├── 27
│   │   └── 12.mvt
│   └── 28
│       └── 12.mvt

...

├── 9
│   ├── 447

...

└── metadata.json

34 directories, 76 files

(若干のハマりどころとしては、なんらかのオプションを間違えたとしても空ディレクトリ(この場合で言うと./data)ができてしまい、 上書きできないのでエラーになります。そんなときは unlink("./data", recursive = TRUE) とかで消しましょう。上書きするオプションがある気もしつつ...)

よくわからない点

とりあえず簡単なLINESTRINGPOLYGONは書き出せることを確認したんですが、以下の点がまだ謎です。詳しい方いれば教えてください...

  • POINT を書き出してもなにも出力されない。
  • 複数のレイヤーを書き出す方法がわからない。レイヤーが違えば append できそうな感じがするけど、上書きできないというエラーになる。

GitHub Pages で表示

MVTを表示するにはMapbox GL JSかLeaflet.VectorGridのようですが、 とりあえず今回はMapbox GL JSでやってみます。

Mapbox GL JS は、Mapbox の地図などのリソースを使うには Mapbox access token が必要ですが、 Mapbox以外の地図を表示するような場合には指定せずに使えます。 日本の地図だと、地理院地図Vector(仮称)がバイナリベクトルタイルを試験的に提供しているので、 これを使うことができます(Mapbox もアクセストークンを取得すれば簡単に使えるのでふつうにそっちでいい気もしつつ)。

以下のレポジトリにあるコード例通り、用意されている style を使ってベースマップをつくります。

たぶんこんな感じ。

var map = new mapboxgl.Map({
    container: 'map',
    hash: true,
    style: './std.json', // https://gsi-cyberjapan.github.io/gsivectortile-mapbox-gl-js/std.json
    center: [139.767144, 35.680621],
    zoom: 5,
    maxZoom: 17.99,
    minZoom: 4,
    localIdeographFontFamily: false
});

次に、先程のベクトルタイルを指定します。 まずはaddSource()でベクトルタイルのソースを追加し、 addLayer()でそのソースからレイヤーを指定して地図に描き加える、という流れです。

ちなみにタイルの場所は相対パスでは指定できないようで、 baseURL を指定しています。

var baseURL = 'https://<ユーザー名>.github.io/<レポジトリ名>';

map.on('load', function () {
    map.addSource('linestring', {
        'type': 'vector',
        'tiles': [
            baseURL + '/data/{z}/{x}/{y}.mvt'
        ],
        'minzoom': 4,
        'maxzoom': 9
    });
    map.addLayer(
        {
            'id': 'linestring',       // レイヤーの ID。他のレイヤーとかぶるとエラーになる 
            'type': 'line',           // レイヤーの種類。 fill や symbol などがある
            'source': 'linestring',   // ソース名
            'source-layer': 'foo',    // ここは書き出した layer 名を指定する必要がある
            'layout': {
                'line-cap': 'round',
                'line-join': 'round'
            },
            'paint': {
                'line-opacity': 0.6,
                'line-color': 'rgb(53, 175, 109)',
                'line-width': 100
            }
        }
    );
});

結果

こんな感じになります(緑の線2本が↑で描かれるもの。赤いポリゴンのコードも気になるという方はレポジトリを参照してください)。

f:id:yutannihilation:20200928223917p:plain:w450

  • 結果の地図:
  • レポジトリ:

感想

まだあまりでかいデータでは試してないんですが、書き出しにけっこう時間がかかりそうな予感がします。 とりあえず GeoJSON で書き出して、それを tippecanoe で MVT に変換する、みたいな方が現実的なのかも知れません。 まあでも R で手軽に MVT を書き出せるようになるとそのうち使いどころあるかな?と思ったのでメモでした。