Annotations and edits
Load a map the way the viewer shows it: with its annotation store merged and the traversability rules applied.
from map_data.annotations import load_mapdata_with_annotations
md, store = load_mapdata_with_annotations("my_area.mapdata")
Merging a store
Apply a viewer annotation store to a :class:~map_data.map_data.MapData.
The viewer keeps every user edit of a map (deleted ways/nodes, splits, moved
nodes, tag overrides, drawn paths and obstacles) in <stem>.annotations.json
next to the .mapdata file. Merging it here, outside the web app, lets
scripts, map_data_plan and the ROS nodes use the map the viewer shows.
:func:load_mapdata_with_annotations is the one-call entry point and
:func:apply_store the merge itself.
load_mapdata_with_annotations(mapdata_path, annotations_path=None, exclude_highway=NON_ROUTABLE_HIGHWAY_VALUES, traversability=None)
Load a .mapdata file and merge its annotation store.
annotations_path defaults to :func:annotation_path_for; a missing
store yields the unedited map, :data:NO_ANNOTATIONS skips it.
traversability is a
:class:~map_data.traversability.TraversabilityRules, a rule file, or
None for the package's config/traversability.yaml. It is applied
after the store, so tag overrides affect it. exclude_highway values
become one leading deny rule. Pass an empty iterable and
TraversabilityRules() to keep every way.
Returns (map_data, store).
edit_mapdata(md, source_path, annotations_path=None, exclude_highway=NON_ROUTABLE_HIGHWAY_VALUES, traversability=None)
Apply the traversability rules and the annotation store to a freshly built md.
:func:load_mapdata_with_annotations for a map not read from a .mapdata
file (see there for the arguments). source_path names the default store.
apply_store(md, store)
Apply every kind of edit in store to a copy of md and return it.
No pass mutates a Way, a node entry or the waypoints; they rebind the
map's attributes, except :func:merge_annotations, which appends to the way
lists and adds nodes to nodes_cache. A shallow copy with its own
containers therefore leaves md untouched, far cheaper than a deepcopy.
annotation_path_for(mapdata_path)
<dir>/<stem>.annotations.json for a .mapdata file.
load_summary(path, md, store, annotations, exclude_highway, trav_path, *, crossroads=False)
One-line "loaded
annotations is the store argument used when loading (None for the
default sidecar), trav_path the resolved traversability rule file.
Store and way edits
The JSON annotation store and the way-edit pipeline.
The pipeline rebuilds a :class:~map_data.utils.way.Way's nodes and
line from stored edits (deleted/moved/added/split nodes), for both
LineString ways and buffered-Polygon ways.
AnnotationStoreError
Bases: Exception
The annotation store exists but is not a readable JSON object.
load_annotations(path)
Load the annotation store from path, or {"version": 1, "annotations": []} if absent.
Raises:
| Type | Description |
|---|---|
AnnotationStoreError
|
If path exists but cannot be read, is not valid JSON, or is not a JSON object. Treating it as empty would let the next edit overwrite every edit it holds. |
annotation_store(path)
Yield the store at path for in-place mutation, under its lock, and save it on exit.
The per-path lock is held across load -> mutate -> save, so concurrent
edits cannot overwrite each other. The store is written (atomically)
only when the body exits normally; if it raises (including abort())
nothing is saved. An unreadable store raises :class:AnnotationStoreError
before the body runs, so it is never overwritten.
quarantine_annotation_store(path)
Move an unreadable store at path aside to <path>.corrupt-<timestamp>.
Runs under the store's lock and re-checks the file first, so a store another request already replaced with a good one is left alone.
Returns:
| Type | Description |
|---|---|
Path or None
|
The backup path, or |
split_way(way, split_nids, zone_number, zone_letter, nodes_cache, detached=None)
Split a way into segments at the given interior node IDs.
A LineString way is cut along its own coordinates; a buffered
Polygon way's centerline is rebuilt from nodes_cache and each
segment re-buffered by :func:_buffer_radius. Returns [way]
unchanged when there is nothing to split: no split_nids, no geometry
or nodes, a closed way, or fewer than two resulting segments. Split IDs
at the first or last node are ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
way
|
Way
|
The way to split. |
required |
split_nids
|
list of int
|
Interior node IDs at which to split the way. |
required |
zone_number
|
int
|
UTM zone number. |
required |
zone_letter
|
str
|
UTM zone letter. |
required |
nodes_cache
|
dict or None
|
|
required |
detached
|
dict of {int: int}
|
Split node ID -> synthetic node ID that starts the following segment
instead (see :func: |
None
|
Returns:
| Type | Description |
|---|---|
list of Way
|
Shallow copies of way with their own |
apply_node_position_overrides(way, overrides, zone_number, zone_letter, nodes_cache=None, category=None)
Return a copy of way with node positions moved per overrides.
Each node's position comes from overrides, else nodes_cache, else
way.line (least reliable: a Polygon's ring is the buffer
outline, not the centerline). A LineString is rebuilt from the new
coordinates, a Polygon by :func:_rebuild_polygon. A way without
nodes (a point barrier) is translated so its centroid lands on the
first override.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
way
|
Way
|
The way whose node positions to override. |
required |
overrides
|
dict of {int: dict}
|
|
required |
zone_number
|
int
|
UTM zone number. |
required |
zone_letter
|
str
|
UTM zone letter. |
required |
nodes_cache
|
dict or None
|
|
None
|
category
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
Way or None
|
The edited copy; way itself when there is nothing to apply or it
cannot be rebuilt; |
apply_added_nodes(way, store, zone_number, zone_letter)
Return a copy of way with its user-added synthetic nodes spliced in.
Each store["added_nodes"] entry of the original way ({"id",
"after_node_id", "lat", "lon"}) is inserted after after_node_id,
at its moved position if it has one; an entry whose anchor is not in
the node list is skipped. A LineString gets the coordinate too; a
Polygon keeps its geometry, since its ring is the buffer outline.
Returns way itself when nothing was inserted, the geometry is another
type, or the new line is invalid.
rebuild_way_without_nodes(way, del_nids, zone_number, zone_letter, nodes_cache, category=None)
Return a copy of way with the nodes del_nids removed and line rebuilt.
A LineString keeps the coordinates of the remaining nodes (re-closed
if the way was closed). A Polygon is rebuilt from the remaining
nodes' nodes_cache positions by :func:_rebuild_polygon, falling back
to the bare centerline if re-buffering fails.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
way
|
Way
|
The way to rebuild. |
required |
del_nids
|
set of int or list of int
|
Node IDs to remove from the way. |
required |
zone_number
|
int
|
UTM zone number. |
required |
zone_letter
|
str
|
UTM zone letter. |
required |
nodes_cache
|
dict or None
|
|
required |
category
|
str
|
Way category (e.g. |
None
|
Returns:
| Type | Description |
|---|---|
Way or None
|
The edited copy, or |