Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

前回の記事 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 なんかをそのまま使えばいいと思いますが、トラブルシューティングなんかの際にも役にたつかもしれません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした