QBTiles
QBTiles (Quadkey Bitmask Tiles)는 존재하는 위치를 트리 구조로 암시하여 ID 저장 비용을 0으로 만드는 공간 데이터 포맷이다.
타일/셀의 존재 여부를 BFS 순서의 4-bit bitmask로 저장한다. 각 항목의 위치는 트리 구조에 의해 암시되므로 ID도, 좌표도 저장하지 않는다.
왜 QBTiles인가?
- 더 작지만 더 정밀한 접근: PMTiles보다 20–30% 작은 인덱스, COG의 512×512 블록 대비 셀 단위 Range Request 지원. 비교 보기 →
- 빈 공간은 건너뜀: bitmask 트리는 존재하는 셀만 저장 — nodata에 바이트를 낭비하지 않음
- 3가지 모드 지원: 타일 아카이브 (variable-entry), 래스터 그리드 (fixed row), 압축 그리드 (fixed columnar)
- 인덱스 재사용: SHA-256으로 동일한 공간 구조를 가진 시계열 파일 간 인덱스 재사용 가능
- 클라우드 네이티브: HTTP Range Request를 통한 서버리스 서빙 (S3, R2 등)
설치
pip install qbtiles # Python — build & write QBT files
npm install qbtiles # TypeScript — read & query in the browser
빠른 시작
Python — QBT 파일 생성
import qbtiles as qbt
# Mode 1: Tile archive — from a folder of z/x/y tiles (e.g., tiles/5/27/12.mvt)
qbt.build("korea_tiles.qbt", folder="tiles/")
# Mode 2: Columnar — coordinates + multiple value columns
# coords: list of (x, y) in the target CRS
# columns: dict of column_name → value list (same length as coords)
# cell_size: grid cell size in CRS units (meters for EPSG:5179)
# → zoom, origin, extent are auto-calculated from coords and cell_size
qbt.build("population.qbt.gz",
coords=list(zip(df["x"], df["y"])), # [(950000, 1950000), ...]
columns={"total": totals, "male": males, "female": females},
cell_size=100, crs=5179) # 100m grid, Korean CRS
# Mode 3: Fixed row — coordinates + single value array (for Range Request)
# values: flat list of numbers (one per cell)
# entry_size: bytes per cell (4 for float32)
qbt.build("global_pop.qbt",
coords=list(zip(lons, lats)), # [(-73.99, 40.75), ...]
values=population, # [52.3, 41.2, ...]
cell_size=1000, entry_size=4, # 1km grid, 4 bytes/cell
fields=[{"type": qbt.TYPE_FLOAT32, "name": "pop"}])
# GeoTIFF → QBTiles conversion (cell_size, CRS, extent auto-detected)
qbt.build("worldpop.qbt", geotiff="worldpop_2025.tif")
TypeScript — 읽기 및 쿼리
import { openQBT } from "qbtiles";
// openQBT reads the header, detects the mode, and loads data automatically.
// Mode 1: Tile archive — serve MVT/PNG tiles from a single .qbt file
const tiles = await openQBT("korea_tiles.qbt");
const tile = await tiles.getTile(7, 109, 49); // ArrayBuffer (gzip MVT) | null
tiles.addProtocol(maplibregl, "qbt"); // one-line MapLibre integration
// Mode 3: Fixed row — per-cell Range Request on a remote file
const grid = await openQBT("https://cdn.example.com/global_pop.qbt");
const cells = await grid.query([126, 35, 128, 37]); // [west, south, east, north]
// → Array<{ position: [lng, lat], value: number }>
// Mode 2: Columnar — downloads entire file, queries in memory
const pop = await openQBT("population.qbt.gz");
pop.columns!.get("total")!; // number[931495] — direct access
const result = await pop.query([126, 35, 128, 37]);
// → Array<{ position: [lng, lat], values: {total: 523, male: 261, female: 262} }>
3가지 모드
| Mode | Flags | 용도 | 비교 대상(유사 형식) |
|---|---|---|---|
| Variable-entry | 0x0 |
타일 아카이브 (MVT, PNG) | PMTiles |
| Fixed row | 0x1 |
래스터 그리드 | COG (GeoTIFF) |
| Fixed columnar | 0x3 |
압축 그리드 | Parquet |
프로젝트 구조
src/
python/qbtiles.py — Python library: build(), quadtree, QBT write/read
typescript/qbtiles.ts — TypeScript entry point: re-exports all modules
typescript/qbt.ts — Unified reader: QBT class, openQBT(), registerCRS()
typescript/qbt-header.ts — Header parser (parseQBTHeader)
typescript/qbt-reader.ts — Low-level reader (loadQBTVariable, loadQBTColumnar)
typescript/bitmask-index.ts — Lazy tree index, spatial query, Range Request
typescript/types.ts — Shared types (BBox, GridParams, coord utils)
cpp/ — Native Hilbert→quadkey encoder (pybind11, optional)
demo-src/ — Vite + React demo source (3 pages + landing)
docs/ — MkDocs documentation site source
examples/ — Sample data files (.qbt, .qbt.gz, .pmtiles)
dist/ — npm build output (ESM + CJS + .d.ts)
라이선스
MIT