Lecture#

Prerequisites#

Ensure you have followed the simulation instructions and have everything set up before running any code in this lecture.

Before You Start
  • Remove the log/, build/, and install/ folders.

  • Do a git pull.

  • Install dependencies:

rosdep install -i --from-path src \
    --rosdistro $ROS_DISTRO --ignore-src -y \
    --skip-keys "micro_ros_agent python3-ftdi"
  • Compile lecture 13 packages only:

colcon build --symlink-install \
    --cmake-args -DCMAKE_BUILD_TYPE=Release \
    --packages-up-to lecture13_meta
  • Source your workspace.

  • Verify Gazebo launches cleanly:

ros2 launch rosbot_gazebo husarion_world.launch.py

Mapping#

A map is a spatial model of the environment that a robot uses to plan paths, localize itself, and reason about the world. Before a robot can navigate autonomously, it must either be given a map or build one from sensor data.

Why Does a Robot Need a Map?#

Without a map, a robot can only react to its immediate sensor readings. A map enables:

  • Global path planning: computing a route from the current position to a distant goal, even when the goal is not directly visible.

  • Localization: answering where am I? by matching sensor readings against a known spatial model.

  • Semantic reasoning: knowing not just that an obstacle exists, but what it is and how to interact with it.

  • Multi-session operation: a persistent map lets a robot resume work in a familiar environment without re-exploring it from scratch.

Note

The choice of map representation directly determines what the robot can and cannot do. A representation that is efficient for path planning may be useless for object recognition, and vice versa.

Map Representations#

Three broad families of map representation are used in mobile robotics.

Table 28 Map representation families#

Type

What it stores

Typical use

Metric

Geometric structure of the environment at a known scale

Path planning, obstacle avoidance

Topological

Graph of places and the connections between them

High-level navigation, long-range planning

Semantic

Objects, regions, and their meaning

Task planning, human-robot interaction

Note

These families are not mutually exclusive. Modern systems often layer them: a metric map for local collision avoidance, a topological graph for room-to-room routing, and semantic labels on top for task-level reasoning.

Metric, topological, and semantic map representations

Fig. 100 Map representations: metric, topological, and semantic.#

Metric, topological, and semantic map representations

Fig. 101 Map representations: metric, topological, and semantic.#

Occupancy Grid Maps#

An occupancy grid map is a metric map that divides the environment into a regular grid of cells. Each cell stores a value representing the probability that the corresponding region of space is occupied by an obstacle.

Grid Parameters

An occupancy grid is defined by a few key parameters:

  • Resolution: the width (and height) of one cell in metres. A typical value is 0.05 m (5 cm per cell). Finer resolution gives more detail but requires more memory and computation.

  • Width and Height: the number of cells in each dimension. A \(4000 \times 4000\) map at 0.05 m/cell covers a \(200 \times 200\)-m area.

  • Origin: the pose of the bottom-left corner of the map in the map frame, stored as a geometry_msgs/Pose.

Note

The nav_msgs/msg/OccupancyGrid message bundles all of this together with the cell data array.

ros2 interface show nav_msgs/msg/OccupancyGrid

Cell States

Each cell in the grid can be in one of three states.

Table 29 Free, occupied, and unknown states#

State

Value

Meaning

Free

0

The cell is known to be unoccupied.

Occupied

100

The cell is known to contain an obstacle.

Unknown

-1

The cell has not been observed yet.

Values between 0 and 100 represent intermediate probabilities produced by probabilistic map-update algorithms such as those used by slam_toolbox.

Note

When visualized in RViz2: free cells appear white, occupied cells appear black, and unknown cells appear grey.

Occupancy grid showing free, occupied, and unknown cells

Fig. 102 Occupancy grid: cell states.#

Occupancy grid showing free, occupied, and unknown cells

Fig. 103 Occupancy grid: cell states.#

Why Probabilities? How LiDAR Updates the Map#

Real sensors are noisy: a single reading can be a spurious reflection, a glass surface, or a moving person. A binary free/occupied decision from one scan would be fragile, so probabilistic algorithms accumulate evidence over many scans.

A LiDAR fires many rays per scan. For each ray, slam_toolbox applies a Bayesian update (typically log-odds based) to every cell the ray touches:

  • Cells the ray passes through -> probability decreases (evidence for free).

  • The cell where the ray terminates -> probability increases (evidence for occupied).

  • Cells the ray never reaches -> stay at -1 (unknown).

Example

A cell at the edge of a thin obstacle evolves across four scans (hit, hit, pass-through, hit) as \(65 \rightarrow 80 \rightarrow 70 \rightarrow 85\), stabilizing near \(80\): very likely occupied, with some sensor disagreement.

Note

Downstream consumers (e.g., Nav2 costmaps) apply a threshold to convert probabilities into planning decisions, trading off conservative vs. aggressive behavior.

The map Frame#

The map frame is the global reference frame for navigation. REP 105 defines a chain of frames that every mobile robot stack uses.

Table 30 The full REP 105 frame chain#

Transform

Who publishes it and why

world \(\to\) map

Published by a higher-level mission system when a global reference is needed. Often omitted in single-robot setups.

map \(\to\) odom

Published by the localization stack (SLAM or AMCL). Corrects accumulated drift by aligning the robot’s estimated pose with the map.

odom \(\to\) base_link

Published by the robot driver (wheel encoders + IMU). Locally consistent; never jumps but drifts over time.

base_link \(\to\) other robot frames

Published by robot_state_publisher from the URDF. Fixed geometric offsets.

Important

The map \(\to\) odom transform can jump when the localization estimate is corrected (e.g., loop closure in SLAM, particle filter convergence in AMCL). This is intentional: the odom frame absorbs smooth motion; the map frame absorbs corrections.

REP 105 frame chain

Fig. 104 REP 105 frame chain: who publishes each transform and what it represents.#

REP 105 frame chain

Fig. 105 REP 105 frame chain: who publishes each transform and what it represents.#

``map`` vs. ``odom``

odom

map

Publisher

Robot driver

Localization stack (SLAM/AMCL)

Can jump?

No

Yes (on correction)

Drifts?

Yes (over time)

No (corrected against map)

Use for

Short-term, smooth control

Global planning and goal poses

Note

Navigation goals sent to Nav2 are expressed in the map frame. The planner plans in map. The controller executes in odom. TF2 handles the conversion automatically.

SLAM#

SLAM (Simultaneous Localization and Mapping) solves a chicken-and-egg problem: to build a map you need to know where you are, but to know where you are you need a map. SLAM estimates both simultaneously from sensor data alone.

Note

slam_toolbox is a graph-based 2D SLAM library. It builds a pose graph from successive LiDAR scans and corrects accumulated drift through loop closure.

Scan Matching#

As the robot moves, the LiDAR produces a stream of scans. Each scan is a snapshot of the surrounding obstacles as seen from the robot’s current position.

  • Scan matching aligns two consecutive scans by finding the rigid transformation (translation + rotation) that best overlaps them.

  • The result is an estimate of how much the robot moved between the two scans, independently of wheel odometry.

  • This incremental pose estimate is much more accurate than wheel odometry alone, especially on slippery or uneven surfaces.

Scan matching aligning two LiDAR point clouds

Fig. 106 Scan matching: the LiDAR point cloud from pose \(t_1\) is aligned with the cloud from pose \(t_2\) to recover the rigid transform between them, independently of wheel odometry.#

Scan matching aligning two LiDAR point clouds

Fig. 107 Scan matching: the LiDAR point cloud from pose \(t_1\) is aligned with the cloud from pose \(t_2\) to recover the rigid transform between them, independently of wheel odometry.#

Pose Graph#

  • Every time the robot moves far enough (configured by minimum_travel_distance and minimum_travel_heading relative to the last node, measured from the incoming odom estimate), slam_toolbox creates a new node representing the robot’s pose at that moment.

  • An edge is added between consecutive nodes. Its transform comes from scan matching, not from odometry, so the graph reflects what the LiDAR actually saw.

  • The pose graph is a sparse representation of the robot’s trajectory. The occupancy grid is rendered from it by stamping each node’s scan into the grid at the node’s optimized pose.

Pose graph: nodes and edges

Fig. 108 A pose graph: nodes are estimated robot poses, edges encode the relative transforms between them.#

Pose graph: nodes and edges

Fig. 109 A pose graph: nodes are estimated robot poses, edges encode the relative transforms between them.#

Loop Closure#

  • When the robot returns near a previously visited area (detected by pose proximity in the graph), the current scan is matched against the stored scan from that earlier visit.

  • If the scan match succeeds with high confidence, a loop-closure edge is added connecting the current node to the earlier node.

  • The graph is then optimized: all node poses are adjusted to minimize the total inconsistency across odometry edges, scan-match edges, and loop-closure edges. This corrects accumulated drift across the entire trajectory.

Loop closure correcting accumulated drift

Fig. 110 Loop closure corrects accumulated drift: the loop-closure edge lets graph optimization realign the whole trajectory into a globally consistent map.#

Loop closure correcting accumulated drift

Fig. 111 Loop closure corrects accumulated drift: the loop-closure edge lets graph optimization realign the whole trajectory into a globally consistent map.#

Note

Loop closure is why SLAM produces globally consistent maps even after long traversals. Without it, the map would shear and overlap as drift accumulates.

How the Three Pieces Connect#

Scan matching, the pose graph, and loop closure form a pipeline:

  1. Scan matching produces the edges. It aligns consecutive LiDAR scans to estimate the relative motion (translation + rotation) between two robot poses. Each result becomes a constraint.

    • The covariance (uncertainty) of the scan match result determines the constraint strength. When two scans have many overlapping features and align well, the covariance is small – the optimizer trusts that edge heavily. When scans are sparse or ambiguous (e.g., a featureless corridor), the covariance is large – the optimizer treats that edge as weak and allows other constraints to override it.

  2. The pose graph stores the structure. Each pose is a node; each scan-match result is an edge. The graph grows as the robot drives. Sequential edges accumulate drift over time.

  3. Loop closure adds correction edges. When the robot revisits a previously seen area, scan matching is performed between the current scan and a stored scan from the earlier visit. This creates a new edge connecting two distant nodes. A graph optimizer then adjusts all node poses simultaneously to minimize the total error, correcting the accumulated drift.

Note

In short: scan matching produces local motion estimates, the pose graph accumulates them, and loop closure constrains the graph globally to fix drift.

Launching slam_toolbox#

We launch slam_toolbox alongside Gazebo and RViz2. As the robot moves, the map grows in real time. slam_toolbox is configured via a YAML parameter file. The most important parameters for this course:

Table 31 Key parameters#

Parameter

Default

Effect

resolution

0.05

Map cell size in metres.

max_laser_range

20.0

Ignore LiDAR returns beyond this range.

minimum_travel_distance

0.5

Minimum distance before a new node is added.

minimum_travel_heading

0.5

Minimum rotation (rad) before a new node is added.

use_scan_matching

true

Enable scan-to-scan matching.

do_loop_closing

true

Enable loop closure.

Note

The full parameter list is in the slam_toolbox repository under config/mapper_params_online_async.yaml. You rarely need to change parameters beyond resolution and laser range for typical indoor environments.

Demonstration: Building a Map (teleop)

Command

T1

ros2 launch rosbot_gazebo husarion_world.launch.py rviz:=False

T2

ros2 launch nav_demo map_nav.launch.py mode:=mapping

Gazebo

Drive the robot with teleop

RViz

Observe the map display

Experiment

Drive the robot fast through a narrow corridor. Does the map look accurate? Now drive slowly. What changes? Why does speed affect map quality?

Answer

SLAM quality is bounded by the worst link in the chain of odometry, sensor sweep rate, and scan matching. Speed amplifies all three error sources simultaneously.

The practical rule is: drive at a speed where the displacement between two scans is small compared to the features being mapped, and keep angular velocity especially low since rotational motion distorts LiDAR scans more severely than translation.

Map Saving and Loading#

Once a satisfactory map has been built, it is saved to disk and reloaded later for localization-only operation. The nav2_map_server package handles both halves: map_saver_cli writes the grid to disk, and map_server reads the YAML at startup and republishes the grid on /map for AMCL and the costmaps to consume.

Saving the Map

While slam_toolbox is running, save the current map with:

ros2 run nav2_map_server map_saver_cli -f /path/to/husarion_map

This produces two files:

  • husarion_map.pgm: a grayscale image where each pixel corresponds to one grid cell. White = free, black = occupied, grey = unknown.

  • husarion_map.yaml: metadata file.

image: husarion_map.pgm
mode: trinary
resolution: 0.050
origin: [-11.809, -10.197, 0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196