/ Technical Overview

altpbf — 標高タイルフォーマット

Protocol Buffers ベースのラスター標高タイル。
3段階ズーム LOD · GEBCO + ALOS 2ソース · Int16 デルタ + deflateRaw · Worker ゼロコピー

Table of Contents
  1. altpbf とは
  2. データソース
  3. 3段階タイル階層
  4. バイナリフォーマット
  5. タイル名エンコーディング
  6. Worker と Cache アーキテクチャ
  7. GEBCO 処理パイプライン
  8. API リファレンス

1. altpbf とは

altpbf は Protocol Buffers ベースのバイナリ標高タイルフォーマットである。 GeoPBF がベクタージオメトリ(海岸線・道路・ポリゴン)を扱うのに対し、 altpbf はラスター標高グリッド、すなわち 地形レンダリング・影計算・3D 可視化に必要な数値標高モデル(DEM)と 海底地形データを扱う。

各タイルは Int16 標高値(メートル)の矩形グリッドを デルタエンコードし deflateRaw で圧縮したものである。 3 種類のタイルサイズが異なる解像度で地球全体をカバーし、 ズームレベルに応じて適切なティアが自動選択される。

なぜ Int16 か

地球の標高範囲はおよそ −11,000 m(マリアナ海溝)から +8,849 m(エベレスト)。 符号付き 16 bit 整数は −32,768 〜 +32,767 m をカバーできる — 必要十分でビット無駄なし。 Float32 と比較してデータサイズが圧縮前から半分になる。

2. データソース

ソースカバレッジ解像度用途
GEBCO
General Bathymetric Chart of the Oceans
全球(陸・海底) 約 450 m (15 arc-sec) HGT90 · HGT10
ALOS AW3D30
JAXA ALOS 全球 3D 地図
陸地のみ 約 30 m (1 arc-sec) HGT01

GEBCO は海底地形を含む全球データであり、すべてのズームレベルのベースラインとして機能する。 ALOS AW3D30 は JAXA が公開する高精度陸地 DEM であり、 30 m 解像度により近距離の地形レンダリングで視覚的に意味のある詳細を提供する。 ALOS タイルは陸地のみに存在するため、altpbf は利用可能タイルのインデックスを保持し、 海洋クエリに対しては GEBCO HGT10 へシームレスにフォールバックする。

3. 3段階タイル階層

altpbf は 3 種類のタイルサイズを定義し、それぞれが異なる角度範囲の地球をカバーする。 createGetHeight は現在のズームレベルに基づいてティアを切り替え、 デフォルトは level1 = 7level2 = 12 である。

HGT90
90° タイル
全球概観 — GEBCO
8 タイルで全球をカバー。21600 px ソースを 8× 平均プーリングでダウンサンプル。最速ロード。
zoom < 7
HGT10
10° タイル
広域 — GEBCO
324 タイルで全球。緯度調整幅(極収縮)適用。100 km スケールの地形が可視化できる。
7 ≤ zoom < 12
HGT01
1° タイル
局所詳細 — ALOS AW3D30
約 64,800 陸地タイル、30 m 解像度。陸地のみ存在。それ以外は HGT10 へフォールバック。
zoom ≥ 12

各ティアは厳格なフォールバックチェーンを形成する:HGT01 → HGT10 → HGT90。 高解像度タイルが存在しない(海洋、ALOS データなし)か標高値が得られない場合、 次のより粗いティアが透過的にクエリに答える。 呼び出し側は常に有効な標高値を受け取る。

バイリニア補間

タイル内では calcHeight(x, y, v) が最近傍 4 グリッドセルにわたる バイリニア補間を実行するため、階段状のアーティファクトが生じない。 タイル内の正規化座標(0〜1)は生の lat/lng から計算され、 そのまま補間関数に渡される。

4. バイナリフォーマット

各 .altpbf タイルは deflateRaw 圧縮された Protocol Buffers メッセージである。

Header
タイルメタデータ
NAME (1) · SOURCE (2) · WIDTH (3) · HEIGHT (4)
LNG (5) · LAT (6) · RANGE (7)
座標フィールドはすべて符号付き Varint(SVarint)
DATA (10)
標高グリッド
Packed SVarint — デルタエンコードされた Int16 標高値列
行優先順、上から下 · width × height 個
deflateRaw
メッセージ全体
Raw DEFLATE(gzip ヘッダなし):deflateRaw / inflateRaw
PBF パース前にその場で伸張

4.1 標高のデルタエンコーディング

地形の標高はなだらかに変化する — DEM の隣接セルが数十メートル以上異なることはまれである。 デルタエンコーディングはこの局所性を利用し、絶対値ではなく差分を格納することで 値を小さく保ち、Varint バイト数を削減する。

生 Int16 (m)
1240
1255
1268
1241
1195
デルタ
1240
+15
+13
−27
−46
SVarint バイト数
2 bytes
1 byte
1 byte
1 byte
1 byte

なだらかな地形タイルでは、デルタエンコード後のほとんどの値が 1 Varint バイトに収まる。 その上に deflateRaw を重ねることで、altpbf タイルは生 Int16 配列と比べて 典型的に 5〜15 倍 小さくなる。

4.2 標高カラーマップ

altpbf2png は標高・水深ステップに応じたカラーで PNG としてタイルをレンダリングする:

深海
< −2000 m
海洋
< −200 m
大陸棚
0 以下(海)
低地
0–200 m
丘陵
200–500 m
高地
1000–2000 m
高山
2000–4000 m
雪線
> 6000 m

5. タイル名エンコーディング

各タイルは範囲・緯度・経度をコンパクトにエンコードした名前文字列で一意に識別される:

encodeName(lng, lat, range)
// range > 0  →  "R{02d(range)}{緯度符号}{03d(|lat|)}{経度符号}{03d(|lng|)}"
// range = 0  →  "{緯度符号}{03d(|lat|)}{経度符号}{03d(|lng|)}"  (ALOS 1° タイル)

// 例:
encodeName(0, -90, 90)  // → "R90S090E000"  (HGT90 南西象限)
encodeName(130, 30, 10)  // → "R10N030E130"  (HGT10 日本周辺)
encodeName(135, 35, 0)   // →    "N035E135"  (HGT01 ALOS、近畿地方)
ALOS タイル存在確認

ALOS AW3D30 タイルは陸地にしか存在しない。 HGT01 タイルをリクエストする前に、altpbf は JAXA タイルリストから 読み込んだインメモリインデックス(index_alos())を確認する。 海洋クエリはネットワークラウンドトリップなしで即座に HGT10 へフォールバックする。

6. Worker と Cache アーキテクチャ

リクエスト
getHeight(lng, lat, zoom)
ズームでティア選択
L1 キャッシュ
IndexedDB
"GIS/alt" 名前空間
デコード済みオブジェクト
→ ミス →
Worker
worker.js
ロード + デコード
Int16Array 転送
L2 ストレージ
nativeBucket
サーバーバケット
"GIS/alt"
// worker.js — Int16Array をゼロコピーでメインスレッドへ転送
import { load } from "./altpbf.js";
onmessage = async e => {
	const { name, apiUrl } = e.data;
	if (apiUrl) setApiUrl(apiUrl);
	const obj = await load(name);
	obj ? postMessage(obj, [obj.data.buffer])  // ← Transferable: ゼロコピー
		: postMessage(null);
};

Worker はタイルの伸張・デコードをメインスレッド外で行い、 Int16Array バッファを Transferable インターフェースでゼロコピー返却する。 1 つの Worker がリクエストを直列処理するため、ロード中に来た追加クエリは同じ Promise を待つ。 デコード後はセッションをまたいで即時再利用できるよう IndexedDB に永続化される。

7. GEBCO 処理パイプライン

GEBCO の生ソースは 90° 象限ごとに 21,600 × 21,600 ピクセルのグローバル GeoTIFF (全球で 43,200 × 21,600)。 GEBCO() 関数がこれをダウンロードして altpbf 形式にタイル化する:

// Step 1 — HGT90: 8× 平均プーリングダウンサンプル
// 21600 × 21600 → 2700 × 2700  (象限ごとに 1 つの 90° タイル)
// 出力 1 ピクセル = 8×8 ソースピクセルの平均

// Step 2 — HGT10: 象限ごとに 9×9 = 81 サブタイルを切り出し
// 極収縮(polar shrink)を適用してタイルが画面上で正方形に見えるよう調整

7.1 極収縮補正(Polar Width Correction)

経線は極に向かうにつれ収束するため、高緯度では 10° の経度幅が表す実距離が赤道より大幅に短い。 GEBCO HGT10 タイル生成では、極域のタイルのピクセル幅を縮小することでこれを補正する:

±80°
17%
±70°
33%
±60°
50%
±50°
67%
±40°
100%
赤道
100%

赤道基準に対するタイルピクセル幅の比。極域タイルは細く、 1 経度あたりの実距離縮小と対応している。

8. API リファレンス

// ── createGetHeight — メインエントリーポイント ─────────
const getHeight = await createGetHeight({
	apiUrl:  'https://your-api.example.com',  // nativeBucket ベース URL
	level1:  7,    // HGT90 → HGT10 切替ズーム閾値
	level2:  12,   // HGT10 → HGT01 切替ズーム閾値
	onstart: name => console.log(`loading ${name}...`),
	onend:   name => console.log(`done ${name}`),
});

// 任意の (lng, lat, zoom) で標高クエリ
const h = await getHeight(139.77, 35.68, 14);  // → 標高(メートル)

// zoom 省略 → 常に最高解像度(HGT01)を使用
const h = await getHeight(139.77, 35.68);

// ── altpbf2png — タイルを PNG としてレンダリング ────────
const blob = await altpbf2png(pbfBlob, {
	size:     256,          // 出力 PNG サイズ(正方形)
	colorMap: h => [r, g, b]  // カスタムカラー関数(省略可)
});

// ── 低レベル encode / decode ─────────────────────────────
const buf = await encode({ name, source, lng, lat, range, width, height, data });
const obj = await decode(blob);  // → { name, source, lng, lat, range, width, height, data: Int16Array }

// ── GEBCO 一括タイル生成 ─────────────────────────────────
await GEBCO({ year: 2026, noIce: false, log: console });
// 約 12 GB ソースをダウンロード、HGT90 + HGT10 altpbf タイルを生成してバケットへアップロード

// ── ALOS タイルインデックス ───────────────────────────────
const index = await index_alos();
// → { "N035E135": "v2404", "N036E135": "v2404", ... }

altpbf v1.0.0 · Kenji Yoshida · MIT License · 2026