Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
smoother.cpp
1 // Copyright (c) 2021, Samsung Research America
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License. Reserved.
14 
15 #include <ompl/base/ScopedState.h>
16 #include <ompl/base/spaces/DubinsStateSpace.h>
17 
18 #include <chrono>
19 #include <memory>
20 #include <vector>
21 
22 #include "angles/angles.h"
23 
24 #include "tf2/utils.hpp"
25 
26 #include "nav2_smac_planner/smoother.hpp"
27 #include "nav2_util/smoother_utils.hpp"
28 
29 namespace nav2_smac_planner
30 {
31 using namespace nav2_util::geometry_utils; // NOLINT
32 using namespace std::chrono; // NOLINT
34 
36 {
37  tolerance_ = params.tolerance_;
38  max_its_ = params.max_its_;
39  data_w_ = params.w_data_;
40  smooth_w_ = params.w_smooth_;
41  is_holonomic_ = params.holonomic_;
42  do_refinement_ = params.do_refinement_;
43  refinement_num_ = params.refinement_num_;
44 }
45 
46 void Smoother::initialize(const double & min_turning_radius)
47 {
48  min_turning_rad_ = min_turning_radius;
49  state_space_ = std::make_unique<ompl::base::DubinsStateSpace>(min_turning_rad_);
50 }
51 
53  nav_msgs::msg::Path & path,
54  const nav2_costmap_2d::Costmap2D * costmap,
55  const double & max_time,
56  const std::vector<geometry_msgs::msg::Point> & footprint)
57 {
58  // by-pass path orientations approximation when skipping smac smoother
59  if (max_its_ == 0) {
60  return false;
61  }
62 
63  steady_clock::time_point start = steady_clock::now();
64  double time_remaining = max_time;
65  bool success = true, reversing_segment;
66  nav_msgs::msg::Path curr_path_segment;
67  curr_path_segment.header = path.header;
68  std::vector<PathSegment> path_segments = nav2_util::findDirectionalPathSegments(
69  path,
70  is_holonomic_);
71 
72  for (unsigned int i = 0; i != path_segments.size(); i++) {
73  if (path_segments[i].end - path_segments[i].start > 10) {
74  // Populate path segment
75  curr_path_segment.poses.clear();
76  std::copy(
77  path.poses.begin() + path_segments[i].start,
78  path.poses.begin() + path_segments[i].end + 1,
79  std::back_inserter(curr_path_segment.poses));
80 
81  // Make sure we're still able to smooth with time remaining
82  steady_clock::time_point now = steady_clock::now();
83  time_remaining = max_time - duration_cast<duration<double>>(now - start).count();
84  refinement_ctr_ = 0;
85 
86  // Smooth path segment naively
87  const geometry_msgs::msg::Pose start_pose = curr_path_segment.poses.front().pose;
88  const geometry_msgs::msg::Pose goal_pose = curr_path_segment.poses.back().pose;
89  bool local_success =
90  smoothImpl(
91  curr_path_segment, reversing_segment, costmap, time_remaining,
92  footprint);
93  success = success && local_success;
94 
95  // Enforce boundary conditions
96  if (!is_holonomic_ && local_success) {
97  enforceStartBoundaryConditions(start_pose, curr_path_segment, costmap, reversing_segment);
98  enforceEndBoundaryConditions(goal_pose, curr_path_segment, costmap, reversing_segment);
99  }
100 
101  // Assemble the path changes to the main path
102  std::copy(
103  curr_path_segment.poses.begin(),
104  curr_path_segment.poses.end(),
105  path.poses.begin() + path_segments[i].start);
106  }
107  }
108 
109  return success;
110 }
111 
113  nav_msgs::msg::Path & path,
114  bool & reversing_segment,
115  const nav2_costmap_2d::Costmap2D * costmap,
116  const double & max_time,
117  const std::vector<geometry_msgs::msg::Point> & footprint)
118 {
119  steady_clock::time_point a = steady_clock::now();
120  rclcpp::Duration max_dur = rclcpp::Duration::from_seconds(max_time);
121 
122  int its = 0;
123  double change = tolerance_;
124  const unsigned int & path_size = path.poses.size();
125  double x_i, y_i, y_m1, y_ip1, y_i_org;
126  unsigned int mx, my;
127 
128  nav_msgs::msg::Path new_path = path;
129  nav_msgs::msg::Path last_path = path;
130 
131  while (change >= tolerance_) {
132  its += 1;
133  change = 0.0;
134 
135  // Make sure the smoothing function will converge
136  if (its >= max_its_) {
137  RCLCPP_DEBUG(
138  rclcpp::get_logger("SmacPlannerSmoother"),
139  "Number of iterations has exceeded limit of %i.", max_its_);
140  path = last_path;
141  nav2_util::updateApproximatePathOrientations(path, reversing_segment, is_holonomic_);
142  return false;
143  }
144 
145  // Make sure still have time left to process
146  steady_clock::time_point b = steady_clock::now();
147  rclcpp::Duration timespan(duration_cast<duration<double>>(b - a));
148  if (timespan > max_dur) {
149  RCLCPP_DEBUG(
150  rclcpp::get_logger("SmacPlannerSmoother"),
151  "Smoothing time exceeded allowed duration of %0.2f.", max_time);
152  path = last_path;
153  nav2_util::updateApproximatePathOrientations(path, reversing_segment, is_holonomic_);
154  return false;
155  }
156 
157  for (unsigned int i = 1; i != path_size - 1; i++) {
158  for (unsigned int j = 0; j != 2; j++) {
159  x_i = getFieldByDim(path.poses[i], j);
160  y_i = getFieldByDim(new_path.poses[i], j);
161  y_m1 = getFieldByDim(new_path.poses[i - 1], j);
162  y_ip1 = getFieldByDim(new_path.poses[i + 1], j);
163  y_i_org = y_i;
164 
165  // Smooth based on local 3 point neighborhood and original data locations
166  y_i += data_w_ * (x_i - y_i) + smooth_w_ * (y_ip1 + y_m1 - (2.0 * y_i));
167  setFieldByDim(new_path.poses[i], j, y_i);
168  change += abs(y_i - y_i_org);
169  }
170  }
171 
172  nav2_util::updateApproximatePathOrientations(
173  new_path, reversing_segment, is_holonomic_);
174 
175  // validate update is admissible, only checks cost if a valid costmap pointer is provided
176  if (costmap) {
177  if (!footprint.empty()) {
178  footprint_checker_.setCostmap(
179  const_cast<nav2_costmap_2d::Costmap2D *>(costmap));
180  }
181 
182  for (unsigned int i = 1; i != path_size - 1; i++) {
183  float cost = 0.0;
184 
185  if (footprint.empty()) {
186  costmap->worldToMap(
187  getFieldByDim(new_path.poses[i], 0),
188  getFieldByDim(new_path.poses[i], 1),
189  mx, my);
190  cost = static_cast<float>(costmap->getCost(mx, my));
191  } else {
192  cost = static_cast<float>(footprint_checker_.footprintCostAtPose(
193  new_path.poses[i].pose.position.x,
194  new_path.poses[i].pose.position.y,
195  tf2::getYaw(new_path.poses[i].pose.orientation),
196  footprint));
197  }
198 
199  if (cost > MAX_NON_OBSTACLE_COST && cost != UNKNOWN_COST) {
200  RCLCPP_DEBUG(
201  rclcpp::get_logger("SmacPlannerSmoother"),
202  "Smoothing process resulted in an infeasible collision. "
203  "Returning the last path before the infeasibility was introduced.");
204  path = last_path;
205  return false;
206  }
207  }
208  }
209 
210  last_path = new_path;
211  }
212 
213  // Let's do additional refinement, it shouldn't take more than a couple milliseconds
214  // but really puts the path quality over the top.
215  if (do_refinement_ && refinement_ctr_ < refinement_num_) {
216  refinement_ctr_++;
217  smoothImpl(new_path, reversing_segment, costmap, max_time, footprint);
218  }
219 
220  path = new_path;
221  return true;
222 }
223 
225  const geometry_msgs::msg::PoseStamped & msg, const unsigned int & dim)
226 {
227  if (dim == 0) {
228  return msg.pose.position.x;
229  } else if (dim == 1) {
230  return msg.pose.position.y;
231  } else {
232  return msg.pose.position.z;
233  }
234 }
235 
237  geometry_msgs::msg::PoseStamped & msg, const unsigned int dim,
238  const double & value)
239 {
240  if (dim == 0) {
241  msg.pose.position.x = value;
242  } else if (dim == 1) {
243  msg.pose.position.y = value;
244  } else {
245  msg.pose.position.z = value;
246  }
247 }
248 
250  const BoundaryExpansions & boundary_expansions)
251 {
252  // Check which is valid with the minimum integrated length such that
253  // shorter end-points away that are infeasible to achieve without
254  // a loop-de-loop are punished
255  double min_length = 1e9;
256  int shortest_boundary_expansion_idx = 1e9;
257  for (unsigned int idx = 0; idx != boundary_expansions.size(); idx++) {
258  if (boundary_expansions[idx].expansion_path_length<min_length &&
259  !boundary_expansions[idx].in_collision &&
260  boundary_expansions[idx].path_end_idx>0.0 &&
261  boundary_expansions[idx].expansion_path_length > 0.0)
262  {
263  min_length = boundary_expansions[idx].expansion_path_length;
264  shortest_boundary_expansion_idx = idx;
265  }
266  }
267 
268  return shortest_boundary_expansion_idx;
269 }
270 
272  const geometry_msgs::msg::Pose & start,
273  const geometry_msgs::msg::Pose & end,
274  BoundaryExpansion & expansion,
275  const nav2_costmap_2d::Costmap2D * costmap)
276 {
277  ompl::base::ScopedState<> from(state_space_), to(state_space_), s(state_space_);
278 
279  from[0] = start.position.x;
280  from[1] = start.position.y;
281  from[2] = tf2::getYaw(start.orientation);
282  to[0] = end.position.x;
283  to[1] = end.position.y;
284  to[2] = tf2::getYaw(end.orientation);
285 
286  double d = state_space_->distance(from(), to());
287  // If this path is too long compared to the original, then this is probably
288  // a loop-de-loop, treat as invalid as to not deviate too far from the original path.
289  // 2.0 selected from prinicipled choice of boundary test points
290  // r, 2 * r, r * PI, and 2 * PI * r. If there is a loop, it will be
291  // approximately 2 * PI * r, which is 2 * PI > r, PI > 2 * r, and 2 > r * PI.
292  // For all but the last backup test point, a loop would be approximately
293  // 2x greater than any of the selections.
294  if (d > 2.0 * expansion.original_path_length) {
295  return;
296  }
297 
298  std::vector<double> reals;
299  double theta(0.0), x(0.0), y(0.0);
300  double x_m = start.position.x;
301  double y_m = start.position.y;
302 
303  // Get intermediary poses
304  for (double i = 0; i <= expansion.path_end_idx; i++) {
305  state_space_->interpolate(from(), to(), i / expansion.path_end_idx, s());
306  reals = s.reals();
307  // Make sure in range [0, 2PI)
308  theta = (reals[2] < 0.0) ? (reals[2] + 2.0 * M_PI) : reals[2];
309  theta = (theta > 2.0 * M_PI) ? (theta - 2.0 * M_PI) : theta;
310  x = reals[0];
311  y = reals[1];
312 
313  // Check for collision
314  unsigned int mx, my;
315  costmap->worldToMap(x, y, mx, my);
316  if (static_cast<float>(costmap->getCost(mx, my)) >= INSCRIBED_COST) {
317  expansion.in_collision = true;
318  }
319 
320  // Integrate path length
321  expansion.expansion_path_length += hypot(x - x_m, y - y_m);
322  x_m = x;
323  y_m = y;
324 
325  // Store point
326  expansion.pts.emplace_back(x, y, theta);
327  }
328 }
329 
330 template<typename IteratorT>
331 BoundaryExpansions Smoother::generateBoundaryExpansionPoints(IteratorT start, IteratorT end)
332 {
333  std::vector<double> distances = {
334  min_turning_rad_, // Radius
335  2.0 * min_turning_rad_, // Diameter
336  M_PI * min_turning_rad_, // 50% Circumference
337  2.0 * M_PI * min_turning_rad_ // Circumference
338  };
339 
340  BoundaryExpansions boundary_expansions;
341  boundary_expansions.resize(distances.size());
342  double curr_dist = 0.0;
343  double x_last = start->pose.position.x;
344  double y_last = start->pose.position.y;
345  geometry_msgs::msg::Point pt;
346  unsigned int curr_dist_idx = 0;
347 
348  for (IteratorT iter = start; iter != end; iter++) {
349  pt = iter->pose.position;
350  curr_dist += hypot(pt.x - x_last, pt.y - y_last);
351  x_last = pt.x;
352  y_last = pt.y;
353 
354  if (curr_dist >= distances[curr_dist_idx]) {
355  boundary_expansions[curr_dist_idx].path_end_idx = iter - start;
356  boundary_expansions[curr_dist_idx].original_path_length = curr_dist;
357  curr_dist_idx++;
358  }
359 
360  if (curr_dist_idx == boundary_expansions.size()) {
361  break;
362  }
363  }
364 
365  return boundary_expansions;
366 }
367 
369  const geometry_msgs::msg::Pose & start_pose,
370  nav_msgs::msg::Path & path,
371  const nav2_costmap_2d::Costmap2D * costmap,
372  const bool & reversing_segment)
373 {
374  // Find range of points for testing
375  BoundaryExpansions boundary_expansions =
376  generateBoundaryExpansionPoints<PathIterator>(path.poses.begin(), path.poses.end());
377 
378  // Generate the motion model and metadata from start -> test points
379  for (unsigned int i = 0; i != boundary_expansions.size(); i++) {
380  BoundaryExpansion & expansion = boundary_expansions[i];
381  if (expansion.path_end_idx == 0.0) {
382  continue;
383  }
384 
385  if (!reversing_segment) {
386  findBoundaryExpansion(
387  start_pose, path.poses[expansion.path_end_idx].pose, expansion,
388  costmap);
389  } else {
390  findBoundaryExpansion(
391  path.poses[expansion.path_end_idx].pose, start_pose, expansion,
392  costmap);
393  }
394  }
395 
396  // Find the shortest kinematically feasible boundary expansion
397  unsigned int best_expansion_idx = findShortestBoundaryExpansionIdx(boundary_expansions);
398  if (best_expansion_idx > boundary_expansions.size()) {
399  return;
400  }
401 
402  // Override values to match curve
403  BoundaryExpansion & best_expansion = boundary_expansions[best_expansion_idx];
404  if (reversing_segment) {
405  std::reverse(best_expansion.pts.begin(), best_expansion.pts.end());
406  }
407  for (unsigned int i = 0; i != best_expansion.pts.size(); i++) {
408  path.poses[i].pose.position.x = best_expansion.pts[i].x;
409  path.poses[i].pose.position.y = best_expansion.pts[i].y;
410  path.poses[i].pose.orientation = orientationAroundZAxis(best_expansion.pts[i].theta);
411  }
412 }
413 
415  const geometry_msgs::msg::Pose & end_pose,
416  nav_msgs::msg::Path & path,
417  const nav2_costmap_2d::Costmap2D * costmap,
418  const bool & reversing_segment)
419 {
420  // Find range of points for testing
421  BoundaryExpansions boundary_expansions =
422  generateBoundaryExpansionPoints<ReversePathIterator>(path.poses.rbegin(), path.poses.rend());
423 
424  // Generate the motion model and metadata from start -> test points
425  unsigned int expansion_starting_idx;
426  for (unsigned int i = 0; i != boundary_expansions.size(); i++) {
427  BoundaryExpansion & expansion = boundary_expansions[i];
428  if (expansion.path_end_idx == 0.0) {
429  continue;
430  }
431  expansion_starting_idx = path.poses.size() - expansion.path_end_idx - 1;
432  if (!reversing_segment) {
433  findBoundaryExpansion(path.poses[expansion_starting_idx].pose, end_pose, expansion, costmap);
434  } else {
435  findBoundaryExpansion(end_pose, path.poses[expansion_starting_idx].pose, expansion, costmap);
436  }
437  }
438 
439  // Find the shortest kinematically feasible boundary expansion
440  unsigned int best_expansion_idx = findShortestBoundaryExpansionIdx(boundary_expansions);
441  if (best_expansion_idx > boundary_expansions.size()) {
442  return;
443  }
444 
445  // Override values to match curve
446  BoundaryExpansion & best_expansion = boundary_expansions[best_expansion_idx];
447  if (reversing_segment) {
448  std::reverse(best_expansion.pts.begin(), best_expansion.pts.end());
449  }
450  expansion_starting_idx = path.poses.size() - best_expansion.path_end_idx - 1;
451  for (unsigned int i = 0; i != best_expansion.pts.size(); i++) {
452  path.poses[expansion_starting_idx + i].pose.position.x = best_expansion.pts[i].x;
453  path.poses[expansion_starting_idx + i].pose.position.y = best_expansion.pts[i].y;
454  path.poses[expansion_starting_idx + i].pose.orientation = orientationAroundZAxis(
455  best_expansion.pts[i].theta);
456  }
457 }
458 
459 } // namespace nav2_smac_planner
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:69
unsigned char getCost(unsigned int mx, unsigned int my) const
Get the cost of a cell in the costmap.
Definition: costmap_2d.cpp:265
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
void initialize(const double &min_turning_radius)
Initialization of the smoother.
Definition: smoother.cpp:46
bool smoothImpl(nav_msgs::msg::Path &path, bool &reversing_segment, const nav2_costmap_2d::Costmap2D *costmap, const double &max_time, const std::vector< geometry_msgs::msg::Point > &footprint)
Smoother method - does the smoothing on a segment.
Definition: smoother.cpp:112
void enforceStartBoundaryConditions(const geometry_msgs::msg::Pose &start_pose, nav_msgs::msg::Path &path, const nav2_costmap_2d::Costmap2D *costmap, const bool &reversing_segment)
Enforced minimum curvature boundary conditions on plan output the robot is traveling in the same dire...
Definition: smoother.cpp:368
bool smooth(nav_msgs::msg::Path &path, const nav2_costmap_2d::Costmap2D *costmap, const double &max_time, const std::vector< geometry_msgs::msg::Point > &footprint=std::vector< geometry_msgs::msg::Point >())
Smoother API method.
Definition: smoother.cpp:52
double getFieldByDim(const geometry_msgs::msg::PoseStamped &msg, const unsigned int &dim)
Get the field value for a given dimension.
Definition: smoother.cpp:224
void enforceEndBoundaryConditions(const geometry_msgs::msg::Pose &end_pose, nav_msgs::msg::Path &path, const nav2_costmap_2d::Costmap2D *costmap, const bool &reversing_segment)
Enforced minimum curvature boundary conditions on plan output the robot is traveling in the same dire...
Definition: smoother.cpp:414
void setFieldByDim(geometry_msgs::msg::PoseStamped &msg, const unsigned int dim, const double &value)
Set the field value for a given dimension.
Definition: smoother.cpp:236
Smoother(const SmootherParams &params)
A constructor for nav2_smac_planner::Smoother.
Definition: smoother.cpp:35
BoundaryExpansions generateBoundaryExpansionPoints(IteratorT start, IteratorT end)
Generates boundary expansions with end idx at least strategic distances away, using either Reverse or...
Definition: smoother.cpp:331
unsigned int findShortestBoundaryExpansionIdx(const BoundaryExpansions &boundary_expansions)
Given a set of boundary expansion, find the one which is shortest such that it is least likely to con...
Definition: smoother.cpp:249
void findBoundaryExpansion(const geometry_msgs::msg::Pose &start, const geometry_msgs::msg::Pose &end, BoundaryExpansion &expansion, const nav2_costmap_2d::Costmap2D *costmap)
Populate a motion model expansion from start->end into expansion.
Definition: smoother.cpp:271
Boundary expansion state.
Definition: smoother.hpp:55
Parameters for the smoother cost function.
A segment of a path in start/end indices.