1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MapLibreでFlatGeobuf形式の町丁目ポリゴンを表示してみる

Posted at

はじめに

FlatGeobufファイルがどんなものなのか?気になっていたので、TerraMap APIでも利用している町丁目ポリゴンをFlatGeobuf形式に変換することで、実際に試すことにしました。作成したFlatGeobufファイルについては、MapLibreでOpenStreetMap上に表示してみました。

FlatGeobufとは

FlatGeobufとは、GeoJSONをflatbuffersでシリアライズ(バイナリ化)することで圧縮するファイルフォーマットを指します。シェープファイルと異なりファイルサイズやフィールド名の文字数の制限が無いことや、GeoJSONには無い空間インデックスを持つといったメリットがあり、クラウドに最適化(Cloud Optimized)されたものとしても期待されている規格です。

その他の参考情報
https://gunmagisgeek.com/blog/data/7222
https://www.slideshare.net/slideshow/foss4g-japan-2021-flatgeobuf/250787048

FlatGeobufファイル作成

FlatGeobufファイルは、QGIS Desktopのベクタレイヤからのエクスポート機能で出力することができ、大量なデータでもQGIS Desktopではかなり軽快に利用することができます。

ちなみに弊社が提供できる行政界ポリゴンについて、同じくエクスポート機能で出力したGeoJSONと比較すると、以下のような圧縮効果がみられました。

行政界 ポリゴン数 GeoJSON FlatGeobuf 圧縮率
都道府県 47 75MB 27MB 36%
市区町村 1,896 195MB 71MB 36%
大字 113,116 884MB 331MB 37%
町丁目 182,625 964MB 368MB 38%

参考情報
https://www.aeroasahi.co.jp/fun/column/48/

MapLibreでのポリゴン表示処理

Web地図での使い勝手をみたいので、MapLibreでOpenStreetMap上に町丁目ポリゴンを表示させることにしました。この記事ではMapLibreでFlatGeobufを表示させる方法を知りたいので、ローカルにFlatGeobufファイルを置いて確認しました。

flatgeobuf.org/examples/maplibre/には3つのサンプルプログラムが用意されています。町丁目ポリゴンは比較的大きいデータなので、今回はFiltering a Large Datasetのソースをアレンジし、以下のHTMLファイルを作成してみました。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>MapLibreでFlatGeobuf形式の町丁目ポリゴンを表示してみる</title>
    <link
      href="https://unpkg.com/maplibre-gl@3.3.1/dist/maplibre-gl.css"
      rel="stylesheet"
    />
    <script src="https://unpkg.com/maplibre-gl@3.3.1/dist/maplibre-gl.js"></script>
    <script src="https://unpkg.com/underscore@1.13.1/underscore-min.js"></script>
    <script src="https://unpkg.com/flatgeobuf@3.32.0/dist/flatgeobuf-geojson.min.js"></script>
  </head>
  <body>
    <div id="map" style="width: 100%; height: 95vh"></div>
    <script>
      const mapMaxZoom = 18;
      const fgbMinZoom = 9;

      document.addEventListener("DOMContentLoaded", async () => {
        // 背景地図
        const map = new maplibregl.Map({
          container: "map",
          style:
            "https://tile.openstreetmap.jp/styles/osm-bright-ja/style.json",
          center: [139.662, 35.6727],
          zoom: 10,
          maxZoom: mapMaxZoom,
        });

        // バウンディングボックスをFlatGeobufで期待される形式に変換
        function fgbBoundingBox() {
          const { _sw, _ne } = map.getBounds();
          return {
            minX: _sw.lng,
            minY: _sw.lat,
            maxX: _ne.lng,
            maxY: _ne.lat,
          };
        }

        // バウンディングボックス内のポリゴンデータを取得してソースにセット
        async function updateResults() {
          if (map.getZoom() < fgbMinZoom) {
            return;
          };

          let i = 0;
          const fc = { type: "FeatureCollection", features: [] };
          let iter = flatgeobuf.deserialize(
            "chomoku.fgb",     //町丁目ポリゴンのFlatGeobufファイルURL
            fgbBoundingBox()
          );
          for await (let feature of iter) {
            fc.features.push({ ...feature, id: i });
            i += 1;
          }
          map.getSource("polygons").setData(fc);
        }

        map.on("load", () => {
          map.addSource("polygons", {
            type: "geojson",
            data: { type: "FeatureCollection", features: [] },
          });

          map.addLayer({
            id: "polygons-fill",
            type: "fill",
            source: "polygons",
            minzoom: fgbMinZoom,        // 最小ズームレベル
            maxzoom: mapMaxZoom + 1,    // 最大ズームレベル
            paint: {
              "fill-color": "#0000FF",
              "fill-opacity": 0.5,
            },
          });
          map.addLayer({
            id: "polygons-line",
            type: "line",
            source: "polygons",
            minzoom: fgbMinZoom,        // 最小ズームレベル
            maxzoom: mapMaxZoom + 1,    // 最大ズームレベル
            paint: {
              "line-color": "#0000FF",
              "line-opacity": 0.9,
              "line-width": 1,
            },
          });

          // マップが移動する度にデータを更新すると重くなるので、1秒に1回にする
          updateResults = _.throttle(updateResults, 1000);

          // 初期状態でデータを表示
          updateResults();

          // マップが移動する度にデータを更新する
          map.on("moveend", function (s) {
            updateResults();
          });
        });
      });
    </script>
  </body>
</html>

詳細① flatgeobuf-geojson.min.js がFlatGeobufを扱うスクリプトで、以下の箇所ではバウンディングボックスごとにデシリアライズしてます。

let iter = flatgeobuf.deserialize(
  "chomoku.fgb",     //町丁目ポリゴンのFlatGeobufファイルURL
  fgbBoundingBox()
);

詳細② FlatGeobufのデシリアライズやポリゴンレイヤの表示に関しては、ズームレベルで制限をかけています。制限が無いと、メモリ使用量がすぐに膨大となりエラーになってしまいます。

// バウンディングボックス内のポリゴンデータを取得してソースにセット
async function updateResults() {
  if (map.getZoom() < fgbMinZoom) {
    return;
  };
map.addLayer({
  id: "polygons-fill",
  type: "fill",
  source: "polygons",
  minzoom: fgbMinZoom,        // 最小ズームレベル
  maxzoom: mapMaxZoom + 1,    // 最大ズームレベル

またポリゴンレイヤのmaxzoom値は、背景地図のmaxzoom値 + 1 にすると最大拡大範囲でもポリゴンが表示されます。

町丁目ポリゴンの表示結果

実際に町丁目ポリゴンを表示した例が以下になります。
flatgeobuf_polygons.gif
このように、ズームレベルを限定させることにはなりますが、ベクトルタイルのような使い方ができました。また表示されるポリゴンについては、単純化されることは無いのがFlatGeobufのメリットの一つと思われます。

1
1
0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?