7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「地名の最後の一文字だけで地図をつくると地形が見える :: デイリーポータルZ」を Leaflet と 地理院ベクトルタイルで

Last updated at Posted at 2020-06-19

はじめに

地名の最後の一文字だけで地図をつくると地形が見える :: デイリーポータルZ という素敵な記事があります。
これを Leaflet国土地理院ベクトルタイル提供実験(地図情報-注記) を使って実装してみましょう。

できあがり

このように動作するものが出来上がりました。日本全国閲覧可能です。

Screenshot

ワーキングデモはこちらからどうぞ。スマホでも PC(IE以外) でも動くと思います。

ソースコードはこちらに置いてますので fork して改造してみてください。

使用上の注意

  • ズームレベルが 13 以上のときに「一文字」が表示されます (引いた状態では出ません)
  • 「一文字」にマウスを載せるともともとの地名が表示されます
  • それなりに通信が発生するので従量課金環境での閲覧はおすすめしません

解説

解説、といっても100行くらいの HTML+JS なのでそのまま貼っておきます。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>saigo-no-hitomoji-chizu</title>
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" />
  <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
  <script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
  <style>
    #map {
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
    }

    .knj {
      font-size: 18pt;
      font-weight: bold;
      color: white;
      text-shadow: 0 0 8px black, 0 0 8px black, 0 0 8px black, 0 0 8px black, 0 0 8px black;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <script>
    const map = L.map("map", L.extend({
      zoom: 13,
      maxZoom: 18,
      center: [35.6707, 139.7852]
    }, L.Hash.parseHash(location.hash)));

    L.tileLayer("https://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png", {
      attribution: "<a href='http://maps.gsi.go.jp/development/ichiran.html'>地理院タイル</a>",
      opacity: 0.4
    }).addTo(map);

    map.attributionControl.addAttribution("<a href='https://github.com/frogcat/saigo-no-hitomoji-chizu'>fork me on GitHub</a>");

    map.zoomControl.setPosition("bottomright");

    L.hash(map);

    const knjLayer = Object.assign(L.gridLayer({
      maxNativeZoom: 15,
      minNativeZoom: 15
    }), {
      onAdd: function(map) {
        this._group = L.featureGroup().addTo(map);
        L.GridLayer.prototype.onAdd.call(this, map);
      },
      onRemove: function(map) {
        if (this._group) {
          this._group.remove();
          delete this._group;
        }
        L.GridLayer.prototype.onRemove.call(this, map);
      },
      createTile: function(coords) {
        const div = document.createElement("div");
        div.layers = [];
        if (this._map.getZoom() < 13) return div;
        const url = L.Util.template("https://cyberjapandata.gsi.go.jp/xyz/experimental_anno/{z}/{x}/{y}.geojson", coords);
        fetch(url).then(a => a.ok ? a.json() : null).then(json => {
          if (json === null || !this._map || this._map.getZoom() < 13 || !this._group) return;
          json.features.filter(x => x.properties.annoCtg.match(/^居住/)).forEach(x => {
            const knj = x.properties.knj;
            const k = knj.replace(/(.+)$/, "").split("").pop();
            const p = x.geometry.coordinates;
            const m = L.marker([p[1], p[0]], {
              icon: L.divIcon({
                html: k,
                className: "knj"
              })
            }).bindTooltip(knj).addTo(this._group);
            div.layers.push(m)
          });
        });
        return div;
      }
    }).on("tileunload", function(e) {
      while (e.tile.layers.length > 0) {
        e.tile.layers.pop().remove();
      }
    });

    map.on("zoomend", function() {
      const z = this.getZoom();
      if (z < 13) {
        if (this.hasLayer(knjLayer)) this.removeLayer(knjLayer);
      } else {
        if (!this.hasLayer(knjLayer)) this.addLayer(knjLayer);
      }
    }).fire("zoomend");
  </script>
</body>

</html>

  • Leaflet から GeoJSON のベクトルタイルを使用する方法は以前 https://qiita.com/frogcat/items/97ab41c6675213b1a3f4 に書きました。ここでは L.TileLayer + emptyImage を使う代わりに Object.assign で直接 GridLayer のインスタンスをいじっています。
  • minNativeZoom と maxNativeZoom をそれぞれ 15 に指定することで、ズームレベルによらず z=15 のタイルを作ってくれます。ただ、z=15 で使用されるタイル数を基準とすると、z=14 では4倍、z=13 では16倍、さらに z=12 では64倍の GeoJSON タイルを読み込むことになり z=12 あたりから挙動が怪しくなってきます。そのため、z=12以下の環境では knjLayer を map から remove することで対処しています

まとめ

今のトレンドは mapbox-gl-js地理院地図VECTOR だとは思うのですが、どうもしっくりくるやり方が思いつきませんでした。ここでは Leaflet と地理院ベクトルタイル (GeoJSON) でやっつけてみました。

7
5
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
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?