Skip to content

Commit 6e4c6cd

Browse files
committed
use integrated distance for lookahead distance
1 parent 77c88bf commit 6e4c6cd

4 files changed

Lines changed: 104 additions & 97 deletions

File tree

nav2_regulated_pure_pursuit_controller/include/nav2_regulated_pure_pursuit_controller/regulated_pure_pursuit_controller.hpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,14 @@ class RegulatedPurePursuitController : public nav2_core::Controller
116116
* Points ineligible to be selected as a lookahead point if they are any of the following:
117117
* - Outside the local_costmap (collision avoidance cannot be assured)
118118
* - Beyond a "cusp": a change from forward to reverse or vice-versa
119-
* @param pose pose to transform
119+
* @param pose pose to transform from
120+
* @param cusp_it iterator to pose in global_plan where the first cusp appears
120121
* @return Path in new frame
121122
*/
122123
nav_msgs::msg::Path transformGlobalPlan(
123124
const geometry_msgs::msg::PoseStamped & pose,
124-
double dist_to_cusp);
125+
const std::vector<geometry_msgs::msg::PoseStamped>::iterator cusp_it,
126+
double lookahead_distance);
125127

126128
/**
127129
* @brief Transform a pose to another frame.
@@ -233,11 +235,15 @@ class RegulatedPurePursuitController : public nav2_core::Controller
233235
geometry_msgs::msg::PoseStamped getLookAheadPoint(const double &, const nav_msgs::msg::Path &);
234236

235237
/**
236-
* @brief checks for the cusp position
238+
* @brief Find the first cusp position in the global_plan_
237239
* @param pose Pose input to determine the cusp position
238-
* @return robot distance from the cusp
240+
* @return iterator to the pose on the cusp
239241
*/
240-
double findCusp(const geometry_msgs::msg::PoseStamped & pose);
242+
std::vector<geometry_msgs::msg::PoseStamped>::iterator findCusp();
243+
244+
double integratedDistanceToPose(
245+
const nav_msgs::msg::Path &,
246+
const std::vector<geometry_msgs::msg::PoseStamped>::iterator);
241247

242248
/**
243249
* @brief Callback executed when a parameter change is detected

nav2_regulated_pure_pursuit_controller/src/regulated_pure_pursuit_controller.cpp

Lines changed: 50 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <memory>
2020
#include <vector>
2121
#include <utility>
22+
#include <numeric>
2223

2324
#include "nav2_regulated_pure_pursuit_controller/regulated_pure_pursuit_controller.hpp"
2425
#include "nav2_core/exceptions.hpp"
@@ -232,7 +233,8 @@ std::unique_ptr<geometry_msgs::msg::PointStamped> RegulatedPurePursuitController
232233
return carrot_msg;
233234
}
234235

235-
double RegulatedPurePursuitController::getLookAheadDistance(const geometry_msgs::msg::Twist & speed)
236+
double RegulatedPurePursuitController::getLookAheadDistance(
237+
const geometry_msgs::msg::Twist & speed)
236238
{
237239
// If using velocity-scaled look ahead distances, find and clamp the dist
238240
// Else, use the static look ahead distance
@@ -261,13 +263,14 @@ geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocity
261263
goal_dist_tol_ = pose_tolerance.position.x;
262264
}
263265

264-
double dist_to_cusp = findCusp(pose);
266+
auto cusp_pose_it = findCusp();
267+
268+
double lookahead_dist = getLookAheadDistance(speed);
265269

266270
// Transform path to robot base frame
267-
auto transformed_plan = transformGlobalPlan(pose, dist_to_cusp);
271+
auto transformed_plan = transformGlobalPlan(pose, cusp_pose_it, lookahead_dist);
268272

269-
// Find look ahead distance and point on path and publish
270-
double lookahead_dist = getLookAheadDistance(speed);
273+
double dist_to_cusp = integratedDistanceToPose(transformed_plan, cusp_pose_it);
271274

272275
// if the lookahead distance is further than the cusp, use the cusp distance instead
273276
if (dist_to_cusp < lookahead_dist) {
@@ -366,10 +369,8 @@ geometry_msgs::msg::PoseStamped RegulatedPurePursuitController::getLookAheadPoin
366369
const nav_msgs::msg::Path & transformed_plan)
367370
{
368371
// Find the first pose which is at a distance greater than the lookahead distance
369-
auto goal_pose_it = std::find_if(
370-
transformed_plan.poses.begin(), transformed_plan.poses.end(), [&](const auto & ps) {
371-
return hypot(ps.pose.position.x, ps.pose.position.y) >= lookahead_dist;
372-
});
372+
auto goal_pose_it = nav2_util::geometry_utils::first_element_beyond(
373+
transformed_plan.poses.begin(), transformed_plan.poses.end(), lookahead_dist);
373374

374375
// If the no pose is not far enough, take the last pose
375376
if (goal_pose_it == transformed_plan.poses.end()) {
@@ -585,7 +586,9 @@ void RegulatedPurePursuitController::setSpeedLimit(
585586
}
586587

587588
nav_msgs::msg::Path RegulatedPurePursuitController::transformGlobalPlan(
588-
const geometry_msgs::msg::PoseStamped & pose, double dist_to_cusp)
589+
const geometry_msgs::msg::PoseStamped & pose,
590+
const std::vector<geometry_msgs::msg::PoseStamped>::iterator cusp_it,
591+
double lookahead_distance)
589592
{
590593
if (global_plan_.poses.empty()) {
591594
throw nav2_core::PlannerException("Received plan with zero length");
@@ -602,29 +605,31 @@ nav_msgs::msg::Path RegulatedPurePursuitController::transformGlobalPlan(
602605
const double max_costmap_dim = std::max(costmap->getSizeInCellsX(), costmap->getSizeInCellsY());
603606
const double max_costmap_extent = max_costmap_dim * costmap->getResolution() / 2.0;
604607

605-
// Find the first pose that turns in the opposite direction from the first pose
606-
findPathUpToTurn(global_plan_, M_PI);
607-
608-
const double max_transform_dist = std::min(max_costmap_extent, dist_to_cusp);
609-
608+
// This bound is based on the assumption that the robot can't have traveled very far
609+
// since the last iteration, and that when the global_plan was first passed, the robot
610+
// was intended to start at the beginning.
611+
// lookahead distance should be a pretty good proxy for this bound.
610612
auto closest_pose_upper_bound =
611613
nav2_util::geometry_utils::first_element_beyond(
612-
global_plan_.poses.begin(), global_plan_.poses.end(), max_transform_dist);
614+
global_plan_.poses.begin(), global_plan_.poses.end(), lookahead_distance);
613615

614-
// First find the closest pose on the path to the robot
615-
// bounded by when the path turns around (if it does) so we don't get a pose from a later
616-
// portion of the path
617616
auto transformation_begin =
618-
nav2_util::geometry_utils::min_by(
617+
std::min_element(
619618
global_plan_.poses.begin(), closest_pose_upper_bound,
620-
[&robot_pose](const geometry_msgs::msg::PoseStamped & ps) {
621-
return euclidean_distance(robot_pose, ps);
619+
[&robot_pose](const auto & pose1, const auto & pose2) {
620+
return euclidean_distance(robot_pose, pose1) < euclidean_distance(robot_pose, pose2);
622621
});
623622

624-
// Find points up to max_transform_dist so we only transform them.
623+
auto first_pose_after_cusp = cusp_it != global_plan_.poses.end() ? std::next(cusp_it) : cusp_it;
624+
625+
// Find points within the local costmap up to the cusp pose
625626
auto transformation_end =
626-
nav2_util::geometry_utils::first_element_beyond(
627-
transformation_begin, global_plan_.poses.end(), max_transform_dist);
627+
std::find_if(
628+
transformation_begin, first_pose_after_cusp,
629+
[&](const auto & pose) -> bool {
630+
// This is constraining us to the biggest circle we can fit inside the local costmap
631+
return euclidean_distance(pose, robot_pose) > max_costmap_extent;
632+
});
628633

629634
// Lambda to transform a PoseStamped from global frame to local
630635
auto transformGlobalPoseToLocal = [&](const auto & global_plan_pose) {
@@ -638,9 +643,10 @@ nav_msgs::msg::Path RegulatedPurePursuitController::transformGlobalPlan(
638643

639644
// Transform the near part of the global plan into the robot's frame of reference.
640645
nav_msgs::msg::Path transformed_plan;
646+
transformed_plan.poses.resize(std::distance(transformation_begin, transformation_end));
641647
std::transform(
642648
transformation_begin, transformation_end,
643-
std::back_inserter(transformed_plan.poses),
649+
transformed_plan.poses.begin(),
644650
transformGlobalPoseToLocal);
645651
transformed_plan.header.frame_id = costmap_ros_->getBaseFrameID();
646652
transformed_plan.header.stamp = robot_pose.header.stamp;
@@ -657,48 +663,35 @@ nav_msgs::msg::Path RegulatedPurePursuitController::transformGlobalPlan(
657663
return transformed_plan;
658664
}
659665

660-
double RegulatedPurePursuitController::findCusp(
661-
const geometry_msgs::msg::PoseStamped & pose)
666+
std::vector<geometry_msgs::msg::PoseStamped>::iterator RegulatedPurePursuitController::findCusp()
662667
{
663668
// Iterating through the global path to determine the position of the cusp
664-
for (unsigned int pose_id = 1; pose_id < global_plan_.poses.size() - 1; ++pose_id) {
669+
for (auto a = std::next(global_plan_.poses.begin()); a != std::prev(global_plan_.poses.end());
670+
++a)
671+
{
672+
auto o = std::prev(a);
673+
auto b = std::next(a);
665674
// We have two vectors for the dot product OA and AB. Determining the vectors.
666-
double oa_x = global_plan_.poses[pose_id].pose.position.x -
667-
global_plan_.poses[pose_id - 1].pose.position.x;
668-
double oa_y = global_plan_.poses[pose_id].pose.position.y -
669-
global_plan_.poses[pose_id - 1].pose.position.y;
670-
double ab_x = global_plan_.poses[pose_id + 1].pose.position.x -
671-
global_plan_.poses[pose_id].pose.position.x;
672-
double ab_y = global_plan_.poses[pose_id + 1].pose.position.y -
673-
global_plan_.poses[pose_id].pose.position.y;
674-
675-
/* Checking for the existance of cusp, in the path, using the dot product
676-
and determine it's distance from the robot. If there is no cusp in the path,
677-
then just determine the distance to the goal location. */
675+
double oa_x = a->pose.position.x - o->pose.position.x;
676+
double oa_y = a->pose.position.y - o->pose.position.y;
677+
double ab_x = b->pose.position.x - a->pose.position.x;
678+
double ab_y = b->pose.position.y - a->pose.position.y;
679+
680+
// Checking for the existance of cusp, in the path, using the dot product
678681
if ( (oa_x * ab_x) + (oa_y * ab_y) < 0.0) {
679-
auto x = global_plan_.poses[pose_id].pose.position.x - pose.pose.position.x;
680-
auto y = global_plan_.poses[pose_id].pose.position.y - pose.pose.position.y;
681-
return hypot(x, y); // returning the distance if there is a cusp
682+
return a;
682683
}
683684
}
684685

685-
return std::numeric_limits<double>::max();
686+
return global_plan_.poses.end();
686687
}
687688

688-
std::vector<geometry_msgs::msg::PoseStamped>::iterator findPathUpToTurn(
689+
double RegulatedPurePursuitController::integratedDistanceToPose(
689690
const nav_msgs::msg::Path & path,
690-
double angle)
691+
std::vector<geometry_msgs::msg::PoseStamped>::iterator pose_it)
691692
{
692-
tf2::Quaternion starting_orientation;
693-
tf2::fromMsg(path.poses.begin()->pose.orientation, starting_orientation);
694-
return std::find_if(
695-
path.poses.begin(), path.poses.end(),
696-
[](const auto& pose) {
697-
tf2::Quaternion quat;
698-
tf2::fromMsg(pose.pose.orientation, quat);
699-
return quat.angleShortestPath(starting_orientation) > angle;
700-
}
701-
);
693+
auto after_pose_it = pose_it != path.poses.end() ? std::next(pose_it) : pose_it;
694+
return nav2_util::geometry_utils::calculate_path_length(path.poses.begin(), after_pose_it);
702695
}
703696

704697
bool RegulatedPurePursuitController::transformPose(

nav2_regulated_pure_pursuit_controller/test/test_regulated_pp.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,16 @@ class BasicAPIRPP : public nav2_regulated_pure_pursuit_controller::RegulatedPure
9292
linear_vel, sign);
9393
}
9494

95-
double findCuspWrapper(
96-
const geometry_msgs::msg::PoseStamped & pose)
95+
std::vector<geometry_msgs::msg::PoseStamped>::iterator findCuspWrapper()
9796
{
98-
return findCusp(pose);
97+
return findCusp();
98+
}
99+
100+
double integratedDistanceToPoseWrapper(
101+
const nav_msgs::msg::Path & path,
102+
const std::vector<geometry_msgs::msg::PoseStamped>::iterator iterator)
103+
{
104+
return integratedDistanceToPose(path, iterator);
99105
}
100106
};
101107

@@ -166,13 +172,15 @@ TEST(RegulatedPurePursuitTest, findCusp)
166172
path.poses[2].pose.position.x = -1.0;
167173
path.poses[2].pose.position.y = -1.0;
168174
ctrl->setPlan(path);
169-
auto rtn = ctrl->findCuspWrapper(pose);
175+
auto cusp = ctrl->findCuspWrapper();
176+
auto rtn = ctrl->integratedDistanceToPoseWrapper(path, cusp);
170177
EXPECT_EQ(rtn, sqrt(5.0));
171178

172179
path.poses[2].pose.position.x = 3.0;
173180
path.poses[2].pose.position.y = 3.0;
174181
ctrl->setPlan(path);
175-
rtn = ctrl->findCuspWrapper(pose);
182+
cusp = ctrl->findCuspWrapper();
183+
rtn = ctrl->integratedDistanceToPoseWrapper(path, cusp);
176184
EXPECT_EQ(rtn, std::numeric_limits<double>::max());
177185
}
178186

nav2_util/include/nav2_util/geometry_utils.hpp

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -103,27 +103,6 @@ inline double euclidean_distance(
103103
return hypot(dx, dy);
104104
}
105105

106-
/**
107-
* Find element in iterator with the minimum calculated value
108-
*/
109-
template<typename Iter, typename Getter>
110-
inline Iter min_by(Iter begin, Iter end, Getter getCompareVal)
111-
{
112-
if (begin == end) {
113-
return end;
114-
}
115-
auto lowest = getCompareVal(*begin);
116-
Iter lowest_it = begin;
117-
for (Iter it = ++begin; it != end; ++it) {
118-
auto comp = getCompareVal(*it);
119-
if (comp < lowest) {
120-
lowest = comp;
121-
lowest_it = it;
122-
}
123-
}
124-
return lowest_it;
125-
}
126-
127106
/**
128107
* Find first element in iterator that is greater integrated distance than comparevalue
129108
*/
@@ -134,7 +113,7 @@ inline Iter first_element_beyond(Iter begin, Iter end, Getter getCompareVal)
134113
return end;
135114
}
136115
Getter dist = 0.0;
137-
for (Iter it = begin; it != end - 1; it++) {
116+
for (auto it = begin; it != end - 1; it++) {
138117
dist += euclidean_distance(*it, *(it + 1));
139118
if (dist > getCompareVal) {
140119
return it + 1;
@@ -143,6 +122,34 @@ inline Iter first_element_beyond(Iter begin, Iter end, Getter getCompareVal)
143122
return end;
144123
}
145124

125+
// Using more than one iterator type allows for mixing const and non-const iterators
126+
template<typename Iter1, typename Iter2, typename Value, typename BinaryOp>
127+
inline Value accumulate_window(Iter1 begin, Iter2 end, Value init, BinaryOp window)
128+
{
129+
// Check this so we don't call std::next on end
130+
if (begin == end) {
131+
return init;
132+
}
133+
Value accumulator = init;
134+
for (auto i = std::next(begin); i != end; ++i) {
135+
auto prev = std::prev(i);
136+
accumulator += window(prev, i);
137+
}
138+
return accumulator;
139+
}
140+
141+
// Using more than one iterator type allows for mixing const and non-const iterators
142+
template<typename Iter1, typename Iter2>
143+
inline double calculate_path_length(Iter1 begin, Iter2 end)
144+
{
145+
return accumulate_window(
146+
begin, end,
147+
0.0,
148+
[](const auto & pose1, const auto & pose2) -> double {
149+
return euclidean_distance(*pose1, *pose2);
150+
});
151+
}
152+
146153
/**
147154
* @brief Calculate the length of the provided path, starting at the provided index
148155
* @param path Path containing the poses that are planned
@@ -153,14 +160,7 @@ inline Iter first_element_beyond(Iter begin, Iter end, Getter getCompareVal)
153160
*/
154161
inline double calculate_path_length(const nav_msgs::msg::Path & path, size_t start_index = 0)
155162
{
156-
if (start_index + 1 >= path.poses.size()) {
157-
return 0.0;
158-
}
159-
double path_length = 0.0;
160-
for (size_t idx = start_index; idx < path.poses.size() - 1; ++idx) {
161-
path_length += euclidean_distance(path.poses[idx].pose, path.poses[idx + 1].pose);
162-
}
163-
return path_length;
163+
return calculate_path_length(path.poses.begin() + start_index, path.poses.end());
164164
}
165165

166166
} // namespace geometry_utils

0 commit comments

Comments
 (0)