Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
keepout_filter.cpp
1 /*********************************************************************
2  *
3  * Software License Agreement (BSD License)
4  *
5  * Copyright (c) 2020 Samsung Research Russia
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * * Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  * * Redistributions in binary form must reproduce the above
15  * copyright notice, this list of conditions and the following
16  * disclaimer in the documentation and/or other materials provided
17  * with the distribution.
18  * * Neither the name of the <ORGANIZATION> nor the names of its
19  * contributors may be used to endorse or promote products derived
20  * from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  *
35  * Author: Alexey Merzlyakov
36  *********************************************************************/
37 
38 #include "nav2_costmap_2d/costmap_filters/keepout_filter.hpp"
39 
40 #include <string>
41 #include <memory>
42 #include <algorithm>
43 #include "tf2/convert.hpp"
44 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
45 
46 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
47 #include "nav2_util/geometry_utils.hpp"
48 #include "nav2_util/occ_grid_utils.hpp"
49 
50 namespace nav2_costmap_2d
51 {
52 
54 : filter_info_sub_(nullptr), mask_sub_(nullptr), filter_mask_(nullptr),
55  global_frame_("")
56 {
57 }
58 
60  const std::string & filter_info_topic)
61 {
62  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
63 
64  nav2::LifecycleNode::SharedPtr node = node_.lock();
65  if (!node) {
66  throw std::runtime_error{"Failed to lock node"};
67  }
68 
69  filter_info_topic_ = joinWithParentNamespace(filter_info_topic);
70  // Setting new costmap filter info subscriber
71  RCLCPP_INFO(
72  logger_,
73  "KeepoutFilter: Subscribing to \"%s\" topic for filter info...",
74  filter_info_topic_.c_str());
75  filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
77  std::bind(&KeepoutFilter::filterInfoCallback, this, std::placeholders::_1),
79 
80  global_frame_ = layered_costmap_->getGlobalFrameID();
81 
82  override_lethal_cost_ = node->declare_or_get_parameter(name_ + "." + "override_lethal_cost",
83  false);
84  lethal_override_cost_ = node->declare_or_get_parameter(name_ + "." + "lethal_override_cost",
85  MAX_NON_OBSTACLE);
86 
87  // clamp lethal_override_cost_ in case if higher than MAX_NON_OBSTACLE is given
88  lethal_override_cost_ = \
89  std::clamp<unsigned int>(lethal_override_cost_, FREE_SPACE, MAX_NON_OBSTACLE);
90  lethal_state_update_max_x_ = lethal_state_update_max_y_ = std::numeric_limits<double>::lowest();
91  lethal_state_update_min_x_ = lethal_state_update_min_y_ = std::numeric_limits<double>::max();
92 }
93 
95  const nav2_msgs::msg::CostmapFilterInfo::ConstSharedPtr & msg)
96 {
97  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
98 
99  nav2::LifecycleNode::SharedPtr node = node_.lock();
100  if (!node) {
101  throw std::runtime_error{"Failed to lock node"};
102  }
103 
104  if (!mask_sub_) {
105  RCLCPP_INFO(
106  logger_,
107  "KeepoutFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
108  } else {
109  RCLCPP_WARN(
110  logger_,
111  "KeepoutFilter: New costmap filter info arrived from %s topic. Updating old filter info.",
112  filter_info_topic_.c_str());
113  // Resetting previous subscriber each time when new costmap filter information arrives
114  mask_sub_.reset();
115  }
116 
117  // Checking that base and multiplier are set to their default values
118  if (msg->base != BASE_DEFAULT || msg->multiplier != MULTIPLIER_DEFAULT) {
119  RCLCPP_ERROR(
120  logger_,
121  "KeepoutFilter: For proper use of keepout filter base and multiplier"
122  " in CostmapFilterInfo message should be set to their default values (%f and %f)",
123  BASE_DEFAULT, MULTIPLIER_DEFAULT);
124  }
125 
126  mask_topic_ = joinWithParentNamespace(msg->filter_mask_topic);
127 
128  // Setting new filter mask subscriber
129  RCLCPP_INFO(
130  logger_,
131  "KeepoutFilter: Subscribing to \"%s\" topic for filter mask...",
132  mask_topic_.c_str());
133  mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
134  mask_topic_,
135  std::bind(&KeepoutFilter::maskCallback, this, std::placeholders::_1),
137 }
138 
140  const nav_msgs::msg::OccupancyGrid::ConstSharedPtr & msg)
141 {
142  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
143 
144  nav2::LifecycleNode::SharedPtr node = node_.lock();
145  if (!node) {
146  throw std::runtime_error{"Failed to lock node"};
147  }
148 
149  if (!filter_mask_) {
150  RCLCPP_INFO(
151  logger_,
152  "KeepoutFilter: Received filter mask from %s topic.", mask_topic_.c_str());
153  } else {
154  RCLCPP_WARN(
155  logger_,
156  "KeepoutFilter: New filter mask arrived from %s topic. Updating old filter mask.",
157  mask_topic_.c_str());
158  filter_mask_.reset();
159  }
160 
161  // Store filter_mask_
162  filter_mask_ = msg;
163  has_updated_data_ = true;
164  x_ = y_ = 0;
165  width_ = msg->info.width;
166  height_ = msg->info.height;
167 }
168 
170  double robot_x, double robot_y, double robot_yaw,
171  double * min_x, double * min_y, double * max_x, double * max_y)
172 {
173  if (!enabled_) {
174  return;
175  }
176 
177  CostmapFilter::updateBounds(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);
178 
179  if (!filter_mask_) {
180  RCLCPP_WARN_THROTTLE(
181  logger_, *(clock_), 2000,
182  "KeepoutFilter: Filter mask was not received");
183  return;
184  }
185 
186  // If new keepout zone received
187  if (has_updated_data_) {
188  double wx, wy;
189  layered_costmap_->getCostmap()->mapToWorld(x_, y_, wx, wy);
190  *min_x = std::min(wx, *min_x);
191  *min_y = std::min(wy, *min_y);
192 
193  layered_costmap_->getCostmap()->mapToWorld(x_ + width_, y_ + height_, wx, wy);
194  *max_x = std::max(wx, *max_x);
195  *max_y = std::max(wy, *max_y);
196 
197  has_updated_data_ = false;
198  return;
199  }
200 
201  // Let's find the pose's cost if we are allowed to override the lethal cost
202  is_pose_lethal_ = false;
203  if (override_lethal_cost_) {
204  geometry_msgs::msg::Pose pose;
205  pose.position.x = robot_x;
206  pose.position.y = robot_y;
207  pose.position.z = 0.0;
208  pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(robot_yaw);
209  geometry_msgs::msg::Pose mask_pose;
210  if (transformPose(global_frame_, pose, filter_mask_->header.frame_id, mask_pose)) {
211  unsigned int mask_robot_i, mask_robot_j;
212  if (nav2_util::worldToMap(
213  filter_mask_, mask_pose.position.x, mask_pose.position.y,
214  mask_robot_i, mask_robot_j))
215  {
216  auto data = getMaskCost(filter_mask_, mask_robot_i, mask_robot_j);
217  is_pose_lethal_ = (data == INSCRIBED_INFLATED_OBSTACLE || data == LETHAL_OBSTACLE);
218  if (is_pose_lethal_) {
219  RCLCPP_WARN_THROTTLE(
220  logger_, *(clock_), 2000,
221  "KeepoutFilter: Pose is in keepout zone, reducing cost override to navigate out.");
222  }
223  }
224  }
225 
226  // If in lethal space or just exited lethal space,
227  // we need to update all possible spaces touched during this state
228  if (is_pose_lethal_ || (last_pose_lethal_ && !is_pose_lethal_)) {
229  lethal_state_update_min_x_ = std::min(*min_x, lethal_state_update_min_x_);
230  *min_x = lethal_state_update_min_x_;
231  lethal_state_update_min_y_ = std::min(*min_y, lethal_state_update_min_y_);
232  *min_y = lethal_state_update_min_y_;
233  lethal_state_update_max_x_ = std::max(*max_x, lethal_state_update_max_x_);
234  *max_x = lethal_state_update_max_x_;
235  lethal_state_update_max_y_ = std::max(*max_y, lethal_state_update_max_y_);
236  *max_y = lethal_state_update_max_y_;
237  } else {
238  // If out of lethal space, reset managed lethal state sizes
239  lethal_state_update_min_x_ = std::numeric_limits<double>::max();
240  lethal_state_update_min_y_ = std::numeric_limits<double>::max();
241  lethal_state_update_max_x_ = std::numeric_limits<double>::lowest();
242  lethal_state_update_max_y_ = std::numeric_limits<double>::lowest();
243  }
244  }
245 }
246 
248  nav2_costmap_2d::Costmap2D & master_grid,
249  int min_i, int min_j, int max_i, int max_j,
250  const geometry_msgs::msg::Pose & /*pose*/)
251 {
252  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
253 
254  if (!filter_mask_) {
255  // Show warning message every 2 seconds to not litter an output
256  RCLCPP_WARN_THROTTLE(
257  logger_, *(clock_), 2000,
258  "KeepoutFilter: Filter mask was not received");
259  return;
260  }
261 
262  tf2::Transform tf2_transform;
263  tf2_transform.setIdentity(); // initialize by identical transform
264  int mg_min_x, mg_min_y; // master_grid indexes of bottom-left window corner
265  int mg_max_x, mg_max_y; // master_grid indexes of top-right window corner
266 
267  const std::string mask_frame = filter_mask_->header.frame_id;
268 
269  if (mask_frame != global_frame_) {
270  // Filter mask and current layer are in different frames:
271  // prepare frame transformation if mask_frame != global_frame_
272  geometry_msgs::msg::TransformStamped transform;
273  try {
274  transform = tf_->lookupTransform(
275  mask_frame, global_frame_, tf2::TimePointZero,
277  } catch (tf2::TransformException & ex) {
278  RCLCPP_ERROR_THROTTLE(
279  logger_, *(clock_), 2000,
280  "KeepoutFilter: Failed to get costmap frame (%s) "
281  "transformation to mask frame (%s) with error: %s",
282  global_frame_.c_str(), mask_frame.c_str(), ex.what());
283  return;
284  }
285  tf2::fromMsg(transform.transform, tf2_transform);
286 
287  mg_min_x = min_i;
288  mg_min_y = min_j;
289  mg_max_x = max_i;
290  mg_max_y = max_j;
291  } else {
292  // Filter mask and current layer are in the same frame:
293  // apply the following optimization - iterate only in overlapped
294  // (min_i, min_j)..(max_i, max_j) & filter_mask_ area.
295  //
296  // filter_mask_
297  // *----------------------------*
298  // | |
299  // | |
300  // | (2) |
301  // *-----+-------* |
302  // | |///////|<- overlapped area |
303  // | |///////| to iterate in |
304  // | *-------+--------------------*
305  // | (1) |
306  // | |
307  // *-------------*
308  // master_grid (min_i, min_j)..(max_i, max_j) window
309  //
310  // ToDo: after costmap rotation will be added, this should be re-worked.
311 
312  double wx, wy; // world coordinates
313 
314  // Calculating bounds corresponding to bottom-left overlapping (1) corner
315  // filter_mask_ -> master_grid indexes conversion
316  const double half_cell_size = 0.5 * filter_mask_->info.resolution;
317  wx = filter_mask_->info.origin.position.x + half_cell_size;
318  wy = filter_mask_->info.origin.position.y + half_cell_size;
319  master_grid.worldToMapNoBounds(wx, wy, mg_min_x, mg_min_y);
320  // Calculation of (1) corner bounds
321  if (mg_min_x >= max_i || mg_min_y >= max_j) {
322  // There is no overlapping. Do nothing.
323  return;
324  }
325  mg_min_x = std::max(min_i, mg_min_x);
326  mg_min_y = std::max(min_j, mg_min_y);
327 
328  // Calculating bounds corresponding to top-right window (2) corner
329  // filter_mask_ -> master_grid indexes conversion
330  wx = filter_mask_->info.origin.position.x +
331  filter_mask_->info.width * filter_mask_->info.resolution + half_cell_size;
332  wy = filter_mask_->info.origin.position.y +
333  filter_mask_->info.height * filter_mask_->info.resolution + half_cell_size;
334  master_grid.worldToMapNoBounds(wx, wy, mg_max_x, mg_max_y);
335  // Calculation of (2) corner bounds
336  if (mg_max_x <= min_i || mg_max_y <= min_j) {
337  // There is no overlapping. Do nothing.
338  return;
339  }
340  mg_max_x = std::min(max_i, mg_max_x);
341  mg_max_y = std::min(max_j, mg_max_y);
342  }
343 
344  // unsigned<-signed conversions.
345  const unsigned int mg_min_x_u = static_cast<unsigned int>(mg_min_x);
346  const unsigned int mg_min_y_u = static_cast<unsigned int>(mg_min_y);
347  const unsigned int mg_max_x_u = static_cast<unsigned int>(mg_max_x);
348  const unsigned int mg_max_y_u = static_cast<unsigned int>(mg_max_y);
349 
350  unsigned int i, j; // master_grid iterators
351  unsigned int index; // corresponding index of master_grid
352  double gl_wx, gl_wy; // world coordinates in a global_frame_
353  double msk_wx, msk_wy; // world coordinates in a mask_frame
354  unsigned int mx, my; // filter_mask_ coordinates
355  unsigned char data, old_data; // master_grid element data
356 
357  // Main master_grid updating loop
358  // Iterate in costmap window by master_grid indexes
359  unsigned char * master_array = master_grid.getCharMap();
360  for (i = mg_min_x_u; i < mg_max_x_u; i++) {
361  for (j = mg_min_y_u; j < mg_max_y_u; j++) {
362  index = master_grid.getIndex(i, j);
363  old_data = master_array[index];
364  // Calculating corresponding to (i, j) point at filter_mask_:
365  // Get world coordinates in global_frame_
366  master_grid.mapToWorld(i, j, gl_wx, gl_wy);
367  if (mask_frame != global_frame_) {
368  // Transform (i, j) point from global_frame_ to mask_frame
369  tf2::Vector3 point(gl_wx, gl_wy, 0);
370  point = tf2_transform * point;
371  msk_wx = point.x();
372  msk_wy = point.y();
373  } else {
374  // In this case master_grid and filter-mask are in the same frame
375  msk_wx = gl_wx;
376  msk_wy = gl_wy;
377  }
378  // Get mask coordinates corresponding to (i, j) point at filter_mask_
379  if (nav2_util::worldToMap(filter_mask_, msk_wx, msk_wy, mx, my)) {
380  data = getMaskCost(filter_mask_, mx, my);
381  // Update if mask_ data is valid and greater than existing master_grid's one
382  if (data == NO_INFORMATION) {
383  continue;
384  }
385 
386  if (data > old_data || old_data == NO_INFORMATION) {
387  if (override_lethal_cost_ && is_pose_lethal_) {
388  master_array[index] = lethal_override_cost_;
389  } else {
390  master_array[index] = data;
391  }
392  }
393  }
394  }
395  }
396 
397  last_pose_lethal_ = is_pose_lethal_;
398 }
399 
401 {
402  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
403 
404  filter_info_sub_.reset();
405  mask_sub_.reset();
406 }
407 
409 {
410  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
411 
412  if (filter_mask_) {
413  return true;
414  }
415  return false;
416 }
417 
418 } // namespace nav2_costmap_2d
419 
420 #include "pluginlib/class_list_macros.hpp"
A QoS profile for latched, reliable topics with a history of 10 messages.
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:69
void mapToWorld(unsigned int mx, unsigned int my, double &wx, double &wy) const
Convert from map coordinates to world coordinates.
Definition: costmap_2d.cpp:280
unsigned int getIndex(unsigned int mx, unsigned int my) const
Given two map coordinates... compute the associated index.
Definition: costmap_2d.hpp:231
unsigned char * getCharMap() const
Will return a pointer to the underlying unsigned char array used as the costmap.
Definition: costmap_2d.cpp:260
void worldToMapNoBounds(double wx, double wy, int &mx, int &my) const
Convert from world coordinates to map coordinates without checking for legal bounds.
Definition: costmap_2d.cpp:322
bool transformPose(const std::string global_frame, const geometry_msgs::msg::Pose &global_pose, const std::string mask_frame, geometry_msgs::msg::Pose &mask_pose) const
: Transforms robot pose from current layer frame to mask frame
std::string filter_info_topic_
: Name of costmap filter info topic
std::string mask_topic_
: Name of filter mask topic
tf2::Duration transform_tolerance_
: mask_frame->global_frame_ transform tolerance
unsigned char getMaskCost(nav_msgs::msg::OccupancyGrid::ConstSharedPtr filter_mask, const unsigned int mx, const unsigned int &my) const
Get the cost of a cell in the filter mask.
mutex_t * getMutex()
: returns pointer to a mutex
void updateBounds(double robot_x, double robot_y, double robot_yaw, double *min_x, double *min_y, double *max_x, double *max_y) override
Update the bounds of the master costmap by this layer's update dimensions.
Reads in a keepout mask and marks keepout regions in the map to prevent planning or control in restri...
void initializeFilter(const std::string &filter_info_topic) override
Initialize the filter and subscribe to the info topic.
void resetFilter() override
Reset the costmap filter / topic / info.
void process(nav2_costmap_2d::Costmap2D &master_grid, int min_i, int min_j, int max_i, int max_j, const geometry_msgs::msg::Pose &pose) override
Process the keepout layer at the current pose / bounds / grid.
void filterInfoCallback(const nav2_msgs::msg::CostmapFilterInfo::ConstSharedPtr &msg)
Callback for the filter information.
bool isActive()
If this filter is active.
void updateBounds(double robot_x, double robot_y, double robot_yaw, double *min_x, double *min_y, double *max_x, double *max_y) override
Update the bounds of the master costmap by this layer's update dimensions.
void maskCallback(const nav_msgs::msg::OccupancyGrid::ConstSharedPtr &msg)
Callback for the filter mask.
Abstract class for layered costmap plugin implementations.
Definition: layer.hpp:60
std::string joinWithParentNamespace(const std::string &topic)
Definition: layer.cpp:83
Costmap2D * getCostmap()
Get the costmap pointer to the master costmap.