Carma-platform v4.2.0
CARMA Platform is built on robot operating system (ROS) and utilizes open source software (OSS) that enables Cooperative Driving Automation (CDA) features to allow Automated Driving Systems to interact and cooperate with infrastructure and other vehicles through communication.
plan_delegator.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2022-2023 LEIDOS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17#include <stdexcept>
18#include <carma_wm/Geometry.hpp>
19#include "plan_delegator.hpp"
20
21namespace plan_delegator
22{
23 namespace std_ph = std::placeholders;
24
25 namespace
26 {
32 void setManeuverStartingLaneletId(carma_planning_msgs::msg::Maneuver& mvr, lanelet::Id start_id) {
33 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Updating maneuver starting_lane_id to " << start_id);
34
35 switch(mvr.type) {
36 case carma_planning_msgs::msg::Maneuver::LANE_CHANGE:
37 mvr.lane_change_maneuver.starting_lane_id = std::to_string(start_id);
38 break;
39 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_STRAIGHT:
40 mvr.intersection_transit_straight_maneuver.starting_lane_id = std::to_string(start_id);
41 break;
42 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_LEFT_TURN:
43 mvr.intersection_transit_left_turn_maneuver.starting_lane_id = std::to_string(start_id);
44 break;
45 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_RIGHT_TURN:
46 mvr.intersection_transit_right_turn_maneuver.starting_lane_id = std::to_string(start_id);
47 break;
48 case carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT:
49 mvr.stop_and_wait_maneuver.starting_lane_id = std::to_string(start_id);
50 break;
51 default:
52 throw std::invalid_argument("Maneuver type does not have starting and ending lane ids");
53 }
54 }
55
61 void setManeuverEndingLaneletId(carma_planning_msgs::msg::Maneuver& mvr, lanelet::Id end_id) {
62 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Updating maneuver ending_lane_id to " << end_id);
63
64 switch(mvr.type) {
65 case carma_planning_msgs::msg::Maneuver::LANE_CHANGE:
66 mvr.lane_change_maneuver.ending_lane_id = std::to_string(end_id);
67 break;
68 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_STRAIGHT:
69 mvr.intersection_transit_straight_maneuver.ending_lane_id = std::to_string(end_id);
70 break;
71 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_LEFT_TURN:
72 mvr.intersection_transit_left_turn_maneuver.ending_lane_id = std::to_string(end_id);
73 break;
74 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_RIGHT_TURN:
75 mvr.intersection_transit_right_turn_maneuver.ending_lane_id = std::to_string(end_id);
76 break;
77 case carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT:
78 mvr.stop_and_wait_maneuver.ending_lane_id = std::to_string(end_id);
79 break;
80 default:
81 throw std::invalid_argument("Maneuver type does not have starting and ending lane ids");
82 }
83 }
84
90 std::string getManeuverStartingLaneletId(carma_planning_msgs::msg::Maneuver mvr) {
91 switch(mvr.type) {
92 case carma_planning_msgs::msg::Maneuver::LANE_CHANGE:
93 return mvr.lane_change_maneuver.starting_lane_id;
94 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_STRAIGHT:
95 return mvr.intersection_transit_straight_maneuver.starting_lane_id;
96 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_LEFT_TURN:
97 return mvr.intersection_transit_left_turn_maneuver.starting_lane_id;
98 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_RIGHT_TURN:
99 return mvr.intersection_transit_right_turn_maneuver.starting_lane_id;
100 case carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT:
101 return mvr.stop_and_wait_maneuver.starting_lane_id;
102 default:
103 throw std::invalid_argument("Maneuver type does not have starting and ending lane ids");
104 }
105 }
106
112 std::string getManeuverEndingLaneletId(carma_planning_msgs::msg::Maneuver mvr) {
113 switch(mvr.type) {
114 case carma_planning_msgs::msg::Maneuver::LANE_CHANGE:
115 return mvr.lane_change_maneuver.ending_lane_id;
116 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_STRAIGHT:
117 return mvr.intersection_transit_straight_maneuver.ending_lane_id;
118 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_LEFT_TURN:
119 return mvr.intersection_transit_left_turn_maneuver.ending_lane_id;
120 case carma_planning_msgs::msg::Maneuver::INTERSECTION_TRANSIT_RIGHT_TURN:
121 return mvr.intersection_transit_right_turn_maneuver.ending_lane_id;
122 case carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT:
123 return mvr.stop_and_wait_maneuver.ending_lane_id;
124 default:
125 throw std::invalid_argument("Maneuver type does not have starting and ending lane ids");
126 }
127 }
128 } //namespace
129
130
131 PlanDelegator::PlanDelegator(const rclcpp::NodeOptions &options) : carma_ros2_utils::CarmaLifecycleNode(options),
132 tf2_buffer_(this->get_clock()),
133 wml_(this->get_node_base_interface(), this->get_node_logging_interface(),
134 this->get_node_topics_interface(), this->get_node_parameters_interface())
135 {
136 // Create initial config
137 config_ = Config();
138
139 config_.planning_topic_prefix = declare_parameter<std::string>("planning_topic_prefix", config_.planning_topic_prefix);
140 config_.planning_topic_suffix = declare_parameter<std::string>("planning_topic_suffix", config_.planning_topic_suffix);
141 config_.trajectory_planning_rate = declare_parameter<double>("trajectory_planning_rate", config_.trajectory_planning_rate);
142 config_.max_trajectory_duration = declare_parameter<double>("trajectory_duration_threshold", config_.max_trajectory_duration);
143 config_.min_crawl_speed = declare_parameter<double>("min_speed", config_.min_crawl_speed);
144 config_.duration_to_signal_before_lane_change = declare_parameter<double>("duration_to_signal_before_lane_change", config_.duration_to_signal_before_lane_change);
145 config_.tactical_plugin_service_call_timeout = declare_parameter<int>("tactical_plugin_service_call_timeout", config_.tactical_plugin_service_call_timeout);
146 }
147
148 carma_ros2_utils::CallbackReturn PlanDelegator::handle_on_configure(const rclcpp_lifecycle::State &)
149 {
150 // Reset config
151 config_ = Config();
152
153 get_parameter<std::string>("planning_topic_prefix", config_.planning_topic_prefix);
154 get_parameter<std::string>("planning_topic_suffix", config_.planning_topic_suffix);
155 get_parameter<double>("trajectory_planning_rate", config_.trajectory_planning_rate);
156 get_parameter<double>("trajectory_duration_threshold", config_.max_trajectory_duration);
157 get_parameter<double>("min_speed", config_.min_crawl_speed);
158 get_parameter<double>("duration_to_signal_before_lane_change", config_.duration_to_signal_before_lane_change);
159 get_parameter<int>("tactical_plugin_service_call_timeout", config_.tactical_plugin_service_call_timeout);
160
161 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Done loading parameters: " << config_);
162
163 // Setup publishers
164 traj_pub_ = create_publisher<carma_planning_msgs::msg::TrajectoryPlan>("plan_trajectory", 5);
165 upcoming_lane_change_status_pub_ = create_publisher<carma_planning_msgs::msg::UpcomingLaneChangeStatus>("upcoming_lane_change_status", 1);
166 turn_signal_command_pub_ = create_publisher<autoware_msgs::msg::LampCmd>("lamp_cmd", 1);
167
168 // Setup subscribers
169 plan_sub_ = create_subscription<carma_planning_msgs::msg::ManeuverPlan>("final_maneuver_plan", 5, std::bind(&PlanDelegator::maneuverPlanCallback, this, std_ph::_1));
170 twist_sub_ = create_subscription<geometry_msgs::msg::TwistStamped>("current_velocity", 5,
171 [this](geometry_msgs::msg::TwistStamped::UniquePtr twist) {this->latest_twist_ = *twist;});
172 pose_sub_ = create_subscription<geometry_msgs::msg::PoseStamped>("current_pose", 5, std::bind(&PlanDelegator::poseCallback, this, std_ph::_1));
173 guidance_state_sub_ = create_subscription<carma_planning_msgs::msg::GuidanceState>("guidance_state", 5, std::bind(&PlanDelegator::guidanceStateCallback, this, std_ph::_1));
174
177 return CallbackReturn::SUCCESS;
178 }
179
180 carma_ros2_utils::CallbackReturn PlanDelegator::handle_on_activate(const rclcpp_lifecycle::State &)
181 {
182 traj_timer_ = create_timer(get_clock(),
183 std::chrono::milliseconds((int)(1 / config_.trajectory_planning_rate * 1000)),
184 std::bind(&PlanDelegator::onTrajPlanTick, this));
185 return CallbackReturn::SUCCESS;
186 }
187
188 void PlanDelegator::guidanceStateCallback(carma_planning_msgs::msg::GuidanceState::UniquePtr msg)
189 {
191 }
192
193 void PlanDelegator::maneuverPlanCallback(carma_planning_msgs::msg::ManeuverPlan::UniquePtr plan)
194 {
195 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Received request to delegate plan ID " << std::string(plan->maneuver_plan_id));
196 // do basic check to see if the input is valid
197 auto copy_plan = *plan;
198
199 if (isManeuverPlanValid(copy_plan))
200 {
201 latest_maneuver_plan_ = copy_plan;
202 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Received plan with " << latest_maneuver_plan_.maneuvers.size() << " maneuvers");
203
204 // Update the parameters associated with each maneuver
205 for (auto& maneuver : latest_maneuver_plan_.maneuvers) {
206 updateManeuverParameters(maneuver);
207 }
208 }
209 else {
210 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"),"Received empty plan, no maneuvers found in plan ID " << std::string(plan->maneuver_plan_id));
211 }
212
213 // Update upcoming_lane_change_information_ and current_lane_change_information_ based on the received maneuver plan
214 if(!latest_maneuver_plan_.maneuvers.empty()){
215 // Get ego vehicle's current downtrack
216 lanelet::BasicPoint2d current_loc(latest_pose_.pose.position.x, latest_pose_.pose.position.y);
217 double current_downtrack = wm_->routeTrackPos(current_loc).downtrack;
218
219 // Set upcoming_lane_change_information_ based on the first found lane change in the plan that begins after current_downtrack, if one exists
220 upcoming_lane_change_information_ = boost::optional<LaneChangeInformation>(); // Reset to empty optional
221 for(const auto& maneuver : latest_maneuver_plan_.maneuvers){
222 if(maneuver.type == carma_planning_msgs::msg::Maneuver::LANE_CHANGE){
223 if(current_downtrack >= maneuver.lane_change_maneuver.start_dist){
224 // Skip this lane change maneuver since ego vehicle has passed the lane change start point (this is not an 'upcoming' lane change)
225 continue;
226 }
227 else{
228 LaneChangeInformation upcoming_lane_change_information = getLaneChangeInformation(maneuver);
229 upcoming_lane_change_information_ = boost::optional<LaneChangeInformation>(upcoming_lane_change_information);
230 break;
231 }
232 }
233 }
234
235 // Set current_lane_change_information_ if the first maneuver is a lane change
236 current_lane_change_information_ = boost::optional<LaneChangeInformation>(); // Reset to empty optional
237 if(latest_maneuver_plan_.maneuvers[0].type == carma_planning_msgs::msg::Maneuver::LANE_CHANGE){
238 LaneChangeInformation current_lane_change_information = getLaneChangeInformation(latest_maneuver_plan_.maneuvers[0]);
239 current_lane_change_information_ = boost::optional<LaneChangeInformation>(current_lane_change_information);
240 }
241 }
242 }
243
244 void PlanDelegator::poseCallback(geometry_msgs::msg::PoseStamped::UniquePtr pose_msg)
245 {
246 latest_pose_ = *pose_msg;
247
248 // Publish the upcoming lane change status
250
251 // Publish the current turn signal command
253 }
254
255 LaneChangeInformation PlanDelegator::getLaneChangeInformation(const carma_planning_msgs::msg::Maneuver& lane_change_maneuver){
256 LaneChangeInformation lane_change_information;
257
258 lane_change_information.starting_downtrack = lane_change_maneuver.lane_change_maneuver.start_dist;
259
260 // Get the starting and ending lanelets for this lane change maneuver
261 lanelet::ConstLanelet starting_lanelet = wm_->getMap()->laneletLayer.get(std::stoi(lane_change_maneuver.lane_change_maneuver.starting_lane_id));
262 lanelet::ConstLanelet ending_lanelet = wm_->getMap()->laneletLayer.get(std::stoi(lane_change_maneuver.lane_change_maneuver.ending_lane_id));
263
264 // Determine if lane change is a left or right lane change and update lane_change_information accordingly
265 bool shared_boundary_found = false;
266
267 lanelet::ConstLanelet current_lanelet = starting_lanelet;
268
269 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "Searching for shared boundary with starting lanechange lanelet " << std::to_string(current_lanelet.id()) << " and ending lanelet " << std::to_string(ending_lanelet.id()));
270 while(!shared_boundary_found){
271 // Assumption: Adjacent lanelets share lane boundary
272
273 if(current_lanelet.leftBound() == ending_lanelet.rightBound()){
274 // If current lanelet's left lane boundary matches the ending lanelet's right lane boundary, it is a left lane change
275 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "Lanelet " << std::to_string(current_lanelet.id()) << " shares left boundary with " << std::to_string(ending_lanelet.id()));
276 lane_change_information.is_right_lane_change = false;
277 shared_boundary_found = true;
278 }
279 else if(current_lanelet.rightBound() == ending_lanelet.leftBound()){
280 // If current lanelet's right lane boundary matches the ending lanelet's left lane boundary, it is a right lane change
281 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "Lanelet " << std::to_string(current_lanelet.id()) << " shares right boundary with " << std::to_string(ending_lanelet.id()));
282 lane_change_information.is_right_lane_change = true;
283 shared_boundary_found = true;
284 }
285 else{
286 // If there are no following lanelets on route, lanechange should be completing before reaching it
287 if(wm_->getMapRoutingGraph()->following(current_lanelet, false).empty())
288 {
289 // Maneuver requires we travel further before completing lane change, but there is no routable lanelet directly ahead;
290 // in this case we have reached a lanelet which does not have a routable lanelet ahead and isn't adjacent to the lanelet where lane change ends.
291 // A lane change should have already happened at this point
292 throw(std::invalid_argument("No following lanelets from current lanelet reachable without a lane change, incorrectly chosen end lanelet"));
293 }
294
295 current_lanelet = wm_->getMapRoutingGraph()->following(current_lanelet, false).front();
296 if(current_lanelet.id() == starting_lanelet.id()){
297 //Looped back to starting lanelet
298 throw(std::invalid_argument("No lane change in path"));
299 }
300 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "Now checking for shared lane boundary with lanelet " << std::to_string(current_lanelet.id()) << " and ending lanelet " << std::to_string(ending_lanelet.id()));
301 }
302 }
303
304 return lane_change_information;
305 }
306
307 void PlanDelegator::publishUpcomingLaneChangeStatus(const boost::optional<LaneChangeInformation>& upcoming_lane_change_information){
308 // Initialize an UpcomingLaneChangeStatus message, which will be populated based on upcoming_lane_change_information
309 carma_planning_msgs::msg::UpcomingLaneChangeStatus upcoming_lane_change_status;
310
311 // Update upcoming_lane_change_status
312 if(upcoming_lane_change_information){
313 // Get the downtrack distance between the ego vehicle and the start of the upcoming lane change maneuver
314 lanelet::BasicPoint2d current_loc(latest_pose_.pose.position.x, latest_pose_.pose.position.y);
315 double current_downtrack = wm_->routeTrackPos(current_loc).downtrack;
316 upcoming_lane_change_status.downtrack_until_lanechange = std::max(0.0, upcoming_lane_change_information.get().starting_downtrack - current_downtrack);
317
318 // Set upcoming lane change status as a right lane change or left lane change
319 if(upcoming_lane_change_information.get().is_right_lane_change){
320 upcoming_lane_change_status.lane_change = carma_planning_msgs::msg::UpcomingLaneChangeStatus::RIGHT;
321 }
322 else{
323 upcoming_lane_change_status.lane_change = carma_planning_msgs::msg::UpcomingLaneChangeStatus::LEFT;
324 }
325 }
326 else{
327 upcoming_lane_change_status.lane_change = carma_planning_msgs::msg::UpcomingLaneChangeStatus::NONE;
328 }
329
330 // Publish upcoming_lane_change_status
331 upcoming_lane_change_status_pub_->publish(upcoming_lane_change_status);
332
333 // Store UpcomingLaneChangeStatus in upcoming_lane_change_status_
334 upcoming_lane_change_status_ = upcoming_lane_change_status;
335 }
336
337 void PlanDelegator::publishTurnSignalCommand(const boost::optional<LaneChangeInformation>& current_lane_change_information, const carma_planning_msgs::msg::UpcomingLaneChangeStatus& upcoming_lane_change_status)
338 {
339 // Initialize turn signal command message
340 // NOTE: A LampCmd message can have its 'r' OR 'l' field set to 1 to indicate an activated right or left turn signal, respectively. Both fields cannot be set to 1 at the same time.
341 autoware_msgs::msg::LampCmd turn_signal_command;
342
343 // Publish turn signal command with priority placed on the current lane change, if one exists
344 if(current_lane_change_information){
345 // Publish turn signal command for the current lane change based on the lane change direction
346 if(current_lane_change_information.get().is_right_lane_change){
347 turn_signal_command.r = 1;
348 }
349 else{
350 turn_signal_command.l = 1;
351 }
352 turn_signal_command_pub_->publish(turn_signal_command);
353 }
354 else if(upcoming_lane_change_status.lane_change != carma_planning_msgs::msg::UpcomingLaneChangeStatus::NONE){
355 // Only publish turn signal command for upcoming lane change if it will begin in less than the time defined by config_.duration_to_signal_before_lane_change
356 if((upcoming_lane_change_status.downtrack_until_lanechange / latest_twist_.twist.linear.x) <= config_.duration_to_signal_before_lane_change){
357 if(upcoming_lane_change_status.lane_change == carma_planning_msgs::msg::UpcomingLaneChangeStatus::RIGHT){
358 turn_signal_command.r = 1;
359 }
360 else{
361 turn_signal_command.l = 1;
362 }
363 turn_signal_command_pub_->publish(turn_signal_command);
364 }
365 }
366 else{
367 // Publish turn signal command with neither turn signal activated
368 turn_signal_command_pub_->publish(turn_signal_command);
369 }
370
371 // Store turn signal command in latest_turn_signal_command_
372 latest_turn_signal_command_ = turn_signal_command;
373 }
374
375 carma_ros2_utils::ClientPtr<carma_planning_msgs::srv::PlanTrajectory> PlanDelegator::getPlannerClientByName(const std::string& planner_name)
376 {
377 if(planner_name.size() == 0)
378 {
379 throw std::invalid_argument("Invalid trajectory planner name because it has zero length!");
380 }
381 if(trajectory_planners_.find(planner_name) == trajectory_planners_.end())
382 {
383 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Discovered new trajectory planner: " << planner_name);
384
385 trajectory_planners_.emplace(
386 planner_name, create_client<carma_planning_msgs::srv::PlanTrajectory>(config_.planning_topic_prefix + planner_name + config_.planning_topic_suffix));
387 }
388 return trajectory_planners_[planner_name];
389 }
390
391 bool PlanDelegator::isManeuverPlanValid(const carma_planning_msgs::msg::ManeuverPlan& maneuver_plan) const noexcept
392 {
393 // currently it only checks if maneuver list is empty
394 return !maneuver_plan.maneuvers.empty();
395 }
396
397 bool PlanDelegator::isTrajectoryValid(const carma_planning_msgs::msg::TrajectoryPlan& trajectory_plan) const noexcept
398 {
399 // currently it only checks if trajectory contains less than 2 points
400 return !(trajectory_plan.trajectory_points.size() < 2);
401 }
402
403 bool PlanDelegator::isManeuverExpired(const carma_planning_msgs::msg::Maneuver& maneuver, rclcpp::Time current_time) const
404 {
405 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "maneuver start time:" << std::to_string(rclcpp::Time(GET_MANEUVER_PROPERTY(maneuver, start_time)).seconds()));
406 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "maneuver end time:" << std::to_string(rclcpp::Time(GET_MANEUVER_PROPERTY(maneuver, end_time)).seconds()));
407 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "current time:" << std::to_string(now().seconds()));
408 bool isexpired = rclcpp::Time(GET_MANEUVER_PROPERTY(maneuver, end_time), get_clock()->get_clock_type()) <= current_time; // TODO maneuver expiration should maybe be based off of distance not time? https://github.com/usdot-fhwa-stol/carma-platform/issues/1107
409 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "isexpired:" << isexpired);
410 // TODO: temporary disabling expiration check
411 return false;
412 }
413
414 std::shared_ptr<carma_planning_msgs::srv::PlanTrajectory::Request> PlanDelegator::composePlanTrajectoryRequest(const carma_planning_msgs::msg::TrajectoryPlan& latest_trajectory_plan, const uint16_t& current_maneuver_index) const
415 {
416 auto plan_req = std::make_shared<carma_planning_msgs::srv::PlanTrajectory::Request>();
417 plan_req->maneuver_plan = latest_maneuver_plan_;
418
419 // set current vehicle state if we have NOT planned any previous trajectories
420 if(latest_trajectory_plan.trajectory_points.empty())
421 {
422 plan_req->header.stamp = latest_pose_.header.stamp;
423 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "latest_pose_.header.stamp: " << std::to_string(rclcpp::Time(latest_pose_.header.stamp).seconds()));
424 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "plan_req->header.stamp: " << std::to_string(rclcpp::Time(plan_req->header.stamp).seconds()));
425
426 plan_req->vehicle_state.longitudinal_vel = latest_twist_.twist.linear.x;
427 plan_req->vehicle_state.x_pos_global = latest_pose_.pose.position.x;
428 plan_req->vehicle_state.y_pos_global = latest_pose_.pose.position.y;
429 double roll, pitch, yaw;
430 carma_wm::geometry::rpyFromQuaternion(latest_pose_.pose.orientation, roll, pitch, yaw);
431 plan_req->vehicle_state.orientation = yaw;
432 plan_req->maneuver_index_to_plan = current_maneuver_index;
433 }
434 // set vehicle state based on last two planned trajectory points
435 else
436 {
437 carma_planning_msgs::msg::TrajectoryPlanPoint last_point = latest_trajectory_plan.trajectory_points.back();
438 carma_planning_msgs::msg::TrajectoryPlanPoint second_last_point = *(latest_trajectory_plan.trajectory_points.rbegin() + 1);
439 plan_req->vehicle_state.x_pos_global = last_point.x;
440 plan_req->vehicle_state.y_pos_global = last_point.y;
441 auto distance_diff = std::sqrt(std::pow(last_point.x - second_last_point.x, 2) + std::pow(last_point.y - second_last_point.y, 2));
442 rclcpp::Duration time_diff = rclcpp::Time(last_point.target_time) - rclcpp::Time(second_last_point.target_time);
443 auto time_diff_sec = time_diff.seconds();
444 plan_req->maneuver_index_to_plan = current_maneuver_index;
445 // this assumes the vehicle does not have significant lateral velocity
446 plan_req->header.stamp = latest_trajectory_plan.trajectory_points.back().target_time;
447 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "plan_req->header.stamp: " << std::to_string(rclcpp::Time(plan_req->header.stamp).seconds()));
448
449 plan_req->vehicle_state.longitudinal_vel = distance_diff / time_diff_sec;
450 // TODO develop way to set yaw value for future points
451 }
452 return plan_req;
453 }
454
455 bool PlanDelegator::isTrajectoryLongEnough(const carma_planning_msgs::msg::TrajectoryPlan& plan) const noexcept
456 {
457 rclcpp::Duration time_diff = rclcpp::Time(plan.trajectory_points.back().target_time) - rclcpp::Time(plan.trajectory_points.front().target_time);
458 return time_diff.seconds() >= config_.max_trajectory_duration;
459 }
460
461 void PlanDelegator::updateManeuverParameters(carma_planning_msgs::msg::Maneuver& maneuver)
462 {
463 if (!wm_->getMap())
464 {
465 RCLCPP_ERROR_STREAM(rclcpp::get_logger("plan_delegator"), "Map is not set yet");
466 return;
467 }
468
469 // Update maneuver starting and ending downtrack distances
470 double original_start_dist = GET_MANEUVER_PROPERTY(maneuver, start_dist);
471 double original_end_dist = GET_MANEUVER_PROPERTY(maneuver, end_dist);
472 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Changing maneuver distances for planner: " << GET_MANEUVER_PROPERTY(maneuver, parameters.planning_tactical_plugin));
473 double adjusted_start_dist = original_start_dist - length_to_front_bumper_;
474 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"original_start_dist:" << original_start_dist);
475 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"adjusted_start_dist:" << adjusted_start_dist);
476 double adjusted_end_dist = original_end_dist - length_to_front_bumper_;
477 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"original_end_dist:" << original_end_dist);
478 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"adjusted_end_dist:" << adjusted_end_dist);
479 SET_MANEUVER_PROPERTY(maneuver, start_dist, adjusted_start_dist);
480 SET_MANEUVER_PROPERTY(maneuver, end_dist, adjusted_end_dist);
481
482 // Shift maneuver starting and ending lanelets
483 // NOTE: Assumes that maneuver start and end downtrack distances have not been shifted by more than one lanelet
484 if(maneuver.type == carma_planning_msgs::msg::Maneuver::LANE_FOLLOWING && !maneuver.lane_following_maneuver.lane_ids.empty()){
485 // (1) Add new beginning lanelet to maneuver if necessary and (2) remove ending lanelet from maneuver if necessary
486
487 // Obtain the original starting lanelet from the maneuver
488 lanelet::Id original_starting_lanelet_id = std::stoi(maneuver.lane_following_maneuver.lane_ids.front());
489 lanelet::ConstLanelet original_starting_lanelet = wm_->getMap()->laneletLayer.get(original_starting_lanelet_id);
490
491 // Get the downtrack of the start of the original starting lanelet
492 lanelet::BasicPoint2d original_starting_lanelet_centerline_start_point = lanelet::utils::to2D(original_starting_lanelet.centerline()).front();
493 double original_starting_lanelet_centerline_start_point_dt = wm_->routeTrackPos(original_starting_lanelet_centerline_start_point).downtrack;
494
495 if(adjusted_start_dist < original_starting_lanelet_centerline_start_point_dt){
496
497 auto previous_lanelets = wm_->getMapRoutingGraph()->previous(original_starting_lanelet, false);
498
499 if(!previous_lanelets.empty()){
500
501 auto llt_on_route_optional = wm_->getFirstLaneletOnShortestPath(previous_lanelets);
502
503 lanelet::ConstLanelet previous_lanelet_to_add;
504
505 if (llt_on_route_optional){
506 previous_lanelet_to_add = llt_on_route_optional.value();
507 }
508 else{
509 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"), "When adjusting maneuver for lane follow, no previous lanelet found on the shortest path for lanelet "
510 << original_starting_lanelet.id() << ". Picking arbitrary lanelet: " << previous_lanelets[0].id() << ", instead");
511 previous_lanelet_to_add = previous_lanelets[0];
512 }
513
514 // lane_ids array is ordered by increasing downtrack, so this new starting lanelet is inserted at the front
515 maneuver.lane_following_maneuver.lane_ids.insert(maneuver.lane_following_maneuver.lane_ids.begin(), std::to_string(previous_lanelet_to_add.id()));
516
517 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"), "Inserted lanelet " << std::to_string(previous_lanelet_to_add.id()) << " to beginning of maneuver.");
518 }
519 else{
520 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"), "No previous lanelet was found for lanelet " << original_starting_lanelet.id());
521 }
522 }
523
524 // Obtain the maneuver ending lanelet
525 lanelet::Id original_ending_lanelet_id = std::stoi(maneuver.lane_following_maneuver.lane_ids.back());
526 lanelet::ConstLanelet original_ending_lanelet = wm_->getMap()->laneletLayer.get(original_ending_lanelet_id);
527
528 // Get the downtrack of the start of the maneuver ending lanelet
529 lanelet::BasicPoint2d original_ending_lanelet_centerline_start_point = lanelet::utils::to2D(original_ending_lanelet.centerline()).front();
530 double original_ending_lanelet_centerline_start_point_dt = wm_->routeTrackPos(original_ending_lanelet_centerline_start_point).downtrack;
531
532 if(adjusted_end_dist < original_ending_lanelet_centerline_start_point_dt){
533 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Original ending lanelet " << original_ending_lanelet.id() << " removed from lane_ids since the updated maneuver no longer crosses it");
534
535 // lane_ids array is ordered by increasing downtrack, so the last element in the array corresponds to the original ending lanelet
536 maneuver.lane_following_maneuver.lane_ids.pop_back();
537 }
538 }
539 else if (maneuver.type != carma_planning_msgs::msg::Maneuver::LANE_FOLLOWING){
540 // (1) Update starting maneuver lanelet if necessary and (2) Update ending maneuver lanelet if necessary
541
542 // Obtain the original starting lanelet from the maneuver
543 lanelet::Id original_starting_lanelet_id = std::stoi(getManeuverStartingLaneletId(maneuver));
544 lanelet::ConstLanelet original_starting_lanelet = wm_->getMap()->laneletLayer.get(original_starting_lanelet_id);
545
546 // Get the downtrack of the start of the lanelet
547 lanelet::BasicPoint2d original_starting_lanelet_centerline_start_point = lanelet::utils::to2D(original_starting_lanelet.centerline()).front();
548 double original_starting_lanelet_centerline_start_point_dt = wm_->routeTrackPos(original_starting_lanelet_centerline_start_point).downtrack;
549
550 if(adjusted_start_dist < original_starting_lanelet_centerline_start_point_dt){
551 auto previous_lanelets = wm_->getMapRoutingGraph()->previous(original_starting_lanelet, false);
552 if(!previous_lanelets.empty()){
553 auto llt_on_route_optional = wm_->getFirstLaneletOnShortestPath(previous_lanelets);
554 lanelet::ConstLanelet previous_lanelet_to_add;
555
556 if (llt_on_route_optional){
557 previous_lanelet_to_add = llt_on_route_optional.value();
558 }
559 else{
560 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"), "When adjusting non-lane follow maneuver, no previous lanelet found on the shortest path for lanelet "
561 << original_starting_lanelet.id() << ". Picking arbitrary lanelet: " << previous_lanelets[0].id() << ", instead");
562 previous_lanelet_to_add = previous_lanelets[0];
563 }
564 setManeuverStartingLaneletId(maneuver, previous_lanelet_to_add.id());
565 }
566 else{
567 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"), "No previous lanelet was found for lanelet " << original_starting_lanelet.id());
568 }
569 }
570
571 // Obtain the original ending lanelet from the maneuver
572 lanelet::Id original_ending_lanelet_id = std::stoi(getManeuverEndingLaneletId(maneuver));
573 lanelet::ConstLanelet original_ending_lanelet = wm_->getMap()->laneletLayer.get(original_ending_lanelet_id);
574
575 // Get the downtrack of the start of the ending lanelet
576 lanelet::BasicPoint2d original_ending_lanelet_centerline_start_point = lanelet::utils::to2D(original_ending_lanelet.centerline()).front();
577 double original_ending_lanelet_centerline_start_point_dt = wm_->routeTrackPos(original_ending_lanelet_centerline_start_point).downtrack;
578
579 if(adjusted_end_dist < original_ending_lanelet_centerline_start_point_dt){
580 auto previous_lanelets = wm_->getMapRoutingGraph()->previous(original_ending_lanelet, false);
581
582 if(!previous_lanelets.empty()){
583 setManeuverEndingLaneletId(maneuver, previous_lanelets[0].id());
584 }
585 else{
586 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"), "No previous lanelet was found for lanelet " << original_starting_lanelet.id());
587 }
588 }
589 }
590 }
591
592 carma_planning_msgs::msg::TrajectoryPlan PlanDelegator::planTrajectory()
593 {
594 carma_planning_msgs::msg::TrajectoryPlan latest_trajectory_plan;
596 {
597 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Guidance is not engaged. Plan delegator will not plan trajectory.");
598 return latest_trajectory_plan;
599 }
600
601 // Flag for the first received trajectory plan service response
602 bool first_trajectory_plan = true;
603
604 // Track the index of the starting maneuver in the maneuver plan that this trajectory plan service request is for
605 uint16_t current_maneuver_index = 0;
606
607 // Loop through maneuver list to make service call to applicable Tactical Plugin
608 while(current_maneuver_index < latest_maneuver_plan_.maneuvers.size())
609 {
610 // const auto& maneuver = latest_maneuver_plan_.maneuvers[current_maneuver_index];
611 auto& maneuver = latest_maneuver_plan_.maneuvers[current_maneuver_index];
612
613 // ignore expired maneuvers
614 if(isManeuverExpired(maneuver, get_clock()->now()))
615 {
616 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Dropping expired maneuver: " << GET_MANEUVER_PROPERTY(maneuver, parameters.maneuver_id));
617 // Update the maneuver plan index for the next loop
618 ++current_maneuver_index;
619 continue;
620 }
621 lanelet::BasicPoint2d current_loc(latest_pose_.pose.position.x, latest_pose_.pose.position.y);
622 double current_downtrack = wm_->routeTrackPos(current_loc).downtrack;
623 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"current_downtrack" << current_downtrack);
624 double maneuver_end_dist = GET_MANEUVER_PROPERTY(maneuver, end_dist);
625 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"maneuver_end_dist" << maneuver_end_dist);
626
627 // ignore maneuver that is passed.
628 if (current_downtrack > maneuver_end_dist)
629 {
630 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Dropping passed maneuver: " << GET_MANEUVER_PROPERTY(maneuver, parameters.maneuver_id));
631 // Update the maneuver plan index for the next loop
632 ++current_maneuver_index;
633 continue;
634 }
635
636
637 // get corresponding ros service client for plan trajectory
638 auto maneuver_planner = GET_MANEUVER_PROPERTY(maneuver, parameters.planning_tactical_plugin);
639
640 auto client = getPlannerClientByName(maneuver_planner);
641
642 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Current planner: " << maneuver_planner);
643
644 // compose service request
645 auto plan_req = composePlanTrajectoryRequest(latest_trajectory_plan, current_maneuver_index);
646
647 auto plan_response = client->async_send_request(plan_req);
648
649 auto future_status = plan_response.wait_for(std::chrono::milliseconds(config_.tactical_plugin_service_call_timeout));
650
651 // Wait for the result.
652 if (future_status == std::future_status::ready)
653 {
654 // validate trajectory before add to the plan
655 if(!isTrajectoryValid(plan_response.get()->trajectory_plan))
656 {
657 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"),"Found invalid trajectory with less than 2 trajectory points for " << std::string(latest_maneuver_plan_.maneuver_plan_id));
658 break;
659 }
660 //Remove duplicate point from start of trajectory
661 if(latest_trajectory_plan.trajectory_points.size() !=0){
662
663 if(latest_trajectory_plan.trajectory_points.back().target_time == plan_response.get()->trajectory_plan.trajectory_points.front().target_time){
664 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"Removing duplicate point for planner: " << maneuver_planner);
665 plan_response.get()->trajectory_plan.trajectory_points.erase(plan_response.get()->trajectory_plan.trajectory_points.begin());
666 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"plan_response.get()->trajectory_plan size: " << plan_response.get()->trajectory_plan.trajectory_points.size());
667
668 }
669 }
670 latest_trajectory_plan.trajectory_points.insert(latest_trajectory_plan.trajectory_points.end(),
671 plan_response.get()->trajectory_plan.trajectory_points.begin(),
672 plan_response.get()->trajectory_plan.trajectory_points.end());
673 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"new latest_trajectory_plan size: " << latest_trajectory_plan.trajectory_points.size());
674
675 // Assign the trajectory plan's initial longitudinal velocity based on the first tactical plugin's response
676 if(first_trajectory_plan == true)
677 {
678 latest_trajectory_plan.initial_longitudinal_velocity = plan_response.get()->trajectory_plan.initial_longitudinal_velocity;
679 first_trajectory_plan = false;
680 }
681
682 if(isTrajectoryLongEnough(latest_trajectory_plan))
683 {
684 RCLCPP_INFO_STREAM(rclcpp::get_logger("plan_delegator"),"Plan Trajectory completed for " << std::string(latest_maneuver_plan_.maneuver_plan_id));
685 break;
686 }
687
688 // Update the maneuver plan index based on the last maneuver index converted to a trajectory
689 // This is required since inlanecruising_plugin can plan a trajectory over contiguous LANE_FOLLOWING maneuvers
690 if(plan_response.get()->related_maneuvers.size() > 0)
691 {
692 current_maneuver_index = plan_response.get()->related_maneuvers.back() + 1;
693 }
694 }
695 else
696 {
697 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"),"Unsuccessful service call to trajectory planner:" << maneuver_planner << " for plan ID " << std::string(latest_maneuver_plan_.maneuver_plan_id));
698 // if one service call fails, it should end plan immediately because it is there is no point to generate plan with empty space
699 break;
700 }
701 }
702
703 return latest_trajectory_plan;
704 }
705
707 {
708 carma_planning_msgs::msg::TrajectoryPlan trajectory_plan = planTrajectory();
709
710 // Check if planned trajectory is valid before send out
711 if(isTrajectoryValid(trajectory_plan))
712 {
713 trajectory_plan.header.stamp = get_clock()->now();
714 traj_pub_->publish(trajectory_plan);
715 }
716 else
717 {
718 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"),"Planned trajectory is empty. It will not be published!");
719 }
720 }
721
723 {
724 tf2_listener_.reset(new tf2_ros::TransformListener(tf2_buffer_));
725 tf2_buffer_.setUsingDedicatedThread(true);
726 try
727 {
728 geometry_msgs::msg::TransformStamped tf = tf2_buffer_.lookupTransform("base_link", "vehicle_front", rclcpp::Time(0), rclcpp::Duration(20.0, 0)); //save to local copy of transform 20 sec timeout
729 length_to_front_bumper_ = tf.transform.translation.x;
730 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("plan_delegator"),"length_to_front_bumper_: " << length_to_front_bumper_);
731
732 }
733 catch (const tf2::TransformException &ex)
734 {
735 RCLCPP_WARN_STREAM(rclcpp::get_logger("plan_delegator"), ex.what());
736 }
737 }
738
739} // namespace plan_delegator
740
741
742#include "rclcpp_components/register_node_macro.hpp"
743
744// Register the component with class_loader
745RCLCPP_COMPONENTS_REGISTER_NODE(plan_delegator::PlanDelegator)
#define GET_MANEUVER_PROPERTY(mvr, property)
Macro definition to enable easier access to fields shared across the maneuver types.
WorldModelConstPtr getWorldModel()
Returns a pointer to an intialized world model instance.
Definition: WMListener.cpp:184
void onTrajPlanTick()
Callback function for triggering trajectory planning.
carma_ros2_utils::SubPtr< carma_planning_msgs::msg::GuidanceState > guidance_state_sub_
carma_planning_msgs::msg::TrajectoryPlan planTrajectory()
Plan trajectory based on latest maneuver plan via ROS service call to plugins.
LaneChangeInformation getLaneChangeInformation(const carma_planning_msgs::msg::Maneuver &lane_change_maneuver)
Function for generating a LaneChangeInformation object from a provided lane change maneuver.
carma_ros2_utils::ClientPtr< carma_planning_msgs::srv::PlanTrajectory > getPlannerClientByName(const std::string &planner_name)
Get PlanTrajectory service client by plugin name and create new PlanTrajectory service client if spec...
void poseCallback(geometry_msgs::msg::PoseStamped::UniquePtr pose_msg)
Callback function for vehicle pose subscriber. Updates latest_pose_ and makes calls to publishUpcomin...
carma_wm::WorldModelConstPtr wm_
carma_ros2_utils::SubPtr< geometry_msgs::msg::TwistStamped > twist_sub_
carma_ros2_utils::CallbackReturn handle_on_activate(const rclcpp_lifecycle::State &)
boost::optional< LaneChangeInformation > upcoming_lane_change_information_
carma_ros2_utils::PubPtr< carma_planning_msgs::msg::UpcomingLaneChangeStatus > upcoming_lane_change_status_pub_
carma_planning_msgs::msg::UpcomingLaneChangeStatus upcoming_lane_change_status_
bool isTrajectoryValid(const carma_planning_msgs::msg::TrajectoryPlan &trajectory_plan) const noexcept
Example if a trajectory plan contains at least two trajectory points.
carma_ros2_utils::CallbackReturn handle_on_configure(const rclcpp_lifecycle::State &)
std::shared_ptr< carma_planning_msgs::srv::PlanTrajectory::Request > composePlanTrajectoryRequest(const carma_planning_msgs::msg::TrajectoryPlan &latest_trajectory_plan, const uint16_t &current_maneuver_index) const
Generate new PlanTrajecory service request based on current planning progress.
bool isManeuverExpired(const carma_planning_msgs::msg::Maneuver &maneuver, rclcpp::Time current_time) const
Example if a maneuver end time has passed current system time.
void guidanceStateCallback(carma_planning_msgs::msg::GuidanceState::UniquePtr plan)
Callback function of guidance state subscriber.
void updateManeuverParameters(carma_planning_msgs::msg::Maneuver &maneuver)
Update the starting downtrack, ending downtrack, and maneuver-specific Lanelet ID parameters associat...
rclcpp::TimerBase::SharedPtr traj_timer_
carma_ros2_utils::SubPtr< carma_planning_msgs::msg::ManeuverPlan > plan_sub_
bool isTrajectoryLongEnough(const carma_planning_msgs::msg::TrajectoryPlan &plan) const noexcept
Example if a trajectory plan is longer than configured time thresheld.
carma_planning_msgs::msg::ManeuverPlan latest_maneuver_plan_
carma_ros2_utils::PubPtr< carma_planning_msgs::msg::TrajectoryPlan > traj_pub_
carma_ros2_utils::PubPtr< autoware_msgs::msg::LampCmd > turn_signal_command_pub_
std::unique_ptr< tf2_ros::TransformListener > tf2_listener_
boost::optional< LaneChangeInformation > current_lane_change_information_
std::unordered_map< std::string, carma_ros2_utils::ClientPtr< carma_planning_msgs::srv::PlanTrajectory > > trajectory_planners_
bool isManeuverPlanValid(const carma_planning_msgs::msg::ManeuverPlan &maneuver_plan) const noexcept
Example if a maneuver plan contains at least one maneuver.
carma_ros2_utils::SubPtr< geometry_msgs::msg::PoseStamped > pose_sub_
geometry_msgs::msg::TwistStamped latest_twist_
void lookupFrontBumperTransform()
Lookup transfrom from front bumper to base link.
geometry_msgs::msg::PoseStamped latest_pose_
PlanDelegator(const rclcpp::NodeOptions &)
PlanDelegator constructor.
void maneuverPlanCallback(carma_planning_msgs::msg::ManeuverPlan::UniquePtr plan)
Callback function of maneuver plan subscriber.
autoware_msgs::msg::LampCmd latest_turn_signal_command_
void publishTurnSignalCommand(const boost::optional< LaneChangeInformation > &current_lane_change_information, const carma_planning_msgs::msg::UpcomingLaneChangeStatus &upcoming_lane_change_status)
Function for processing an optional LaneChangeInformation object pertaining to the currently-occurrin...
void publishUpcomingLaneChangeStatus(const boost::optional< LaneChangeInformation > &upcoming_lane_change_information)
Function for processing an optional LaneChangeInformation object pertaining to an upcoming lane chang...
auto to_string(const UtmZone &zone) -> std::string
Definition: utm_zone.cpp:21
void rpyFromQuaternion(const tf2::Quaternion &q, double &roll, double &pitch, double &yaw)
Extract extrinsic roll-pitch-yaw from quaternion.
Definition: Geometry.cpp:52
std::string getManeuverEndingLaneletId(carma_planning_msgs::msg::Maneuver mvr)
Anonymous function to get the ending lanelet id for all maneuver types except lane following....
void setManeuverEndingLaneletId(carma_planning_msgs::msg::Maneuver &mvr, lanelet::Id end_id)
Anonymous function to set the ending_lane_id for all maneuver types except lane following....
std::string getManeuverStartingLaneletId(carma_planning_msgs::msg::Maneuver mvr)
Anonymous function to get the starting lanelet id for all maneuver types except lane following....
void setManeuverStartingLaneletId(carma_planning_msgs::msg::Maneuver &mvr, lanelet::Id start_id)
Anonymous function to set the starting_lane_id for all maneuver types except lane following....
#define SET_MANEUVER_PROPERTY(mvr, property, value)
std::string planning_topic_suffix
std::string planning_topic_prefix
double duration_to_signal_before_lane_change
Convenience struct for storing information regarding a lane change maneuver.