Nav2 Navigation Stack - humble  humble
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 <string>
39 #include <memory>
40 #include <algorithm>
41 #include "tf2/convert.h"
42 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
43 
44 #include "nav2_costmap_2d/costmap_filters/keepout_filter.hpp"
45 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
46 
47 namespace nav2_costmap_2d
48 {
49 
51 : filter_info_sub_(nullptr), mask_sub_(nullptr), mask_costmap_(nullptr),
52  mask_frame_(""), global_frame_("")
53 {
54 }
55 
57  const std::string & filter_info_topic)
58 {
59  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
60 
61  rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
62  if (!node) {
63  throw std::runtime_error{"Failed to lock node"};
64  }
65 
66  filter_info_topic_ = filter_info_topic;
67  // Setting new costmap filter info subscriber
68  RCLCPP_INFO(
69  logger_,
70  "KeepoutFilter: Subscribing to \"%s\" topic for filter info...",
71  filter_info_topic_.c_str());
72  filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
73  filter_info_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
74  std::bind(&KeepoutFilter::filterInfoCallback, this, std::placeholders::_1));
75 
76  global_frame_ = layered_costmap_->getGlobalFrameID();
77 }
78 
79 void KeepoutFilter::filterInfoCallback(
80  const nav2_msgs::msg::CostmapFilterInfo::SharedPtr msg)
81 {
82  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
83 
84  rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
85  if (!node) {
86  throw std::runtime_error{"Failed to lock node"};
87  }
88 
89  if (!mask_sub_) {
90  RCLCPP_INFO(
91  logger_,
92  "KeepoutFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
93  } else {
94  RCLCPP_WARN(
95  logger_,
96  "KeepoutFilter: New costmap filter info arrived from %s topic. Updating old filter info.",
97  filter_info_topic_.c_str());
98  // Resetting previous subscriber each time when new costmap filter information arrives
99  mask_sub_.reset();
100  }
101 
102  // Checking that base and multiplier are set to their default values
103  if (msg->base != BASE_DEFAULT || msg->multiplier != MULTIPLIER_DEFAULT) {
104  RCLCPP_ERROR(
105  logger_,
106  "KeepoutFilter: For proper use of keepout filter base and multiplier"
107  " in CostmapFilterInfo message should be set to their default values (%f and %f)",
108  BASE_DEFAULT, MULTIPLIER_DEFAULT);
109  }
110 
111  mask_topic_ = msg->filter_mask_topic;
112 
113  // Setting new filter mask subscriber
114  RCLCPP_INFO(
115  logger_,
116  "KeepoutFilter: Subscribing to \"%s\" topic for filter mask...",
117  mask_topic_.c_str());
118  mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
119  mask_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
120  std::bind(&KeepoutFilter::maskCallback, this, std::placeholders::_1));
121 }
122 
123 void KeepoutFilter::maskCallback(
124  const nav_msgs::msg::OccupancyGrid::SharedPtr msg)
125 {
126  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
127 
128  rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
129  if (!node) {
130  throw std::runtime_error{"Failed to lock node"};
131  }
132 
133  if (!mask_costmap_) {
134  RCLCPP_INFO(
135  logger_,
136  "KeepoutFilter: Received filter mask from %s topic.", mask_topic_.c_str());
137  } else {
138  RCLCPP_WARN(
139  logger_,
140  "KeepoutFilter: New filter mask arrived from %s topic. Updating old filter mask.",
141  mask_topic_.c_str());
142  mask_costmap_.reset();
143  }
144 
145  // Making a new mask_costmap_
146  mask_costmap_ = std::make_unique<Costmap2D>(*msg);
147  mask_frame_ = msg->header.frame_id;
148 
149  has_updated_data_ = true;
150  x_ = y_ = 0;
151  width_ = msg->info.width;
152  height_ = msg->info.height;
153 }
154 
156  double robot_x, double robot_y, double robot_yaw,
157  double * min_x, double * min_y, double * max_x, double * max_y)
158 {
159  if (!enabled_) {
160  return;
161  }
162 
163  CostmapFilter::updateBounds(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);
164 
165  if (!has_updated_data_) {
166  return;
167  }
168 
169  double wx, wy;
170 
171  layered_costmap_->getCostmap()->mapToWorld(x_, y_, wx, wy);
172  *min_x = std::min(wx, *min_x);
173  *min_y = std::min(wy, *min_y);
174 
175  layered_costmap_->getCostmap()->mapToWorld(x_ + width_, y_ + height_, wx, wy);
176  *max_x = std::max(wx, *max_x);
177  *max_y = std::max(wy, *max_y);
178 
179  has_updated_data_ = false;
180 }
181 
183  nav2_costmap_2d::Costmap2D & master_grid,
184  int min_i, int min_j, int max_i, int max_j,
185  const geometry_msgs::msg::Pose2D & /*pose*/)
186 {
187  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
188 
189  if (!mask_costmap_) {
190  // Show warning message every 2 seconds to not litter an output
191  RCLCPP_WARN_THROTTLE(
192  logger_, *(clock_), 2000,
193  "KeepoutFilter: Filter mask was not received");
194  return;
195  }
196 
197  tf2::Transform tf2_transform;
198  tf2_transform.setIdentity(); // initialize by identical transform
199  int mg_min_x, mg_min_y; // masger_grid indexes of bottom-left window corner
200  int mg_max_x, mg_max_y; // masger_grid indexes of top-right window corner
201 
202  if (mask_frame_ != global_frame_) {
203  // Filter mask and current layer are in different frames:
204  // prepare frame transformation if mask_frame_ != global_frame_
205  geometry_msgs::msg::TransformStamped transform;
206  try {
207  transform = tf_->lookupTransform(
208  mask_frame_, global_frame_, tf2::TimePointZero,
210  } catch (tf2::TransformException & ex) {
211  RCLCPP_ERROR(
212  logger_,
213  "KeepoutFilter: Failed to get costmap frame (%s) "
214  "transformation to mask frame (%s) with error: %s",
215  global_frame_.c_str(), mask_frame_.c_str(), ex.what());
216  return;
217  }
218  tf2::fromMsg(transform.transform, tf2_transform);
219 
220  mg_min_x = min_i;
221  mg_min_y = min_j;
222  mg_max_x = max_i;
223  mg_max_y = max_j;
224  } else {
225  // Filter mask and current layer are in the same frame:
226  // apply the following optimization - iterate only in overlapped
227  // (min_i, min_j)..(max_i, max_j) & mask_costmap_ area.
228  //
229  // mask_costmap_
230  // *----------------------------*
231  // | |
232  // | |
233  // | (2) |
234  // *-----+-------* |
235  // | |///////|<- overlapped area |
236  // | |///////| to iterate in |
237  // | *-------+--------------------*
238  // | (1) |
239  // | |
240  // *-------------*
241  // master_grid (min_i, min_j)..(max_i, max_j) window
242  //
243  // ToDo: after costmap rotation will be added, this should be re-worked.
244 
245  double wx, wy; // world coordinates
246 
247  // Calculating bounds corresponding to bottom-left overlapping (1) corner
248  // mask_costmap_ -> master_grid intexes conversion
249  const double half_cell_size = 0.5 * mask_costmap_->getResolution();
250  wx = mask_costmap_->getOriginX() + half_cell_size;
251  wy = mask_costmap_->getOriginY() + half_cell_size;
252  master_grid.worldToMapNoBounds(wx, wy, mg_min_x, mg_min_y);
253  // Calculation of (1) corner bounds
254  if (mg_min_x >= max_i || mg_min_y >= max_j) {
255  // There is no overlapping. Do nothing.
256  return;
257  }
258  mg_min_x = std::max(min_i, mg_min_x);
259  mg_min_y = std::max(min_j, mg_min_y);
260 
261  // Calculating bounds corresponding to top-right window (2) corner
262  // mask_costmap_ -> master_grid intexes conversion
263  wx = mask_costmap_->getOriginX() +
264  mask_costmap_->getSizeInCellsX() * mask_costmap_->getResolution() + half_cell_size;
265  wy = mask_costmap_->getOriginY() +
266  mask_costmap_->getSizeInCellsY() * mask_costmap_->getResolution() + half_cell_size;
267  master_grid.worldToMapNoBounds(wx, wy, mg_max_x, mg_max_y);
268  // Calculation of (2) corner bounds
269  if (mg_max_x <= min_i || mg_max_y <= min_j) {
270  // There is no overlapping. Do nothing.
271  return;
272  }
273  mg_max_x = std::min(max_i, mg_max_x);
274  mg_max_y = std::min(max_j, mg_max_y);
275  }
276 
277  // unsigned<-signed conversions.
278  unsigned const int mg_min_x_u = static_cast<unsigned int>(mg_min_x);
279  unsigned const int mg_min_y_u = static_cast<unsigned int>(mg_min_y);
280  unsigned const int mg_max_x_u = static_cast<unsigned int>(mg_max_x);
281  unsigned const int mg_max_y_u = static_cast<unsigned int>(mg_max_y);
282 
283  unsigned int i, j; // master_grid iterators
284  unsigned int index; // corresponding index of master_grid
285  double gl_wx, gl_wy; // world coordinates in a global_frame_
286  double msk_wx, msk_wy; // world coordinates in a mask_frame_
287  unsigned int mx, my; // mask_costmap_ coordinates
288  unsigned char data, old_data; // master_grid element data
289 
290  // Main master_grid updating loop
291  // Iterate in costmap window by master_grid indexes
292  unsigned char * master_array = master_grid.getCharMap();
293  for (i = mg_min_x_u; i < mg_max_x_u; i++) {
294  for (j = mg_min_y_u; j < mg_max_y_u; j++) {
295  index = master_grid.getIndex(i, j);
296  old_data = master_array[index];
297  // Calculating corresponding to (i, j) point at mask_costmap_:
298  // Get world coordinates in global_frame_
299  master_grid.mapToWorld(i, j, gl_wx, gl_wy);
300  if (mask_frame_ != global_frame_) {
301  // Transform (i, j) point from global_frame_ to mask_frame_
302  tf2::Vector3 point(gl_wx, gl_wy, 0);
303  point = tf2_transform * point;
304  msk_wx = point.x();
305  msk_wy = point.y();
306  } else {
307  // In this case master_grid and filter-mask are in the same frame
308  msk_wx = gl_wx;
309  msk_wy = gl_wy;
310  }
311  // Get mask coordinates corresponding to (i, j) point at mask_costmap_
312  if (mask_costmap_->worldToMap(msk_wx, msk_wy, mx, my)) {
313  data = mask_costmap_->getCost(mx, my);
314  // Update if mask_ data is valid and greater than existing master_grid's one
315  if (data == NO_INFORMATION) {
316  continue;
317  }
318  if (data > old_data || old_data == NO_INFORMATION) {
319  master_array[index] = data;
320  }
321  }
322  }
323  }
324 }
325 
327 {
328  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
329 
330  filter_info_sub_.reset();
331  mask_sub_.reset();
332 }
333 
335 {
336  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
337 
338  if (mask_costmap_) {
339  return true;
340  }
341  return false;
342 }
343 
344 } // namespace nav2_costmap_2d
345 
346 #include "pluginlib/class_list_macros.hpp"
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:68
void mapToWorld(unsigned int mx, unsigned int my, double &wx, double &wy) const
Convert from map coordinates to world coordinates.
Definition: costmap_2d.cpp:281
unsigned int getIndex(unsigned int mx, unsigned int my) const
Given two map coordinates... compute the associated index.
Definition: costmap_2d.hpp:211
unsigned char * getCharMap() const
Will return a pointer to the underlying unsigned char array used as the costmap.
Definition: costmap_2d.cpp:261
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:302
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
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 process(nav2_costmap_2d::Costmap2D &master_grid, int min_i, int min_j, int max_i, int max_j, const geometry_msgs::msg::Pose2D &pose)
Process the keepout layer at the current pose / bounds / grid.
void resetFilter()
Reset the costmap filter / topic / info.
void initializeFilter(const std::string &filter_info_topic)
Initialize the filter and subscribe to the info topic.
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.
Abstract class for layered costmap plugin implementations.
Definition: layer.hpp:59
Costmap2D * getCostmap()
Get the costmap pointer to the master costmap.