Skip to content

Commit e329aa3

Browse files
committed
docs: refocus layered map observation spec
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
1 parent 947c6f8 commit e329aa3

1 file changed

Lines changed: 84 additions & 87 deletions

File tree

Lines changed: 84 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,130 @@
11
[Draft for Discourse]
22

3-
# Layered Global Occupancy Map
3+
# Layered Global Map Observations
44

5-
This post describes the first implemented slice of a layered global occupancy map for the next generation Open-RMF prototype.
5+
This post describes the observation interface for contributing temporary map
6+
information to the next generation Open-RMF prototype.
67

7-
The goal of this slice is to let temporary local observations affect the global planning map without changing the path server interface. The path server can continue to consume a normal `nav_msgs/OccupancyGrid` from `/map`, while a new map server composes that grid from a static map and active observation layers.
8+
The goal is to let robots and perception systems publish what they currently
9+
observe without tying them to a specific central map implementation. The first
10+
prototype consumes sparse 2D occupancy regions because that is enough for the
11+
current global planning grid, but the interface is intentionally isolated in
12+
`rmf_layered_map_msgs` so richer representations, such as height-aware or voxel
13+
observations, can be added later.
814

915
# Quick Summary
1016

11-
* Robots and perception sources can publish sparse 2D region updates with a time-to-live (TTL)
12-
* A central layered map server composes a static map with active dynamic observations
13-
* Observations from multiple sources are stitched into one composed global grid
14-
* The composed output is published as `nav_msgs/OccupancyGrid` on `/map`
15-
* The observation messages live in `rmf_layered_map_msgs`, leaving `rmf_prototype_msgs` unchanged
16-
* The first implementation is a Rust package named `rmf_layered_map_server` under `map_server/`
17+
* Observation sources publish sparse temporary map patches
18+
* One message can contain both clear-space and occupied-space patches from the
19+
same sensor snapshot
20+
* Clear-space patches have TTLs, just like obstacle patches
21+
* A source can reset its previous observations without using a TTL
22+
* Robot-mounted sources can include the robot pose at observation time
23+
* The observation messages live in `rmf_layered_map_msgs`, leaving
24+
`rmf_prototype_msgs` unchanged
1725

18-
# Background
26+
# Observation Topic
1927

20-
The static map support in the prototype gives the path server a useful baseline: it already consumes a `nav_msgs/OccupancyGrid` from `/map` and can plan around occupied cells. This implementation preserves that contract. Instead of teaching the planner about every possible observation source, the layered map server publishes one composed `/map`.
28+
Observation sources publish dynamic map observations on:
2129

22-
Local observations from robots are different from static map data. They may describe temporary objects such as pedestrians, carts, doors held open, or equipment left in the path. For that reason the central map treats robot observations as dynamic layers that expire unless they are refreshed.
30+
* `/map/region_updates` - [`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg)
2331

24-
This implementation builds on the existing static map behavior.
32+
The topic is an event stream. Updates are not latched because expired
33+
observations should not be replayed to a restarted map service as if they were
34+
current.
2535

26-
# Interface Topology Overview
27-
28-
```mermaid
29-
flowchart LR
30-
static["Static map server"] -- "/map/static" --> layered["Layered map server"]
31-
observer["Observation source"] -- "/map/region_updates" --> layered
32-
layered -- "composed /map" --> path["Path server"]
33-
```
34-
35-
The layered map server owns the composed `/map` topic. When it is present, any static map publisher should be remapped to `/map/static`.
36-
37-
The path server continues to subscribe to `/map`. It does not subscribe directly to local robot observations. This keeps map composition independent from multi-agent planning and allows alternative map server implementations to be swapped in later.
38-
39-
Each observation stream publishes updates with a unique `source_id`, such as `robot_1/local_costmap` or `robot_2/lidar_obstacles`. The layered map server tracks these sources independently but composes their active observations into a single `OccupancyGrid`.
40-
41-
# Observation Topics
42-
43-
* `/map/region_updates` - [`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg): Dynamic local observation updates
44-
45-
The topic is treated as an event stream. Updates are not latched because expired observations should not be replayed to a restarted map server as if they were current.
36+
Each observation stream should use a stable `source_id`, such as
37+
`robot_1/local_costmap`, `robot_1/front_lidar`, or `door_sensor/west_lobby`.
38+
The map service tracks each source independently so one robot can update or
39+
reset its own temporary observations without disturbing observations from other
40+
sources.
4641

4742
# Messages
4843

4944
## `MapObservationSource`
5045

51-
[`MapObservationSource.msg`](../rmf_layered_map_msgs/msg/MapObservationSource.msg) describes where an observation came from.
46+
[`MapObservationSource.msg`](../rmf_layered_map_msgs/msg/MapObservationSource.msg)
47+
describes where an observation came from.
5248

5349
Important fields:
5450

55-
* `source_id`: stable source identifier, usually the robot namespace plus the local source name
56-
* `robot_name`: robot that produced the observation, if the source is mounted on a robot. This can be empty for fixed sensors or synthetic layers
57-
* `map_name`: map or level that the observation belongs to
58-
* `frame_id`: coordinate frame for the regions
59-
* `stamp`: time when the observation snapshot was produced
60-
* `default_ttl`: fallback TTL for updates from this source
51+
* `header`: frame and timestamp for the observed regions
52+
* `source_id`: stable source identifier, usually the robot namespace plus the
53+
local source name
54+
* `robot_name`: robot that produced this observation, if the source is mounted
55+
on a robot. This can be empty for fixed sensors or synthetic map layers
56+
* `map_name`: map or level that the observations belong to
57+
* `robot_pose`: robot pose when the observation was produced, so consumers do
58+
not need to reconstruct this from a separate TF-like lookup
59+
* `default_ttl_sec`: fallback TTL in seconds for patches from this source
6160

6261
## `MapRegionUpdate`
6362

64-
[`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg) describes one sparse update from a local observation source.
63+
[`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg)
64+
describes one observation snapshot from a source.
6565

66-
Update types:
67-
68-
* `UPDATE_OBSTACLE`: regions are temporary occupied space
69-
* `UPDATE_CLEAR`: regions are temporary free space
70-
* `UPDATE_RESET`: previous observations from the same source should be removed
71-
72-
The first implementation uses `rmf_prototype_msgs/Region` for 2D geometry. This keeps the message sparse and lets the central map server rasterize regions into its composed `OccupancyGrid`.
73-
74-
The map observation messages live in a dedicated `rmf_layered_map_msgs` package. This keeps `rmf_prototype_msgs` as a stable public API surface while allowing the layered-map interface to evolve in its own package.
66+
Important fields:
7567

76-
# Layered Map Server
68+
* `source`: metadata for the observation source
69+
* `reset_source`: remove active observations from the same `source_id` and
70+
`map_name` before applying the new patches
71+
* `patches`: ordered clear-space and occupied-space patches from this snapshot
7772

78-
The first server implementation is a Rust package named `rmf_layered_map_server` under the `map_server/` directory.
73+
`reset_source` is useful when a source publishes replacement snapshots, changes
74+
maps, shuts down, or knows its previous observation state is no longer valid. It
75+
does not have a TTL because it is a bookkeeping operation, not an active map
76+
observation.
7977

80-
Inputs:
78+
## `MapRegionPatch`
8179

82-
* `/map/static`: `nav_msgs/OccupancyGrid`, transient local, reliable
83-
* `/map/region_updates`: `rmf_layered_map_msgs/MapRegionUpdate`, reliable
80+
[`MapRegionPatch.msg`](../rmf_layered_map_msgs/msg/MapRegionPatch.msg)
81+
describes one group of regions with the same action and TTL.
8482

85-
Output:
83+
Patch types:
8684

87-
* `/map`: composed `nav_msgs/OccupancyGrid`, transient local, reliable
85+
* `UPDATE_CLEAR`: regions are temporary free space
86+
* `UPDATE_OBSTACLE`: regions are temporary occupied space
8887

89-
The server keeps the static grid separate from active dynamic observations:
88+
Clear patches and obstacle patches both require a TTL because both describe
89+
temporary observations. If a patch TTL is zero or negative, the source
90+
`default_ttl_sec` is used. If that is also zero or negative, the map service's
91+
configured default TTL is used.
9092

91-
```text
92-
LayeredMap
93-
static_grid: OccupancyGrid
94-
dynamic_observations: Vec<DynamicObservation>
95-
revision: u64
93+
When a sensor snapshot includes both clear and occupied regions, publish them in
94+
one `MapRegionUpdate` message. The map service applies clear patches before
95+
obstacle patches, which gives deterministic "clear then mark" behavior without
96+
relying on the arrival order of separate ROS messages.
9697

97-
DynamicObservation
98-
source_id: String
99-
map_name: String
100-
update_type: obstacle | clear
101-
occupancy_value: i8
102-
regions: Vec<Region>
103-
expires_at: Time
104-
```
98+
The first prototype uses `rmf_prototype_msgs/Region` for sparse 2D geometry so
99+
robots can publish compact patches. The central map service is responsible for
100+
rasterizing those regions into its internal representation.
105101

106-
Composition rules:
102+
# Example Flow
107103

108-
1. Start from the latest static grid.
109-
2. Drop expired dynamic observations before composing.
110-
3. Rasterize active clear regions to free space.
111-
4. Rasterize active obstacle regions to occupied space.
112-
5. Active obstacle observations win over active clear observations for the same cell.
113-
6. Unknown static cells stay unknown unless an active clear or obstacle observation covers them.
104+
A local costmap or LiDAR observation node can publish a replacement snapshot by:
114105

115-
`UPDATE_RESET` removes observations from the same source and map without disturbing observations from other robots. For example, if `robot_1/local_costmap` resets its obstacle layer, active observations from `robot_2/local_costmap` remain in the composed grid.
106+
1. Stamping the observation source with the sensor frame and observation time.
107+
2. Setting `reset_source` if the new snapshot replaces the source's previous
108+
temporary observations.
109+
3. Adding one or more clear patches for free space seen by the sensor.
110+
4. Adding one or more obstacle patches for occupied space seen by the sensor.
111+
5. Giving each patch a TTL long enough to survive normal publication jitter but
112+
short enough to decay when the observation is no longer refreshed.
116113

117-
The server does not use the full `-/errors` component pattern from the broader topic taxonomy. For transient observations, TTL expiration is the primary recovery mechanism.
114+
The map service should ignore snapshots from a source if their timestamp is
115+
older than a newer snapshot that has already been accepted. This prevents a late
116+
clear/reset message from removing obstacle information that came from a newer
117+
observation.
118118

119119
# Implemented Test Coverage
120120

121-
The Rust tests cover:
121+
The first server tests cover:
122122

123-
* composing obstacle regions over a static map
123+
* composing obstacle regions over a static planning grid
124124
* point regions with non-zero map origins and non-1.0 resolutions
125125
* polygon regions, out-of-bounds regions, and malformed region point arrays
126126
* pruning expired observations by TTL
127-
* active obstacle observations winning over active clear observations
127+
* clear and obstacle patches in the same update
128+
* late older snapshots being ignored
128129
* reset updates removing observations from the same source and map
129130
* multiple robot sources being stitched into one composed grid
130-
131-
The `rmf_layered_map_server_demo` package includes a small publisher that sends a static map and a temporary obstacle update. Its launch file starts the layered map server and RViz on `/map`, so developers can see the dynamic layer appear and decay without needing the full Nav2 or replanning loop.
132-
133-
The `rmf_layered_map_server_test` package adds a launch test for the ROS topic contract: `/map/static` and `/map/region_updates` are consumed, `/map` is published, reset updates remove active observations, and obstacles are removed after TTL expiry.

0 commit comments

Comments
 (0)