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 50 of file yield_plugin.cpp.

53 : nh_(nh), wm_(wm), config_(config),mobility_response_publisher_(mobility_response_publisher), lc_status_publisher_(lc_status_publisher)
54 {
55 basic_autonomy::set_logger(nh_->get_logger().get_child("basic_autonomy"));
56 }
LaneChangeStatusCB lc_status_publisher_
MobilityResponseCB mobility_response_publisher_
std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode > nh_
YieldPluginConfig config_
carma_wm::WorldModelConstPtr wm_
void set_logger(rclcpp::Logger logger)
Replace the module-level logger used by all basic_autonomy functions.
Definition: log.cpp:33

References nh_, and basic_autonomy::set_logger().

Here is the call graph for this function:

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 251 of file yield_plugin.cpp.

252 {
253 carma_v2x_msgs::msg::BSMCoreData bsm_core_ = msg->core_data;
254 host_bsm_id_ = bsmIDtoString(bsm_core_);
255 }
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 1442 of file yield_plugin.cpp.

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

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 138 of file yield_plugin.cpp.

139 {
140 carma_v2x_msgs::msg::MobilityResponse out_mobility_response;
141 out_mobility_response.m_header.sender_id = config_.vehicle_id;
142 out_mobility_response.m_header.recipient_id = resp_recipient_id;
143 out_mobility_response.m_header.sender_bsm_id = host_bsm_id_;
144 out_mobility_response.m_header.plan_id = req_plan_id;
145 out_mobility_response.m_header.timestamp = nh_->now().seconds()*1000;
146
147
149 {
150 out_mobility_response.is_accepted = true;
151 }
152 else out_mobility_response.is_accepted = false;
153
154 return out_mobility_response;
155 }
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 99 of file yield_plugin.cpp.

100 {
101 carma_planning_msgs::msg::TrajectoryPlan trajectory_plan;
102 std::vector<lanelet::BasicPoint2d> map_points;
103
104 lanelet::BasicPoint2d first_point = ecef_to_map_point(ecef_trajectory.location);
105
106 map_points.push_back(first_point);
107 auto curr_point = ecef_trajectory.location;
108
109 for (size_t i = 0; i<ecef_trajectory.offsets.size(); i++)
110 {
111 lanelet::BasicPoint2d offset_point;
112 curr_point.ecef_x += ecef_trajectory.offsets.at(i).offset_x;
113 curr_point.ecef_y += ecef_trajectory.offsets.at(i).offset_y;
114 curr_point.ecef_z += ecef_trajectory.offsets.at(i).offset_z;
115
116 offset_point = ecef_to_map_point(curr_point);
117
118 map_points.push_back(offset_point);
119 }
120
121 return map_points;
122 }
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 78 of file yield_plugin.cpp.

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

125 {
126
127 if (!map_projector_) {
128 throw std::invalid_argument("No map projector available for ecef conversion");
129 }
130
131 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);
132
133 return lanelet::traits::to2D(map_point);
134 }
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 911 of file yield_plugin.cpp.

914 {
915 for (size_t j = 0; j < predictions.size(); j += stride) {
916 const lanelet::BasicPoint2d point(predictions[j].predicted_position.position.x,
917 predictions[j].predicted_position.position.y);
918 for (const auto& llt : route_llt_polygons_) {
919 if (boost::geometry::within(point, llt.polygon2d())) {
920 return {true, static_cast<int>(j)};
921 }
922 }
923 if (object_has_zero_speed) break;
924 }
925 return {false, 0};
926 }
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 470 of file yield_plugin.cpp.

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

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

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

932 {
933 if (!cuda_is_available())
934 return get_collision_times_concurrently_cpu(original_tp, external_objects, original_tp_max_speed);
935 return get_collision_times_concurrently_cuda(original_tp, external_objects, original_tp_max_speed);
936 }
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 938 of file yield_plugin.cpp.

942 {
943 std::unordered_map<uint32_t, std::future<std::optional<rclcpp::Time>>> futures;
944 std::unordered_map<uint32_t, rclcpp::Time> collision_times;
945 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
946 "[CPU] Launching " << external_objects.size() << " async get_collision_time tasks");
947 std::vector<std::thread> threads;
948 threads.reserve(external_objects.size());
949 for (const auto& object : external_objects) {
950 std::packaged_task<std::optional<rclcpp::Time>()> task(
951 [this, &original_tp, &object, &original_tp_max_speed] {
952 return get_collision_time(original_tp, object, original_tp_max_speed);
953 });
954 futures[object.id] = task.get_future();
955 threads.emplace_back(std::move(task));
956 }
957 for (auto& t : threads) t.join();
958 for (const auto& object : external_objects) {
959 if (const auto collision_time{futures.at(object.id).get()}) {
960 collision_times[object.id] = collision_time.value();
961 }
962 }
963 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
964 "[CPU] Done — " << collision_times.size() << " collision(s) confirmed");
965 return collision_times;
966 }
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(), and basic_autonomy::get_logger().

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 968 of file yield_plugin.cpp.

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

1183 {
1184 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"), "ExternalObjects size: " << external_objects.size());
1185
1186 if (!wm_->getRoute())
1187 {
1188 RCLCPP_WARN(nh_->get_logger(), "Yield plugin was not able to analyze collision since route is not available! Please check if route is set");
1189 return std::nullopt;
1190 }
1191
1192 // Populate route_llt_polygons_ if empty but route is actually available.
1193 if (route_llt_polygons_.empty())
1194 {
1196 }
1197
1198 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"External Object List (external_objects) size: " << external_objects.size());
1199 const double original_max_speed = max_trajectory_speed(original_tp.trajectory_points, get_trajectory_end_time(original_tp));
1200 auto _t0_conc = std::chrono::steady_clock::now();
1201 std::unordered_map<uint32_t, rclcpp::Time> collision_times = get_collision_times_concurrently(original_tp,external_objects, original_max_speed);
1202 const double _conc_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - _t0_conc).count();
1203 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("yield_plugin"),
1204 "[timing] get_collision_times_concurrently: " << _conc_ms << " ms");
1205
1206 if (collision_times.empty()) { return std::nullopt; }
1207
1208 const auto earliest_colliding_object_id{std::min_element(
1209 std::cbegin(collision_times), std::cend(collision_times),
1210 [](const auto & a, const auto & b){ return a.second < b.second; })->first};
1211
1212 const auto earliest_colliding_object{std::find_if(
1213 std::cbegin(external_objects), std::cend(external_objects),
1214 [&earliest_colliding_object_id](const auto & object) { return object.id == earliest_colliding_object_id; })};
1215
1216 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"earliest object x: " << earliest_colliding_object->velocity.twist.linear.x
1217 << ", y: " << earliest_colliding_object->velocity.twist.linear.y);
1218 return std::make_pair(*earliest_colliding_object, collision_times.at(earliest_colliding_object_id).seconds());
1219
1220 }
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(), basic_autonomy::get_logger(), 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 1222 of file yield_plugin.cpp.

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

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 1382 of file yield_plugin.cpp.

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

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 780 of file yield_plugin.cpp.

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

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

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 158 of file yield_plugin.cpp.

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

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

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

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 1408 of file yield_plugin.cpp.

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

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 1519 of file yield_plugin.cpp.

1520 {
1521 external_objects_ = object_list;
1522 }

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 1510 of file yield_plugin.cpp.

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

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 242 of file yield_plugin.cpp.

243 {
244 req_trajectory_points_ = req_trajectory;
245 req_target_speed_ = req_speed;
246 req_target_plan_time_ = req_planning_time;
247 RCLCPP_DEBUG_STREAM(nh_->get_logger(),"req_target_plan_time_" << req_target_plan_time_);
248 req_timestamp_ = req_timestamp;
249 }
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 1524 of file yield_plugin.cpp.

1525 {
1526 if (!wm_->getRoute())
1527 {
1528 RCLCPP_WARN(nh_->get_logger(), "update_route_llt_cache called but route is not available");
1529 return;
1530 }
1531 route_llt_ids_.clear();
1532 route_llt_polygons_.clear();
1533 for (const auto& llt : wm_->getRoute()->shortestPath())
1534 {
1535 route_llt_ids_.insert(llt.id());
1536 route_llt_polygons_.push_back(llt);
1537 }
1538 }
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 385 of file yield_plugin.cpp.

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

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