Skip to the lesson
FixTheRobot Explore Robotics / Localization & SLAM / M7 SLAM with ROS 2 / M7-T6

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.

  • Lesson 6 of 8
  • Module 7: SLAM with ROS 2
  • Theory
  • About 8 minutes to read

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.

Two kinds of saved mapA mapping session can be saved as an occupancy grid, a picture plus a yaml file used by the map server and AMCL, or as a serialised pose graph, the scans and constraints used by SLAM Toolbox. The occupancy grid is derived from the graph and cannot be turned back into it.Mapping sessiongraph + scans Occupancy gridmap.pgm + map.yamlSerialised pose graphmap.posegraph + map.data for: map_server, AMCL, Nav2 static layerfor: SLAM Toolbox localization / continue mapping derivedone way only Save both. The picture is lossy: it cannot be turned back into scans and constraints.
The occupancy grid is derived from the pose graph, never the reverse.

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:

image
mymap.pgm\mathrm{my}_{\text{map}}.\mathrm{pgm}
mode
trinary\text{trinary}
resolution
0.050.05
origin
[−10.0, −10.0, 0.0][-10.0,\,-10.0,\,0.0]
negate: 0\text{negate}:\,0
occupiedthresh: 0.65\mathrm{occupied}_{\text{thresh}}:\,0.65
freethresh: 0.25\mathrm{free}_{\text{thresh}}:\,0.25
Map yaml fields
FieldMeaning
imageImage file, relative to the yaml
resolutionMetres per pixel
origin[x, y, yaw] of the lower-left corner of the image in the map frame
negateWhether to invert the meaning of white and black
occupied_thresh, free_threshOccupancy probability above which a cell is occupied, and below which it is free (in between: unknown)
modetrinary (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:

pixel to world coordinates
x=originx+(c+0.5)⋅resolutionx=\mathrm{origin}_{x}+(c+0.5)\cdot \text{resolution}
y=originy+(H−r−0.5)⋅resolutiony=\mathrm{origin}_{y}+(H-r-0.5)\cdot \text{resolution}

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

Saved map problems
SymptomCauseRemedy
Map loads but the robot appears inside wallsWrong origin, or the map was edited and cropped without updating itRestore the original yaml, or recompute the origin
Map is mirrored or invertednegate or image editor flipped the pictureFix the yaml or re-export
Map is blurred or tinyImage resampled in an editor; resolution no longer matchesNever resample; edit at native resolution
“Unable to open map file”Relative path in the yaml, different working directoryUse an absolute path, or keep image and yaml together
Localisation mode starts with an empty mapOnly the image was saved, or the graph filename has an extensionSerialise the graph; pass the name without extension
Navigation treats unknown as freeThresholds or costmap track_unknown_space misconfiguredCheck free_thresh and the costmap settings (M7-T7)
Map in a new session is shiftedDifferent start pose; map origin not anchoredGive the correct initial pose or use a dock/anchor

A worked example: reading a yaml and finding a world coordinate

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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 /map and verify width, height, resolution and origin against the yaml.
  • Keep the frame names fixed. A map built with base_footprint and reused with base_link may 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

  1. Save the occupancy grid (image + yaml) for Nav2 and AMCL, and the serialised pose graph for SLAM Toolbox.
  2. The yaml carries resolution and origin; x = origin_x + (c + 0.5) r, y = origin_y + (H − row − 0.5) r.
  3. Never resample or crop the image without updating the yaml; keep image and yaml together.
  4. Pass the graph filename without an extension and keep the SLAM Toolbox version.
  5. Verify a loaded map by echoing /map and comparing its metadata with the yaml.

References and further reading

Primary papers and authoritative documentation.

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.

Practice this lesson

Configure Mapping and Localization

Build a map, save it and serialised graph, then relaunch in localization mode with a correct initial pose.

Next lesson

M7-T7 — SLAM and Nav2 Integration

Connect SLAM outputs to Nav2 costmaps and navigation, and know what Nav2 needs from SLAM.

Nothing below is required to finish this lesson.