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 <mutex>
19 #include <string>
20 
21 #include "nav2_route/plugins/route_operations/collision_monitor.hpp"
22 
23 namespace nav2_route
24 {
25 
27  const nav2::LifecycleNode::SharedPtr node,
28  std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_subscriber,
29  const std::string & name)
30 {
31  name_ = name;
32  clock_ = node->get_clock();
33  logger_ = node->get_logger();
34  last_check_time_ = clock_->now();
35 
36  std::string server_costmap_topic = node->get_parameter("costmap_topic").as_string();
37  std::string costmap_topic = node->declare_or_get_parameter(
38  getName() + ".costmap_topic", std::string("local_costmap/costmap_raw"));
39  if (costmap_topic != server_costmap_topic) {
40  RCLCPP_INFO(
41  node->get_logger(),
42  "Using costmap topic: %s instead of server costmap topic: %s for CollisionMonitor.",
43  costmap_topic.c_str(), server_costmap_topic.c_str());
44  costmap_subscriber_ = std::make_shared<nav2_costmap_2d::CostmapSubscriber>(node, costmap_topic);
45  topic_ = costmap_topic;
46  } else {
47  costmap_subscriber_ = costmap_subscriber;
48  topic_ = server_costmap_topic;
49  }
50 
51  double checking_rate = node->declare_or_get_parameter(getName() + ".rate", 1.0);
52  checking_duration_ = rclcpp::Duration::from_seconds(1.0 / checking_rate);
53 
54  reroute_on_collision_ = node->declare_or_get_parameter(
55  getName() + ".reroute_on_collision", true);
56 
57  max_cost_ = static_cast<float>(
58  node->declare_or_get_parameter(getName() + ".max_cost", 253.0));
59 
60  // Resolution to check the costmap over (1=every cell, 2=every other cell, etc.)
61  check_resolution_ = static_cast<unsigned int>(
62  node->declare_or_get_parameter(getName() + ".check_resolution", 1));
63 
64  max_collision_dist_ = static_cast<float>(
65  node->declare_or_get_parameter(getName() + ".max_collision_dist", 5.0));
66  if (max_collision_dist_ <= 0.0) {
67  RCLCPP_INFO(
68  logger_, "Max collision distance to evaluate is zero or negative, checking the full route.");
69  max_collision_dist_ = std::numeric_limits<float>::max();
70  }
71 }
72 
74 {
75  try {
76  costmap_ = costmap_subscriber_->getCostmap();
77  } catch (...) {
79  "Collision Monitor could not obtain a costmap from topic: " + topic_);
80  }
81 }
82 
84  NodePtr /*node*/,
85  EdgePtr curr_edge,
86  EdgePtr /*edge_exited*/,
87  const Route & route,
88  const geometry_msgs::msg::PoseStamped & curr_pose,
89  const Metadata * /*mdata*/)
90 {
91  // Not time yet to check or before getting to first route edge
92  auto now = clock_->now();
93  if (now - last_check_time_ < checking_duration_ || !curr_edge) {
94  return OperationResult();
95  }
96  last_check_time_ = now;
97 
98  OperationResult result;
99  getCostmap();
100 
101  std::lock_guard<nav2_costmap_2d::Costmap2D::mutex_t> lock(*costmap_->getMutex());
102 
103  float dist_checked = 0.0;
104  Coordinates end = curr_edge->end->coords;
105  Coordinates start = utils::findClosestPoint(
106  curr_pose, curr_edge->start->coords, end);
107  unsigned int curr_edge_id = curr_edge->edgeid;
108 
109  bool final_edge = false;
110  while (!final_edge) {
111  // Track how far we've checked and should check for collisions
112  const float edge_dist = hypotf(end.x - start.x, end.y - start.y);
113  if (dist_checked + edge_dist >= max_collision_dist_) {
114  float dist_to_eval = max_collision_dist_ - dist_checked;
115  end = backoutValidEndPoint(start, end, dist_to_eval);
116  final_edge = true;
117  }
118  dist_checked += edge_dist;
119 
120  // Find the valid edge grid coords, in case the edge is partially off the grid
121  LineSegment line;
122  if (!lineToMap(start, end, line)) {
123  final_edge = true;
124  if (!backoutValidEndPoint(start, line)) {
125  break;
126  }
127  }
128 
129  // Collision check edge on grid within max distance and
130  // report blocked edges for rerouting or exit task
131  if (isInCollision(line)) {
132  RCLCPP_INFO(
133  logger_, "Collision has been detected within %0.2fm of robot pose!", max_collision_dist_);
134 
135  if (reroute_on_collision_) {
136  result.reroute = true;
137  result.blocked_ids.push_back(curr_edge_id);
138  return result;
139  }
140 
142  "Collision detected, but rerouting is not enabled, canceling tracking task.");
143  }
144 
145  // Restart loop for next edge until complete
146  start = end;
147  if (!final_edge) {
148  auto isCurrEdge = [&](const EdgePtr & edge) {return edge->edgeid == curr_edge_id;};
149  auto iter = std::find_if(route.edges.begin(), route.edges.end(), isCurrEdge);
150  if (iter != route.edges.end() && ++iter != route.edges.end()) {
151  // If we found the edge and the next edge is also valid
152  curr_edge_id = (*iter)->edgeid;
153  end = (*iter)->end->coords;
154  } else {
155  final_edge = true;
156  }
157  }
158  }
159 
160  return result;
161 }
162 
164  const Coordinates & start, const Coordinates & end, const float dist)
165 {
166  Coordinates new_end;
167  const float dx = end.x - start.x;
168  const float dy = end.y - start.y;
169  const float mag = hypotf(dx, dy);
170  if (mag < 1e-6) {
171  return start;
172  }
173  new_end.x = (dx / mag) * dist + start.x;
174  new_end.y = (dy / mag) * dist + start.y;
175  return new_end;
176 }
177 
179  const Coordinates & start, LineSegment & line)
180 {
181  // Check if any part of this edge is potentially valid
182  if (!costmap_->worldToMap(start.x, start.y, line.x0, line.y0)) {
183  return false;
184  }
185 
186  // Since worldToMap will populate the out-of-bounds (x1, y1), we can
187  // iterate through the partially valid line until we hit invalid coords
188  unsigned int last_end_x = line.x0, last_end_y = line.y0;
189  nav2_util::LineIterator iter(line.x0, line.y0, line.x1, line.y1);
190  int size_x = static_cast<int>(costmap_->getSizeInCellsX());
191  int size_y = static_cast<int>(costmap_->getSizeInCellsY());
192  for (; iter.isValid(); iter.advance()) {
193  if (iter.getX() >= size_x || iter.getY() >= size_y) {
194  line.x1 = last_end_x;
195  line.y1 = last_end_y;
196  return true;
197  }
198  last_end_x = iter.getX();
199  last_end_y = iter.getY();
200  }
201 
202  return false;
203 }
204 
206  const Coordinates & start, const Coordinates & end, LineSegment & line)
207 {
208  if (!costmap_->worldToMap(start.x, start.y, line.x0, line.y0) ||
209  !costmap_->worldToMap(end.x, end.y, line.x1, line.y1))
210  {
211  return false;
212  }
213  return true;
214 }
215 
217 {
218  nav2_util::LineIterator iter(line.x0, line.y0, line.x1, line.y1);
219  for (; iter.isValid(); ) {
220  float cost = static_cast<float>(costmap_->getCost(iter.getX(), iter.getY()));
221  if (cost >= max_cost_ && cost != 255.0 /*unknown*/) {
222  return true;
223  }
224 
225  // Advance the iterator by the check resolution on the edge, pruning to a coarse resolution
226  for (unsigned int i = 0; i < check_resolution_; i++) {
227  iter.advance();
228  }
229  }
230  return false;
231 }
232 
233 } // namespace nav2_route
234 
235 #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