Repository navigation
Topological YAML Format
Topological maps are YAML files. This page describes the current ROS 2 format; the authoritative definition is the JSON schema config/tmap-schema.yaml (draft-07, additionalProperties: true almost everywhere, so extra application specific keys are allowed). Validate a map with:
ros2 run topological_navigation validate_map.py my_map.tmap2.yaml
# or, without ROS 2
python3 topological_navigation/topological_navigation/validate_map.py my_map.tmap2.yamlBoth extensions (.tmap2.yaml and .tmap3.yaml) use this structure. The tmap3 variant is what convert_tmap.py produces from legacy maps: nodes carry nav_frame, transformation uses topo_frame_id, snake_case action names are used, and the definitions and actions sections are normally kept in a sidecar file (see below).
meta:
last_updated: 07-15-2026_13-26-26 # required, MM-DD-YYYY_HH-MM-SS
origin: {latitude: 53.268, longitude: -0.524, altitude: 0} # optional GPS origin
metric_map: my_farm # required
name: my_farm # required
pointset: my_farm # required
navigation_config_file: topological_navigation_config.yaml # optional sidecar
transformation: # published by map_manager2 as a static TF
topo_frame_id: my_farm # child frame of the node poses
parent: map # parent TF frame
rotation: {w: 1.0, x: 0.0, y: 0.0, z: 0.0}
translation: {x: 0.0, y: 0.0, z: 0.0}
definitions: {} # reusable values, e.g. behaviour trees
actions: {} # named edge actions
nodes: [] # requiredRequired by the schema: meta (with meta.last_updated), metric_map, name, nodes and pointset. transformation, definitions and actions are needed in practice for navigation (the former for the TF and the latter two for goal building, either inline or via the sidecar).
nodes:
- meta: # required: map, node, pointset
map: my_farm
node: RowEntry_A1
pointset: my_farm
tag: [node_semantic::indoor] # optional extra meta
node:
name: RowEntry_A1 # required
nav_frame: '${transformation.topo_frame_id}'
pose:
position: {x: 10.5, y: 5.2, z: 0.0}
orientation: {w: 0.707, x: 0.0, y: 0.0, z: 0.707}
properties: # optional, freeform
xy_goal_tolerance: 0.3
yaw_goal_tolerance: 0.1
verts: # influence zone polygon, relative to the node pose
- {x: -0.5, y: -0.5}
- {x: 0.5, y: -0.5}
- {x: 0.5, y: 0.5}
- {x: -0.5, y: 0.5}
edges: [] # required: outgoing edges| Field | Description |
|---|---|
meta.map, meta.node, meta.pointset
|
Required. Further meta keys are allowed. |
node.name |
Unique node name, used as the GotoNode target. |
node.edges |
Required list of outgoing edges (may be empty). |
node.pose |
Position and orientation in the node frame (nav_frame or the topo_frame_id frame). |
node.nav_frame |
Frame used for the goal poses. A ${transformation.<key>} reference is resolved against the map. |
node.verts |
Influence zone polygon vertices relative to the node pose. The robot is at the node while inside the polygon. An empty list means no zone. |
node.properties |
Freeform dictionary, see Properties. |
node.localise_by_topic |
Optional JSON string for topic based localisation, see the topological_navigation README. |
edges:
- edge_id: RowEntry_A1_RowEnd_A1 # required, unique
node: RowEnd_A1 # required, destination node name
action: row_traversal # key into the 'actions' section
properties: # optional
max_speed: 0.5
boundary_left: 0.4
boundary_right: 0.6| Field | Description |
|---|---|
edge_id |
Required. By convention <origin>_<destination>. |
node |
Required. Name of the destination node. |
action |
Name of an entry in actions. |
properties |
Optional freeform dictionary, see Properties. |
actions maps an action name to the way an edge using it is executed. definitions holds named values, typically Nav2 behaviour tree XML, that goal templates can reference.
definitions:
default_bt: |
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<PipelineSequence name="NavigateWithReplanning">
<ComputePathToPose goal="{goal}" path="{path}" planner_id="GridBased"/>
<FollowPath path="{path}" controller_id="FollowPath"/>
</PipelineSequence>
</BehaviorTree>
</root>
actions:
navigate_to_pose:
composable: false # consecutive edges are not merged
action_type: nav2_msgs.action.NavigateToPose # dotted Python import path
action_server: /navigate_to_pose
action_goal_template:
pose:
header:
frame_id: '${node.nav_frame}'
pose: '${node.pose}'
behavior_tree: '${definitions.default_bt}'
row_traversal:
composable: true # consecutive row_traversal edges become one goal
action_type: nav2_msgs.action.NavigateThroughPoses
action_server: /navigate_through_poses
ros_parameters: # edge property -> goal checker parameter
max_speed: FollowPath.max_robot_speed
set_ros_params: # constants set while the segment runs
collision_monitor:
FootprintApproach.enabled: false
action_goal_template:
poses:
- header:
frame_id: '${node.nav_frame}'
pose: '${node.pose}'
behavior_tree: '${definitions.row_traversal_bt}'| Key | Description |
|---|---|
action_type |
Required. Dotted Python path of the ROS action type. |
action_server |
Required. Name of the action server. One client is created per unique (action_type, action_server) pair. |
action_goal_template |
Required. Goal structure; ${node.<attr>}, ${transformation.<key>} and ${definitions.<name>} are substituted. |
composable |
Whether consecutive edges with this action are merged into one goal (the template is then repeated per node in poses). |
ros_parameters |
Optional. Maps an edge property name to a parameter of the goal checker node. |
set_ros_params |
Optional. Constant ROS parameters applied during the segment and restored afterwards. |
The default action configuration is in config/topological_navigation_config.yaml and defines navigate_to_pose, navigate_to_poses, navigate_through_poses, goal_align and row_traversal. See doc/NAVIGATION2.md and doc/SET_ROS_PARAMS.md for details, including how edge properties are merged within a segment.
definitions and actions may live in a separate YAML file (schema: config/navigation-config-schema.yaml, both sections required, no other top-level keys). Reference it from the map with navigation_config_file, or pass --navigation-config-file to map_manager2.py (the launch file argument is navigation_config_file). Relative paths are resolved against the map file. Only definitions and actions are imported and sections that exist inline in the map take precedence.
Both nodes and edges accept an optional properties dictionary of arbitrary keys. Read them defensively, since none are mandatory.
| Level | Property | Used by |
|---|---|---|
| node |
xy_goal_tolerance, yaw_goal_tolerance
|
Pushed to the Nav2 goal checker while heading to the node |
| node | roboflow.enabled |
Highlighted in the web app |
| node |
semantics, zone, any namespaced dictionary |
Application specific |
| edge |
boundary_left, boundary_right
|
Corridor half-widths (m) used for the boundary polygon and route map |
| edge | set_ros_params |
Per-edge override of the action's set_ros_params
|
| edge | any key listed in the action's ros_parameters (for example max_speed) |
Forwarded to the mapped ROS parameter |
Namespacing (for example roboflow: {enabled: true, confidence: 0.7}) is recommended to avoid clashes between applications.
Older (ROS 1 era) maps used parent_frame, restrictions_planning, restrictions_runtime, action_type, fluid_navigation, fail_policy, config, top_vel and goal with $node.pose style references. These are not used by the ROS 2 navigation server. Use convert_tmap.py to migrate such maps.
| Tool | Purpose |
|---|---|
map_analyser.py analyse / check
|
Schema, orphaned nodes, disconnected sub-maps, zone overlaps, statistics |
map_analyser.py svg / minify / merge
|
Render, compress with YAML anchors, merge maps |
map_recorder.py / manual_topomapping.py
|
Create maps while driving |
topological_map_visualiser.py |
Edit node positions in RViz and save the map |