Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
smac_planner_2d_impl.hpp
1 // Copyright (c) 2020, Samsung Research America
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. Reserved.
14 
15 #ifndef NAV2_SMAC_PLANNER__SMAC_PLANNER_2D_IMPL_HPP_
16 #define NAV2_SMAC_PLANNER__SMAC_PLANNER_2D_IMPL_HPP_
17 
18 #include <algorithm>
19 #include <limits>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 #include "nav2_smac_planner/smac_planner_2d.hpp"
26 #include "nav2_util/geometry_utils.hpp"
27 #include "nav2_ros_common/tf2_factories.hpp"
28 
29 // #define BENCHMARK_TESTING
30 
31 namespace nav2_smac_planner
32 {
33 using namespace std::chrono; // NOLINT
34 using rcl_interfaces::msg::ParameterType;
35 using std::placeholders::_1;
36 
37 template<typename NodeT>
39 : _a_star(nullptr),
40  _collision_checker(nullptr, 1, nullptr),
41  _smoother(nullptr),
42  _costmap(nullptr),
43  _costmap_downsampler(nullptr)
44 {
45 }
46 
47 template<typename NodeT>
49 {
50  RCLCPP_INFO(
51  _logger, "Destroying plugin %s of type SmacPlanner2D",
52  _name.c_str());
53 }
54 
55 template<typename NodeT>
57  const nav2::LifecycleNode::WeakPtr & parent,
58  std::string name, nav2::TransformBuffer::SharedPtr/*tf*/,
59  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
60 {
61  _node = parent;
62  auto node = parent.lock();
63  _logger = node->get_logger();
64  _clock = node->get_clock();
65  _costmap = costmap_ros->getCostmap();
66  _costmap_ros = costmap_ros;
67  _name = name;
68  _global_frame = costmap_ros->getGlobalFrameID();
69 
70  RCLCPP_INFO(_logger, "Configuring %s of type SmacPlanner2D", name.c_str());
71 
72  // General planner params
73  _tolerance = static_cast<float>(node->declare_or_get_parameter(name + ".tolerance", 0.125));
74  _downsample_costmap = node->declare_or_get_parameter(name + ".downsample_costmap", false);
75  _downsampling_factor = node->declare_or_get_parameter(name + ".downsampling_factor", 1);
76  _search_info.cost_penalty =
77  node->declare_or_get_parameter(name + ".cost_travel_multiplier", 1.0);
78 
79  _allow_unknown = node->declare_or_get_parameter(name + ".allow_unknown", true);
80  _max_iterations = node->declare_or_get_parameter(name + ".max_iterations", 1000000);
81  _max_on_approach_iterations =
82  node->declare_or_get_parameter(name + ".max_on_approach_iterations", 1000);
83  _terminal_checking_interval =
84  node->declare_or_get_parameter(name + ".terminal_checking_interval", 5000);
85  _use_final_approach_orientation =
86  node->declare_or_get_parameter(name + ".use_final_approach_orientation", false);
87 
88  _max_planning_time = node->declare_or_get_parameter(name + ".max_planning_time", 2.0);
89 
90  _motion_model = MotionModel::TWOD;
91 
92  if (_max_on_approach_iterations <= 0) {
93  RCLCPP_INFO(
94  _logger, "On approach iteration selected as <= 0, "
95  "disabling tolerance and on approach iterations.");
96  _max_on_approach_iterations = std::numeric_limits<int>::max();
97  }
98 
99  if (_max_iterations <= 0) {
100  RCLCPP_INFO(
101  _logger, "maximum iteration selected as <= 0, "
102  "disabling maximum iterations.");
103  _max_iterations = std::numeric_limits<int>::max();
104  }
105 
106  // Initialize collision checker
107  _collision_checker = GridCollisionChecker(costmap_ros, 1 /*for 2D, most be 1*/, node);
108  _collision_checker.setFootprint(
109  costmap_ros->getRobotFootprint(),
110  true /*for 2D, most use radius*/,
111  0.0 /*for 2D cost at inscribed isn't relevant*/);
112 
113  // Initialize A* template
114  _a_star = std::make_unique<AStarAlgorithm<NodeT>>(_motion_model, _search_info);
115  _a_star->initialize(
116  _allow_unknown,
117  _max_iterations,
118  _max_on_approach_iterations,
119  _terminal_checking_interval,
120  _max_planning_time,
121  0.0 /*unused for 2D*/,
122  1.0 /*unused for 2D*/);
123 
124  // Initialize path smoother
125  SmootherParams params;
126  params.get(node, name);
127  params.holonomic_ = true; // So smoother will treat this as a grid search
128  _smoother = std::make_unique<Smoother>(params);
129  _smoother->initialize(1e-50 /*No valid minimum turning radius for 2D*/);
130 
131  // Initialize costmap downsampler
132  std::string topic_name = "downsampled_costmap";
133  _costmap_downsampler = std::make_unique<CostmapDownsampler>();
134  _costmap_downsampler->on_configure(
135  node, _global_frame, topic_name, _costmap, _downsampling_factor);
136 
137  _raw_plan_publisher = node->create_publisher<nav_msgs::msg::Path>("unsmoothed_plan");
138 
139  RCLCPP_INFO(
140  _logger, "Configured plugin %s of type SmacPlanner2D with "
141  "tolerance %.2f, maximum iterations %i, "
142  "max on approach iterations %i, and %s.",
143  _name.c_str(), _tolerance, _max_iterations, _max_on_approach_iterations,
144  _allow_unknown ? "allowing unknown traversal" : "not allowing unknown traversal");
145 }
146 
147 template<typename NodeT>
149 {
150  RCLCPP_INFO(
151  _logger, "Activating plugin %s of type SmacPlanner2D",
152  _name.c_str());
153  _raw_plan_publisher->on_activate();
154  if (_costmap_downsampler) {
155  _costmap_downsampler->on_activate();
156  }
157  auto node = _node.lock();
158  // Add callback for dynamic parameters
159  _post_set_params_handler = node->add_post_set_parameters_callback(
160  std::bind(
162  this, std::placeholders::_1));
163  _on_set_params_handler = node->add_on_set_parameters_callback(
164  std::bind(
166  this, std::placeholders::_1));
167 }
168 
169 template<typename NodeT>
171 {
172  RCLCPP_INFO(
173  _logger, "Deactivating plugin %s of type SmacPlanner2D",
174  _name.c_str());
175  _raw_plan_publisher->on_deactivate();
176  if (_costmap_downsampler) {
177  _costmap_downsampler->on_deactivate();
178  }
179  // shutdown dyn_param_handler
180  auto node = _node.lock();
181  if (_post_set_params_handler && node) {
182  node->remove_post_set_parameters_callback(_post_set_params_handler.get());
183  }
184  _post_set_params_handler.reset();
185  if (_on_set_params_handler && node) {
186  node->remove_on_set_parameters_callback(_on_set_params_handler.get());
187  }
188  _on_set_params_handler.reset();
189 }
190 
191 template<typename NodeT>
193 {
194  RCLCPP_INFO(
195  _logger, "Cleaning up plugin %s of type SmacPlanner2D",
196  _name.c_str());
197  _a_star.reset();
198  _smoother.reset();
199  if (_costmap_downsampler) {
200  _costmap_downsampler->on_cleanup();
201  _costmap_downsampler.reset();
202  }
203  _raw_plan_publisher.reset();
204 }
205 
206 template<typename NodeT>
208  const geometry_msgs::msg::PoseStamped & start,
209  const geometry_msgs::msg::PoseStamped & goal,
210  const std::vector<geometry_msgs::msg::PoseStamped> & viapoints,
211  std::function<bool()> cancel_checker)
212 {
213  if (!viapoints.empty()) {
214  RCLCPP_WARN(_logger, "Received %zu viapoints, but this planner ignores them",
215  viapoints.size());
216  }
217 
218  std::lock_guard<std::mutex> lock_reinit(_mutex);
219  steady_clock::time_point a = steady_clock::now();
220 
221  std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(_costmap->getMutex()));
222 
223  // Downsample costmap, if required
224  nav2_costmap_2d::Costmap2D * costmap = _costmap;
225  if (_downsample_costmap && _downsampling_factor > 1) {
226  costmap = _costmap_downsampler->downsample(_downsampling_factor);
227  _collision_checker.setCostmap(costmap);
228  }
229 
230  // Set collision checker and costmap information
231  _a_star->setCollisionChecker(&_collision_checker);
232 
233  // Set starting point
234  float mx_start, my_start, mx_goal, my_goal;
235  if (!costmap->worldToMapContinuous(
236  start.pose.position.x,
237  start.pose.position.y,
238  mx_start,
239  my_start))
240  {
242  "Start Coordinates of(" + std::to_string(start.pose.position.x) + ", " +
243  std::to_string(start.pose.position.y) + ") was outside bounds");
244  }
245  _a_star->setStart(mx_start, my_start, 0);
246 
247  // Set goal point
248  if (!costmap->worldToMapContinuous(
249  goal.pose.position.x,
250  goal.pose.position.y,
251  mx_goal,
252  my_goal))
253  {
255  "Goal Coordinates of(" + std::to_string(goal.pose.position.x) + ", " +
256  std::to_string(goal.pose.position.y) + ") was outside bounds");
257  }
258  _a_star->setGoal(mx_goal, my_goal, 0);
259 
260  // Setup message
261  nav_msgs::msg::Path plan;
262  plan.header.stamp = _clock->now();
263  plan.header.frame_id = _global_frame;
264  geometry_msgs::msg::PoseStamped pose;
265  pose.header = plan.header;
266  pose.pose.position.z = 0.0;
267  pose.pose.orientation.x = 0.0;
268  pose.pose.orientation.y = 0.0;
269  pose.pose.orientation.z = 0.0;
270  pose.pose.orientation.w = 1.0;
271 
272  // Corner case of start and goal being on the same cell
273  if (std::floor(mx_start) == std::floor(mx_goal) && std::floor(my_start) == std::floor(my_goal)) {
274  pose.pose = goal.pose;
275  // if we have a different start and goal orientation, set the unique path pose to the goal
276  // orientation, unless use_final_approach_orientation=true where we need it to be the start
277  // orientation to avoid movement from the local planner
278  if (start.pose.orientation != goal.pose.orientation && _use_final_approach_orientation) {
279  pose.pose.orientation = start.pose.orientation;
280  }
281  plan.poses.push_back(pose);
282 
283  // Publish raw path for debug
284  if (_raw_plan_publisher->get_subscription_count() > 0) {
285  auto msg = std::make_unique<nav_msgs::msg::Path>(plan);
286  _raw_plan_publisher->publish(std::move(msg));
287  }
288 
289  return plan;
290  }
291 
292  // Compute plan
293  typename NodeT::CoordinateVector path;
294  int num_iterations = 0;
295  // Note: All exceptions thrown are handled by the planner server and returned to the action
296  if (!_a_star->createPath(
297  path, num_iterations,
298  _tolerance / static_cast<float>(costmap->getResolution()), cancel_checker))
299  {
300  // Note: If the start is blocked only one iteration will occur before failure
301  if (num_iterations == 1) {
302  throw nav2_core::StartOccupied("Start occupied");
303  }
304 
305  if (num_iterations < _a_star->getMaxIterations()) {
306  throw nav2_core::NoValidPathCouldBeFound("no valid path found");
307  } else {
308  throw nav2_core::PlannerTimedOut("exceeded maximum iterations");
309  }
310  }
311 
312  const bool reached_goal_cell =
313  std::floor(path.front().x) == std::floor(mx_goal) &&
314  std::floor(path.front().y) == std::floor(my_goal);
315 
316  // Convert to world coordinates
317  plan.poses.reserve(path.size());
318  for (int i = path.size() - 1; i >= 0; --i) {
319  pose.pose = getWorldCoords(path[i].x, path[i].y, costmap);
320  plan.poses.push_back(pose);
321  }
322 
323  // Publish raw path for debug
324  if (_raw_plan_publisher->get_subscription_count() > 0) {
325  auto msg = std::make_unique<nav_msgs::msg::Path>(plan);
326  _raw_plan_publisher->publish(std::move(msg));
327  }
328 
329  // Find how much time we have left to do smoothing
330  steady_clock::time_point b = steady_clock::now();
331  duration<double> time_span = duration_cast<duration<double>>(b - a);
332  double time_remaining = _max_planning_time - static_cast<double>(time_span.count());
333 
334 #ifdef BENCHMARK_TESTING
335  std::cout << "It took " << time_span.count() * 1000 <<
336  " milliseconds with " << num_iterations << " iterations." << std::endl;
337 #endif
338 
339  // Smooth plan
340  _smoother->smooth(
341  plan,
342  costmap,
343  time_remaining,
344  _costmap_ros->getUseRadius() ? std::vector<geometry_msgs::msg::Point>() :
345  _costmap_ros->getRobotFootprint());
346 
347  // If use_final_approach_orientation=true, interpolate the last pose orientation from the
348  // previous pose to set the orientation to the 'final approach' orientation of the robot so
349  // it does not rotate.
350  // And deal with corner case of plan of length 1
351  // If use_final_approach_orientation=false (default), override last pose orientation to match goal
352  size_t plan_size = plan.poses.size();
353  if (reached_goal_cell && plan_size > 0) {
354  plan.poses.back().pose.position = goal.pose.position;
355  }
356 
357  if (_use_final_approach_orientation) {
358  if (plan_size == 1) {
359  plan.poses.back().pose.orientation = start.pose.orientation;
360  } else if (plan_size > 1) {
361  double dx, dy, theta;
362  auto last_pose = plan.poses.back().pose.position;
363  auto approach_pose = plan.poses[plan_size - 2].pose.position;
364  dx = last_pose.x - approach_pose.x;
365  dy = last_pose.y - approach_pose.y;
366  theta = atan2(dy, dx);
367  plan.poses.back().pose.orientation =
368  nav2_util::geometry_utils::orientationAroundZAxis(theta);
369  }
370  } else if (plan_size > 0) {
371  plan.poses.back().pose.orientation = goal.pose.orientation;
372  }
373 
374  return plan;
375 }
376 
377 template<typename NodeT>
378 rcl_interfaces::msg::SetParametersResult
380  const std::vector<rclcpp::Parameter> & parameters)
381 {
382  rcl_interfaces::msg::SetParametersResult result;
383  result.successful = true;
384  for (const auto & parameter : parameters) {
385  const auto & param_type = parameter.get_type();
386  const auto & param_name = parameter.get_name();
387  if (param_name.find(_name + ".") != 0) {
388  continue;
389  }
390  if (param_type == ParameterType::PARAMETER_DOUBLE) {
391  if (parameter.as_double() < 0.0) {
392  RCLCPP_WARN(
393  _logger, "The value of parameter '%s' is incorrectly set to %f, "
394  "it should be >=0. Ignoring parameter update.",
395  param_name.c_str(), parameter.as_double());
396  result.successful = false;
397  }
398  } else if (param_type == ParameterType::PARAMETER_INTEGER) {
399  if (parameter.as_int() <= 0 &&
400  (param_name != _name + ".max_on_approach_iterations" &&
401  param_name != _name + ".max_iterations"))
402  {
403  RCLCPP_WARN(
404  _logger, "The value of parameter '%s' is incorrectly set to %ld, "
405  "it should be >0. Ignoring parameter update.",
406  param_name.c_str(), parameter.as_int());
407  result.successful = false;
408  }
409  }
410  }
411  return result;
412 }
413 
414 template<typename NodeT>
415 void
416 SmacPlanner2DT<NodeT>::updateParametersCallback(const std::vector<rclcpp::Parameter> & parameters)
417 {
418  std::lock_guard<std::mutex> lock_reinit(_mutex);
419 
420  bool reinit_a_star = false;
421  bool reinit_downsampler = false;
422 
423  for (const auto & parameter : parameters) {
424  const auto & param_type = parameter.get_type();
425  const auto & param_name = parameter.get_name();
426  if (param_name.find(_name + ".") != 0) {
427  continue;
428  }
429  if (param_type == ParameterType::PARAMETER_DOUBLE) {
430  if (param_name == _name + ".tolerance") {
431  _tolerance = static_cast<float>(parameter.as_double());
432  } else if (param_name == _name + ".cost_travel_multiplier") {
433  reinit_a_star = true;
434  _search_info.cost_penalty = parameter.as_double();
435  } else if (param_name == _name + ".max_planning_time") {
436  reinit_a_star = true;
437  _max_planning_time = parameter.as_double();
438  }
439  } else if (param_type == ParameterType::PARAMETER_BOOL) {
440  if (param_name == _name + ".downsample_costmap") {
441  reinit_downsampler = true;
442  _downsample_costmap = parameter.as_bool();
443  } else if (param_name == _name + ".allow_unknown") {
444  reinit_a_star = true;
445  _allow_unknown = parameter.as_bool();
446  } else if (param_name == _name + ".use_final_approach_orientation") {
447  _use_final_approach_orientation = parameter.as_bool();
448  }
449  } else if (param_type == ParameterType::PARAMETER_INTEGER) {
450  if (param_name == _name + ".downsampling_factor") {
451  reinit_downsampler = true;
452  _downsampling_factor = parameter.as_int();
453  } else if (param_name == _name + ".max_iterations") {
454  reinit_a_star = true;
455  _max_iterations = parameter.as_int();
456  if (_max_iterations <= 0) {
457  RCLCPP_INFO(
458  _logger, "maximum iteration selected as <= 0, "
459  "disabling maximum iterations.");
460  _max_iterations = std::numeric_limits<int>::max();
461  }
462  } else if (param_name == _name + ".max_on_approach_iterations") {
463  reinit_a_star = true;
464  _max_on_approach_iterations = parameter.as_int();
465  if (_max_on_approach_iterations <= 0) {
466  RCLCPP_INFO(
467  _logger, "On approach iteration selected as <= 0, "
468  "disabling tolerance and on approach iterations.");
469  _max_on_approach_iterations = std::numeric_limits<int>::max();
470  }
471  } else if (param_name == _name + ".terminal_checking_interval") {
472  reinit_a_star = true;
473  _terminal_checking_interval = parameter.as_int();
474  }
475  }
476  }
477 
478  // Re-init if needed with mutex lock (to avoid re-init while creating a plan)
479  if (reinit_a_star || reinit_downsampler) {
480  // Re-Initialize A* template
481  if (reinit_a_star) {
482  _a_star->setSearchInfo(_search_info);
483  _a_star->initialize(
484  _allow_unknown,
485  _max_iterations,
486  _max_on_approach_iterations,
487  _terminal_checking_interval,
488  _max_planning_time,
489  0.0 /*unused for 2D*/,
490  1.0 /*unused for 2D*/);
491  }
492 
493  // Re-Initialize costmap downsampler
494  if (reinit_downsampler) {
495  if (_downsample_costmap && _downsampling_factor > 1) {
496  auto node = _node.lock();
497  std::string topic_name = "downsampled_costmap";
498  _costmap_downsampler = std::make_unique<CostmapDownsampler>();
499  _costmap_downsampler->on_configure(
500  node, _global_frame, topic_name, _costmap, _downsampling_factor);
501  _costmap_downsampler->on_activate();
502  }
503  }
504  }
505 }
506 
507 } // namespace nav2_smac_planner
508 
509 #endif // NAV2_SMAC_PLANNER__SMAC_PLANNER_2D_IMPL_HPP_
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:69
double getResolution() const
Accessor for the resolution of the costmap.
Definition: costmap_2d.cpp:578
bool worldToMapContinuous(double wx, double wy, float &mx, float &my) const
Convert from world coordinates to map coordinates.
Definition: costmap_2d.cpp:307
A costmap grid collision checker.
A templated 2D planner that allows custom node types.
rcl_interfaces::msg::SetParametersResult validateParameterUpdatesCallback(const std::vector< rclcpp::Parameter > &parameters)
Validate incoming parameter updates before applying them. This callback is triggered when one or more...
void activate() override
Activate lifecycle node.
void updateParametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
void deactivate() override
Deactivate lifecycle node.
void configure(const nav2::LifecycleNode::WeakPtr &parent, std::string name, nav2::TransformBuffer::SharedPtr tf, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros) override
Configuring plugin.
void cleanup() override
Cleanup lifecycle node.
nav_msgs::msg::Path createPlan(const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal, const std::vector< geometry_msgs::msg::PoseStamped > &viapoints, std::function< bool()> cancel_checker) override
Creating a plan from start and goal poses.
Parameters for the smoother cost function.
void get(nav2::LifecycleNode::SharedPtr node, const std::string &name)
Get params from ROS parameter.
Definition: types.hpp:77