Skip to main content

Quadkey Bitmask Tiles — cloud-optimized tile index format

Project description

QBTiles

QBTiles (Quadkey Bitmask Tiles) — a spatial data format that encodes existence as a tree structure, reducing ID storage cost to zero.

Documentation | Live Demos | 한국어 문서

Install

# Python — build & write QBT files
pip install qbtiles

# TypeScript/JavaScript — read & query QBT files in the browser
npm install qbtiles

What It Does

Map tiles and spatial grids are inherently quadtrees. QBTiles encodes cell existence as 4-bit bitmasks in BFS order. The position of each entry is implied by the tree structure — no IDs, no coordinates stored.

Level 1:  [0100]          → only child 1 exists
Level 2:  [0001]          → only child 3 exists
Level 3:  [0010]          → only child 2 exists

quadkey bitmask structure

Three Modes

Mode Flags Use Case Access Comparable to
Variable-entry 0x0 Tile archives (MVT, PNG) Per tile PMTiles
Fixed row 0x1 Raster grids Per cell (Range Request) COG (GeoTIFF)
Fixed columnar 0x3 Compressed grids Whole file (gzip) Parquet

Benchmarks

Variable-entry — Tile Index (vs PMTiles)

Dataset Entries PMTiles QBTiles Reduction
adm-korea 36K 80.9 KB 61.3 KB -24.3%
Full OSM 160M 300.7 MB 235.2 MB -21.8%

Fixed row — Raster Grid (WorldPop 51M cells, float32)

Format Size Ratio Per-cell access
FlatGeobuf 6,001 MB 29.4x per feature
GeoParquet 700 MB 3.4x full download only
GeoTIFF (COG) 276 MB 1.4x 512×512 block
QBTiles 204 MB 1.0x single cell

Fixed columnar — Compressed Grid (Korea 100m, 931K cells × 3 values)

Format Size Per cell
Parquet (gzip) 2.9 MB coordinate scan
QBTiles columnar 1.7 MB O(log N) quadkey search

Quick Start

Python — Build QBT Files

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")

# Bitmask-only — store only cell existence (no values), e.g. land/water mask
qbt.build("landmask.qbt", geotiff="landmask.tif", nodata=0, bitmask_only=True)

TypeScript — Read & Query

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} }>

File Format (v1)

[Header 128B+]  magic, version, flags, zoom, CRS, origin, extent,
                bitmask_length, values_offset, index_hash (SHA-256), field schema
[Bitmask]       gzip-compressed 4-bit nibbles in BFS order
[Values]        row: raw entry_size × leaf_count (Range-requestable)
                columnar: column-by-column (varint + fixed types)

Full spec: format-spec.md

API Reference

Python (pip install qbtiles) — Writer

Function Description
build(output, ...) Unified builder — auto-detects mode from folder / columns / values / geotiff. Options: nodata=, bitmask_only=True
read_qbt_header(path_or_bytes) Parse QBT header to dict
tile_to_quadkey_int64(z, x, y) Tile coords → 64-bit quadkey

Low-level: build_quadtree(), serialize_bitmask(), write_qbt_variable(), write_qbt_fixed(), write_qbt_columnar()

TypeScript/JavaScript (npm install qbtiles) — Reader

Function / Class Description
openQBT(url)QBT Unified loader — auto-detects mode from header flags
QBT.getTile(z, x, y) Fetch tile data via Range Request (variable mode)
QBT.query(bbox) Spatial query — all modes (Range Request or in-memory)
QBT.columns Column values as Map<string, number[]> (columnar mode)
QBT.addProtocol(maplibregl) One-line MapLibre custom protocol (variable mode)
QBT.toWGS84(x, y) CRS conversion via proj4 (built-in for common EPSG codes)
registerCRS(epsg, proj4Def) Register custom CRS definitions

Low-level: parseQBTHeader(), queryBbox(), mergeRanges(), fetchRanges(), readColumnarValues()

Live Demos

Tile Viewer — Variable-entry (0x0)

MVT vector tiles served via QBTiles index + Range Request. Administrative boundaries of South Korea.

Tile Viewer

Population Grid — Fixed columnar (0x3)

931K cells in 1.75 MB. Korea 100m population grid with 3 values per cell at 1.97 Byte/cell.

Population Grid

Range Request Comparison — Fixed row (0x1)

Split-screen comparison: QBTiles cell-level vs COG block-level Range Request on WorldPop 1km global population.

Range Request Comparison

File Viewer — All modes

Drag & drop any .qbt or .qbt.gz file to inspect its contents. Supports all three modes with auto-detection.

File Viewer

Related Work

  • Sparse Voxel Octree (SVO) — Same bitmask principle in 3D (8-bit masks for octree)
  • LOUDS — Succinct tree encoding via BFS bit sequences
  • PMTiles — Hilbert curve tile indexing with varint delta encoding

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qbtiles-0.7.0.tar.gz (27.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qbtiles-0.7.0-py3-none-any.whl (20.5 kB view details)

Uploaded Python 3

File details

Details for the file qbtiles-0.7.0.tar.gz.

File metadata

  • Download URL: qbtiles-0.7.0.tar.gz
  • Upload date:
  • Size: 27.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for qbtiles-0.7.0.tar.gz
Algorithm Hash digest
SHA256 50050b3388f0f09694610fe90c2e785ebbf8d90ed89271231f54e786903d5d57
MD5 192346f55130b6432a40c1fba436401f
BLAKE2b-256 d54edfe8ba18b05ca4fd0b48b95d7c09f65556e7772418ef0ca3ff6770903d79

See more details on using hashes here.

File details

Details for the file qbtiles-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: qbtiles-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 20.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for qbtiles-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 733c69daf1be96af677abcb75d117a920f7f3a8ab63716c988e464aa7230eb80
MD5 123647f6c7c26bc7e1a1b4aca5ad9e9f
BLAKE2b-256 1f28f62fafe61d3b6976ee65825888a0a11d9ef1fca828ca269b680b04e551fa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page