SMACC2
Loading...
Searching...
No Matches
undo_path_global_planner.cpp
Go to the documentation of this file.
1// Copyright 2025 Robosoft Inc.
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/*****************************************************************************************************************
16 *
17 * Authors: Pablo Inigo Blasco, Brett Aldrich
18 *
19 ******************************************************************************************************************/
20
21#include <angles/angles.h>
22#include <tf2/transform_datatypes.h>
23
24#include <boost/assign.hpp>
25#include <boost/range/adaptor/reversed.hpp>
26#include <boost/range/algorithm/copy.hpp>
27#include <geometry_msgs/msg/quaternion.hpp>
28
30#include <nav_2d_utils/tf_help.hpp>
31#include <pluginlib/class_list_macros.hpp>
32#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
34
35// register this planner as a BaseGlobalPlanner plugin
36namespace cl_nav2z
37{
38namespace undo_path_global_planner
39{
40using namespace std::chrono_literals;
41
52
54{
55 // clear "rviz"- publish empty path
56 nav_msgs::msg::Path planMsg;
57 planMsg.header.stamp = this->nh_->now();
58 planPub_->publish(planMsg);
59}
60
62
64{
65 RCLCPP_INFO_STREAM(nh_->get_logger(), "activating planner UndoPathGlobalPlanner");
66 planPub_->on_activate();
67 markersPub_->on_activate();
68}
69
71{
72 RCLCPP_INFO_STREAM(nh_->get_logger(), "deactivating planner UndoPathGlobalPlanner");
73 this->clearGoalMarker();
74 planPub_->on_deactivate();
75 markersPub_->on_deactivate();
76}
77
84 const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, std::string name,
85 std::shared_ptr<tf2_ros::Buffer> tf, std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
86{
87 nh_ = parent.lock();
88 costmap_ros_ = costmap_ros;
89 tf_ = tf;
90 name_ = name;
91 // RCLCPP_WARN_NAMED(nh_->get_logger(), "Backwards", "initializing global planner, costmap address: %ld",
92 // (long)costmap_ros);
93
94 rclcpp::SensorDataQoS qos;
95 qos.keep_last(2);
96 forwardPathSub_ = nh_->create_subscription<nav_msgs::msg::Path>(
97 "odom_tracker_path", qos,
98 std::bind(&UndoPathGlobalPlanner::onForwardTrailMsg, this, std::placeholders::_1));
99
100 planPub_ =
101 nh_->create_publisher<nav_msgs::msg::Path>("undo_path_planner/global_plan", rclcpp::QoS(1));
102 markersPub_ = nh_->create_publisher<visualization_msgs::msg::MarkerArray>(
103 "undo_path_planner/markers", rclcpp::QoS(1));
104
105 declareOrSet(nh_, name_ + ".transform_tolerance", transform_tolerance_);
106}
112void UndoPathGlobalPlanner::onForwardTrailMsg(const nav_msgs::msg::Path::SharedPtr forwardPath)
113{
114 lastForwardPathMsg_ = *forwardPath;
115 RCLCPP_INFO_STREAM_THROTTLE(
116 nh_->get_logger(), *nh_, 1000,
117 "[UndoPathGlobalPlanner] received backward path msg poses [" << lastForwardPathMsg_.poses.size()
118 << "]");
119}
120
127{
128 visualization_msgs::msg::Marker marker;
129 marker.header.frame_id = this->costmap_ros_->getGlobalFrameID();
130 marker.header.stamp = nh_->now();
131 marker.ns = "my_namespace2";
132 marker.id = 0;
133 marker.action = visualization_msgs::msg::Marker::DELETEALL;
134
135 visualization_msgs::msg::MarkerArray ma;
136 ma.markers.push_back(marker);
137 markersPub_->publish(ma);
138}
139
146 const geometry_msgs::msg::Pose & pose, double r, double g, double b)
147{
148 double phi = tf2::getYaw(pose.orientation);
149
150 visualization_msgs::msg::Marker marker;
151 marker.header.frame_id = this->costmap_ros_->getGlobalFrameID();
152 marker.header.stamp = nh_->now();
153 marker.ns = "my_namespace2";
154 marker.id = 0;
155 marker.type = visualization_msgs::msg::Marker::ARROW;
156 marker.action = visualization_msgs::msg::Marker::ADD;
157 marker.scale.x = 0.1;
158 marker.scale.y = 0.3;
159 marker.scale.z = 0.1;
160 marker.color.a = 1.0;
161
162 marker.color.r = r;
163 marker.color.g = g;
164 marker.color.b = b;
165
166 marker.lifetime = rclcpp::Duration(0s);
167
168 geometry_msgs::msg::Point start, end;
169 start.x = pose.position.x;
170 start.y = pose.position.y;
171
172 end.x = pose.position.x + 0.5 * cos(phi);
173 end.y = pose.position.y + 0.5 * sin(phi);
174
175 marker.points.push_back(start);
176 marker.points.push_back(end);
177
178 visualization_msgs::msg::MarkerArray ma;
179 ma.markers.push_back(marker);
180
181 markersPub_->publish(ma);
182}
189 const geometry_msgs::msg::PoseStamped & start, const geometry_msgs::msg::PoseStamped & /*goal*/,
190 std::vector<geometry_msgs::msg::PoseStamped> & plan)
191{
192 //------------- TRANSFORM TO GLOBAL FRAME PATH ---------------------------
193 // the forward plan might be recoreded in a different frame of the global (costmap) frame. Transform it.
194 // transform global plan to the navigation reference frame
195
196 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] Transforming forward path");
197 nav_msgs::msg::Path transformedPlan;
198 rclcpp::Duration ttol = rclcpp::Duration::from_seconds(transform_tolerance_);
199 for (auto p : lastForwardPathMsg_.poses)
200 {
201 geometry_msgs::msg::PoseStamped transformedPose;
202 p.header.stamp = nh_->now(); // otherwise we can get some time tolerance error
203 transformedPose.header.stamp = nh_->now();
204 transformedPose.header.frame_id = costmap_ros_->getGlobalFrameID();
205 nav_2d_utils::transformPose(tf_, costmap_ros_->getGlobalFrameID(), p, transformedPose, ttol);
206 transformedPlan.poses.push_back(transformedPose);
207 }
208
209 lastForwardPathMsg_ = transformedPlan;
210 //---------------------------------------------------------------------------
211
212 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] finding goal closest point");
213 // reverse index of the pose being examined in the first pass (the last pose
214 // of the recorded forward path is the one closest to the robot now)
215 int reverseIndex = lastForwardPathMsg_.poses.size() - 1;
216 double linear_mindist = std::numeric_limits<double>::max();
217 int mindistindex = -1;
218 double startPoseAngle = tf2::getYaw(start.pose.orientation);
219 geometry_msgs::msg::Pose startPositionProjected;
220
221 // The goal of this code is finding the most convenient initial path pose.
222 // first, find closest linear point to the current robot position
223 // we start from the final goal, that is, the beginning of the trajectory
224 // (since this was the forward motion from the odom tracker)
225 for (auto & p : transformedPlan.poses /*| boost::adaptors::reversed*/)
226 {
227 geometry_msgs::msg::PoseStamped pose = p;
228 pose.header.frame_id = costmap_ros_->getGlobalFrameID();
229
230 double dx = pose.pose.position.x - start.pose.position.x;
231 double dy = pose.pose.position.y - start.pose.position.y;
232
233 double dist = sqrt(dx * dx + dy * dy);
234 double angleOrientation = tf2::getYaw(pose.pose.orientation);
235 double angleError = fabs(angles::shortest_angular_distance(angleOrientation, startPoseAngle));
236 if (dist <= linear_mindist)
237 {
238 mindistindex = reverseIndex;
239 linear_mindist = dist;
240 startPositionProjected = pose.pose;
241
242 RCLCPP_INFO_STREAM(
243 nh_->get_logger(), "[UndoPathGlobalPlanner] initial start point search, NEWBEST_LINEAR= "
244 << reverseIndex << ". error, linear: " << linear_mindist
245 << ", angular: " << angleError);
246 }
247 else
248 {
249 RCLCPP_INFO_STREAM(
250 nh_->get_logger(), "[UndoPathGlobalPlanner] initial start point search, skipped= "
251 << reverseIndex << ". best linear error: " << linear_mindist
252 << ". current error, linear: " << dist << " angular: " << angleError);
253 }
254
255 reverseIndex--;
256 }
257
258 double const ERROR_DISTANCE_PURE_SPINNING_FACTOR = 1.5;
259 // Concept of second pass: now we only consider a pure spinning motion in this point. We want to consume some very
260 // close angular targets, (accepting a larger linear minerror of 1.5 besterror. That is, more or less in the same
261 // point).
262
263 RCLCPP_INFO(nh_->get_logger(), "[UndoPathGlobalPlanner] second angular pass");
264 double angularMinDist = std::numeric_limits<double>::max();
265
266 if (mindistindex >= (int)transformedPlan.poses.size())
267 mindistindex =
268 transformedPlan.poses.size() -
269 1; // workaround, something is making a out of bound exception in poses array access
270 {
271 if (transformedPlan.poses.size() == 0)
272 {
273 RCLCPP_WARN_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] Warning possible bug");
274 }
275
276 // ------- FULL FORWARD PASS TO FIND THE STARTING POINT OF THE FORWARD MOTION ------
277 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] second pass loop");
278 for (int i = mindistindex; i >= 0; i--)
279 {
280 // warning this index, i refers to some inverse interpretation from the previous loop,
281 // (last indexes in this path corresponds to the poses closer to our current position)
282 RCLCPP_INFO_STREAM(
283 nh_->get_logger(), "[UndoPathGlobalPlanner] " << i << "/" << transformedPlan.poses.size());
284 auto index = (int)transformedPlan.poses.size() - i - 1;
285 if (index < 0 || (size_t)index >= transformedPlan.poses.size())
286 {
287 RCLCPP_WARN_STREAM(
288 nh_->get_logger(),
289 "[UndoPathGlobalPlanner] this should not happen. Check implementation.");
290 break;
291 }
292 geometry_msgs::msg::PoseStamped pose =
293 transformedPlan.poses[transformedPlan.poses.size() - i - 1];
294
295 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] global frame");
296 pose.header.frame_id = costmap_ros_->getGlobalFrameID();
297
298 double dx = pose.pose.position.x - start.pose.position.x;
299 double dy = pose.pose.position.y - start.pose.position.y;
300
301 double dist = sqrt(dx * dx + dy * dy);
302 if (dist <= linear_mindist * ERROR_DISTANCE_PURE_SPINNING_FACTOR)
303 {
304 double angleOrientation = tf2::getYaw(pose.pose.orientation);
305 double angleError =
306 fabs(angles::shortest_angular_distance(angleOrientation, startPoseAngle));
307 if (angleError < angularMinDist)
308 {
309 angularMinDist = angleError;
310 mindistindex = i;
311 RCLCPP_INFO_STREAM(
312 nh_->get_logger(),
313 "[UndoPathGlobalPlanner] initial start point search (angular update), NEWBEST_ANGULAR= "
314 << i << ". error, linear: " << dist << "(" << linear_mindist << ")"
315 << ", angular: " << angleError << "(" << angularMinDist << ")");
316 }
317 else
318 {
319 RCLCPP_INFO_STREAM(
320 nh_->get_logger(),
321 "[UndoPathGlobalPlanner] initial start point search (angular update), skipped= "
322 << i << ". error, linear: " << dist << "(" << linear_mindist << ")"
323 << ", angular: " << angleError << "(" << angularMinDist << ")");
324 }
325 }
326 else
327 {
328 RCLCPP_INFO_STREAM(
329 nh_->get_logger(),
330 "[UndoPathGlobalPlanner] initial start point search (angular update) not in linear "
331 "range, skipped= "
332 << i << " linear error: " << dist << "(" << linear_mindist << ")");
333 }
334 }
335 }
336
337 // REVERSE FORWARD PASS
338 if (mindistindex != -1)
339 {
340 RCLCPP_WARN_STREAM(
341 nh_->get_logger(),
342 "[UndoPathGlobalPlanner] Creating the backwards plan from odom tracker path (, "
343 << transformedPlan.poses.size() << ") poses");
344
345 RCLCPP_WARN_STREAM(
346 nh_->get_logger(), "[UndoPathGlobalPlanner] closer point to goal i="
347 << mindistindex << " (linear min dist " << linear_mindist << ")");
348
349 // copy the path at the inverse direction, but only up to the closest point to the goal in the path (for partial undoing)
350 // NOTE: mindistindex lives in reverse-index space (passes above iterate with i counting
351 // down while walking the array forward: reverse index m maps to array index N-1-m).
352 // The emitted plan must start at the pose closest to the robot (array index
353 // N-1-mindistindex) and walk back to the start of the recorded trail (array index 0),
354 // which is the undo goal. When mindistindex == 0 (robot exactly at the trail tail)
355 // this is identical to emitting the whole path reversed.
356 //
357 // Same-position rotation clusters at the goal end (the poses recorded while the
358 // robot rotated in place before starting the forward motion) are pruned down to
359 // the final goal pose: they carry no positional information for the backward
360 // controller and destabilize the carrot/goal-checker endgame.
361 const double SAME_POSITION_PRUNE_DISTANCE = 0.02;
362 const auto & goalPosition = transformedPlan.poses.front().pose.position;
363 for (int i = (int)transformedPlan.poses.size() - 1 - mindistindex; i >= 0; i--)
364 {
365 auto & pose = transformedPlan.poses[i];
366
367 if (i != 0)
368 {
369 double gdx = pose.pose.position.x - goalPosition.x;
370 double gdy = pose.pose.position.y - goalPosition.y;
371 if (sqrt(gdx * gdx + gdy * gdy) < SAME_POSITION_PRUNE_DISTANCE)
372 {
373 // part of the in-place rotation cluster at the goal: skip, keep only i == 0
374 continue;
375 }
376 }
377
378 rclcpp::Time t(pose.header.stamp);
379
380 RCLCPP_INFO_STREAM(
381 nh_->get_logger(),
382 "[UndoPathGlobalPlanner] adding to plan i = " << i << " stamp:" << t.seconds());
383 plan.push_back(pose);
384 }
385 RCLCPP_WARN_STREAM(
386 nh_->get_logger(), "[UndoPathGlobalPlanner] refined plan has " << plan.size() << " points");
387 }
388 else
389 {
390 RCLCPP_ERROR_STREAM(
391 nh_->get_logger(), "[UndoPathGlobalPlanner ] undo global plan size: " << plan.size());
392 }
393}
394
401 const geometry_msgs::msg::PoseStamped & start, const geometry_msgs::msg::PoseStamped & goal,
402 std::function<bool()> /*cancel_checker*/)
403{
404 // -------------- BASIC CHECKS ---------------------
405
406 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] Undo global plan start ");
407 nav_msgs::msg::Path planMsg;
408 std::vector<geometry_msgs::msg::PoseStamped> & plan = planMsg.poses;
409
410 RCLCPP_INFO_STREAM(
411 nh_->get_logger(),
412 "[UndoPathGlobalPlanner] last forward path msg size: " << lastForwardPathMsg_.poses.size());
413
414 if (lastForwardPathMsg_.poses.size() == 0)
415 {
416 RCLCPP_WARN_STREAM(
417 nh_->get_logger(),
418 "[UndoPathGlobalPlanner] no forward path received yet (odom_tracker_path), returning "
419 "empty plan");
420 return planMsg;
421 }
422
423 RCLCPP_INFO_STREAM(
424 nh_->get_logger(), "[UndoPathGlobalPlanner] last forward path frame id: "
425 << lastForwardPathMsg_.poses.front().header.frame_id);
426 RCLCPP_INFO_STREAM(
427 nh_->get_logger(), "[UndoPathGlobalPlanner] start pose frame id: " << start.header.frame_id);
428 RCLCPP_INFO_STREAM(
429 nh_->get_logger(), "[UndoPathGlobalPlanner] goal pose frame id: " << goal.header.frame_id);
430
431 // ---------- INPUTS ACCOMMODATION -------------------
432 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] Inputs accommodation");
433 geometry_msgs::msg::PoseStamped transformedStart, transformedGoal;
434 {
435 rclcpp::Duration ttol = rclcpp::Duration::from_seconds(transform_tolerance_);
436
437 geometry_msgs::msg::PoseStamped pstart = start;
438 pstart.header.stamp = nh_->now();
439 nav_2d_utils::transformPose(
440 tf_, costmap_ros_->getGlobalFrameID(), pstart, transformedStart, ttol);
441 transformedStart.header.frame_id = costmap_ros_->getGlobalFrameID();
442
443 //--------------- FORCE GOAL POSE----------------------------
444 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] Forced goal");
445 auto forcedGoal =
446 lastForwardPathMsg_.poses[lastForwardPathMsg_.poses.size() - 1]; // FORCE LAST POSE
447 forcedGoal.header.stamp = nh_->now();
448 nav_2d_utils::transformPose(
449 tf_, costmap_ros_->getGlobalFrameID(), forcedGoal, transformedGoal, ttol);
450 transformedGoal.header.frame_id = costmap_ros_->getGlobalFrameID();
451 }
452
453 //------------- CREATING GLOBAL PLAN -----------------------------------------------
454 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] Creating undo plan");
455 this->createDefaultUndoPathPlan(transformedStart, transformedGoal, plan);
456 planMsg.header.frame_id = this->costmap_ros_->getGlobalFrameID();
457
458 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] publishing goal markers");
459 publishGoalMarker(plan.back().pose, 1.0, 0, 1.0 /*purple color*/);
460
461 //-------- CHECKING VALID PLAN ------------------------------------
462 bool acceptedGlobalPlan = true;
463 RCLCPP_INFO_STREAM(nh_->get_logger(), "[UndoPathGlobalPlanner] valid plan checking");
464
465 auto costmap2d = this->costmap_ros_->getCostmap();
466 for (auto & p : plan)
467 {
468 unsigned int mx, my;
469 costmap2d->worldToMap(p.pose.position.x, p.pose.position.y, mx, my);
470 auto cost = costmap2d->getCost(mx, my);
471
472 if (cost >= nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE)
473 {
474 acceptedGlobalPlan = false;
475 break;
476 }
477 }
478
479 //-------- PUBLISHING RESULTS ---------------------------------------
480 RCLCPP_INFO_STREAM(
481 nh_->get_logger(), "[UndoPathGlobalPlanner] plan publishing. size: " << plan.size());
482 planPub_->publish(planMsg);
483 if (!acceptedGlobalPlan)
484 {
485 RCLCPP_INFO(
486 nh_->get_logger(),
487 "[UndoPathGlobalPlanner] not accepted global plan because of possible collision");
488 }
489
490 RCLCPP_INFO_STREAM(
491 nh_->get_logger(), "[UndoPathGlobalPlanner] plan publishing. size: " << planMsg.poses.size());
492
493 return planMsg;
494}
495
496} // namespace undo_path_global_planner
497} // namespace cl_nav2z
498PLUGINLIB_EXPORT_CLASS(
virtual nav_msgs::msg::Path createPlan(const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal, std::function< bool()> cancel_checker) override
Method create the plan from a starting and ending goal.
virtual void deactivate()
Method to deactivate planner and any threads involved in execution.
virtual void cleanup()
Method to cleanup resources used on shutdown.
rclcpp_lifecycle::LifecyclePublisher< visualization_msgs::msg::MarkerArray >::SharedPtr markersPub_
virtual void configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr &parent, std::string name, std::shared_ptr< tf2_ros::Buffer > tf, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros)
rclcpp::Subscription< nav_msgs::msg::Path >::SharedPtr forwardPathSub_
std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros_
stored but almost not used
rclcpp_lifecycle::LifecyclePublisher< nav_msgs::msg::Path >::SharedPtr planPub_
virtual void activate()
Method to active planner and any threads involved in execution.
void onForwardTrailMsg(const nav_msgs::msg::Path::SharedPtr trailMessage)
void publishGoalMarker(const geometry_msgs::msg::Pose &pose, double r, double g, double b)
virtual void createDefaultUndoPathPlan(const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal, std::vector< geometry_msgs::msg::PoseStamped > &plan)
void declareOrSet(rclcpp_lifecycle::LifecycleNode::SharedPtr &node, std::string param, T &value)
Definition common.hpp:34