Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
collision_monitor.cpp
1 // Copyright (c) 2025, Open Navigation LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 
16 #include <math.h>
17 #include <memory>
18 #include <string>
19 
20 #include "nav2_route/plugins/route_operations/collision_monitor.hpp"
21 
22 namespace nav2_route
23 {
24 
26  const nav2::LifecycleNode::SharedPtr node,
27  std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_subscriber,
28  const std::string & name)
29 {
30  name_ = name;
31  clock_ = node->get_clock();
32  logger_ = node->get_logger();
33  last_check_time_ = clock_->now();
34 
35  std::string server_costmap_topic = node->get_parameter("costmap_topic").as_string();
36  std::string costmap_topic = node->declare_or_get_parameter(
37  getName() + ".costmap_topic", std::string("local_costmap/costmap_raw"));
38  if (costmap_topic != server_costmap_topic) {
39  RCLCPP_INFO(
40  node->get_logger(),
41  "Using costmap topic: %s instead of server costmap topic: %s for CollisionMonitor.",
42  costmap_topic.c_str(), server_costmap_topic.c_str());
43  costmap_subscriber_ = std::make_shared<nav2_costmap_2d::CostmapSubscriber>(node, costmap_topic);
44  topic_ = costmap_topic;
45  } else {
46  costmap_subscriber_ = costmap_subscriber;
47  topic_ = server_costmap_topic;
48  }
49 
50  double checking_rate = node->declare_or_get_parameter(getName() + ".rate", 1.0);
51  checking_duration_ = rclcpp::Duration::from_seconds(1.0 / checking_rate);
52 
53  reroute_on_collision_ = node->declare_or_get_parameter(
54  getName() + ".reroute_on_collision", true);
55 
56  max_cost_ = static_cast<float>(
57  node->declare_or_get_parameter(getName() + ".max_cost", 253.0));
58 
59  // Resolution to check the costmap over (1=every cell, 2=every other cell, etc.)
60  check_resolution_ = static_cast<unsigned int>(
61  node->declare_or_get_parameter(getName() + ".check_resolution", 1));
62 
63  max_collision_dist_ = static_cast<float>(
64  node->declare_or_get_parameter(getName() + ".max_collision_dist", 5.0));
65  if (max_collision_dist_ <= 0.0) {
66  RCLCPP_INFO(
67  logger_, "Max collision distance to evaluate is zero or negative, checking the full route.");
68  max_collision_dist_ = std::numeric_limits<float>::max();
69  }
70 }
71 
73 {
74  try {
75  costmap_ = costmap_subscriber_->getCostmap();
76  } catch (...) {
78  "Collision Monitor could not obtain a costmap from topic: " + topic_);
79  }
80 }
81 
83  NodePtr /*node*/,
84  EdgePtr curr_edge,
85  EdgePtr /*edge_exited*/,
86  const Route & route,
87  const geometry_msgs::msg::PoseStamped & curr_pose,
88  const Metadata * /*mdata*/)
89 {
90  // Not time yet to check or before getting to first route edge
91  auto now = clock_->now();
92  if (now - last_check_time_ < checking_duration_ || !curr_edge) {
93  return OperationResult();
94  }
95  last_check_time_ = now;
96 
97  OperationResult result;
98  getCostmap();
99 
100  float dist_checked = 0.0;
101  Coordinates end = curr_edge->end->coords;
102  Coordinates start = utils::findClosestPoint(
103  curr_pose, curr_edge->start->coords, end);
104  unsigned int curr_edge_id = curr_edge->edgeid;
105 
106  bool final_edge = false;
107  while (!final_edge) {
108  // Track how far we've checked and should check for collisions
109  const float edge_dist = hypotf(end.x - start.x, end.y - start.y);
110  if (dist_checked + edge_dist >= max_collision_dist_) {
111  float dist_to_eval = max_collision_dist_ - dist_checked;
112  end = backoutValidEndPoint(start, end, dist_to_eval);
113  final_edge = true;
114  }
115  dist_checked += edge_dist;
116 
117  // Find the valid edge grid coords, in case the edge is partially off the grid
118  LineSegment line;
119  if (!lineToMap(start, end, line)) {
120  final_edge = true;
121  if (!backoutValidEndPoint(start, line)) {
122  break;
123  }
124  }
125 
126  // Collision check edge on grid within max distance and
127  // report blocked edges for rerouting or exit task
128  if (isInCollision(line)) {
129  RCLCPP_INFO(
130  logger_, "Collision has been detected within %0.2fm of robot pose!", max_collision_dist_);
131 
132  if (reroute_on_collision_) {
133  result.reroute = true;
134  result.blocked_ids.push_back(curr_edge_id);
135  return result;
136  }
137 
139  "Collision detected, but rerouting is not enabled, canceling tracking task.");
140  }
141 
142  // Restart loop for next edge until complete
143  start = end;
144  if (!final_edge) {
145  auto isCurrEdge = [&](const EdgePtr & edge) {return edge->edgeid == curr_edge_id;};
146  auto iter = std::find_if(route.edges.begin(), route.edges.end(), isCurrEdge);
147  if (iter != route.edges.end() && ++iter != route.edges.end()) {
148  // If we found the edge and the next edge is also valid
149  curr_edge_id = (*iter)->edgeid;
150  end = (*iter)->end->coords;
151  } else {
152  final_edge = true;
153  }
154  }
155  }
156 
157  return result;
158 }
159 
161  const Coordinates & start, const Coordinates & end, const float dist)
162 {
163  Coordinates new_end;
164  const float dx = end.x - start.x;
165  const float dy = end.y - start.y;
166  const float mag = hypotf(dx, dy);
167  if (mag < 1e-6) {
168  return start;
169  }
170  new_end.x = (dx / mag) * dist + start.x;
171  new_end.y = (dy / mag) * dist + start.y;
172  return new_end;
173 }
174 
176  const Coordinates & start, LineSegment & line)
177 {
178  // Check if any part of this edge is potentially valid
179  if (!costmap_->worldToMap(start.x, start.y, line.x0, line.y0)) {
180  return false;
181  }
182 
183  // Since worldToMap will populate the out-of-bounds (x1, y1), we can
184  // iterate through the partially valid line until we hit invalid coords
185  unsigned int last_end_x = line.x0, last_end_y = line.y0;
186  nav2_util::LineIterator iter(line.x0, line.y0, line.x1, line.y1);
187  int size_x = static_cast<int>(costmap_->getSizeInCellsX());
188  int size_y = static_cast<int>(costmap_->getSizeInCellsY());
189  for (; iter.isValid(); iter.advance()) {
190  if (iter.getX() >= size_x || iter.getY() >= size_y) {
191  line.x1 = last_end_x;
192  line.y1 = last_end_y;
193  return true;
194  }
195  last_end_x = iter.getX();
196  last_end_y = iter.getY();
197  }
198 
199  return false;
200 }
201 
203  const Coordinates & start, const Coordinates & end, LineSegment & line)
204 {
205  if (!costmap_->worldToMap(start.x, start.y, line.x0, line.y0) ||
206  !costmap_->worldToMap(end.x, end.y, line.x1, line.y1))
207  {
208  return false;
209  }
210  return true;
211 }
212 
214 {
215  nav2_util::LineIterator iter(line.x0, line.y0, line.x1, line.y1);
216  for (; iter.isValid(); ) {
217  float cost = static_cast<float>(costmap_->getCost(iter.getX(), iter.getY()));
218  if (cost >= max_cost_ && cost != 255.0 /*unknown*/) {
219  return true;
220  }
221 
222  // Advance the iterator by the check resolution on the edge, pruning to a coarse resolution
223  for (unsigned int i = 0; i < check_resolution_; i++) {
224  iter.advance();
225  }
226  }
227  return false;
228 }
229 
230 } // namespace nav2_route
231 
232 #include "pluginlib/class_list_macros.hpp"
A route operation to process a costmap to determine if a route is blocked requiring immediate rerouti...
std::string getName() override
Get name of the plugin for parameter scope mapping.
Coordinates backoutValidEndPoint(const Coordinates &start, const Coordinates &end, const float dist)
Backs out the end coordinate along the line segment start-end to length dist.
void configure(const nav2::LifecycleNode::SharedPtr node, std::shared_ptr< nav2_costmap_2d::CostmapSubscriber > costmap_subscriber, const std::string &name) override
Configure.
bool lineToMap(const Coordinates &start, const Coordinates &end, LineSegment &line)
Converts a line segment start-end into a LineSegment struct in costmap frame.
bool isInCollision(const LineSegment &line)
Checks a line segment in costmap frame for validity.
void getCostmap()
Gets the latest costmap from the costmap subscriber.
OperationResult perform(NodePtr, EdgePtr curr_edge, EdgePtr, const Route &route, const geometry_msgs::msg::PoseStamped &curr_pose, const Metadata *) override
The main speed limit operation to adjust the maximum speed of the vehicle.
A plugin interface to perform an operation while tracking the route such as triggered from the graph ...
An iterator implementing Bresenham Ray-Tracing.
bool isValid() const
If the iterator is valid.
int getX() const
Get current X value.
void advance()
Advance iteration along the line.
int getY() const
Get current Y value.
An object to store Node coordinates in different frames.
Definition: types.hpp:173
An object representing edges between nodes.
Definition: types.hpp:134
An object to store arbitrary metadata regarding nodes from the graph file.
Definition: types.hpp:35
An object to store the nodes in the graph file.
Definition: types.hpp:183
a struct to hold return from an operation
An ordered set of nodes and edges corresponding to the planned route.
Definition: types.hpp:211