11#include <lanelet2_core/geometry/LaneletMap.h>
12#include <tf2/LinearMath/Quaternion.h>
13#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
27std::optional<std::vector<std::pair<double, double>>>
parseWaypoints(
const std::vector<std::string>& waypoints_param,
28 std::vector<double>& waypoint_wait_times,
29 const rclcpp::Logger& logger) {
30 std::vector<std::pair<double, double>> waypoints;
31 std::vector<double> parsed_wait_times;
32 for (
const auto& waypoint : waypoints_param) {
33 size_t comma_pos = waypoint.find(
',');
34 if (comma_pos != std::string::npos) {
36 size_t wait_time_comma_pos = waypoint.find(
',', comma_pos + 1);
37 if (wait_time_comma_pos != std::string::npos && waypoint.find(
',', wait_time_comma_pos + 1) != std::string::npos) {
40 double lat = std::stod(waypoint.substr(0, comma_pos));
41 double lon = std::stod(waypoint.substr(comma_pos + 1, wait_time_comma_pos - comma_pos - 1));
42 double wait_time_s = 0.0;
43 if (wait_time_comma_pos != std::string::npos) {
44 wait_time_s = std::stod(waypoint.substr(wait_time_comma_pos + 1));
46 waypoints.emplace_back(lat, lon);
47 parsed_wait_times.push_back(wait_time_s);
48 }
catch (
const std::invalid_argument& e) {
50 }
catch (
const std::out_of_range& e) {
58 if (!parsed_wait_times.empty() && parsed_wait_times.back() < 0.0) {
59 RCLCPP_WARN(logger,
"Last waypoint cannot be intermediate, treating it as stop with wait_time_s 0.0");
60 parsed_wait_times.back() = 0.0;
63 waypoint_wait_times = parsed_wait_times;
71 "List of WGS84 waypoints to follow (list of strings with comma-separated '<LATITUDE>,<LONGITUDE>[,<WAIT_TIME_S>]', missing "
72 "wait time defaults to 0s, negative wait time means intermediate destination)",
75 "Whether to plan a route to a random destination",
true);
77 "Whether to continuously plan a new route (either looping waypoints or to a random destination)",
87 const std::string& description,
88 const bool add_to_auto_reconfigurable_params,
89 const bool is_required,
91 const std::optional<double>& from_value,
92 const std::optional<double>& to_value,
93 const std::optional<double>& step_value,
94 const std::string& additional_constraints) {
95 rcl_interfaces::msg::ParameterDescriptor param_desc;
96 param_desc.description = description;
97 param_desc.additional_constraints = additional_constraints;
98 param_desc.read_only = read_only;
100 auto type = rclcpp::ParameterValue(param).get_type();
102 if (from_value.has_value() && to_value.has_value()) {
103 if constexpr (std::is_integral_v<T>) {
104 rcl_interfaces::msg::IntegerRange range;
105 T step =
static_cast<T
>(step_value.has_value() ? step_value.value() : 1);
106 range.set__from_value(
static_cast<T
>(from_value.value())).set__to_value(
static_cast<T
>(to_value.value())).set__step(step);
107 param_desc.integer_range = {range};
108 }
else if constexpr (std::is_floating_point_v<T>) {
109 rcl_interfaces::msg::FloatingPointRange range;
110 T step =
static_cast<T
>(step_value.has_value() ? step_value.value() : 1.0);
111 range.set__from_value(
static_cast<T
>(from_value.value())).set__to_value(
static_cast<T
>(to_value.value())).set__step(step);
112 param_desc.floating_point_range = {range};
114 RCLCPP_WARN(this->get_logger(),
"Parameter type of parameter '%s' does not support specifying a range", name.c_str());
118 this->declare_parameter(name, type, param_desc);
121 param = this->get_parameter(name).get_value<T>();
122 std::stringstream ss;
123 ss <<
"Loaded parameter '" << name <<
"': ";
126 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"]");
130 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
131 }
catch (rclcpp::exceptions::ParameterUninitializedException&) {
133 RCLCPP_FATAL_STREAM(this->get_logger(),
"Missing required parameter '" << name <<
"', exiting");
136 std::stringstream ss;
137 ss <<
"Missing parameter '" << name <<
"', using default value: ";
140 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"]");
144 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
145 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
149 if (add_to_auto_reconfigurable_params) {
150 std::function<void(
const rclcpp::Parameter&)> setter = [¶m](
const rclcpp::Parameter& p) { param = p.get_value<T>(); };
156 const std::vector<rclcpp::Parameter>& parameters) {
157 for (
const auto& param : parameters) {
159 if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
160 std::get<1>(auto_reconfigurable_param)(param);
161 RCLCPP_INFO(this->get_logger(),
"Reconfigured parameter '%s'", param.get_name().c_str());
167 if (param.get_name() ==
"waypoints") {
169 if (parsed_waypoints) {
172 std::stringstream ss;
173 ss <<
"Failed to parse parameter 'waypoints': [";
177 RCLCPP_ERROR(this->get_logger(),
"%s", ss.str().c_str());
182 if (param.get_name() ==
"cancel_route") {
184 if (
action_client_->wait_for_action_server(std::chrono::duration<double>(0.1))) {
185 RCLCPP_INFO(this->get_logger(),
"Cancelling route");
188 RCLCPP_WARN(this->get_logger(),
"Action server not available, cannot cancel route");
194 rcl_interfaces::msg::SetParametersResult result;
195 result.successful =
true;
211 action_client_ = rclcpp_action::create_client<PlanRoute>(
this,
"/planning/lanelet2_route_planning/plan_route");
218 if (parsed_waypoints) {
221 std::stringstream ss;
222 ss <<
"Failed to parse parameter 'waypoints': [";
226 RCLCPP_ERROR(this->get_logger(),
"%s", ss.str().c_str());
235 RCLCPP_INFO(this->get_logger(),
"Received goal pose (%.3f, %.3f, %.3f) in frame '%s'", msg->pose.position.x,
236 msg->pose.position.y, msg->pose.position.z, msg->header.frame_id.c_str());
244 const double now_s = std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
259 RCLCPP_DEBUG(this->get_logger(),
"Nothing to plan, waiting for waypoints or random destination");
270 RCLCPP_ERROR(this->get_logger(),
"Waypoint wait times do not match waypoints, skipping");
276 RCLCPP_ERROR(this->get_logger(),
"Map not loaded, cannot generate waypoint");
281 auto goal_pose = std::make_shared<geometry_msgs::msg::PoseStamped>();
282 std::vector<geometry_msgs::msg::PointStamped> intermediate_destinations;
284 if (!ll2_projector) {
285 RCLCPP_ERROR(this->get_logger(),
"Failed to generate waypoint goal pose");
289 size_t checked_waypoints = 0;
290 while (checked_waypoints <
waypoints_.size()) {
293 lanelet::GPSPoint gps_waypoint;
294 gps_waypoint.lat = waypoint.first;
295 gps_waypoint.lon = waypoint.second;
296 lanelet::BasicPoint3d map_waypoint = ll2_projector->forward(gps_waypoint);
298 geometry_msgs::msg::PointStamped waypoint_point;
300 waypoint_point.header.stamp = this->now();
301 waypoint_point.point.x = map_waypoint.x();
302 waypoint_point.point.y = map_waypoint.y();
303 waypoint_point.point.z = 0.0;
305 if (wait_time_s < 0.0) {
306 RCLCPP_INFO(this->get_logger(),
"Adding intermediate waypoint (%.6f, %.6f)", waypoint.first, waypoint.second);
307 intermediate_destinations.push_back(waypoint_point);
313 RCLCPP_ERROR(this->get_logger(),
"No destination waypoint found after intermediate waypoints");
321 RCLCPP_INFO(this->get_logger(),
"Planning route to next waypoint (%.6f, %.6f)", waypoint.first, waypoint.second);
322 goal_pose->pose.position = waypoint_point.point;
323 goal_pose->header = waypoint_point.header;
330 if (!goal_pose->header.frame_id.empty()) {
331 RCLCPP_INFO(this->get_logger(),
"Generated waypoint goal pose (%.3f, %.3f, %.3f) in frame '%s'", goal_pose->pose.position.x,
332 goal_pose->pose.position.y, goal_pose->pose.position.z, goal_pose->header.frame_id.c_str());
335 this->
sendGoal(goal_pose, intermediate_destinations);
337 RCLCPP_ERROR(this->get_logger(),
"Failed to generate waypoint goal pose");
342 RCLCPP_INFO(this->get_logger(),
"Planning route to random destination");
346 RCLCPP_ERROR(this->get_logger(),
"Map not loaded, cannot generate a random destination");
351 auto goal_pose = std::make_shared<geometry_msgs::msg::PoseStamped>();
353 if (!map->laneletLayer.empty()) {
354 const auto lanelet_count =
static_cast<int>(map->laneletLayer.size());
355 const auto random_lanelet_idx =
356 static_cast<std::iterator_traits<decltype(map-
>laneletLayer.begin())>::difference_type>(std::rand() % lanelet_count);
357 auto random_lanelet = *std::next(map->laneletLayer.begin(), random_lanelet_idx);
358 auto centerline = random_lanelet.centerline();
359 if (!centerline.empty()) {
360 auto point = centerline.back();
361 goal_pose->pose.position.x = point.x();
362 goal_pose->pose.position.y = point.y();
363 goal_pose->pose.position.z = point.z();
364 if (centerline.size() > 1) {
366 std::atan2(point.y() - centerline[centerline.size() - 2].y(), point.x() - centerline[centerline.size() - 2].x());
368 q.setRPY(0, 0, heading);
369 goal_pose->pose.orientation = tf2::toMsg(q);
372 goal_pose->header.stamp = this->now();
377 if (!goal_pose->header.frame_id.empty()) {
378 RCLCPP_INFO(this->get_logger(),
"Generated random goal pose (%.3f, %.3f, %.3f) in frame '%s'", goal_pose->pose.position.x,
379 goal_pose->pose.position.y, goal_pose->pose.position.z, goal_pose->header.frame_id.c_str());
385 RCLCPP_ERROR(this->get_logger(),
"Failed to generate random goal pose");
390 const std::vector<geometry_msgs::msg::PointStamped>& intermediate_destinations) {
391 RCLCPP_INFO(this->get_logger(),
"Requesting to plan route to destination (%.3f, %.3f, %.3f) in frame '%s'",
392 msg->pose.position.x, msg->pose.position.y, msg->pose.position.z, msg->header.frame_id.c_str());
395 if (!
action_client_->wait_for_action_server(std::chrono::duration<double>(0.1))) {
396 RCLCPP_ERROR(this->get_logger(),
"Action server not available, aborting");
403 auto goal = PlanRoute::Goal();
404 goal.destination = geometry_msgs::msg::PointStamped();
405 goal.destination.header = msg->header;
406 goal.destination.point = msg->pose.position;
407 goal.intermediate_destinations = intermediate_destinations;
410 auto send_goal_options = rclcpp_action::Client<PlanRoute>::SendGoalOptions();
412 send_goal_options.feedback_callback =
416 RCLCPP_INFO(this->get_logger(),
"Goal sent");
421 RCLCPP_ERROR(this->get_logger(),
"Goal rejected by action server");
425 RCLCPP_INFO(this->get_logger(),
"Goal accepted by action server");
430 const std::shared_ptr<const PlanRoute::Feedback> feedback) {
433 const double distance_traveled = feedback->distance_traveled;
434 const double distance_total = feedback->distance_remaining + feedback->distance_traveled;
435 rclcpp::Duration time_traveled(feedback->time_traveled.sec, feedback->time_traveled.nanosec);
436 rclcpp::Duration time_remaining(feedback->time_remaining.sec, feedback->time_remaining.nanosec);
437 rclcpp::Duration time_total = time_traveled + time_remaining;
438 RCLCPP_INFO(this->get_logger(),
"Route progress: %.2f / %.2f m, %.1f / %.1f s", distance_traveled, distance_total,
439 time_traveled.seconds(), time_total.seconds());
443 const double distance_traveled = result.result->distance_traveled;
444 const builtin_interfaces::msg::Duration& time_traveled = result.result->time_traveled;
447 if (result.code == rclcpp_action::ResultCode::SUCCEEDED) {
448 if (result.result->destination_reached) {
449 RCLCPP_INFO(this->get_logger(),
"Goal succeeded: destination reached after %.2fm and %ds", distance_traveled,
452 RCLCPP_WARN(this->get_logger(),
"Goal succeeded, but destination not reached after %.2fm and %ds", distance_traveled,
455 }
else if (result.code == rclcpp_action::ResultCode::CANCELED) {
456 RCLCPP_WARN(this->get_logger(),
"Goal canceled: traveled %.2fm and %ds", distance_traveled, time_traveled.sec);
457 }
else if (result.code == rclcpp_action::ResultCode::ABORTED) {
458 RCLCPP_ERROR(this->get_logger(),
"Goal aborted: traveled %.2fm and %ds", distance_traveled, time_traveled.sec);
460 RCLCPP_ERROR(this->get_logger(),
"Goal finished with unknown result code: %d",
static_cast<int>(result.code));
465 if (result.code == rclcpp_action::ResultCode::SUCCEEDED && wait_time_s > 0.0) {
466 RCLCPP_INFO(this->get_logger(),
"Waiting %.2fs before planning next waypoint", wait_time_s);
467 const double now_s = std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
482int main(
int argc,
char* argv[]) {
483 rclcpp::init(argc, argv);
484 rclcpp::spin(std::make_shared<plan_route_action_client::PlanRouteActionClient>());
size_t next_waypoint_idx_
Index of next waypoint to follow.
double active_waypoint_wait_time_s_
Wait time of the active waypoint [s].
std::shared_future< GoalHandlePlanRoute::SharedPtr > goal_handle_future_
Goal handle.
std::vector< double > waypoint_wait_times_
Wait time for each waypoint [s]; negative values mark intermediate destinations.
bool has_active_waypoint_
Whether the active goal belongs to the waypoint list.
void planToRandomDestination()
Plans to a random destination.
void planToNextWaypoint()
Plans to next waypoint.
void goalPoseCallback(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
Callback for goal pose (most likely received from RViz)
void resultCallback(const GoalHandlePlanRoute::WrappedResult &result)
Callback for result from the action server.
bool cancel_route_
Flag to cancel the route planning action (parameter)
double auto_planning_resume_time_s_
Earliest wall-clock time at which automatic planning may continue.
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > ¶meters)
Handles reconfiguration when a parameter value is changed.
bool enable_continuous_planning_
Whether to continuously plan a new route (parameter)
std::vector< std::pair< double, double > > waypoints_
WGS84 waypoints to endlessly follow.
rclcpp::TimerBase::SharedPtr auto_planning_timer_
Timer to automatically plan route, e.g., if waypoints are given.
rclcpp_action::Client< PlanRoute >::SharedPtr action_client_
Action client.
PlanRouteActionClient()
Constructor.
void sendGoal(const geometry_msgs::msg::PoseStamped::SharedPtr msg, const std::vector< geometry_msgs::msg::PointStamped > &intermediate_destinations={})
Sends a goal to the action server.
std::string ll2_map_server_name_
Name of lanelet2_map_server node (parameter)
void autoPlanningTimerCallback()
Callback for automatically planning a route, e.g., if waypoints are given.
bool has_completed_one_goal_
Whether one goal has been completed (succeeded or failed)
OnSetParametersCallbackHandle::SharedPtr parameters_callback_
Callback handle for dynamic parameter reconfiguration.
void goalResponseCallback(const GoalHandlePlanRoute::SharedPtr &goal_handle)
Callback for goal response from the action server.
std::vector< std::string > waypoints_param_
WGS84 waypoints to endlessly follow (parameter)
void feedbackCallback(GoalHandlePlanRoute::SharedPtr goal_handle, const std::shared_ptr< const PlanRoute::Feedback > feedback)
Callback for feedback from the action server.
bool enable_random_destination_
Whether to plan a route to a random destination (parameter)
void setup()
Sets up subscribers, publishers, etc. to configure the node.
rclcpp::Subscription< geometry_msgs::msg::PoseStamped >::SharedPtr goal_pose_subscriber_
Subscriber for goal pose.
void declareAndLoadParameter(const std::string &name, T ¶m, const std::string &description, const bool add_to_auto_reconfigurable_params=true, const bool is_required=false, const bool read_only=false, const std::optional< double > &from_value=std::nullopt, const std::optional< double > &to_value=std::nullopt, const std::optional< double > &step_value=std::nullopt, const std::string &additional_constraints="")
Declares and loads a ROS parameter.
std::vector< std::tuple< std::string, std::function< void(const rclcpp::Parameter &)> > > auto_reconfigurable_params_
Auto-reconfigurable parameters for dynamic reconfiguration.
std::unique_ptr< Lanelet2MapInterface > ll2_interface_
Lanelet2 map interface.
std::optional< std::vector< std::pair< double, double > > > parseWaypoints(const std::vector< std::string > &waypoints_param, std::vector< double > &waypoint_wait_times, const rclcpp::Logger &logger)
Parses WGS84 waypoints from "<LATITUDE>,<LONGITUDE>[,<WAIT_TIME_S>]" strings.
constexpr bool is_vector_v
int main(int argc, char *argv[])
Starts the ROS node.