未来を形作るテクノロジーの深掘り記事。

国土全体をボクセル化する:地理データパイプラインの舞台裏

オープンな地理データで国土を1:1ボクセル化した事例を解説。タイル分割、LOD、空間データのストリーミングの要点をまとめます。

ブロック状の氷河と階段状の立方体の山頂を持つ、ボクセル風のアルプスの谷。
1ブロック=1メートル。政府の公開地理データから国土全体をボクセルで再現。

スイス政府のオープンデータポータルには、エイガー北壁を十分に細かくサンプリングした標高データが眠っています。岩肌の質感まで感じられるほどです。ある開発者はこうしたファイルをボクセル化パイプラインに流し込み、結果を遊べるプロダクトとして公開しました。スイス全土を1:1のMinecraftワールドにしたもので、1ブロックが1メートル、マッターホルンは実物と同じ高さと急峻さで立っています。ダウンロードは圧縮で24GB、無料のサンプル地域だけでもおよそ2,400km²あります。その裏側のエンジニアリング(タイル分割、LOD、遅延生成)は、巨大な空間データを扱いながらすべてが炎上することなく動かす見事な手本です。

課題:ボクセル化した国土はメモリに収まらない

一緒に計算してみましょう。スイスの面積は41,285km²です。1ブロック=1平方メートルとすると、穴を一つ掘る前から表層の柱が413億本になります。柱ごとに石、土、湖底などを含めて深さ100ブロックずつ持たせると、4兆ブロックです。ブロックあたり1バイトでも、生のジオメトリだけで4TBになります。それなのに完成したワールドは24GBに圧縮されてダウンロードできます。ここから重要なことがわかります。ほとんどのジオメトリは実際にはジオメトリとして保存されていないのです。レシピとして保存されているのです。

縦方向の制約も問題を起こします。素のMinecraftではビルドの高さは384ブロックが上限です。スイスはマッジョーレ湖の海抜193mからデュフールシュピッツの4,634mまで広がっており、4,441mの幅は、鉱山を一つ掘る前から素の上限の11倍以上になります。そのため、柱単位のストレージを置き換え、必要なだけ積み重ねられる16×16×16の立方体を使うCubic Chunksというmodが必要になります。このmodが安定して動くのはMinecraft Java 1.12.2だけなので、今も開発が続くプロジェクトが数年前のゲームバージョンに固定されているのはそのためです。

3つ目の壁はメモリです。プレイヤーが初めてエリアを訪れたときに元データからブロックを組み立てるのは計算負荷が高く、プロジェクトは最低16GBのRAMを要求し、Ryzen 7 7800X3D・32GBのマシンでテストされています。ゲームやGISでストリーミングの予算と格闘したことがある人なら、この形に見覚えがあるでしょう。GPUのVRAMが足りなくなったときと同じトリアージです。全部は保持できないので、今保持すべきものを決めることにどんどん長けていくしかないのです。

失敗その1:先に全世界を焼き付けてしまう

最も素直なアプローチは、大きなオフラインジョブを一本流すことです。地理データを読み込み、Minecraftのリージョンファイルを書き出し(Anvil形式では1リージョンが512×512ブロック)、圧縮して配布する。デンマークは2014年に似たことをやっています。デンマーク地理データ庁がデンマーク全土をMinecraftマップとして公開したのです。オープンデータの金字塔となったデモで、国規模のマップが可能であることを証明しました。では、なぜそれをそのままやらないのか。

事前焼き付けには、野心の大きさに比例して増える構造的なコストがあるからです。すべてのリージョンのすべてのブロックを、プレイ前に生成・書き出し・圧縮しなければならず、データ更新のたびに全体の再構築と数十GBの再配布が必要になります。プレイヤーは訪れることのない山々のデータまでダウンロードすることになり、41,000km²のほとんどは誰にも訪れられることがありません。何より、焼き付けられたワールドは公開した瞬間にデータのスナップショットで固定されてしまいます。書き捨ての成果物であり、全体を再生成して再配布しない限り改善できず、ユーザーは惑星丸ごとを再ダウンロードすることになります。

失敗その2:単一の標高ラスタを単一の解像度で作る

二つ目の誘惑は正規化です。すべての入力を単一の解像度の全球ラスタに変換すれば、パイプラインのコードパスは一つで済みます。スイス国内にはswissALTI3Dという高解像度の地形モデルがあります。国境の外にはCopernicus GLO-30 DEMがあり、30mごとに標高点が一つだけです。一様にするために全部を30mに落とすと、エイガー北壁は階段状の砂山になってしまいます。逆に最も細かい解像度に合わせて全部を上げると、ストレージと処理が爆発し、しかも国境付近には本物のデータが存在しないため、単に無知をアップサンプリングしただけになります。

意味情報にも同じくらい深刻な異質性があります。swissBUILDINGS3Dはスイス全土をカバーしているので、ジェネレーターは実際の建物の輪郭に簡略化したボクセル建築を立てられます。ただし国内に限られます。ジュネーブ付近で国境を越えると建物レイヤーがまったく存在しないため、パイプラインは唯一まともなフォールバックを使います。家があるべき場所に木を植えるのです。水域には独自のソース(湖底はswissBATHY3D)が必要で、氷河、森林、ブドウ畑も同様です。単一の入力ファイルなどありません。地理データには、それが存在したことは一度もないのです。

この教訓はMinecraftの枠を超えて通用します。解像度は全体の定数ではなくタイルごとのメタデータです。一様な入力を前提にしたパイプラインは、忠実度を無駄にするか、捏造するかのどちらかになります。そして捏造された忠実度は、正直な粗さよりたちが悪い。内側からは見分けがつかないからです。

地図タイルのグリッドが漏斗を流れ落ち、組み立てられたボクセルの地形として現れる様子。
中核となる発想の転換:データはタイルとレシピとして保持し、誰かが実際に訪れる場所でだけブロックを組み立てる。

実際にうまくいく方法:遅延生成する、タイルベースのパイプライン

スイスのプロジェクトがうまくいっているのは、すべてを焼き付けようとする本能を逆転させているからです。完成したワールドをダウンロードするのではなく、ワールドパッケージ、つまり前処理済みの地理データとカスタムジェネレーターをダウンロードします。ブロックは、実際にその場所を訪れた最初の瞬間にゲームが構築します。新しいエリアへの初回訪問には数分かかりますが、その後はどのMinecraftワールドとも同じようにチャンクがキャッシュされます。計算コストは事前ではなく訪問ごとに支払われ、誰も立たない地形は生成されません。タイルがストレージ、キャッシュ、生成、障害処理のすべての単位になっています。

タイル分割はスイスの公式測量座標系であるLV95に基づいています。各タイルには、どのデータセットがどの解像度でカバーしているかが記録されています。測量座標からブロック座標への変換は単純なアフィン変換です。プロジェクトは計算式も公開しており、国の地図ビューアから座標を取得してゲーム内の該当地点にテレポートできます:

def lv95_to_block(east: float, north: float, height: float) -> tuple[int, int, int]:
"""Convert Swiss LV95 survey coordinates to Minecraft block coordinates."""
x = int(east - 2_480_000)   # strip the LV95 easting offset
z = int(1_300_000 - north)  # Minecraft's z axis grows southward
y = int(height)             # one block = one meter above sea level
return x, y, z
# The summit of the Jungfrau, 4,158 m:
print(lv95_to_block(2_640_188, 1_154_072, 4158))
# -> (160188, 4158, 145928)
# x and z match the in-game teleport command exactly; the game just
# drops you a few meters higher so you land on the summit, not in it.

タイルのグリッドの上に、列ごとに評価されるレイヤースタックとして地形生成が行われます。基本の標高はswissALTI3Dから、水域、森林、道路、鉄道線路、ダム、雪崩防護柵などの地表被覆はswissTLM3Dから取得します。湖底はswissBATHY3D、樹木の高さはswissSURFACE3D、氷河の輪郭は1850年までさかのぼるGLAMOSの目録、氷の厚さはETHチューリッヒのデータセットから取得しているため、アレッチ氷河は岩の上に白いテクスチャを貼ったものではなく、本当に削り出された氷になっています。草地、岩屑地、ブドウ畑は連邦の生息地マップから、チューリッヒの街路樹さえ市の樹木台帳から取得しています。合計で約15の公開データセットがあり、すべて無料で利用できます。swisstopoは2021年に、商用利用を含めてデータを公開しました。

ボクセルワールドは地形の写真ではありません。パイプラインが1平方メートルごとに下した、膨大な小さな判断のキャッシュなのです。だからワールドの品質は、そうした判断の品質そのものなのです。

LODは描画のトリックではなく、データの問題である

Minecraftの標準的な描画距離は数百メートル程度しか見せてくれません。プロジェクトのスクリーンショットではアレッチ氷河が何キロも先へ湾曲して消えていく様子が見えます。この遠景(「Fernsicht」)は、ゲームが初回起動時に一度だけ構築する、忠実度を落とした別の表現です。これは古典的なLODですが、重要なのはそのLODがどこに存在するかという判断です。描画側で即興的に補正するのではなく、取り込み時に元データから計算されているのです。

地図タイルのピラミッドを作ったことがあれば、この計算はおなじみでしょう。ズームレベルごとに両軸の解像度が半分になるため、フルピラミッドのコストは 1 + 1/4 + 1/16 + … ≈ ベースレイヤーの4/3 になります。つまり、あらゆるスケールで滑らかな振る舞いを得るために33%のストレージ上乗せを払うことになります。ボクセル地形のLODは、このピラミッドを立てたものと同じです。遠くの地形は粗いズームレベル、近くの地形は細かいズームレベルです。本当の技は、適切な対象を素早く取得できるキーの設計にあります。ジオハッシュと本格的な空間インデックスのトレードオフでも同じ問題に直面しました。キーの選び方が、その後のあらゆる検索コストを決めるのです。

ここで私の一番のお気に入りのLOD判断は、見た目とはまったく関係ありません。無料ダウンロードは2,400km²のスライス、ユングフラウ・アレッチ世界遺産地域です。エイガー、メンヒ、ユングフラウ、そしてアルプス最大の氷河を含みます。有料の国全体と同じパイプラインで動いています。これは製品機能としてのカナリアリリースです。1.1GBで、24GBの全体版と比べて小さく、サインアップも不要です。自分のマシンで動くなら、フルマップも動くはずだと確信できます。大規模データのプロダクトを作るなら、これをそのまま真似してください。無料枠は、最も簡単な地形の劣化版デモではなく、最も難しい地形の代表的なスライスにすべきです。

手前の細かい小さなブロックから、霞んだ遠方の粗い巨大ブロックへと変わるボクセルの山々。
遠景の地形は同じピラミッドの粗いズームレベルにすぎません。LODは描画側ではなく取り込み時に決まります。

ブラウザでの空間タイルのストリーミング

同じ規律は、プロジェクトの控えめなWebページにも表れています。概要マップではスライダーをドラッグして、ボクセルワールドと実際の航空写真を比較できます。しかも画像はプロジェクトのサーバーからではありません。スライダーを動かしたときに初めて、ブラウザがswisstopo自身のサービスからSWISSIMAGEのタイルを直接取得します。遅延読み込みで、オリジンに直接アクセスし、キャッシュに優しい。大量の帯域は、まさにこの目的のために存在する公共インフラが担い、プロジェクト自身のホスティング費用はほぼゼロに抑えられています。

Webでのタイルのアドレッシングはすでに解決された問題で、地理空間の仕事に関わるなら頭に入れておく価値があります:

import math
def latlon_to_tile(lat: float, lon: float, zoom: int) -> tuple[int, int]:
"""Standard slippy-map (XYZ) tile addressing used by OSM and most WMTS fronts."""
n = 2 ** zoom
x = int((lon + 180.0) / 360.0 * n)
lat_rad = math.radians(lat)
y = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
return x, y
# At zoom 15, any point on Earth lands in one of 1,073,741,824 tiles.
# Fetch the few that intersect the viewport. Skip the other billion.

式そのものより重要な点が二つあります。まず、リクエストはユーザーの意図が明確になるまで遅らされます。つまりスライダーが実際に動いたときです。これは帯域の最適化であると同時に、公的資金で運営される無料の公共サービスへの配慮でもあります。次に、出典情報がタイルに付随しています。ページはマップのすぐそばでSWISSIMAGE、swissBOUNDARIES3D、swissNAMES3Dをクレジット表記しています。製品がオープンデータの二次的著作物であるなら、表示箇所での帰属表示は飾りではありません。ライセンスが意図どおりに機能している証なのです。

スイスのパイプラインから学ぶべきこと

まず反論から取り上げましょう。実務家からは良い指摘がありました。Build the Earthのような手作業のプロジェクトは、何千人ものボランティアを動員して実際の建物をモデリングしています。自動化パイプラインがそこまでの忠実度に届くことはありません。swissBUILDINGS3Dの輪郭をボクセル化したものは粗い近似にすぎず、建築に関心があるなら、地形だけのエクスポートのほうが純粋な成果物と言えるかもしれません。その通りです。ただ、カバー範囲と忠実度は直接トレードオフの関係にあります。自動化なら今日すぐに41,285km²を提供できますが、手作業では美しい旧市街が完成するのはいつになるかわかりません。私の立場としては、データプロダクトなら出典を公開した自動生成が勝ると考えます。手作りの目玉となるランドマークは、重要な場所にその上から重ねればよいのです。

そして、出典情報はみんなが省略しがちな部分です。このプロジェクトは既知の欠陥リストを公開しています。30mのCopernicusグリッドしか届かない国境付近の隙間、まだらや平坦すぎる箇所、間違っていたり欠けていたりする橋や道路、そしてメートル単位ではなくサンプリングで行われたQAです。このリストは実際に役立っています。「ここのワールドは間違っている」というバグ報告を、「はい、そのデータセットはここで終わっています」というドキュメントに変えてくれるのです。派生データのプロダクトは、どれも出典マトリクスと既知の誤りリストを公開すべきです。それを公開しないプロダクトは、より正確なわけではなく、単により不誠実なだけです。

  • すべてをタイルにする。 タイルは、ストレージ、キャッシュ、生成、障害処理の単位です。モノリスは穏やかに劣化してくれません。
  • 取り込み時にダウンサンプリングしない。 各ソースのネイティブ解像度を保持し、描画時にリサンプリングしてください。
  • 遅延生成する。 ワールドが誰のプレイ時間よりも大きくなったら、訪問ごとの計算が事前焼き付けに勝ります。
  • LODピラミッドは取り込み時に作る。 約33%のオーバーヘッドを見込み、あらゆる距離で滑らかなスケーリングを手に入れましょう。
  • カナリアスライスを出荷する。 無料で提供される、最も難しいデータの代表的なかけらは、どんなスペックシートよりも雄弁です。
  • 出典と既知の欠陥を公開する。 それらは製品の一部であり、言い訳ではありません。

国全体版は15CHFで、今後のすべてのバージョンを含みます。ただし法的な理由からスイス居住者だけに販売されています。純粋なデジタル製品としては不思議と愛嬌のある制約です。しかし本当のプロダクトは地図そのものではありません。公開データ、タイルベースのパイプライン、誠実なドキュメントがあれば、一人の人間でも多くのチームの地理空間インフラを上回る速さで出荷できることの実証です。あなたのデータは41,285km²ではないでしょう。言い訳も、もっと小さいはずです。