For People Who Like Reading File Formats

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.

Step one

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.db

The 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:

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/{}
Step two

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 important bit

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.

Cross-referencing

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.

Finding roads

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)

Metadata

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.

Before you redistribute

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';
-- 25278

Boundary geometry itself is not in the packs. It is used only to decide which roads belong in which download.

Fair warning

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.

79.1M roads. One you at a time.

START
EXPLORING
TODAY

We're putting the finishing touches on the app. Be among the first to explore every road.

App coming soonView Pricing