0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

plateau-lod2-mvt を MapLibre Tile に変換した作業記録

Last updated at Posted at 2026-01-30

1. はじめに

2026-01-23 に MapLibre Tile (MLT) がリリースされ、エンコーダーで MLT を生成したり、maplibre-gl-js で表示したり、といった環境が整いました。

この記事では plateau-lod2-mvt をソースとして MVT を MLT に変換し、実際に maplibre-gl-js で表示できるかどうかを確かめてみました。

2 準備

MLT のエンコーダーは Java版と Rust版が提供されています。今回の試行では Java 版を使ってみます。

2.1 エンコーダーのビルド

こちらの手順に従って encoder.jar をビルドします。

Java 21 以上をインストールしておきましょう。Java 17 だとビルドに失敗します。

$ git clone https://github.com/maplibre/maplibre-tile-spec
$ cd maplibre-tile-spec/java
$ chmod +x gradlew
$ ./gradlew cli
$ ls -1 mlt-cli/build/libs/
decode.jar
encode.jar
mlt-cli.jar
$

encoder.jar がビルドできていたら成功です。

2.2 エンコーダーのテスト

git clone でついてくるサンプルファイルを使って、以下のように mvt-to-mlt 変換が可能です。

$ cd maplibre-tile-spec/java
$ java -jar mlt-cli/build/libs/encode.jar --mvt ../test/fixtures/simple/point-boolean.pbf --mlt /tmp/point-boolean.mlt --tessellate --outlines ALL --verbose

また、引数なし、あるいは -h or --help を付与して実行することでオプションを確認できます。

$ java -jar mlt-cli/build/libs/encode.jar --help
 usage:  org.maplibre.mlt.cli.Encode [--coerce-mismatch] [--colmap-auto
 <columns>] [--colmap-delim <map>] [--colmap-list <map>] [--compare-all]
    [--compare-geometry] [--compare-properties] [--compress <algorithm>]
    [--decode] [--dir <dir>] [--elide-mismatch] [--enable-fastpfor]
    [--enable-fsst] [--enable-fsst-native] [--filter-layers <arg>]
    [--filter-layers-invert] [-h] [--maxzoom <level>] [--mbtiles <file>]
    [--metadata] [--minzoom <level>] [--mlt <file>] [--mvt <file>]
    [--noids] [--nomorton] [--offlinedb <file>] [--outlines <tables>]
    [--printmlt] [--printmvt] [--rawstreams] [--regen-ids <pattern>]
    [--server <port>] [--sort-ids <pattern>] [--tessellate]
    [--tessellateurl <arg>] [--tilesetmetadata] [--timer] [-v <level>]
    [--vectorized]


 Convert an MVT tile file or MBTiles containing MVT tiles to MLT format.


         Options             Since                   Description
 --mvt <file>                 --       Path to the input MVT file
 --mbtiles <file>             --       Path of the input MBTiles file.
 --offlinedb <file>           --       Path of the input offline database
                                        file.
 --dir <dir>                  --       Directory where the output is written,
                                        using the input file basename
                                        (OPTIONAL).
 --mlt <file>                 --       Filename where the output will be
                                        written. Overrides --dir.
 --noids                      --       Do not include feature IDs.
 --sort-ids <pattern>         --       Reorder features of matching layers
                                        (default all) by ID, for optimal
                                        encoding of ID values.
 --regen-ids <pattern>        --       Re-generate ID values of matching
                                        layers (default all).  Sequential
                                        values are assigned for optimal
                                        encoding, when ID values have no
                                        special meaning.
 --filter-layers <arg>        --       Filter layers by regex
 --filter-layers-invert       --       Invert the result of --filter-layers
 --minzoom <level>            --       Minimum zoom level to encode tiles.
                                        Only applies with --mbtiles
 --maxzoom <level>            --       Maximum zoom level to encode tiles.
                                        Only applies with --mbtiles
 --metadata                   --       Write tile metadata alongside the
                                        output tile (adding '.meta'). Only
                                        applies with --mvt.
 --coerce-mismatch            --       Allow coercion of property values
 --elide-mismatch             --       Allow elision of mismatched property
                                        types
 --tilesetmetadata            --       Write tileset metadata JSON alongside
                                       the output tile (adding '.json'). Only
                                        applies with --mvt.
 --enable-fastpfor            --       Enable FastPFOR encodings of integer
                                        columns
 --enable-fsst                --       Enable FSST encodings of string columns
                                        (Java implementation)
 --enable-fsst-native         --       Enable FSST encodings of string columns
                                        (Native implementation: Not
                                        available)
 --colmap-auto <columns>      --       Automatic column mapping for the
                                        specified layers (Not implemented)
                                        Layer specifications may be regular
                                        expressions if surrounded by '/'.
                                        An empty set of layers applies to all
                                        layers.
 --colmap-delim <map>         --       Add a separator-based column mapping:
                                        '[<layer>,...]<name><separator>'
                                        e.g. '[][:]name' combines 'name' and
                                        'name:de' on all layers.
 --colmap-list <map>          --       Add an explicit column mapping on the
                                        specified layers:
                                        '[<layer>,...]<name>,...'
                                        e.g. '[]name,name:de' combines 'name'
                                        and 'name:de' on all layers.
 --nomorton                   --       Disable Morton encoding.
 --tessellate                 --       Include tessellation data in converted
                                        tiles.
 --tessellateurl <arg>        --       Use a tessellation server (implies
                                        --tessellate).
 --outlines <tables>          --       The feature tables for which outlines
                                        are included ([OPTIONAL],
                                        comma-separated, 'ALL' for all,
                                        default: none).
 --decode                     --       Test decoding the tile after encoding
                                        it. Only applies with --mvt.
 --printmlt                   --       Print the MLT tile after encoding it.
                                        Only applies with --mvt.
 --printmvt                   --       Print the round-tripped MVT tile. Only
                                        applies with --mvt.
 --compare-geometry           --       Assert that geometry in the decoded
                                        tile is the same as the input tile.
                                        Only applies with --mvt.
 --compare-properties         --       Assert that properties in the decoded
                                        tile is the same as the input tile.
                                        Only applies with --mvt.
 --compare-all                --       Equivalent to --compare-geometry
                                        --compare-properties.
 --vectorized                 --       Use the vectorized decoding path.
 --rawstreams                 --       Dump the raw contents of the individual
                                        streams. Only applies with --mvt.
 --timer                      --       Print the time it takes, in ms, to
                                        decode a tile.
 --compress <algorithm>       --       Compress tile data with one of
                                        'deflate', 'gzip'. Only applies to
                                        MBTiles and offline databases.
 -v, --verbose <level>        --       Enable verbose output.
 -h, --help                   --       Show this output.
 --server <port>              --       Start encoding server

$

3. ファーストテイク (失敗)

3.1 MVT2MLT

変換元となる MVT を git clone して、pbf を探して mlt に変換していくだけです。

$ git clone https://github.com/indigo-lab/plateau-lod2-mvt.git
$ touch mvt2mlt.sh
$ bash mvt2mlt.sh

変換処理はこのような形に。とりあえずパラメータはチュートリアルのパラメータをそのまま流用しています。

mvt2mlt.sh
#/bin/bash

mkdir dist
for pbf in `find plateau-lod2-mvt -name "*.pbf" | sort ` ; do
    echo $pbf
    dir=$(dirname dist/${pbf#*/})
    mkdir -p $dir
    java -jar maplibre-tile-spec/java/mlt-cli/build/libs/encode.jar --mvt $pbf --dir $dir --tessellate --outlines ALL
done

しかし、実際にこれを実行するとこんなエラーが出て異常終了してしまいます。

java.lang.RuntimeException: Layer 'bldg' Feature index 1 Property 'z' has different type: DOUBLE / INT_32
        at org.maplibre.mlt.converter.MltConverter.resolveColumnType(MltConverter.java:164)
        at org.maplibre.mlt.converter.MltConverter.lambda$createTilesetMetadata$0(MltConverter.java:60)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
        at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
        at org.maplibre.mlt.converter.MltConverter.createTilesetMetadata(MltConverter.java:58)
        at org.maplibre.mlt.cli.Encode.encodeTile(Encode.java:370)
        at org.maplibre.mlt.cli.Encode.run(Encode.java:193)
        at org.maplibre.mlt.cli.Encode.run(Encode.java:98)
        at org.maplibre.mlt.cli.Encode.main(Encode.java:82)

3.2 分析

実際にエラーを投げているのはこのあたりのコードです。

詳細は省きますが、あるプロパティの値が Feature によって int だったり float だったりする場合にエラーが出ている、ということになります。

エンコーダー側では INT_32INT_64 にしたり、 FLOATDOUBLE にしたりといった型拡張はしてくれるのですが、 INT_32 / INT_64FLOAT / DOUBLE にするような型拡張には対応していないようです。

3.3 対策

MVT 側で 「プロパティ z は絶対に float でエンコード」 するように強制することができるのであれば、MVT を再生成することで対策が可能です。

tippecanoe --attribute-type
tippecanoe の --attribute-type=z:float はプロパティの型を明示的に指定するオプションですが、float を指定した場合であっても小数点以下がゼロの場合には、やはり int としてエンコードされてしまっているようでした。

あるいは、 Encoder 側で 「プロパティ z は float にキャストして処理」のようなオプションがあれば対策できそうです。

encoder.jar --coerce-mismatch
MLT Encoder の --coerce-mismatch オプションを有効にすると、型の衝突があった場合には型を String に固定する挙動となるようです。float を string として保持するのはサイズメリットが薄れてしまうので、今回のようなケースでは有効ではありません。

encoder.jar --elide-mismatch
MLT Encoder の --elide-mismatch オプションを有効にすると、型の衝突があった場合にもエラー終了せずに処理を継続してくれるようです。しかし、実際にこのように生成された MLT をブラウザで表示しようとすると、デコードエラーのためにタイル自体が非表示になったり、異常な z 値による表示の破綻など、実用に耐えない結果となりました。

現在の tippecanoe および encoder.jar ではちょうどよい対策を見出せませんでした。

若干の敗北感はありますが、 z を メートル単位の float で扱うのをあきらめて、 センチメートル単位の int とすることとしました。

4. セカンドテイク

4.1 MVT 生成

MVT を一旦 GeoJSON にデコードして z をセンチメートル単位の int となるように変換します。こんな実装です。

fix.js
const vt2geojson = require("@mapbox/vt2geojson");

function processFile(file) {
  return new Promise((resolve, reject) => {
    const tokens = file.replace(".pbf", "").split("/");
    const y = parseInt(tokens.pop());
    const x = parseInt(tokens.pop());
    const z = parseInt(tokens.pop());
    vt2geojson(
      {
        uri: file,
        layer: "bldg",
        z: z,
        x: x,
        y: y,
      },
      function (err, result) {
        if (err) reject(err);
        else resolve(result);
      },
    );
  });
}
(async function () {
  for (const file of Array.from(process.argv.slice(2))) {
    const geojson = await processFile(file);
    for (const feature of geojson.features) {
      delete feature.id;
      if (feature.properties.z !== undefined)
        feature.properties.z = Math.round(feature.properties.z * 100);
      console.log(JSON.stringify(feature));
    }
  }
})();

以下のように MVT を再生成しました。

$ node fix.js plateau-lod2-mvt/*/*/*.pbf > temp.jsonl
$ cat temp.jsonl | tippecanoe --no-tile-compression -ad -an -Z10 -z16 -e mvt -l bldg -ai

4.2 MVT2MLT

再度 MVT2MLT 変換を実施します。

$ bash mvt2mlt.sh
mvt2mlt.sh
#/bin/bash

mkdir dist
for pbf in `find mvt -name "*.pbf" | sort ` ; do
    echo $pbf
    dir=$(dirname dist/${pbf#*/})
    mkdir -p $dir
    java -jar maplibre-tile-spec/java/mlt-cli/build/libs/encode.jar --mvt $pbf --dir $dir --tessellate --outlines ALL
done

今回はエラーなしに実行が完了しました。

4.3 プレビュー

生成した MLT を使って、以下のように問題なく表示されました。

image.png

実際の HTML はこのようになっています。

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>plateau-lod2-mvt</title>
    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
    <link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5.17.0/dist/maplibre-gl.css" />
    <script src="https://unpkg.com/maplibre-gl@5.17.0/dist/maplibre-gl.js"></script>
  </head>

  <body>
    <div id="map" style="position: absolute; top: 0; left: 0; bottom: 0; right: 0"></div>
    <script>
      const style = {
        version: 8,
        glyphs: "https://maps.gsi.go.jp/xyz/noto-jp/{fontstack}/{range}.pbf",
        sources: {
          pale: {
            type: "raster",
            tiles: ["https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png"],
            minzoom: 5,
            maxzoom: 18,
            tileSize: 256,
            attribution:
              "<a href='http://maps.gsi.go.jp/development/ichiran.html'>地理院タイル</a>",
          },
          bldg: {
            type: "vector",
            tiles: [new URL("./", location.href).href + "{z}/{x}/{y}.mlt"],
            minzoom: 10,
            maxzoom: 16,
            attribution:
              "<a href='https://github.com/indigo-lab/plateau-lod2-mvt'>plateau-lod2-mvt by indigo-lab</a> (<a href='https://www.mlit.go.jp/plateau/'>国土交通省 Project PLATEAU</a> のデータを加工して作成)",
            encoding: "mlt",
          },
        },
        layers: [
          {
            id: "pale",
            type: "raster",
            source: "pale",
            minzoom: 5,
            maxzoom: 20,
          },
          {
            id: "fill-extrusion-bldg",
            type: "fill-extrusion",
            source: "bldg",
            "source-layer": "bldg",
            minzoom: 10,
            maxzoom: 20,
            filter: ["has", "z"],
            paint: {
              "fill-extrusion-color": [
                "interpolate-lab",
                ["linear"],
                ["%", ["round", ["*", 0.01, ["get", "z"]]], 10],
                0,
                "#d95d2a",
                1,
                "#d95d2a",
                2,
                "#f8df4a",
                3,
                "#f8df4a",
                4,
                "#1d508e",
                5,
                "#1d508e",
                6,
                "#3c782e",
                7,
                "#3c782e",
                8,
                "#845c40",
                9,
                "#726f62",
              ],
              "fill-extrusion-height": ["*", ["get", "z"], 0.01],
            },
          },
        ],
      };

      new maplibregl.Map({
        container: "map",
        center: [139.693909, 35.686302],
        hash: true,
        zoom: 16,
        pitch: 60,
        bearing: 22,
        style: style,
      });
    </script>
  </body>
</html>

JS と CSS は maplibre-gl@5.12.0 以降のものを使用します

z の単位変更(メートル → センチメートル) を受けて各係数を調整しています

"encoding": "mlt" の追加を忘れずに

4.4 所見

ブラウザでの表示速度については MVT の場合と比較して体感できるほどの差は感じられません。他方、変換前の MVT の合計サイズが 468Mbytes, MLT が 484Mbytes と、微増という結果になりました。

MLT 変換の際に --tessellate というオプションが有効になっているのですが、これを少し追ってみましょう。

MaplibreTile 論文 では tesselation について以下のような説明があります。

A notable feature of MLT is the storage of pre-tessellated polygon
meshes directly within the file. This approach allows the computationally intensive triangulation step during runtime (online tessellation), often considered a major bottleneck in modern GPU-based map rendering, to be offloaded to the tile generation phase (offline tessellation).

一般的に WebGL は点・線・三角形しか描画できないので、ポリゴンを描画する場合にはどこかの段階で必ず三角形分割の処理が必要になります。従来これはクライアントサイドでやるしかなかったのですが (online tessellation) 、 MLT ではこれを事前に済ます offline tessellation が導入されているということです。 --tessellate はこのような offline tessellation を有効にするオプションだと推測できます。

しかし、 plateau-lod2-mvt のように fill-extrusion を想定した利用方法の場合、壁面部分のポリゴン生成と三角形分割の処理は事前にやっておくことはできず、依然としてクライアントでの処理が必要です。

encoder.jar --outlines
MLT Encoder の --outlines オプションはデフォルトのサンプルでも有効にされているのでそれを流用しています。こちらを無効にしても MLT の生成は可能なのですが、クライアントでのデコードの際に got error: Cannot convert GpuVector to coordinates without topology information" といったエラーが発生するために、今回のケースではこちらを無効にするのは得策ではないようです。

5. サードテイク

5.1 MVT2MLT

MVT2MLT 変換を実施します。 単に --tessellate を除いただけです。

$ bash mvt2mlt.sh
mvt2mlt.sh
#/bin/bash

mkdir dist
for pbf in `find mvt -name "*.pbf" | sort ` ; do
    echo $pbf
    dir=$(dirname dist/${pbf#*/})
    mkdir -p $dir
    java -jar maplibre-tile-spec/java/mlt-cli/build/libs/encode.jar --mvt $pbf --dir $dir --outlines ALL
done

問題なく実行され、 HTML でのプレビューも機能しました。

image.png

5.2 所見

ブラウザでの表示速度については MVT、 MLT (tessellate あり) と比較して体感できるほどの差は感じられません(すこしもたつく?ような気がする、というレベル)。他方、サイズについては大きく変化がありました。

name size (Mbytes)
mvt 468
mlt (tessellate on) 484
mlt (tessellate off) 279

MVT に対して約6割のファイルサイズに抑えられています。

6. まとめ

MVT からの MLT 生成と表示を試行してみました。

encoder.jar によるエンコード、maplibre-gl-js によるビューはいずれも問題なく機能し、テスト可能な状態であることを確認できました。エンコーダーは MVT からの変換を前提としているため、MLT が仕様上対応を謳っている三次元座標については未確認です。

MVT から MLT への変換において、プロパティとして float 値が使用されている場合には注意が必要です。今後のツールでの対応に期待したいですが、int への移行は現実的な対策です。

MLT の offline tessellate を利用した速度向上について、今回のように fill-extrusion 利用を前提としたセッティングでは効果は体感しづらかったです。大量・複雑なポリゴンを fill で表示するようなベンチマークがあるとわかりやすく効果が体感できるかもしれません。

MLT のサイズ圧縮効果について、tessallate を無効にした場合に顕著な効果がありました。全体サイズは 4割減少しています。通常、MVT (PBF) は1タイルあたり 500kbytes 程度の上限を設定して生成しますが、今後 MLTが500kbytes 程度になるように MVT 生成の上限を引き上げたり、といった調整が必要になるかもしれません。

追記

生成した MLT をこちらのレポジトリから公開しました。

(以上)

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?