Localization & SLAM · Theory · M7
M7-T6 — Map Saving and Reuse
A saved map is two different things. Knowing which one you have decides what you can do with it later.
After an hour of careful mapping, the robot’s map exists only in memory. Saving it is one command, but the result differs depending on the command, and the difference decides whether you can localise, continue mapping or navigate on it later. This lesson explains the two formats, how coordinates map between the picture and the world, and the errors that make a saved map fail to load, load shifted, or look right but navigate wrongly.
Learning objectives
After this lesson, you should be able to:
- Distinguish the occupancy-grid map files from the serialised pose graph and say what each is used for.
- Read a map yaml file and convert between image pixels and world coordinates.
- Save and load both formats with the standard ROS 2 tools.
- Diagnose a saved map that loads shifted, rotated, inverted or empty.
The problem and the intuition
A pose graph is like the full set of surveyor’s notes: each measurement, with the readings that justify it. An occupancy grid is the printed map drawn from the notes. From the notes you can redraw the map at any scale, correct a mistake, or extend it. From the printed map you cannot recover the notes. Navigation only needs the printed map, so Nav2 loads the grid; relocalising against scans or continuing to survey needs the notes, so SLAM Toolbox loads the graph.
The printed map also needs a legend: how many metres one pixel is, and where the corner of the picture sits in the world. These are in the yaml file. If the legend is lost or wrong, a perfect picture is placed at the wrong place or scale, and the robot appears to move through walls.
Save both: the grid for navigation, the graph for localisation and extension. The yaml legend (resolution and origin) must travel with the image.
The formats and the commands
Problem. Persist the result of a mapping session so that it can be reloaded later, on the same or another machine, for navigation, localisation or further mapping, with the same coordinates.
Assumptions. The mapping session has finished with loop closures applied; the same sensor setup and frame names will be used later; the maps are stored with their parameter files.
Occupancy grid: image plus yaml
Save with the Nav2 map saver (needs /map to be published) or SLAM Toolbox’s service:
ros2 run nav2_map_server map_saver_cli -f my_map
ros2 service call /slam_toolbox/save_map slam_toolbox/srv/SaveMap "{name: {data: 'my_map'}}"
This writes my_map.pgm (or .png) and my_map.yaml:
| Field | Meaning |
|---|---|
image | Image file, relative to the yaml |
resolution | Metres per pixel |
origin | [x, y, yaw] of the lower-left corner of the image in the map frame |
negate | Whether to invert the meaning of white and black |
occupied_thresh, free_thresh | Occupancy probability above which a cell is occupied, and below which it is free (in between: unknown) |
mode | trinary (free, occupied, unknown), scale or raw |
Pixel to world, for the centre of the cell in column c and row r (row 0 at the top of the image) of an image with H rows:
The sign flip in y is because images count rows downward and the map frame has y up.
Serialised pose graph
ros2 service call /slam_toolbox/serialize_map slam_toolbox/srv/SerializePoseGraph "{filename: '/maps/my_graph'}"
ros2 service call /slam_toolbox/deserialize_map slam_toolbox/srv/DeserializePoseGraph "{filename: '/maps/my_graph', match_type: 1, initial_pose: {x: 0.0, y: 0.0, theta: 0.0}}"
The files my_graph.posegraph and my_graph.data contain the nodes, edges and scans. Pass the filename without extension. In a launch parameter file, map_file_name and map_start_pose (or map_start_at_dock) load a graph at startup with mode: localization or continued mapping. The match_type argument chooses how the loaded graph is placed relative to the robot (start at the first node, at a given pose, or at the current location).
Serving a saved grid
For Nav2, nav2_map_server loads the yaml and publishes /map once, with transient-local QoS so that late subscribers still get it (M7-T3); it is a lifecycle node that must be configured and activated by a lifecycle manager:
ros2 run nav2_map_server map_server --ros-args -p yaml_filename:=/maps/my_map.yaml ros2 lifecycle set /map_server configure && ros2 lifecycle set /map_server activate
- occupancy grid
- a 2D array of cells with occupancy probability (−1 unknown, 0 to 100 percent).
- origin
- pose of the image’s lower-left corner in the map frame.
- serialised pose graph
- the saved nodes, constraints and scans of a SLAM Toolbox session.
- lifecycle node
- a node with configure, activate and deactivate states, as used by map_server and Nav2.
Common mistakes
| Symptom | Cause | Remedy |
|---|---|---|
| Map loads but the robot appears inside walls | Wrong origin, or the map was edited and cropped without updating it | Restore the original yaml, or recompute the origin |
| Map is mirrored or inverted | negate or image editor flipped the picture | Fix the yaml or re-export |
| Map is blurred or tiny | Image resampled in an editor; resolution no longer matches | Never resample; edit at native resolution |
| “Unable to open map file” | Relative path in the yaml, different working directory | Use an absolute path, or keep image and yaml together |
| Localisation mode starts with an empty map | Only the image was saved, or the graph filename has an extension | Serialise the graph; pass the name without extension |
| Navigation treats unknown as free | Thresholds or costmap track_unknown_space misconfigured | Check free_thresh and the costmap settings (M7-T7) |
| Map in a new session is shifted | Different start pose; map origin not anchored | Give the correct initial pose or use a dock/anchor |
A worked example: reading a yaml and finding a world coordinate
- The file.
resolution: 0.05,origin: [-10.0, -10.0, 0.0], and the image is 400 × 400 pixels. The map covers 400 × 0.05 = 20 m in each direction, from −10 to +10 in x and y. - A pixel to a point. The pixel in column c = 250, row r = 100 (counting from the top). x = −10.0 + (250 + 0.5) × 0.05 = 2.525 m. y = −10.0 + (400 − 100 − 0.5) × 0.05 = 4.975 m. So the pixel is at (2.525, 4.975) in the map frame.
- The world origin. The map origin (0, 0) lies at column (0 − (−10)) / 0.05 = 200 and row 400 − 200 = 200 (the centre of the image). A robot standing at the map origin appears at the picture’s centre.
- What a bad origin does. If the yaml were edited to
origin: [-12.0, -10.0, 0.0], every pixel would be 2 m to the left; the localiser, trying to match scans to walls, would be consistently wrong by 2 m or would fail to converge. Because the picture itself looks identical, the fault is easy to miss. - Graph versus grid. A 1 000 × 1 000 grid is 1 MB. The pose graph of the same session with 800 keyframes of 360-beam scans (each beam a 4-byte float, 1 440 bytes) is about 800 × 1.5 kB ≈ 1.2 MB of scan data plus the constraints, in the same range here, but it grows with the number of keyframes and not with the area, and it holds what the localiser needs: the original scans. A grid at 1 cm resolution would be 25 times larger and add no information that the scans did not already contain.
Engineering implications and limitations
Engineering implications
- Save after loop closure, not before. Wait for the last corrections to settle, inspect the map in RViz, then save.
- Save a set. The grid, the yaml, the serialised graph, the parameter file, and a screenshot, in a folder named with the date and place.
- Never edit the image at a different resolution. Cleaning stray pixels is fine at native size; cropping needs a matching change of the origin.
- Use absolute paths or a package share directory in launch files so that the map is found from any working directory.
- Check the load. After starting the map server, echo one message from
/mapand verify width, height, resolution and origin against the yaml. - Keep the frame names fixed. A map built with
base_footprintand reused withbase_linkmay load but localise badly.
Limits
A map is a snapshot. It records what was there, not what is allowed: Nav2 needs additional layers for keep-out zones and speed limits. Occupancy grids lose the original scans and cannot be improved by later loop closures. Serialised graphs are specific to a SLAM Toolbox version and its internal formats, so keep the version with them, and cannot be exchanged with other SLAM packages. Compression or format changes may be needed for very large sites.
Common misconceptions
“The .pgm is the map.”
It is one derived representation; the pose graph holds the data it was drawn from.
“I can crop the map image to remove empty margins.”
Only if you also update the origin; otherwise every coordinate shifts.
“Saving twice gives the same files.”
Saving before a loop closure gives a different, worse map from saving after it.
“White pixels are free space.”
It depends on negate and the thresholds; grey is unknown, and many tools treat unknown differently.
Engineering takeaways
- Save the occupancy grid (image + yaml) for Nav2 and AMCL, and the serialised pose graph for SLAM Toolbox.
- The yaml carries resolution and origin; x = origin_x + (c + 0.5) r, y = origin_y + (H − row − 0.5) r.
- Never resample or crop the image without updating the yaml; keep image and yaml together.
- Pass the graph filename without an extension and keep the SLAM Toolbox version.
- Verify a loaded map by echoing /map and comparing its metadata with the yaml.
References and further reading
Primary papers and authoritative documentation.
- Open Navigation. Nav2 documentation: Map Server / Saver. docs.nav2.org.
- Macenski, S. and Jambrecic, I. (2021). SLAM Toolbox: SLAM for the dynamic world. Journal of Open Source Software 6(61), 2783.
- Open Robotics. ROS wiki: map_server — Map format. wiki.ros.org/map_server.
- Elfes, A. (1989). Using occupancy grids for mobile robot perception and navigation. Computer 22(6).
- REP 2003: Sensor Data and Map QoS Settings. ROS Enhancement Proposals (ros.org/reps/rep-2003.html).
Knowledge check
Three conceptual questions. Write an answer, then reveal the explanation. Your answers stay in your browser.
Question 1
A map is 200 × 200 pixels, resolution 0.1 m, origin (−5, −5). Where is the centre of the pixel in column 150, row 40?
Explanation
x = −5 + (150 + 0.5) × 0.1 = 10.05 m. y = −5 + (200 − 40 − 0.5) × 0.1 = 10.95 m. The pixel centre is at (10.05, 10.95); the map spans −5 to +15 m in both axes, so the point is inside it.
Question 2
Why can SLAM Toolbox not localise from only the .pgm and .yaml?
Explanation
Its localisation matches the live scan against the stored scans and constraints in the pose graph, which the picture does not contain. The picture can be derived from the graph but not converted back.
Question 3
After editing the map image to crop empty borders, the robot appears 2 m off. What did you forget?
Explanation
Cropping removed columns on the left, moving the image’s lower-left corner; the yaml origin must be changed by the same distance (columns removed × resolution) or every world coordinate shifts.
What’s next
A saved occupancy grid is what Nav2 uses. The next lesson shows how a SLAM system and Nav2 are connected, what each needs from the other, and how the combined system is brought up.
Related content
Nothing below is required to finish this lesson.
- BeforeM7-T5: Mapping vs Localization Mode
Choose between mapping, localization and lifelong modes and set the initial pose properly.
- LabM7-L4: Configure Mapping and Localization
Build a map, save it and serialised graph, then relaunch in localization mode with a correct initial pose.
Get notified when the next lesson and new labs are released.
One email per new lesson or lab.