/ Technical Overview

altpbf — Elevation Tile Format

Protocol Buffers-based raster elevation tiles.
3-tier zoom LOD · GEBCO + ALOS dual sources · Int16 delta + deflateRaw · Worker zero-copy

Table of Contents
  1. What is altpbf?
  2. Data Sources
  3. 3-Tier Tile Hierarchy
  4. Binary Format
  5. Tile Naming
  6. Worker & Cache Architecture
  7. GEBCO Processing Pipeline
  8. API Reference

1. What is altpbf?

altpbf is a binary raster elevation tile format built on Protocol Buffers. Where GeoPBF handles vector geometry (coastlines, roads, polygons), altpbf handles raster elevation grids — the digital elevation model (DEM) and bathymetry data needed for terrain rendering, shadow calculation, and 3D visualization.

Each tile is a rectangular grid of Int16 elevation values (meters), delta-encoded and compressed with deflateRaw. Three tile sizes cover the globe at different resolutions, and the appropriate tier is selected automatically based on zoom level.

Why Int16?

Earth's elevation range is roughly −11,000 m (Mariana Trench) to +8,849 m (Everest). A signed 16-bit integer covers −32,768 to +32,767 m — exactly enough, with no wasted bits. Compared to Float32, Int16 halves the data size before any compression is applied.

2. Data Sources

SourceCoverageResolutionUsed for
GEBCO
General Bathymetric Chart of the Oceans
Global (land + sea) ~450 m (15 arc-sec) HGT90 · HGT10
ALOS AW3D30
JAXA ALOS World 3D
Land only ~30 m (1 arc-sec) HGT01

GEBCO provides global coverage including ocean bathymetry, making it the baseline for all zoom levels. ALOS AW3D30 is a high-precision land-only DEM published by JAXA, used for close-up terrain rendering where its 30 m resolution delivers visually meaningful detail. Since ALOS tiles only exist for land areas, altpbf maintains an index of available tiles and falls back gracefully to GEBCO HGT10 for ocean queries.

3. 3-Tier Tile Hierarchy

altpbf defines three tile sizes, each covering a different angular extent of the globe. createGetHeight switches between tiers based on the current zoom level, defaulting to level1 = 7 and level2 = 12.

HGT90
90° tile
Global overview — GEBCO
Only 8 tiles cover the globe. Downsampled 8× from the 21600-px source (average pooling). Fastest to load.
zoom < 7
HGT10
10° tile
Regional — GEBCO
324 tiles cover the globe. Latitude-adjusted width (polar shrink). Sub-100 km features become visible.
7 ≤ zoom < 12
HGT01
1° tile
Local detail — ALOS AW3D30
~64,800 land tiles, 30 m resolution. Only exists for land areas; falls back to HGT10 otherwise.
zoom ≥ 12

Each tier is a strict fallback chain: HGT01 → HGT10 → HGT90. If a high-resolution tile is unavailable (ocean, no ALOS data) or returns no elevation, the next coarser tier answers the query transparently. The caller always receives a valid elevation value.

Bilinear Interpolation

Within each tile, calcHeight(x, y, v) performs bilinear interpolation across the four nearest grid cells — no staircase artifacts. Normalized coordinates (0–1) within the tile are computed from the raw lat/lng and passed directly to the interpolation function.

4. Binary Format

Each .altpbf tile is a deflateRaw-compressed Protocol Buffers message.

Header
tile metadata
NAME (1) · SOURCE (2) · WIDTH (3) · HEIGHT (4)
LNG (5) · LAT (6) · RANGE (7)
All coordinate fields use signed Varint (SVarint)
DATA (10)
elevation grid
Packed SVarint — delta-encoded Int16 elevation values
Row-major order, top-to-bottom · width × height values total
deflateRaw
whole message
Raw DEFLATE (no gzip header) via deflateRaw / inflateRaw
Decompressed in-place before PBF parsing

4.1 Delta Encoding for Elevation

Terrain elevation changes gradually — neighboring cells in a DEM rarely differ by more than a few tens of metres. Delta encoding exploits this locality: storing differences rather than absolute values keeps numbers small, and small numbers encode to fewer Varint bytes.

Raw Int16 (m)
1240
1255
1268
1241
1195
Delta
1240
+15
+13
−27
−46
SVarint bytes
2 bytes
1 byte
1 byte
1 byte
1 byte

After delta encoding, most values in a smooth terrain tile fit in a single Varint byte. Combined with deflateRaw on top, altpbf tiles are typically 5–15× smaller than an equivalent raw Int16 array.

4.2 Elevation Color Map

altpbf2png renders a tile as a PNG image using altitude/depth step colors:

Deep
< −2000 m
Ocean
< −200 m
Shelf
0– (sea)
Lowland
0–200 m
Hill
200–500 m
Highland
1000–2000 m
Alpine
2000–4000 m
Snow
> 6000 m

5. Tile Naming

Each tile is uniquely identified by a compact name string encoding its range, latitude, and longitude:

encodeName(lng, lat, range)
// range > 0  →  "R{02d(range)}{lat_prefix}{03d(|lat|)}{lng_prefix}{03d(|lng|)}"
// range = 0  →  "{lat_prefix}{03d(|lat|)}{lng_prefix}{03d(|lng|)}"  (ALOS 1° tiles)

// Examples:
encodeName(0, -90, 90)  // → "R90S090E000"  (HGT90 south-west quadrant)
encodeName(130, 30, 10)  // → "R10N030E130"  (HGT10 Japan region)
encodeName(135, 35, 0)   // →    "N035E135"  (HGT01 ALOS, Kinki region)
ALOS Tile Existence Check

ALOS AW3D30 tiles exist only over land. Before requesting an HGT01 tile, altpbf checks an in-memory index loaded from the JAXA tile list (index_alos()). Ocean queries immediately fall back to HGT10 without a network round-trip.

6. Worker & Cache Architecture

Request
getHeight(lng, lat, zoom)
select tier by zoom
L1 Cache
IndexedDB
"GIS/alt" namespace
decoded objects
→ miss →
Worker
worker.js
load + decode
Int16Array transfer
L2 Storage
nativeBucket
server-side bucket
"GIS/alt"
// worker.js — zero-copy Int16Array transfer to main thread
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: zero-copy
		: postMessage(null);
};

The worker inflates and decodes the tile off-main-thread, then transfers the Int16Array buffer back via the Transferable interface — no copying, no serialization. A single worker handles requests one at a time; if a load is already in flight, subsequent callers wait for the same Promise. Once decoded, tiles are persisted to IndexedDB for instant re-use across sessions.

7. GEBCO Processing Pipeline

The raw GEBCO source is a global GeoTIFF at 21,600 × 21,600 pixels per 90° quadrant (total 43,200 × 21,600 for the globe). The GEBCO() function downloads and tiles it into altpbf format:

// Step 1 — HGT90: 8× average-pool downsampling
// 21600 × 21600 → 2700 × 2700  (one 90° tile per quadrant)
// Each output pixel = mean of 8×8 source pixels

// Step 2 — HGT10: extract 9×9 = 81 sub-tiles per quadrant
// Apply polar width shrink so tiles remain square on-screen
// shrink(lat): fraction of full-width actually used at that latitude

7.1 Polar Width Correction

Meridians converge toward the poles, so a 10° longitude span represents far fewer kilometres at high latitudes than at the equator. GEBCO HGT10 tile generation compensates by reducing the pixel width of polar tiles:

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

Tile pixel width relative to equatorial baseline. Polar tiles are narrower, matching the reduced real-world extent per degree of longitude.

8. API Reference

// ── createGetHeight — main entry point ─────────────────
const getHeight = await createGetHeight({
	apiUrl:  'https://your-api.example.com',  // nativeBucket base URL
	level1:  7,    // zoom threshold HGT90 → HGT10
	level2:  12,   // zoom threshold HGT10 → HGT01
	onstart: name => console.log(`loading ${name}...`),
	onend:   name => console.log(`done ${name}`),
});

// Query elevation at any (lng, lat, zoom)
const h = await getHeight(139.77, 35.68, 14);  // → height in metres

// Zoom omitted → always use highest resolution (HGT01)
const h = await getHeight(139.77, 35.68);

// ── altpbf2png — render tile as PNG ────────────────────
const blob = await altpbf2png(pbfBlob, {
	size:     256,         // output PNG size (square)
	colorMap: h => [r, g, b] // custom color function (optional)
});

// ── Low-level 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 bulk tile generation ──────────────────────────
await GEBCO({ year: 2026, noIce: false, log: console });
// Downloads ~12 GB source, generates HGT90 + HGT10 altpbf tiles, uploads to bucket

// ── ALOS tile index ─────────────────────────────────────
const index = await index_alos();
// → { "N035E135": "v2404", "N036E135": "v2404", ... }

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