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