LoginSignup
9
10

More than 5 years have passed since last update.

Leaflet でバイナリベクトルタイル処理の流れを追ってみる

Posted at

はじめに

前回の記事 Leaflet.VectorGrid でバイナリベクトルタイルを表示する では leaflet.VectorGrid.Protobuf というプラグインをつかってバイナリベクトルタイルを表示してみました。

ただ、正直ブラックボックスで中で何をやっているかよくわかりません。この記事では理解のために

  • ベクトルタイル取得
  • バイナリから GeoJSON を読み出し
  • GeoJSON をレイヤーとして Leaflet に追加

という手順を実行するカスタムプラグインを作ることで、中でどんなことが行われているかを追ってみたいと思います。

やってみた

こんなコードが

index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Hello MVT</title>
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" />
  <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
  <script src="https://unpkg.com/leaflet.vectorgrid@1.3.0/dist/Leaflet.VectorGrid.bundled.min.js"></script>
</head>

<body>
  <div id="map" style="position:absolute;top:0;left:0;bottom:0;right:0;"></div>
  <script>
    var MyGridLayer = L.GridLayer.extend({
      createTile: function(coords) {
        var template = "https://wata909.github.io/atsuma_mvt/sediment/{z}/{x}/{y}.pbf";
        fetch(L.Util.template(template, coords)).then(a => a.arrayBuffer()).then(buffer => {
          var layers = new VectorTile(new Pbf(buffer)).layers;
          Object.keys(layers).forEach(name => {
            var layer = layers[name];
            for (var i = 0; i < layer.length; i++) {
              var geojson = layer.feature(i).toGeoJSON(coords.x, coords.y, coords.z);
              L.geoJson(geojson).addTo(map);
            }
          });
        });
        return document.createElement('div');
      }
    });

    var map = L.map("map", {
      maxZoom: 19,
      zoom: 15,
      center: [42.7450, 141.9123],
    });

    L.tileLayer('https://maps.gsi.go.jp/xyz/ort/{z}/{x}/{y}.jpg', {
      attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>"
    }).addTo(map);

    new MyGridLayer({
      attribution: "<a href='https://github.com/koukita/2018_09_06_atumatyou' target='_blank'>この地図は地理院地図 平成30年北海道胆振東部地震 厚真川地区 正射画像をトレースした地図です</a>"
    }).addTo(map);
  </script>
</body>

</html>


こうなりました

image.png

ワーキングデモはこちらから。

解説

ライブラリ

さすがにバイナリのパースを自分でやるのは大変なので、ライブラリの力を借ります。前回記事でも少し触れたのですが、@mapbox/vector-tilepbf があればとりあえず大丈夫です。

ただ、こいつらは基本的には node.js 用なので webpack なり browserify なりしないといけないので面倒です。実は https://unpkg.com/leaflet.vectorgrid@1.3.0/dist/Leaflet.VectorGrid.bundled.min.js にはこのへんのライブラリがバンドルされていて使えるようになっています。今回は VectorGrid は使いませんが、バンドルライブラリを使わせてもらいましょう。

@mapbox/vector-tileVectorTile という名前で、pbfPbf という名前でバンドルされています。

L.GridLayer

Leaflet の L.GridLayer を拡張して作りました。
唯一実装が必要なメソッド createTile は xyz を入力としてタイル (任意の HTML 要素) を返すことになっているので、これを実装しています。詳しくは このあたり

メインの処理

本丸の処理ですが結構短いです。

createTile: function(coords) {
  var template = "https://wata909.github.io/atsuma_mvt/sediment/{z}/{x}/{y}.pbf";
  fetch(L.Util.template(template, coords)).then(a => a.arrayBuffer()).then(buffer => {
    var layers = new VectorTile(new Pbf(buffer)).layers;
    Object.keys(layers).forEach(name => {
      var layer = layers[name];
      for (var i = 0; i < layer.length; i++) {
        var geojson = layer.feature(i).toGeoJSON(coords.x, coords.y, coords.z);
        L.geoJson(geojson).addTo(map);
      }
    });
  });
  return document.createElement('div');
}
  1. createTile メソッドは coords オブジェクトを引数として呼び出されます。coords の中身は {z:15,x:29299,y:12071} のようにタイルの xyz 座標を持っています。
  2. 続いて L.Util.template(url,coords) によって、バイナリベクトルタイルの取得先の URL を作ります
  3. 作った URL からデータを取得します。jquery でも生の XHR でもなんでもいいのですがここでは fetch(url) を使っています。最終的に arrayBuffer が欲しいので途中で a.arrayBuffer() を使っていることに注意。
  4. うけとった arrayBuffer から new Pbf(buffer) によって、 Pbf のインスタンスを作ります
  5. さらに Pbf から new VectorTile(pbf) によって VectorTile のインスタンスを作ります
  6. VectorTile のインスタンスには layers というプロパティがあり、これは レイヤー名 をキー、 VectorTileLayer オブジェクトをバリューとしたオブジェクトです
  7. VectorTileLayer オブジェクトは VectorTileFeature オブジェクトをたくさんもった配列のようなものです。保持数は length に格納されており、VectorTileLayer.feature(index) メソッドによって VectorTileFeature を得ることができます。
  8. VectorTileFeature には toGeoJSON というメソッドがあり、このメソッドを実行することで GeoJSON を得ることができます。ただし、feature.toGeoJSON(x,y,z) のように、冒頭のタイル座標を渡す必要があることに注意が必要です。
  9. ここまでで GeoJSON が得られたので L.geoJson(geojson).addTo(map) によって雑にレイヤー化して地図に追加します

とりあえず押さえておくべきは VectorTile VectorTileLayer VectorTileFeature の3つのクラスぐらいで、https://github.com/mapbox/vector-tile-js の API ガイドを読めば十分という印象でした。

まとめ

ベクトルタイルを取得して GeoJSON 化して Leaflet に追加する処理を自分で書いてみました。実はあんまり難しい部分はなくて、割と短いステップでできたことに驚きです。もちろん普段は VectorGrid なんかをそのまま使えばいいと思いますが、トラブルシューティングなんかの際にも役にたつかもしれません。

9
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10