It's just SQLite
Every region pack on the coverage page is a zipped SQLite database. No proprietary container, no bespoke binary wrapper, just open it with the sqlite3 CLI and start querying. The only part you cannot guess is how the geometry is packed, so that is written out below with a worked example.
Unzip it
Each archive holds exactly one file, that is, the sqlite database, named the same as the archive minus .zip. No nesting, no sidecar files. Deflate, single entry, no zip64.
$ unzip -l australia-other-territories.db.zip
Length Date Time Name
--------- ---------- ----- ----
458752 08-23-2026 17:40 australia-other-territories.dbThe manifest.json beside the packs lists every region with its filename, road count, download size, bounding box and licence records which is enough to script a bulk fetch without scraping this site. One per country:
- germanyhttps://data.exploreeveryroad.com/offline-packs/2026/germany/manifest.json
- australiahttps://data.exploreeveryroad.com/offline-packs/2026/australia/manifest.json
- indiahttps://data.exploreeveryroad.com/offline-packs/2026/india/manifest.json
- usahttps://data.exploreeveryroad.com/offline-packs/2026/usa/manifest.json
Pack URLs are that same path with manifest.json swapped for a region's file value, so the manifest is all you need to fetch every pack for a country.
# for example, Germany, straight from the manifest
BASE=https://data.exploreeveryroad.com/offline-packs/2026/germany
curl -s $BASE/manifest.json \
| python3 -c "import json,sys;[print(r['file']) for r in json.load(sys.stdin)['regions']]" \
| xargs -I{} curl -O $BASE/{}Four tables
CREATE TABLE local_roads (
road_id INTEGER PRIMARY KEY, -- the OSM way id
ref TEXT, -- e.g. "A1", "NH 66"
highway TEXT, -- OSM highway tag
name TEXT,
surface TEXT,
length_m REAL, -- haversine metres along the polyline
geom BLOB NOT NULL -- see "the geometry blob" below
);
CREATE TABLE local_roads_grid ( -- spatial index; see "finding roads"
cell_x INTEGER NOT NULL,
cell_y INTEGER NOT NULL,
road_id INTEGER NOT NULL,
PRIMARY KEY (cell_x, cell_y, road_id)
) WITHOUT ROWID;
CREATE TABLE pack_meta (key TEXT PRIMARY KEY, value TEXT);
CREATE TABLE pack_license (
component TEXT NOT NULL, -- 'road_data' | 'boundaries'
source TEXT NOT NULL,
license TEXT NOT NULL,
url TEXT,
notice TEXT NOT NULL,
license_text TEXT -- full ODbL text, ~25k chars
);
CREATE INDEX local_roads_highway_idx ON local_roads (highway);
CREATE INDEX local_roads_ref_idx ON local_roads (ref) WHERE ref IS NOT NULL;The geometry blob
geom is not WKB or GeoJSON. It is a flat array of little-endian (here's a name I haven't heard since my time at University) int32 pairs - longitude then latitude, each multiplied by 1e7 and rounded half to even. Eight bytes per point, no header, no terminator. Point count is simply len(blob) / 8.
It is stored this way so a phone can decode straight into a typed array with no parsing. pack_meta.geom_encoding records the format as int32le_lng_lat_e7, so check it rather than assuming. If the format ever changes, that key changes with it.
import sqlite3, struct
db = sqlite3.connect("australia-other-territories.db")
rid, name, blob = db.execute(
"SELECT road_id, name, geom FROM local_roads LIMIT 1"
).fetchone()
flat = struct.unpack("<%di" % (len(blob) // 4), blob)
points = [(flat[i] / 1e7, flat[i + 1] / 1e7) for i in range(0, len(flat), 2)]
# road_id 20900897 "Boorala Road"
# 16 bytes -> 2 points
# [(150.6942791, -35.1629805), (150.694593, -35.163061)]That is a real row from Australia's Other Territories pack, not a made-up example.
road_id is the OSM way id
This is the useful part. The primary key is not a random id, it is the OpenStreetMap way id, so every row joins straight back to OSM which you can use to do whatever OSM stuff you want to do. Road 20900897 above is openstreetmap.org/way/20900897. Confirm it with pack_meta.id_scheme, which reads osm_way_id.
Older packs used a sequential id and are not comparable. Always read the key rather than assuming.
The grid index
local_roads_grid maps a square cell to every road passing through it. Cell size is in pack_meta.cell_deg — currently 0.002 degrees, roughly 220 m, and the cell for a point is just a floor divide:
cell_x = floor(lng / cell_deg)
cell_y = floor(lat / cell_deg)
-- every road near a point, the way the app does it:
SELECT DISTINCT r.road_id, r.name, r.highway
FROM local_roads_grid g
JOIN local_roads r USING (road_id)
WHERE g.cell_x BETWEEN :cx - 1 AND :cx + 1
AND g.cell_y BETWEEN :cy - 1 AND :cy + 1;Roads are registered in every cell they cross, not only the ones their vertices land in and segments are sampled at half-cell steps when the pack is built. That is what makes the 3×3 probe complete: a long straight road spanning several cells still appears in each of them. (More about this in the Blog, coming soon)
What pack_meta tells you
There are Fourteen keys worth reading before you parse anything:
country "Australia"
state "Other Territories"
road_count "2141"
id_scheme "osm_way_id" -- check before joining to OSM
geom_encoding "int32le_lng_lat_e7" -- check before decoding geom
coord_scale "1e7"
cell_deg "0.002"
srid "4326" -- WGS84
min_lng min_lat max_lng max_lat -- coverage bbox
built_at "2026-08-23T17:40:11Z"
attribution "(c) OpenStreetMap contributors, ..."One quirk worth knowing: As per the GEOJSON used, Australia's “Other Territories” pack has a bounding box spanning about 71 degrees of longitude. That is correct, not corruption and it is the bucket holding Christmas Island, Cocos (Keeling), Norfolk Island and Jervis Bay, which are genuinely that far apart.
The licence travels with the file
Every pack carries its own licensing in pack_license, including the complete ODbL text which is about 25,000 characters as a pack is an offline artifact and a link is no use to a device with no signal. Read it from the file rather than trusting this page.
The road data is ODbL 1.0, which is share-alike: if you publish a derived database, it has to carry the same licence, and you must credit OpenStreetMap contributors. Region boundaries carry their own separate terms per country and several require attribution and are listed on the coverage page.
SELECT component, source, license, url FROM pack_license;
SELECT length(license_text) FROM pack_license WHERE component='road_data';
-- 25278Boundary geometry itself is not in the packs. It is used only to decide which roads belong in which download.
Nothing here is a stable API
These files exist to make the app work offline. They are public because the data is open, not because there is a contract behind them. Schema, filenames and the URL prefix can all change when packs are rebuilt. If you build something on top, read manifest.json and the pack_meta keys at runtime rather than hardcoding what you see today coz that is exactly why those keys exist.
Built something interesting? Tell me about it.