Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
axis_goal_checker.cpp
1 // Copyright (c) 2025 Dexory
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.
14 
15 #include <memory>
16 #include <string>
17 #include <limits>
18 #include <vector>
19 
20 #include "angles/angles.h"
21 #include "nav2_controller/plugins/axis_goal_checker.hpp"
22 #include "pluginlib/class_list_macros.hpp"
23 #include "nav2_ros_common/node_utils.hpp"
24 #include "nav2_util/geometry_utils.hpp"
25 
26 using rcl_interfaces::msg::ParameterType;
27 using std::placeholders::_1;
28 
29 namespace nav2_controller
30 {
31 
33 : along_path_tolerance_(0.25), cross_track_tolerance_(0.25),
34  path_length_tolerance_(1.0), is_overshoot_valid_(false)
35 {
36 }
37 
39 {
40  auto node = node_.lock();
41  if (post_set_params_handler_ && node) {
42  node->remove_post_set_parameters_callback(post_set_params_handler_.get());
43  }
44  post_set_params_handler_.reset();
45  if (on_set_params_handler_ && node) {
46  node->remove_on_set_parameters_callback(on_set_params_handler_.get());
47  }
48  on_set_params_handler_.reset();
49 }
50 
52  const nav2::LifecycleNode::WeakPtr & parent,
53  const std::string & plugin_name,
54  const std::shared_ptr<nav2_costmap_2d::Costmap2DROS>/*costmap_ros*/)
55 {
56  plugin_name_ = plugin_name;
57  node_ = parent;
58  auto node = node_.lock();
59  logger_ = node->get_logger();
60 
61  along_path_tolerance_ = node->declare_or_get_parameter(
62  plugin_name + ".along_path_tolerance", 0.25);
63  cross_track_tolerance_ = node->declare_or_get_parameter(
64  plugin_name + ".cross_track_tolerance", 0.25);
65  path_length_tolerance_ = node->declare_or_get_parameter(
66  plugin_name + ".path_length_tolerance", 1.0);
67  is_overshoot_valid_ = node->declare_or_get_parameter(
68  plugin_name + ".is_overshoot_valid", false);
69 
70  // Add callback for dynamic parameters
71  post_set_params_handler_ = node->add_post_set_parameters_callback(
72  std::bind(
74  this, std::placeholders::_1));
75  on_set_params_handler_ = node->add_on_set_parameters_callback(
76  std::bind(
78  this, std::placeholders::_1));
79 }
80 
82 {
83 }
84 
86  const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose,
87  const geometry_msgs::msg::Twist & velocity,
88  const nav_msgs::msg::Path & transformed_global_plan)
89 {
90  // Since we do not consider orientation in this goal checker
91  // we can directly check if the XY position is reached
92  return isGoalXYReached(query_pose, goal_pose, velocity, transformed_global_plan);
93 }
94 
96  const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose,
97  const geometry_msgs::msg::Twist &,
98  const nav_msgs::msg::Path & transformed_global_plan)
99 {
100  std::lock_guard<std::mutex> lock_reinit(mutex_);
101  // If the local plan length is longer than the tolerance, we skip the check
102  if (nav2_util::geometry_utils::calculate_path_length(transformed_global_plan) >
103  path_length_tolerance_)
104  {
105  return false;
106  }
107 
108  // Check if we have at least 2 poses to determine path direction
109  if (transformed_global_plan.poses.size() >= 2) {
110  // Use axis-aligned goal checking with path direction
111  // Find a pose before goal that is sufficiently far from goal
112  const geometry_msgs::msg::Pose * before_goal_pose_ptr = nullptr;
113  double dx = 0.0;
114  double dy = 0.0;
115 
116  for (int i = transformed_global_plan.poses.size() - 2; i >= 0; --i) {
117  const auto & candidate_pose = transformed_global_plan.poses[i].pose;
118  dx = goal_pose.position.x - candidate_pose.position.x;
119  dy = goal_pose.position.y - candidate_pose.position.y;
120  double pose_distance = std::hypot(dx, dy);
121 
122  if (pose_distance >= 1e-6) {
123  before_goal_pose_ptr = &candidate_pose;
124  break;
125  }
126  }
127 
128  // If all poses are too close to goal, fall back to simple distance check
129  if (!before_goal_pose_ptr) {
130  RCLCPP_DEBUG(
131  logger_,
132  "All poses in path are too close to goal, falling back to simple distance check");
133  double distance_to_goal = std::hypot(
134  goal_pose.position.x - query_pose.position.x,
135  goal_pose.position.y - query_pose.position.y);
136  double tolerance = std::hypot(along_path_tolerance_, cross_track_tolerance_);
137  return distance_to_goal < tolerance;
138  }
139 
140  // end of path direction
141  double end_of_path_yaw = atan2(dy, dx);
142 
143  // Check if robot is already at goal (would cause atan2(0,0))
144  double robot_to_goal_dx = goal_pose.position.x - query_pose.position.x;
145  double robot_to_goal_dy = goal_pose.position.y - query_pose.position.y;
146  double distance_to_goal = std::hypot(robot_to_goal_dx, robot_to_goal_dy);
147 
148  if (distance_to_goal < 1e-6) {
149  return true; // Robot is at goal
150  }
151 
152  double robot_to_goal_yaw = atan2(robot_to_goal_dy, robot_to_goal_dx);
153  double projection_angle = angles::shortest_angular_distance(
154  robot_to_goal_yaw, end_of_path_yaw);
155  double along_path_distance = distance_to_goal * cos(projection_angle);
156  double cross_track_distance = distance_to_goal * sin(projection_angle);
157 
158  if (is_overshoot_valid_) {
159  return along_path_distance < along_path_tolerance_ &&
160  fabs(cross_track_distance) < cross_track_tolerance_;
161  } else {
162  return fabs(along_path_distance) < along_path_tolerance_ &&
163  fabs(cross_track_distance) < cross_track_tolerance_;
164  }
165  } else {
166  // Fallback: path has only 1 point, use simple distance check
167  RCLCPP_DEBUG(
168  logger_,
169  "Path has fewer than 2 poses, falling back to simple distance check");
170  double distance_to_goal = std::hypot(
171  goal_pose.position.x - query_pose.position.x,
172  goal_pose.position.y - query_pose.position.y);
173  double tolerance = std::hypot(along_path_tolerance_, cross_track_tolerance_);
174  return distance_to_goal < tolerance;
175  }
176 }
177 
179  geometry_msgs::msg::Pose & pose_tolerance,
180  geometry_msgs::msg::Twist & vel_tolerance,
181  double & path_length_tolerance)
182 {
183  std::lock_guard<std::mutex> lock_reinit(mutex_);
184  double invalid_field = std::numeric_limits<double>::lowest();
185 
186  pose_tolerance.position.x = std::min(along_path_tolerance_, cross_track_tolerance_);
187  pose_tolerance.position.y = std::min(along_path_tolerance_, cross_track_tolerance_);
188  pose_tolerance.position.z = invalid_field;
189  pose_tolerance.orientation =
190  nav2_util::geometry_utils::orientationAroundZAxis(M_PI_2);
191 
192  vel_tolerance.linear.x = invalid_field;
193  vel_tolerance.linear.y = invalid_field;
194  vel_tolerance.linear.z = invalid_field;
195 
196  vel_tolerance.angular.x = invalid_field;
197  vel_tolerance.angular.y = invalid_field;
198  vel_tolerance.angular.z = invalid_field;
199 
200  path_length_tolerance = path_length_tolerance_;
201 
202  return true;
203 }
204 
205 rcl_interfaces::msg::SetParametersResult
207  const std::vector<rclcpp::Parameter> & parameters)
208 {
209  rcl_interfaces::msg::SetParametersResult result;
210  result.successful = true;
211  for (auto parameter : parameters) {
212  const auto & param_type = parameter.get_type();
213  const auto & param_name = parameter.get_name();
214  if (param_name.find(plugin_name_ + ".") != 0) {
215  continue;
216  }
217  if (param_type == ParameterType::PARAMETER_DOUBLE) {
218  if (parameter.as_double() < 0.0) {
219  RCLCPP_WARN(
220  logger_, "The value of parameter '%s' is incorrectly set to %f, "
221  "it should be >=0. Ignoring parameter update.",
222  param_name.c_str(), parameter.as_double());
223  result.successful = false;
224  }
225  }
226  }
227  return result;
228 }
229 
230 void
232  const std::vector<rclcpp::Parameter> & parameters)
233 {
234  std::lock_guard<std::mutex> lock_reinit(mutex_);
235  for (const auto & parameter : parameters) {
236  const auto & type = parameter.get_type();
237  const auto & name = parameter.get_name();
238  if (name.find(plugin_name_ + ".") != 0) {
239  continue;
240  }
241  if (type == ParameterType::PARAMETER_DOUBLE) {
242  if (name == plugin_name_ + ".along_path_tolerance") {
243  along_path_tolerance_ = parameter.as_double();
244  } else if (name == plugin_name_ + ".cross_track_tolerance") {
245  cross_track_tolerance_ = parameter.as_double();
246  } else if (name == plugin_name_ + ".path_length_tolerance") {
247  path_length_tolerance_ = parameter.as_double();
248  }
249  } else if (type == ParameterType::PARAMETER_BOOL) {
250  if (name == plugin_name_ + ".is_overshoot_valid") {
251  is_overshoot_valid_ = parameter.as_bool();
252  }
253  }
254  }
255 }
256 
257 } // namespace nav2_controller
258 
Goal Checker plugin that checks progress along the axis defined by the last segment of the path to th...
bool getTolerances(geometry_msgs::msg::Pose &pose_tolerance, geometry_msgs::msg::Twist &vel_tolerance, double &path_length_tolerance) override
Get the position and velocity tolerances.
~AxisGoalChecker()
Destroy the Axis Goal Checker object.
bool isGoalXYReached(const geometry_msgs::msg::Pose &query_pose, const geometry_msgs::msg::Pose &goal_pose, const geometry_msgs::msg::Twist &velocity, const nav_msgs::msg::Path &transformed_global_plan) override
Check if XY goal position has been reached (without considering yaw)
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 reset() override
Reset the goal checker state.
void initialize(const nav2::LifecycleNode::WeakPtr &parent, const std::string &plugin_name, const std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros) override
Initialize the goal checker.
bool isGoalReached(const geometry_msgs::msg::Pose &query_pose, const geometry_msgs::msg::Pose &goal_pose, const geometry_msgs::msg::Twist &velocity, const nav_msgs::msg::Path &transformed_global_plan) override
Check if the goal is reached.
void updateParametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
AxisGoalChecker()
Construct a new Axis Goal Checker object.
Function-object for checking whether a goal has been reached.