Mastering Nav2 Costmaps in ROS 2: Static, Obstacle, and Inflation Layers Explained

When we move from simple robot experiments to real-world navigation, the environment is no longer an abstract concept. A robot needs to understand where it can move, where it cannot, and how to plan paths that are not only valid but also safe and efficient. This is where costmaps come into play in ROS 2 Navigation (Nav2).

A costmap is essentially a grid-based representation of the environment, where each cell carries a “cost” that indicates how desirable or undesirable it is for the robot to step into it. Free space has low cost, obstacles have high cost, and unsafe zones around obstacles are inflated to discourage risky paths. By combining different layers of information, costmaps provide a comprehensive, real-time view of the robot’s surroundings.

 

In this article, we will dive into the theory behind costmaps and then build a full Nav2 costmap configuration using three essential layers: the static layer, the obstacle layer, and the inflation layer. At the end, you will have a working configuration file, fully explained line by line, that you can plug into your robot’s navigation system.

🤔 Understanding Costmaps in ROS 2

At its core, a costmap is a 2D grid laid over the robot’s environment. Each cell in this grid contains an integer value:

  • 0 represents free space

  • 100 represents an obstacle

  • values in between represent inflated or uncertain zones

This grid allows Nav2 to perform collision checking and path planning efficiently. Rather than reasoning about the entire geometry of the robot and the environment, the system just checks the costs of cells and looks for low-cost paths.

🗺️ Global and Local Costmaps

In Nav2, we typically define two separate costmaps. The global costmap provides a large-scale, static view of the environment, usually aligned with the map, and is used for long-term path planning. The local costmap instead provides a smaller, robot-centered view, updated at a higher frequency, and accounts for dynamic obstacles, enabling local replanning and obstacle avoidance.

By combining the two, the robot can plan safe long-distance routes while also reacting in real-time to sudden changes in the environment.

Static Layer: Integrating the Map

The static layer imports information from a pre-built occupancy grid map, usually the one generated by SLAM or mapping. By subscribing to /map, it guarantees that the costmap reflects static structures like walls and corridors.

🚧 Obstacle Layer: Detecting What’s Around

The obstacle layer integrates sensor data from LiDAR or depth cameras to detect obstacles in real-time. It listens to topics like /scan and constantly updates the costmap, ensuring dynamic objects are considered.

🚦 Inflation Layer: Creating Safe Navigation Zones

The inflation layer expands the occupied areas detected in the costmap, effectively creating a buffer zone around obstacles. This prevents the robot from planning paths that pass dangerously close to walls or objects. Parameters like inflation_radius and cost_scaling_factor determine how wide and steep this buffer is.

</> Practical Implementation: Configuring Nav2 Costmaps

We are now ready to implement a working costmap in ROS 2. Costmaps are configured using YAML parameter files. Let’s create a file called costmap_params.yaml inside the config folder of our navigation package.

Here is the complete configuration file:

				
					# costmap_params.yaml
nav2_costmap_2d:
  global_costmap:
    ros__parameters:
      global_frame: map
      robot_base_frame: base_link
      update_frequency: 10.0
      publish_frequency: 10.0
      resolution: 0.05
      width: 10
      height: 10
      origin_x: 0.0
      origin_y: 0.0
      plugins: ["static_layer", "obstacle_layer", "inflation_layer"]

      static_layer:
        plugin: "nav2_costmap_2d::StaticLayer"
        map_subscribe_transient_local: true

      obstacle_layer:
        plugin: "nav2_costmap_2d::ObstacleLayer"
        observation_sources: laser_scan_sensor
        laser_scan_sensor:
          topic: /scan
          clearing: true
          marking: true
          data_type: "LaserScan"
          inf_is_valid: true

      inflation_layer:
        plugin: "nav2_costmap_2d::InflationLayer"
        inflation_radius: 0.55
        cost_scaling_factor: 10.0

				
			

🧠 Breaking Down the Code

We now break down the file, section by section, and explain what each parameter does.

Declaring the costmap section
				
					nav2_costmap_2d:
  global_costmap:
    ros__parameters:

				
			

This opens the costmap configuration inside the nav2_params file. The section ros__parameters will contain all the parameters that define how the costmap works.

Time and reference frames
				
					      use_sim_time: true
      global_frame: map
      robot_base_frame: base_link

				
			

Here we enable simulation time (use_sim_time: true) so the costmap synchronizes with Gazebo or another simulator. The global_frame is set to map so all obstacle positions are relative to the global map. The robot_base_frame indicates the robot’s main reference frame, usually base_link.

Frequencies and resolution
				
					      update_frequency: 5.0
      publish_frequency: 2.0
      resolution: 0.05
				
			

The update frequency determines how often the costmap integrates new sensor data (in Hz), while the publish frequency sets how often the costmap is broadcast to other nodes. The resolution defines the grid cell size in meters — in this case, each cell is 5 cm.

Size and origin
				
					      width: 10.0
      height: 10.0
      origin_x: -5.0
      origin_y: -5.0

				
			

These parameters configure the costmap’s size in meters and where it is centered. A width and height of 10 meters with origin at -5,-5 places the costmap symmetrically around the robot.

Adding the static layer
				
					      plugins:
        - {name: static_layer, type: "nav2_costmap_2d::StaticLayer"}
      static_layer:
        map_subscribe_transient_local: true

				
			

The static layer plugin loads the environment map. The option map_subscribe_transient_local ensures the costmap always receives the map even if it was published before the costmap started.

Adding the obstacle layer
				
					        - {name: obstacle_layer, type: "nav2_costmap_2d::ObstacleLayer"}
      obstacle_layer:
        observation_sources: scan

				
			

This section enables the obstacle layer. It subscribes to one or more sensor topics, in this case just scan, which is usually the LiDAR data.

				
					        scan:
          topic: /scan
          max_obstacle_height: 2.0
          clearing: true
          marking: true

				
			

The obstacle data comes from /scan. Any obstacle taller than 2 meters is ignored. Marking adds detected obstacles into the costmap, while clearing removes them when the sensor no longer detects them.

Adding the inflation layer
				
					        - {name: inflation_layer, type: "nav2_costmap_2d::InflationLayer"}
      inflation_layer:
        inflation_radius: 0.55
        cost_scaling_factor: 10.0

				
			

The inflation layer creates a safety buffer around obstacles. The inflation_radius determines how far costs spread, and the cost_scaling_factor adjusts how quickly the costs decay with distance from obstacles.

🚀 Running the Costmap

With the configuration in place, we can build and run the navigation stack.

				
					cd ~/arduinobot_ws
colcon build
source install/setup.bash
ros2 launch nav2_bringup bringup_launch.py params_file:=/path/to/costmap_params.yaml

				
			

Opening RViz will show the costmap overlayed on the map. Obstacles will appear as blocked areas, and inflated zones will surround them as gray gradients, making it clear where the robot can move safely.

🎯 Conclusion

Costmaps are a fundamental part of robot navigation in ROS 2. They combine static knowledge from maps, dynamic perception from sensors, and safety margins from inflation, resulting in a layered representation of the environment. In this article, we explored the theory behind costmaps, then implemented a complete configuration with static, obstacle, and inflation layers, and finally explained each section of the code in detail.

With this setup, your robot can plan paths that are not only feasible but also safe, handling both static maps and dynamic environments.

Want to learn more?

Discover how to design, build, and use costmaps in real robotic systems in the "Self Driving and ROS 2 - Learn by doing! Plan & Navigation" course
DISCOUNT
en_US

Discover BumperBot Kit

Assemble your robot and get started to learn Robotics!