Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
navfn_planner.cpp
1 // Copyright (c) 2018 Intel Corporation
2 // Copyright (c) 2018 Simbe Robotics
3 // Copyright (c) 2019 Samsung Research America
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 // Navigation Strategy based on:
18 // Brock, O. and Oussama K. (1999). High-Speed Navigation Using
19 // the Global Dynamic Window Approach. IEEE.
20 // https://cs.stanford.edu/group/manips/publications/pdfs/Brock_1999_ICRA.pdf
21 
22 // #define BENCHMARK_TESTING
23 
24 #include "nav2_navfn_planner/navfn_planner.hpp"
25 
26 #include <chrono>
27 #include <cmath>
28 #include <iomanip>
29 #include <iostream>
30 #include <limits>
31 #include <memory>
32 #include <string>
33 #include <vector>
34 
35 #include "builtin_interfaces/msg/duration.hpp"
36 #include "nav2_navfn_planner/navfn.hpp"
37 #include "nav2_util/costmap.hpp"
38 #include "nav2_ros_common/node_utils.hpp"
39 #include "nav2_costmap_2d/cost_values.hpp"
40 #include "nav2_ros_common/tf2_factories.hpp"
41 
42 using namespace std::chrono_literals;
43 using namespace std::chrono; // NOLINT
44 using rcl_interfaces::msg::ParameterType;
45 using std::placeholders::_1;
46 
47 namespace nav2_navfn_planner
48 {
49 
50 NavfnPlanner::NavfnPlanner()
51 : tf_(nullptr), costmap_(nullptr)
52 {
53 }
54 
56 {
57  RCLCPP_INFO(
58  logger_, "Destroying plugin %s of type NavfnPlanner",
59  name_.c_str());
60 }
61 
62 void
64  const nav2::LifecycleNode::WeakPtr & parent,
65  std::string name, nav2::TransformBuffer::SharedPtr tf,
66  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
67 {
68  tf_ = tf;
69  name_ = name;
70  costmap_ = costmap_ros->getCostmap();
71  global_frame_ = costmap_ros->getGlobalFrameID();
72 
73  node_ = parent;
74  auto node = parent.lock();
75  clock_ = node->get_clock();
76  logger_ = node->get_logger();
77 
78  RCLCPP_INFO(
79  logger_, "Configuring plugin %s of type NavfnPlanner",
80  name_.c_str());
81 
82  // Handles storage and dynamic configuration of parameters.
83  // Returns pointer to data current param settings.
84  param_handler_ = std::make_unique<ParameterHandler>(
85  node, name_, logger_);
86  params_ = param_handler_->getParams();
87 
88  // Create a planner based on the new costmap size
89  planner_ = std::make_unique<NavFn>(
90  costmap_->getSizeInCellsX(),
91  costmap_->getSizeInCellsY());
92 }
93 
94 void
96 {
97  RCLCPP_INFO(
98  logger_, "Activating plugin %s of type NavfnPlanner",
99  name_.c_str());
100  param_handler_->activate();
101 }
102 
103 void
105 {
106  RCLCPP_INFO(
107  logger_, "Deactivating plugin %s of type NavfnPlanner",
108  name_.c_str());
109  param_handler_->deactivate();
110 }
111 
112 void
114 {
115  RCLCPP_INFO(
116  logger_, "Cleaning up plugin %s of type NavfnPlanner",
117  name_.c_str());
118  planner_.reset();
119 }
120 
121 nav_msgs::msg::Path NavfnPlanner::createPlan(
122  const geometry_msgs::msg::PoseStamped & start,
123  const geometry_msgs::msg::PoseStamped & goal,
124  const std::vector<geometry_msgs::msg::PoseStamped> & viapoints,
125  std::function<bool()> cancel_checker)
126 {
127 #ifdef BENCHMARK_TESTING
128  steady_clock::time_point a = steady_clock::now();
129 #endif
130 
131  if (!viapoints.empty()) {
132  RCLCPP_WARN(logger_, "Received %zu viapoints, but this planner ignores them",
133  viapoints.size());
134  }
135 
136  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
137  unsigned int mx_start, my_start, mx_goal, my_goal;
138  if (!costmap_->worldToMap(start.pose.position.x, start.pose.position.y, mx_start, my_start)) {
140  "Start Coordinates of(" + std::to_string(start.pose.position.x) + ", " +
141  std::to_string(start.pose.position.y) + ") was outside bounds");
142  }
143 
144  if (!costmap_->worldToMap(goal.pose.position.x, goal.pose.position.y, mx_goal, my_goal)) {
146  "Goal Coordinates of(" + std::to_string(goal.pose.position.x) + ", " +
147  std::to_string(goal.pose.position.y) + ") was outside bounds");
148  }
149 
150  if (params_->tolerance == 0 && costmap_->getCost(mx_goal,
151  my_goal) == nav2_costmap_2d::LETHAL_OBSTACLE)
152  {
154  "Goal Coordinates of(" + std::to_string(goal.pose.position.x) + ", " +
155  std::to_string(goal.pose.position.y) + ") was in lethal cost");
156  }
157 
158  // Update planner based on the new costmap size
159  if (isPlannerOutOfDate()) {
160  planner_->setNavArr(
161  costmap_->getSizeInCellsX(),
162  costmap_->getSizeInCellsY());
163  }
164 
165  nav_msgs::msg::Path path;
166 
167  // Corner case of the start(x,y) = goal(x,y)
168  if (start.pose.position.x == goal.pose.position.x &&
169  start.pose.position.y == goal.pose.position.y)
170  {
171  path.header.stamp = clock_->now();
172  path.header.frame_id = global_frame_;
173  geometry_msgs::msg::PoseStamped pose;
174  pose.header = path.header;
175  pose.pose.position.z = 0.0;
176 
177  pose.pose = start.pose;
178  // if we have a different start and goal orientation, set the unique path pose to the goal
179  // orientation, unless use_final_approach_orientation=true where we need it to be the start
180  // orientation to avoid movement from the local planner
181  if (start.pose.orientation != goal.pose.orientation &&
182  !params_->use_final_approach_orientation)
183  {
184  pose.pose.orientation = goal.pose.orientation;
185  }
186  path.poses.push_back(pose);
187  return path;
188  }
189 
190  if (!makePlan(start.pose, goal.pose, params_->tolerance, cancel_checker, path)) {
192  "Failed to create plan with tolerance of: " + std::to_string(params_->tolerance) );
193  }
194 
195 
196 #ifdef BENCHMARK_TESTING
197  steady_clock::time_point b = steady_clock::now();
198  duration<double> time_span = duration_cast<duration<double>>(b - a);
199  std::cout << "It took " << time_span.count() * 1000 << std::endl;
200 #endif
201 
202  return path;
203 }
204 
205 bool
207 {
208  if (!planner_.get() ||
209  planner_->nx != static_cast<int>(costmap_->getSizeInCellsX()) ||
210  planner_->ny != static_cast<int>(costmap_->getSizeInCellsY()))
211  {
212  return true;
213  }
214  return false;
215 }
216 
217 bool
219  const geometry_msgs::msg::Pose & start,
220  const geometry_msgs::msg::Pose & goal, double tolerance,
221  std::function<bool()> cancel_checker,
222  nav_msgs::msg::Path & plan)
223 {
224  // clear the plan, just in case
225  plan.poses.clear();
226 
227  plan.header.stamp = clock_->now();
228  plan.header.frame_id = global_frame_;
229 
230  double wx = start.position.x;
231  double wy = start.position.y;
232 
233  RCLCPP_DEBUG(
234  logger_, "Making plan from (%.2f,%.2f) to (%.2f,%.2f)",
235  start.position.x, start.position.y, goal.position.x, goal.position.y);
236 
237  unsigned int mx, my;
238  worldToMap(wx, wy, mx, my);
239 
240  // clear the starting cell within the costmap because we know it can't be an obstacle
241  clearRobotCell(mx, my);
242 
243  std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap_->getMutex()));
244 
245  // make sure to resize the underlying array that Navfn uses
246  planner_->setNavArr(
247  costmap_->getSizeInCellsX(),
248  costmap_->getSizeInCellsY());
249 
250  planner_->setCostmap(costmap_->getCharMap(), true, params_->allow_unknown);
251 
252  lock.unlock();
253 
254  int map_start[2];
255  map_start[0] = mx;
256  map_start[1] = my;
257 
258  wx = goal.position.x;
259  wy = goal.position.y;
260 
261  worldToMap(wx, wy, mx, my);
262  int map_goal[2];
263  map_goal[0] = mx;
264  map_goal[1] = my;
265 
266  planner_->setStart(map_goal);
267  planner_->setGoal(map_start);
268  if (params_->use_astar) {
269  planner_->calcNavFnAstar(cancel_checker);
270  } else {
271  planner_->calcNavFnDijkstra(cancel_checker, true);
272  }
273 
274  double resolution = costmap_->getResolution();
275  geometry_msgs::msg::Pose p, best_pose;
276 
277  bool found_legal = false;
278 
279  p = goal;
280  double potential = getPointPotential(p.position);
281  if (potential < POT_HIGH) {
282  // Goal is reachable by itself
283  best_pose = p;
284  found_legal = true;
285  } else {
286  // Goal is not reachable. Trying to find nearest to the goal
287  // reachable point within its tolerance region
288  double best_sdist = std::numeric_limits<double>::max();
289 
290  p.position.y = goal.position.y - tolerance;
291  while (p.position.y <= goal.position.y + tolerance) {
292  p.position.x = goal.position.x - tolerance;
293  while (p.position.x <= goal.position.x + tolerance) {
294  potential = getPointPotential(p.position);
295  if (potential < POT_HIGH) {
296  double sdist = squared_distance(p, goal);
297  if (sdist < best_sdist) {
298  best_sdist = sdist;
299  best_pose = p;
300  found_legal = true;
301  }
302  }
303  p.position.x += resolution;
304  }
305  p.position.y += resolution;
306  }
307  }
308 
309  if (found_legal) {
310  // extract the plan
311  if (getPlanFromPotential(best_pose, plan)) {
312  smoothApproachToGoal(best_pose, plan);
313 
314  // If use_final_approach_orientation=true, interpolate the last pose orientation from the
315  // previous pose to set the orientation to the 'final approach' orientation of the robot so
316  // it does not rotate.
317  // And deal with corner case of plan of length 1
318  if (params_->use_final_approach_orientation) {
319  size_t plan_size = plan.poses.size();
320  if (plan_size == 1) {
321  plan.poses.back().pose.orientation = start.orientation;
322  } else if (plan_size > 1) {
323  double dx, dy, theta;
324  auto last_pose = plan.poses.back().pose.position;
325  auto approach_pose = plan.poses[plan_size - 2].pose.position;
326  // Deal with the case of NavFn producing a path with two equal last poses
327  if (std::abs(last_pose.x - approach_pose.x) < 0.0001 &&
328  std::abs(last_pose.y - approach_pose.y) < 0.0001 && plan_size > 2)
329  {
330  approach_pose = plan.poses[plan_size - 3].pose.position;
331  }
332  dx = last_pose.x - approach_pose.x;
333  dy = last_pose.y - approach_pose.y;
334  theta = atan2(dy, dx);
335  plan.poses.back().pose.orientation =
336  nav2_util::geometry_utils::orientationAroundZAxis(theta);
337  }
338  }
339  } else {
340  RCLCPP_ERROR(
341  logger_,
342  "Failed to create a plan from potential when a legal"
343  " potential was found. This shouldn't happen.");
344  }
345  }
346 
347  return !plan.poses.empty();
348 }
349 
350 void
352  const geometry_msgs::msg::Pose & goal,
353  nav_msgs::msg::Path & plan)
354 {
355  if (plan.poses.size() >= 2) {
356  auto second_to_last_pose = plan.poses.end()[-2];
357  auto last_pose = plan.poses.back();
358  // Replace the last pose of the computed path if it's actually further away
359  // to the second to last pose than the goal pose.
360  if (
361  squared_distance(last_pose.pose, second_to_last_pose.pose) >
362  squared_distance(goal, second_to_last_pose.pose))
363  {
364  plan.poses.back().pose = goal;
365  return;
366  }
367  // Replace the last pose of the computed path if its position matches but orientation differs
368  if (squared_distance(last_pose.pose, goal) < 1e-6) {
369  plan.poses.back().pose = goal;
370  return;
371  }
372  }
373  geometry_msgs::msg::PoseStamped goal_copy;
374  goal_copy.pose = goal;
375  goal_copy.header = plan.header;
376  plan.poses.push_back(goal_copy);
377 }
378 
379 bool
381  const geometry_msgs::msg::Pose & goal,
382  nav_msgs::msg::Path & plan)
383 {
384  // clear the plan, just in case
385  plan.poses.clear();
386 
387  // Goal should be in global frame
388  double wx = goal.position.x;
389  double wy = goal.position.y;
390 
391  // the potential has already been computed, so we won't update our copy of the costmap
392  unsigned int mx, my;
393  worldToMap(wx, wy, mx, my);
394 
395  int map_goal[2];
396  map_goal[0] = mx;
397  map_goal[1] = my;
398 
399  planner_->setStart(map_goal);
400 
401  const int & max_cycles = (costmap_->getSizeInCellsX() >= costmap_->getSizeInCellsY()) ?
402  (costmap_->getSizeInCellsX() * 4) : (costmap_->getSizeInCellsY() * 4);
403 
404  int path_len = planner_->calcPath(max_cycles);
405  if (path_len == 0) {
406  return false;
407  }
408 
409  auto cost = planner_->getLastPathCost();
410  RCLCPP_DEBUG(
411  logger_,
412  "Path found, %d steps, %f cost\n", path_len, cost);
413 
414  // extract the plan
415  float * x = planner_->getPathX();
416  float * y = planner_->getPathY();
417  int len = planner_->getPathLen();
418 
419  for (int i = len - 1; i >= 0; --i) {
420  // convert the plan to world coordinates
421  double world_x, world_y;
422  mapToWorld(x[i], y[i], world_x, world_y);
423 
424  geometry_msgs::msg::PoseStamped pose;
425  pose.header = plan.header;
426  pose.pose.position.x = world_x;
427  pose.pose.position.y = world_y;
428  pose.pose.position.z = 0.0;
429  pose.pose.orientation.x = 0.0;
430  pose.pose.orientation.y = 0.0;
431  pose.pose.orientation.z = 0.0;
432  pose.pose.orientation.w = 1.0;
433  plan.poses.push_back(pose);
434  }
435 
436  return !plan.poses.empty();
437 }
438 
439 double
440 NavfnPlanner::getPointPotential(const geometry_msgs::msg::Point & world_point)
441 {
442  unsigned int mx, my;
443  if (!worldToMap(world_point.x, world_point.y, mx, my)) {
444  return std::numeric_limits<double>::max();
445  }
446 
447  unsigned int index = my * planner_->nx + mx;
448  return planner_->potarr[index];
449 }
450 
451 // bool
452 // NavfnPlanner::validPointPotential(const geometry_msgs::msg::Point & world_point)
453 // {
454 // return validPointPotential(world_point, tolerance_);
455 // }
456 
457 // bool
458 // NavfnPlanner::validPointPotential(
459 // const geometry_msgs::msg::Point & world_point, double tolerance)
460 // {
461 // const double resolution = costmap_->getResolution();
462 
463 // geometry_msgs::msg::Point p = world_point;
464 // double potential = getPointPotential(p);
465 // if (potential < POT_HIGH) {
466 // // world_point is reachable by itself
467 // return true;
468 // } else {
469 // // world_point, is not reachable. Trying to find any
470 // // reachable point within its tolerance region
471 // p.y = world_point.y - tolerance;
472 // while (p.y <= world_point.y + tolerance) {
473 // p.x = world_point.x - tolerance;
474 // while (p.x <= world_point.x + tolerance) {
475 // potential = getPointPotential(p);
476 // if (potential < POT_HIGH) {
477 // return true;
478 // }
479 // p.x += resolution;
480 // }
481 // p.y += resolution;
482 // }
483 // }
484 
485 // return false;
486 // }
487 
488 bool
489 NavfnPlanner::worldToMap(double wx, double wy, unsigned int & mx, unsigned int & my)
490 {
491  if (wx < costmap_->getOriginX() || wy < costmap_->getOriginY()) {
492  return false;
493  }
494 
495  mx = static_cast<int>(
496  std::round((wx - costmap_->getOriginX()) / costmap_->getResolution()));
497  my = static_cast<int>(
498  std::round((wy - costmap_->getOriginY()) / costmap_->getResolution()));
499 
500  if (mx < costmap_->getSizeInCellsX() && my < costmap_->getSizeInCellsY()) {
501  return true;
502  }
503 
504  RCLCPP_ERROR(
505  logger_,
506  "worldToMap failed: mx,my: %d,%d, size_x,size_y: %d,%d", mx, my,
507  costmap_->getSizeInCellsX(), costmap_->getSizeInCellsY());
508 
509  return false;
510 }
511 
512 void
513 NavfnPlanner::mapToWorld(double mx, double my, double & wx, double & wy)
514 {
515  wx = costmap_->getOriginX() + mx * costmap_->getResolution();
516  wy = costmap_->getOriginY() + my * costmap_->getResolution();
517 }
518 
519 void
520 NavfnPlanner::clearRobotCell(unsigned int mx, unsigned int my)
521 {
522  // TODO(orduno): check usage of this function, might instead be a request to
523  // world_model / map server
524  costmap_->setCost(mx, my, nav2_costmap_2d::FREE_SPACE);
525 }
526 
527 } // namespace nav2_navfn_planner
528 
529 #include "pluginlib/class_list_macros.hpp"
Abstract interface for global planners to adhere to with pluginlib.
unsigned char getCost(unsigned int mx, unsigned int my) const
Get the cost of a cell in the costmap.
Definition: costmap_2d.cpp:265
unsigned char * getCharMap() const
Will return a pointer to the underlying unsigned char array used as the costmap.
Definition: costmap_2d.cpp:260
bool worldToMap(double wx, double wy, unsigned int &mx, unsigned int &my) const
Convert from world coordinates to map coordinates.
Definition: costmap_2d.cpp:292
double getResolution() const
Accessor for the resolution of the costmap.
Definition: costmap_2d.cpp:578
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
Definition: costmap_2d.cpp:548
double getOriginY() const
Accessor for the y origin of the costmap.
Definition: costmap_2d.cpp:573
unsigned int getSizeInCellsY() const
Accessor for the y size of the costmap in cells.
Definition: costmap_2d.cpp:553
double getOriginX() const
Accessor for the x origin of the costmap.
Definition: costmap_2d.cpp:568
void setCost(unsigned int mx, unsigned int my, unsigned char cost)
Set the cost of a cell in the costmap.
Definition: costmap_2d.cpp:275
double squared_distance(const geometry_msgs::msg::Pose &p1, const geometry_msgs::msg::Pose &p2)
Compute the squared distance between two points.
double getPointPotential(const geometry_msgs::msg::Point &world_point)
Compute the potential, or navigation cost, at a given point in the world must call computePotential f...
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 activate() override
Activate lifecycle node.
void mapToWorld(double mx, double my, double &wx, double &wy)
Transform a point from map to world frame.
bool makePlan(const geometry_msgs::msg::Pose &start, const geometry_msgs::msg::Pose &goal, double tolerance, std::function< bool()> cancel_checker, nav_msgs::msg::Path &plan)
Compute a plan given start and goal poses, provided in global world frame.
bool worldToMap(double wx, double wy, unsigned int &mx, unsigned int &my)
Transform a point from world to map frame.
bool isPlannerOutOfDate()
Determine if a new planner object should be made.
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 clearRobotCell(unsigned int mx, unsigned int my)
Set the corresponding cell cost to be free space.
void smoothApproachToGoal(const geometry_msgs::msg::Pose &goal, nav_msgs::msg::Path &plan)
Remove artifacts at the end of the path - originated from planning on a discretized world.
void cleanup() override
Cleanup lifecycle node.
void deactivate() override
Deactivate lifecycle node.
bool getPlanFromPotential(const geometry_msgs::msg::Pose &goal, nav_msgs::msg::Path &plan)
Compute a plan to a goal from a potential - must call computePotential first.