Data Formats
This page documents every file format produced or consumed by the map_data package.
.mapdata file format
A .mapdata file is a JSON text file written by MapData.save() via json.dump. It is not a pickle file and it is not binary. Legacy files created before the JSON migration were Python pickle — support for these has been removed for security reasons. Legacy files must be re-parsed from their source GPX/YAML file to be converted to the JSON format.
Read a .mapdata file with:
Never open .mapdata files with pickle.load directly — use MapData.load().
Top-level JSON structure
{
"format_version": 1,
"metadata": { ... },
"waypoints": [[x0, y0], [x1, y1], ...],
"roads": [ <way object>, ... ],
"footways": [ <way object>, ... ],
"barriers": [ <way object>, ... ],
"crossroads": [ <way object>, ... ],
"nodes_cache": { "<node_id>": {"lat": 50.1, "lon": 14.5, "tags": {}}, ... }
}
format_version is the schema version (currently 1, see FORMAT_VERSION in
map_data/utils/serialization.py). Files written before the marker existed have no such key and
are read as the pre-versioning schema; a file whose version is newer than the one this build
knows is rejected with a ValueError rather than half-read. Every way key is optional on load, so
a file written by an older version falls back to the Way defaults for the keys added since.
metadata object
| Field | Type | Description |
|---|---|---|
zone_number |
int | UTM zone number |
zone_letter |
str | UTM zone letter |
min_x / max_x |
float | UTM easting bounding box (includes margins) |
min_y / max_y |
float | UTM northing bounding box (includes margins) |
min_lat / max_lat |
float | WGS-84 latitude bounding box |
min_long / max_long |
float | WGS-84 longitude bounding box |
coords_file |
str or null | Path of the source GPX/YAML file, or null if constructed from an array |
Way object (within roads, footways, barriers, crossroads)
| Field | Type | Description |
|---|---|---|
id |
int or str | OSM way/node ID, or a negative synthetic ID for merged segments |
is_area |
bool | true if the geometry is a closed polygon |
nodes |
list[int] | Ordered list of OSM node IDs |
tags |
object | OSM tag key-value pairs |
line |
str | Shapely WKT representation of the geometry (UTM coordinates) |
in_out |
str or null | "outer" or "inner" for multipolygon relation members; null otherwise |
Human-readable export
The viewer's Export button writes <stem>.exported.mapdata — a JSON file with the same schema as above but with all viewer annotations (deleted ways, tag overrides, node position overrides, drawn annotations) applied. This file can be used as a clean, annotation-resolved snapshot for downstream tools.
The GeoJSON button next to it writes the same annotation-resolved data as <stem>.geojson — a
standard GeoJSON FeatureCollection (see mapdata_to_geojson in map_data/viewer/helpers.py) for
use in QGIS, geojson.io, or other GIS tools.
.annotations.json sidecar format
Annotation files are written alongside the .mapdata file as <stem>.annotations.json. They store all edits made in the viewer without modifying the original map data. The viewer merges the sidecar at load time.
Top-level keys
| Key | Type | Description |
|---|---|---|
version |
int | Schema version (currently 1) |
annotations |
list | Drawn geometry annotations (obstacles, path segments) |
deleted_ways |
list | Ways removed from the map view |
hidden_ways |
list | Ways temporarily hidden but not deleted |
tag_overrides |
object | Per-way OSM tag edits |
split_ways |
object | Node IDs at which a way is split into segments |
detached_nodes |
list | Synthetic node that starts the segment after a split |
deleted_nodes |
object | Node IDs deleted from specific ways |
node_position_overrides |
object | Dragged node position corrections |
change_log |
list | Audit log of all user-initiated changes |
change_log_migration |
str | Internal migration version tag |
annotations list
Each entry is a GeoJSON Feature-like object:
{
"id": "ann_1715600000000",
"type": "obstacle",
"geometry": {
"type": "Polygon",
"coordinates": [[[14.5678, 50.1234], [14.5680, 50.1234],
[14.5680, 50.1236], [14.5678, 50.1234]]]
},
"properties": {}
}
type is either "obstacle" (drawn barrier polygon) or "path" (drawn navigable path segment).
deleted_ways list
hidden_ways list
Same structure as deleted_ways. Hidden ways are still included in export but are not rendered in the viewer.
tag_overrides object
Keys are way IDs as strings; values are dicts of tag key-value pairs that replace the original OSM tags for that way:
split_ways object
Keys are way IDs as strings; values are lists of OSM node IDs at which the way is split:
detached_nodes list
A split made in the viewer detaches the segments. The segment after the split
starts at its own copy of the split node, with a negative synthetic id. This
means each end can be moved on its own, and the planner does not route across
the split. Splits without an entry (older stores) keep sharing the node.
Undoing the split removes the entry and the position overrides of both ends.
Positions of moved nodes, including detached ends, are also what the graph planner uses.
deleted_nodes object
Keys are way IDs as strings; values are lists of OSM node IDs deleted from that way:
node_position_overrides object
Keys are way IDs as strings; values are dicts mapping node IDs (as strings) to corrected {lat, lon} positions:
A move applies to that way only. When the node is also used by another way, the merged map gives the moved way its own copy of the node under a fresh negative id (minted at merge time, not stored) and the other ways keep the original position, so the ways no longer join there. Ways that put the node within 0.5 m of each other keep sharing it; when all of them do, the node simply moves. A move recorded on a way that no longer uses the node is ignored.
A moved end node that lands within 5 m of another way is joined to it at merge time
(join_ways in map_data/annotations.py): the junction, a node of that way or a new one on
its edge, is put after the end in the way's node list. The stored position is not changed.
change_log list
An ordered audit log. Each entry has a type field and optional ts (ISO timestamp for user-initiated changes):
[
{"type": "way", "id": 123456789, "category": "barrier", "label": "building", "ts": "2025-11-01T10:00:00Z"},
{"type": "node", "way_id": 123456789, "node_id": 9876540, "ts": "2025-11-01T10:01:00Z"},
{"type": "tag", "id": 987654321},
{"type": "move", "id": 123456789, "category": "footway", "label": ""},
{"type": "split","way_id": 123456789, "node_id": 9876543}
]
GPX waypoint format
MapData reads standard GPX 1.1 files (used for map creation, e.g. create_mapdata and osm_cloud's gpx_file parameter). It reads <wpt> elements if the file has any; otherwise it falls back to track points (<trkpt>) from <trk> elements, and then to route points (<rtept>) from <rte> elements. Loading fails if none of the three are present.
<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="MapData Planner">
<wpt lat="50.1234" lon="14.5678"></wpt>
<wpt lat="50.1240" lon="14.5690"></wpt>
<wpt lat="50.1250" lon="14.5700"></wpt>
</gpx>
YAML waypoint files are not supported.