Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
feasible_path_handler.cpp
1 // Copyright (c) 2022 Samsung Research America, @artofnothingness Alexey Budyakov
2 // Copyright (c) 2023 Dexory
3 // Copyright (c) 2023 Open Navigation LLC
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 #include <memory>
18 #include "angles/angles.h"
19 #include "pluginlib/class_list_macros.hpp"
20 #include "nav2_controller/plugins/feasible_path_handler.hpp"
21 #include "nav2_util/path_utils.hpp"
22 #include "nav2_util/geometry_utils.hpp"
23 #include "nav2_core/controller_exceptions.hpp"
24 #include "nav2_ros_common/tf2_factories.hpp"
25 
26 
27 using rcl_interfaces::msg::ParameterType;
28 using std::placeholders::_1;
29 
30 namespace nav2_controller
31 {
32 using nav2_util::geometry_utils::euclidean_distance;
33 
35 {
36  auto node = node_.lock();
37  if (post_set_params_handler_ && node) {
38  node->remove_post_set_parameters_callback(post_set_params_handler_.get());
39  }
40  post_set_params_handler_.reset();
41  if (on_set_params_handler_ && node) {
42  node->remove_on_set_parameters_callback(on_set_params_handler_.get());
43  }
44  on_set_params_handler_.reset();
45 }
46 
48  const nav2::LifecycleNode::WeakPtr & parent,
49  const rclcpp::Logger & logger,
50  const std::string & plugin_name,
51  const std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
52  nav2::TransformBuffer::SharedPtr tf)
53 {
54  logger_ = logger;
55  plugin_name_ = plugin_name;
56  node_ = parent;
57  auto node = node_.lock();
58  costmap_ros_ = costmap_ros;
59  tf_ = tf;
60  transform_tolerance_ = costmap_ros_->getTransformTolerance();
61  reject_unit_path_ = node->declare_or_get_parameter(
62  plugin_name + ".reject_unit_path", false);
63  max_robot_pose_search_dist_ = node->declare_or_get_parameter(
64  plugin_name + ".max_robot_pose_search_dist", getCostmapMaxExtent());
65  prune_distance_ = node->declare_or_get_parameter(
66  plugin_name + ".prune_distance", 2.0);
67  enforce_path_inversion_ = node->declare_or_get_parameter(
68  plugin_name + ".enforce_path_inversion", false);
69  enforce_path_rotation_ = node->declare_or_get_parameter(
70  plugin_name + ".enforce_path_rotation", false);
71  inversion_xy_tolerance_ = node->declare_or_get_parameter(
72  plugin_name + ".inversion_xy_tolerance", 0.2);
73  inversion_yaw_tolerance_ = node->declare_or_get_parameter(
74  plugin_name + ".inversion_yaw_tolerance", 0.4);
75  minimum_rotation_angle_ = node->declare_or_get_parameter(
76  plugin_name + ".minimum_rotation_angle", 0.785);
77  if (max_robot_pose_search_dist_ < 0.0) {
78  RCLCPP_WARN(
79  logger_, "Max robot search distance is negative, setting to max to search"
80  " every point on path for the closest value.");
81  max_robot_pose_search_dist_ = std::numeric_limits<double>::max();
82  }
83  constraint_locale_ = 0u;
84  if (!enforce_path_rotation_) {
85  minimum_rotation_angle_ = 0.0f;
86  }
87 
88  // Add callback for dynamic parameters
89  post_set_params_handler_ = node->add_post_set_parameters_callback(
90  std::bind(
92  this, std::placeholders::_1));
93  on_set_params_handler_ = node->add_on_set_parameters_callback(
94  std::bind(
96  this, std::placeholders::_1));
97 }
98 
100 {
101  const double max_costmap_dim_meters = std::max(
102  costmap_ros_->getCostmap()->getSizeInMetersX(),
103  costmap_ros_->getCostmap()->getSizeInMetersY());
104  return max_costmap_dim_meters / 2.0;
105 }
106 
107 void FeasiblePathHandler::prunePlan(nav_msgs::msg::Path & plan, const nav2_core::PathIterator end)
108 {
109  plan.poses.erase(plan.poses.begin(), end);
110 }
111 
113  const geometry_msgs::msg::PoseStamped & robot_pose)
114 {
115  // Keep full path if we are within tolerance of the inversion pose
116  const auto last_pose = global_plan_up_to_constraint_.poses.back();
117  float distance = hypotf(
118  robot_pose.pose.position.x - last_pose.pose.position.x,
119  robot_pose.pose.position.y - last_pose.pose.position.y);
120 
121  float angle_distance = angles::shortest_angular_distance(
122  tf2::getYaw(robot_pose.pose.orientation),
123  tf2::getYaw(last_pose.pose.orientation));
124 
125  return distance <= inversion_xy_tolerance_ &&
126  fabs(angle_distance) <= inversion_yaw_tolerance_;
127 }
128 
129 void FeasiblePathHandler::setPlan(const nav_msgs::msg::Path & path)
130 {
131  std::lock_guard<std::mutex> lock_reinit(mutex_);
132  global_plan_ = path;
133  global_plan_up_to_constraint_ = global_plan_;
134  if (enforce_path_inversion_ || enforce_path_rotation_) {
135  constraint_locale_ = nav2_util::removePosesAfterFirstConstraint(global_plan_up_to_constraint_,
136  enforce_path_inversion_, minimum_rotation_angle_);
137  }
138 }
139 
140 geometry_msgs::msg::PoseStamped FeasiblePathHandler::transformToGlobalPlanFrame(
141  const geometry_msgs::msg::PoseStamped & pose)
142 {
143  if (global_plan_up_to_constraint_.poses.empty()) {
144  throw nav2_core::InvalidPath("Received plan with zero length");
145  }
146 
147  if (reject_unit_path_ && global_plan_up_to_constraint_.poses.size() == 1) {
148  throw nav2_core::InvalidPath("Received plan with length of one");
149  }
150 
151  // let's get the pose of the robot in the frame of the plan
152  geometry_msgs::msg::PoseStamped robot_pose;
153  if (!nav2_util::transformPoseInTargetFrame(pose, robot_pose, *tf_,
154  global_plan_up_to_constraint_.header.frame_id,
155  transform_tolerance_))
156  {
157  throw nav2_core::ControllerTFError("Unable to transform robot pose into global plan's frame");
158  }
159 
160  return robot_pose;
161 }
162 
164  const geometry_msgs::msg::PoseStamped & pose)
165 {
166  std::lock_guard<std::mutex> lock_reinit(mutex_);
167  global_pose_ = transformToGlobalPlanFrame(pose);
168 
169  // Limit the search for the closest pose up to max_robot_pose_search_dist on the path
170  auto closest_pose_upper_bound =
171  nav2_util::geometry_utils::first_after_integrated_distance(
172  global_plan_up_to_constraint_.poses.begin(), global_plan_up_to_constraint_.poses.end(),
173  max_robot_pose_search_dist_);
174 
175  // First find the closest pose on the path to the robot
176  // bounded by when the path turns around (if it does) so we don't get a pose from a later
177  // portion of the path
178  auto closest_point =
179  nav2_util::geometry_utils::min_by(
180  global_plan_up_to_constraint_.poses.begin(), closest_pose_upper_bound,
181  [this](const geometry_msgs::msg::PoseStamped & ps) {
182  return euclidean_distance(global_pose_, ps);
183  });
184 
185  // Make sure we always have at least 2 points on the transformed plan and that we don't prune
186  // the global plan below 2 points in order to have always enough point to interpolate the
187  // end of path direction
188  if (global_plan_up_to_constraint_.poses.begin() != closest_pose_upper_bound &&
189  global_plan_up_to_constraint_.poses.size() > 1 &&
190  closest_point == std::prev(closest_pose_upper_bound))
191  {
192  closest_point = std::prev(std::prev(closest_pose_upper_bound));
193  }
194 
195  auto pruned_plan_end =
196  nav2_util::geometry_utils::first_after_integrated_distance(
197  closest_point, global_plan_up_to_constraint_.poses.end(), prune_distance_);
198 
199  return {closest_point, pruned_plan_end};
200 }
201 
202 
204  const nav2_core::PathIterator & closest_point,
205  const nav2_core::PathIterator & pruned_plan_end)
206 {
207  std::lock_guard<std::mutex> lock_reinit(mutex_);
208  nav_msgs::msg::Path transformed_plan;
209  transformed_plan.header.frame_id = costmap_ros_->getGlobalFrameID();
210  transformed_plan.header.stamp = global_pose_.header.stamp;
211  unsigned int mx, my;
212  // Find the furthest relevant pose on the path to consider within costmap
213  // bounds
214  // Transforming it to the costmap frame in the same loop
215  for (auto global_plan_pose = closest_point; global_plan_pose != pruned_plan_end;
216  ++global_plan_pose)
217  {
218  // Transform from global plan frame to costmap frame
219  geometry_msgs::msg::PoseStamped costmap_plan_pose;
220  global_plan_pose->header.stamp = global_pose_.header.stamp;
221  global_plan_pose->header.frame_id = global_plan_.header.frame_id;
222  nav2_util::transformPoseInTargetFrame(*global_plan_pose, costmap_plan_pose, *tf_,
223  costmap_ros_->getGlobalFrameID(), transform_tolerance_);
224 
225  // Check if pose is inside the costmap
226  if (!costmap_ros_->getCostmap()->worldToMap(
227  costmap_plan_pose.pose.position.x, costmap_plan_pose.pose.position.y, mx, my))
228  {
229  break;
230  }
231 
232  // Filling the transformed plan to return with the transformed pose
233  transformed_plan.poses.push_back(costmap_plan_pose);
234  }
235 
236  // Remove the portion of the global plan that we've already passed so we don't
237  // process it on the next iteration (this is called path pruning)
238  prunePlan(global_plan_up_to_constraint_, closest_point);
239 
240  if ((enforce_path_inversion_ || enforce_path_rotation_) && constraint_locale_ != 0u) {
241  if (isWithinInversionTolerances(global_pose_)) {
242  prunePlan(global_plan_, global_plan_.poses.begin() + constraint_locale_);
243  global_plan_up_to_constraint_ = global_plan_;
244  constraint_locale_ = nav2_util::removePosesAfterFirstConstraint(global_plan_up_to_constraint_,
245  enforce_path_inversion_, minimum_rotation_angle_);
246  }
247  }
248 
249  if (transformed_plan.poses.empty()) {
250  throw nav2_core::InvalidPath("Resulting plan has 0 poses in it.");
251  }
252 
253  return transformed_plan;
254 }
255 
256 geometry_msgs::msg::PoseStamped FeasiblePathHandler::getTransformedGoal(
257  const builtin_interfaces::msg::Time & stamp)
258 {
259  auto goal = global_plan_.poses.back();
260  goal.header.frame_id = global_plan_.header.frame_id;
261  goal.header.stamp = stamp;
262  if (goal.header.frame_id.empty()) {
263  throw nav2_core::ControllerTFError("Goal pose has an empty frame_id");
264  }
265  geometry_msgs::msg::PoseStamped transformed_goal;
266  if (!nav2_util::transformPoseInTargetFrame(goal, transformed_goal, *costmap_ros_->getTfBuffer(),
267  costmap_ros_->getGlobalFrameID(), transform_tolerance_))
268  {
269  throw nav2_core::ControllerTFError("Unable to transform goal pose into costmap frame");
270  }
271  return transformed_goal;
272 }
273 
274 rcl_interfaces::msg::SetParametersResult
276  const std::vector<rclcpp::Parameter> & parameters)
277 {
278  rcl_interfaces::msg::SetParametersResult result;
279  result.successful = true;
280  for (const auto & parameter : parameters) {
281  const auto & param_type = parameter.get_type();
282  const auto & param_name = parameter.get_name();
283  if (param_name.find(plugin_name_ + ".") != 0) {
284  continue;
285  }
286  if (param_type == ParameterType::PARAMETER_DOUBLE) {
287  if (parameter.as_double() < 0.0) {
288  RCLCPP_WARN(
289  logger_, "The value of parameter '%s' is incorrectly set to %f, "
290  "it should be >=0. Ignoring parameter update.",
291  param_name.c_str(), parameter.as_double());
292  result.successful = false;
293  }
294  }
295  }
296  return result;
297 }
298 
299 void
301  const std::vector<rclcpp::Parameter> & parameters)
302 {
303  std::lock_guard<std::mutex> lock_reinit(mutex_);
304  rcl_interfaces::msg::SetParametersResult result;
305  for (const auto & parameter : parameters) {
306  const auto & param_type = parameter.get_type();
307  const auto & param_name = parameter.get_name();
308  if (param_name.find(plugin_name_ + ".") != 0) {
309  continue;
310  }
311  if (param_type == ParameterType::PARAMETER_DOUBLE) {
312  if (param_name == plugin_name_ + ".max_robot_pose_search_dist") {
313  max_robot_pose_search_dist_ = parameter.as_double();
314  } else if (param_name == plugin_name_ + ".inversion_xy_tolerance") {
315  inversion_xy_tolerance_ = parameter.as_double();
316  } else if (param_name == plugin_name_ + ".inversion_yaw_tolerance") {
317  inversion_yaw_tolerance_ = parameter.as_double();
318  } else if (param_name == plugin_name_ + ".prune_distance") {
319  prune_distance_ = parameter.as_double();
320  } else if (param_name == plugin_name_ + ".minimum_rotation_angle") {
321  minimum_rotation_angle_ = parameter.as_double();
322  }
323  } else if (param_type == ParameterType::PARAMETER_BOOL) {
324  if (param_name == plugin_name_ + ".enforce_path_inversion") {
325  enforce_path_inversion_ = parameter.as_bool();
326  } else if (param_name == plugin_name_ + ".enforce_path_rotation") {
327  enforce_path_rotation_ = parameter.as_bool();
328  if (!enforce_path_rotation_) {
329  minimum_rotation_angle_ = 0.0f;
330  }
331  }
332  }
333  }
334 }
335 
336 } // namespace nav2_controller
337 
This plugin manages the global plan by clipping it to the local segment, typically bounded by the loc...
~FeasiblePathHandler()
Destroy the Feasible Path Handler object.
void prunePlan(nav_msgs::msg::Path &plan, const nav2_core::PathIterator end)
Prune a path to only interesting portions.
nav_msgs::msg::Path transformLocalPlan(const nav2_core::PathIterator &closest_point, const nav2_core::PathIterator &pruned_plan_end) override
Transforms a predefined segment of the global plan into the costmap global frame.
bool isWithinInversionTolerances(const geometry_msgs::msg::PoseStamped &robot_pose)
Check if the robot pose is within the set inversion tolerances.
geometry_msgs::msg::PoseStamped transformToGlobalPlanFrame(const geometry_msgs::msg::PoseStamped &pose)
Transform a pose to the global reference frame.
void setPlan(const nav_msgs::msg::Path &path) override
Set new reference plan.
void updateParametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
geometry_msgs::msg::PoseStamped getTransformedGoal(const builtin_interfaces::msg::Time &stamp) override
Get the global goal pose transformed to the costmap global frame.
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...
nav2_core::PathSegment findPlanSegment(const geometry_msgs::msg::PoseStamped &pose) override
Determines the portion of the global plan to be used for local control. This function locates the sta...
void initialize(const nav2::LifecycleNode::WeakPtr &parent, const rclcpp::Logger &logger, const std::string &plugin_name, const std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros, nav2::TransformBuffer::SharedPtr tf) override
Initialize parameters.
Function-object for handling the path from Planner Server.