Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
smac_planner_hybrid_impl.hpp
1 // Copyright (c) 2020, Samsung Research America
2 // Copyright (c) 2023, Open Navigation LLC
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License. Reserved.
15 
16 #ifndef NAV2_SMAC_PLANNER__SMAC_PLANNER_HYBRID_IMPL_HPP_
17 #define NAV2_SMAC_PLANNER__SMAC_PLANNER_HYBRID_IMPL_HPP_
18 
19 #include <algorithm>
20 #include <limits>
21 #include <memory>
22 #include <string>
23 #include <tuple>
24 #include <utility>
25 #include <vector>
26 
27 #include "nav2_smac_planner/smac_planner_hybrid.hpp"
28 #include "nav2_ros_common/tf2_factories.hpp"
29 
30 // #define BENCHMARK_TESTING
31 
32 namespace nav2_smac_planner
33 {
34 
35 using namespace std::chrono; // NOLINT
36 using rcl_interfaces::msg::ParameterType;
37 using std::placeholders::_1;
38 
39 template<typename NodeT>
41 : _a_star(nullptr),
42  _collision_checker(nullptr, 1, nullptr),
43  _smoother(nullptr),
44  _costmap(nullptr),
45  _costmap_ros(nullptr),
46  _costmap_downsampler(nullptr)
47 {
48 }
49 
50 template<typename NodeT>
52 {
53  RCLCPP_INFO(
54  _logger, "Destroying plugin %s of type SmacPlannerHybrid",
55  _name.c_str());
56 }
57 
58 template<typename NodeT>
60  const nav2::LifecycleNode::WeakPtr & parent,
61  std::string name, nav2::TransformBuffer::SharedPtr/*tf*/,
62  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
63 {
64  _node = parent;
65  auto node = parent.lock();
66  _logger = node->get_logger();
67  _clock = node->get_clock();
68  _costmap = costmap_ros->getCostmap();
69  _costmap_ros = costmap_ros;
70  _name = name;
71  _global_frame = costmap_ros->getGlobalFrameID();
72 
73  RCLCPP_INFO(_logger, "Configuring %s of type SmacPlannerHybrid", name.c_str());
74 
75  int angle_quantizations;
76  bool smooth_path;
77 
78  // General planner params
79  _downsample_costmap = node->declare_or_get_parameter(name + ".downsample_costmap", false);
80  _downsampling_factor = node->declare_or_get_parameter(name + ".downsampling_factor", 1);
81 
82  angle_quantizations = node->declare_or_get_parameter(name + ".angle_quantization_bins", 72);
83  _angle_bin_size = 2.0 * M_PI / angle_quantizations;
84  _angle_quantizations = static_cast<unsigned int>(angle_quantizations);
85 
86  _tolerance = static_cast<float>(node->declare_or_get_parameter(name + ".tolerance", 0.25));
87  _allow_unknown = node->declare_or_get_parameter(name + ".allow_unknown", true);
88  _max_iterations = node->declare_or_get_parameter(name + ".max_iterations", 1000000);
89  _max_on_approach_iterations =
90  node->declare_or_get_parameter(name + ".max_on_approach_iterations", 1000);
91  _terminal_checking_interval =
92  node->declare_or_get_parameter(name + ".terminal_checking_interval", 5000);
93  smooth_path = node->declare_or_get_parameter(name + ".smooth_path", true);
94 
95  _minimum_turning_radius_global_coords =
96  node->declare_or_get_parameter(name + ".minimum_turning_radius", 0.4);
97  _search_info.allow_primitive_interpolation =
98  node->declare_or_get_parameter(name + ".allow_primitive_interpolation", true);
99  _search_info.cache_obstacle_heuristic =
100  node->declare_or_get_parameter(name + ".cache_obstacle_heuristic", false);
101  _search_info.reverse_penalty =
102  node->declare_or_get_parameter(name + ".reverse_penalty", 2.0);
103  _search_info.change_penalty =
104  node->declare_or_get_parameter(name + ".change_penalty", 0.0);
105  _search_info.non_straight_penalty =
106  node->declare_or_get_parameter(name + ".non_straight_penalty", 1.2);
107  _search_info.cost_penalty =
108  node->declare_or_get_parameter(name + ".cost_penalty", 2.0);
109  _search_info.retrospective_penalty =
110  node->declare_or_get_parameter(name + ".retrospective_penalty", 0.015);
111  _search_info.analytic_expansion_ratio =
112  node->declare_or_get_parameter(name + ".analytic_expansion_ratio", 3.5);
113  _search_info.analytic_expansion_max_cost =
114  node->declare_or_get_parameter(name + ".analytic_expansion_max_cost", 200.0);
115  _search_info.analytic_expansion_max_cost_override =
116  node->declare_or_get_parameter(name + ".analytic_expansion_max_cost_override", false);
117  _search_info.use_quadratic_cost_penalty =
118  node->declare_or_get_parameter(name + ".use_quadratic_cost_penalty", false);
119  _search_info.downsample_obstacle_heuristic =
120  node->declare_or_get_parameter(name + ".downsample_obstacle_heuristic", true);
121 
122  _analytic_expansion_max_length_m =
123  node->declare_or_get_parameter(name + ".analytic_expansion_max_length", 3.0);
124 
125  _max_planning_time = node->declare_or_get_parameter(name + ".max_planning_time", 5.0);
126  _lookup_table_size = node->declare_or_get_parameter(name + ".lookup_table_size", 20.0);
127 
128  _debug_visualizations = node->declare_or_get_parameter(name + ".debug_visualizations", false);
129 
130  _motion_model_for_search =
131  node->declare_or_get_parameter(name + ".motion_model_for_search", std::string("DUBIN"));
132 
133  std::string goal_heading_type =
134  node->declare_or_get_parameter(name + ".goal_heading_mode", std::string("DEFAULT"));
135  _goal_heading_mode = fromStringToGH(goal_heading_type);
136 
137  _coarse_search_resolution =
138  node->declare_or_get_parameter(name + ".coarse_search_resolution", 1);
139 
140  if (_goal_heading_mode == GoalHeadingMode::UNKNOWN) {
141  std::string error_msg = "Unable to get GoalHeader type. Given '" + goal_heading_type + "' "
142  "Valid options are DEFAULT, BIDIRECTIONAL, ALL_DIRECTION. ";
143  throw nav2_core::PlannerException(error_msg);
144  }
145 
146  _motion_model = fromString(_motion_model_for_search);
147 
148  if (_motion_model == MotionModel::UNKNOWN) {
149  RCLCPP_WARN(
150  _logger,
151  "Unable to get MotionModel search type. Given '%s', "
152  "valid options are MOORE, VON_NEUMANN, DUBIN, REEDS_SHEPP, STATE_LATTICE.",
153  _motion_model_for_search.c_str());
154  }
155 
156  if (_max_on_approach_iterations <= 0) {
157  RCLCPP_WARN(
158  _logger, "On approach iteration selected as <= 0, "
159  "disabling tolerance and on approach iterations.");
160  _max_on_approach_iterations = std::numeric_limits<int>::max();
161  }
162 
163  if (_max_iterations <= 0) {
164  RCLCPP_WARN(
165  _logger, "maximum iteration selected as <= 0, "
166  "disabling maximum iterations.");
167  _max_iterations = std::numeric_limits<int>::max();
168  }
169 
170  if (_coarse_search_resolution <= 0) {
171  RCLCPP_WARN(
172  _logger, "coarse iteration resolution selected as <= 0, "
173  "disabling coarse iteration resolution search for goal heading"
174  );
175 
176  _coarse_search_resolution = 1;
177  }
178 
179  if (angle_quantizations <= 0 || angle_quantizations > 1024) {
180  std::string error_msg = "angle_quantization_bins must be in [1, 1024]";
181  throw nav2_core::PlannerException(error_msg);
182  }
183 
184  if (_angle_quantizations % _coarse_search_resolution != 0) {
185  std::string error_msg = "coarse iteration should be an increment"
186  " of the number of angular bins configured";
187  throw nav2_core::PlannerException(error_msg);
188  }
189 
190  if (_analytic_expansion_max_length_m < 0.0 ||
191  _analytic_expansion_max_length_m > 1000.0)
192  {
193  std::string error_msg = "analytic_expansion_max_length must be in [0.0, 1000.0]";
194  throw nav2_core::PlannerException(error_msg);
195  }
196 
197  if (_lookup_table_size < 0.0 || _lookup_table_size > 20.0) {
198  std::string error_msg = "lookup_table_size must be in [0.0, 20.0]";
199  throw nav2_core::PlannerException(error_msg);
200  }
201 
202  if (_minimum_turning_radius_global_coords < _costmap->getResolution() * _downsampling_factor) {
203  RCLCPP_WARN(
204  _logger, "Min turning radius cannot be less than the search grid cell resolution!");
205  _minimum_turning_radius_global_coords = _costmap->getResolution() * _downsampling_factor;
206  }
207 
208  // convert to grid coordinates
209  if (!_downsample_costmap) {
210  _downsampling_factor = 1;
211  }
212  updateSearchResolution(_costmap->getResolution() * _downsampling_factor);
213 
214  // Initialize collision checker
215  _collision_checker = GridCollisionChecker(_costmap_ros, _angle_quantizations, node);
216  _collision_checker.setFootprint(
217  _costmap_ros->getRobotFootprint(),
218  _costmap_ros->getUseRadius(),
219  findCircumscribedCost(_costmap_ros));
220 
221  // Initialize A* template
222  _a_star = std::make_unique<AStarAlgorithm<NodeT>>(_motion_model, _search_info);
223  _a_star->initialize(
224  _allow_unknown,
225  _max_iterations,
226  _max_on_approach_iterations,
227  _terminal_checking_interval,
228  _max_planning_time,
229  _lookup_table_dim,
230  _angle_quantizations);
231 
232  // Initialize path smoother
233  SmootherParams params;
234  params.get(node, name);
235  if (smooth_path) {
236  _smoother = std::make_unique<Smoother>(params);
237  _smoother->initialize(_minimum_turning_radius_global_coords);
238  }
239 
240  // Initialize costmap downsampler
241  _costmap_downsampler = std::make_unique<CostmapDownsampler>();
242  std::string topic_name = "downsampled_costmap";
243  _costmap_downsampler->on_configure(
244  node, _global_frame, topic_name, _costmap, _downsampling_factor);
245 
246  _raw_plan_publisher = node->create_publisher<nav_msgs::msg::Path>("unsmoothed_plan");
247 
248  if (_debug_visualizations) {
249  _expansions_publisher = node->create_publisher<geometry_msgs::msg::PoseArray>("expansions");
250  _planned_footprints_publisher = node->create_publisher<visualization_msgs::msg::MarkerArray>(
251  "planned_footprints");
252  _smoothed_footprints_publisher =
253  node->create_publisher<visualization_msgs::msg::MarkerArray>(
254  "smoothed_footprints");
255  }
256 
257  RCLCPP_INFO(
258  _logger, "Configured plugin %s of type SmacPlannerHybrid with "
259  "maximum iterations %i, max on approach iterations %i, and %s. Tolerance %.2f."
260  "Using motion model: %s.",
261  _name.c_str(), _max_iterations, _max_on_approach_iterations,
262  _allow_unknown ? "allowing unknown traversal" : "not allowing unknown traversal",
263  _tolerance, toString(_motion_model).c_str());
264 }
265 
266 template<typename NodeT>
268 {
269  RCLCPP_INFO(
270  _logger, "Activating plugin %s of type SmacPlannerHybrid",
271  _name.c_str());
272  _raw_plan_publisher->on_activate();
273  if (_debug_visualizations) {
274  _expansions_publisher->on_activate();
275  _planned_footprints_publisher->on_activate();
276  _smoothed_footprints_publisher->on_activate();
277  }
278  if (_costmap_downsampler) {
279  _costmap_downsampler->on_activate();
280  }
281  auto node = _node.lock();
282  // Add callback for dynamic parameters
283  _post_set_params_handler = node->add_post_set_parameters_callback(
284  std::bind(
286  this, std::placeholders::_1));
287  _on_set_params_handler = node->add_on_set_parameters_callback(
288  std::bind(
290  this, std::placeholders::_1));
291 }
292 
293 template<typename NodeT>
295 {
296  RCLCPP_INFO(
297  _logger, "Deactivating plugin %s of type SmacPlannerHybrid",
298  _name.c_str());
299  _raw_plan_publisher->on_deactivate();
300  if (_debug_visualizations) {
301  _expansions_publisher->on_deactivate();
302  _planned_footprints_publisher->on_deactivate();
303  _smoothed_footprints_publisher->on_deactivate();
304  }
305  if (_costmap_downsampler) {
306  _costmap_downsampler->on_deactivate();
307  }
308  // shutdown dyn_param_handler
309  auto node = _node.lock();
310  if (_post_set_params_handler && node) {
311  node->remove_post_set_parameters_callback(_post_set_params_handler.get());
312  }
313  _post_set_params_handler.reset();
314  if (_on_set_params_handler && node) {
315  node->remove_on_set_parameters_callback(_on_set_params_handler.get());
316  }
317  _on_set_params_handler.reset();
318 }
319 
320 template<typename NodeT>
322 {
323  RCLCPP_INFO(
324  _logger, "Cleaning up plugin %s of type SmacPlannerHybrid",
325  _name.c_str());
326  _a_star.reset();
327  _smoother.reset();
328  if (_costmap_downsampler) {
329  _costmap_downsampler->on_cleanup();
330  _costmap_downsampler.reset();
331  }
332  _raw_plan_publisher.reset();
333  if (_debug_visualizations) {
334  _expansions_publisher.reset();
335  _planned_footprints_publisher.reset();
336  _smoothed_footprints_publisher.reset();
337  }
338 }
339 
340 template<typename NodeT>
342 {
343  _search_resolution = resolution;
344  _search_info.minimum_turning_radius =
345  std::max(_minimum_turning_radius_global_coords / resolution, 1.0);
346  _search_info.analytic_expansion_max_length = _analytic_expansion_max_length_m / resolution;
347  _lookup_table_dim =
348  static_cast<float>(_lookup_table_size) /
349  static_cast<float>(resolution);
350 
351  // Make sure its a whole number
352  _lookup_table_dim = static_cast<float>(static_cast<int>(_lookup_table_dim));
353 
354  // Make sure its an odd number
355  if (static_cast<int>(_lookup_table_dim) % 2 == 0) {
356  RCLCPP_INFO(
357  _logger,
358  "Even sized heuristic lookup table size set %f, increasing size by 1 to make odd",
359  _lookup_table_dim);
360  _lookup_table_dim += 1.0;
361  }
362 }
363 
364 template<typename NodeT>
366  const geometry_msgs::msg::PoseStamped & start,
367  const geometry_msgs::msg::PoseStamped & goal,
368  const std::vector<geometry_msgs::msg::PoseStamped> & viapoints,
369  std::function<bool()> cancel_checker)
370 {
371  if (!viapoints.empty()) {
372  RCLCPP_WARN(_logger, "Received %zu viapoints, but this planner ignores them",
373  viapoints.size());
374  }
375 
376  std::lock_guard<std::mutex> lock_reinit(_mutex);
377  steady_clock::time_point a = steady_clock::now();
378 
379  std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(_costmap->getMutex()));
380 
381  // Downsample costmap, if required
382  nav2_costmap_2d::Costmap2D * costmap = _costmap;
383  if (_downsample_costmap && _downsampling_factor > 1) {
384  costmap = _costmap_downsampler->downsample(_downsampling_factor);
385  }
386 
387  // The resolution can change, even if the parameter is not updated
388  // (e.g. if the map is provided with a different resolution). Therefore, we
389  // need to check the costmap resolution and update the search resolution if it has changed.
390  if (_search_resolution != costmap->getResolution()) {
391  updateSearchResolution(costmap->getResolution());
392  _a_star = std::make_unique<AStarAlgorithm<NodeT>>(_motion_model, _search_info);
393  _a_star->initialize(
394  _allow_unknown, _max_iterations, _max_on_approach_iterations,
395  _terminal_checking_interval, _max_planning_time, _lookup_table_dim,
396  _angle_quantizations);
397  }
398  _collision_checker.setCostmap(costmap);
399 
400  // Set collision checker and costmap information
401  _collision_checker.setFootprint(
402  _costmap_ros->getRobotFootprint(),
403  _costmap_ros->getUseRadius(),
404  findCircumscribedCost(_costmap_ros));
405  _a_star->setCollisionChecker(&_collision_checker);
406 
407  // Set starting point, in A* bin search coordinates
408  float mx_start, my_start, mx_goal, my_goal;
409  if (!costmap->worldToMapContinuous(
410  start.pose.position.x,
411  start.pose.position.y,
412  mx_start,
413  my_start))
414  {
416  "Start Coordinates of(" + std::to_string(start.pose.position.x) + ", " +
417  std::to_string(start.pose.position.y) + ") was outside bounds");
418  }
419 
420  double start_orientation_bin = std::round(tf2::getYaw(start.pose.orientation) / _angle_bin_size);
421  while (start_orientation_bin < 0.0) {
422  start_orientation_bin += static_cast<float>(_angle_quantizations);
423  }
424  // This is needed to handle precision issues
425  if (start_orientation_bin >= static_cast<float>(_angle_quantizations)) {
426  start_orientation_bin -= static_cast<float>(_angle_quantizations);
427  }
428  unsigned int start_orientation_bin_int =
429  static_cast<unsigned int>(start_orientation_bin);
430  _a_star->setStart(mx_start, my_start, start_orientation_bin_int);
431 
432  // Set goal point, in A* bin search coordinates
433  if (!costmap->worldToMapContinuous(
434  goal.pose.position.x,
435  goal.pose.position.y,
436  mx_goal,
437  my_goal))
438  {
440  "Goal Coordinates of(" + std::to_string(goal.pose.position.x) + ", " +
441  std::to_string(goal.pose.position.y) + ") was outside bounds");
442  }
443  double goal_orientation_bin = std::round(tf2::getYaw(goal.pose.orientation) / _angle_bin_size);
444  while (goal_orientation_bin < 0.0) {
445  goal_orientation_bin += static_cast<float>(_angle_quantizations);
446  }
447  // This is needed to handle precision issues
448  if (goal_orientation_bin >= static_cast<float>(_angle_quantizations)) {
449  goal_orientation_bin -= static_cast<float>(_angle_quantizations);
450  }
451  unsigned int goal_orientation_bin_int =
452  static_cast<unsigned int>(goal_orientation_bin);
453  _a_star->setGoal(
454  mx_goal, my_goal, static_cast<unsigned int>(goal_orientation_bin_int),
455  _goal_heading_mode, _coarse_search_resolution);
456 
457  // Setup message
458  nav_msgs::msg::Path plan;
459  plan.header.stamp = _clock->now();
460  plan.header.frame_id = _global_frame;
461  geometry_msgs::msg::PoseStamped pose;
462  pose.header = plan.header;
463  pose.pose.position.z = 0.0;
464  pose.pose.orientation.x = 0.0;
465  pose.pose.orientation.y = 0.0;
466  pose.pose.orientation.z = 0.0;
467  pose.pose.orientation.w = 1.0;
468 
469  // Corner case of start and goal being on the same cell
470  if (std::floor(mx_start) == std::floor(mx_goal) &&
471  std::floor(my_start) == std::floor(my_goal) &&
472  start_orientation_bin_int == goal_orientation_bin_int)
473  {
474  pose.pose = start.pose;
475  pose.pose.orientation = goal.pose.orientation;
476  plan.poses.push_back(pose);
477 
478  // Publish raw path for debug
479  if (_raw_plan_publisher->get_subscription_count() > 0) {
480  auto msg = std::make_unique<nav_msgs::msg::Path>(plan);
481  _raw_plan_publisher->publish(std::move(msg));
482  }
483 
484  return plan;
485  }
486 
487  // Compute plan
488  typename NodeT::CoordinateVector path;
489  int num_iterations = 0;
490  std::string error;
491  std::unique_ptr<std::vector<std::tuple<float, float, float>>> expansions = nullptr;
492  if (_debug_visualizations) {
493  expansions = std::make_unique<std::vector<std::tuple<float, float, float>>>();
494  }
495  // Note: All exceptions thrown are handled by the planner server and returned to the action
496  if (!_a_star->createPath(
497  path, num_iterations,
498  _tolerance / static_cast<float>(costmap->getResolution()), cancel_checker, expansions.get()))
499  {
500  if (_debug_visualizations) {
501  auto msg = std::make_unique<geometry_msgs::msg::PoseArray>();
502  geometry_msgs::msg::Pose msg_pose;
503  msg->header.stamp = _clock->now();
504  msg->header.frame_id = _global_frame;
505  for (auto & e : *expansions) {
506  msg_pose.position.x = std::get<0>(e);
507  msg_pose.position.y = std::get<1>(e);
508  msg_pose.orientation = getWorldOrientation(std::get<2>(e));
509  msg->poses.push_back(msg_pose);
510  }
511  _expansions_publisher->publish(std::move(msg));
512  }
513 
514  // Note: If the start is blocked only one iteration will occur before failure
515  if (num_iterations == 1) {
516  throw nav2_core::StartOccupied("Start occupied");
517  }
518 
519  if (num_iterations < _a_star->getMaxIterations()) {
520  throw nav2_core::NoValidPathCouldBeFound("no valid path found");
521  } else {
522  throw nav2_core::PlannerTimedOut("exceeded maximum iterations");
523  }
524  }
525 
526  // Convert to world coordinates
527  plan.poses.reserve(path.size());
528  for (int i = path.size() - 1; i >= 0; --i) {
529  pose.pose = getWorldCoords(path[i].x, path[i].y, costmap);
530  pose.pose.orientation = getWorldOrientation(path[i].theta);
531  plan.poses.push_back(pose);
532  }
533 
534  // Publish raw path for debug
535  if (_raw_plan_publisher->get_subscription_count() > 0) {
536  auto msg = std::make_unique<nav_msgs::msg::Path>(plan);
537  _raw_plan_publisher->publish(std::move(msg));
538  }
539 
540  if (_debug_visualizations) {
541  // Publish expansions for debug
542  auto now = _clock->now();
543  auto msg = std::make_unique<geometry_msgs::msg::PoseArray>();
544  geometry_msgs::msg::Pose msg_pose;
545  msg->header.stamp = now;
546  msg->header.frame_id = _global_frame;
547  for (auto & e : *expansions) {
548  msg_pose.position.x = std::get<0>(e);
549  msg_pose.position.y = std::get<1>(e);
550  msg_pose.orientation = getWorldOrientation(std::get<2>(e));
551  msg->poses.push_back(msg_pose);
552  }
553  _expansions_publisher->publish(std::move(msg));
554 
555  if (_planned_footprints_publisher->get_subscription_count() > 0) {
556  // Clear all markers first
557  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
558  visualization_msgs::msg::Marker clear_all_marker;
559  clear_all_marker.action = visualization_msgs::msg::Marker::DELETEALL;
560  marker_array->markers.push_back(clear_all_marker);
561  _planned_footprints_publisher->publish(std::move(marker_array));
562 
563  // Publish smoothed footprints for debug
564  marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
565  for (size_t i = 0; i < plan.poses.size(); i++) {
566  const std::vector<geometry_msgs::msg::Point> edge =
567  transformFootprintToEdges(plan.poses[i].pose, _costmap_ros->getRobotFootprint());
568  marker_array->markers.push_back(createMarker(edge, i, _global_frame, now));
569  }
570  _planned_footprints_publisher->publish(std::move(marker_array));
571  }
572  }
573 
574  // Find how much time we have left to do smoothing
575  steady_clock::time_point b = steady_clock::now();
576  duration<double> time_span = duration_cast<duration<double>>(b - a);
577  double time_remaining = _max_planning_time - static_cast<double>(time_span.count());
578 
579 #ifdef BENCHMARK_TESTING
580  std::cout << "It took " << time_span.count() * 1000 <<
581  " milliseconds with " << num_iterations << " iterations." << std::endl;
582 #endif
583 
584  // Smooth plan
585  if (_smoother && num_iterations > 1) {
586  _smoother->smooth(
587  plan,
588  costmap,
589  time_remaining,
590  _costmap_ros->getUseRadius() ? std::vector<geometry_msgs::msg::Point>() :
591  _costmap_ros->getRobotFootprint());
592  }
593 
594 #ifdef BENCHMARK_TESTING
595  steady_clock::time_point c = steady_clock::now();
596  duration<double> time_span2 = duration_cast<duration<double>>(c - b);
597  std::cout << "It took " << time_span2.count() * 1000 <<
598  " milliseconds to smooth path." << std::endl;
599 #endif
600 
601  if (_debug_visualizations) {
602  if (_smoothed_footprints_publisher->get_subscription_count() > 0) {
603  // Clear all markers first
604  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
605  visualization_msgs::msg::Marker clear_all_marker;
606  clear_all_marker.action = visualization_msgs::msg::Marker::DELETEALL;
607  marker_array->markers.push_back(clear_all_marker);
608  _smoothed_footprints_publisher->publish(std::move(marker_array));
609 
610  // Publish smoothed footprints for debug
611  marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
612  auto now = _clock->now();
613  for (size_t i = 0; i < plan.poses.size(); i++) {
614  const std::vector<geometry_msgs::msg::Point> edge =
615  transformFootprintToEdges(plan.poses[i].pose, _costmap_ros->getRobotFootprint());
616  marker_array->markers.push_back(createMarker(edge, i, _global_frame, now));
617  }
618  _smoothed_footprints_publisher->publish(std::move(marker_array));
619  }
620  }
621 
622  return plan;
623 }
624 
625 template<typename NodeT>
626 rcl_interfaces::msg::SetParametersResult
628  const std::vector<rclcpp::Parameter> & parameters)
629 {
630  rcl_interfaces::msg::SetParametersResult result;
631  result.successful = true;
632  for (const auto & parameter : parameters) {
633  const auto & param_type = parameter.get_type();
634  const auto & param_name = parameter.get_name();
635  if (param_name.find(_name + ".") != 0) {
636  continue;
637  }
638  if (param_type == ParameterType::PARAMETER_DOUBLE) {
639  if (parameter.as_double() < 0.0) {
640  RCLCPP_WARN(
641  _logger, "The value of parameter '%s' is incorrectly set to %f, "
642  "it should be >=0. Ignoring parameter update.",
643  param_name.c_str(), parameter.as_double());
644  result.successful = false;
645  } else if (param_name == _name + ".lookup_table_size" && // NOLINT
646  parameter.as_double() > 20.0)
647  {
648  RCLCPP_WARN(
649  _logger, "The value of parameter lookup_table_size is incorrectly set to %f, "
650  "it should be in [0.0, 20.0]. Ignoring parameter update.",
651  parameter.as_double());
652  result.successful = false;
653  } else if (param_name == _name + ".analytic_expansion_max_length" && // NOLINT
654  parameter.as_double() > 1000.0)
655  {
656  RCLCPP_WARN(
657  _logger, "The value of parameter analytic_expansion_max_length is incorrectly set "
658  "to %f, it should be <= 1000.0. Ignoring parameter update.",
659  parameter.as_double());
660  result.successful = false;
661  } else if (param_name == _name + ".minimum_turning_radius" && // NOLINT
662  parameter.as_double() < _costmap->getResolution() * _downsampling_factor)
663  {
664  RCLCPP_WARN(
665  _logger, "The value of parameter minimum_turning_radius is incorrectly set to %f, "
666  "it should be >= costmap resolution * downsampling factor (%f). "
667  "Ignoring parameter update.",
668  parameter.as_double(),
669  _costmap->getResolution() * _downsampling_factor);
670  result.successful = false;
671  }
672  } else if (param_type == ParameterType::PARAMETER_INTEGER) {
673  if (parameter.as_int() <= 0 && (param_name != _name + ".max_on_approach_iterations" && // NOLINT
674  param_name != _name + ".max_iterations"))
675  {
676  RCLCPP_WARN(
677  _logger, "The value of parameter '%s' is incorrectly set to %ld, "
678  "it should be >0. Ignoring parameter update.",
679  param_name.c_str(), parameter.as_int());
680  result.successful = false;
681  } else if (param_name == _name + ".angle_quantization_bins") {
682  unsigned int angle_quantizations = static_cast<unsigned int>(parameter.as_int());
683  if (angle_quantizations > 1024) {
684  RCLCPP_WARN(
685  _logger,
686  "The value of parameter angle_quantization_bins is incorrectly set to %u, "
687  "it should be <= 1024. Ignoring parameter update.",
688  angle_quantizations);
689  result.successful = false;
690  } else if (angle_quantizations % _coarse_search_resolution != 0) {
691  RCLCPP_WARN(
692  _logger,
693  "The value of parameter angle_quantization_bins is incorrectly set to %u, "
694  "it should be an increment of the coarse_search_resolution (%u). "
695  "Ignoring parameter update.",
696  angle_quantizations,
697  _coarse_search_resolution);
698  result.successful = false;
699  }
700  } else if (param_name == _name + ".coarse_search_resolution") {
701  if (_angle_quantizations % static_cast<unsigned int>(parameter.as_int()) != 0) {
702  RCLCPP_WARN(
703  _logger,
704  "The value of parameter coarse_search_resolution is incorrectly set to %ld, "
705  "it should be an increment of the angle_quantization_bins (%u). "
706  "Ignoring parameter update.",
707  parameter.as_int(),
708  _angle_quantizations);
709  result.successful = false;
710  }
711  }
712  } else if (param_type == ParameterType::PARAMETER_STRING) {
713  if (param_name == _name + ".motion_model_for_search") {
714  MotionModel motion_model = fromString(parameter.as_string());
715  if (motion_model == MotionModel::UNKNOWN) {
716  RCLCPP_WARN(
717  _logger,
718  "Unable to get MotionModel search type. Given '%s', "
719  "valid options are MOORE, VON_NEUMANN, DUBIN, REEDS_SHEPP, STATE_LATTICE. "
720  "Ignoring parameter update.",
721  parameter.as_string().c_str());
722  result.successful = false;
723  }
724  } else if (param_name == _name + ".goal_heading_mode") {
725  GoalHeadingMode goal_heading_mode = fromStringToGH(parameter.as_string());
726  if (goal_heading_mode == GoalHeadingMode::UNKNOWN) {
727  RCLCPP_WARN(
728  _logger,
729  "Unable to get GoalHeader type. Given '%s' Valid options are DEFAULT, "
730  "BIDIRECTIONAL, ALL_DIRECTION. Ignoring parameter update.",
731  parameter.as_string().c_str());
732  result.successful = false;
733  }
734  }
735  }
736  }
737  return result;
738 }
739 
740 template<typename NodeT>
741 void
743  const std::vector<rclcpp::Parameter> & parameters)
744 {
745  std::lock_guard<std::mutex> lock_reinit(_mutex);
746 
747  bool reinit_collision_checker = false;
748  bool reinit_a_star = false;
749  bool reinit_lookup_table = false;
750  bool reinit_downsampler = false;
751  bool reinit_smoother = false;
752 
753  for (auto parameter : parameters) {
754  const auto & param_type = parameter.get_type();
755  const auto & param_name = parameter.get_name();
756  if (param_name.find(_name + ".") != 0) {
757  continue;
758  }
759  if (param_type == ParameterType::PARAMETER_DOUBLE) {
760  if (param_name == _name + ".max_planning_time") {
761  reinit_a_star = true;
762  _max_planning_time = parameter.as_double();
763  } else if (param_name == _name + ".tolerance") {
764  _tolerance = static_cast<float>(parameter.as_double());
765  } else if (param_name == _name + ".lookup_table_size") {
766  reinit_a_star = true;
767  reinit_lookup_table = true;
768  _lookup_table_size = parameter.as_double();
769  } else if (param_name == _name + ".minimum_turning_radius") {
770  reinit_a_star = true;
771  reinit_lookup_table = true;
772  if (_smoother) {
773  reinit_smoother = true;
774  }
775  _minimum_turning_radius_global_coords = static_cast<float>(parameter.as_double());
776  } else if (param_name == _name + ".reverse_penalty") {
777  reinit_a_star = true;
778  _search_info.reverse_penalty = static_cast<float>(parameter.as_double());
779  } else if (param_name == _name + ".change_penalty") {
780  reinit_a_star = true;
781  _search_info.change_penalty = static_cast<float>(parameter.as_double());
782  } else if (param_name == _name + ".non_straight_penalty") {
783  reinit_a_star = true;
784  _search_info.non_straight_penalty = static_cast<float>(parameter.as_double());
785  } else if (param_name == _name + ".cost_penalty") {
786  reinit_a_star = true;
787  _search_info.cost_penalty = static_cast<float>(parameter.as_double());
788  } else if (param_name == _name + ".analytic_expansion_ratio") {
789  reinit_a_star = true;
790  _search_info.analytic_expansion_ratio = static_cast<float>(parameter.as_double());
791  } else if (param_name == _name + ".analytic_expansion_max_length") {
792  reinit_a_star = true;
793  _analytic_expansion_max_length_m = parameter.as_double();
794  } else if (param_name == _name + ".analytic_expansion_max_cost") {
795  reinit_a_star = true;
796  _search_info.analytic_expansion_max_cost = static_cast<float>(parameter.as_double());
797  }
798  } else if (param_type == ParameterType::PARAMETER_BOOL) {
799  if (param_name == _name + ".downsample_costmap") {
800  reinit_downsampler = true;
801  _downsample_costmap = parameter.as_bool();
802  } else if (param_name == _name + ".allow_unknown") {
803  reinit_a_star = true;
804  _allow_unknown = parameter.as_bool();
805  } else if (param_name == _name + ".cache_obstacle_heuristic") {
806  reinit_a_star = true;
807  _search_info.cache_obstacle_heuristic = parameter.as_bool();
808  } else if (param_name == _name + ".allow_primitive_interpolation") {
809  _search_info.allow_primitive_interpolation = parameter.as_bool();
810  reinit_a_star = true;
811  } else if (param_name == _name + ".smooth_path") {
812  if (parameter.as_bool()) {
813  reinit_smoother = true;
814  } else {
815  _smoother.reset();
816  }
817  } else if (param_name == _name + ".analytic_expansion_max_cost_override") {
818  _search_info.analytic_expansion_max_cost_override = parameter.as_bool();
819  reinit_a_star = true;
820  }
821  } else if (param_type == ParameterType::PARAMETER_INTEGER) {
822  if (param_name == _name + ".downsampling_factor") {
823  reinit_a_star = true;
824  reinit_lookup_table = true;
825  reinit_downsampler = true;
826  _downsampling_factor = parameter.as_int();
827  } else if (param_name == _name + ".max_iterations") {
828  reinit_a_star = true;
829  _max_iterations = parameter.as_int();
830  if (_max_iterations <= 0) {
831  RCLCPP_INFO(
832  _logger, "maximum iteration selected as <= 0, "
833  "disabling maximum iterations.");
834  _max_iterations = std::numeric_limits<int>::max();
835  }
836  } else if (param_name == _name + ".max_on_approach_iterations") {
837  reinit_a_star = true;
838  _max_on_approach_iterations = parameter.as_int();
839  if (_max_on_approach_iterations <= 0) {
840  RCLCPP_INFO(
841  _logger, "On approach iteration selected as <= 0, "
842  "disabling tolerance and on approach iterations.");
843  _max_on_approach_iterations = std::numeric_limits<int>::max();
844  }
845  } else if (param_name == _name + ".terminal_checking_interval") {
846  reinit_a_star = true;
847  _terminal_checking_interval = parameter.as_int();
848  } else if (param_name == _name + ".angle_quantization_bins") {
849  reinit_collision_checker = true;
850  reinit_a_star = true;
851  reinit_lookup_table = true;
852  int angle_quantizations = parameter.as_int();
853  _angle_bin_size = 2.0 * M_PI / angle_quantizations;
854  _angle_quantizations = static_cast<unsigned int>(angle_quantizations);
855  } else if (param_name == _name + ".coarse_search_resolution") {
856  _coarse_search_resolution = parameter.as_int();
857  }
858  } else if (param_type == ParameterType::PARAMETER_STRING) {
859  if (param_name == _name + ".motion_model_for_search") {
860  reinit_a_star = true;
861  reinit_lookup_table = true;
862  _motion_model = fromString(parameter.as_string());
863  } else if (param_name == _name + ".goal_heading_mode") {
864  std::string goal_heading_type = parameter.as_string();
865  GoalHeadingMode goal_heading_mode = fromStringToGH(goal_heading_type);
866  RCLCPP_INFO(
867  _logger,
868  "GoalHeadingMode type set to '%s'.",
869  goal_heading_type.c_str());
870  _goal_heading_mode = goal_heading_mode;
871  }
872  }
873  }
874 
875  if (reinit_a_star || reinit_downsampler || reinit_collision_checker || reinit_smoother) {
876  std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(_costmap->getMutex()));
877  if (_search_resolution != _costmap->getResolution() *
878  (_downsample_costmap ? _downsampling_factor : 1))
879  {
880  reinit_a_star = true;
881  reinit_lookup_table = true;
882  }
883  // convert to grid coordinates
884  if (!_downsample_costmap) {
885  _downsampling_factor = 1;
886  }
887  updateSearchResolution(_costmap->getResolution() * _downsampling_factor);
888  auto node = _node.lock();
889 
890  // Re-Initialize A* template
891  if (reinit_a_star) {
892  if (reinit_lookup_table) {
893  _a_star = std::make_unique<AStarAlgorithm<NodeT>>(_motion_model, _search_info);
894  } else {
895  _a_star->setSearchInfo(_search_info);
896  }
897  _a_star->initialize(
898  _allow_unknown,
899  _max_iterations,
900  _max_on_approach_iterations,
901  _terminal_checking_interval,
902  _max_planning_time,
903  _lookup_table_dim,
904  _angle_quantizations);
905  }
906 
907  // Re-Initialize costmap downsampler
908  if (reinit_downsampler) {
909  if (_downsample_costmap && _downsampling_factor > 1) {
910  std::string topic_name = "downsampled_costmap";
911  _costmap_downsampler = std::make_unique<CostmapDownsampler>();
912  _costmap_downsampler->on_configure(
913  node, _global_frame, topic_name, _costmap, _downsampling_factor);
914  _costmap_downsampler->on_activate();
915  }
916  }
917 
918  // Re-Initialize collision checker
919  if (reinit_collision_checker) {
920  _collision_checker = GridCollisionChecker(_costmap_ros, _angle_quantizations, node);
921  _collision_checker.setFootprint(
922  _costmap_ros->getRobotFootprint(),
923  _costmap_ros->getUseRadius(),
924  findCircumscribedCost(_costmap_ros));
925  }
926 
927  // Re-Initialize smoother
928  if (reinit_smoother) {
929  SmootherParams params;
930  params.get(node, _name);
931  _smoother = std::make_unique<Smoother>(params);
932  _smoother->initialize(_minimum_turning_radius_global_coords);
933  }
934  }
935 }
936 
937 } // namespace nav2_smac_planner
938 
939 #endif // NAV2_SMAC_PLANNER__SMAC_PLANNER_HYBRID_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 hybrid-A* planner that allows custom node types.
void activate() override
Activate lifecycle node.
void updateSearchResolution(double resolution)
Update resolution-dependent search settings from their world-unit values. Converts the turning radius...
void deactivate() override
Deactivate lifecycle node.
void cleanup() override
Cleanup lifecycle node.
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 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.
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.
void updateParametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
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