Aerostack2

DocsPlugins

Map server

Build a 2D occupancy grid from a lidar scan, a depth camera or a point cloud.

Authored by
  • @alvgaona

The map server builds a 2D occupancy grid of the world from the drone's sensors, for the path planner to plan on. Its plugin turns a laser scan into the grid, and two launch presets add a converter in front of it for depth cameras and point clouds.

plugin_nameBuilds the map from
scan2occ_gridA laser scan on sensor_measurements/lidar/scan
depth2occ_gridA depth image, converted to a scan first
point_cloud2occ_gridA point cloud, converted to a scan first

Every option publishes the same two grids in the drone's namespace:

TopicContentsUsed by
mapThe grid as measuredThe a_star planner
map_filteredThe grid with small specks inside free space cleared. Confirmed obstacles are keptThe voronoi planner

The map server needs the transform from earth to the sensor's frame, so the state estimator has to be running. Scans whose transform isn't available in time are dropped.

Running it

ros2 launch as2_map_server map_server.launch.py \
  namespace:=drone0 \
  plugin_name:=scan2occ_grid

Each option has its default configuration in the package. A file given as plugin_config_file is merged over it, so it only needs the values you change. Each value can also be passed as a launch argument, such as map_width:=600. Start the node through these launch files, since it has no usable defaults of its own for the size, the resolution and the range.

Scan to occupancy grid

scan2occ_grid traces every ray of the scan in the earth frame. Values go from 0 for free to 100 for occupied, with -1 for cells never seen:

  • The first ray through an unknown cell marks it free, at 0.
  • Each later ray through a cell removes miss_confidence.
  • Each hit adds hit_confidence. With the defaults, one hit takes a cell to about 40 and three hits take it to 100.
  • A cell that goes above 80 stays occupied for good.

Ranges beyond scan_range_max, and readings with no return, are cut to that length and count as free space.

The grid has a fixed size, centered on the origin of earth, and doesn't follow the drone. Hits outside it are dropped, and the planner can't plan to a goal outside it, so make it large enough for the whole flight area. Heights are ignored, since the grid is 2D.

ParameterDefaultWhat it does
map_resolution0.1The size of a cell, in meters
map_width300The width of the grid, in cells
map_height300The height of the grid, in cells
scan_range_max30.0Longer ranges are cut to this length, in meters
hit_confidence40How much a hit adds to a cell
miss_confidence10How much a ray passing through removes from a cell

The defaults give a 30 by 30 m map, 15 m to each side of the origin.

Depth camera and point cloud

depth2occ_grid and point_cloud2occ_grid are launch presets, not separate plugins. Each starts a ROS converter that turns its input into a laser scan on sensor_measurements/lidar/scan. It then starts the map server with scan2occ_grid.

PresetConverterInput topics, as launch arguments
depth2occ_griddepthimage_to_laserscandepth_topic_in, default sensor_measurements/front_camera/depth, and depth_camera_info_topic_in, default sensor_measurements/front_camera/camera_info
point_cloud2occ_gridpointcloud_to_laserscancloud_topic_in, default sensor_measurements/lidar/cloud

Set the input topics on the launch line, since they aren't read from the configuration file:

ros2 launch as2_map_server map_server.launch.py \
  namespace:=drone0 \
  plugin_name:=point_cloud2occ_grid \
  cloud_topic_in:=sensor_measurements/lidar_3d/points

The configuration file holds the converter's parameters next to the scan2occ_grid ones. Both converters cut ranges at 10 m by default. The point cloud preset keeps points between min_height 0.0 and max_height 1.0, measured in the cloud's own frame rather than above the ground.

The default depth camera configuration sets the scan's frame to drone0/front_camera/rgbd_camera. For another drone or camera, set output_frame in your own file.

Last updated on November 3, 2026

On this page

Last updated on November 3, 2026