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 
151  nav2_costmap_2d::Costmap2D & master_grid,
152  int min_i, int min_j, int max_i, int max_j,
153  const geometry_msgs::msg::Pose2D & /*pose*/)
154 {
155  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
156 
157  if (!mask_costmap_) {
158  // Show warning message every 2 seconds to not litter an output
159  RCLCPP_WARN_THROTTLE(
160  logger_, *(clock_), 2000,
161  "KeepoutFilter: Filter mask was not received");
162  return;
163  }
164 
165  tf2::Transform tf2_transform;
166  tf2_transform.setIdentity(); // initialize by identical transform
167  int mg_min_x, mg_min_y; // masger_grid indexes of bottom-left window corner
168  int mg_max_x, mg_max_y; // masger_grid indexes of top-right window corner
169 
170  if (mask_frame_ != global_frame_) {
171  // Filter mask and current layer are in different frames:
172  // prepare frame transformation if mask_frame_ != global_frame_
173  geometry_msgs::msg::TransformStamped transform;
174  try {
175  transform = tf_->lookupTransform(
176  mask_frame_, global_frame_, tf2::TimePointZero,
178  } catch (tf2::TransformException & ex) {
179  RCLCPP_ERROR(
180  logger_,
181  "KeepoutFilter: Failed to get costmap frame (%s) "
182  "transformation to mask frame (%s) with error: %s",
183  global_frame_.c_str(), mask_frame_.c_str(), ex.what());
184  return;
185  }
186  tf2::fromMsg(transform.transform, tf2_transform);
187 
188  mg_min_x = min_i;
189  mg_min_y = min_j;
190  mg_max_x = max_i;
191  mg_max_y = max_j;
192  } else {
193  // Filter mask and current layer are in the same frame:
194  // apply the following optimization - iterate only in overlapped
195  // (min_i, min_j)..(max_i, max_j) & mask_costmap_ area.
196  //
197  // mask_costmap_
198  // *----------------------------*
199  // | |
200  // | |
201  // | (2) |
202  // *-----+-------* |
203  // | |///////|<- overlapped area |
204  // | |///////| to iterate in |
205  // | *-------+--------------------*
206  // | (1) |
207  // | |
208  // *-------------*
209  // master_grid (min_i, min_j)..(max_i, max_j) window
210  //
211  // ToDo: after costmap rotation will be added, this should be re-worked.
212 
213  double wx, wy; // world coordinates
214 
215  // Calculating bounds corresponding to bottom-left overlapping (1) corner
216  // mask_costmap_ -> master_grid intexes conversion
217  const double half_cell_size = 0.5 * mask_costmap_->getResolution();
218  wx = mask_costmap_->getOriginX() + half_cell_size;
219  wy = mask_costmap_->getOriginY() + half_cell_size;
220  master_grid.worldToMapNoBounds(wx, wy, mg_min_x, mg_min_y);
221  // Calculation of (1) corner bounds
222  if (mg_min_x >= max_i || mg_min_y >= max_j) {
223  // There is no overlapping. Do nothing.
224  return;
225  }
226  mg_min_x = std::max(min_i, mg_min_x);
227  mg_min_y = std::max(min_j, mg_min_y);
228 
229  // Calculating bounds corresponding to top-right window (2) corner
230  // mask_costmap_ -> master_grid intexes conversion
231  wx = mask_costmap_->getOriginX() +
232  mask_costmap_->getSizeInCellsX() * mask_costmap_->getResolution() + half_cell_size;
233  wy = mask_costmap_->getOriginY() +
234  mask_costmap_->getSizeInCellsY() * mask_costmap_->getResolution() + half_cell_size;
235  master_grid.worldToMapNoBounds(wx, wy, mg_max_x, mg_max_y);
236  // Calculation of (2) corner bounds
237  if (mg_max_x <= min_i || mg_max_y <= min_j) {
238  // There is no overlapping. Do nothing.
239  return;
240  }
241  mg_max_x = std::min(max_i, mg_max_x);
242  mg_max_y = std::min(max_j, mg_max_y);
243  }
244 
245  // unsigned<-signed conversions.
246  unsigned const int mg_min_x_u = static_cast<unsigned int>(mg_min_x);
247  unsigned const int mg_min_y_u = static_cast<unsigned int>(mg_min_y);
248  unsigned const int mg_max_x_u = static_cast<unsigned int>(mg_max_x);
249  unsigned const int mg_max_y_u = static_cast<unsigned int>(mg_max_y);
250 
251  unsigned int i, j; // master_grid iterators
252  unsigned int index; // corresponding index of master_grid
253  double gl_wx, gl_wy; // world coordinates in a global_frame_
254  double msk_wx, msk_wy; // world coordinates in a mask_frame_
255  unsigned int mx, my; // mask_costmap_ coordinates
256  unsigned char data, old_data; // master_grid element data
257 
258  // Main master_grid updating loop
259  // Iterate in costmap window by master_grid indexes
260  unsigned char * master_array = master_grid.getCharMap();
261  for (i = mg_min_x_u; i < mg_max_x_u; i++) {
262  for (j = mg_min_y_u; j < mg_max_y_u; j++) {
263  index = master_grid.getIndex(i, j);
264  old_data = master_array[index];
265  // Calculating corresponding to (i, j) point at mask_costmap_:
266  // Get world coordinates in global_frame_
267  master_grid.mapToWorld(i, j, gl_wx, gl_wy);
268  if (mask_frame_ != global_frame_) {
269  // Transform (i, j) point from global_frame_ to mask_frame_
270  tf2::Vector3 point(gl_wx, gl_wy, 0);
271  point = tf2_transform * point;
272  msk_wx = point.x();
273  msk_wy = point.y();
274  } else {
275  // In this case master_grid and filter-mask are in the same frame
276  msk_wx = gl_wx;
277  msk_wy = gl_wy;
278  }
279  // Get mask coordinates corresponding to (i, j) point at mask_costmap_
280  if (mask_costmap_->worldToMap(msk_wx, msk_wy, mx, my)) {
281  data = mask_costmap_->getCost(mx, my);
282  // Update if mask_ data is valid and greater than existing master_grid's one
283  if (data == NO_INFORMATION) {
284  continue;
285  }
286  if (data > old_data || old_data == NO_INFORMATION) {
287  master_array[index] = data;
288  }
289  }
290  }
291  }
292 }
293 
295 {
296  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
297 
298  filter_info_sub_.reset();
299  mask_sub_.reset();
300 }
301 
303 {
304  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
305 
306  if (mask_costmap_) {
307  return true;
308  }
309  return false;
310 }
311 
312 } // namespace nav2_costmap_2d
313 
314 #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
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.
Abstract class for layered costmap plugin implementations.
Definition: layer.hpp:59