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:
- Explain what localization means.
- Explain what AMCL does.
- Explain the difference between SLAM and localization.
- Load a saved occupancy grid map.
- Launch AMCL localization.
- Set the robot initial pose in RViz2.
- Check whether
map β odomis published. - Check
/map,/scan,/odom,/tf, and/particle_cloud. - Understand why AMCL is important before Nav2 path planning.
- 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
- What does localization mean?
- What does AMCL stand for?
- What is the difference between SLAM and AMCL?
- What map file does AMCL need?
- What sensor topic does AMCL usually need?
- What is the usual message type of
/scan? - What transform does AMCL usually publish?
- What transform should odometry publish?
- Why do you need to set the initial pose in RViz2?
- What topic receives the 2D Pose Estimate message?
- What topic shows AMCL particles?
- 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.