Building a QBTiles Archive from XYZ Tile Folders¶
This example demonstrates the full workflow:
- Unzip a z/x/y tile folder
- Build a single
.qbtfile withqbt.build() - Read back and verify a tile
Sample tiles¶
Download the sample tile set (zoom 0-5, 1365 PNG tiles, 11MB):
curl -LO https://raw.githubusercontent.com/vuski/qbtiles/main/docs/examples/tiles.zip
1. Unzip sample tiles¶
In [1]:
Copied!
import zipfile, os, sys
tiles_dir = "./tiles"
if not os.path.exists(tiles_dir):
with zipfile.ZipFile("tiles.zip", "r") as zf:
zf.extractall(tiles_dir)
# Quick check
n = sum(1 for r, d, fs in os.walk(tiles_dir) for f in fs if f.endswith(".png"))
print(f"{n} tiles extracted")
import zipfile, os, sys
tiles_dir = "./tiles"
if not os.path.exists(tiles_dir):
with zipfile.ZipFile("tiles.zip", "r") as zf:
zf.extractall(tiles_dir)
# Quick check
n = sum(1 for r, d, fs in os.walk(tiles_dir) for f in fs if f.endswith(".png"))
print(f"{n} tiles extracted")
1365 tiles extracted
2. Build the archive¶
qbt.build() scans the folder, builds the index, and packs everything into a single .qbt file.
In [2]:
Copied!
sys.path.insert(0, os.path.join("..", "..", "src", "python"))
import qbtiles as qbt
qbt_path = "sample.qbt"
qbt.build(qbt_path, folder=tiles_dir)
idx_size = os.path.getsize(qbt_path)
print(f"File: {idx_size:,} bytes ({idx_size/1024/1024:.1f} MB)")
h = qbt.read_qbt_header(qbt_path)
print(f"Tiles: {h['values_length']:,} bytes of tile data")
print(f"Index: {h['bitmask_length']:,} bytes")
print(f"Zoom: {h['zoom']}")
sys.path.insert(0, os.path.join("..", "..", "src", "python"))
import qbtiles as qbt
qbt_path = "sample.qbt"
qbt.build(qbt_path, folder=tiles_dir)
idx_size = os.path.getsize(qbt_path)
print(f"File: {idx_size:,} bytes ({idx_size/1024/1024:.1f} MB)")
h = qbt.read_qbt_header(qbt_path)
print(f"Tiles: {h['values_length']:,} bytes of tile data")
print(f"Index: {h['bitmask_length']:,} bytes")
print(f"Zoom: {h['zoom']}")
File: 11,005,322 bytes (10.5 MB) Tiles: 11,002,844 bytes of tile data Index: 2,350 bytes Zoom: 6
3. Retrieve a tile¶
Read back a tile from the single .qbt file to verify it matches the original.
In [3]:
Copied!
import gzip
# Parse header
h = qbt.read_qbt_header(qbt_path)
print(f"Format: QBT v{h['version']}, zoom={h['zoom']}")
# Decompress index section and deserialize
with open(qbt_path, "rb") as f:
raw = f.read()
compressed = raw[h["header_size"]:h["header_size"] + h["bitmask_length"]]
index_bytes = gzip.decompress(compressed)
entries = qbt.deserialize_quadtree_index(index_bytes)
index = {e["quadkey_int"]: e for e in entries}
print(f"Loaded {len(index)} entries")
# Read tile z=3/x=4/y=2 via byte offset
qk = qbt.tile_to_quadkey_int64(3, 4, 2)
entry = index[qk]
tile_data = raw[h["values_offset"] + entry["offset"]:h["values_offset"] + entry["offset"] + entry["length"]]
print(f"Tile z=3 x=4 y=2: {len(tile_data)} bytes")
# Verify against original
with open(os.path.join(tiles_dir, "3/4/2.png"), "rb") as f:
assert tile_data == f.read(), "Mismatch!"
print("Matches original file.")
import gzip
# Parse header
h = qbt.read_qbt_header(qbt_path)
print(f"Format: QBT v{h['version']}, zoom={h['zoom']}")
# Decompress index section and deserialize
with open(qbt_path, "rb") as f:
raw = f.read()
compressed = raw[h["header_size"]:h["header_size"] + h["bitmask_length"]]
index_bytes = gzip.decompress(compressed)
entries = qbt.deserialize_quadtree_index(index_bytes)
index = {e["quadkey_int"]: e for e in entries}
print(f"Loaded {len(index)} entries")
# Read tile z=3/x=4/y=2 via byte offset
qk = qbt.tile_to_quadkey_int64(3, 4, 2)
entry = index[qk]
tile_data = raw[h["values_offset"] + entry["offset"]:h["values_offset"] + entry["offset"] + entry["length"]]
print(f"Tile z=3 x=4 y=2: {len(tile_data)} bytes")
# Verify against original
with open(os.path.join(tiles_dir, "3/4/2.png"), "rb") as f:
assert tile_data == f.read(), "Mismatch!"
print("Matches original file.")
Format: QBT v1, zoom=6 Loaded 1365 entries Tile z=3 x=4 y=2: 38897 bytes Matches original file.
In [4]:
Copied!
from IPython.display import display, Image
display(Image(data=tile_data))
from IPython.display import display, Image
display(Image(data=tile_data))
In [5]:
Copied!
# Cleanup extracted tiles (keep .qbt for inspection)
import shutil
if os.path.exists(tiles_dir): shutil.rmtree(tiles_dir)
print(f"Output: {qbt_path} ({os.path.getsize(qbt_path):,} bytes)")
# Cleanup extracted tiles (keep .qbt for inspection)
import shutil
if os.path.exists(tiles_dir): shutil.rmtree(tiles_dir)
print(f"Output: {qbt_path} ({os.path.getsize(qbt_path):,} bytes)")
Output: sample.qbt (11,005,322 bytes)