Carma-platform v4.11.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.
yield_plugin::YieldPlugin Class Reference

Class containing primary business logic for the In-Lane Cruising Plugin. More...

#include <yield_plugin.hpp>

Collaboration diagram for yield_plugin::YieldPlugin:
Collaboration graph

Public Member Functions

 YieldPlugin (std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode > nh, carma_wm::WorldModelConstPtr wm, YieldPluginConfig config, MobilityResponseCB mobility_response_publisher, LaneChangeStatusCB lc_status_publisher)
 Constructor. More...
 
void plan_trajectory_callback (carma_planning_msgs::srv::PlanTrajectory::Request::SharedPtr req, carma_planning_msgs::srv::PlanTrajectory::Response::SharedPtr resp)
 Service callback for trajectory planning. More...
 
carma_planning_msgs::msg::TrajectoryPlan update_traj_for_object (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double initial_velocity)
 trajectory is modified to safely avoid obstacles on the road More...
 
double polynomial_calc (std::vector< double > coeff, double x) const
 calculate quintic polynomial equation for a given x More...
 
double polynomial_calc_d (std::vector< double > coeff, double x) const
 calculate derivative of quintic polynomial equation for a given x More...
 
double max_trajectory_speed (const std::vector< carma_planning_msgs::msg::TrajectoryPlanPoint > &trajectory_points, double timestamp_in_sec_to_search_until) const
 calculates the maximum speed in a set of tajectory points More...
 
std::vector< double > get_relative_downtracks (const carma_planning_msgs::msg::TrajectoryPlan &trajectory_plan) const
 calculates distance between trajectory points in a plan More...
 
void mobilityrequest_cb (const carma_v2x_msgs::msg::MobilityRequest::UniquePtr msg)
 callback for mobility request More...
 
void bsm_cb (const carma_v2x_msgs::msg::BSM::UniquePtr msg)
 callback for bsm message More...
 
std::vector< lanelet::BasicPoint2d > convert_eceftrajectory_to_mappoints (const carma_v2x_msgs::msg::Trajectory &ecef_trajectory) const
 convert a carma trajectory from ecef frame to map frame ecef trajectory consists of the point and a set of offsets with reference to the point More...
 
lanelet::BasicPoint2d ecef_to_map_point (const carma_v2x_msgs::msg::LocationECEF &ecef_point) const
 convert a point in ecef frame (in cm) into map frame (in meters) More...
 
carma_v2x_msgs::msg::MobilityResponse compose_mobility_response (const std::string &resp_recipient_id, const std::string &req_plan_id, bool response) const
 compose a mobility response message More...
 
carma_planning_msgs::msg::TrajectoryPlan generate_JMT_trajectory (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, double initial_pos, double goal_pos, double initial_velocity, double goal_velocity, double planning_time, double original_max_speed)
 generate a Jerk Minimizing Trajectory(JMT) with the provided start and end conditions More...
 
carma_planning_msgs::msg::TrajectoryPlan update_traj_for_cooperative_behavior (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, double current_speed)
 update trajectory for yielding to an incoming cooperative behavior More...
 
std::vector< std::pair< int, lanelet::BasicPoint2d > > detect_trajectories_intersection (std::vector< lanelet::BasicPoint2d > self_trajectory, std::vector< lanelet::BasicPoint2d > incoming_trajectory) const
 detect intersection point(s) of two trajectories More...
 
void set_incoming_request_info (std::vector< lanelet::BasicPoint2d > req_trajectory, double req_speed, double req_planning_time, double req_timestamp)
 set values for member variables related to cooperative behavior More...
 
void lookup_ecef_to_map_transform ()
 Looks up the transform between map and earth frames, and sets the member variable. More...
 
double check_traj_for_digital_min_gap (const carma_planning_msgs::msg::TrajectoryPlan &original_tp) const
 checks trajectory for minimum gap associated with it from the road More...
 
void set_georeference_string (const std::string &georeference)
 Setter for map projection string to define lat/lon -> map conversion. More...
 
void set_external_objects (const std::vector< carma_perception_msgs::msg::ExternalObject > &object_list)
 Setter for external objects with predictions in the environment. More...
 
void update_route_llt_cache ()
 Rebuild the cached route lanelet polygons and IDs from the current route. Should be called once whenever the route changes via setRouteCallback(). More...
 
std::optional< GetCollisionResultget_collision (const carma_planning_msgs::msg::TrajectoryPlan &ego_trajectory, const std::vector< carma_perception_msgs::msg::PredictedState > &object_predictions, double collision_radius, double ego_max_speed)
 Return naive collision time and locations based on collision radius given two trajectories with one being obstacle's predicted steps. More...
 
std::optional< rclcpp::Time > get_collision_time (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const carma_perception_msgs::msg::ExternalObject &curr_obstacle, double original_tp_max_speed)
 Return collision time given two trajectories with one being external object with predicted steps. More...
 
bool is_object_behind_vehicle (uint32_t object_id, const rclcpp::Time &collision_time, double vehicle_point, double object_downtrack)
 Check if object location is behind the vehicle using estimates of the vehicle's length and route downtracks. More...
 
std::optional< std::pair< carma_perception_msgs::msg::ExternalObject, double > > get_earliest_collision_object_and_time (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects)
 Return the earliest collision object and time of collision pair from the given trajectory and list of external objects with predicted states. Function first filters obstacles based on whether if their any of predicted state will be on the route. Only then, the logic compares trajectory and predicted states. More...
 
std::unordered_map< uint32_t, rclcpp::Time > get_collision_times_concurrently (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double original_tp_max_speed)
 Given the list of objects with predicted states, get all collision times concurrently using multi-threading. More...
 
double get_predicted_velocity_at_time (const geometry_msgs::msg::Twist &object_velocity_in_map_frame, const carma_planning_msgs::msg::TrajectoryPlan &original_tp, double timestamp_in_sec_to_predict)
 Given the object velocity in map frame with x,y components, this function returns the projected velocity along the trajectory at given time. More...
 

Private Member Functions

std::unordered_map< uint32_t, rclcpp::Time > get_collision_times_concurrently_cpu (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double original_tp_max_speed)
 CPU implementation of get_collision_times_concurrently. Launches one thread per external object and computes get_collision_time for each in parallel. More...
 
std::unordered_map< uint32_t, rclcpp::Time > get_collision_times_concurrently_cuda (const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double original_tp_max_speed)
 CUDA implementation of get_collision_times_concurrently. Filters objects to those with on-route predictions, then batches the collision check across all objects on the GPU. Falls back to get_collision_times_concurrently_cpu if no CUDA device is available or on error. More...
 
std::pair< bool, int > find_on_route_in_predictions (const std::vector< carma_perception_msgs::msg::PredictedState > &predictions, int stride, bool zero_speed) const
 Check whether any predicted state of an object falls within the route lanelet polygons. More...
 
std::string bsmIDtoString (carma_v2x_msgs::msg::BSMCoreData bsm_core)
 

Private Attributes

carma_wm::WorldModelConstPtr wm_
 
YieldPluginConfig config_
 
MobilityResponseCB mobility_response_publisher_
 
LaneChangeStatusCB lc_status_publisher_
 
std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode > nh_
 
std::set< lanelet::Id > route_llt_ids_
 
std::vector< lanelet::ConstLanelet > route_llt_polygons_
 
lanelet::Id previous_llt_id_
 
std::vector< carma_perception_msgs::msg::ExternalObject > external_objects_
 
std::unordered_map< uint32_t, int > consecutive_clearance_count_for_obstacles_
 
bool cooperative_request_acceptable_ = false
 
std::vector< lanelet::BasicPoint2d > req_trajectory_points_
 
double req_target_speed_ = 0
 
double req_timestamp_ = 0
 
double req_target_plan_time_ = 0
 
int timesteps_since_last_req_ = 0
 
int clc_urgency_ = 0
 
std::optional< double > last_speed_ = std::nullopt
 
std::optional< rclcpp::Time > last_speed_time_ = std::nullopt
 
std::optional< rclcpp::Time > first_time_stopped_to_prevent_collision_ = std::nullopt
 
std::optional< carma_planning_msgs::msg::TrajectoryPlan > last_traj_plan_committed_to_stopping_ = std::nullopt
 
double ecef_traj_timestep_ = 0.1
 
geometry_msgs::msg::Vector3 host_vehicle_size
 
double current_speed_
 
std::string host_bsm_id_
 
std::string georeference_ {""}
 
std::shared_ptr< lanelet::projection::LocalFrameProjector > map_projector_
 

Detailed Description

Class containing primary business logic for the In-Lane Cruising Plugin.

Definition at line 77 of file yield_plugin.hpp.

Constructor & Destructor Documentation

◆ YieldPlugin()

yield_plugin::YieldPlugin::YieldPlugin ( std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode >  nh,
carma_wm::WorldModelConstPtr  wm,
YieldPluginConfig  config,
MobilityResponseCB  mobility_response_publisher,
LaneChangeStatusCB  lc_status_publisher 
)

Constructor.

Parameters
wmPointer to intialized instance of the carma world model for accessing semantic map data
configThe configuration to be used for this object
mobility_response_publisherCallback which will publish the mobility response
lc_status_publisherCallback which will publish the cooperative lane change status

Definition at line 49 of file yield_plugin.cpp.

52 : nh_(nh), wm_(wm), config_(config),mobility_response_publisher_(mobility_response_publisher), lc_status_publisher_(lc_status_publisher)
53 {
54
55 }
LaneChangeStatusCB lc_status_publisher_
MobilityResponseCB mobility_response_publisher_
std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode > nh_
YieldPluginConfig config_
carma_wm::WorldModelConstPtr wm_

Member Function Documentation

◆ bsm_cb()

void yield_plugin::YieldPlugin::bsm_cb ( const carma_v2x_msgs::msg::BSM::UniquePtr  msg)

callback for bsm message

Parameters
msgmobility bsm message

Definition at line 250 of file yield_plugin.cpp.

251 {
252 carma_v2x_msgs::msg::BSMCoreData bsm_core_ = msg->core_data;
253 host_bsm_id_ = bsmIDtoString(bsm_core_);
254 }
std::string bsmIDtoString(carma_v2x_msgs::msg::BSMCoreData bsm_core)

References bsmIDtoString(), and host_bsm_id_.

Referenced by yield_plugin::YieldPluginNode::on_configure_plugin().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ bsmIDtoString()

std::string yield_plugin::YieldPlugin::bsmIDtoString ( carma_v2x_msgs::msg::BSMCoreData  bsm_core)
inlineprivate

Definition at line 394 of file yield_plugin.hpp.

395 {
396 std::string res = "";
397 for (size_t i=0; i<bsm_core.id.size(); i++)
398 {
399 res+=std::to_string(bsm_core.id[i]);
400 }
401 return res;
402 }
auto to_string(const UtmZone &zone) -> std::string
Definition: utm_zone.cpp:21

References process_bag::i, and carma_cooperative_perception::to_string().

Referenced by bsm_cb().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_traj_for_digital_min_gap()

double yield_plugin::YieldPlugin::check_traj_for_digital_min_gap ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp) const

checks trajectory for minimum gap associated with it from the road

Parameters
original_tporiginal trajectory plan
Returns
minumum required min_gap from the road, if none exists, return default minimum_safety_gap_in_meters

Definition at line 1441 of file yield_plugin.cpp.

1442 {
1443 double desired_gap = 0;
1444 if (!wm_->getRoute()) return desired_gap;
1445
1446 const lanelet::BasicPoint2d traj_start(original_tp.trajectory_points.front().x,
1447 original_tp.trajectory_points.front().y);
1448 const lanelet::BasicPoint2d traj_end(original_tp.trajectory_points.back().x,
1449 original_tp.trajectory_points.back().y);
1450
1451 // Fetch up to 4 candidates per endpoint — a boundary point can lie on
1452 // overlapping lanelets and a single result may be the wrong one.
1453 auto start_llts = wm_->getLaneletsFromPoint(traj_start, 4);
1454 auto end_llts = wm_->getLaneletsFromPoint(traj_end, 4);
1455
1456 if (start_llts.empty() || end_llts.empty())
1457 {
1458 // Trajectory generation may place a point off-road in rare edge cases
1459 // (see https://github.com/usdot-fhwa-stol/carma-platform/issues/2503)
1460 RCLCPP_WARN_STREAM(nh_->get_logger(), "check_traj_for_digital_min_gap: trajectory endpoint "
1461 "not on a lanelet, skipping digital gap check.");
1462 return desired_gap;
1463 }
1464
1465 std::unordered_set<lanelet::Id> start_ids;
1466 std::unordered_set<lanelet::Id> end_ids;
1467 for (const auto& llt : start_llts) start_ids.insert(llt.id());
1468 for (const auto& llt : end_llts) end_ids.insert(llt.id());
1469
1470 // One pass: first index matching any start candidate, last index matching any end candidate.
1471 // Taking "last" for end covers all overlapping lanelets at the trajectory's back boundary.
1472 std::optional<size_t> start_pos;
1473 std::optional<size_t> end_pos;
1474 size_t pos = 0;
1475 const auto& path = wm_->getRoute()->shortestPath();
1476 for (const auto& llt : path)
1477 {
1478 if (!start_pos.has_value() && start_ids.count(llt.id())) start_pos = pos;
1479 if (end_ids.count(llt.id())) end_pos = pos;
1480 ++pos;
1481 }
1482
1483 if (!start_pos || !end_pos || *start_pos > *end_pos)
1484 {
1485 RCLCPP_WARN_STREAM(nh_->get_logger(), "check_traj_for_digital_min_gap: trajectory endpoints "
1486 "not found on route shortest path, skipping digital gap check.");
1487 return desired_gap;
1488 }
1489
1490 pos = 0;
1491 for (const auto& llt : path)
1492 {
1493 if (pos > *end_pos) break;
1494 if (pos >= *start_pos)
1495 {
1496 auto digital_min_gap = llt.regulatoryElementsAs<lanelet::DigitalMinimumGap>();
1497 if (!digital_min_gap.empty())
1498 {
1499 double digital_gap = digital_min_gap[0]->getMinimumGap();
1500 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "Digital Gap found with value: " << digital_gap);
1501 desired_gap = std::max(desired_gap, digital_gap);
1502 }
1503 }
1504 ++pos;
1505 }
1506 return desired_gap;
1507 }

References nh_, and wm_.

Referenced by update_traj_for_cooperative_behavior(), and update_traj_for_object().

Here is the caller graph for this function:

◆ compose_mobility_response()

carma_v2x_msgs::msg::MobilityResponse yield_plugin::YieldPlugin::compose_mobility_response ( const std::string &  resp_recipient_id,
const std::string &  req_plan_id,
bool  response 
) const

compose a mobility response message

Parameters
resp_recipient_idvehicle id of the recipient of the message
req_plan_idplan id from the requested message
responseaccept/reject to the response based on conditions
Returns
filled mobility response

Definition at line 137 of file yield_plugin.cpp.

138 {
139 carma_v2x_msgs::msg::MobilityResponse out_mobility_response;
140 out_mobility_response.m_header.sender_id = config_.vehicle_id;
141 out_mobility_response.m_header.recipient_id = resp_recipient_id;
142 out_mobility_response.m_header.sender_bsm_id = host_bsm_id_;
143 out_mobility_response.m_header.plan_id = req_plan_id;
144 out_mobility_response.m_header.timestamp = nh_->now().seconds()*1000;
145
146
148 {
149 out_mobility_response.is_accepted = true;
150 }
151 else out_mobility_response.is_accepted = false;
152
153 return out_mobility_response;
154 }
bool always_accept_mobility_request
std::string vehicle_id

References YieldPluginConfig::always_accept_mobility_request, config_, host_bsm_id_, nh_, and YieldPluginConfig::vehicle_id.

Referenced by mobilityrequest_cb().

Here is the caller graph for this function:

◆ convert_eceftrajectory_to_mappoints()

std::vector< lanelet::BasicPoint2d > yield_plugin::YieldPlugin::convert_eceftrajectory_to_mappoints ( const carma_v2x_msgs::msg::Trajectory &  ecef_trajectory) const

convert a carma trajectory from ecef frame to map frame ecef trajectory consists of the point and a set of offsets with reference to the point

Parameters
ecef_trajectorycarma trajectory (ecef frame)
Returns
vector of 2d points in map frame

Definition at line 98 of file yield_plugin.cpp.

99 {
100 carma_planning_msgs::msg::TrajectoryPlan trajectory_plan;
101 std::vector<lanelet::BasicPoint2d> map_points;
102
103 lanelet::BasicPoint2d first_point = ecef_to_map_point(ecef_trajectory.location);
104
105 map_points.push_back(first_point);
106 auto curr_point = ecef_trajectory.location;
107
108 for (size_t i = 0; i<ecef_trajectory.offsets.size(); i++)
109 {
110 lanelet::BasicPoint2d offset_point;
111 curr_point.ecef_x += ecef_trajectory.offsets.at(i).offset_x;
112 curr_point.ecef_y += ecef_trajectory.offsets.at(i).offset_y;
113 curr_point.ecef_z += ecef_trajectory.offsets.at(i).offset_z;
114
115 offset_point = ecef_to_map_point(curr_point);
116
117 map_points.push_back(offset_point);
118 }
119
120 return map_points;
121 }
lanelet::BasicPoint2d ecef_to_map_point(const carma_v2x_msgs::msg::LocationECEF &ecef_point) const
convert a point in ecef frame (in cm) into map frame (in meters)
list first_point
Definition: process_bag.py:52

References ecef_to_map_point(), process_bag::first_point, and process_bag::i.

Referenced by mobilityrequest_cb().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ detect_trajectories_intersection()

std::vector< std::pair< int, lanelet::BasicPoint2d > > yield_plugin::YieldPlugin::detect_trajectories_intersection ( std::vector< lanelet::BasicPoint2d >  self_trajectory,
std::vector< lanelet::BasicPoint2d >  incoming_trajectory 
) const

detect intersection point(s) of two trajectories

Parameters
self_trajectoryvector of 2d trajectory points
incoming_trajectoryvector of 2d trajectory points
Returns
vector of pairs of 2d intersection points and index of the point in trajectory array

Definition at line 77 of file yield_plugin.cpp.

78 {
79 std::vector<std::pair<int, lanelet::BasicPoint2d>> intersection_points;
80 boost::geometry::model::linestring<lanelet::BasicPoint2d> self_traj;
81 for (auto tpp:self_trajectory)
82 {
83 boost::geometry::append(self_traj, tpp);
84 }
85 // distance to consider trajectories colliding (chosen based on lane width and vehicle size)
86 for (size_t i=0; i<incoming_trajectory.size(); i++)
87 {
88 double res = boost::geometry::distance(incoming_trajectory.at(i), self_traj);
89
91 {
92 intersection_points.push_back(std::make_pair(i, incoming_trajectory.at(i)));
93 }
94 }
95 return intersection_points;
96 }
double intervehicle_collision_distance_in_m

References config_, process_bag::i, and YieldPluginConfig::intervehicle_collision_distance_in_m.

Referenced by update_traj_for_cooperative_behavior().

Here is the caller graph for this function:

◆ ecef_to_map_point()

lanelet::BasicPoint2d yield_plugin::YieldPlugin::ecef_to_map_point ( const carma_v2x_msgs::msg::LocationECEF &  ecef_point) const

convert a point in ecef frame (in cm) into map frame (in meters)

Parameters
ecef_pointcarma point ecef frame in cm
map_in_earthtranslate frame
Returns
2d point in map frame

Definition at line 123 of file yield_plugin.cpp.

124 {
125
126 if (!map_projector_) {
127 throw std::invalid_argument("No map projector available for ecef conversion");
128 }
129
130 lanelet::BasicPoint3d map_point = map_projector_->projectECEF( { static_cast<double>(ecef_point.ecef_x)/100.0, static_cast<double>(ecef_point.ecef_y)/100.0, static_cast<double>(ecef_point.ecef_z)/100.0 } , 1);
131
132 return lanelet::traits::to2D(map_point);
133 }
std::shared_ptr< lanelet::projection::LocalFrameProjector > map_projector_

References map_projector_.

Referenced by convert_eceftrajectory_to_mappoints().

Here is the caller graph for this function:

◆ find_on_route_in_predictions()

std::pair< bool, int > yield_plugin::YieldPlugin::find_on_route_in_predictions ( const std::vector< carma_perception_msgs::msg::PredictedState > &  predictions,
int  stride,
bool  zero_speed 
) const
private

Check whether any predicted state of an object falls within the route lanelet polygons.

Parameters
predictionslist of predicted states for an object
stridestep size used to iterate over predictions
zero_speedif true, only the first prediction is checked (object is stationary)
Returns
pair of (true, index of first on-route prediction) if found, otherwise (false, 0)

Definition at line 910 of file yield_plugin.cpp.

913 {
914 for (size_t j = 0; j < predictions.size(); j += stride) {
915 const lanelet::BasicPoint2d point(predictions[j].predicted_position.position.x,
916 predictions[j].predicted_position.position.y);
917 for (const auto& llt : route_llt_polygons_) {
918 if (boost::geometry::within(point, llt.polygon2d())) {
919 return {true, static_cast<int>(j)};
920 }
921 }
922 if (object_has_zero_speed) break;
923 }
924 return {false, 0};
925 }
std::vector< lanelet::ConstLanelet > route_llt_polygons_

References process_traj_logs::point, and route_llt_polygons_.

Referenced by get_collision_times_concurrently_cuda().

Here is the caller graph for this function:

◆ generate_JMT_trajectory()

carma_planning_msgs::msg::TrajectoryPlan yield_plugin::YieldPlugin::generate_JMT_trajectory ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
double  initial_pos,
double  goal_pos,
double  initial_velocity,
double  goal_velocity,
double  planning_time,
double  original_max_speed 
)

generate a Jerk Minimizing Trajectory(JMT) with the provided start and end conditions

Parameters
original_tporiginal trajectory plan
intial_posstart position
goal_posfinal position
initial_velocitystart velocity
goal_velocityend velocity
planning_timetime duration of the planning
originaloriginal_max_speed from original_tp up until the goal_pos portion NOTE: the function would generate trajectory duration arbitrarily high if stopping motion is needed. This is to keep the original trajectory's shape in terms of location so that the vehicle steers toward the direction of travel even when stopping.
Returns
updated JMT trajectory

Definition at line 469 of file yield_plugin.cpp.

471 {
472 carma_planning_msgs::msg::TrajectoryPlan jmt_trajectory;
473 std::vector<carma_planning_msgs::msg::TrajectoryPlanPoint> jmt_trajectory_points;
474 jmt_trajectory_points.push_back(original_tp.trajectory_points[0]);
475
476 std::vector<double> original_traj_relative_downtracks = get_relative_downtracks(original_tp);
477 std::vector<double> calculated_speeds = {};
478 std::vector<double> new_relative_downtracks = {};
479 new_relative_downtracks.push_back(0.0);
480 calculated_speeds.push_back(initial_velocity);
481 double new_traj_accumulated_downtrack = 0.0;
482 double original_traj_accumulated_downtrack = original_traj_relative_downtracks.at(1);
483
484 // Up until goal_pos (which also can be until end of the entire original trajectory), generate new speeds at
485 // or near original trajectory points by generating them at a fixed time interval using the JMT polynomial equation
486 const double initial_time = 0;
487 double initial_accel = 0;
489 {
490 initial_accel = (initial_velocity - last_speed_.value()) /
491 (nh_->now() - last_speed_time_.value()).seconds();
492
493 if (!std::isnormal(initial_accel))
494 {
495 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),"Detecting nan initial_accel set to 0");
496 initial_accel = 0.0;
497 }
498
499 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),"Detecting initial_accel: " << initial_accel
500 << ", initial_velocity:" << initial_velocity
501 << ", last_speed_: " << last_speed_.value()
502 << ", nh_->now(): " << nh_->now().seconds()
503 << ", last_speed_time_.get(): " << last_speed_time_.value().seconds());
504 }
505
506 const double goal_accel = 0;
507 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),"Following parameters used for JMT: "
508 "\ninitial_pos: " << initial_pos <<
509 "\ngoal_pos: " << goal_pos <<
510 "\ninitial_velocity: " << initial_velocity <<
511 "\ngoal_velocity: " << goal_velocity <<
512 "\ninitial_accel: " << initial_accel <<
513 "\ngoal_accel: " << goal_accel <<
514 "\nplanning_time: " << planning_time <<
515 "\noriginal_max_speed: " << original_max_speed);
516
517 // Get the polynomial solutions used to generate the trajectory
518 std::vector<double> polynomial_coefficients = quintic_coefficient_calculator::quintic_coefficient_calculator(initial_pos,
519 goal_pos,
520 initial_velocity,
521 goal_velocity,
522 initial_accel,
523 goal_accel,
524 initial_time,
525 planning_time);
526 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),"Used original_max_speed: " << original_max_speed);
527 for (size_t i = 0; i < polynomial_coefficients.size(); i++) {
528 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),"Coefficient " << i << ": " << polynomial_coefficients[i]);
529 }
530 // Cap at 0.1 s: finer steps gave no accuracy benefit but caused O(1/dt) loop blow-up
531 // when upstream planners produced sub-10 ms trajectory timesteps.
532 const double smallest_time_step = std::max(get_smallest_time_step_of_traj(original_tp), 0.1);
533 int new_traj_idx = 1;
534 int original_traj_idx = 1;
535 while (new_traj_accumulated_downtrack < goal_pos - EPSILON && original_traj_idx < original_traj_relative_downtracks.size())
536 {
537 const double target_time = new_traj_idx * smallest_time_step;
538 const double downtrack_at_target_time = polynomial_calc(polynomial_coefficients, target_time);
539 double velocity_at_target_time = polynomial_calc_d(polynomial_coefficients, target_time);
540
541 // if the speed becomes negative, the downtrack starts reversing to negative as well
542 // which will never reach the goal_pos, so break here.
543 if (velocity_at_target_time < 0.0)
544 {
545 break;
546 }
547
548 // Cannot have a negative speed or have a higher speed than that of the original trajectory
549 velocity_at_target_time = std::clamp(velocity_at_target_time, 0.0, original_max_speed);
550
551 // Pick the speed if it matches with the original downtracks
552 if (downtrack_at_target_time >= original_traj_accumulated_downtrack)
553 {
554 // velocity_at_target_time doesn't exactly correspond to original_traj_accumulated_downtrack but does for new_traj_accumulated_downtrack.
555 // however, the logic is assuming they are close enough that the speed is usable
556 calculated_speeds.push_back(velocity_at_target_time);
557 original_traj_accumulated_downtrack += original_traj_relative_downtracks.at(original_traj_idx);
558 original_traj_idx ++;
559 }
560 new_traj_accumulated_downtrack = downtrack_at_target_time;
561 new_traj_idx++;
562
563 }
564
565 // if the loop above finished prematurely due to negative speed, fill with 0.0 speeds
566 // since the speed crossed 0.0 and algorithm indicates stopping
567 std::fill_n(std::back_inserter(calculated_speeds),
568 std::size(original_traj_relative_downtracks) - std::size(calculated_speeds),
569 0.0);
570
571 // Moving average filter to smoothen the speeds
572 std::vector<double> filtered_speeds = basic_autonomy::smoothing::moving_average_filter(calculated_speeds, config_.speed_moving_average_window_size);
573 // Replace the original trajectory's associated timestamps based on the newly calculated speeds
574 double prev_speed = filtered_speeds.at(0);
575 last_speed_ = prev_speed;
576 last_speed_time_ = nh_->now();
577
578 for(size_t i = 1; i < original_tp.trajectory_points.size(); i++)
579 {
580 carma_planning_msgs::msg::TrajectoryPlanPoint jmt_tpp = original_tp.trajectory_points.at(i);
581
582 // In case only subset of original trajectory needs modification,
583 // the rest of the points should keep the last speed to cruise
584 double current_speed = goal_velocity;
585
586 if (i < filtered_speeds.size())
587 {
588 current_speed = filtered_speeds.at(i);
589 }
590
591 //Force the speed to 0 if below configured value for more control over stopping behavior
592 if (current_speed < config_.max_stop_speed_in_ms)
593 {
594 current_speed = 0;
595 }
596
597 // Derived from constant accelaration kinematic equation: (vi + vf) / 2 * dt = d_dist
598 // This also handles a case correctly when current_speed is 0, but prev_speed is not 0 yet
599 const double dt = (2 * original_traj_relative_downtracks.at(i)) / (current_speed + prev_speed);
600 jmt_tpp.target_time = rclcpp::Time(jmt_trajectory_points.back().target_time) + rclcpp::Duration::from_nanoseconds(dt*1e9);
601
602 if (prev_speed < EPSILON) // Handle a special case if prev_speed (thus current_speed too) is 0
603 {
604 // NOTE: Assigning arbitrary 100 mins dt between points where normally dt is only 1 sec to model a stopping behavior.
605 // Another way to model it is to keep the trajectory point at a same location and increment time slightly. However,
606 // if the vehicle goes past the point, it may cruise toward undesirable location (for example into the intersection).
607 // Keeping the points help the controller steer the vehicle toward direction of travel even when stopping.
608 // Only downside is the trajectory plan is huge where only 15 sec is expected, but since this is stopping case, it shouldn't matter.
609 jmt_tpp.target_time = rclcpp::Time(jmt_trajectory_points.back().target_time) + rclcpp::Duration::from_nanoseconds(6000 * 1e9);
610 }
611
612
613 jmt_trajectory_points.push_back(jmt_tpp);
614 prev_speed = current_speed;
615 }
616
617 jmt_trajectory.header = original_tp.header;
618 jmt_trajectory.trajectory_id = original_tp.trajectory_id;
619 jmt_trajectory.trajectory_points = jmt_trajectory_points;
620 jmt_trajectory.initial_longitudinal_velocity = initial_velocity;
621 return jmt_trajectory;
622 }
std::vector< double > get_relative_downtracks(const carma_planning_msgs::msg::TrajectoryPlan &trajectory_plan) const
calculates distance between trajectory points in a plan
std::optional< rclcpp::Time > last_speed_time_
double polynomial_calc(std::vector< double > coeff, double x) const
calculate quintic polynomial equation for a given x
std::optional< double > last_speed_
double polynomial_calc_d(std::vector< double > coeff, double x) const
calculate derivative of quintic polynomial equation for a given x
std::vector< double > moving_average_filter(const std::vector< double > input, int window_size, bool ignore_first_point=true)
Extremely simplie moving average filter.
Definition: filters.cpp:24
double get_smallest_time_step_of_traj(const carma_planning_msgs::msg::TrajectoryPlan &original_tp)
double max_stop_speed_in_ms
double speed_moving_average_window_size
constexpr auto EPSILON

References config_, EPSILON, get_relative_downtracks(), yield_plugin::get_smallest_time_step_of_traj(), process_bag::i, last_speed_, last_speed_time_, YieldPluginConfig::max_stop_speed_in_ms, basic_autonomy::smoothing::moving_average_filter(), nh_, polynomial_calc(), polynomial_calc_d(), and YieldPluginConfig::speed_moving_average_window_size.

Referenced by update_traj_for_cooperative_behavior(), and update_traj_for_object().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_collision()

std::optional< GetCollisionResult > yield_plugin::YieldPlugin::get_collision ( const carma_planning_msgs::msg::TrajectoryPlan &  ego_trajectory,
const std::vector< carma_perception_msgs::msg::PredictedState > &  object_predictions,
double  collision_radius,
double  ego_max_speed 
)

Return naive collision time and locations based on collision radius given two trajectories with one being obstacle's predicted steps.

Parameters
ego_trajectorytrajectory of the ego vehicle
object_predictionstrajectory of predicted steps
collision_radiusa distance to check between two trajectory points at a same timestamp that is considered a collision
ego_max_speedmax speed of the ego_trajectory to efficiently traverse through possible collision combination of the two trajectories NOTE: Currently object_predictions is assumed to be a simple cv model to save computational performance NOTE: Collisions are based on only collision radius at the same predicted time even if ego vehicle maybe past the obstacle. To filter these cases, see is_object_behind_vehicle()
Returns
data of time of collision if detected, otherwise, std::nullopt

Definition at line 624 of file yield_plugin.cpp.

626 {
627
628 // Iterate through each pair of consecutive points in the trajectories
629 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "Starting a new collision detection, trajectory size: "
630 << ego_trajectory.trajectory_points.size() << ". prediction size: " << object_predictions.size());
631
632 // Iterate through the object to check if it's on the route
633 bool on_route = false;
634 int on_route_idx = 0;
635
636 // A flag to stop searching more than one lanelet if the object has no velocity
637 const auto object_speed{std::hypot(object_predictions.front().predicted_velocity.linear.x,
638 object_predictions.front().predicted_velocity.linear.y)};
639 bool object_has_zero_speed = object_speed < config_.obstacle_zero_speed_threshold_in_ms;
640
641 if (object_predictions.size() < 2)
642 {
643 throw std::invalid_argument("Object on ther road doesn't have enough predicted states! Please check motion_computation is correctly applying predicted states");
644 }
645 const double object_prediction_step_duration = (rclcpp::Time(object_predictions.at(1).header.stamp) - rclcpp::Time(object_predictions.front().header.stamp)).seconds();
646 const double object_prediction_total_duration = get_trajectory_duration(object_predictions);
647
648 if (object_prediction_step_duration < 0.0)
649 {
650 throw std::invalid_argument("Predicted states of the object is malformed. Detected trajectory going backwards in time!");
651 }
652
653 // In order to optimize the for loops for comparing two trajectories, following logic skips every iteration_stride-th points of the object_predictions.
654 // Since skipping number of points from the object_predictions may result in ignoring potential collisions, its value is dependent on two
655 // trajectories' speeds and intervehicle_collision_distance_in_m radius.
656 // Therefore, the derivation first calculates the max time, t, that both actors can move while still being in collision radius:
657 // sqrt( (v1 * t / 2)^2 + (v2 * t / 2)^2 ) = collision_radius. Here v1 and v2 are assumed to be perpendicular to each other and
658 // intersecting at t/2 to get max possible collision_radius. Solving for t gives following:
659 double iteration_stride_max_time_s = 2 * config_.intervehicle_collision_distance_in_m / sqrt(pow(object_speed, 2) + pow(ego_max_speed, 2));
660 int iteration_stride = std::max(1, static_cast<int>(iteration_stride_max_time_s / object_prediction_step_duration));
661
662 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "Determined iteration_stride: " << iteration_stride
663 << ", with object_speed: " << object_speed
664 << ", with ego_max_speed: " << ego_max_speed
665 << ", with object_prediction_step_duration: " << object_prediction_step_duration
666 << ", iteration_stride_max_time_s: " << iteration_stride_max_time_s);
667
668 for (size_t j = 0; j < object_predictions.size(); j += iteration_stride)
669 {
670 const lanelet::BasicPoint2d point(object_predictions.at(j).predicted_position.position.x,
671 object_predictions.at(j).predicted_position.position.y);
672 for (const auto& llt : route_llt_polygons_)
673 {
674 if (boost::geometry::within(point, llt.polygon2d()))
675 {
676 on_route = true;
677 on_route_idx = j;
678 break;
679 }
680 }
681 if (on_route || object_has_zero_speed)
682 break;
683 }
684
685 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"), "[CPU] on_route=" << on_route
686 << " on_route_idx=" << on_route_idx
687 << " speed=" << object_speed
688 << " stride=" << iteration_stride);
689
690 if (!on_route)
691 {
692 RCLCPP_DEBUG(rclcpp::get_logger("yield_plugin"), "[CPU] Object not on route — skipping");
693 return std::nullopt;
694 }
695
696 double smallest_dist = std::numeric_limits<double>::infinity();
697 for (size_t i = 0; i < ego_trajectory.trajectory_points.size() - 1; ++i)
698 {
699 auto ego_seg_start = ego_trajectory.trajectory_points.at(i);
700 auto ego_seg_end = ego_trajectory.trajectory_points.at(i + 1);
701 double previous_distance = std::numeric_limits<double>::infinity();
702 for (size_t j = on_route_idx; j < object_predictions.size() - 1; j += iteration_stride)
703 {
704 auto object_seg_start = object_predictions.at(j);
705 auto object_seg_end = object_predictions.at(j + 1);
706 double ego_seg_start_time = rclcpp::Time(ego_seg_start.target_time).seconds();
707 double ego_seg_end_time = rclcpp::Time(ego_seg_end.target_time).seconds();
708 double object_seg_start_time = rclcpp::Time(object_seg_start.header.stamp).seconds();
709 double object_seg_end_time = rclcpp::Time(object_seg_end.header.stamp).seconds();
710
711 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "ego_seg_start.target_time: " << std::to_string(ego_seg_start_time) << ", ego_seg_end.target_time: " << std::to_string(ego_seg_end_time));
712 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "object_seg_start.target_time: " << std::to_string(object_seg_start_time) << ", object_seg_end.target_time: " << std::to_string(object_seg_end_time));
713 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "ego_seg_start.x: " << ego_seg_start.x << ", ego_seg_start.y: " << ego_seg_start.y);
714 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "ego_seg_end.x: " << ego_seg_end.x << ", ego_seg_end.y: " << ego_seg_end.y);
715
716 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "object_seg_start.x: " << object_seg_start.predicted_position.position.x << ", object_seg_start.y: " << object_seg_start.predicted_position.position.y);
717 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "object_seg_end.x: " << object_seg_end.predicted_position.position.x << ", object_seg_end.y: " << object_seg_end.predicted_position.position.y);
718
719 // Linearly interpolate positions at a common timestamp for both trajectories
720 double interp_ratio = (object_seg_start_time - ego_seg_start_time) / (ego_seg_end_time - ego_seg_start_time);
721 // if negative extrapolation, skip because car wouldn't go backwards
722 if (interp_ratio < 0)
723 {
724 RCLCPP_DEBUG_STREAM(nh_->get_logger(),
725 "Negative extrapolation, skipping this pair of points. object_seg_start_time: "
726 << std::to_string(object_seg_start_time) << ", ego_seg_start_time: "
727 << std::to_string(ego_seg_start_time));
728 continue;
729 }
730 double ego_interp_x = ego_seg_start.x + interp_ratio * (ego_seg_end.x - ego_seg_start.x);
731 double ego_interp_y = ego_seg_start.y + interp_ratio * (ego_seg_end.y - ego_seg_start.y);
732 double object_x = object_seg_start.predicted_position.position.x;
733 double object_y = object_seg_start.predicted_position.position.y;
734
735 // Calculate the distance between the two interpolated points
736 const auto distance{std::hypot(ego_interp_x - object_x, ego_interp_y - object_y)};
737
738 smallest_dist = std::min(distance, smallest_dist);
739
740 // Following "if logic" assumes the object_predictions is a simple cv model, aka, object_predictions point is a straight line over time.
741 // And current ego_trajectory point is fixed in this iteration.
742 // Then once the distance between the two start to increase over object_predictions iteration,
743 // the distance will always increase and it's unnecessary to continue the logic to find the smallest_dist
744 if (previous_distance < distance)
745 {
746 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "Stopping search here because the distance between predictions started to increase");
747 break;
748 }
749 previous_distance = distance;
750
751 if (i == 0 && j == 0 && distance > config_.collision_check_radius_in_m)
752 {
753 RCLCPP_DEBUG(nh_->get_logger(), "Too far away" );
754 return std::nullopt;
755 }
756
757 if (distance > collision_radius)
758 {
759 // continue searching for collision
760 continue;
761 }
762
763 GetCollisionResult collision_result;
764 collision_result.ego_point = lanelet::BasicPoint2d(ego_interp_x, ego_interp_y);
765 collision_result.object_point = lanelet::BasicPoint2d(object_x, object_y);
766 collision_result.collision_time = rclcpp::Time(object_seg_start.header.stamp);
767 return collision_result;
768 }
769 }
770 RCLCPP_DEBUG_STREAM(
771 rclcpp::get_logger("yield_plugin"),
772 "Was not able to find collision: smallest_dist: " << smallest_dist);
773
774 // No collision detected
775 return std::nullopt;
776 }
double get_trajectory_duration(const carma_planning_msgs::msg::TrajectoryPlan &trajectory)
double collision_check_radius_in_m
double obstacle_zero_speed_threshold_in_ms

References YieldPluginConfig::collision_check_radius_in_m, yield_plugin::GetCollisionResult::collision_time, config_, yield_plugin::GetCollisionResult::ego_point, yield_plugin::get_trajectory_duration(), process_bag::i, YieldPluginConfig::intervehicle_collision_distance_in_m, nh_, yield_plugin::GetCollisionResult::object_point, YieldPluginConfig::obstacle_zero_speed_threshold_in_ms, process_traj_logs::point, route_llt_polygons_, and carma_cooperative_perception::to_string().

Referenced by get_collision_time().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_collision_time()

std::optional< rclcpp::Time > yield_plugin::YieldPlugin::get_collision_time ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
const carma_perception_msgs::msg::ExternalObject &  curr_obstacle,
double  original_tp_max_speed 
)

Return collision time given two trajectories with one being external object with predicted steps.

Parameters
original_tptrajectory of the ego vehicle
curr_obstacletrajectory of the obstacle
original_tp_max_speedmax speed of the original_tp to efficiently traverse through possible collision combination of the two trajectories NOTE: Currently curr_obstacle is assumed to be using a simple cv model to save computational performance
Returns
time_of_collision if collision detected, otherwise, std::nullopt

Definition at line 805 of file yield_plugin.cpp.

807 {
808 auto plan_start_time = get_trajectory_start_time(original_tp);
809
810 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "Object's back time: " << std::to_string(rclcpp::Time(curr_obstacle.predictions.back().header.stamp).seconds())
811 << ", plan_start_time: " << std::to_string(plan_start_time));
812
813 // do not process outdated objects
814 if (rclcpp::Time(curr_obstacle.predictions.back().header.stamp).seconds() <= plan_start_time)
815 {
816 return std::nullopt;
817 }
818
819 std::vector<carma_perception_msgs::msg::PredictedState> new_list;
820 carma_perception_msgs::msg::PredictedState curr_state;
821 // artificially include current position as one of the predicted states
822 curr_state.header.stamp = curr_obstacle.header.stamp;
823 curr_state.predicted_position.position.x = curr_obstacle.pose.pose.position.x;
824 curr_state.predicted_position.position.y = curr_obstacle.pose.pose.position.y;
825 // NOTE: predicted_velocity is not used for collision calculation, but timestamps
826 curr_state.predicted_velocity.linear.x = curr_obstacle.velocity.twist.linear.x;
827 curr_state.predicted_velocity.linear.y = curr_obstacle.velocity.twist.linear.y;
828 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "Object: " << curr_obstacle.id <<", type: " << static_cast<int>(curr_obstacle.object_type)
829 << ", speed_x: " << curr_obstacle.velocity.twist.linear.x << ", speed_y: " << curr_obstacle.velocity.twist.linear.y);
830 new_list.push_back(curr_state);
831 new_list.insert(new_list.end(), curr_obstacle.predictions.cbegin(), curr_obstacle.predictions.cend());
832
833 const auto collision_result = get_collision(original_tp, new_list, config_.intervehicle_collision_distance_in_m, original_tp_max_speed);
834
835 if (!collision_result)
836 {
837 // reset the consecutive clearance counter because no collision was detected at this iteration
838 consecutive_clearance_count_for_obstacles_[curr_obstacle.id] = 0;
839 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"), "[CPU] obj=" << curr_obstacle.id << " no collision detected");
840 return std::nullopt;
841 }
842
843 // if within collision radius, it is not a collision if obstacle is behind the vehicle despite being in collision radius
844 const double vehicle_downtrack = wm_->routeTrackPos(collision_result.value().ego_point).downtrack;
845 const double object_downtrack = wm_->routeTrackPos(collision_result.value().object_point).downtrack;
846
847 if (is_object_behind_vehicle(curr_obstacle.id, collision_result.value().collision_time, vehicle_downtrack, object_downtrack))
848 {
849 RCLCPP_INFO_STREAM(nh_->get_logger(), "Confirmed that the object: " << curr_obstacle.id << " is behind the vehicle at timestamp " << std::to_string(collision_result.value().collision_time.seconds()));
850 return std::nullopt;
851 }
852
853 const auto distance{std::hypot(
854 collision_result.value().ego_point.x() - collision_result.value().object_point.x(),
855 collision_result.value().ego_point.y() - collision_result.value().object_point.y()
856 )}; //for debug
857
858 RCLCPP_WARN_STREAM(nh_->get_logger(), "Collision detected for object: " << curr_obstacle.id << ", at timestamp " << std::to_string(collision_result.value().collision_time.seconds()) <<
859 ", x: " << collision_result.value().ego_point.x() << ", y: " << collision_result.value().ego_point.y() <<
860 ", within actual downtrack distance: " << object_downtrack - vehicle_downtrack <<
861 ", and collision distance: " << distance);
862 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"), "[CPU] obj=" << curr_obstacle.id << " collision at t=" << collision_result.value().collision_time.seconds());
863
864 return collision_result.value().collision_time;
865 }
std::optional< GetCollisionResult > get_collision(const carma_planning_msgs::msg::TrajectoryPlan &ego_trajectory, const std::vector< carma_perception_msgs::msg::PredictedState > &object_predictions, double collision_radius, double ego_max_speed)
Return naive collision time and locations based on collision radius given two trajectories with one b...
std::unordered_map< uint32_t, int > consecutive_clearance_count_for_obstacles_
bool is_object_behind_vehicle(uint32_t object_id, const rclcpp::Time &collision_time, double vehicle_point, double object_downtrack)
Check if object location is behind the vehicle using estimates of the vehicle's length and route down...
double get_trajectory_start_time(const carma_planning_msgs::msg::TrajectoryPlan &trajectory)

References config_, consecutive_clearance_count_for_obstacles_, get_collision(), yield_plugin::get_trajectory_start_time(), YieldPluginConfig::intervehicle_collision_distance_in_m, is_object_behind_vehicle(), nh_, carma_cooperative_perception::to_string(), and wm_.

Referenced by get_collision_times_concurrently_cpu().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_collision_times_concurrently()

std::unordered_map< uint32_t, rclcpp::Time > yield_plugin::YieldPlugin::get_collision_times_concurrently ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
const std::vector< carma_perception_msgs::msg::ExternalObject > &  external_objects,
double  original_tp_max_speed 
)

Given the list of objects with predicted states, get all collision times concurrently using multi-threading.

Parameters
original_tptrajectory of the ego vehicle
external_objectslist of external objects with predicted states
original_tp_max_speedmax speed of the original_tp to efficiently traverse through possible collision combination of the two trajectories
Returns
mapping of objects' ids and their corresponding collision times (non-colliding objects are omitted)

Definition at line 927 of file yield_plugin.cpp.

931 {
932 if (!cuda_is_available())
933 return get_collision_times_concurrently_cpu(original_tp, external_objects, original_tp_max_speed);
934 return get_collision_times_concurrently_cuda(original_tp, external_objects, original_tp_max_speed);
935 }
std::unordered_map< uint32_t, rclcpp::Time > get_collision_times_concurrently_cpu(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double original_tp_max_speed)
CPU implementation of get_collision_times_concurrently. Launches one thread per external object and c...
std::unordered_map< uint32_t, rclcpp::Time > get_collision_times_concurrently_cuda(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double original_tp_max_speed)
CUDA implementation of get_collision_times_concurrently. Filters objects to those with on-route predi...

References get_collision_times_concurrently_cpu(), and get_collision_times_concurrently_cuda().

Referenced by get_earliest_collision_object_and_time().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_collision_times_concurrently_cpu()

std::unordered_map< uint32_t, rclcpp::Time > yield_plugin::YieldPlugin::get_collision_times_concurrently_cpu ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
const std::vector< carma_perception_msgs::msg::ExternalObject > &  external_objects,
double  original_tp_max_speed 
)
private

CPU implementation of get_collision_times_concurrently. Launches one thread per external object and computes get_collision_time for each in parallel.

Parameters
original_tptrajectory of the ego vehicle
external_objectslist of external objects with predicted states
original_tp_max_speedmax speed of the original_tp to efficiently traverse through possible collision combination of the two trajectories
Returns
mapping of objects' ids and their corresponding collision times (non-colliding objects are omitted)

Definition at line 937 of file yield_plugin.cpp.

941 {
942 std::unordered_map<uint32_t, std::future<std::optional<rclcpp::Time>>> futures;
943 std::unordered_map<uint32_t, rclcpp::Time> collision_times;
944 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
945 "[CPU] Launching " << external_objects.size() << " async get_collision_time tasks");
946 std::vector<std::thread> threads;
947 threads.reserve(external_objects.size());
948 for (const auto& object : external_objects) {
949 std::packaged_task<std::optional<rclcpp::Time>()> task(
950 [this, &original_tp, &object, &original_tp_max_speed] {
951 return get_collision_time(original_tp, object, original_tp_max_speed);
952 });
953 futures[object.id] = task.get_future();
954 threads.emplace_back(std::move(task));
955 }
956 for (auto& t : threads) t.join();
957 for (const auto& object : external_objects) {
958 if (const auto collision_time{futures.at(object.id).get()}) {
959 collision_times[object.id] = collision_time.value();
960 }
961 }
962 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
963 "[CPU] Done — " << collision_times.size() << " collision(s) confirmed");
964 return collision_times;
965 }
std::optional< rclcpp::Time > get_collision_time(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const carma_perception_msgs::msg::ExternalObject &curr_obstacle, double original_tp_max_speed)
Return collision time given two trajectories with one being external object with predicted steps.

References get_collision_time().

Referenced by get_collision_times_concurrently(), and get_collision_times_concurrently_cuda().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_collision_times_concurrently_cuda()

std::unordered_map< uint32_t, rclcpp::Time > yield_plugin::YieldPlugin::get_collision_times_concurrently_cuda ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
const std::vector< carma_perception_msgs::msg::ExternalObject > &  external_objects,
double  original_tp_max_speed 
)
private

CUDA implementation of get_collision_times_concurrently. Filters objects to those with on-route predictions, then batches the collision check across all objects on the GPU. Falls back to get_collision_times_concurrently_cpu if no CUDA device is available or on error.

Parameters
original_tptrajectory of the ego vehicle
external_objectslist of external objects with predicted states
original_tp_max_speedmax speed of the original_tp to efficiently traverse through possible collision combination of the two trajectories
Returns
mapping of objects' ids and their corresponding collision times (non-colliding objects are omitted)

Definition at line 967 of file yield_plugin.cpp.

971 {
972 std::unordered_map<uint32_t, rclcpp::Time> collision_times;
973
974 if (original_tp.trajectory_points.size() < 2) return collision_times;
975
976 const double plan_start_time = get_trajectory_start_time(original_tp);
977
978 // Timestamps as absolute doubles; reference used to normalise into float32.
979 const double ref_time = plan_start_time;
980
981 // Build ego SoA (structure of array) with normalised timestamps.
982 const auto num_ego_points = static_cast<int>(original_tp.trajectory_points.size());
983 std::vector<CudaPoint> ego_pts;
984 ego_pts.reserve(num_ego_points);
985 for (const auto& tp : original_tp.trajectory_points) {
986 ego_pts.push_back({
987 static_cast<float>(tp.x),
988 static_cast<float>(tp.y),
989 static_cast<float>(rclcpp::Time(tp.target_time).seconds() - ref_time)
990 });
991 }
992
993 // Per-object data accumulated for the CUDA batch.
994 struct ActiveObject {
995 uint32_t id;
996 // Prediction list with current position prepended (same as get_collision_time builds).
997 std::vector<carma_perception_msgs::msg::PredictedState> predictions;
998 int on_route_idx; // first prediction index known to be on the route
999 };
1000
1001 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1002 "[GPU] Processing " << external_objects.size() << " external objects");
1003
1004 std::vector<ActiveObject> active;
1005 std::vector<CudaPoint> obs_flat;
1006 std::vector<int> obs_offsets;
1007 std::vector<int> obs_sizes;
1008
1009 for (const auto& obj : external_objects) {
1010 // Skip objects whose entire prediction horizon is before the plan start.
1011 if (rclcpp::Time(obj.predictions.back().header.stamp).seconds() <= plan_start_time) {
1012 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1013 "[GPU] obj=" << obj.id << " skipped — predictions expired before plan start");
1014 continue;
1015 }
1016
1017 // Build prediction list: prepend current position (mirrors get_collision_time).
1018 std::vector<carma_perception_msgs::msg::PredictedState> pred_list;
1019 pred_list.reserve(obj.predictions.size() + 1);
1020 {
1021 carma_perception_msgs::msg::PredictedState curr;
1022 curr.header.stamp = obj.header.stamp;
1023 curr.predicted_position.position.x = obj.pose.pose.position.x;
1024 curr.predicted_position.position.y = obj.pose.pose.position.y;
1025 curr.predicted_velocity.linear.x = obj.velocity.twist.linear.x;
1026 curr.predicted_velocity.linear.y = obj.velocity.twist.linear.y;
1027 pred_list.push_back(curr);
1028 }
1029 pred_list.insert(pred_list.end(), obj.predictions.cbegin(), obj.predictions.cend());
1030
1031 if (pred_list.size() < 2) continue;
1032
1033 const double object_prediction_step_duration =
1034 (rclcpp::Time(pred_list.at(1).header.stamp) - rclcpp::Time(pred_list.front().header.stamp)).seconds();
1035 if (object_prediction_step_duration < 0.0) continue;
1036
1037 // Quick spatial pre-filter: if the object starts beyond collision_check_radius_in_m
1038 // it cannot collide with the ego at the start of the trajectory.
1039 {
1040 const double ego_to_object_dx = ego_pts[0].x - obj.pose.pose.position.x;
1041 const double ego_to_object_dy = ego_pts[0].y - obj.pose.pose.position.y;
1042 const double ego_to_object_dist = std::hypot(ego_to_object_dx, ego_to_object_dy);
1043 if (ego_to_object_dist > config_.collision_check_radius_in_m) {
1044 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1045 "[GPU] obj=" << obj.id << " skipped — dist_from_ego=" << ego_to_object_dist
1046 << " > radius=" << config_.collision_check_radius_in_m);
1048 continue;
1049 }
1050 }
1051
1052 // On-route check — stride logic as in get_collision, but uses pre-computed
1053 // per-lanelet bounding boxes instead of getLaneletsFromPoint (O(1) vs spatial query).
1054 const double object_speed = std::hypot(
1055 pred_list.front().predicted_velocity.linear.x,
1056 pred_list.front().predicted_velocity.linear.y);
1057 const bool object_has_zero_speed = object_speed < config_.obstacle_zero_speed_threshold_in_ms;
1058
1059 const double stride_max_t = 2.0 * config_.intervehicle_collision_distance_in_m /
1060 std::sqrt(std::pow(object_speed, 2) + std::pow(original_tp_max_speed, 2));
1061 const int iteration_stride = std::max(1, static_cast<int>(stride_max_t / object_prediction_step_duration));
1062
1063 const auto [on_route, on_route_idx] = find_on_route_in_predictions(pred_list, iteration_stride, object_has_zero_speed);
1064
1065 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1066 "[GPU] obj=" << obj.id << " on_route=" << on_route
1067 << " on_route_idx=" << on_route_idx
1068 << " speed=" << object_speed
1069 << " stride=" << iteration_stride);
1070
1071 if (!on_route) {
1073 continue;
1074 }
1075
1076 // Pack the on-route portion of the prediction into the flat obstacle buffer.
1077 obs_offsets.push_back(static_cast<int>(obs_flat.size()));
1078 int count = 0;
1079 for (int j = on_route_idx; j < static_cast<int>(pred_list.size()); ++j) {
1080 obs_flat.push_back({
1081 static_cast<float>(pred_list[j].predicted_position.position.x),
1082 static_cast<float>(pred_list[j].predicted_position.position.y),
1083 static_cast<float>(rclcpp::Time(pred_list[j].header.stamp).seconds() - ref_time)
1084 });
1085 ++count;
1086 }
1087 obs_sizes.push_back(count);
1088 active.push_back({obj.id, std::move(pred_list), on_route_idx});
1089 }
1090
1091 if (active.empty()) return collision_times;
1092
1093 // -----------------------------------------------------------------------
1094 // GPU: exact, continuous-time segment-pair collision detection.
1095 // -----------------------------------------------------------------------
1096 auto _t0_cuda = std::chrono::steady_clock::now();
1097 try {
1098 const auto cuda_results = cuda_check_all_collisions(
1099 ego_pts, obs_flat, obs_offsets, obs_sizes,
1100 static_cast<float>(config_.intervehicle_collision_distance_in_m));
1101 const double _cuda_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_cuda).count();
1102 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1103 "[timing] cuda_check_all_collisions: " << _cuda_ms << " ms");
1104
1105 // -----------------------------------------------------------------------
1106 // Post-process: recover collision positions and run behind-vehicle check.
1107 // -----------------------------------------------------------------------
1108 for (size_t k = 0; k < active.size(); ++k) {
1109 const auto& object_result = cuda_results[k];
1110
1111 if (!object_result.has_collision) {
1112 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1113 "[GPU] obj=" << active[k].id << " no collision detected");
1115 continue;
1116 }
1117
1118 const double collision_time_abs = static_cast<double>(object_result.collision_t_norm) + ref_time;
1119 const rclcpp::Time collision_time(static_cast<int64_t>(collision_time_abs * 1e9));
1120
1121 const lanelet::BasicPoint2d ego_collision_point = interp_trajectory_pt_at_time(collision_time_abs, ego_pts, num_ego_points, original_tp);
1122 const lanelet::BasicPoint2d object_collision_point = interp_predicted_pt_at_time(collision_time_abs, active[k].predictions, active[k].on_route_idx);
1123
1124 const double vehicle_downtrack = wm_->routeTrackPos(ego_collision_point).downtrack;
1125 const double object_downtrack = wm_->routeTrackPos(object_collision_point).downtrack;
1126
1127 if (is_object_behind_vehicle(active[k].id, collision_time,
1128 vehicle_downtrack, object_downtrack)) {
1129 RCLCPP_INFO_STREAM(nh_->get_logger(),
1130 "Confirmed that the object: " << active[k].id
1131 << " is behind the vehicle at timestamp "
1132 << std::to_string(collision_time.seconds()));
1133 continue;
1134 }
1135
1136 RCLCPP_WARN_STREAM(nh_->get_logger(),
1137 "Collision detected for object: " << active[k].id
1138 << ", at timestamp " << std::to_string(collision_time.seconds())
1139 << ", x: " << ego_collision_point.x() << ", y: " << ego_collision_point.y()
1140 << ", within actual downtrack distance: "
1141 << object_downtrack - vehicle_downtrack);
1142
1143 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1144 "[GPU] obj=" << active[k].id
1145 << " collision at t=" << collision_time_abs
1146 << " ego=(" << ego_collision_point.x() << "," << ego_collision_point.y() << ")"
1147 << " obs=(" << object_collision_point.x() << "," << object_collision_point.y() << ")"
1148 << " downtrack_gap=" << (object_downtrack - vehicle_downtrack));
1149 collision_times[active[k].id] = collision_time;
1150 }
1151
1152 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1153 "[GPU] Done — " << collision_times.size() << " collision(s) confirmed");
1154
1155 } catch (const std::runtime_error& e) {
1156 std::string error_msg(e.what());
1157 // Detect CUDA-specific errors
1158 bool is_cuda_error = (error_msg.find("CUDA") != std::string::npos ||
1159 error_msg.find("cuda") != std::string::npos ||
1160 error_msg.find("driver version") != std::string::npos ||
1161 error_msg.find("runtime version") != std::string::npos ||
1162 error_msg.find("GPU") != std::string::npos);
1163
1164 if (is_cuda_error) {
1165 RCLCPP_WARN_STREAM_ONCE(rclcpp::get_logger("yield_plugin"),
1166 "[GPU] CUDA unavailable (" << e.what() << "), please make sure GPU is accessible for this node or container. Using CPU fallback");
1167 } else {
1168 RCLCPP_ERROR_STREAM_ONCE(rclcpp::get_logger("yield_plugin"),
1169 "[GPU] Unexpected error during GPU collision detection: " << e.what() << ", using CPU fallback");
1170 }
1171 return get_collision_times_concurrently_cpu(original_tp, external_objects, original_tp_max_speed);
1172 } catch (const std::exception& e) {
1173 RCLCPP_ERROR_STREAM_ONCE(rclcpp::get_logger("yield_plugin"),
1174 "[GPU] Unexpected exception during GPU collision detection: " << typeid(e).name() << " - " << e.what() << ", using CPU fallback");
1175 return get_collision_times_concurrently_cpu(original_tp, external_objects, original_tp_max_speed);
1176 }
1177 return collision_times;
1178 }
std::pair< bool, int > find_on_route_in_predictions(const std::vector< carma_perception_msgs::msg::PredictedState > &predictions, int stride, bool zero_speed) const
Check whether any predicted state of an object falls within the route lanelet polygons.
static lanelet::BasicPoint2d interp_predicted_pt_at_time(double query_time, const std::vector< carma_perception_msgs::msg::PredictedState > &predictions, int start_index)
static lanelet::BasicPoint2d interp_trajectory_pt_at_time(double query_time, const std::vector< CudaPoint > &ego_points, int num_ego_points, const carma_planning_msgs::msg::TrajectoryPlan &trajectory_plan)

References YieldPluginConfig::collision_check_radius_in_m, config_, consecutive_clearance_count_for_obstacles_, find_on_route_in_predictions(), get_collision_times_concurrently_cpu(), yield_plugin::get_trajectory_start_time(), yield_plugin::interp_predicted_pt_at_time(), yield_plugin::interp_trajectory_pt_at_time(), YieldPluginConfig::intervehicle_collision_distance_in_m, is_object_behind_vehicle(), nh_, YieldPluginConfig::obstacle_zero_speed_threshold_in_ms, carma_cooperative_perception::to_string(), and wm_.

Referenced by get_collision_times_concurrently().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_earliest_collision_object_and_time()

std::optional< std::pair< carma_perception_msgs::msg::ExternalObject, double > > yield_plugin::YieldPlugin::get_earliest_collision_object_and_time ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
const std::vector< carma_perception_msgs::msg::ExternalObject > &  external_objects 
)

Return the earliest collision object and time of collision pair from the given trajectory and list of external objects with predicted states. Function first filters obstacles based on whether if their any of predicted state will be on the route. Only then, the logic compares trajectory and predicted states.

Parameters
original_tptrajectory of the ego vehicle
external_objectslist of external objects with predicted states
Returns
earliest collision object and its collision time if collision detected. std::nullopt if no collision is detected or if route is not available.

Definition at line 1180 of file yield_plugin.cpp.

1182 {
1183 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"), "ExternalObjects size: " << external_objects.size());
1184
1185 if (!wm_->getRoute())
1186 {
1187 RCLCPP_WARN(nh_->get_logger(), "Yield plugin was not able to analyze collision since route is not available! Please check if route is set");
1188 return std::nullopt;
1189 }
1190
1191 // Populate route_llt_polygons_ if empty but route is actually available.
1192 if (route_llt_polygons_.empty())
1193 {
1195 }
1196
1197 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"External Object List (external_objects) size: " << external_objects.size());
1198 const double original_max_speed = max_trajectory_speed(original_tp.trajectory_points, get_trajectory_end_time(original_tp));
1199 auto _t0_conc = std::chrono::steady_clock::now();
1200 std::unordered_map<uint32_t, rclcpp::Time> collision_times = get_collision_times_concurrently(original_tp,external_objects, original_max_speed);
1201 const double _conc_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_conc).count();
1202 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1203 "[timing] get_collision_times_concurrently: " << _conc_ms << " ms");
1204
1205 if (collision_times.empty()) { return std::nullopt; }
1206
1207 const auto earliest_colliding_object_id{std::min_element(
1208 std::cbegin(collision_times), std::cend(collision_times),
1209 [](const auto & a, const auto & b){ return a.second < b.second; })->first};
1210
1211 const auto earliest_colliding_object{std::find_if(
1212 std::cbegin(external_objects), std::cend(external_objects),
1213 [&earliest_colliding_object_id](const auto & object) { return object.id == earliest_colliding_object_id; })};
1214
1215 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"earliest object x: " << earliest_colliding_object->velocity.twist.linear.x
1216 << ", y: " << earliest_colliding_object->velocity.twist.linear.y);
1217 return std::make_pair(*earliest_colliding_object, collision_times.at(earliest_colliding_object_id).seconds());
1218
1219 }
void update_route_llt_cache()
Rebuild the cached route lanelet polygons and IDs from the current route. Should be called once whene...
std::unordered_map< uint32_t, rclcpp::Time > get_collision_times_concurrently(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double original_tp_max_speed)
Given the list of objects with predicted states, get all collision times concurrently using multi-thr...
double max_trajectory_speed(const std::vector< carma_planning_msgs::msg::TrajectoryPlanPoint > &trajectory_points, double timestamp_in_sec_to_search_until) const
calculates the maximum speed in a set of tajectory points
double get_trajectory_end_time(const carma_planning_msgs::msg::TrajectoryPlan &trajectory)

References get_collision_times_concurrently(), yield_plugin::get_trajectory_end_time(), max_trajectory_speed(), nh_, route_llt_polygons_, update_route_llt_cache(), and wm_.

Referenced by update_traj_for_object().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_predicted_velocity_at_time()

double yield_plugin::YieldPlugin::get_predicted_velocity_at_time ( const geometry_msgs::msg::Twist &  object_velocity_in_map_frame,
const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
double  timestamp_in_sec_to_predict 
)

Given the object velocity in map frame with x,y components, this function returns the projected velocity along the trajectory at given time.

Parameters
object_velocity_in_map_frametrajectory of the ego vehicle
original_tptrajectory of the ego vehicle
timestamp_in_sec_to_predicttimestamp in seconds along the trajectory to return the projected velocity NOTE: returns the last point's speed if the specified time is past the trajectory's planning time
Returns
get_predicted_velocity_at_time

Definition at line 1221 of file yield_plugin.cpp.

1223 {
1224 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "timestamp_in_sec_to_predict: " << std::to_string(timestamp_in_sec_to_predict) <<
1225 ", trajectory_end_time: " << std::to_string(get_trajectory_end_time(original_tp)));
1226
1227 double point_b_time = 0.0;
1228 carma_planning_msgs::msg::TrajectoryPlanPoint point_a;
1229 carma_planning_msgs::msg::TrajectoryPlanPoint point_b;
1230
1231 // trajectory points' time is guaranteed to be increasing
1232 // then find the corresponding point at timestamp_in_sec_to_predict
1233 for (size_t i = 0; i < original_tp.trajectory_points.size() - 1; ++i)
1234 {
1235 point_a = original_tp.trajectory_points.at(i);
1236 point_b = original_tp.trajectory_points.at(i + 1);
1237 point_b_time = rclcpp::Time(point_b.target_time).seconds();
1238 if (point_b_time >= timestamp_in_sec_to_predict)
1239 {
1240 break;
1241 }
1242 }
1243
1244 auto dx = point_b.x - point_a.x;
1245 auto dy = point_b.y - point_a.y;
1246 const tf2::Vector3 trajectory_direction(dx, dy, 0);
1247
1248 RCLCPP_DEBUG_STREAM(nh_->get_logger(), "timestamp_in_sec_to_predict: " << std::to_string(timestamp_in_sec_to_predict)
1249 << ", point_b_time: " << std::to_string(point_b_time)
1250 << ", dx: " << dx << ", dy: " << dy << ", "
1251 << ", object_velocity_in_map_frame.x: " << object_velocity_in_map_frame.linear.x
1252 << ", object_velocity_in_map_frame.y: " << object_velocity_in_map_frame.linear.y);
1253
1254 if (trajectory_direction.length() < 0.001) //EPSILON
1255 {
1256 return 0.0;
1257 }
1258
1259 const tf2::Vector3 object_direction(object_velocity_in_map_frame.linear.x, object_velocity_in_map_frame.linear.y, 0);
1260
1261 return tf2::tf2Dot(object_direction, trajectory_direction) / trajectory_direction.length();
1262 }

References visualize_xodr::dx, visualize_xodr::dy, yield_plugin::get_trajectory_end_time(), process_bag::i, nh_, and carma_cooperative_perception::to_string().

Referenced by update_traj_for_object().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_relative_downtracks()

std::vector< double > yield_plugin::YieldPlugin::get_relative_downtracks ( const carma_planning_msgs::msg::TrajectoryPlan &  trajectory_plan) const

calculates distance between trajectory points in a plan

Parameters
trajectory_planinput trajectory plan
Returns
vector of relative distances between trajectory points

Definition at line 1381 of file yield_plugin.cpp.

1382 {
1383 std::vector<double> downtracks;
1384 downtracks.reserve(trajectory_plan.trajectory_points.size());
1385 // relative downtrack distance of the fist Point is 0.0
1386 downtracks.push_back(0.0);
1387 for (size_t i=1; i < trajectory_plan.trajectory_points.size(); i++){
1388
1389 double dx = trajectory_plan.trajectory_points.at(i).x - trajectory_plan.trajectory_points.at(i-1).x;
1390 double dy = trajectory_plan.trajectory_points.at(i).y - trajectory_plan.trajectory_points.at(i-1).y;
1391 downtracks.push_back(sqrt(dx*dx + dy*dy));
1392 }
1393 return downtracks;
1394 }

References visualize_xodr::dx, visualize_xodr::dy, and process_bag::i.

Referenced by generate_JMT_trajectory().

Here is the caller graph for this function:

◆ is_object_behind_vehicle()

bool yield_plugin::YieldPlugin::is_object_behind_vehicle ( uint32_t  object_id,
const rclcpp::Time &  collision_time,
double  vehicle_point,
double  object_downtrack 
)

Check if object location is behind the vehicle using estimates of the vehicle's length and route downtracks.

Parameters
object_idobject id to use for the consecutive_clearance_count_for_obstacles_
collision_timepredicted time of collision
vehicle_downtrackat the time of collision
object_downtrackat the time of collision NOTE: Uses internal counter low pass filter to confirm the object is behind only if it counted continuously above config_.consecutive_clearance_count_for_passed_obstacles_threshold
Returns
return true if object is behind the vehicle

Definition at line 779 of file yield_plugin.cpp.

780 {
781 const auto previous_clearance_count = consecutive_clearance_count_for_obstacles_[object_id];
782 // if the object's location is half a length of the vehicle past its rear-axle, it is considered behind
783 // half a length of the vehicle to conservatively estimate the rear axle to rear bumper length
784 if (object_downtrack < vehicle_downtrack - config_.vehicle_length / 2)
785 {
787 RCLCPP_INFO_STREAM(nh_->get_logger(), "Detected an object nearby might be behind the vehicle at timestamp: " << std::to_string(collision_time.seconds()) <<
788 ", and consecutive_clearance_count_for obstacle: " << object_id << ", is: " << consecutive_clearance_count_for_obstacles_[object_id]);
789 }
790 // confirmed false positive for a collision
792 {
793 return true;
794 }
795 // if the clearance counter didn't increase by this point, true collision was detected
796 // therefore reset the consecutive clearance counter as it is no longer consecutive
797 if (consecutive_clearance_count_for_obstacles_[object_id] == previous_clearance_count)
798 {
800 }
801
802 return false;
803 }
int consecutive_clearance_count_for_passed_obstacles_threshold

References config_, consecutive_clearance_count_for_obstacles_, YieldPluginConfig::consecutive_clearance_count_for_passed_obstacles_threshold, nh_, carma_cooperative_perception::to_string(), and YieldPluginConfig::vehicle_length.

Referenced by get_collision_time(), and get_collision_times_concurrently_cuda().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ lookup_ecef_to_map_transform()

void yield_plugin::YieldPlugin::lookup_ecef_to_map_transform ( )

Looks up the transform between map and earth frames, and sets the member variable.

◆ max_trajectory_speed()

double yield_plugin::YieldPlugin::max_trajectory_speed ( const std::vector< carma_planning_msgs::msg::TrajectoryPlanPoint > &  trajectory_points,
double  timestamp_in_sec_to_search_until 
) const

calculates the maximum speed in a set of tajectory points

Parameters
trajectory_pointstrajectory points

\param timestamp_in_sec_to_search_until before which to look for max trajectory speed

Returns
maximum speed

Definition at line 1418 of file yield_plugin.cpp.

1419 {
1420 double max_speed = 0;
1421 for(size_t i = 0; i < trajectory_points.size() - 2; i++ )
1422 {
1423 double dx = trajectory_points.at(i + 1).x - trajectory_points.at(i).x;
1424 double dy = trajectory_points.at(i + 1).y - trajectory_points.at(i).y;
1425 double d = sqrt(dx*dx + dy*dy);
1426 double t = (rclcpp::Time(trajectory_points.at(i + 1).target_time).seconds() - rclcpp::Time(trajectory_points.at(i).target_time).seconds());
1427 double v = d/t;
1428 if(v > max_speed)
1429 {
1430 max_speed = v;
1431 }
1432 if (rclcpp::Time(trajectory_points.at(i + 1).target_time).seconds() >= timestamp_in_sec_to_search_until)
1433 {
1434 break;
1435 }
1436
1437 }
1438 return max_speed;
1439 }

References visualize_xodr::dx, visualize_xodr::dy, and process_bag::i.

Referenced by get_earliest_collision_object_and_time(), update_traj_for_cooperative_behavior(), and update_traj_for_object().

Here is the caller graph for this function:

◆ mobilityrequest_cb()

void yield_plugin::YieldPlugin::mobilityrequest_cb ( const carma_v2x_msgs::msg::MobilityRequest::UniquePtr  msg)

callback for mobility request

Parameters
msgmobility request message

Definition at line 157 of file yield_plugin.cpp.

158 {
159 carma_v2x_msgs::msg::MobilityRequest incoming_request = *msg;
160 carma_planning_msgs::msg::LaneChangeStatus lc_status_msg;
161 if (incoming_request.strategy == "carma/cooperative-lane-change")
162 {
163 if (!map_projector_) {
164 RCLCPP_ERROR(nh_->get_logger(),"Cannot process mobility request as map projection is not yet set!");
165 return;
166 }
167 if (incoming_request.plan_type.type == carma_v2x_msgs::msg::PlanType::CHANGE_LANE_LEFT || incoming_request.plan_type.type == carma_v2x_msgs::msg::PlanType::CHANGE_LANE_RIGHT)
168 {
169 RCLCPP_DEBUG(nh_->get_logger(),"Cooperative Lane Change Request Received");
170 lc_status_msg.status = carma_planning_msgs::msg::LaneChangeStatus::REQUEST_RECEIVED;
171 lc_status_msg.description = "Received lane merge request";
172
173 if (incoming_request.m_header.recipient_id == config_.vehicle_id)
174 {
175 RCLCPP_DEBUG(nh_->get_logger(),"CLC Request correctly received");
176 }
177
178 // extract mobility header
179 std::string req_sender_id = incoming_request.m_header.sender_id;
180 std::string req_plan_id = incoming_request.m_header.plan_id;
181 // extract mobility request
182 carma_v2x_msgs::msg::LocationECEF ecef_location = incoming_request.location;
183 carma_v2x_msgs::msg::Trajectory incoming_trajectory = incoming_request.trajectory;
184 std::string req_strategy_params = incoming_request.strategy_params;
185 clc_urgency_ = incoming_request.urgency;
186 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"received urgency: " << clc_urgency_);
187
188 // Parse strategy parameters
189 using boost::property_tree::ptree;
190 ptree pt;
191 std::istringstream strstream(req_strategy_params);
192 boost::property_tree::json_parser::read_json(strstream, pt);
193 int req_traj_speed_full = pt.get<int>("s");
194 int req_traj_fractional = pt.get<int>("f");
195 int start_lanelet_id = pt.get<int>("sl");
196 int end_lanelet_id = pt.get<int>("el");
197 double req_traj_speed = static_cast<double>(req_traj_speed_full) + static_cast<double>(req_traj_fractional)/10.0;
198 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"req_traj_speed" << req_traj_speed);
199 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"start_lanelet_id" << start_lanelet_id);
200 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"end_lanelet_id" << end_lanelet_id);
201
202 std::vector<lanelet::BasicPoint2d> req_traj_plan = {};
203
204 req_traj_plan = convert_eceftrajectory_to_mappoints(incoming_trajectory);
205
206 double req_expiration_sec = static_cast<double>(incoming_request.expiration);
207 double current_time_sec = nh_->now().seconds();
208
209 bool response_to_clc_req = false;
210 // ensure there is enough time for the yield
211 double req_plan_time = req_expiration_sec - current_time_sec;
212 double req_timestamp = static_cast<double>(incoming_request.m_header.timestamp) / 1000.0 - current_time_sec;
213 set_incoming_request_info(req_traj_plan, req_traj_speed, req_plan_time, req_timestamp);
214
215
216 if (req_expiration_sec - current_time_sec >= config_.min_obj_avoidance_plan_time_in_s && cooperative_request_acceptable_)
217 {
219 lc_status_msg.status = carma_planning_msgs::msg::LaneChangeStatus::REQUEST_ACCEPTED;
220 lc_status_msg.description = "Accepted lane merge request";
221 response_to_clc_req = true;
222 RCLCPP_DEBUG(nh_->get_logger(),"CLC accepted");
223 }
224 else
225 {
226 lc_status_msg.status = carma_planning_msgs::msg::LaneChangeStatus::REQUEST_REJECTED;
227 lc_status_msg.description = "Rejected lane merge request";
228 response_to_clc_req = false;
229 RCLCPP_DEBUG(nh_->get_logger(),"CLC rejected");
230 }
231 carma_v2x_msgs::msg::MobilityResponse outgoing_response = compose_mobility_response(req_sender_id, req_plan_id, response_to_clc_req);
232 mobility_response_publisher_(outgoing_response);
233 lc_status_msg.status = carma_planning_msgs::msg::LaneChangeStatus::RESPONSE_SENT;
234 RCLCPP_DEBUG(nh_->get_logger(),"response sent");
235 }
236 }
237 lc_status_publisher_(lc_status_msg);
238
239 }
void set_incoming_request_info(std::vector< lanelet::BasicPoint2d > req_trajectory, double req_speed, double req_planning_time, double req_timestamp)
set values for member variables related to cooperative behavior
std::vector< lanelet::BasicPoint2d > convert_eceftrajectory_to_mappoints(const carma_v2x_msgs::msg::Trajectory &ecef_trajectory) const
convert a carma trajectory from ecef frame to map frame ecef trajectory consists of the point and a s...
carma_v2x_msgs::msg::MobilityResponse compose_mobility_response(const std::string &resp_recipient_id, const std::string &req_plan_id, bool response) const
compose a mobility response message
double min_obj_avoidance_plan_time_in_s

References clc_urgency_, compose_mobility_response(), config_, convert_eceftrajectory_to_mappoints(), cooperative_request_acceptable_, lc_status_publisher_, map_projector_, YieldPluginConfig::min_obj_avoidance_plan_time_in_s, mobility_response_publisher_, nh_, set_incoming_request_info(), timesteps_since_last_req_, and YieldPluginConfig::vehicle_id.

Referenced by yield_plugin::YieldPluginNode::on_configure_plugin().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ plan_trajectory_callback()

void yield_plugin::YieldPlugin::plan_trajectory_callback ( carma_planning_msgs::srv::PlanTrajectory::Request::SharedPtr  req,
carma_planning_msgs::srv::PlanTrajectory::Response::SharedPtr  resp 
)

Service callback for trajectory planning.

Parameters
srv_headerheader
reqThe service request
respThe service response

Definition at line 256 of file yield_plugin.cpp.

259{
260 RCLCPP_DEBUG(nh_->get_logger(),"Yield_plugin was called!");
261 if (req->initial_trajectory_plan.trajectory_points.size() < 2){
262 throw std::invalid_argument("Empty Trajectory received by Yield");
263 }
264 rclcpp::Clock system_clock(RCL_SYSTEM_TIME);
265 rclcpp::Time start_time = system_clock.now(); // Start timing the execution time for planning so it can be logged
266
267 carma_planning_msgs::msg::TrajectoryPlan original_trajectory = req->initial_trajectory_plan;
268 carma_planning_msgs::msg::TrajectoryPlan yield_trajectory;
269
270 // if ego is not stopped and we committed to stopping, use the last committed trajectory
271 if (req->vehicle_state.longitudinal_vel > EPSILON &&
273 {
274 RCLCPP_DEBUG(nh_->get_logger(), "Using last committed trajectory to stopping");
275 lanelet::BasicPoint2d veh_pos(req->vehicle_state.x_pos_global,
276 req->vehicle_state.y_pos_global);
277 auto updated_trajectory = last_traj_plan_committed_to_stopping_.value();
278
279 // Find closest point in last trajectory to current vehicle position
280 size_t idx_to_start_new_traj =
282 updated_trajectory.trajectory_points,
283 veh_pos);
284
285 // Update last trajectory to start from closest point (remove passed points)
286 if (!updated_trajectory.trajectory_points.empty()) {
287 updated_trajectory.trajectory_points =
288 std::vector<carma_planning_msgs::msg::TrajectoryPlanPoint>
289 (updated_trajectory.trajectory_points.begin() + idx_to_start_new_traj,
290 updated_trajectory.trajectory_points.end());
291 }
292 last_traj_plan_committed_to_stopping_ = updated_trajectory;
293 resp->trajectory_plan = last_traj_plan_committed_to_stopping_.value();
294
295 rclcpp::Time end_time = system_clock.now(); // Planning complete
296
297 auto duration = end_time - start_time;
298 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
299 "ExecutionTime Yield: " << std::to_string(duration.seconds()));
300 return;
301 }
302
303 // Otherwise, we are planning a new trajectory by checking collision
304 try
305 {
306 // NOTE: Wrapping entire plan_trajectory logic with try catch because there is intermittent
307 // open issue of which cause is uncertain:
308 // https://github.com/usdot-fhwa-stol/carma-platform/issues/2501
309
310 double initial_velocity = req->vehicle_state.longitudinal_vel;
311 // If vehicle_state is stopped, non-zero velocity from the trajectory
312 // should be used. Otherwise, vehicle will not move.
313 if (initial_velocity < EPSILON)
314 {
315 initial_velocity = original_trajectory.initial_longitudinal_velocity;
316 // Record the time when vehicle was stopped first due to collision avoidance
319 {
321 RCLCPP_DEBUG(nh_->get_logger(), "First time stopped to prevent collision: %f",
323 }
324 }
325
326 // seperating cooperative yield with regular object detection for better performance.
328 {
329 RCLCPP_DEBUG(nh_->get_logger(),"Only consider high urgency clc");
331 {
332 RCLCPP_DEBUG(nh_->get_logger(),"Yield for CLC. We haven't received an updated negotiation this timestep");
333 yield_trajectory = update_traj_for_cooperative_behavior(original_trajectory, initial_velocity);
335 }
336 else
337 {
338 RCLCPP_DEBUG(nh_->get_logger(),"unreliable CLC communication, switching to object avoidance");
339 yield_trajectory = update_traj_for_object(original_trajectory, external_objects_, initial_velocity); // Compute the trajectory
340 }
341 }
342 else
343 {
344 RCLCPP_DEBUG(nh_->get_logger(),"Yield for object avoidance");
345 auto _t0_upd = std::chrono::steady_clock::now();
346 yield_trajectory = update_traj_for_object(original_trajectory, external_objects_, initial_velocity); // Compute the trajectory
347 const double _upd_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_upd).count();
348 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
349 "[timing] update_traj_for_object: " << _upd_ms << " ms");
350 }
351
352
353 // return original trajectory if no difference in trajectory points a.k.a no collision
354 if (fabs(get_trajectory_end_time(original_trajectory) - get_trajectory_end_time(yield_trajectory)) < EPSILON)
355 {
356
357 resp->trajectory_plan = original_trajectory;
358 // Reset the collision prevention variables
361
362 }
363 else
364 {
365 yield_trajectory.header.frame_id = "map";
366 yield_trajectory.header.stamp = nh_->now();
367 yield_trajectory.trajectory_id = original_trajectory.trajectory_id;
368 resp->trajectory_plan = yield_trajectory;
369 }
370 }
371 catch(const std::runtime_error& e) {
372 RCLCPP_WARN_STREAM(nh_->get_logger(), "Yield Plugin failed to plan trajectory due to known negative time issue: " << e.what());
373 RCLCPP_WARN_STREAM(nh_->get_logger(), "Returning the original trajectory, and retrying at the next call.");
374 resp->trajectory_plan = original_trajectory;
375 }
376
377 rclcpp::Time end_time = system_clock.now(); // Planning complete
378
379 auto duration = end_time - start_time;
380 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
381 "ExecutionTime Yield: " << std::to_string(duration.seconds()));
382 }
std::optional< rclcpp::Time > first_time_stopped_to_prevent_collision_
std::optional< carma_planning_msgs::msg::TrajectoryPlan > last_traj_plan_committed_to_stopping_
carma_planning_msgs::msg::TrajectoryPlan update_traj_for_object(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects, double initial_velocity)
trajectory is modified to safely avoid obstacles on the road
carma_planning_msgs::msg::TrajectoryPlan update_traj_for_cooperative_behavior(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, double current_speed)
update trajectory for yielding to an incoming cooperative behavior
std::vector< carma_perception_msgs::msg::ExternalObject > external_objects_
int get_nearest_point_index(const std::vector< lanelet::BasicPoint2d > &points, const carma_planning_msgs::msg::VehicleState &state)
Returns the nearest point (in terms of cartesian 2d distance) to the provided vehicle pose in the pro...
bool enable_cooperative_behavior

References YieldPluginConfig::acceptable_passed_timesteps, YieldPluginConfig::acceptable_urgency, clc_urgency_, config_, YieldPluginConfig::enable_cooperative_behavior, EPSILON, external_objects_, first_time_stopped_to_prevent_collision_, basic_autonomy::waypoint_generation::get_nearest_point_index(), yield_plugin::get_trajectory_end_time(), last_traj_plan_committed_to_stopping_, nh_, timesteps_since_last_req_, carma_cooperative_perception::to_string(), update_traj_for_cooperative_behavior(), and update_traj_for_object().

Here is the call graph for this function:

◆ polynomial_calc()

double yield_plugin::YieldPlugin::polynomial_calc ( std::vector< double >  coeff,
double  x 
) const

calculate quintic polynomial equation for a given x

Parameters
coeffvector including polynomial coefficiencrs
xinput variable to the polynomial
Returns
value of polynomial for given input

Definition at line 1396 of file yield_plugin.cpp.

1397 {
1398 double result = 0;
1399 for (size_t i = 0; i < coeff.size(); i++)
1400 {
1401 double value = coeff.at(i) * pow(x, static_cast<int>(coeff.size() - 1 - i));
1402 result = result + value;
1403 }
1404 return result;
1405 }

References process_bag::i, and osm_transform::x.

Referenced by generate_JMT_trajectory().

Here is the caller graph for this function:

◆ polynomial_calc_d()

double yield_plugin::YieldPlugin::polynomial_calc_d ( std::vector< double >  coeff,
double  x 
) const

calculate derivative of quintic polynomial equation for a given x

Parameters
coeffvector including polynomial coefficiencrs
xinput variable to the polynomial
Returns
value of derivative polynomial for given input

Definition at line 1407 of file yield_plugin.cpp.

1408 {
1409 double result = 0;
1410 for (size_t i = 0; i < coeff.size()-1; i++)
1411 {
1412 double value = static_cast<int>(coeff.size() - 1 - i) * coeff.at(i) * pow(x, static_cast<int>(coeff.size() - 2 - i));
1413 result = result + value;
1414 }
1415 return result;
1416 }

References process_bag::i, and osm_transform::x.

Referenced by generate_JMT_trajectory().

Here is the caller graph for this function:

◆ set_external_objects()

void yield_plugin::YieldPlugin::set_external_objects ( const std::vector< carma_perception_msgs::msg::ExternalObject > &  object_list)

Setter for external objects with predictions in the environment.

Parameters
object_listThe object list.

Definition at line 1518 of file yield_plugin.cpp.

1519 {
1520 external_objects_ = object_list;
1521 }

References external_objects_.

◆ set_georeference_string()

void yield_plugin::YieldPlugin::set_georeference_string ( const std::string &  georeference)

Setter for map projection string to define lat/lon -> map conversion.

Parameters
georeferenceThe proj string defining the projection.

Definition at line 1509 of file yield_plugin.cpp.

1510 {
1511 if (georeference_ != georeference)
1512 {
1513 georeference_ = georeference;
1514 map_projector_ = std::make_shared<lanelet::projection::LocalFrameProjector>(georeference.c_str()); // Build projector from proj string
1515 }
1516 }

References georeference_, and map_projector_.

◆ set_incoming_request_info()

void yield_plugin::YieldPlugin::set_incoming_request_info ( std::vector< lanelet::BasicPoint2d >  req_trajectory,
double  req_speed,
double  req_planning_time,
double  req_timestamp 
)

set values for member variables related to cooperative behavior

Parameters
req_trajectoryrequested trajectory
req_speedspeed of requested cooperative behavior
req_planning_timeplanning time for the requested cooperative behavior
req_timestampthe mobility request time stamp

Definition at line 241 of file yield_plugin.cpp.

242 {
243 req_trajectory_points_ = req_trajectory;
244 req_target_speed_ = req_speed;
245 req_target_plan_time_ = req_planning_time;
246 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"req_target_plan_time_" << req_target_plan_time_);
247 req_timestamp_ = req_timestamp;
248 }
std::vector< lanelet::BasicPoint2d > req_trajectory_points_

References nh_, req_target_plan_time_, req_target_speed_, req_timestamp_, and req_trajectory_points_.

Referenced by mobilityrequest_cb().

Here is the caller graph for this function:

◆ update_route_llt_cache()

void yield_plugin::YieldPlugin::update_route_llt_cache ( )

Rebuild the cached route lanelet polygons and IDs from the current route. Should be called once whenever the route changes via setRouteCallback().

Definition at line 1523 of file yield_plugin.cpp.

1524 {
1525 if (!wm_->getRoute())
1526 {
1527 RCLCPP_WARN(nh_->get_logger(), "update_route_llt_cache called but route is not available");
1528 return;
1529 }
1530 route_llt_ids_.clear();
1531 route_llt_polygons_.clear();
1532 for (const auto& llt : wm_->getRoute()->shortestPath())
1533 {
1534 route_llt_ids_.insert(llt.id());
1535 route_llt_polygons_.push_back(llt);
1536 }
1537 }
std::set< lanelet::Id > route_llt_ids_

References nh_, route_llt_ids_, route_llt_polygons_, and wm_.

Referenced by get_earliest_collision_object_and_time().

Here is the caller graph for this function:

◆ update_traj_for_cooperative_behavior()

carma_planning_msgs::msg::TrajectoryPlan yield_plugin::YieldPlugin::update_traj_for_cooperative_behavior ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
double  current_speed 
)

update trajectory for yielding to an incoming cooperative behavior

Parameters
original_tporiginal trajectory plan
current_speedcurrent speed of the vehicle
Returns
updated trajectory for cooperative behavior

Definition at line 384 of file yield_plugin.cpp.

385 {
386 carma_planning_msgs::msg::TrajectoryPlan cooperative_trajectory;
387
388 double initial_pos = 0;
389 double goal_pos;
390 double initial_velocity = current_speed;
391 double goal_velocity = req_target_speed_;
392 double planning_time = req_target_plan_time_;
393
394 std::vector<lanelet::BasicPoint2d> host_traj_points = {};
395 for (size_t i=0; i<original_tp.trajectory_points.size(); i++)
396 {
397 lanelet::BasicPoint2d traj_point;
398 traj_point.x() = original_tp.trajectory_points.at(i).x;
399 traj_point.y() = original_tp.trajectory_points.at(i).y;
400 host_traj_points.push_back(traj_point);
401 }
402
403 std::vector<std::pair<int, lanelet::BasicPoint2d>> intersection_points = detect_trajectories_intersection(host_traj_points, req_trajectory_points_);
404 if (!intersection_points.empty())
405 {
406 lanelet::BasicPoint2d intersection_point = intersection_points[0].second;
407 double dx = original_tp.trajectory_points[0].x - intersection_point.x();
408 double dy = original_tp.trajectory_points[0].y - intersection_point.y();
409 // check if a digital_gap is available
410 double digital_gap = check_traj_for_digital_min_gap(original_tp);
411 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"digital_gap: " << digital_gap);
412 goal_pos = sqrt(dx*dx + dy*dy) - std::max(config_.minimum_safety_gap_in_meters, digital_gap);
413 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"Goal position (goal_pos): " << goal_pos);
414 double collision_time = req_timestamp_ + (intersection_points[0].first * ecef_traj_timestep_) - config_.safety_collision_time_gap_in_s;
415 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"req time stamp: " << req_timestamp_);
416 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"Collision time: " << collision_time);
417 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"intersection num: " << intersection_points[0].first);
418 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"Planning time: " << planning_time);
419 // calculate distance traveled from beginning of trajectory to collision point
420 double dx2 = intersection_point.x() - req_trajectory_points_[0].x();
421 double dy2 = intersection_point.y() - req_trajectory_points_[0].y();
422 // calculate incoming trajectory speed from time and distance between trajectory points
423 double incoming_trajectory_speed = sqrt(dx2*dx2 + dy2*dy2)/(intersection_points[0].first * ecef_traj_timestep_);
424 // calculate goal velocity from request trajectory
425 goal_velocity = std::min(goal_velocity, incoming_trajectory_speed);
426 double min_time = (initial_velocity - goal_velocity)/config_.yield_max_deceleration_in_ms2;
427
428 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"goal_velocity: " << goal_velocity);
429 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"incoming_trajectory_speed: " << incoming_trajectory_speed);
430
431 if (planning_time > min_time)
432 {
434 double original_max_speed = max_trajectory_speed(original_tp.trajectory_points, get_trajectory_end_time(original_tp));
435 cooperative_trajectory = generate_JMT_trajectory(original_tp, initial_pos, goal_pos, initial_velocity, goal_velocity, planning_time, original_max_speed);
436 }
437 else
438 {
440 RCLCPP_DEBUG(nh_->get_logger(),"The incoming requested trajectory is rejected, due to insufficient gap");
441 cooperative_trajectory = original_tp;
442 }
443
444 }
445 else
446 {
448 RCLCPP_DEBUG(nh_->get_logger(),"The incoming requested trajectory does not overlap with host vehicle's trajectory");
449 cooperative_trajectory = original_tp;
450 }
451
452 return cooperative_trajectory;
453 }
double check_traj_for_digital_min_gap(const carma_planning_msgs::msg::TrajectoryPlan &original_tp) const
checks trajectory for minimum gap associated with it from the road
std::vector< std::pair< int, lanelet::BasicPoint2d > > detect_trajectories_intersection(std::vector< lanelet::BasicPoint2d > self_trajectory, std::vector< lanelet::BasicPoint2d > incoming_trajectory) const
detect intersection point(s) of two trajectories
carma_planning_msgs::msg::TrajectoryPlan generate_JMT_trajectory(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, double initial_pos, double goal_pos, double initial_velocity, double goal_velocity, double planning_time, double original_max_speed)
generate a Jerk Minimizing Trajectory(JMT) with the provided start and end conditions
double yield_max_deceleration_in_ms2
double minimum_safety_gap_in_meters
double safety_collision_time_gap_in_s

References check_traj_for_digital_min_gap(), config_, cooperative_request_acceptable_, detect_trajectories_intersection(), visualize_xodr::dx, visualize_xodr::dy, ecef_traj_timestep_, generate_JMT_trajectory(), yield_plugin::get_trajectory_end_time(), process_bag::i, max_trajectory_speed(), YieldPluginConfig::minimum_safety_gap_in_meters, nh_, req_target_plan_time_, req_target_speed_, req_timestamp_, req_trajectory_points_, YieldPluginConfig::safety_collision_time_gap_in_s, and YieldPluginConfig::yield_max_deceleration_in_ms2.

Referenced by plan_trajectory_callback().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ update_traj_for_object()

carma_planning_msgs::msg::TrajectoryPlan yield_plugin::YieldPlugin::update_traj_for_object ( const carma_planning_msgs::msg::TrajectoryPlan &  original_tp,
const std::vector< carma_perception_msgs::msg::ExternalObject > &  external_objects,
double  initial_velocity 
)

trajectory is modified to safely avoid obstacles on the road

Parameters
original_tporiginal trajectory plan without object avoidance
current_speed_current speed of the vehicle

return modified trajectory plan

Definition at line 1264 of file yield_plugin.cpp.

1266 {
1267 if (original_tp.trajectory_points.size() < 2)
1268 {
1269 RCLCPP_WARN(nh_->get_logger(), "Yield plugin received less than 2 points in update_traj_for_object, returning unchanged...");
1270 return original_tp;
1271 }
1272
1273 // Get earliest collision object
1274 auto _t0_ect = std::chrono::steady_clock::now();
1275 const auto earliest_collision_obj_pair = get_earliest_collision_object_and_time(original_tp, external_objects);
1276 const double _ect_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_ect).count();
1277 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1278 "[timing] get_earliest_collision_object_and_time: " << _ect_ms << " ms");
1279
1280 if (!earliest_collision_obj_pair)
1281 {
1282 RCLCPP_DEBUG(nh_->get_logger(),"No collision detected, so trajectory not modified.");
1283 return original_tp;
1284 }
1285
1286 carma_perception_msgs::msg::ExternalObject earliest_collision_obj = earliest_collision_obj_pair.value().first;
1287 double earliest_collision_time_in_seconds = earliest_collision_obj_pair.value().second;
1288
1289 // Issue (https://github.com/usdot-fhwa-stol/carma-platform/issues/2155): If the yield_plugin can detect if the roadway object is moving along the route,
1290 // it is able to plan yielding much earlier and smoother using on_route_vehicle_collision_horizon_in_s.
1291
1292 const lanelet::BasicPoint2d vehicle_point(original_tp.trajectory_points[0].x,original_tp.trajectory_points[0].y);
1293 auto _rtp_t0_upd = std::chrono::steady_clock::now();
1294 const double vehicle_downtrack = wm_->routeTrackPos(vehicle_point).downtrack;
1295
1296 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"vehicle_downtrack: " << vehicle_downtrack);
1297
1298 RCLCPP_WARN_STREAM(nh_->get_logger(),"Collision Detected!");
1299
1300 const lanelet::BasicPoint2d object_point(earliest_collision_obj.pose.pose.position.x, earliest_collision_obj.pose.pose.position.y);
1301 const double object_downtrack = wm_->routeTrackPos(object_point).downtrack;
1302
1303 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"object_downtrack: " << object_downtrack);
1304 const double _rtp_ms_upd = std::chrono::duration<double, std::milli>(
1305 std::chrono::steady_clock::now() - _rtp_t0_upd).count();
1306 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1307 "[update_traj] routeTrackPos x2: " << _rtp_ms_upd << " ms");
1308
1309 const double object_downtrack_lead = std::max(0.0, object_downtrack - vehicle_downtrack);
1310 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"object_downtrack_lead: " << object_downtrack_lead);
1311
1312 // The vehicle's goal velocity of the yielding behavior is to match the velocity of the object along the trajectory.
1313 double goal_velocity = get_predicted_velocity_at_time(earliest_collision_obj.velocity.twist, original_tp, earliest_collision_time_in_seconds);
1314 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"object's speed along trajectory at collision: " << goal_velocity);
1315
1316 // roadway object position
1317 const double gap_time_until_min_gap_distance = std::max(0.0, object_downtrack_lead - config_.minimum_safety_gap_in_meters)/initial_velocity;
1318
1319 if (goal_velocity <= config_.obstacle_zero_speed_threshold_in_ms){
1320 RCLCPP_WARN_STREAM(nh_->get_logger(),"The obstacle is not moving, goal velocity is set to 0 from: " << goal_velocity);
1321 goal_velocity = 0.0;
1322 }
1323
1324 // determine the safety inter-vehicle gap based on speed
1325 double safety_gap = std::max(goal_velocity * gap_time_until_min_gap_distance, config_.minimum_safety_gap_in_meters);
1326 if (!std::isnormal(safety_gap))
1327 {
1328 RCLCPP_WARN_STREAM(rclcpp::get_logger("yield_plugin"),"Detected non-normal (nan, inf, etc.) safety_gap."
1329 "Making it desired safety gap configured at config_.minimum_safety_gap_in_meters: " << config_.minimum_safety_gap_in_meters);
1331 }
1333 {
1334 // externally_commanded_safety_gap is desired distance gap commanded from external sources
1335 // such as different plugin, map, or infrastructure depending on the use case
1336 auto _t0_gap = std::chrono::steady_clock::now();
1337 double externally_commanded_safety_gap = check_traj_for_digital_min_gap(original_tp);
1338 const double _gap_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_gap).count();
1339 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1340 "[timing] check_traj_for_digital_min_gap: " << _gap_ms << " ms");
1341 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"externally_commanded_safety_gap: " << externally_commanded_safety_gap);
1342 // if a digital gap is available, it is replaced as safety gap
1343 safety_gap = std::max(safety_gap, externally_commanded_safety_gap);
1344 }
1345
1346 const double goal_pos = std::max(0.0, object_downtrack_lead - safety_gap - config_.vehicle_length);
1347 const double initial_pos = 0.0; //relative initial position (first trajectory point)
1348 const double original_max_speed = max_trajectory_speed(original_tp.trajectory_points, earliest_collision_time_in_seconds);
1349 const double delta_v_max = fabs(goal_velocity - original_max_speed);
1350 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"delta_v_max: " << delta_v_max << ", safety_gap: " << safety_gap);
1351
1352 const double time_required_for_comfortable_decel_in_s = config_.acceleration_adjustment_factor * 2 * goal_pos / delta_v_max;
1353 const double min_time_required_for_comfortable_decel_in_s = delta_v_max / config_.yield_max_deceleration_in_ms2;
1354
1355 // planning time for object avoidance
1356 double planning_time_in_s = std::max({config_.min_obj_avoidance_plan_time_in_s, time_required_for_comfortable_decel_in_s, min_time_required_for_comfortable_decel_in_s});
1357 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"time_required_for_comfortable_decel_in_s: " << time_required_for_comfortable_decel_in_s << ", min_time_required_for_comfortable_decel_in_s: " << min_time_required_for_comfortable_decel_in_s);
1358
1359 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"Object avoidance planning time: " << planning_time_in_s);
1360
1361 auto _t0_jmt = std::chrono::steady_clock::now();
1362 auto jmt_trajectory = generate_JMT_trajectory(original_tp,
1363 initial_pos, goal_pos, initial_velocity, goal_velocity,
1364 planning_time_in_s, original_max_speed);
1365 const double _jmt_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_jmt).count();
1366 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1367 "[timing] generate_JMT_trajectory: " << _jmt_ms << " ms");
1368
1369 // If expected to stop to prevent collision, we should save this trajectory to commit to it
1370 if (!last_traj_plan_committed_to_stopping_.has_value() &&
1371 goal_velocity < EPSILON &&
1372 earliest_collision_time_in_seconds - nh_->now().seconds()
1374 {
1376 }
1377 return jmt_trajectory;
1378 }
double get_predicted_velocity_at_time(const geometry_msgs::msg::Twist &object_velocity_in_map_frame, const carma_planning_msgs::msg::TrajectoryPlan &original_tp, double timestamp_in_sec_to_predict)
Given the object velocity in map frame with x,y components, this function returns the projected veloc...
std::optional< std::pair< carma_perception_msgs::msg::ExternalObject, double > > get_earliest_collision_object_and_time(const carma_planning_msgs::msg::TrajectoryPlan &original_tp, const std::vector< carma_perception_msgs::msg::ExternalObject > &external_objects)
Return the earliest collision object and time of collision pair from the given trajectory and list of...
double acceleration_adjustment_factor
double time_horizon_until_collision_to_commit_to_stop_in_s

References YieldPluginConfig::acceleration_adjustment_factor, check_traj_for_digital_min_gap(), config_, YieldPluginConfig::enable_adjustable_gap, EPSILON, generate_JMT_trajectory(), get_earliest_collision_object_and_time(), get_predicted_velocity_at_time(), last_traj_plan_committed_to_stopping_, max_trajectory_speed(), YieldPluginConfig::min_obj_avoidance_plan_time_in_s, YieldPluginConfig::minimum_safety_gap_in_meters, nh_, YieldPluginConfig::obstacle_zero_speed_threshold_in_ms, YieldPluginConfig::time_horizon_until_collision_to_commit_to_stop_in_s, YieldPluginConfig::vehicle_length, wm_, and YieldPluginConfig::yield_max_deceleration_in_ms2.

Referenced by plan_trajectory_callback().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ clc_urgency_

int yield_plugin::YieldPlugin::clc_urgency_ = 0
private

Definition at line 377 of file yield_plugin.hpp.

Referenced by mobilityrequest_cb(), and plan_trajectory_callback().

◆ config_

◆ consecutive_clearance_count_for_obstacles_

std::unordered_map<uint32_t, int> yield_plugin::YieldPlugin::consecutive_clearance_count_for_obstacles_
private

◆ cooperative_request_acceptable_

bool yield_plugin::YieldPlugin::cooperative_request_acceptable_ = false
private

Definition at line 369 of file yield_plugin.hpp.

Referenced by mobilityrequest_cb(), and update_traj_for_cooperative_behavior().

◆ current_speed_

double yield_plugin::YieldPlugin::current_speed_
private

Definition at line 387 of file yield_plugin.hpp.

◆ ecef_traj_timestep_

double yield_plugin::YieldPlugin::ecef_traj_timestep_ = 0.1
private

Definition at line 384 of file yield_plugin.hpp.

Referenced by update_traj_for_cooperative_behavior().

◆ external_objects_

std::vector<carma_perception_msgs::msg::ExternalObject> yield_plugin::YieldPlugin::external_objects_
private

Definition at line 366 of file yield_plugin.hpp.

Referenced by plan_trajectory_callback(), and set_external_objects().

◆ first_time_stopped_to_prevent_collision_

std::optional<rclcpp::Time> yield_plugin::YieldPlugin::first_time_stopped_to_prevent_collision_ = std::nullopt
private

Definition at line 380 of file yield_plugin.hpp.

Referenced by plan_trajectory_callback().

◆ georeference_

std::string yield_plugin::YieldPlugin::georeference_ {""}
private

Definition at line 391 of file yield_plugin.hpp.

Referenced by set_georeference_string().

◆ host_bsm_id_

std::string yield_plugin::YieldPlugin::host_bsm_id_
private

Definition at line 389 of file yield_plugin.hpp.

Referenced by bsm_cb(), and compose_mobility_response().

◆ host_vehicle_size

geometry_msgs::msg::Vector3 yield_plugin::YieldPlugin::host_vehicle_size
private

Definition at line 386 of file yield_plugin.hpp.

◆ last_speed_

std::optional<double> yield_plugin::YieldPlugin::last_speed_ = std::nullopt
private

Definition at line 378 of file yield_plugin.hpp.

Referenced by generate_JMT_trajectory().

◆ last_speed_time_

std::optional<rclcpp::Time> yield_plugin::YieldPlugin::last_speed_time_ = std::nullopt
private

Definition at line 379 of file yield_plugin.hpp.

Referenced by generate_JMT_trajectory().

◆ last_traj_plan_committed_to_stopping_

std::optional< carma_planning_msgs::msg::TrajectoryPlan> yield_plugin::YieldPlugin::last_traj_plan_committed_to_stopping_ = std::nullopt
private

Definition at line 382 of file yield_plugin.hpp.

Referenced by plan_trajectory_callback(), and update_traj_for_object().

◆ lc_status_publisher_

LaneChangeStatusCB yield_plugin::YieldPlugin::lc_status_publisher_
private

Definition at line 360 of file yield_plugin.hpp.

Referenced by mobilityrequest_cb().

◆ map_projector_

std::shared_ptr<lanelet::projection::LocalFrameProjector> yield_plugin::YieldPlugin::map_projector_
private

◆ mobility_response_publisher_

MobilityResponseCB yield_plugin::YieldPlugin::mobility_response_publisher_
private

Definition at line 359 of file yield_plugin.hpp.

Referenced by mobilityrequest_cb().

◆ nh_

◆ previous_llt_id_

lanelet::Id yield_plugin::YieldPlugin::previous_llt_id_
private

Definition at line 365 of file yield_plugin.hpp.

◆ req_target_plan_time_

double yield_plugin::YieldPlugin::req_target_plan_time_ = 0
private

◆ req_target_speed_

double yield_plugin::YieldPlugin::req_target_speed_ = 0
private

◆ req_timestamp_

double yield_plugin::YieldPlugin::req_timestamp_ = 0
private

◆ req_trajectory_points_

std::vector<lanelet::BasicPoint2d> yield_plugin::YieldPlugin::req_trajectory_points_
private

◆ route_llt_ids_

std::set<lanelet::Id> yield_plugin::YieldPlugin::route_llt_ids_
private

Definition at line 362 of file yield_plugin.hpp.

Referenced by update_route_llt_cache().

◆ route_llt_polygons_

std::vector<lanelet::ConstLanelet> yield_plugin::YieldPlugin::route_llt_polygons_
private

◆ timesteps_since_last_req_

int yield_plugin::YieldPlugin::timesteps_since_last_req_ = 0
private

Definition at line 376 of file yield_plugin.hpp.

Referenced by mobilityrequest_cb(), and plan_trajectory_callback().

◆ wm_


The documentation for this class was generated from the following files: