lanelet2_route_planning v2.0.0
Loading...
Searching...
No Matches
plan_route_action_client.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
4#include <chrono>
5#include <functional>
6#include <iterator>
7#include <optional>
8#include <sstream>
9#include <stdexcept>
10
11#include <lanelet2_core/geometry/LaneletMap.h>
12#include <tf2/LinearMath/Quaternion.h>
13#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
14
16
18
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) {
35 try {
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) {
38 return std::nullopt;
39 }
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));
45 }
46 waypoints.emplace_back(lat, lon);
47 parsed_wait_times.push_back(wait_time_s);
48 } catch (const std::invalid_argument& e) {
49 return std::nullopt;
50 } catch (const std::out_of_range& e) {
51 return std::nullopt;
52 }
53 } else {
54 return std::nullopt;
55 }
56 }
57
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;
61 }
62
63 waypoint_wait_times = parsed_wait_times;
64 return waypoints;
65}
66
67PlanRouteActionClient::PlanRouteActionClient() : Node("plan_route_action_client") {
68 this->declareAndLoadParameter("ll2_map_server_name", ll2_map_server_name_, "Name of lanelet2_map_server node", false);
70 "waypoints", waypoints_param_,
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)",
73 true);
74 this->declareAndLoadParameter("enable_random_destination", enable_random_destination_,
75 "Whether to plan a route to a random destination", true);
76 this->declareAndLoadParameter("enable_continuous_planning", enable_continuous_planning_,
77 "Whether to continuously plan a new route (either looping waypoints or to a random destination)",
78 true);
79 this->declareAndLoadParameter("cancel_route", cancel_route_, "Cancel active route planning action (to be set at runtime)",
80 true);
81 this->setup();
82}
83
84template <typename T>
86 T& param,
87 const std::string& description,
88 const bool add_to_auto_reconfigurable_params,
89 const bool is_required,
90 const bool read_only,
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;
99
100 auto type = rclcpp::ParameterValue(param).get_type();
101
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};
113 } else {
114 RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
115 }
116 }
117
118 this->declare_parameter(name, type, param_desc);
119
120 try {
121 param = this->get_parameter(name).get_value<T>();
122 std::stringstream ss;
123 ss << "Loaded parameter '" << name << "': ";
124 if constexpr (is_vector_v<T>) {
125 ss << "[";
126 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "]");
127 } else {
128 ss << param;
129 }
130 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
131 } catch (rclcpp::exceptions::ParameterUninitializedException&) {
132 if (is_required) {
133 RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
134 exit(EXIT_FAILURE);
135 } else {
136 std::stringstream ss;
137 ss << "Missing parameter '" << name << "', using default value: ";
138 if constexpr (is_vector_v<T>) {
139 ss << "[";
140 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "]");
141 } else {
142 ss << param;
143 }
144 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
145 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
146 }
147 }
148
149 if (add_to_auto_reconfigurable_params) {
150 std::function<void(const rclcpp::Parameter&)> setter = [&param](const rclcpp::Parameter& p) { param = p.get_value<T>(); };
151 auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
152 }
153}
154
155rcl_interfaces::msg::SetParametersResult PlanRouteActionClient::parametersCallback(
156 const std::vector<rclcpp::Parameter>& parameters) {
157 for (const auto& param : parameters) {
158 for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
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());
162 break;
163 }
164 }
165
166 // handle waypoints
167 if (param.get_name() == "waypoints") {
168 auto parsed_waypoints = parseWaypoints(waypoints_param_, waypoint_wait_times_, this->get_logger());
169 if (parsed_waypoints) {
170 waypoints_ = *parsed_waypoints;
171 } else {
172 std::stringstream ss;
173 ss << "Failed to parse parameter 'waypoints': [";
174 for (const auto& waypoint : waypoints_param_) {
175 ss << waypoint << (&waypoint != &waypoints_param_.back() ? ", " : "]");
176 }
177 RCLCPP_ERROR(this->get_logger(), "%s", ss.str().c_str());
178 }
179 }
180
181 // handle cancel_route
182 if (param.get_name() == "cancel_route") {
183 if (cancel_route_) {
184 if (action_client_->wait_for_action_server(std::chrono::duration<double>(0.1))) {
185 RCLCPP_INFO(this->get_logger(), "Cancelling route");
186 action_client_->async_cancel_all_goals();
187 } else {
188 RCLCPP_WARN(this->get_logger(), "Action server not available, cannot cancel route");
189 }
190 }
191 }
192 }
193
194 rcl_interfaces::msg::SetParametersResult result;
195 result.successful = true;
196
197 return result;
198}
199
201 // callback for dynamic parameter configuration
203 this->add_on_set_parameters_callback(std::bind(&PlanRouteActionClient::parametersCallback, this, std::placeholders::_1));
204
205 // subscriber for goal pose
206 goal_pose_subscriber_ = this->create_subscription<geometry_msgs::msg::PoseStamped>(
207 "~/goal_pose", 10, std::bind(&PlanRouteActionClient::goalPoseCallback, this, std::placeholders::_1));
208 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", goal_pose_subscriber_->get_topic_name());
209
210 // action client
211 action_client_ = rclcpp_action::create_client<PlanRoute>(this, "/planning/lanelet2_route_planning/plan_route");
212
213 // ll2 map interface
214 ll2_interface_ = std::make_unique<Lanelet2MapInterface>(*this, ll2_map_server_name_);
215
216 // parse waypoints
217 auto parsed_waypoints = parseWaypoints(waypoints_param_, waypoint_wait_times_, this->get_logger());
218 if (parsed_waypoints) {
219 waypoints_ = *parsed_waypoints;
220 } else {
221 std::stringstream ss;
222 ss << "Failed to parse parameter 'waypoints': [";
223 for (const auto& waypoint : waypoints_param_) {
224 ss << waypoint << (&waypoint != &waypoints_param_.back() ? ", " : "]");
225 }
226 RCLCPP_ERROR(this->get_logger(), "%s", ss.str().c_str());
227 }
228
229 // set up auto-planning timer
230 auto_planning_timer_ = this->create_wall_timer(std::chrono::milliseconds(1000),
232}
233
234void PlanRouteActionClient::goalPoseCallback(const geometry_msgs::msg::PoseStamped::SharedPtr msg) {
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());
237 has_active_waypoint_ = false;
240 sendGoal(msg);
241}
242
244 const double now_s = std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
245 if (now_s < auto_planning_resume_time_s_) {
246 return;
247 }
248
251 } else if (!waypoints_.empty()) {
253 next_waypoint_idx_ = 0; // loop waypoints, if continuous planning is enabled
254 }
255 if (next_waypoint_idx_ < waypoints_.size()) {
256 this->planToNextWaypoint();
257 }
258 } else {
259 RCLCPP_DEBUG(this->get_logger(), "Nothing to plan, waiting for waypoints or random destination");
260 }
261}
262
264 // check if waypoint is valid
265 if (next_waypoint_idx_ >= waypoints_.size()) {
266 RCLCPP_ERROR(this->get_logger(), "Waypoint index %ld out of bounds (%ld), skipping", next_waypoint_idx_, waypoints_.size());
267 return;
268 }
269 if (waypoint_wait_times_.size() != waypoints_.size()) {
270 RCLCPP_ERROR(this->get_logger(), "Waypoint wait times do not match waypoints, skipping");
271 return;
272 }
273
274 // check if map is loaded
275 if (!ll2_interface_->map_loaded_) {
276 RCLCPP_ERROR(this->get_logger(), "Map not loaded, cannot generate waypoint");
277 return;
278 }
279
280 // generate goal pose from waypoint
281 auto goal_pose = std::make_shared<geometry_msgs::msg::PoseStamped>();
282 std::vector<geometry_msgs::msg::PointStamped> intermediate_destinations;
283 auto ll2_projector = ll2_interface_->getProjectorPtr();
284 if (!ll2_projector) {
285 RCLCPP_ERROR(this->get_logger(), "Failed to generate waypoint goal pose");
286 return;
287 }
288
289 size_t checked_waypoints = 0;
290 while (checked_waypoints < waypoints_.size()) {
291 const auto& waypoint = waypoints_[next_waypoint_idx_];
292 const double wait_time_s = waypoint_wait_times_[next_waypoint_idx_];
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);
297
298 geometry_msgs::msg::PointStamped waypoint_point;
299 waypoint_point.header.frame_id = ll2_interface_->map_frame_id_;
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;
304
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);
309 if (next_waypoint_idx_ >= waypoints_.size()) {
312 } else {
313 RCLCPP_ERROR(this->get_logger(), "No destination waypoint found after intermediate waypoints");
314 return;
315 }
316 }
317 checked_waypoints++;
318 continue;
319 }
320
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;
324 active_waypoint_wait_time_s_ = wait_time_s;
326 break;
327 }
328
329 // send goal
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());
333 auto_planning_timer_->cancel(); // cancel auto-planning timer until goal completion
335 this->sendGoal(goal_pose, intermediate_destinations);
336 } else {
337 RCLCPP_ERROR(this->get_logger(), "Failed to generate waypoint goal pose");
338 }
339}
340
342 RCLCPP_INFO(this->get_logger(), "Planning route to random destination");
343
344 // check if map is loaded
345 if (!ll2_interface_->map_loaded_) {
346 RCLCPP_ERROR(this->get_logger(), "Map not loaded, cannot generate a random destination");
347 return;
348 }
349
350 // generate random goal pose by sampling a random lanelet
351 auto goal_pose = std::make_shared<geometry_msgs::msg::PoseStamped>();
352 lanelet::LaneletMapConstPtr map = ll2_interface_->getMapPtr();
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) {
365 auto heading =
366 std::atan2(point.y() - centerline[centerline.size() - 2].y(), point.x() - centerline[centerline.size() - 2].x());
367 tf2::Quaternion q;
368 q.setRPY(0, 0, heading);
369 goal_pose->pose.orientation = tf2::toMsg(q);
370 }
371 goal_pose->header.frame_id = ll2_interface_->map_frame_id_;
372 goal_pose->header.stamp = this->now();
373 }
374 }
375
376 // send goal
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());
380 auto_planning_timer_->cancel(); // cancel auto-planning timer until goal completion
381 has_active_waypoint_ = false;
383 this->sendGoal(goal_pose);
384 } else {
385 RCLCPP_ERROR(this->get_logger(), "Failed to generate random goal pose");
386 }
387}
388
389void PlanRouteActionClient::sendGoal(const geometry_msgs::msg::PoseStamped::SharedPtr msg,
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());
393
394 // check if action server is available
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");
397 has_active_waypoint_ = false;
398 auto_planning_timer_->reset(); // restart auto-planning timer
399 return;
400 }
401
402 // build goal
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;
408
409 // send goal
410 auto send_goal_options = rclcpp_action::Client<PlanRoute>::SendGoalOptions();
411 send_goal_options.goal_response_callback = std::bind(&PlanRouteActionClient::goalResponseCallback, this, std::placeholders::_1);
412 send_goal_options.feedback_callback =
413 std::bind(&PlanRouteActionClient::feedbackCallback, this, std::placeholders::_1, std::placeholders::_2);
414 send_goal_options.result_callback = std::bind(&PlanRouteActionClient::resultCallback, this, std::placeholders::_1);
415 goal_handle_future_ = action_client_->async_send_goal(goal, send_goal_options);
416 RCLCPP_INFO(this->get_logger(), "Goal sent");
417}
418
419void PlanRouteActionClient::goalResponseCallback(const GoalHandlePlanRoute::SharedPtr& goal_handle) {
420 if (!goal_handle) {
421 RCLCPP_ERROR(this->get_logger(), "Goal rejected by action server");
422 has_active_waypoint_ = false;
423 auto_planning_timer_->reset(); // restart auto-planning timer
424 } else {
425 RCLCPP_INFO(this->get_logger(), "Goal accepted by action server");
426 }
427}
428
429void PlanRouteActionClient::feedbackCallback(GoalHandlePlanRoute::SharedPtr goal_handle,
430 const std::shared_ptr<const PlanRoute::Feedback> feedback) {
431 (void)goal_handle;
432
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());
440}
441
442void PlanRouteActionClient::resultCallback(const GoalHandlePlanRoute::WrappedResult& result) {
443 const double distance_traveled = result.result->distance_traveled;
444 const builtin_interfaces::msg::Duration& time_traveled = result.result->time_traveled;
445 const double wait_time_s = has_active_waypoint_ ? active_waypoint_wait_time_s_ : 0.0;
446
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,
450 time_traveled.sec);
451 } else {
452 RCLCPP_WARN(this->get_logger(), "Goal succeeded, but destination not reached after %.2fm and %ds", distance_traveled,
453 time_traveled.sec);
454 }
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);
459 } else {
460 RCLCPP_ERROR(this->get_logger(), "Goal finished with unknown result code: %d", static_cast<int>(result.code));
461 }
462
463 has_active_waypoint_ = false;
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();
468 auto_planning_resume_time_s_ = now_s + wait_time_s;
469 }
470 auto_planning_timer_->reset(); // restart auto-planning timer
471}
472
473} // namespace plan_route_action_client
474
482int main(int argc, char* argv[]) {
483 rclcpp::init(argc, argv);
484 rclcpp::spin(std::make_shared<plan_route_action_client::PlanRouteActionClient>());
485 rclcpp::shutdown();
486
487 return 0;
488}
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 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 > &parameters)
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.
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 &param, 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.
int main(int argc, char *argv[])
Starts the ROS node.