Sim map layers

The map on /sim/[robotId] is in-browser simulated mapping. Planar range rays are cast from the robot pose against the sim's static obstacles. This path does not open a ROS connection.

OccupancyGrid

MapSource exposes grids in nav_msgs/OccupancyGrid shape:

  • resolution — metres per cell
  • origin — pose of cell (0, 0), position plus quaternion
  • width, height
  • data — row-major int8, index iy * width + ix
    • -1 unknown
    • 0 free
    • 100 occupied
    • 1..99 partially observed

InBrowserMapSource fills that grid from a 360° scan. Ray count and max range are configurable. Each ray is a Bresenham-style cell walk (Amanatides–Woo DDA). Cells the ray crosses are misses. The first cell that overlaps an obstacle is a hit. Hits and misses accumulate as log-odds, once per cell per scan. The grid is integrated at 8 Hz, clamped to 5–10 Hz. observe still records the pose trail on frames that skip the scan.

Costmaps

Both costmaps use the same OccupancyGrid shape.

  • Global — the whole grid. Cells with occupancy >= 65 are obstacles. Distance is centre-to-centre. Cost is 100 inside the robot radius, then falls linearly to 0 at robot radius + inflation radius. Unknown cells outside that reach stay -1. Known free cells outside it stay 0.
  • Local — a rolling window, 4 m × 4 m by default, cropped from the global costmap and centred on the robot cell. Cells outside the global grid are unknown.

Paths and overhead

getPath().trail is the travelled pose trail. setPlan(poses | null) stores a planner path for a later planner; this mapper does not compute one.

The overhead layer is a top-down colouring of the same grid: the floor plus obstacle footprints.

The MAP panel toggles visibility and opacity for occupancy, global costmap, local costmap, paths, and overhead. Those settings persist for the browser session. The meshes sit a few millimetres above the floor in the three.js scene.

The panel is the map slot in the sim workbench left column (lib/sim/workbench), ordered before telemetry. Layouts that hide the panel column hide this panel with the others. The meshes stay in the viewport.

Save map

Save map downloads map.pgm (P5 binary) and map.yaml in map_server form:

image: map.pgm
resolution: 0.050000
origin: [-5.000000, -5.000000, 0.000000]
negate: 0
occupied_thresh: 0.650000
free_thresh: 0.196000

origin is [x, y, yaw] in metres and radians. The PGM's first row is the highest map y. Pixel 0 is occupied, 254 is free, and 205 is unknown. readMapServer parses that pair. Values 1..99 are stored as that pixel so a file written here round-trips. A classic trinary map only uses 0, 205, and 254. Pixel 205 stays unknown when free_thresh is 0.25.

Rosbridge stub

A future source can implement MapSource by subscribing to these topics. RosbridgeMapSource is only that shape: ingest copies a decoded nav_msgs/OccupancyGrid JSON object. It does not open a socket, subscribe, or apply TF.

Topic Message
/map nav_msgs/OccupancyGrid
/global_costmap/costmap nav_msgs/OccupancyGrid
/local_costmap/costmap nav_msgs/OccupancyGrid
interface RosbridgeOccupancyGridMsg {
  header: { stamp: { sec: number; nanosec: number }; frame_id: string };
  info: {
    map_load_time: { sec: number; nanosec: number };
    resolution: number;
    width: number;
    height: number;
    origin: {
      position: { x: number; y: number; z: number };
      orientation: { x: number; y: number; z: number; w: number };
    };
  };
  data: number[]; // int8, length width * height
}

observe on the stub only records the pose trail. The grids come from ingest.