TL;DR
- 成果: MBTilesファイルサイズ 15MB → 2MB(87%削減)、更新時間 60秒 → 5秒
- 手法:
-zgオプションで最適なzoomレベルを調査し、-Z14 -z14で単一zoomに固定(元は0〜16 → 最適化後は14のみ) - 効果: 容量に最も影響するmaxzoomを16→14に下げたことで大幅削減(minzoomの影響は小さい)
- Mapbox側: ソースとレイヤーで
minzoom/maxzoomを設定し、クライアント側でも最適化
はじめに
この記事の対象読者
- Mapbox GL JSやtippecanoeを使った地図アプリ開発の経験がある方
- GeoJSONからMBTilesへの変換でパフォーマンス課題に直面している方
- ベクタータイルの基本概念(zoom、タイル分割)を理解している方
課題の背景
地図アプリケーションで大量のポリゴンデータを扱う場合、GeoJSONをそのままMapboxに読み込ませると、パフォーマンスが著しく低下します。そこで、tippecanoeを使ってGeoJSONをMBTiles(ベクタータイル)に変換するのが一般的な手法です。
しかし、tippecanoeで広いzoom範囲(例えば0〜16)を指定すると、全てのレベルのタイルが生成されます。特に高zoomレベル(15以上)のタイルは容量が指数関数的に増加するため、これにより:
- ファイルサイズが膨大になる(各zoomレベルで約4倍のタイル数)
- 更新処理に時間がかかる(全zoomレベルのタイル再生成)
- ストレージとネットワークコストが増加
今回、データの特性を分析して必要なzoomレベル範囲を特定し、tippecanoeとMapboxの設定を最適化することで、更新時間を1分から5秒に短縮できました。
なぜzoomレベルの最適化が重要なのか
MBTilesとzoomレベルの関係
MBTilesは、異なるzoomレベルのタイルを格納するSQLiteデータベースです。各zoomレベルで地図を表示するために必要なタイルが事前に生成されています。
zoom 0: 1タイル(世界全体)
zoom 1: 4タイル
zoom 2: 16タイル
zoom 3: 64タイル
...
zoom 10: 約100万タイル
zoom 14: 約2.7億タイル
zoom 16: 約43億タイル※上記は世界全体をカバーする場合の理論値です。実際にはtippecanoeはデータが存在する領域のみタイルを生成するため、生成されるタイル数はデータの範囲に応じて大幅に少なくなります。
zoomレベルが1上がるごとに、タイル数は約4倍に増加します。
重要: MBTilesの容量に最も影響を与えるのはmaxzoomの設定です。maxzoomを高くすると、タイル数が爆増し、ジオメトリも細かいまま保持されるため、容量と生成時間が急増します。
一方、minzoomを低くして表示範囲を広げても(例:zoom 10〜14を生成)、低zoomレベルのタイル数は相対的に少ないため、容量増加は非常に小さいです。そのため、「どこまで詳細に表示するか」を決めるmaxzoomの見極めが最も重要です。
実際のユースケースを考える
数百メートル〜数キロメートル程度のポリゴンデータを考えてみましょう。この場合:
- zoom 0〜9: ポリゴンが小さすぎて表示しても意味がない
- zoom 10〜14: ポリゴンの形状が識別でき、ユーザーが操作できる
- zoom 15〜16: データ解像度以上の詳細は得られないのに、タイル数は膨大
このように、データの特性に応じた適切なzoomレベルの範囲を設定することが重要です。
ポリゴンサイズとzoomレベルの目安
以下の表は、代表的なポリゴンサイズと推奨されるzoomレベルの対応です。
| ポリゴンサイズ | 推奨minzoom | 推奨maxzoom | 用途例 |
|---|---|---|---|
| 10m × 10m | zoom 14 | zoom 18 | 小規模ポリゴン |
| 100m × 100m | zoom 11 | zoom 15 | 中小規模ポリゴン |
| 500m × 500m | zoom 9 | zoom 13 | 中規模ポリゴン |
| 1km × 1km | zoom 8 | zoom 12 | 大規模ポリゴン |
| 10km × 10km | zoom 5 | zoom 9 | 広域ポリゴン |
tippecanoeのzoomオプションで最適化
Step 1: -zgで最適なzoomレベルを調査
まず、tippecanoeの-zgオプションを使ってデータに適したmaxzoomを調査します。
tippecanoe -zg -o test.mbtiles input.geojson実行すると以下のようなメッセージが出力されます:
Choosing a maxzoom of -z14 for features about 150 meters apartこの出力から、今回のデータではzoom 14が適切であることがわかりました。
Step 2: 調査結果を元に本番コマンドを設定
-zgの調査結果を踏まえ、本番では-Zと-zで明示的にzoom範囲を指定します。
# 最適化前:広いzoom範囲(デフォルトまたは0〜16)
tippecanoe -o output.mbtiles -z16 input.geojson
# 最適化後:zoom 14のみに固定
tippecanoe -o output.mbtiles -Z14 -z14 \
-pk -pf -pg \
-L '{"file": "polygons.geojson", "layer": "polygons"}' \
-L '{"file": "points.geojson", "layer": "points"}' \
--forceオプションの解説
zoomレベル制御:
-Z14: minzoomを14に設定(zoom 0〜13のタイルは生成しない)-z14: maxzoomを14に設定(zoom 15以上のタイルは生成しない)
制限解除オプション:
-pk: タイルサイズ制限を解除(デフォルトは500KB)-pf: タイル内のフィーチャー数制限を解除(デフォルトは200,000)-pg: タイル生成時の統計情報出力を無効化
注意: -pkと-pfを使用すると、1タイルが巨大化する可能性があります。特に複雑なポリゴンでは2MBを超えるとMapbox側で読み込みに失敗することがあるため、データ量が多い場合は注意が必要です。
レイヤー指定:
-L '{"file": "...", "layer": "..."}': 複数のGeoJSONファイルを異なるレイヤー名で追加
その他:
--force: 既存ファイルを上書き
なぜ--drop-densest-as-neededを使わないのか
--drop-densest-as-neededはタイルサイズ制限を超えた場合にフィーチャーを間引くオプションですが、ポリゴンが消えてしまう問題が発生する可能性があります。
そのため、代わりに-pk(タイルサイズ制限解除)と-pf(フィーチャー数制限解除)を使用して、全てのポリゴンを保持しています。
主要なオプション一覧
| オプション | 説明 | 例 |
|---|---|---|
-Z / --minimum-zoom |
最小zoomレベルを指定 | -Z14 |
-z / --maximum-zoom |
最大zoomレベルを指定 | -z14 |
-zg |
データから最適なmaxzoomを自動推測 | -zg |
-pk |
タイルサイズ制限を解除 | - |
-pf |
タイル内フィーチャー数制限を解除 | - |
-pg |
統計情報出力を無効化 | - |
-L |
レイヤー名を指定してファイルを追加 | -L '{"file": "...", "layer": "..."}' |
--force |
既存ファイルを上書き | - |
Mapbox GL JS(react-map-gl)での設定
ソース設定
tippecanoeで生成したMBTilesをMapboxで使用する際も、適切なzoom設定が重要です。
tippecanoeで指定したzoomレベルに合わせて、Sourceのminzoom/maxzoomも同じ値に設定します。
<Source
id="polygons"
type="vector"
tiles={[`https://example.com/api/tiles?z={z}&x={x}&y={y}`]}
maxzoom={14} // tippecanoeの-zと同じ値
minzoom={14} // tippecanoeの-Zと同じ値
>
{tileLayers}
</Source>ソースとレイヤーのzoom設定の違い
| 設定箇所 | 効果 | 用途 |
|---|---|---|
Source minzoom |
指定zoom未満ではタイルをリクエストしない | ネットワーク・パフォーマンス最適化 |
Source maxzoom |
指定zoomを超えるとoverzoom(既存タイルを拡大) | ストレージ最適化 |
Layer minzoom |
指定zoom未満では非表示(タイルは読み込む) | 視覚的な制御 |
Layer maxzoom |
指定zoomを超えると非表示 | 視覚的な制御 |
ポイント: Source側のminzoom/maxzoomをtippecanoeで生成したzoomレベルと一致させることで、不要なタイルリクエストを防ぎ、パフォーマンスを最適化できます。
注意: 今回の例のようにminzoomとmaxzoomを同じ値(14)に設定すると、zoom 13以下やzoom 15以上ではタイルが取得されず、地図上にデータが表示されなくなります。これは特定のzoomレベルでのみデータを表示する要件がある場合の設定であり、一般的なユースケースではminzoomとmaxzoomに幅を持たせることが多いです。
実装結果
Before/After比較
| 項目 | 最適化前 | 最適化後 | 改善率 |
|---|---|---|---|
| zoomレベル範囲 | 0〜16 | 14のみ | 17レベル → 1レベル |
| MBTilesファイルサイズ | 約15MB | 約2MB | 87%削減 |
| 更新処理時間 | 約60秒 | 約5秒 | 92%削減 |
ポイント: -zgオプションで調査した結果、データ特性上zoom 14のみで十分と判明。不要なzoomレベル(0〜13、15〜16)を全て削減することで、劇的にファイルサイズが減少しました。
パフォーマンスへの影響
- 初期読み込み: zoom 14のみでタイルを読み込むため、広域表示時のリクエストが削減
- ズーム操作: zoom 14のタイルのみ存在するため、キャッシュ効率が向上
- 更新デプロイ: MBTilesの再生成・アップロード時間が大幅に短縮
まとめ
MBTilesのzoomレベル最適化は、地図アプリケーションのパフォーマンス改善に非常に効果的です。
- 調査:
-zgオプションでデータに適した最適なzoomレベルを特定 - tippecanoe設定:
-Zと-zで必要なzoomレベルのみ生成(今回はzoom 14のみ) - Mapbox設定: Sourceの
minzoom/maxzoomをtippecanoeと同じ値に設定
「全zoomレベルでタイルを生成する」設定から、「データに適したzoomレベルのみ生成する」設定に変更するだけで、ファイルサイズと処理時間を大幅に削減できます。
また、--drop-densest-as-neededでフィーチャーを間引くのではなく、-pkと-pfで制限を解除することで、ポリゴンが消えてしまう問題を回避できます。
この最適化が特に有効なケース
今回のようにzoomレベルを1つに絞る極端な最適化は、更新処理が頻繁に発生するケースで特に有効です。ストレージ容量や更新頻度に問題がなければ、ここまで削減する必要はありません。
ただし、MBTilesの再生成・アップロードが頻繁に発生するシステムでは、処理時間の短縮がユーザー体験やシステム負荷に大きく影響するため、積極的に検討する価値があると考えます。
