By ihsumlee , 9 June 2026
content

Position in the full course

This is the seventh step of the course.

The full learning order is:

1. ROS 2 node/topic/launch review
2. TF2 frame concept
3. Static sensor frames
4. Dynamic odometry frame
5. URDF + robot_state_publisher
6. SLAM mapping
7. AMCL localization
8. Nav2 path planning
9. RViz goal navigation
10. TF2 debugging in Nav2

In Step 6, you used SLAM to create and save a map:

classroom_map.pgm
classroom_map.yaml

In Step 7, you will load this saved map and localize the robot on it using AMCL.

The key transform in this step is:

map β†’ odom

After AMCL works correctly, your TF tree should look like:

map
 └── odom
      └── base_link
            β”œβ”€β”€ base_laser
            β”œβ”€β”€ camera_link
            β”œβ”€β”€ imu_link
            β”œβ”€β”€ left_wheel_link
            └── right_wheel_link

1. Learning objectives

After this module, you should be able to:

  1. Explain what localization means.
  2. Explain what AMCL does.
  3. Explain the difference between SLAM and localization.
  4. Load a saved occupancy grid map.
  5. Launch AMCL localization.
  6. Set the robot initial pose in RViz2.
  7. Check whether map β†’ odom is published.
  8. Check /map, /scan, /odom, /tf, and /particle_cloud.
  9. Understand why AMCL is important before Nav2 path planning.
  10. Debug common AMCL localization problems.

2. Suggested teaching duration

Suggested duration: 2 hours

| Time | Activity | | ----------- | --------------------------------------- | | 0–15 min | Review SLAM mapping and saved map files | | 15–30 min | Explain localization and AMCL | | 30–45 min | Explain AMCL inputs and outputs | | 45–65 min | Check /map, /scan, /odom, and TF | | 65–85 min | Launch AMCL localization | | 85–100 min | Set initial pose in RViz2 | | 100–110 min | Verify map β†’ odom and particle cloud | | 110–120 min | Summary and homework |


3. What is localization?

Localization means:

The robot estimates where it is in a known map.

In Step 6, you built a map.

In Step 7, you use the saved map and ask:

Where is the robot inside this map?

This is different from SLAM.

| Item | SLAM mapping | AMCL localization | | ------------------------ | ----------------------- | ---------------------------------- | | Map needed before start? | No | Yes | | Main purpose | Build a map | Find robot pose in an existing map | | Output | /map and map β†’ odom | Robot pose and map β†’ odom | | Typical use | First exploration | Daily navigation after map exists |

Simple explanation:

SLAM is for creating the map.
AMCL is for using the map.

4. What is AMCL?

AMCL means:

Adaptive Monte Carlo Localization

AMCL uses many possible robot poses called particles.

Each particle is a guess of where the robot might be.

AMCL compares:

the real LiDAR scan
with
the expected scan from the map

Then AMCL updates the particles.

If the robot is well localized, the particles become concentrated around the robot.

In RViz2, you can usually visualize AMCL particles using:

/particle_cloud

5. Why AMCL is needed for Nav2

Nav2 needs to know the robot pose in the map.

For global path planning, Nav2 needs:

map β†’ base_link

But this is usually calculated through the TF chain:

map β†’ odom β†’ base_link

AMCL provides:

map β†’ odom

Odometry provides:

odom β†’ base_link

Together, ROS 2 can calculate:

map β†’ base_link

This tells Nav2 where the robot is inside the map.


6. Required inputs for AMCL

Before launching AMCL, you should have these items.

6.1 Saved map

You need the map file from Step 6:

classroom_map.yaml
classroom_map.pgm

Usually located at:

~/ros2_ws/maps/classroom_map.yaml

Check:

ls ~/ros2_ws/maps

Expected result:

classroom_map.pgm
classroom_map.yaml

6.2 LiDAR scan topic

Usually:

/scan

Message type:

sensor_msgs/msg/LaserScan

Check:

ros2 topic list | grep scan
ros2 topic type /scan
ros2 topic echo /scan --once

AMCL needs this because it compares the LiDAR scan with the known map.


6.3 Odometry topic

Usually:

/odom

Message type:

nav_msgs/msg/Odometry

Check:

ros2 topic list | grep odom
ros2 topic type /odom
ros2 topic echo /odom --once

6.4 TF tree before AMCL

Before AMCL starts, you should already have:

odom β†’ base_link
base_link β†’ base_laser

Check:

ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser

Before AMCL, the TF tree may look like this:

odom
 └── base_link
      └── base_laser

After AMCL works, the TF tree should become:

map
 └── odom
      └── base_link
            └── base_laser

7. Important warning: do not run SLAM and AMCL together for this lesson

In Step 6, SLAM also published:

map β†’ odom

In Step 7, AMCL will publish:

map β†’ odom

For this lesson, do not run SLAM mapping and AMCL localization at the same time.

Otherwise, two systems may try to publish the same transform:

map β†’ odom

This can cause confusing TF behavior.

Use this rule:

Mapping mode: run SLAM.
Localization mode: run map_server + AMCL.

8. Install required packages

Open a terminal:

source /opt/ros/jazzy/setup.bash

Install the required packages:

sudo apt update
sudo apt install ros-jazzy-navigation2 \
                 ros-jazzy-nav2-bringup \
                 ros-jazzy-nav2-amcl \
                 ros-jazzy-nav2-map-server \
                 ros-jazzy-rviz2 \
                 ros-jazzy-tf2-tools

For keyboard teleoperation:

sudo apt install ros-jazzy-teleop-twist-keyboard

9. Classroom practice option A: Use TurtleBot simulation

This option is easiest for classroom practice because the simulation already provides:

robot model
/scan
/odom
TF tree
simulation environment

Terminal 1: Start simulation without SLAM

source /opt/ros/jazzy/setup.bash
ros2 launch nav2_bringup tb3_simulation_launch.py \
  headless:=False \
  slam:=False \
  map:=$HOME/ros2_ws/maps/classroom_map.yaml

Important:

Replace classroom_map.yaml with the map that matches your simulation world.

If your map was created from the same simulation world, AMCL localization should work.


10. Classroom practice option B: Use your own AMR or robot interface

For your own AMR, first start the robot bringup.

You need:

LiDAR driver
wheel odometry or fused odometry
robot_state_publisher
base_link β†’ base_laser
odom β†’ base_link
/cmd_vel support if you want to move the robot

Check the required items:

ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser

Then launch localization.


11. Launch AMCL localization using Nav2 bringup

Use the saved map from Step 6:

source /opt/ros/jazzy/setup.bash

ros2 launch nav2_bringup localization_launch.py \
  map:=$HOME/ros2_ws/maps/classroom_map.yaml \
  use_sim_time:=False

For simulation, use:

ros2 launch nav2_bringup localization_launch.py \
  map:=$HOME/ros2_ws/maps/classroom_map.yaml \
  use_sim_time:=True

Important:

use_sim_time:=True is for simulation.
use_sim_time:=False is for a real robot.

This launch file usually starts:

map_server
amcl
lifecycle_manager_localization

12. Verify that the map is loaded

Open a new terminal:

source /opt/ros/jazzy/setup.bash

Check the map topic:

ros2 topic list | grep map
ros2 topic type /map
ros2 topic echo /map --once

Expected type:

nav_msgs/msg/OccupancyGrid

If /map does not appear, AMCL localization cannot work correctly.


13. Open RViz2 for localization

Open RViz2:

rviz2

Set:

Fixed Frame = map

Add these displays:

Map
LaserScan
TF
RobotModel
PoseArray
Odometry

Recommended settings:

| Display | Topic or setting | | ---------- | ------------------- | | Map | /map | | LaserScan | /scan | | TF | Show Names enabled | | RobotModel | robot_description | | PoseArray | /particle_cloud | | Odometry | /odom |

The PoseArray display is useful because AMCL particles are usually published on:

/particle_cloud

14. Set the initial pose in RViz2

After launching AMCL, the robot may not immediately know where it is in the map.

You need to give an initial pose estimate.

In RViz2:

1. Click 2D Pose Estimate.
2. Click the robot's approximate position on the map.
3. Hold and drag the arrow to set the robot's facing direction.
4. Release the mouse button.

This publishes a message to:

/initialpose

AMCL uses this initial pose to start localization.


15. Verify /initialpose

You can check whether RViz2 publishes the initial pose.

Open another terminal:

source /opt/ros/jazzy/setup.bash
ros2 topic echo /initialpose

Then click 2D Pose Estimate in RViz2.

If the terminal shows a message, RViz2 successfully published the initial pose.


16. Verify AMCL output

After setting the initial pose, check the AMCL particle cloud:

ros2 topic list | grep particle
ros2 topic echo /particle_cloud --once

You should also see particles in RViz2.

If the particles are spread widely, AMCL is uncertain.

If the particles are concentrated around the robot, AMCL is more confident.


17. Verify map β†’ odom

Check the transform:

ros2 run tf2_ros tf2_echo map odom

This asks:

Where is odom relative to map?

If AMCL is working, you should see transform values.

Then check the full chain:

ros2 run tf2_ros tf2_echo map base_link

This asks:

Where is the robot body relative to the map?

This is very important for Nav2.


18. Generate the TF tree

Run:

ros2 run tf2_tools view_frames
xdg-open frames.pdf

Expected result:

map
 └── odom
      └── base_link
            └── base_laser

If you use the URDF model from Step 5, you may see:

map
 └── odom
      └── base_link
            β”œβ”€β”€ base_laser
            β”œβ”€β”€ camera_link
            β”œβ”€β”€ imu_link
            β”œβ”€β”€ left_wheel_link
            └── right_wheel_link

This means localization is connected to your robot TF tree.


19. Move the robot and observe localization

Use teleoperation to move the robot slowly.

For simulation or a robot that accepts /cmd_vel:

ros2 run teleop_twist_keyboard teleop_twist_keyboard

Move slowly:

small forward motions
small rotations
avoid fast spinning
avoid collision

Observe in RViz2:

LiDAR scan should align with the map.
Particles should stay near the robot.
RobotModel should move correctly on the map.

If the LiDAR scan does not align with the map, localization may be wrong.

Use 2D Pose Estimate again to reset the pose.


20. What good localization looks like

Good localization:

LiDAR scan overlaps with walls on the map.
Particle cloud is concentrated near the robot.
RobotModel is at the correct position.
Robot orientation matches the real robot.
map β†’ odom exists.
map β†’ base_link exists.

Bad localization:

LiDAR scan is shifted away from map walls.
Particles are spread everywhere.
RobotModel appears in the wrong room.
Robot orientation is wrong.
map β†’ odom is missing.

21. Create your own localization package

This is useful if you want a clean course package.

Create a package:

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src

ros2 pkg create --build-type ament_python --license Apache-2.0 amr_localization

Create folders:

cd ~/ros2_ws/src/amr_localization
mkdir launch config maps rviz

Copy your map files:

cp ~/ros2_ws/maps/classroom_map.yaml maps/
cp ~/ros2_ws/maps/classroom_map.pgm maps/

22. Create an AMCL parameter file

Create:

nano config/amcl_params.yaml

Paste this beginner configuration:

amcl:
  ros__parameters:
    use_sim_time: false

    global_frame_id: map
    odom_frame_id: odom
    base_frame_id: base_link

    scan_topic: scan

    set_initial_pose: false
    always_reset_initial_pose: false

    min_particles: 500
    max_particles: 2000

    update_min_d: 0.25
    update_min_a: 0.20

    resample_interval: 1

    transform_tolerance: 1.0

    laser_min_range: -1.0
    laser_max_range: 100.0
    max_beams: 60

    alpha1: 0.2
    alpha2: 0.2
    alpha3: 0.2
    alpha4: 0.2
    alpha5: 0.2

map_server:
  ros__parameters:
    use_sim_time: false
    yaml_filename: ""

lifecycle_manager_localization:
  ros__parameters:
    use_sim_time: false
    autostart: true
    node_names:
      - map_server
      - amcl

Important parameters:

| Parameter | Meaning | | ----------------- | ------------------------------------- | | global_frame_id | Usually map | | odom_frame_id | Usually odom | | base_frame_id | Usually base_link | | scan_topic | Usually scan or /scan | | min_particles | Minimum particle number | | max_particles | Maximum particle number | | update_min_d | Minimum travel distance before update | | update_min_a | Minimum rotation before update |


23. Create a localization launch file

Create:

nano launch/amcl_localization.launch.py

Paste:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

from launch_ros.actions import Node


def generate_launch_description():

    package_name = 'amr_localization'

    bringup_dir = get_package_share_directory('nav2_bringup')

    map_file = os.path.join(
        get_package_share_directory(package_name),
        'maps',
        'classroom_map.yaml'
    )

    params_file = os.path.join(
        get_package_share_directory(package_name),
        'config',
        'amcl_params.yaml'
    )

    localization_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(bringup_dir, 'launch', 'localization_launch.py')
        ),
        launch_arguments={
            'map': map_file,
            'use_sim_time': 'false',
            'params_file': params_file
        }.items()
    )

    rviz_node = Node(
        package='rviz2',
        executable='rviz2',
        name='rviz2',
        output='screen'
    )

    return LaunchDescription([
        localization_launch,
        rviz_node
    ])

This launch file starts localization using:

map_server
amcl
lifecycle_manager_localization
rviz2

24. Modify setup.py

Open:

nano setup.py

Add imports at the top:

import os
from glob import glob

Modify data_files:

data_files=[
    ('share/ament_index/resource_index/packages',
        ['resource/' + package_name]),
    ('share/' + package_name, ['package.xml']),
    (os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
    (os.path.join('share', package_name, 'config'), glob('config/*.yaml')),
    (os.path.join('share', package_name, 'maps'), glob('maps/*')),
    (os.path.join('share', package_name, 'rviz'), glob('rviz/*.rviz')),
],

25. Modify package.xml

Open:

nano package.xml

Add dependencies before the closing </package> tag:

<exec_depend>nav2_bringup</exec_depend>
<exec_depend>nav2_amcl</exec_depend>
<exec_depend>nav2_map_server</exec_depend>
<exec_depend>rviz2</exec_depend>
<exec_depend>tf2_tools</exec_depend>

26. Build the localization package

Run:

cd ~/ros2_ws
colcon build --packages-select amr_localization
source install/setup.bash

27. Run your localization launch file

Before launching localization, make sure your robot interface is running.

Check:

ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser

Then run:

ros2 launch amr_localization amcl_localization.launch.py

In RViz2:

Set Fixed Frame to map.
Add Map, LaserScan, TF, RobotModel, PoseArray, and Odometry.
Use 2D Pose Estimate to set the initial pose.

28. How Step 7 connects to Step 8

In Step 7, you localize the robot on a known map.

Now Nav2 can know:

Where the robot is in the map.

This gives the TF chain:

map β†’ odom β†’ base_link

In Step 8, you will use this localization result for path planning.

That means:

Step 7: Robot knows where it is.
Step 8: Robot plans how to reach a goal.

29. Common problems and solutions

Problem 1: /map does not appear

Possible reasons:

Map file path is wrong.
Map YAML file cannot find the PGM image.
map_server is not active.
localization launch failed.

Check:

ls ~/ros2_ws/maps
cat ~/ros2_ws/maps/classroom_map.yaml
ros2 topic list | grep map

Solution:

Use the correct full path to classroom_map.yaml.
Make sure the image path in YAML is correct.
Relaunch localization.

Problem 2: RViz2 shows β€œNo map received”

Possible reasons:

Map display topic is wrong.
Map server is not active.
RViz2 QoS setting is not compatible.

Solution:

Set Map topic to /map.
Check map_server terminal output.
In RViz2 Map display, try setting Durability Policy to Transient Local.

Problem 3: map β†’ odom does not exist

Possible reasons:

AMCL is not active.
Initial pose has not been set.
No /scan data is received.
Frame names are wrong.

Check:

ros2 lifecycle get /amcl
ros2 topic echo /scan --once
ros2 run tf2_ros tf2_echo map odom

Solution:

Set the initial pose in RViz2.
Check scan_topic.
Check frame names: map, odom, base_link.

Problem 4: Laser scan does not align with the map

Possible reasons:

Initial pose is wrong.
LiDAR static transform is wrong.
Map does not match the current environment.
Odometry is poor.
Robot moved too fast.

Solution:

Use 2D Pose Estimate again.
Check base_link β†’ base_laser.
Move slowly.
Use a better map.
Avoid dynamic obstacles.

Problem 5: Particles spread everywhere

Possible reasons:

Initial pose is very wrong.
Environment has repeated features.
Map is poor.
LiDAR scan quality is poor.
Odometry is unstable.

Solution:

Set a better initial pose.
Move the robot slowly.
Use a more complete map.
Improve odometry and sensor TF.

Problem 6: AMCL receives no scan

Check:

ros2 topic echo /scan --once

Possible reasons:

LiDAR driver is not running.
The scan topic name is different.
AMCL scan_topic parameter is wrong.

Solution:

Start the LiDAR driver.
Find the correct scan topic with ros2 topic list.
Update scan_topic in amcl_params.yaml.

Problem 7: Duplicate map β†’ odom

Problem:

SLAM and AMCL are both publishing map β†’ odom.

Solution:

Stop SLAM when using AMCL localization.
Use only one localization or mapping source for map β†’ odom.

30. Practice assignment

Assignment title

Localize an AMR on a Saved Map Using AMCL

Task 1: Prepare the saved map

Submit screenshots of:

ls ~/ros2_ws/maps

and:

cat ~/ros2_ws/maps/classroom_map.yaml

Task 2: Check required topics and TF

Submit screenshots of:

ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser

Task 3: Launch localization

Run:

ros2 launch nav2_bringup localization_launch.py \
  map:=$HOME/ros2_ws/maps/classroom_map.yaml \
  use_sim_time:=False

For simulation:

ros2 launch nav2_bringup localization_launch.py \
  map:=$HOME/ros2_ws/maps/classroom_map.yaml \
  use_sim_time:=True

Submit a screenshot of the localization terminal output.


Task 4: Set the initial pose

In RViz2:

Use 2D Pose Estimate.
Click the robot's approximate position.
Drag the arrow to set the robot direction.

Submit a screenshot showing the robot on the map.


Task 5: Check AMCL output

Submit screenshots of:

ros2 topic echo /particle_cloud --once
ros2 run tf2_ros tf2_echo map odom
ros2 run tf2_ros tf2_echo map base_link

Task 6: Generate the TF tree

Run:

ros2 run tf2_tools view_frames

Submit the TF tree screenshot.

Expected result:

map
 └── odom
      └── base_link
            └── base_laser

or, with the URDF model:

map
 └── odom
      └── base_link
            β”œβ”€β”€ base_laser
            β”œβ”€β”€ camera_link
            β”œβ”€β”€ imu_link
            β”œβ”€β”€ left_wheel_link
            └── right_wheel_link

Task 7: Explain the result

Write a short explanation:

The map file I used is:
The scan topic is:
The odometry topic is:
The AMCL particle topic is:
The global frame is:
The odometry frame is:
The robot base frame is:
The LiDAR frame is:
The transform published by AMCL is:
The transform published by odometry is:
After setting the initial pose, I observed:

31. Mini quiz

  1. What does localization mean?
  2. What does AMCL stand for?
  3. What is the difference between SLAM and AMCL?
  4. What map file does AMCL need?
  5. What sensor topic does AMCL usually need?
  6. What is the usual message type of /scan?
  7. What transform does AMCL usually publish?
  8. What transform should odometry publish?
  9. Why do you need to set the initial pose in RViz2?
  10. What topic receives the 2D Pose Estimate message?
  11. What topic shows AMCL particles?
  12. Why should you not run SLAM and AMCL together in this lesson?

32. Key summary

In this module, you learned how to localize a robot on a saved map using AMCL.

Before AMCL, you need:

saved map: classroom_map.yaml and classroom_map.pgm
LiDAR scan: /scan
odometry: /odom
TF: odom β†’ base_link
TF: base_link β†’ base_laser

AMCL uses:

map
LiDAR scan
odometry
TF
initial pose

AMCL provides:

map β†’ odom
/particle_cloud

After AMCL works, your TF tree becomes:

map
 └── odom
      └── base_link
            └── base_laser

With URDF, it becomes:

map
 └── odom
      └── base_link
            β”œβ”€β”€ base_laser
            β”œβ”€β”€ camera_link
            β”œβ”€β”€ imu_link
            β”œβ”€β”€ left_wheel_link
            └── right_wheel_link

The most important idea is:

SLAM builds the map.
AMCL localizes the robot on the map.
Nav2 uses this localization result for path planning and navigation.

In the next module, you will learn:

Nav2 path planning

That means the robot will use the map and localization result to plan a path from its current pose to a goal pose.

Tags