Leaflet1.0系でGeoJSONタイルを扱うTIPS

これは [ベクトルタイル Advent Calendar 2017](https://qiita.com/advent-calendar/2017/vt) の記事です。

# はじめに

[Leaflet](http://leafletjs.com/) で GeoJSON タイルを扱う方法としては [L.TileLayer.GeoJSON プラグイン](https://github.com/glenrobertson/leaflet-tilelayer-geojson) が定番で、[国土地理院ベクトルタイル提供実験](https://github.com/gsi-cyberjapan/vector-tile-experiment) でもこのプラグインを使ったデモを紹介しています。
ただ、このプラグインは一世代前の Leaflet0.7系を対象としていて、現行版である Leaflet1.0系では動作しません。[作者も忙しいといっている](https://github.com/glenrobertson/leaflet-tilelayer-geojson/issues/39) ので今後も対応の見込みは薄そうです。

このエントリではプラグインや自前クラスを使わずにささっと Leaflet1.0系で GeoJSON タイルを表示する TIPS を紹介します。

# デモ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/82494/2e2e3ad2-c956-327a-9a06-a523272807c3.png)

<https://bl.ocks.org/frogcat/raw/334998cbd13c4875211debf0a9e6f785/#17/35.66171/139.67930>


# ソース

```index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
  <title>Leaflet1.2 GeoJSON Tile Tips</title>
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css" />
  <script src="https://unpkg.com/leaflet@1.2.0/dist/leaflet.js"></script>
  <script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
</head>

<body>
  <div id="map" style="position:absolute;top:0;left:0;bottom:0;right:0;"></div>
  <script>
    // Initalize map
    var map = L.map("map", L.extend({
      minZoom: 17,
      zoom: 18,
      maxZoom: 22,
      center: [35.64430, 139.67124]
    }, L.Hash.parseHash(location.hash)));
    map.zoomControl.setPosition("bottomright");
    L.hash(map);

    // Add background layer
    L.tileLayer("https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg", {
      attribution: "<a href='http://maps.gsi.go.jp/development/ichiran.html'>GSI</a>",
      maxZoom: 22,
      maxNativeZoom: 18
    }).addTo(map);

    // Create tilelayer with empty image
    L.tileLayer(L.Util.emptyImageUrl, {
      attribution: "<a href='http://maps.gsi.go.jp/development/ichiran.html'>GSI</a>",
      maxZoom: 22,
      maxNativeZoom: 18,
      minNativeZoom: 18
    }).on("tileload", function(event) {
      // Add tileload event handler to load geojson and add geojson layer
      var url = "https://cyberjapandata.gsi.go.jp/xyz/experimental_fgd/{z}/{x}/{y}.geojson";
      fetch(L.Util.template(url, event.coords)).then(a => a.ok ? a.json() : null).then(geojson => {
        if (!geojson || !this._map) return;
        event.tile.geojson = L.geoJSON(geojson, {
          style: function(geojson) {
            return {
              weight: 1.5,
              color: "#ffffff"
            };
          }
        }).addTo(this._map);
      });
    }).on("tileunload", function(event) {
      // Add tileunload event handler to remove geojson layer
      if (event.tile.geojson && this._map)
        this._map.removeLayer(event.tile.geojson);
    }).addTo(map);
  </script>
</body>

</html>

```

<https://gist.github.com/frogcat/334998cbd13c4875211debf0a9e6f785>

# メモ

こんな仕組みです。

1. 透明な画像を表示する L.TileLayer を作成
2. L.TileLayer のタイル追加イベントを拾って GeoJSON の取得／L.GeoJSON の追加
3. L.TileLayer のタイルの削除イベントを拾って L.GeoJSON の削除



```js
L.tileLayer(L.Util.emptyImageUrl,{
  // レイヤーが表示される最大ズームレベル。デフォルトが18なので大きくしておく
  maxZoom: 22, 

  // ベクトルタイルの提供ズームレベル上限を指定
  maxNativeZoom: 18, 

  // ベクトルタイルの提供ズームレベル下限を指定
  minNativeZoom: 18 

}).on("tileload",function(event){

  // タイルをロードするときの処理を記述

}).on("tileunload",function(event){

  // タイルが消去されるときの処理を記述

}).addTo(map);
```

- [L.Util.emptyImageUrl](http://leafletjs.com/reference-1.2.0.html#util-emptyimageurl) はビルトインの透過GIFです
- [tileload event](http://leafletjs.com/reference-1.2.0.html#gridlayer-tileload) はタイルを読み込んだときに発行されるイベントです
- 同様に [tileunload event](http://leafletjs.com/reference-1.2.0.html#gridlayer-tileunload) はタイルが不要になったときに発行されるイベントです
- maxNativeZoom / minNativeZoom にはベクトルタイルの提供ズームレベルの上下限を指定します

Leaflet1.0は従来の maxNativeZoom に加えて minNativeZoom にも対応しているので、上のような設定で勝手にオーバーズーミングもやってくれます。ただ、minNativeZoom を設定した状態でズームアウトするととてもたくさんのタイルを取得しに行くことになるので注意しましょう。