Skip to content

Commit 800c4aa

Browse files
alexanderjyuenSteveMacenski
authored andcommitted
Route server corner smoothing (ros-navigation#5226)
* added edge length method Signed-off-by: Alexander Yuen <[email protected]> * Added corner arc class Signed-off-by: Alexander Yuen <[email protected]> * replaced double vectors with Coordinates, added methods to return start and end coordinates Signed-off-by: Alexander Yuen <[email protected]> * using Coordinates, fixed direction of tangents Signed-off-by: Alexander Yuen <[email protected]> * added corner arc in header, added logger in protected variable Signed-off-by: Alexander Yuen <[email protected]> * first pass of corner smoothing algorithm Signed-off-by: Alexander Yuen <[email protected]> * reassigning next edge to have a different start, if a corner occurs before it Signed-off-by: Alexander Yuen <[email protected]> * using unique pointer instead of raw pointers for new edges and nodes Signed-off-by: Alexander Yuen <[email protected]> * added smoothing parameter Signed-off-by: Alexander Yuen <[email protected]> * made angle of interpolation a parameter Signed-off-by: Alexander Yuen <[email protected]> * const for return methods, added flag for smoothing corners Signed-off-by: Alexander Yuen <[email protected]> * moved getEdgeLength() into the Directional Edge struct Signed-off-by: Alexander Yuen <[email protected]> * using float instead of double Signed-off-by: Alexander Yuen <[email protected]> * smoothing radius is float, couple methods moved to protected Signed-off-by: Alexander Yuen <[email protected]> * removed signed_angle_ as a member variable Signed-off-by: Alexander Yuen <[email protected]> * removed unnecessary member variables Signed-off-by: Alexander Yuen <[email protected]> * removed angle of interpolation and inferring it from path density and radius instead Signed-off-by: Alexander Yuen <[email protected]> * consolidated corner arc into one header function Signed-off-by: Alexander Yuen <[email protected]> * readded newline Signed-off-by: Alexander Yuen <[email protected]> * changed corner arc to corner smoothing Signed-off-by: Alexander Yuen <[email protected]> * replaced the use of edges with coordinates to generate smoothing arc, removed storage of nodes and edges Signed-off-by: Alexander Yuen <[email protected]> * linting Signed-off-by: Alexander Yuen <[email protected]> * fixing cpplint Signed-off-by: Alexander Yuen <[email protected]> * linting for headers Signed-off-by: Alexander Yuen <[email protected]> * cpplinting Signed-off-by: Alexander Yuen <[email protected]> * Update nav2_route/src/path_converter.cpp Signed-off-by: Steve Macenski <[email protected]> * Update nav2_route/src/path_converter.cpp Signed-off-by: Steve Macenski <[email protected]> * Update nav2_route/src/path_converter.cpp Signed-off-by: Steve Macenski <[email protected]> * Update nav2_route/src/path_converter.cpp Signed-off-by: Steve Macenski <[email protected]> * Update nav2_route/include/nav2_route/corner_smoothing.hpp Signed-off-by: Steve Macenski <[email protected]> * fixed divide by zeros and accessing empty route.edges Signed-off-by: Alexander Yuen <[email protected]> * uncrustify linting Signed-off-by: Alexander Yuen <[email protected]> * cpp linting Signed-off-by: Alexander Yuen <[email protected]> * path converter linting Signed-off-by: Alexander Yuen <[email protected]> * changed all doubles to floats Signed-off-by: Alexander Yuen <[email protected]> * added check for edges that are colinear to avoid divide by 0, fixed final edge interpolation Signed-off-by: Alexander Yuen <[email protected]> * linting Signed-off-by: Alexander Yuen <[email protected]> * Update nav2_route/include/nav2_route/corner_smoothing.hpp Signed-off-by: Steve Macenski <[email protected]> * added doxygen for corner arc class Signed-off-by: Alexander Yuen <[email protected]> * added warning message if corner can't be smoothed Signed-off-by: Alexander Yuen <[email protected]> * added smooth_corners to the nav2 params file Signed-off-by: Alexander Yuen <[email protected]> * added smoothing flag and radius parameter to README.md' Signed-off-by: Alexander Yuen <[email protected]> * typo in README Signed-off-by: Alexander Yuen <[email protected]> * added testing for corner smoothing Signed-off-by: Alexander Yuen <[email protected]> * Update nav2_route/include/nav2_route/corner_smoothing.hpp Signed-off-by: Steve Macenski <[email protected]> --------- Signed-off-by: Alexander Yuen <[email protected]> Signed-off-by: Steve Macenski <[email protected]> Co-authored-by: Steve Macenski <[email protected]>
1 parent e0a4227 commit 800c4aa

File tree

8 files changed

+361
-6
lines changed

8 files changed

+361
-6
lines changed

nav2_bringup/params/nav2_params.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ route_server:
447447
# graph_filepath: $(find-pkg-share nav2_route)/graphs/aws_graph.geojson
448448
boundary_radius_to_achieve_node: 1.0
449449
radius_to_achieve_node: 2.0
450+
smooth_corners: true
450451
operations: ["AdjustSpeedLimit", "ReroutingService", "CollisionMonitor"]
451452
ReroutingService:
452453
plugin: "nav2_route::ReroutingService"

nav2_route/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ route_server:
114114
path_density: 0.05 # Density of points for generating the dense nav_msgs/Path from route (m)
115115
max_iterations: 0 # Maximum number of search iterations, if 0, uses maximum possible
116116
max_planning_time: 2.0 # Maximum planning time (seconds)
117+
smooth_corners: true # Whether to smooth corners formed by adjacent edges or not
118+
smoothing_radius: 1.0 # Radius of corner to fit into the corner
117119
costmap_topic: 'global_costmap/costmap_raw' # Costmap topic when enable_nn_search is enabled. May also be used by the collision monitor operation and/or the costmap edge scorer if using the same topic to share resources.
118120
119121
graph_file_loader: "GeoJsonGraphFileLoader" # Name of default file loader
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) 2025, Polymath Robotics
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+
#ifndef NAV2_ROUTE__CORNER_SMOOTHING_HPP_
16+
#define NAV2_ROUTE__CORNER_SMOOTHING_HPP_
17+
18+
#include <vector>
19+
#include <cmath>
20+
#include <algorithm>
21+
22+
#include "nav2_route/types.hpp"
23+
#include "nav2_route/utils.hpp"
24+
25+
namespace nav2_route
26+
{
27+
/**
28+
* @class nav2_route::CornerArc
29+
* @brief A class used to smooth corners defined by the edges and nodes
30+
* of the route graph. Used with path converter to output a corner smoothed plan
31+
*/
32+
class CornerArc
33+
{
34+
public:
35+
/**
36+
* @brief A constructor for nav2_route::CornerArc
37+
* @param start start coordinate of corner to be smoothed
38+
* @param corner corner coordinate of corner to be smoothed
39+
* @param end end coordinate of corner to be smoothed
40+
* @param minimum_radius smoothing radius to fit to the corner
41+
*/
42+
CornerArc(
43+
const Coordinates & start, const Coordinates & corner, const Coordinates & end,
44+
float minimum_radius)
45+
{
46+
start_edge_length_ = hypotf(corner.x - start.x, corner.y - start.y);
47+
end_edge_length_ = hypotf(end.x - corner.x, end.y - corner.y);
48+
49+
// invalid scenario would cause equations to blow up
50+
if (start_edge_length_ == 0.0 || end_edge_length_ == 0.0) {return;}
51+
52+
float angle = getAngleBetweenEdges(start, corner, end);
53+
54+
// cannot smooth a 0 (back and forth) or 180 (straight line) angle
55+
if (std::abs(angle) < 1E-6 || std::abs(angle - M_PI) < 1E-6) {return;}
56+
57+
float tangent_length = minimum_radius / (std::tan(std::fabs(angle) / 2));
58+
59+
if (tangent_length < start_edge_length_ && tangent_length < end_edge_length_) {
60+
std::vector<float> start_edge_unit_tangent =
61+
{(start.x - corner.x) / start_edge_length_, (start.y - corner.y) / start_edge_length_};
62+
std::vector<float> end_edge_unit_tangent =
63+
{(end.x - corner.x) / end_edge_length_, (end.y - corner.y) / end_edge_length_};
64+
65+
float bisector_x = start_edge_unit_tangent[0] + end_edge_unit_tangent[0];
66+
float bisector_y = start_edge_unit_tangent[1] + end_edge_unit_tangent[1];
67+
float bisector_magnitude = std::sqrt(bisector_x * bisector_x + bisector_y * bisector_y);
68+
69+
std::vector<float> unit_bisector =
70+
{bisector_x / bisector_magnitude, bisector_y / bisector_magnitude};
71+
72+
start_coordinate_.x = corner.x + start_edge_unit_tangent[0] * tangent_length;
73+
start_coordinate_.y = corner.y + start_edge_unit_tangent[1] * tangent_length;
74+
75+
end_coordinate_.x = corner.x + end_edge_unit_tangent[0] * tangent_length;
76+
end_coordinate_.y = corner.y + end_edge_unit_tangent[1] * tangent_length;
77+
78+
float bisector_length = minimum_radius / std::sin(angle / 2);
79+
80+
circle_center_coordinate_.x = corner.x + unit_bisector[0] * bisector_length;
81+
circle_center_coordinate_.y = corner.y + unit_bisector[1] * bisector_length;
82+
83+
valid_corner_ = true;
84+
}
85+
}
86+
87+
/**
88+
* @brief A destructor for nav2_route::CornerArc
89+
*/
90+
~CornerArc() = default;
91+
92+
/**
93+
* @brief interpolates the arc for a path of certain density
94+
* @param max_angle_resolution Resolution to interpolate path to
95+
* @param poses Pose output
96+
*/
97+
void interpolateArc(
98+
const float & max_angle_resolution,
99+
std::vector<geometry_msgs::msg::PoseStamped> & poses)
100+
{
101+
std::vector<float> r_start{start_coordinate_.x - circle_center_coordinate_.x,
102+
start_coordinate_.y - circle_center_coordinate_.y};
103+
std::vector<float> r_end{end_coordinate_.x - circle_center_coordinate_.x,
104+
end_coordinate_.y - circle_center_coordinate_.y};
105+
float cross = r_start[0] * r_end[1] - r_start[1] * r_end[0];
106+
float dot = r_start[0] * r_end[0] + r_start[1] * r_end[1];
107+
float signed_angle = std::atan2(cross, dot);
108+
// lower limit for N is 1 to protect against divide by 0
109+
int N = std::max(1, static_cast<int>(std::ceil(std::abs(signed_angle) / max_angle_resolution)));
110+
float angle_resolution = signed_angle / N;
111+
112+
float x, y;
113+
for (int i = 0; i <= N; i++) {
114+
float angle = i * angle_resolution;
115+
x = circle_center_coordinate_.x +
116+
(r_start[0] * std::cos(angle) - r_start[1] * std::sin(angle));
117+
y = circle_center_coordinate_.y +
118+
(r_start[0] * std::sin(angle) + r_start[1] * std::cos(angle));
119+
poses.push_back(utils::toMsg(x, y));
120+
}
121+
}
122+
123+
/**
124+
* @brief return if a valid corner arc (one that doesn't overrun the edge lengths) is generated
125+
* @return if the a corner was able to be generated
126+
*/
127+
bool isCornerValid() const {return valid_corner_;}
128+
129+
/**
130+
* @brief return the start coordinate of the corner arc
131+
* @return start coordinate of the arc
132+
*/
133+
Coordinates getCornerStart() const {return start_coordinate_;}
134+
135+
/**
136+
* @brief return the end coordinate of the corner arc
137+
* @return end coordinate of the arc
138+
*/
139+
Coordinates getCornerEnd() const {return end_coordinate_;}
140+
141+
protected:
142+
/**
143+
* @brief find the signed angle between a corner generated by 3 points
144+
* @param start start coordinate of corner to be smoothed
145+
* @param corner corner coordinate of corner to be smoothed
146+
* @param end end coordinate of corner to be smoothed
147+
* @return signed angle of the corner
148+
*/
149+
float getAngleBetweenEdges(
150+
const Coordinates & start, const Coordinates & corner,
151+
const Coordinates & end)
152+
{
153+
float start_dx = start.x - corner.x;
154+
float start_dy = start.y - corner.y;
155+
156+
float end_dx = end.x - corner.x;
157+
float end_dy = end.y - corner.y;
158+
159+
float angle =
160+
acos((start_dx * end_dx + start_dy * end_dy) / (start_edge_length_ * end_edge_length_));
161+
162+
return angle;
163+
}
164+
165+
private:
166+
bool valid_corner_{false};
167+
float start_edge_length_;
168+
float end_edge_length_;
169+
Coordinates start_coordinate_;
170+
Coordinates end_coordinate_;
171+
Coordinates circle_center_coordinate_;
172+
};
173+
174+
} // namespace nav2_route
175+
176+
#endif // NAV2_ROUTE__CORNER_SMOOTHING_HPP_

nav2_route/include/nav2_route/path_converter.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "nav2_util/node_utils.hpp"
2727
#include "nav2_route/types.hpp"
2828
#include "nav2_route/utils.hpp"
29+
#include "nav2_route/corner_smoothing.hpp"
2930

3031
namespace nav2_route
3132
{
@@ -80,7 +81,10 @@ class PathConverter
8081

8182
protected:
8283
rclcpp_lifecycle::LifecyclePublisher<nav_msgs::msg::Path>::SharedPtr path_pub_;
84+
rclcpp::Logger logger_{rclcpp::get_logger("PathConverter")};
8385
float density_;
86+
float smoothing_radius_;
87+
bool smooth_corners_;
8488
};
8589

8690
} // namespace nav2_route

nav2_route/include/nav2_route/types.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ struct DirectionalEdge
138138
EdgeCost edge_cost; // Cost information associated with edge
139139
Metadata metadata; // Any metadata stored in the graph file of interest
140140
Operations operations; // Operations to perform related to the edge
141+
142+
float getEdgeLength();
141143
};
142144

143145
typedef DirectionalEdge * EdgePtr;
@@ -194,6 +196,13 @@ struct Node
194196
}
195197
};
196198

199+
inline float DirectionalEdge::getEdgeLength()
200+
{
201+
return hypotf(
202+
end->coords.x - start->coords.x,
203+
end->coords.y - start->coords.y);
204+
}
205+
197206
/**
198207
* @struct nav2_route::Route
199208
* @brief An ordered set of nodes and edges corresponding to the planned route

nav2_route/src/path_converter.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@ void PathConverter::configure(nav2_util::LifecycleNode::SharedPtr node)
3030
nav2_util::declare_parameter_if_not_declared(
3131
node, "path_density", rclcpp::ParameterValue(0.05));
3232
density_ = static_cast<float>(node->get_parameter("path_density").as_double());
33+
nav2_util::declare_parameter_if_not_declared(
34+
node, "smoothing_radius", rclcpp::ParameterValue(1.0));
35+
smoothing_radius_ = static_cast<float>(node->get_parameter("smoothing_radius").as_double());
36+
nav2_util::declare_parameter_if_not_declared(
37+
node, "smooth_corners", rclcpp::ParameterValue(false));
38+
smooth_corners_ = node->get_parameter("smooth_corners").as_bool();
3339

3440
path_pub_ = node->create_publisher<nav_msgs::msg::Path>("plan", 1);
3541
path_pub_->on_activate();
42+
logger_ = node->get_logger();
3643
}
3744

3845
nav_msgs::msg::Path PathConverter::densify(
@@ -54,17 +61,50 @@ nav_msgs::msg::Path PathConverter::densify(
5461
interpolateEdge(start.x, start.y, end.x, end.y, path.poses);
5562
}
5663

57-
// Fill in path via route edges
58-
for (unsigned int i = 0; i != route.edges.size(); i++) {
59-
const EdgePtr edge = route.edges[i];
60-
const Coordinates & start = edge->start->coords;
61-
const Coordinates & end = edge->end->coords;
62-
interpolateEdge(start.x, start.y, end.x, end.y, path.poses);
64+
Coordinates start;
65+
Coordinates end;
66+
67+
if (!route.edges.empty()) {
68+
start = route.edges[0]->start->coords;
69+
70+
// Fill in path via route edges
71+
for (unsigned int i = 0; i < route.edges.size() - 1; i++) {
72+
const EdgePtr edge = route.edges[i];
73+
const EdgePtr & next_edge = route.edges[i + 1];
74+
end = edge->end->coords;
75+
76+
CornerArc corner_arc(start, end, next_edge->end->coords, smoothing_radius_);
77+
if (corner_arc.isCornerValid() && smooth_corners_) {
78+
// if an arc exists, end of the first edge is the start of the arc
79+
end = corner_arc.getCornerStart();
80+
81+
// interpolate to start of arc
82+
interpolateEdge(start.x, start.y, end.x, end.y, path.poses);
83+
84+
// interpolate arc
85+
corner_arc.interpolateArc(density_ / smoothing_radius_, path.poses);
86+
87+
// new start of next edge is end of smoothing arc
88+
start = corner_arc.getCornerEnd();
89+
} else {
90+
if (smooth_corners_) {
91+
RCLCPP_WARN(
92+
logger_, "Unable to smooth corner between edge %i and edge %i", edge->edgeid,
93+
next_edge->edgeid);
94+
}
95+
interpolateEdge(start.x, start.y, end.x, end.y, path.poses);
96+
start = end;
97+
}
98+
}
6399
}
64100

65101
if (route.edges.empty()) {
66102
path.poses.push_back(utils::toMsg(route.start_node->coords.x, route.start_node->coords.y));
67103
} else {
104+
interpolateEdge(
105+
start.x, start.y, route.edges.back()->end->coords.x,
106+
route.edges.back()->end->coords.y, path.poses);
107+
68108
path.poses.push_back(
69109
utils::toMsg(route.edges.back()->end->coords.x, route.edges.back()->end->coords.y));
70110
}

nav2_route/test/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,11 @@ ament_add_gtest(test_goal_intent_search
127127
target_link_libraries(test_goal_intent_search
128128
${library_name}
129129
)
130+
131+
# Test corner smoothing
132+
ament_add_gtest(test_corner_smoothing
133+
test_corner_smoothing.cpp
134+
)
135+
target_link_libraries(test_corner_smoothing
136+
${library_name}
137+
)

0 commit comments

Comments
 (0)