Skip to content

Commit cef7fc2

Browse files
arjo129xiyuoh
andauthored
feat:Add a very rudimentary way of loading maps (#22)
Related to #12 ### Implemented feature Adds a very rudimentary way of loading environment maps. This is by no means a final approach to loading maps, but it provides a high level way of representing them for the time being. ### Implementation description We subscribe to the `/map` topic to listen for OccupancyGrids. It gets converted internally to a `Map` struct ### Test it Use the launch file provided by `demo_map.launch.py` . It spins up a ROS 2 `map_server` loaded with a custom 20x20 grid map (`demo_grid.png`) and activates it. The dashboard subscribes to `/map` and visualizes it as a background layer. Run the launch file: ```bash ros2 launch rmf_path_server_demo demo_map.launch.py ``` - Open `http://localhost:8080` in your web browser. - The canvas will automatically fit to the 20x20 grid map. - You can add robots and set goals as in the default mode, and see the map obstacles (a box in the center) drawn on the canvas. ### GenAI Use We follow [OSRA's policy on GenAI tools](https://github.com/openrobotics/osrf-policies-and-procedures/blob/main/OSRF%20Policy%20on%20the%20Use%20of%20Generative%20Tools%20(%E2%80%9CGenerative%20AI%E2%80%9D)%20in%20Contributions.md) - [x] I used a GenAI tool in this PR. - [ ] I did not use GenAI Generated-by: Gemini Pro 3.1 --------- Signed-off-by: Arjo Chakravarty <arjoc@intrinsic.ai> Signed-off-by: Arjo Chakravarty <arjo129@gmail.com> Co-authored-by: Xiyu <ohxiyu@gmail.com>
1 parent 03df998 commit cef7fc2

13 files changed

Lines changed: 610 additions & 73 deletions

File tree

path_server/rmf_path_server/src/lib.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,23 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
1514
use mapf_post::{na::Isometry2, MapfResult, SemanticPlan, SemanticWaypoint};
1615
use rclrs::{IntoPrimitiveOptions, Node};
17-
use ros_env::builtin_interfaces;
18-
use ros_env::nav_msgs::msg::Odometry;
19-
use ros_env::rmf_prototype_msgs;
20-
use ros_env::rmf_prototype_msgs::msg::{Destination, Plan, PlanId, TrafficDependency, Waypoint};
21-
use std::collections::{hash_map::Entry, HashMap};
22-
use std::sync::Arc;
16+
use ros_env::{
17+
builtin_interfaces,
18+
nav_msgs::msg::{OccupancyGrid, Odometry},
19+
rmf_prototype_msgs::{
20+
self,
21+
msg::{Destination, Plan, PlanId, TrafficDependency, Waypoint},
22+
},
23+
};
24+
use std::{
25+
collections::{hash_map::Entry, HashMap},
26+
sync::Arc,
27+
};
2328

2429
pub mod planner;
25-
pub use planner::{MapfPlanner, MockPlanner, PibtPlanner};
30+
pub use planner::{Map, MapfPlanner, MockPlanner, PibtPlanner};
2631

2732
pub struct PlanSuccess {
2833
pub session_id: u64,
@@ -53,6 +58,7 @@ pub struct PlanServer<P: MapfPlanner> {
5358
pub current_planning_session: Option<u64>,
5459
pub footprints: Arc<std::sync::Mutex<HashMap<String, f32>>>,
5560
pub active_plan_ids: HashMap<String, PlanId>,
61+
pub map: Arc<Map>,
5662
}
5763

5864
impl<P: MapfPlanner> PlanServer<P> {
@@ -77,6 +83,7 @@ impl<P: MapfPlanner> PlanServer<P> {
7783
current_planning_session: None,
7884
footprints,
7985
active_plan_ids: HashMap::new(),
86+
map: Arc::new(Map::default()),
8087
}
8188
}
8289

@@ -322,6 +329,7 @@ impl<P: MapfPlanner> PlanServer<P> {
322329
let planner_clone = Arc::clone(&self.planner);
323330
let footprints_clone = Arc::clone(&self.footprints);
324331
let sender_clone = self.plan_sender.clone();
332+
let map_clone = self.map.clone();
325333

326334
std::thread::spawn(move || {
327335
if cancellation.load(std::sync::atomic::Ordering::Relaxed) {
@@ -351,6 +359,7 @@ impl<P: MapfPlanner> PlanServer<P> {
351359
&goals,
352360
&footprints_map,
353361
&robot_ids,
362+
map_clone.as_ref(),
354363
Arc::clone(&cancellation),
355364
) {
356365
Ok(plan) => plan,
@@ -489,6 +498,7 @@ pub struct PathServerRunning<P: MapfPlanner> {
489498
rclrs::WorkerSubscription<rmf_prototype_msgs::msg::ParticipantList, DiscoveryServer<P>>,
490499
pub discovery_subscription:
491500
rclrs::WorkerSubscription<rmf_prototype_msgs::msg::ParticipantList, DiscoveryServer<P>>,
501+
pub map_subscription: rclrs::WorkerSubscription<OccupancyGrid, PlanServer<P>>,
492502
}
493503

494504
pub fn start_path_server<P: MapfPlanner + 'static>(
@@ -502,6 +512,14 @@ pub fn start_path_server<P: MapfPlanner + 'static>(
502512
let destinations_worker =
503513
node.create_worker(PlanServer::new(node.clone(), planner, footprints));
504514

515+
let map_subscription = destinations_worker.create_subscription::<OccupancyGrid, _>(
516+
"/map".transient_local().reliable(),
517+
move |server: &mut PlanServer<P>, msg: OccupancyGrid| {
518+
rclrs::log!(server.node.logger(), "Received map message");
519+
server.map = Arc::new(Map { grid: msg });
520+
},
521+
)?;
522+
505523
// Create a periodic timer on the Destinations worker to trigger replans asynchronously.
506524
let replan_timer = destinations_worker.create_timer_repeating(
507525
std::time::Duration::from_millis(100),
@@ -611,5 +629,6 @@ pub fn start_path_server<P: MapfPlanner + 'static>(
611629
replan_timer: Box::new(replan_timer),
612630
list_subscription,
613631
discovery_subscription,
632+
map_subscription,
614633
})
615634
}

path_server/rmf_path_server/src/planner.rs

Lines changed: 87 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414

1515
use hetpibt::external_tracks_pibt::PiBTWithExternalTracks;
1616
use mapf_post::na::Isometry2;
17-
use ros_env::nav_msgs::msg::Odometry;
17+
use ros_env::nav_msgs::msg::{OccupancyGrid, Odometry};
1818
use ros_env::rmf_prototype_msgs::msg::Destination;
1919
use std::collections::HashMap;
2020

2121
use std::sync::atomic::AtomicBool;
2222
use std::sync::Arc;
2323

24+
#[derive(Clone, Debug, Default)]
25+
pub struct Map {
26+
pub grid: OccupancyGrid,
27+
}
28+
2429
/// Implement this trait to use your own custom MAPF
2530
/// planner. The planner in this scenario will take in
2631
/// starts and goals and assign a trajectory to the agents.
@@ -31,6 +36,7 @@ pub trait MapfPlanner: Send + Sync + 'static {
3136
goals: &HashMap<String, Destination>,
3237
footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
3338
robot_ids: &[String],
39+
map: &Map,
3440
cancellation: Arc<AtomicBool>,
3541
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>>;
3642
}
@@ -45,6 +51,7 @@ impl MapfPlanner for MockPlanner {
4551
_goals: &HashMap<String, Destination>,
4652
_footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
4753
robot_ids: &[String],
54+
_map: &Map,
4855
_cancellation: Arc<AtomicBool>,
4956
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>> {
5057
let mut plan = Vec::new();
@@ -79,74 +86,96 @@ impl MapfPlanner for PibtPlanner {
7986
goals: &HashMap<String, Destination>,
8087
_footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
8188
robot_ids: &[String],
89+
map: &Map,
8290
_cancellation: Arc<AtomicBool>,
8391
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>> {
8492
if robot_ids.is_empty() {
8593
return Ok(Vec::new());
8694
}
8795

88-
let mut min_x = f32::MAX;
89-
let mut min_y = f32::MAX;
90-
let mut max_x = f32::MIN;
91-
let mut max_y = f32::MIN;
92-
93-
for id in robot_ids {
94-
if let Some(odom) = starts.get(id) {
95-
let x = odom.pose.pose.position.x as f32;
96-
let y = odom.pose.pose.position.y as f32;
97-
if x < min_x {
98-
min_x = x;
99-
}
100-
if y < min_y {
101-
min_y = y;
102-
}
103-
if x > max_x {
104-
max_x = x;
105-
}
106-
if y > max_y {
107-
max_y = y;
96+
let use_map =
97+
map.grid.info.width > 0 && map.grid.info.height > 0 && map.grid.info.resolution > 0.0;
98+
99+
let (width, height, resolution, offset_x, offset_y, grid) = if use_map {
100+
let w = map.grid.info.width as usize;
101+
let h = map.grid.info.height as usize;
102+
let r = map.grid.info.resolution;
103+
let ox = map.grid.info.origin.position.x as f32;
104+
let oy = map.grid.info.origin.position.y as f32;
105+
106+
let mut g = vec![vec![0; h]; w];
107+
for x in 0..w {
108+
for y in 0..h {
109+
let ros_val = map.grid.data[y * w + x];
110+
g[x][y] = if ros_val > 50 || ros_val == -1 { 1 } else { 0 };
108111
}
109112
}
110-
if let Some(dest) = goals.get(id) {
111-
if let Some(region) = dest.constraints.regions.first() {
112-
if region.region.points.len() >= 2 {
113-
let x = region.region.points[0];
114-
let y = region.region.points[1];
115-
if x < min_x {
116-
min_x = x;
117-
}
118-
if y < min_y {
119-
min_y = y;
120-
}
121-
if x > max_x {
122-
max_x = x;
123-
}
124-
if y > max_y {
125-
max_y = y;
113+
(w, h, r, ox, oy, g)
114+
} else {
115+
let mut min_x = f32::MAX;
116+
let mut min_y = f32::MAX;
117+
let mut max_x = f32::MIN;
118+
let mut max_y = f32::MIN;
119+
120+
for id in robot_ids {
121+
if let Some(odom) = starts.get(id) {
122+
let x = odom.pose.pose.position.x as f32;
123+
let y = odom.pose.pose.position.y as f32;
124+
if x < min_x {
125+
min_x = x;
126+
}
127+
if y < min_y {
128+
min_y = y;
129+
}
130+
if x > max_x {
131+
max_x = x;
132+
}
133+
if y > max_y {
134+
max_y = y;
135+
}
136+
}
137+
if let Some(dest) = goals.get(id) {
138+
if let Some(region) = dest.constraints.regions.first() {
139+
if region.region.points.len() >= 2 {
140+
let x = region.region.points[0];
141+
let y = region.region.points[1];
142+
if x < min_x {
143+
min_x = x;
144+
}
145+
if y < min_y {
146+
min_y = y;
147+
}
148+
if x > max_x {
149+
max_x = x;
150+
}
151+
if y > max_y {
152+
max_y = y;
153+
}
126154
}
127155
}
128156
}
129157
}
130-
}
131158

132-
if min_x == f32::MAX {
133-
min_x = 0.0;
134-
min_y = 0.0;
135-
max_x = 0.0;
136-
max_y = 0.0;
137-
}
159+
if min_x == f32::MAX {
160+
min_x = 0.0;
161+
min_y = 0.0;
162+
max_x = 0.0;
163+
max_y = 0.0;
164+
}
138165

139-
let padding = 10.0;
140-
let offset_x = min_x.floor() - padding;
141-
let offset_y = min_y.floor() - padding;
166+
let padding = 10.0;
167+
let ox = min_x.floor() - padding;
168+
let oy = min_y.floor() - padding;
142169

143-
let width = (max_x.ceil() - offset_x + padding) as usize;
144-
let height = (max_y.ceil() - offset_y + padding) as usize;
170+
let w = (max_x.ceil() - ox + padding) as usize;
171+
let h = (max_y.ceil() - oy + padding) as usize;
145172

146-
let width = width.max(1);
147-
let height = height.max(1);
173+
let w = w.max(1);
174+
let h = h.max(1);
148175

149-
let grid = vec![vec![0; height]; width];
176+
let g = vec![vec![0; h]; w];
177+
(w, h, 1.0f32, ox, oy, g)
178+
};
150179

151180
let mut grid_starts = Vec::new();
152181
let mut grid_ends = Vec::new();
@@ -165,8 +194,8 @@ impl MapfPlanner for PibtPlanner {
165194
))
166195
})?;
167196

168-
let sx = (odom.pose.pose.position.x as f32 - offset_x).round() as usize;
169-
let sy = (odom.pose.pose.position.y as f32 - offset_y).round() as usize;
197+
let sx = ((odom.pose.pose.position.x as f32 - offset_x) / resolution).round() as usize;
198+
let sy = ((odom.pose.pose.position.y as f32 - offset_y) / resolution).round() as usize;
170199

171200
let gx_f32;
172201
let gy_f32;
@@ -188,8 +217,8 @@ impl MapfPlanner for PibtPlanner {
188217
)));
189218
}
190219

191-
let gx = (gx_f32 - offset_x).round() as usize;
192-
let gy = (gy_f32 - offset_y).round() as usize;
220+
let gx = ((gx_f32 - offset_x) / resolution).round() as usize;
221+
let gy = ((gy_f32 - offset_y) / resolution).round() as usize;
193222

194223
let sx = sx.min(width - 1);
195224
let sy = sy.min(height - 1);
@@ -211,8 +240,8 @@ impl MapfPlanner for PibtPlanner {
211240
let mut trajectories = vec![Vec::new(); robot_ids.len()];
212241
for time_step in solved_paths {
213242
for (agent_idx, pos) in time_step.iter().enumerate() {
214-
let world_x = pos.0 as f32 + offset_x;
215-
let world_y = pos.1 as f32 + offset_y;
243+
let world_x = pos.0 as f32 * resolution + offset_x;
244+
let world_y = pos.1 as f32 * resolution + offset_y;
216245
trajectories[agent_idx].push(Isometry2::translation(world_x, world_y));
217246
}
218247
}

0 commit comments

Comments
 (0)