diff --git a/planning/behavior_velocity_intersection_module/CMakeLists.txt b/planning/behavior_velocity_intersection_module/CMakeLists.txt index 9e7eb196cd0c1..07847a08c1209 100644 --- a/planning/behavior_velocity_intersection_module/CMakeLists.txt +++ b/planning/behavior_velocity_intersection_module/CMakeLists.txt @@ -8,11 +8,18 @@ pluginlib_export_plugin_description_file(behavior_velocity_planner plugins.xml) find_package(OpenCV REQUIRED) ament_auto_add_library(${PROJECT_NAME} SHARED - src/debug.cpp src/manager.cpp + src/util.cpp src/scene_intersection.cpp + src/intersection_lanelets.cpp + src/object_manager.cpp + src/decision_result.cpp + src/scene_intersection_prepare_data.cpp + src/scene_intersection_stuck.cpp + src/scene_intersection_occlusion.cpp + src/scene_intersection_collision.cpp src/scene_merge_from_private_road.cpp - src/util.cpp + src/debug.cpp ) target_link_libraries(${PROJECT_NAME} diff --git a/planning/behavior_velocity_intersection_module/README.md b/planning/behavior_velocity_intersection_module/README.md index 58c2ce59edd48..4e13a1fa169de 100644 --- a/planning/behavior_velocity_intersection_module/README.md +++ b/planning/behavior_velocity_intersection_module/README.md @@ -88,7 +88,7 @@ To precisely calculate stop positions, the path is interpolated at the certain i - closest_idx denotes the path point index which is closest to ego position. - first_attention_stopline denotes the first path point where ego footprint intersects with the attention_area. -- If a stopline is associated with the intersection lane on the map, that line is used as the default_stopline for collision detection. Otherwise the point which is `common.default_stopline_margin` meters behind first_attention_stopline is defined as the default_stopline instead. +- If a stopline is associated with the intersection lane on the map, that line is used as default_stopline for collision detection. Otherwise the point which is `common.default_stopline_margin` meters behind first_attention_stopline is defined as default_stopline instead. - occlusion_peeking_stopline is a bit ahead of first_attention_stopline as described later. - occlusion_wo_tl_pass_judge_line is the first position where ego footprint intersects with the centerline of the first attention_area lane. @@ -113,8 +113,8 @@ There are several behaviors depending on the scene. | Safe | Ego detected no occlusion and collision | Ego passes the intersection | | StuckStop | The exit of the intersection is blocked by traffic jam | Ego stops before the intersection or the boundary of attention area | | YieldStuck | Another vehicle stops to yield ego | Ego stops before the intersection or the boundary of attention area | -| NonOccludedCollisionStop | Ego detects no occlusion but detects collision | Ego stops at the default_stop_line | -| FirstWaitBeforeOcclusion | Ego detected occlusion when entering the intersection | Ego stops at the default_stop_line at first | +| NonOccludedCollisionStop | Ego detects no occlusion but detects collision | Ego stops at default_stopline | +| FirstWaitBeforeOcclusion | Ego detected occlusion when entering the intersection | Ego stops at default_stopline at first | | PeekingTowardOcclusion | Ego detected occlusion and but no collision within the FOV (after FirstWaitBeforeOcclusion) | Ego approaches the boundary of the attention area slowly | | OccludedCollisionStop | Ego detected both occlusion and collision (after FirstWaitBeforeOcclusion) | Ego stops immediately | | FullyPrioritized | Ego is fully prioritized by the RED/Arrow signal | Ego only cares vehicles still running inside the intersection. Occlusion is ignored | @@ -217,9 +217,16 @@ ros2 run behavior_velocity_intersection_module ttc.py --lane_id ![ego ttc profile](./docs/ttc.gif) +### about use_upstream_velocity flag + +There are some use cases where ego should check collision before entering the intersection considering the temporal stop by walkway/crosswalk module around the exit of the intersection, because their stop position can be inside the intersection and it could bother upcoming vehicles. By setting the flag `collision_detection.velocity_profile.use_upstream` to true and running the walkway/crosswalk module prior to this module, ego velocity profile is calculated considering their velocity and stop positions. + +As illustrated in below figure if upstream module inserted a stopline, ego position profile will remain there for the infinite time, thus it leads to the judgement that ego cannot exit the intersection during the interval [$t$ - `collision_detection.collision_start_margin_time`, $t$ + `collision_detection.collision_end_margin_time`]. In this way this feature considers possible collision for the infinite time if stoplines exist ahead of ego position (practically the prediction horizon is limited so the collision check horizon is bounded). +![upstream_velocity](./docs/upstream-velocity.drawio.svg) + ## Occlusion detection -If the flag `occlusion.enable` is true this module checks if there is sufficient field of view (FOV) on the attention area up to `occlusion.occlusion_attention_area_length`. If FOV is not clear enough ego first makes a brief stop at the default stop line for `occlusion.temporal_stop_time_before_peeking`, and then slowly creeps toward occlusion_peeking_stop_line. If `occlusion.creep_during_peeking.enable` is true `occlusion.creep_during_peeking.creep_velocity` is inserted up to occlusion_peeking_stop_line. Otherwise only stop line is inserted. +If the flag `occlusion.enable` is true this module checks if there is sufficient field of view (FOV) on the attention area up to `occlusion.occlusion_attention_area_length`. If FOV is not clear enough ego first makes a brief stop at default_stopline for `occlusion.temporal_stop_time_before_peeking`, and then slowly creeps toward occlusion_peeking_stopline. If `occlusion.creep_during_peeking.enable` is true `occlusion.creep_during_peeking.creep_velocity` is inserted up to occlusion_peeking_stopline. Otherwise only stop line is inserted. During the creeping if collision is detected this module inserts a stop line in front of ego immediately, and if the FOV gets sufficiently clear the intersection_occlusion wall will disappear. If occlusion is cleared and no collision is detected ego will pass the intersection. @@ -241,7 +248,7 @@ The remaining time is visualized on the intersection_occlusion virtual wall. ### Occlusion handling at intersection without traffic light -At intersection without traffic light, if occlusion is detected, ego makes a brief stop at the default_stopline and first_attention_stopline respectively. After stopping at the first_attention_area_stopline this module inserts `occlusion.absence_traffic_light.creep_velocity` velocity between ego and occlusion_wo_tl_pass_judge_line while occlusion is not cleared. If collision is detected, ego immediately stops. Once the occlusion is cleared or ego has passed occlusion_wo_tl_pass_judge_line this module does not detect collision and occlusion because ego footprint is already inside the intersection. +At intersection without traffic light, if occlusion is detected, ego makes a brief stop at default_stopline and first_attention_stopline respectively. After stopping at the first_attention_area_stopline this module inserts `occlusion.absence_traffic_light.creep_velocity` velocity between ego and occlusion_wo_tl_pass_judge_line while occlusion is not cleared. If collision is detected, ego immediately stops. Once the occlusion is cleared or ego has passed occlusion_wo_tl_pass_judge_line this module does not detect collision and occlusion because ego footprint is already inside the intersection. ![occlusion_detection](./docs/occlusion-without-tl.drawio.svg) @@ -263,7 +270,7 @@ TTC parameter varies depending on the traffic light color/shape as follows. ### yield on GREEN -If the traffic light color changed to GREEN and ego approached the entry of the intersection lane within the distance `collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start` and there is any object whose distance to its stopline is less than `collision_detection.yield_on_green_traffic_light.object_dist_to_stopline`, this module commands to stop for the duration of `collision_detection.yield_on_green_traffic_light.duration` at the default_stopline. +If the traffic light color changed to GREEN and ego approached the entry of the intersection lane within the distance `collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start` and there is any object whose distance to its stopline is less than `collision_detection.yield_on_green_traffic_light.object_dist_to_stopline`, this module commands to stop for the duration of `collision_detection.yield_on_green_traffic_light.duration` at default_stopline. ### skip on AMBER @@ -281,18 +288,49 @@ When the traffic light color/shape is RED/Arrow, occlusion detection is skipped. ## Pass Judge Line -To avoid sudden braking, if deceleration and jerk more than the threshold (`common.max_accel` and `common.max_jerk`) is required to stop at first_attention_stopline, this module does not command to stop once it passed the default_stopline position. +Generally it is not tolerable for vehicles that have lower traffic priority to stop in the middle of the unprotected area in intersections, and they need to stop at the stop line beforehand if there will be any risk of collision, which introduces two requirements: + +1. The vehicle must start braking before the boundary of the unprotected area at least by the braking distance if it is supposed to stop +2. The vehicle must recognize upcoming vehicles and check safety beforehand with enough braking distance margin if it is supposed to go + 1. And the SAFE decision must be absolutely certain and remain to be valid for the future horizon so that the safety condition will be always satisfied while ego is driving inside the unprotected area. +3. (TODO): Since it is almost impossible to make perfectly safe decision beforehand given the limited detection range/velocity tracking performance, intersection module should plan risk-evasive acceleration velocity profile AND/OR relax lateral acceleration limit while ego is driving inside the unprotected area, if the safety decision is "betrayed" later due to the following reasons: + 1. The situation _turned out to be dangerous_ later, mainly because velocity tracking was underestimated or the object accelerated beyond TTC margin + 2. The situation _turned dangerous_ later, mainly because the object is suddenly detected out of nowhere + +The position which is before the boundary of unprotected area by the braking distance which is obtained by + +$$ +\dfrac{v_{\mathrm{ego}}^{2}}{2a_{\mathrm{max}}} + v_{\mathrm{ego}} * t_{\mathrm{delay}} +$$ + +is called pass_judge_line, and safety decision must be made before ego passes this position because ego does not stop anymore. + +1st_pass_judge_line is before the first upcoming lane, and at intersections with multiple upcoming lanes, 2nd_pass_judge_line is defined as the position which is before the centerline of the first attention lane by the braking distance. 1st/2nd_pass_judge_line are illustrated in the following figure. + +![pass-judge-line](./docs/pass-judge-line.drawio.svg) + +Intersection module will command to GO if + +- ego is over default_stopline(or `common.enable_pass_judge_before_default_stopline` is true) AND +- ego is over 1st_pass judge line AND +- ego judged SAFE previously AND +- (ego is over 2nd_pass_judge_line OR ego is between 1st and 2nd pass_judge_line but most probable collision is expected to happen in the 1st attention lane) + +because it is expected to stop or continue stop decision if + +1. ego is before default_stopline && `common.enable_pass_judge_before_default_stopline` is false OR + 1. reason: default_stopline is defined on the map and should be respected +2. ego is before 1st_pass_judge_line OR + 1. reason: it has enough braking distance margin +3. ego judged UNSAFE previously + 1. reason: ego is now trying to stop and should continue stop decision if collision is detected in later calculation +4. (ego is between 1st and 2nd pass_judge_line and the most probable collision is expected to happen in the 2nd attention lane) -If ego passed pass_judge_line, then ego does not stop anymore. If ego passed pass_judge_line while ego is stopping for dangerous decision, then ego stops while the situation is judged as dangerous. Once the judgement turned safe, ego restarts and does not stop anymore. +For the 3rd condition, it is possible that ego stops with some overshoot to the unprotected area while it is trying to stop for collision detection, because ego should keep stop decision while UNSAFE decision is made even if it passed 1st_pass_judge_line during deceleration. -The position of the pass judge line depends on the occlusion detection configuration and the existence of the associated traffic light of the intersection lane. +For the 4th condition, at intersections with 2nd attention lane, even if ego is over the 1st pass_judge_line, still intersection module commands to stop if the most probable collision is expected to happen in the 2nd attention lane. -- If `occlusion.enable` is false, the pass judge line before the `first_attention_stopline` by the braking distance $v_{ego}^{2} / 2a_{max}$. -- If `occlusion.enable` is true and: - - if there are associated traffic lights, the pass judge line is at the `occlusion_peeking_stopline` in order to continue peeking/collision detection while occlusion is detected. - - if there are no associated traffic lights and: - - if occlusion is detected, pass judge line is at the `occlusion_wo_tl_pass_judge_line` to continue peeking. - - if occlusion is not detected, pass judge line is at the same place at the case where `occlusion.enable` is false. +Also if `occlusion.enable` is true, the position of 1st_pass_judge line changes to occlusion_peeking_stopline if ego passed the original 1st_pass_judge_line position while ego is peeking. Otherwise ego could inadvertently judge that it passed 1st_pass_judge during peeking and then abort peeking. ## Data Structure @@ -375,17 +413,18 @@ entity TargetObject { ### common -| Parameter | Type | Description | -| --------------------------------- | ------ | ------------------------------------------------------------------------ | -| `.attention_area_length` | double | [m] range for object detection | -| `.attention_area_margin` | double | [m] margin for expanding attention area width | -| `.attention_area_angle_threshold` | double | [rad] threshold of angle difference between the detected object and lane | -| `.use_intersection_area` | bool | [-] flag to use intersection_area for collision detection | -| `.default_stopline_margin` | double | [m] margin before_stop_line | -| `.stopline_overshoot_margin` | double | [m] margin for the overshoot from stopline | -| `.max_accel` | double | [m/ss] max acceleration for stop | -| `.max_jerk` | double | [m/sss] max jerk for stop | -| `.delay_response_time` | double | [s] action delay before stop | +| Parameter | Type | Description | +| -------------------------------------------- | ------ | -------------------------------------------------------------------------------- | +| `.attention_area_length` | double | [m] range for object detection | +| `.attention_area_margin` | double | [m] margin for expanding attention area width | +| `.attention_area_angle_threshold` | double | [rad] threshold of angle difference between the detected object and lane | +| `.use_intersection_area` | bool | [-] flag to use intersection_area for collision detection | +| `.default_stopline_margin` | double | [m] margin before_stop_line | +| `.stopline_overshoot_margin` | double | [m] margin for the overshoot from stopline | +| `.max_accel` | double | [m/ss] max acceleration for stop | +| `.max_jerk` | double | [m/sss] max jerk for stop | +| `.delay_response_time` | double | [s] action delay before stop | +| `.enable_pass_judge_before_default_stopline` | bool | [-] flag not to stop before default_stopline even if ego is over pass_judge_line | ### stuck_vehicle/yield_stuck @@ -430,7 +469,7 @@ entity TargetObject { | `.possible_object_bbox` | [double] | [m] minimum bounding box size for checking if occlusion polygon is small enough | | `.ignore_parked_vehicle_speed_threshold` | double | [m/s] velocity threshold for checking parked vehicle | | `.occlusion_detection_hold_time` | double | [s] hold time of occlusion detection | -| `.temporal_stop_time_before_peeking` | double | [s] temporal stop duration at the default_stop_line before starting peeking | +| `.temporal_stop_time_before_peeking` | double | [s] temporal stop duration at default_stopline before starting peeking | | `.temporal_stop_before_attention_area` | bool | [-] flag to temporarily stop at first_attention_stopline before peeking into attention_area | | `.creep_velocity_without_traffic_light` | double | [m/s] creep velocity to occlusion_wo_tl_pass_judge_line | | `.static_occlusion_with_traffic_light_timeout` | double | [s] the timeout duration for ignoring static occlusion at intersection with traffic light | diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 997addd48d7f8..5ff420edc1c4d 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -12,8 +12,18 @@ max_accel: -2.8 max_jerk: -5.0 delay_response_time: 0.5 + enable_pass_judge_before_default_stopline: false stuck_vehicle: + target_type: + car: true + bus: true + truck: true + trailer: true + motorcycle: false + bicycle: false + unknown: false + turn_direction: left: true right: true @@ -23,10 +33,17 @@ stuck_vehicle_velocity_threshold: 0.833 # enable_front_car_decel_prediction: false # assumed_front_car_decel: 1.0 - timeout_private_area: 3.0 - enable_private_area_stuck_disregard: false + disable_against_private_lane: true yield_stuck: + target_type: + car: true + bus: true + truck: true + trailer: true + motorcycle: false + bicycle: false + unknown: false turn_direction: left: true right: true @@ -37,7 +54,14 @@ consider_wrong_direction_vehicle: false collision_detection_hold_time: 0.5 min_predicted_path_confidence: 0.05 - keep_detection_velocity_threshold: 0.833 + target_type: + car: true + bus: true + truck: true + trailer: true + motorcycle: true + bicycle: true + unknown: false velocity_profile: use_upstream: true minimum_upstream_velocity: 0.01 @@ -57,9 +81,13 @@ duration: 3.0 object_dist_to_stopline: 10.0 ignore_on_amber_traffic_light: - object_expected_deceleration: 2.0 + object_expected_deceleration: + car: 2.0 + bike: 5.0 ignore_on_red_traffic_light: object_margin_to_path: 2.0 + avoid_collision_by_acceleration: + object_time_margin_to_collision_point: 4.0 occlusion: enable: false @@ -73,7 +101,7 @@ enable: false creep_velocity: 0.8333 peeking_offset: -0.5 - occlusion_required_clearance_distance: 55 + occlusion_required_clearance_distance: 55.0 possible_object_bbox: [1.5, 2.5] ignore_parked_vehicle_speed_threshold: 0.8333 occlusion_detection_hold_time: 1.5 diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg index 51e926fe266d6..f3e2f093c2639 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg @@ -8,7 +8,7 @@ width="3723px" height="2401px" viewBox="-0.5 -0.5 3723 2401" - content="<mxfile host="Electron" modified="2023-10-03T02:44:21.616Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="wkqIuFIE5UyEHdVd8sjR" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + content="<mxfile host="Electron" modified="2024-02-15T03:41:38.499Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="Ur7f05zYPc_xPkzcmq8s" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > @@ -1426,6 +1426,32 @@ stroke-miterlimit="10" pointer-events="none" /> + + + + + + diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg index 25a7c6b519f96..deee1d6bba6ec 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg @@ -5,10 +5,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - width="3727px" - height="2651px" - viewBox="-0.5 -0.5 3727 2651" - content="<mxfile host="Electron" modified="2023-10-03T03:32:56.314Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="aYRnTHIqVcSkLQHKGcXP" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + width="3730px" + height="2652px" + viewBox="-0.5 -0.5 3730 2652" + content="<mxfile host="Electron" modified="2024-02-15T03:44:17.561Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="bGatNcOdVXCYYNQ6_AUu" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > @@ -240,9 +240,9 @@ - + - + - + @@ -711,7 +711,7 @@ stroke-miterlimit="10" pointer-events="none" /> - + @@ -778,7 +778,7 @@ - + - + - + - + @@ -1183,7 +1183,7 @@ conflicting lanes - + - + - + @@ -1573,7 +1573,7 @@ stroke-miterlimit="10" pointer-events="none" /> - + - + + + + + + + + + + + + + diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg index 640eba618fa49..c5a4f4c8409b6 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg @@ -8,7 +8,7 @@ width="3390px" height="1412px" viewBox="-0.5 -0.5 3390 1412" - content="<mxfile host="Electron" modified="2023-10-03T04:48:10.725Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="02kGXKyoYgRU_RdR3ghD" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + content="<mxfile host="Electron" modified="2024-02-15T03:41:56.915Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="Xj5KyhPcwUxiMEO_x21H" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > @@ -85,7 +85,7 @@ - + + + + + + + diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg index 94b63a97608ef..6e142c8faabc2 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg @@ -6,28 +6,29 @@ xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="3707px" - height="3570px" - viewBox="-0.5 -0.5 3707 3570" - content="<mxfile host="Electron" modified="2023-11-15T07:03:18.394Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="8yBIGjeANrMFIPyocWYk" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + height="3650px" + viewBox="-0.5 -0.5 3707 3650" + content="<mxfile host="Electron" modified="2024-02-15T03:36:19.295Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="nfHEOSCjOZokmiNFNAXf" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > - - + + + - - - - + + + + - + - - + + - - - - - + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - - - - + + + + + - - - - - + + + + + - + - - + + - + - +
@@ -345,22 +346,22 @@
- Urban crossroads with traffic light... + Urban crossroads with traffic light... - - - - - - - - - - - - + + + + + + + + + + + - + - + - + - - - - - + + + + + - + - - - + + + - - + + - - - - + + + + - - - + + + - - + + - + - - - + + + - - - + + + - - + + - - - - - - - + + + + + + + - +
- ego lane + ego lane - - - + + +
- yield lane + yield lane - - - + + +
- attention lane + attention lane - - + +
@@ -647,66 +648,66 @@
- conflicting lanes + conflicting lanes - - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + +
@@ -800,16 +801,16 @@
- Urban crossroads with traffic light... + Urban crossroads with traffic light... - +
- ego lane + ego lane @@ -830,7 +831,7 @@
@@ -841,16 +842,16 @@
- conflicting lanes + conflicting lanes - +
- yield lane + yield lane - +
- attention lane + attention lane - - - - - - - + + + + + + + - - + + - - - - - - + + + + + + - - - - - + + + + + - - + + - - - + + + - + - + - - - - + + + + - - + +
@@ -1059,16 +1060,16 @@
- T-shape junction w/o traffic light... + T-shape junction w/o traffic light... - +
- ego lane + ego lane - - - + + +
- attention area + attention area - - - - - - + + + + + +
@@ -1132,24 +1133,24 @@
- T-shape junction w/o traffic light... + T-shape junction w/o traffic light... - - - - - + + + + + - - - + + + - - + + - - - - + + + + - +
- ego lane + ego lane - - - + + +
- attention area + attention area - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - +
- attention area + attention area - - - + + +
- attention area + attention area - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - - - - + + + + + - - - - - + + + + + - + - - - - + + + + - +
@@ -1640,22 +1641,22 @@
- Urban crossroads with traffic light... + Urban crossroads with traffic light... - - - - - - - - - - - - + + + + + + + + + + + - + - + - + - - - - - + + + + + - + - - - + + + - - + + - - - - + + + + - - - + + + - - + + - + - - - + + + - - - + + + - - + + - - - - - - - + + + + + + + - +
- ego lane + ego lane - - - + + +
- attention lane + attention lane - - + +
@@ -1918,54 +1919,54 @@
- conflicting lanes + conflicting lanes - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + +
@@ -2041,16 +2042,16 @@
- Urban crossroads with traffic light... + Urban crossroads with traffic light... - +
- ego lane + ego lane @@ -2071,7 +2072,7 @@
@@ -2082,16 +2083,16 @@
- conflicting lanes + conflicting lanes - +
- attention lane + attention lane - - - - - + + + + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - +
- attention area + attention area - - - + + +
- attention area + attention area - - + + + + + + + + + + + + + + + + + + + + diff --git a/planning/behavior_velocity_intersection_module/docs/pass-judge-line.drawio.svg b/planning/behavior_velocity_intersection_module/docs/pass-judge-line.drawio.svg new file mode 100644 index 0000000000000..d1f488af7c2ac --- /dev/null +++ b/planning/behavior_velocity_intersection_module/docs/pass-judge-line.drawio.svg @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + 1st_pass_judge_line + +
+
+
+
+ 1st_pass_judge_line +
+
+ + + + + + + + +
+
+
+ + 2nd_pass_judge_line_magin + +
+
+
+
+ 2nd_pass_judge_line_magin +
+
+ + + + + + + + +
+
+
+ + 2nd_pass_judge_line + +
+
+
+
+ 2nd_pass_judge_line +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + 1st_pass_judge_line + +
+
+
+
+ 1st_pass_judge_line +
+
+ + + + + + + + +
+ + + + Text is not SVG - cannot display + + +
diff --git a/planning/behavior_velocity_intersection_module/docs/upstream-velocity.drawio.svg b/planning/behavior_velocity_intersection_module/docs/upstream-velocity.drawio.svg new file mode 100644 index 0000000000000..d2bd5040fb0f7 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/docs/upstream-velocity.drawio.svg @@ -0,0 +1,1587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + w/ traffic light, right +
+
+ (Left-hand traffic) +
+ + +
+
+
+
+
+
+
+ w/ traffic light, right... +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_1\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_2\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_3\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_4\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_5\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_1\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_2\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_3\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_4\) +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \(t_5\),\(t_6\), \(... +
+
+ + +
+ + + + Text is not SVG - cannot display + + +
diff --git a/planning/behavior_velocity_intersection_module/package.xml b/planning/behavior_velocity_intersection_module/package.xml index 0c9b3407d0f38..0bed7d32f412f 100644 --- a/planning/behavior_velocity_intersection_module/package.xml +++ b/planning/behavior_velocity_intersection_module/package.xml @@ -22,10 +22,12 @@ autoware_auto_planning_msgs autoware_perception_msgs behavior_velocity_planner_common + fmt geometry_msgs interpolation lanelet2_extension libopencv-dev + magic_enum motion_utils nav_msgs pluginlib @@ -33,7 +35,6 @@ route_handler rtc_interface tf2_geometry_msgs - tier4_api_msgs tier4_autoware_utils tier4_planning_msgs vehicle_info_util diff --git a/planning/behavior_velocity_intersection_module/scripts/ttc.py b/planning/behavior_velocity_intersection_module/scripts/ttc.py index 6d1593d95f055..1eb6ae1ffafc1 100755 --- a/planning/behavior_velocity_intersection_module/scripts/ttc.py +++ b/planning/behavior_velocity_intersection_module/scripts/ttc.py @@ -21,6 +21,7 @@ from threading import Lock import time +from PIL import Image import imageio import matplotlib import matplotlib.pyplot as plt @@ -218,7 +219,19 @@ def plot_world(self): def cleanup(self): if self.args.save: kwargs_write = {"fps": self.args.fps, "quantizer": "nq"} - imageio.mimsave("./" + self.args.gif + ".gif", self.images, **kwargs_write) + max_size_total = 0 + max_size = None + for image in self.images: + (w, h) = image.size + if w * h > max_size_total: + max_size = image.size + max_size_total = w * h + reshaped = [] + for image in self.images: + reshaped.append(image.resize(max_size)) + + imageio.mimsave("./" + self.args.gif + ".gif", reshaped, **kwargs_write) + print("saved fig") rclpy.shutdown() def on_plot_timer(self): @@ -241,6 +254,7 @@ def on_plot_timer(self): if self.args.save: image = np.frombuffer(self.fig.canvas.tostring_rgb(), dtype="uint8") image = image.reshape(self.fig.canvas.get_width_height()[::-1] + (3,)) + image = Image.fromarray(image.astype(np.uint8)) self.images.append(image) def on_ego_ttc(self, msg): diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index 83e218185a5ad..bd2ad1935406b 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -31,8 +31,6 @@ #include #include -namespace behavior_velocity_planner -{ namespace { using tier4_autoware_utils::appendMarkerArray; @@ -40,14 +38,14 @@ using tier4_autoware_utils::createMarkerColor; using tier4_autoware_utils::createMarkerOrientation; using tier4_autoware_utils::createMarkerScale; -static visualization_msgs::msg::MarkerArray createLaneletPolygonsMarkerArray( +visualization_msgs::msg::MarkerArray createLaneletPolygonsMarkerArray( const std::vector & polygons, const std::string & ns, const int64_t lane_id, const double r, const double g, const double b) { visualization_msgs::msg::MarkerArray msg; int32_t i = 0; - int32_t uid = planning_utils::bitShift(lane_id); + int32_t uid = behavior_velocity_planner::planning_utils::bitShift(lane_id); for (const auto & polygon : polygons) { visualization_msgs::msg::Marker marker{}; marker.header.frame_id = "map"; @@ -90,7 +88,7 @@ visualization_msgs::msg::MarkerArray createPoseMarkerArray( marker_line.type = visualization_msgs::msg::Marker::LINE_STRIP; marker_line.action = visualization_msgs::msg::Marker::ADD; marker_line.pose.orientation = createMarkerOrientation(0, 0, 0, 1.0); - marker_line.scale = createMarkerScale(0.1, 0.0, 0.0); + marker_line.scale = createMarkerScale(0.2, 0.0, 0.0); marker_line.color = createMarkerColor(r, g, b, 0.999); const double yaw = tf2::getYaw(pose.orientation); @@ -113,7 +111,7 @@ visualization_msgs::msg::MarkerArray createPoseMarkerArray( return msg; } -visualization_msgs::msg::MarkerArray createLineMarkerArray( +visualization_msgs::msg::MarkerArray createArrowLineMarkerArray( const geometry_msgs::msg::Point & point_start, const geometry_msgs::msg::Point & point_end, const std::string & ns, const int64_t id, const double r, const double g, const double b) { @@ -139,6 +137,28 @@ visualization_msgs::msg::MarkerArray createLineMarkerArray( return msg; } +visualization_msgs::msg::MarkerArray createLineMarkerArray( + const geometry_msgs::msg::Point & point_start, const geometry_msgs::msg::Point & point_end, + const std::string & ns, const int64_t id, const double r, const double g, const double b) +{ + visualization_msgs::msg::MarkerArray msg; + + visualization_msgs::msg::Marker marker; + marker.header.frame_id = "map"; + marker.ns = ns + "_line"; + marker.id = id; + marker.lifetime = rclcpp::Duration::from_seconds(0.3); + marker.type = visualization_msgs::msg::Marker::LINE_STRIP; + marker.action = visualization_msgs::msg::Marker::ADD; + marker.scale.x = 0.1; + marker.color = createMarkerColor(r, g, b, 0.999); + marker.points.push_back(point_start); + marker.points.push_back(point_end); + + msg.markers.push_back(marker); + return msg; +} + [[maybe_unused]] visualization_msgs::msg::Marker createPointMarkerArray( const geometry_msgs::msg::Point & point, const std::string & ns, const int64_t id, const double r, const double g, const double b) @@ -158,8 +178,59 @@ visualization_msgs::msg::MarkerArray createLineMarkerArray( return marker_point; } +constexpr std::tuple white() +{ + constexpr uint64_t code = 0xfdfdfd; + constexpr float r = static_cast(code >> 16) / 255.0; + constexpr float g = static_cast((code << 48) >> 56) / 255.0; + constexpr float b = static_cast((code << 56) >> 56) / 255.0; + return {r, g, b}; +} + +constexpr std::tuple green() +{ + constexpr uint64_t code = 0x5fa641; + constexpr float r = static_cast(code >> 16) / 255.0; + constexpr float g = static_cast((code << 48) >> 56) / 255.0; + constexpr float b = static_cast((code << 56) >> 56) / 255.0; + return {r, g, b}; +} + +constexpr std::tuple yellow() +{ + constexpr uint64_t code = 0xebce2b; + constexpr float r = static_cast(code >> 16) / 255.0; + constexpr float g = static_cast((code << 48) >> 56) / 255.0; + constexpr float b = static_cast((code << 56) >> 56) / 255.0; + return {r, g, b}; +} + +constexpr std::tuple red() +{ + constexpr uint64_t code = 0xba1c30; + constexpr float r = static_cast(code >> 16) / 255.0; + constexpr float g = static_cast((code << 48) >> 56) / 255.0; + constexpr float b = static_cast((code << 56) >> 56) / 255.0; + return {r, g, b}; +} + +constexpr std::tuple light_blue() +{ + constexpr uint64_t code = 0x96cde6; + constexpr float r = static_cast(code >> 16) / 255.0; + constexpr float g = static_cast((code << 48) >> 56) / 255.0; + constexpr float b = static_cast((code << 56) >> 56) / 255.0; + return {r, g, b}; +} } // namespace +namespace behavior_velocity_planner +{ +using tier4_autoware_utils::appendMarkerArray; +using tier4_autoware_utils::createMarkerColor; +using tier4_autoware_utils::createMarkerOrientation; +using tier4_autoware_utils::createMarkerScale; + visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray() { visualization_msgs::msg::MarkerArray debug_marker_array; @@ -168,14 +239,14 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( if (debug_data_.attention_area) { appendMarkerArray( - createLaneletPolygonsMarkerArray( + ::createLaneletPolygonsMarkerArray( debug_data_.attention_area.value(), "attention_area", lane_id_, 0.0, 1.0, 0.0), &debug_marker_array); } if (debug_data_.occlusion_attention_area) { appendMarkerArray( - createLaneletPolygonsMarkerArray( + ::createLaneletPolygonsMarkerArray( debug_data_.occlusion_attention_area.value(), "occlusion_attention_area", lane_id_, 0.917, 0.568, 0.596), &debug_marker_array); @@ -183,11 +254,27 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( if (debug_data_.adjacent_area) { appendMarkerArray( - createLaneletPolygonsMarkerArray( + ::createLaneletPolygonsMarkerArray( debug_data_.adjacent_area.value(), "adjacent_area", lane_id_, 0.913, 0.639, 0.149), &debug_marker_array); } + if (debug_data_.first_attention_area) { + appendMarkerArray( + ::createLaneletPolygonsMarkerArray( + {debug_data_.first_attention_area.value()}, "first_attention_area", lane_id_, 1, 0.647, + 0.0), + &debug_marker_array, now); + } + + if (debug_data_.second_attention_area) { + appendMarkerArray( + ::createLaneletPolygonsMarkerArray( + {debug_data_.second_attention_area.value()}, "second_attention_area", lane_id_, 1, 0.647, + 0.0), + &debug_marker_array, now); + } + if (debug_data_.stuck_vehicle_detect_area) { appendMarkerArray( debug::createPolygonMarkerArray( @@ -198,7 +285,7 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( if (debug_data_.yield_stuck_detect_area) { appendMarkerArray( - createLaneletPolygonsMarkerArray( + ::createLaneletPolygonsMarkerArray( debug_data_.yield_stuck_detect_area.value(), "yield_stuck_detect_area", lane_id_, 0.6588235, 0.34509, 0.6588235), &debug_marker_array); @@ -206,7 +293,7 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( if (debug_data_.ego_lane) { appendMarkerArray( - createLaneletPolygonsMarkerArray( + ::createLaneletPolygonsMarkerArray( {debug_data_.ego_lane.value()}, "ego_lane", lane_id_, 1, 0.647, 0.0), &debug_marker_array, now); } @@ -219,59 +306,70 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( &debug_marker_array, now); } - size_t i{0}; - for (const auto & p : debug_data_.candidate_collision_object_polygons) { - appendMarkerArray( - debug::createPolygonMarkerArray( - p, "candidate_collision_object_polygons", lane_id_ + i++, now, 0.3, 0.0, 0.0, 0.0, 0.5, - 0.5), - &debug_marker_array, now); - } - + static constexpr auto white = ::white(); + static constexpr auto green = ::green(); + static constexpr auto yellow = ::yellow(); + static constexpr auto red = ::red(); + static constexpr auto light_blue = ::light_blue(); appendMarkerArray( debug::createObjectsMarkerArray( - debug_data_.conflicting_targets, "conflicting_targets", module_id_, now, 0.99, 0.4, 0.0), + debug_data_.safe_under_traffic_control_targets, "safe_under_traffic_control_targets", + module_id_, now, std::get<0>(light_blue), std::get<1>(light_blue), std::get<2>(light_blue)), &debug_marker_array, now); appendMarkerArray( debug::createObjectsMarkerArray( - debug_data_.amber_ignore_targets, "amber_ignore_targets", module_id_, now, 0.0, 1.0, 0.0), + debug_data_.unsafe_targets, "unsafe_targets", module_id_, now, std::get<0>(green), + std::get<1>(green), std::get<2>(green)), &debug_marker_array, now); appendMarkerArray( debug::createObjectsMarkerArray( - debug_data_.red_overshoot_ignore_targets, "red_overshoot_ignore_targets", module_id_, now, - 0.0, 1.0, 0.0), + debug_data_.misjudge_targets, "misjudge_targets", module_id_, now, std::get<0>(yellow), + std::get<1>(yellow), std::get<2>(yellow)), &debug_marker_array, now); appendMarkerArray( debug::createObjectsMarkerArray( - debug_data_.stuck_targets, "stuck_targets", module_id_, now, 0.99, 0.99, 0.2), + debug_data_.too_late_detect_targets, "too_late_detect_targets", module_id_, now, + std::get<0>(red), std::get<1>(red), std::get<2>(red)), &debug_marker_array, now); appendMarkerArray( debug::createObjectsMarkerArray( - debug_data_.yield_stuck_targets, "stuck_targets", module_id_, now, 0.4, 0.99, 0.2), + debug_data_.parked_targets, "parked_targets", module_id_, now, std::get<0>(white), + std::get<1>(white), std::get<2>(white)), &debug_marker_array, now); appendMarkerArray( debug::createObjectsMarkerArray( - debug_data_.blocking_attention_objects, "blocking_attention_objects", module_id_, now, 0.99, - 0.99, 0.6), + debug_data_.stuck_targets, "stuck_targets", module_id_, now, std::get<0>(white), + std::get<1>(white), std::get<2>(white)), &debug_marker_array, now); - /* appendMarkerArray( - createPoseMarkerArray( - debug_data_.predicted_obj_pose, "predicted_obj_pose", module_id_, 0.7, 0.85, 0.9), + debug::createObjectsMarkerArray( + debug_data_.yield_stuck_targets, "yield_stuck_targets", module_id_, now, std::get<0>(white), + std::get<1>(white), std::get<2>(white)), &debug_marker_array, now); - */ - if (debug_data_.pass_judge_wall_pose) { + if (debug_data_.first_pass_judge_wall_pose) { + const double r = debug_data_.passed_first_pass_judge ? 1.0 : 0.0; + const double g = debug_data_.passed_first_pass_judge ? 0.0 : 1.0; + appendMarkerArray( + ::createPoseMarkerArray( + debug_data_.first_pass_judge_wall_pose.value(), "first_pass_judge_wall_pose", module_id_, r, + g, 0.0), + &debug_marker_array, now); + } + + if (debug_data_.second_pass_judge_wall_pose) { + const double r = debug_data_.passed_second_pass_judge ? 1.0 : 0.0; + const double g = debug_data_.passed_second_pass_judge ? 0.0 : 1.0; appendMarkerArray( - createPoseMarkerArray( - debug_data_.pass_judge_wall_pose.value(), "pass_judge_wall_pose", module_id_, 0.7, 0.85, - 0.9), + ::createPoseMarkerArray( + debug_data_.second_pass_judge_wall_pose.value(), "second_pass_judge_wall_pose", module_id_, + r, g, 0.0), &debug_marker_array, now); } @@ -286,10 +384,43 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( if (debug_data_.nearest_occlusion_projection) { const auto [point_start, point_end] = debug_data_.nearest_occlusion_projection.value(); appendMarkerArray( - createLineMarkerArray( + ::createArrowLineMarkerArray( point_start, point_end, "nearest_occlusion_projection", lane_id_, 0.5, 0.5, 0.0), &debug_marker_array, now); } + + if (debug_data_.nearest_occlusion_triangle) { + const auto [p1, p2, p3] = debug_data_.nearest_occlusion_triangle.value(); + const auto color = debug_data_.static_occlusion ? green : red; + geometry_msgs::msg::Polygon poly; + poly.points.push_back( + geometry_msgs::build().x(p1.x).y(p1.y).z(p1.z)); + poly.points.push_back( + geometry_msgs::build().x(p2.x).y(p2.y).z(p2.z)); + poly.points.push_back( + geometry_msgs::build().x(p3.x).y(p3.y).z(p3.z)); + appendMarkerArray( + debug::createPolygonMarkerArray( + poly, "nearest_occlusion_triangle", lane_id_, now, 0.3, 0.0, 0.0, std::get<0>(color), + std::get<1>(color), std::get<2>(color)), + &debug_marker_array, now); + } + if (debug_data_.traffic_light_observation) { + const auto GREEN = autoware_perception_msgs::msg::TrafficSignalElement::GREEN; + const auto YELLOW = autoware_perception_msgs::msg::TrafficSignalElement::AMBER; + + const auto [ego, tl_point, id, color] = debug_data_.traffic_light_observation.value(); + geometry_msgs::msg::Point tl_point_point; + tl_point_point.x = tl_point.x(); + tl_point_point.y = tl_point.y(); + tl_point_point.z = tl_point.z(); + const auto tl_color = (color == GREEN) ? green : (color == YELLOW ? yellow : red); + const auto [r, g, b] = tl_color; + appendMarkerArray( + ::createLineMarkerArray( + ego.position, tl_point_point, "intersection_traffic_light", lane_id_, r, g, b), + &debug_marker_array, now); + } return debug_marker_array; } @@ -341,11 +472,11 @@ visualization_msgs::msg::MarkerArray MergeFromPrivateRoadModule::createDebugMark const auto state = state_machine_.getState(); - int32_t uid = planning_utils::bitShift(module_id_); + int32_t uid = behavior_velocity_planner::planning_utils::bitShift(module_id_); const auto now = this->clock_->now(); if (state == StateMachine::State::STOP) { appendMarkerArray( - createPoseMarkerArray(debug_data_.stop_point_pose, "stop_point_pose", uid, 1.0, 0.0, 0.0), + ::createPoseMarkerArray(debug_data_.stop_point_pose, "stop_point_pose", uid, 1.0, 0.0, 0.0), &debug_marker_array, now); } diff --git a/planning/behavior_velocity_intersection_module/src/decision_result.cpp b/planning/behavior_velocity_intersection_module/src/decision_result.cpp new file mode 100644 index 0000000000000..7ed896d1b4b55 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/decision_result.cpp @@ -0,0 +1,68 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "decision_result.hpp" + +namespace behavior_velocity_planner::intersection +{ +std::string formatDecisionResult(const DecisionResult & decision_result) +{ + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "InternalError because " + state.error; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "OverPassJudge:\nsafety_report:" + state.safety_report + + "\nevasive_report:" + state.evasive_report; + } + if (std::holds_alternative(decision_result)) { + return "StuckStop"; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "YieldStuckStop:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "NonOccludedCollisionStop:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "FirstWaitBeforeOcclusion:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "PeekingTowardOcclusion:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "OccludedCollisionStop:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "OccludedAbsenceTrafficLight:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "Safe:\nocclusion_report:" + state.occlusion_report; + } + if (std::holds_alternative(decision_result)) { + const auto & state = std::get(decision_result); + return "FullyPrioritized\nsafety_report:" + state.safety_report; + } + return ""; +} + +} // namespace behavior_velocity_planner::intersection diff --git a/planning/behavior_velocity_intersection_module/src/decision_result.hpp b/planning/behavior_velocity_intersection_module/src/decision_result.hpp new file mode 100644 index 0000000000000..5f642db3a462d --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/decision_result.hpp @@ -0,0 +1,177 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DECISION_RESULT_HPP_ +#define DECISION_RESULT_HPP_ + +#include +#include +#include + +namespace behavior_velocity_planner::intersection +{ + +/** + * @brief internal error + */ +struct InternalError +{ + std::string error; +}; + +/** + * @brief + */ +struct OverPassJudge +{ + std::string safety_report; + std::string evasive_report; +}; + +/** + * @brief detected stuck vehicle + */ +struct StuckStop +{ + size_t closest_idx{0}; + size_t stuck_stopline_idx{0}; + std::optional occlusion_stopline_idx{std::nullopt}; +}; + +/** + * @brief yielded by vehicle on the attention area + */ +struct YieldStuckStop +{ + size_t closest_idx{0}; + size_t stuck_stopline_idx{0}; + std::string occlusion_report; +}; + +/** + * @brief only collision is detected + */ +struct NonOccludedCollisionStop +{ + size_t closest_idx{0}; + size_t collision_stopline_idx{0}; + size_t occlusion_stopline_idx{0}; + std::string occlusion_report; +}; + +/** + * @brief occlusion is detected so ego needs to stop at the default stop line position + */ +struct FirstWaitBeforeOcclusion +{ + bool is_actually_occlusion_cleared{false}; + size_t closest_idx{0}; + size_t first_stopline_idx{0}; + size_t occlusion_stopline_idx{0}; + std::string occlusion_report; +}; + +/** + * @brief ego is approaching the boundary of attention area in the presence of traffic light + */ +struct PeekingTowardOcclusion +{ + //! if intersection_occlusion is disapproved externally through RTC, it indicates + //! "is_forcefully_occluded" + bool is_actually_occlusion_cleared{false}; + bool temporal_stop_before_attention_required{false}; + size_t closest_idx{0}; + size_t collision_stopline_idx{0}; + size_t first_attention_stopline_idx{0}; + size_t occlusion_stopline_idx{0}; + //! if null, it is dynamic occlusion and shows up intersection_occlusion(dyn). if valid, it + //! contains the remaining time to release the static occlusion stuck and shows up + //! intersection_occlusion(x.y) + std::optional static_occlusion_timeout{std::nullopt}; + std::string occlusion_report; +}; + +/** + * @brief both collision and occlusion are detected in the presence of traffic light + */ +struct OccludedCollisionStop +{ + bool is_actually_occlusion_cleared{false}; + bool temporal_stop_before_attention_required{false}; + size_t closest_idx{0}; + size_t collision_stopline_idx{0}; + size_t first_attention_stopline_idx{0}; + size_t occlusion_stopline_idx{0}; + //! if null, it is dynamic occlusion and shows up intersection_occlusion(dyn). if valid, it + //! contains the remaining time to release the static occlusion stuck + std::optional static_occlusion_timeout{std::nullopt}; + std::string occlusion_report; +}; + +/** + * @brief at least occlusion is detected in the absence of traffic light + */ +struct OccludedAbsenceTrafficLight +{ + bool is_actually_occlusion_cleared{false}; + bool collision_detected{false}; + bool temporal_stop_before_attention_required{false}; + size_t closest_idx{0}; + size_t first_attention_area_stopline_idx{0}; + size_t peeking_limit_line_idx{0}; + std::string occlusion_report; +}; + +/** + * @brief both collision and occlusion are not detected + */ +struct Safe +{ + size_t closest_idx{0}; + size_t collision_stopline_idx{0}; + size_t occlusion_stopline_idx{0}; + std::string occlusion_report; +}; + +/** + * @brief traffic light is red or arrow signal + */ +struct FullyPrioritized +{ + bool collision_detected{false}; + size_t closest_idx{0}; + size_t collision_stopline_idx{0}; + size_t occlusion_stopline_idx{0}; + std::string safety_report; +}; + +using DecisionResult = std::variant< + InternalError, //! internal process error + OverPassJudge, //! over the pass judge lines + StuckStop, //! detected stuck vehicle + YieldStuckStop, //! detected yield stuck vehicle + NonOccludedCollisionStop, //! detected collision while FOV is clear + FirstWaitBeforeOcclusion, //! stop for a while before peeking to occlusion + PeekingTowardOcclusion, //! peeking into occlusion while collision is not detected + OccludedCollisionStop, //! occlusion and collision are both detected + OccludedAbsenceTrafficLight, //! occlusion is detected in the absence of traffic light + Safe, //! judge as safe + FullyPrioritized //! only detect vehicles violating traffic rules + >; + +std::string formatDecisionResult(const DecisionResult & decision_result); + +} // namespace behavior_velocity_planner::intersection + +#endif // DECISION_RESULT_HPP_ diff --git a/planning/behavior_velocity_intersection_module/src/interpolated_path_info.hpp b/planning/behavior_velocity_intersection_module/src/interpolated_path_info.hpp new file mode 100644 index 0000000000000..9002c88354d68 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/interpolated_path_info.hpp @@ -0,0 +1,48 @@ +// Copyright 2022 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INTERPOLATED_PATH_INFO_HPP_ +#define INTERPOLATED_PATH_INFO_HPP_ + +#include + +#include + +#include +#include +#include + +namespace behavior_velocity_planner::intersection +{ + +/** + * @brief wrapper class of interpolated path with lane id + */ +struct InterpolatedPathInfo +{ + /** the interpolated path */ + autoware_auto_planning_msgs::msg::PathWithLaneId path; + /** discretization interval of interpolation */ + double ds{0.0}; + /** the intersection lanelet id */ + lanelet::Id lane_id{0}; + /** the associative lane ids of lane_id */ + std::set associative_lane_ids{}; + /** the range of indices for the path points with associative lane id */ + std::optional> lane_id_interval{std::nullopt}; +}; + +} // namespace behavior_velocity_planner::intersection + +#endif // INTERPOLATED_PATH_INFO_HPP_ diff --git a/planning/behavior_velocity_intersection_module/src/intersection_lanelets.cpp b/planning/behavior_velocity_intersection_module/src/intersection_lanelets.cpp new file mode 100644 index 0000000000000..555ea424dfef0 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/intersection_lanelets.cpp @@ -0,0 +1,82 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "intersection_lanelets.hpp" + +#include "util.hpp" + +#include +#include + +#include + +namespace behavior_velocity_planner::intersection +{ + +void IntersectionLanelets::update( + const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length, + lanelet::routing::RoutingGraphPtr routing_graph_ptr) +{ + is_prioritized_ = is_prioritized; + // find the first conflicting/detection area polygon intersecting the path + if (!first_conflicting_area_) { + auto first = util::getFirstPointInsidePolygonsByFootprint( + conflicting_area_, interpolated_path_info, footprint, vehicle_length); + if (first) { + first_conflicting_lane_ = conflicting_.at(first.value().second); + first_conflicting_area_ = conflicting_area_.at(first.value().second); + } + } + if (!first_attention_area_) { + const auto first = util::getFirstPointInsidePolygonsByFootprint( + attention_non_preceding_area_, interpolated_path_info, footprint, vehicle_length); + if (first) { + first_attention_lane_ = attention_non_preceding_.at(first.value().second); + first_attention_area_ = attention_non_preceding_area_.at(first.value().second); + } + } + if (first_attention_lane_ && !second_attention_lane_ && !second_attention_lane_empty_) { + const auto first_attention_lane = first_attention_lane_.value(); + // remove first_attention_area_ and non-straight lanelets from attention_non_preceding + lanelet::ConstLanelets attention_non_preceding_ex_first; + lanelet::ConstLanelets sibling_first_attention_lanelets; + for (const auto & previous : routing_graph_ptr->previous(first_attention_lane)) { + for (const auto & following : routing_graph_ptr->following(previous)) { + sibling_first_attention_lanelets.push_back(following); + } + } + for (const auto & ll : attention_non_preceding_) { + // the sibling lanelets of first_attention_lanelet are ruled out + if (lanelet::utils::contains(sibling_first_attention_lanelets, ll)) { + continue; + } + if (std::string(ll.attributeOr("turn_direction", "else")).compare("straight") == 0) { + attention_non_preceding_ex_first.push_back(ll); + } + } + if (attention_non_preceding_ex_first.empty()) { + second_attention_lane_empty_ = true; + } + const auto attention_non_preceding_ex_first_area = + util::getPolygon3dFromLanelets(attention_non_preceding_ex_first); + const auto second = util::getFirstPointInsidePolygonsByFootprint( + attention_non_preceding_ex_first_area, interpolated_path_info, footprint, vehicle_length); + if (second) { + second_attention_lane_ = attention_non_preceding_ex_first.at(second.value().second); + second_attention_area_ = second_attention_lane_.value().polygon3d(); + } + } +} +} // namespace behavior_velocity_planner::intersection diff --git a/planning/behavior_velocity_intersection_module/src/intersection_lanelets.hpp b/planning/behavior_velocity_intersection_module/src/intersection_lanelets.hpp new file mode 100644 index 0000000000000..9624d375de122 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/intersection_lanelets.hpp @@ -0,0 +1,195 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INTERSECTION_LANELETS_HPP_ +#define INTERSECTION_LANELETS_HPP_ + +#include "interpolated_path_info.hpp" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace behavior_velocity_planner::intersection +{ + +/** + * @brief see the document for more details of IntersectionLanelets + */ +struct IntersectionLanelets +{ +public: + /** + * update conflicting lanelets and traffic priority information + */ + void update( + const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length, + lanelet::routing::RoutingGraphPtr routing_graph_ptr); + + const lanelet::ConstLanelets & attention() const + { + return is_prioritized_ ? attention_non_preceding_ : attention_; + } + const std::vector> & attention_stoplines() const + { + return is_prioritized_ ? attention_non_preceding_stoplines_ : attention_stoplines_; + } + const lanelet::ConstLanelets & conflicting() const { return conflicting_; } + const lanelet::ConstLanelets & adjacent() const { return adjacent_; } + const lanelet::ConstLanelets & occlusion_attention() const + { + return is_prioritized_ ? attention_non_preceding_ : occlusion_attention_; + } + const lanelet::ConstLanelets & attention_non_preceding() const + { + return attention_non_preceding_; + } + const std::vector & attention_area() const + { + return is_prioritized_ ? attention_non_preceding_area_ : attention_area_; + } + const std::vector & conflicting_area() const + { + return conflicting_area_; + } + const std::vector & adjacent_area() const { return adjacent_area_; } + const std::vector & occlusion_attention_area() const + { + return occlusion_attention_area_; + } + const std::optional & first_conflicting_lane() const + { + return first_conflicting_lane_; + } + const std::optional & first_conflicting_area() const + { + return first_conflicting_area_; + } + const std::optional & first_attention_lane() const + { + return first_attention_lane_; + } + const std::optional & first_attention_area() const + { + return first_attention_area_; + } + const std::optional & second_attention_lane() const + { + return second_attention_lane_; + } + const std::optional & second_attention_area() const + { + return second_attention_area_; + } + + /** + * the set of attention lanelets which is topologically merged + */ + lanelet::ConstLanelets attention_; + std::vector attention_area_; + + /** + * the stop lines for each attention_lanelets associated with traffic lights. At intersection + * without traffic lights, each value is null + */ + std::vector> attention_stoplines_; + + /** + * the conflicting part of attention lanelets + */ + lanelet::ConstLanelets attention_non_preceding_; + std::vector attention_non_preceding_area_; + + /** + * the stop lines for each attention_non_preceding_ + */ + std::vector> attention_non_preceding_stoplines_; + + /** + * the conflicting lanelets of the objective intersection lanelet + */ + lanelet::ConstLanelets conflicting_; + std::vector conflicting_area_; + + /** + * + */ + lanelet::ConstLanelets adjacent_; + std::vector adjacent_area_; + + /** + * the set of attention lanelets for occlusion detection which is topologically merged + */ + lanelet::ConstLanelets occlusion_attention_; + std::vector occlusion_attention_area_; + + /** + * the vector of sum of each occlusion_attention lanelet + */ + std::vector occlusion_attention_size_; + + /** + * the first conflicting lanelet which ego path points intersect for the first time + */ + std::optional first_conflicting_lane_{std::nullopt}; + std::optional first_conflicting_area_{std::nullopt}; + + /** + * the first attention lanelet which ego path points intersect for the first time + */ + std::optional first_attention_lane_{std::nullopt}; + std::optional first_attention_area_{std::nullopt}; + + /** + * the second attention lanelet which ego path points intersect next to the + * first_attention_lanelet + */ + bool second_attention_lane_empty_{false}; + std::optional second_attention_lane_{std::nullopt}; + std::optional second_attention_area_{std::nullopt}; + + /** + * flag if the intersection is prioritized by the traffic light + */ + bool is_prioritized_{false}; +}; + +/** + * @brief see the document for more details of PathLanelets + */ +struct PathLanelets +{ + lanelet::ConstLanelets prev; + // lanelet::ConstLanelet entry2ego; this is included in `all` if exists + lanelet::ConstLanelet + ego_or_entry2exit; // this is `assigned lane` part of the path(not from + // ego) if ego is before the intersection, otherwise from ego to exit + std::optional next = + std::nullopt; // this is nullopt is the goal is inside intersection + lanelet::ConstLanelets all; + lanelet::ConstLanelets + conflicting_interval_and_remaining; // the left/right-most interval of path conflicting with + // conflicting lanelets plus the next lane part of the + // path +}; +} // namespace behavior_velocity_planner::intersection + +#endif // INTERSECTION_LANELETS_HPP_ diff --git a/planning/behavior_velocity_intersection_module/src/intersection_stoplines.hpp b/planning/behavior_velocity_intersection_module/src/intersection_stoplines.hpp new file mode 100644 index 0000000000000..99d79d4468b38 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/intersection_stoplines.hpp @@ -0,0 +1,77 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INTERSECTION_STOPLINES_HPP_ +#define INTERSECTION_STOPLINES_HPP_ + +#include + +namespace behavior_velocity_planner::intersection +{ + +/** + * @brief see the document for more details of IntersectionStopLines + */ +struct IntersectionStopLines +{ + size_t closest_idx{0}; + + /** + * stuck_stopline is null if ego path does not intersect with first_conflicting_area + */ + std::optional stuck_stopline{std::nullopt}; + + /** + * default_stopline is null if it is calculated negative from first_attention_stopline + */ + std::optional default_stopline{std::nullopt}; + + /** + * first_attention_stopline is null if ego footprint along the path does not intersect with + * attention area. if path[0] satisfies the condition, it is 0 + */ + std::optional first_attention_stopline{std::nullopt}; + + /** + * second_attention_stopline is null if ego footprint along the path does not intersect with + * second_attention_lane. if path[0] satisfies the condition, it is 0 + */ + std::optional second_attention_stopline{std::nullopt}; + + /** + * occlusion_peeking_stopline is null if path[0] is already inside the attention area + */ + std::optional occlusion_peeking_stopline{std::nullopt}; + + /** + * first_pass_judge_line is before first_attention_stopline by the braking distance. if its value + * is calculated negative, it is 0 + */ + size_t first_pass_judge_line{0}; + + /** + * second_pass_judge_line is before second_attention_stopline by the braking distance. if + * second_attention_lane is null, it is null + */ + std::optional second_pass_judge_line{std::nullopt}; + + /** + * occlusion_wo_tl_pass_judge_line is null if ego footprint along the path does not intersect with + * the centerline of the first_attention_lane + */ + size_t occlusion_wo_tl_pass_judge_line{0}; +}; +} // namespace behavior_velocity_planner::intersection + +#endif // INTERSECTION_STOPLINES_HPP_ diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 789708abe98f8..f7a5c3e48155b 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -43,133 +43,263 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) { const std::string ns(getModuleName()); auto & ip = intersection_param_; - ip.common.attention_area_length = - getOrDeclareParameter(node, ns + ".common.attention_area_length"); - ip.common.attention_area_margin = - getOrDeclareParameter(node, ns + ".common.attention_area_margin"); - ip.common.attention_area_angle_threshold = - getOrDeclareParameter(node, ns + ".common.attention_area_angle_threshold"); - ip.common.use_intersection_area = - getOrDeclareParameter(node, ns + ".common.use_intersection_area"); - ip.common.default_stopline_margin = - getOrDeclareParameter(node, ns + ".common.default_stopline_margin"); - ip.common.stopline_overshoot_margin = - getOrDeclareParameter(node, ns + ".common.stopline_overshoot_margin"); - ip.common.path_interpolation_ds = - getOrDeclareParameter(node, ns + ".common.path_interpolation_ds"); - ip.common.max_accel = getOrDeclareParameter(node, ns + ".common.max_accel"); - ip.common.max_jerk = getOrDeclareParameter(node, ns + ".common.max_jerk"); - ip.common.delay_response_time = - getOrDeclareParameter(node, ns + ".common.delay_response_time"); - - ip.stuck_vehicle.turn_direction.left = - getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.left"); - ip.stuck_vehicle.turn_direction.right = - getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.right"); - ip.stuck_vehicle.turn_direction.straight = - getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.straight"); - ip.stuck_vehicle.use_stuck_stopline = - getOrDeclareParameter(node, ns + ".stuck_vehicle.use_stuck_stopline"); - ip.stuck_vehicle.stuck_vehicle_detect_dist = - getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_detect_dist"); - ip.stuck_vehicle.stuck_vehicle_velocity_threshold = - getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_velocity_threshold"); - ip.stuck_vehicle.timeout_private_area = - getOrDeclareParameter(node, ns + ".stuck_vehicle.timeout_private_area"); - ip.stuck_vehicle.enable_private_area_stuck_disregard = - getOrDeclareParameter(node, ns + ".stuck_vehicle.enable_private_area_stuck_disregard"); - - ip.yield_stuck.turn_direction.left = - getOrDeclareParameter(node, ns + ".yield_stuck.turn_direction.left"); - ip.yield_stuck.turn_direction.right = - getOrDeclareParameter(node, ns + ".yield_stuck.turn_direction.right"); - ip.yield_stuck.turn_direction.straight = - getOrDeclareParameter(node, ns + ".yield_stuck.turn_direction.straight"); - ip.yield_stuck.distance_threshold = - getOrDeclareParameter(node, ns + ".yield_stuck.distance_threshold"); - - ip.collision_detection.consider_wrong_direction_vehicle = - getOrDeclareParameter(node, ns + ".collision_detection.consider_wrong_direction_vehicle"); - ip.collision_detection.collision_detection_hold_time = - getOrDeclareParameter(node, ns + ".collision_detection.collision_detection_hold_time"); - ip.collision_detection.min_predicted_path_confidence = - getOrDeclareParameter(node, ns + ".collision_detection.min_predicted_path_confidence"); - ip.collision_detection.keep_detection_velocity_threshold = getOrDeclareParameter( - node, ns + ".collision_detection.keep_detection_velocity_threshold"); - ip.collision_detection.velocity_profile.use_upstream = - getOrDeclareParameter(node, ns + ".collision_detection.velocity_profile.use_upstream"); - ip.collision_detection.velocity_profile.minimum_upstream_velocity = getOrDeclareParameter( - node, ns + ".collision_detection.velocity_profile.minimum_upstream_velocity"); - ip.collision_detection.velocity_profile.default_velocity = getOrDeclareParameter( - node, ns + ".collision_detection.velocity_profile.default_velocity"); - ip.collision_detection.velocity_profile.minimum_default_velocity = getOrDeclareParameter( - node, ns + ".collision_detection.velocity_profile.minimum_default_velocity"); - ip.collision_detection.fully_prioritized.collision_start_margin_time = - getOrDeclareParameter( - node, ns + ".collision_detection.fully_prioritized.collision_start_margin_time"); - ip.collision_detection.fully_prioritized.collision_end_margin_time = - getOrDeclareParameter( - node, ns + ".collision_detection.fully_prioritized.collision_end_margin_time"); - ip.collision_detection.partially_prioritized.collision_start_margin_time = - getOrDeclareParameter( - node, ns + ".collision_detection.partially_prioritized.collision_start_margin_time"); - ip.collision_detection.partially_prioritized.collision_end_margin_time = - getOrDeclareParameter( - node, ns + ".collision_detection.partially_prioritized.collision_end_margin_time"); - ip.collision_detection.not_prioritized.collision_start_margin_time = - getOrDeclareParameter( - node, ns + ".collision_detection.not_prioritized.collision_start_margin_time"); - ip.collision_detection.not_prioritized.collision_end_margin_time = getOrDeclareParameter( - node, ns + ".collision_detection.not_prioritized.collision_end_margin_time"); - ip.collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start = - getOrDeclareParameter( - node, - ns + ".collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start"); - ip.collision_detection.yield_on_green_traffic_light.duration = getOrDeclareParameter( - node, ns + ".collision_detection.yield_on_green_traffic_light.duration"); - ip.collision_detection.yield_on_green_traffic_light.object_dist_to_stopline = - getOrDeclareParameter( - node, ns + ".collision_detection.yield_on_green_traffic_light.object_dist_to_stopline"); - ip.collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration = - getOrDeclareParameter( - node, ns + ".collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration"); - ip.collision_detection.ignore_on_red_traffic_light.object_margin_to_path = - getOrDeclareParameter( - node, ns + ".collision_detection.ignore_on_red_traffic_light.object_margin_to_path"); - - ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); - ip.occlusion.occlusion_attention_area_length = - getOrDeclareParameter(node, ns + ".occlusion.occlusion_attention_area_length"); - ip.occlusion.free_space_max = getOrDeclareParameter(node, ns + ".occlusion.free_space_max"); - ip.occlusion.occupied_min = getOrDeclareParameter(node, ns + ".occlusion.occupied_min"); - ip.occlusion.denoise_kernel = - getOrDeclareParameter(node, ns + ".occlusion.denoise_kernel"); - ip.occlusion.attention_lane_crop_curvature_threshold = - getOrDeclareParameter(node, ns + ".occlusion.attention_lane_crop_curvature_threshold"); - ip.occlusion.attention_lane_curvature_calculation_ds = - getOrDeclareParameter(node, ns + ".occlusion.attention_lane_curvature_calculation_ds"); - ip.occlusion.creep_during_peeking.enable = - getOrDeclareParameter(node, ns + ".occlusion.creep_during_peeking.enable"); - ip.occlusion.creep_during_peeking.creep_velocity = - getOrDeclareParameter(node, ns + ".occlusion.creep_during_peeking.creep_velocity"); - ip.occlusion.peeking_offset = - getOrDeclareParameter(node, ns + ".occlusion.peeking_offset"); - ip.occlusion.possible_object_bbox = - getOrDeclareParameter>(node, ns + ".occlusion.possible_object_bbox"); - ip.occlusion.ignore_parked_vehicle_speed_threshold = - getOrDeclareParameter(node, ns + ".occlusion.ignore_parked_vehicle_speed_threshold"); - ip.occlusion.occlusion_detection_hold_time = - getOrDeclareParameter(node, ns + ".occlusion.occlusion_detection_hold_time"); - ip.occlusion.temporal_stop_time_before_peeking = - getOrDeclareParameter(node, ns + ".occlusion.temporal_stop_time_before_peeking"); - ip.occlusion.temporal_stop_before_attention_area = - getOrDeclareParameter(node, ns + ".occlusion.temporal_stop_before_attention_area"); - ip.occlusion.creep_velocity_without_traffic_light = - getOrDeclareParameter(node, ns + ".occlusion.creep_velocity_without_traffic_light"); - ip.occlusion.static_occlusion_with_traffic_light_timeout = getOrDeclareParameter( - node, ns + ".occlusion.static_occlusion_with_traffic_light_timeout"); + + // common + { + ip.common.attention_area_length = + getOrDeclareParameter(node, ns + ".common.attention_area_length"); + ip.common.attention_area_margin = + getOrDeclareParameter(node, ns + ".common.attention_area_margin"); + ip.common.attention_area_angle_threshold = + getOrDeclareParameter(node, ns + ".common.attention_area_angle_threshold"); + ip.common.use_intersection_area = + getOrDeclareParameter(node, ns + ".common.use_intersection_area"); + ip.common.default_stopline_margin = + getOrDeclareParameter(node, ns + ".common.default_stopline_margin"); + ip.common.stopline_overshoot_margin = + getOrDeclareParameter(node, ns + ".common.stopline_overshoot_margin"); + ip.common.path_interpolation_ds = + getOrDeclareParameter(node, ns + ".common.path_interpolation_ds"); + ip.common.max_accel = getOrDeclareParameter(node, ns + ".common.max_accel"); + ip.common.max_jerk = getOrDeclareParameter(node, ns + ".common.max_jerk"); + ip.common.delay_response_time = + getOrDeclareParameter(node, ns + ".common.delay_response_time"); + ip.common.enable_pass_judge_before_default_stopline = + getOrDeclareParameter(node, ns + ".common.enable_pass_judge_before_default_stopline"); + } + + // stuck + { + // target_type + { + ip.stuck_vehicle.target_type.car = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.car"); + ip.stuck_vehicle.target_type.bus = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.bus"); + ip.stuck_vehicle.target_type.truck = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.truck"); + ip.stuck_vehicle.target_type.trailer = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.trailer"); + ip.stuck_vehicle.target_type.motorcycle = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.motorcycle"); + ip.stuck_vehicle.target_type.bicycle = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.bicycle"); + ip.stuck_vehicle.target_type.unknown = + getOrDeclareParameter(node, ns + ".stuck_vehicle.target_type.unknown"); + } + + // turn_direction + { + ip.stuck_vehicle.turn_direction.left = + getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.left"); + ip.stuck_vehicle.turn_direction.right = + getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.right"); + ip.stuck_vehicle.turn_direction.straight = + getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.straight"); + } + + ip.stuck_vehicle.use_stuck_stopline = + getOrDeclareParameter(node, ns + ".stuck_vehicle.use_stuck_stopline"); + ip.stuck_vehicle.stuck_vehicle_detect_dist = + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_detect_dist"); + ip.stuck_vehicle.stuck_vehicle_velocity_threshold = + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_velocity_threshold"); + ip.stuck_vehicle.disable_against_private_lane = + getOrDeclareParameter(node, ns + ".stuck_vehicle.disable_against_private_lane"); + } + + // yield_stuck + { + // target_type + { + ip.yield_stuck.target_type.car = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.car"); + ip.yield_stuck.target_type.bus = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.bus"); + ip.yield_stuck.target_type.truck = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.truck"); + ip.yield_stuck.target_type.trailer = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.trailer"); + ip.yield_stuck.target_type.motorcycle = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.motorcycle"); + ip.yield_stuck.target_type.bicycle = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.bicycle"); + ip.yield_stuck.target_type.unknown = + getOrDeclareParameter(node, ns + ".yield_stuck.target_type.unknown"); + } + + // turn_direction + { + ip.yield_stuck.turn_direction.left = + getOrDeclareParameter(node, ns + ".yield_stuck.turn_direction.left"); + ip.yield_stuck.turn_direction.right = + getOrDeclareParameter(node, ns + ".yield_stuck.turn_direction.right"); + ip.yield_stuck.turn_direction.straight = + getOrDeclareParameter(node, ns + ".yield_stuck.turn_direction.straight"); + } + + ip.yield_stuck.distance_threshold = + getOrDeclareParameter(node, ns + ".yield_stuck.distance_threshold"); + } + + // collision_detection + { + ip.collision_detection.consider_wrong_direction_vehicle = getOrDeclareParameter( + node, ns + ".collision_detection.consider_wrong_direction_vehicle"); + ip.collision_detection.collision_detection_hold_time = getOrDeclareParameter( + node, ns + ".collision_detection.collision_detection_hold_time"); + ip.collision_detection.min_predicted_path_confidence = getOrDeclareParameter( + node, ns + ".collision_detection.min_predicted_path_confidence"); + + // target_type + { + ip.collision_detection.target_type.car = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.car"); + ip.collision_detection.target_type.bus = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.bus"); + ip.collision_detection.target_type.truck = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.truck"); + ip.collision_detection.target_type.trailer = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.trailer"); + ip.collision_detection.target_type.motorcycle = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.motorcycle"); + ip.collision_detection.target_type.bicycle = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.bicycle"); + ip.collision_detection.target_type.unknown = + getOrDeclareParameter(node, ns + ".collision_detection.target_type.unknown"); + } + + // velocity_profile + { + ip.collision_detection.velocity_profile.use_upstream = getOrDeclareParameter( + node, ns + ".collision_detection.velocity_profile.use_upstream"); + ip.collision_detection.velocity_profile.minimum_upstream_velocity = + getOrDeclareParameter( + node, ns + ".collision_detection.velocity_profile.minimum_upstream_velocity"); + ip.collision_detection.velocity_profile.default_velocity = getOrDeclareParameter( + node, ns + ".collision_detection.velocity_profile.default_velocity"); + ip.collision_detection.velocity_profile.minimum_default_velocity = + getOrDeclareParameter( + node, ns + ".collision_detection.velocity_profile.minimum_default_velocity"); + } + + // fully_prioritized + { + ip.collision_detection.fully_prioritized.collision_start_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.fully_prioritized.collision_start_margin_time"); + ip.collision_detection.fully_prioritized.collision_end_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.fully_prioritized.collision_end_margin_time"); + } + + // partially_prioritized + { + ip.collision_detection.partially_prioritized.collision_start_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.partially_prioritized.collision_start_margin_time"); + ip.collision_detection.partially_prioritized.collision_end_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.partially_prioritized.collision_end_margin_time"); + } + + // not_prioritized + { + ip.collision_detection.not_prioritized.collision_start_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.not_prioritized.collision_start_margin_time"); + ip.collision_detection.not_prioritized.collision_end_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.not_prioritized.collision_end_margin_time"); + } + + // yield_on_green_traffic_light + { + ip.collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start = + getOrDeclareParameter( + node, + ns + + ".collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start"); + ip.collision_detection.yield_on_green_traffic_light.duration = getOrDeclareParameter( + node, ns + ".collision_detection.yield_on_green_traffic_light.duration"); + ip.collision_detection.yield_on_green_traffic_light.object_dist_to_stopline = + getOrDeclareParameter( + node, ns + ".collision_detection.yield_on_green_traffic_light.object_dist_to_stopline"); + } + + // ignore_on_amber_traffic_light, ignore_on_red_traffic_light + { + ip.collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration.car = + getOrDeclareParameter( + node, + ns + + ".collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration.car"); + ip.collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration.bike = + getOrDeclareParameter( + node, + ns + + ".collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration.bike"); + ip.collision_detection.ignore_on_red_traffic_light.object_margin_to_path = + getOrDeclareParameter( + node, ns + ".collision_detection.ignore_on_red_traffic_light.object_margin_to_path"); + } + + ip.collision_detection.avoid_collision_by_acceleration.object_time_margin_to_collision_point = + getOrDeclareParameter( + node, ns + + ".collision_detection.avoid_collision_by_acceleration.object_time_margin_to_" + "collision_point"); + } + + // occlusion + { + ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); + ip.occlusion.occlusion_attention_area_length = + getOrDeclareParameter(node, ns + ".occlusion.occlusion_attention_area_length"); + ip.occlusion.free_space_max = + getOrDeclareParameter(node, ns + ".occlusion.free_space_max"); + ip.occlusion.occupied_min = getOrDeclareParameter(node, ns + ".occlusion.occupied_min"); + ip.occlusion.denoise_kernel = + getOrDeclareParameter(node, ns + ".occlusion.denoise_kernel"); + ip.occlusion.attention_lane_crop_curvature_threshold = getOrDeclareParameter( + node, ns + ".occlusion.attention_lane_crop_curvature_threshold"); + ip.occlusion.attention_lane_curvature_calculation_ds = getOrDeclareParameter( + node, ns + ".occlusion.attention_lane_curvature_calculation_ds"); + + // creep_during_peeking + { + ip.occlusion.creep_during_peeking.enable = + getOrDeclareParameter(node, ns + ".occlusion.creep_during_peeking.enable"); + ip.occlusion.creep_during_peeking.creep_velocity = + getOrDeclareParameter(node, ns + ".occlusion.creep_during_peeking.creep_velocity"); + } + + ip.occlusion.peeking_offset = + getOrDeclareParameter(node, ns + ".occlusion.peeking_offset"); + ip.occlusion.occlusion_required_clearance_distance = + getOrDeclareParameter(node, ns + ".occlusion.occlusion_required_clearance_distance"); + ip.occlusion.possible_object_bbox = + getOrDeclareParameter>(node, ns + ".occlusion.possible_object_bbox"); + ip.occlusion.ignore_parked_vehicle_speed_threshold = + getOrDeclareParameter(node, ns + ".occlusion.ignore_parked_vehicle_speed_threshold"); + ip.occlusion.occlusion_detection_hold_time = + getOrDeclareParameter(node, ns + ".occlusion.occlusion_detection_hold_time"); + ip.occlusion.temporal_stop_time_before_peeking = + getOrDeclareParameter(node, ns + ".occlusion.temporal_stop_time_before_peeking"); + ip.occlusion.temporal_stop_before_attention_area = + getOrDeclareParameter(node, ns + ".occlusion.temporal_stop_before_attention_area"); + ip.occlusion.creep_velocity_without_traffic_light = + getOrDeclareParameter(node, ns + ".occlusion.creep_velocity_without_traffic_light"); + ip.occlusion.static_occlusion_with_traffic_light_timeout = getOrDeclareParameter( + node, ns + ".occlusion.static_occlusion_with_traffic_light_timeout"); + } ip.debug.ttc = getOrDeclareParameter>(node, ns + ".debug.ttc"); + + decision_state_pub_ = + node.create_publisher("~/debug/intersection/decision_state", 1); + tl_observation_pub_ = node.create_publisher( + "~/debug/intersection_traffic_signal", 1); } void IntersectionModuleManager::launchNewModules( @@ -181,8 +311,6 @@ void IntersectionModuleManager::launchNewModules( const auto lanelets = planning_utils::getLaneletsOnPath(path, lanelet_map, planner_data_->current_odometry->pose); // run occlusion detection only in the first intersection - // TODO(Mamoru Sobue): remove `enable_occlusion_detection` variable - const bool enable_occlusion_detection = intersection_param_.occlusion.enable; for (size_t i = 0; i < lanelets.size(); i++) { const auto ll = lanelets.at(i); const auto lane_id = ll.id(); @@ -201,7 +329,6 @@ void IntersectionModuleManager::launchNewModules( } const std::string location = ll.attributeOr("location", "else"); - const bool is_private_area = (location.compare("private") == 0); const auto associative_ids = planning_utils::getAssociativeIntersectionLanelets(ll, lanelet_map, routing_graph); bool has_traffic_light = false; @@ -213,8 +340,7 @@ void IntersectionModuleManager::launchNewModules( } const auto new_module = std::make_shared( module_id, lane_id, planner_data_, intersection_param_, associative_ids, turn_direction, - has_traffic_light, enable_occlusion_detection, is_private_area, node_, - logger_.get_child("intersection_module"), clock_); + has_traffic_light, node_, logger_.get_child("intersection_module"), clock_); generateUUID(module_id); /* set RTC status as non_occluded status initially */ const UUID uuid = getUUID(new_module->getModuleId()); @@ -270,6 +396,10 @@ bool IntersectionModuleManager::hasSameParentLaneletAndTurnDirectionWithRegister void IntersectionModuleManager::sendRTC(const Time & stamp) { + double min_distance = std::numeric_limits::infinity(); + std::optional nearest_tl_observation{std::nullopt}; + std_msgs::msg::String decision_type; + for (const auto & scene_module : scene_modules_) { const auto intersection_module = std::dynamic_pointer_cast(scene_module); const UUID uuid = getUUID(scene_module->getModuleId()); @@ -281,9 +411,27 @@ void IntersectionModuleManager::sendRTC(const Time & stamp) const auto occlusion_safety = intersection_module->getOcclusionSafety(); occlusion_rtc_interface_.updateCooperateStatus( occlusion_uuid, occlusion_safety, occlusion_distance, occlusion_distance, stamp); + + // ========================================================================================== + // module debug data + // ========================================================================================== + const auto internal_debug_data = intersection_module->getInternalDebugData(); + if (internal_debug_data.distance < min_distance) { + min_distance = internal_debug_data.distance; + nearest_tl_observation = internal_debug_data.tl_observation; + } + decision_type.data += (internal_debug_data.decision_type + "\n"); } rtc_interface_.publishCooperateStatus(stamp); // publishRTCStatus() occlusion_rtc_interface_.publishCooperateStatus(stamp); + + // ========================================================================================== + // publish module debug data + // ========================================================================================== + decision_state_pub_->publish(decision_type); + if (nearest_tl_observation) { + tl_observation_pub_->publish(nearest_tl_observation.value().signal); + } } void IntersectionModuleManager::setActivation() diff --git a/planning/behavior_velocity_intersection_module/src/manager.hpp b/planning/behavior_velocity_intersection_module/src/manager.hpp index ff9302db0b6af..46281df2f29c7 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.hpp +++ b/planning/behavior_velocity_intersection_module/src/manager.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -44,7 +45,6 @@ class IntersectionModuleManager : public SceneModuleManagerInterfaceWithRTC IntersectionModule::PlannerParam intersection_param_; // additional for INTERSECTION_OCCLUSION RTCInterface occlusion_rtc_interface_; - std::unordered_map occlusion_map_uuid_; void launchNewModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) override; @@ -58,6 +58,9 @@ class IntersectionModuleManager : public SceneModuleManagerInterfaceWithRTC void setActivation() override; /* called from SceneModuleInterface::updateSceneModuleInstances */ void deleteExpiredModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) override; + + rclcpp::Publisher::SharedPtr decision_state_pub_; + rclcpp::Publisher::SharedPtr tl_observation_pub_; }; class MergeFromPrivateModuleManager : public SceneModuleManagerInterface diff --git a/planning/behavior_velocity_intersection_module/src/object_manager.cpp b/planning/behavior_velocity_intersection_module/src/object_manager.cpp new file mode 100644 index 0000000000000..420031e4df1cf --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/object_manager.cpp @@ -0,0 +1,309 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "object_manager.hpp" + +#include +#include +#include // for toPolygon2d + +#include +#include +#include + +#include +#include + +#include + +namespace +{ +std::string to_string(const unique_identifier_msgs::msg::UUID & uuid) +{ + std::stringstream ss; + for (auto i = 0; i < 16; ++i) { + ss << std::hex << std::setfill('0') << std::setw(2) << +uuid.uuid[i]; + } + return ss.str(); +} + +tier4_autoware_utils::Polygon2d createOneStepPolygon( + const geometry_msgs::msg::Pose & prev_pose, const geometry_msgs::msg::Pose & next_pose, + const autoware_auto_perception_msgs::msg::Shape & shape) +{ + namespace bg = boost::geometry; + const auto prev_poly = tier4_autoware_utils::toPolygon2d(prev_pose, shape); + const auto next_poly = tier4_autoware_utils::toPolygon2d(next_pose, shape); + + tier4_autoware_utils::Polygon2d one_step_poly; + for (const auto & point : prev_poly.outer()) { + one_step_poly.outer().push_back(point); + } + for (const auto & point : next_poly.outer()) { + one_step_poly.outer().push_back(point); + } + + bg::correct(one_step_poly); + + tier4_autoware_utils::Polygon2d convex_one_step_poly; + bg::convex_hull(one_step_poly, convex_one_step_poly); + + return convex_one_step_poly; +} + +} // namespace + +namespace behavior_velocity_planner::intersection +{ +namespace bg = boost::geometry; + +ObjectInfo::ObjectInfo(const unique_identifier_msgs::msg::UUID & uuid) : uuid_str(::to_string(uuid)) +{ +} + +void ObjectInfo::initialize( + const autoware_auto_perception_msgs::msg::PredictedObject & object, + std::optional attention_lanelet_opt_, + std::optional stopline_opt_) +{ + predicted_object_ = object; + attention_lanelet_opt = attention_lanelet_opt_; + stopline_opt = stopline_opt_; + unsafe_interval_ = std::nullopt; + calc_dist_to_stopline(); +} + +void ObjectInfo::update_safety( + const std::optional & unsafe_interval, + const std::optional & safe_interval, const bool safe_under_traffic_control) +{ + unsafe_interval_ = unsafe_interval; + safe_interval_ = safe_interval; + safe_under_traffic_control_ = safe_under_traffic_control; +} + +std::optional ObjectInfo::estimated_past_position( + const double past_duration) const +{ + if (!attention_lanelet_opt) { + return std::nullopt; + } + const auto attention_lanelet = attention_lanelet_opt.value(); + const auto current_arc_coords = lanelet::utils::getArcCoordinates( + {attention_lanelet}, predicted_object_.kinematics.initial_pose_with_covariance.pose); + const auto distance = current_arc_coords.distance; + const auto past_length = + current_arc_coords.length - + predicted_object_.kinematics.initial_twist_with_covariance.twist.linear.x * past_duration; + const auto past_point = lanelet::geometry::fromArcCoordinates( + attention_lanelet.centerline2d(), lanelet::ArcCoordinates{past_length, distance}); + geometry_msgs::msg::Point past_position; + past_position.x = past_point.x(); + past_position.y = past_point.y(); + return std::make_optional(past_position); +} + +void ObjectInfo::calc_dist_to_stopline() +{ + if (!stopline_opt || !attention_lanelet_opt) { + return; + } + const auto attention_lanelet = attention_lanelet_opt.value(); + const auto object_arc_coords = lanelet::utils::getArcCoordinates( + {attention_lanelet}, predicted_object_.kinematics.initial_pose_with_covariance.pose); + const auto stopline = stopline_opt.value(); + geometry_msgs::msg::Pose stopline_center; + stopline_center.position.x = (stopline.front().x() + stopline.back().x()) / 2.0; + stopline_center.position.y = (stopline.front().y() + stopline.back().y()) / 2.0; + stopline_center.position.z = (stopline.front().z() + stopline.back().z()) / 2.0; + const auto stopline_arc_coords = + lanelet::utils::getArcCoordinates({attention_lanelet}, stopline_center); + dist_to_stopline_opt = (stopline_arc_coords.length - object_arc_coords.length); +} + +bool ObjectInfo::can_stop_before_stopline(const double brake_deceleration) const +{ + if (!dist_to_stopline_opt) { + return false; + } + const double observed_velocity = + predicted_object_.kinematics.initial_twist_with_covariance.twist.linear.x; + const double dist_to_stopline = dist_to_stopline_opt.value(); + const double braking_distance = + (observed_velocity * observed_velocity) / (2.0 * brake_deceleration); + return dist_to_stopline > braking_distance; +} + +bool ObjectInfo::can_stop_before_ego_lane( + const double brake_deceleration, const double tolerable_overshoot, + lanelet::ConstLanelet ego_lane) const +{ + if (!dist_to_stopline_opt || !stopline_opt || !attention_lanelet_opt) { + return false; + } + const double dist_to_stopline = dist_to_stopline_opt.value(); + const double observed_velocity = + predicted_object_.kinematics.initial_twist_with_covariance.twist.linear.x; + const double braking_distance = + (observed_velocity * observed_velocity) / (2.0 * brake_deceleration); + if (dist_to_stopline > braking_distance) { + return false; + } + const auto attention_lanelet = attention_lanelet_opt.value(); + const auto stopline = stopline_opt.value(); + const auto stopline_p1 = stopline.front(); + const auto stopline_p2 = stopline.back(); + const tier4_autoware_utils::Point2d stopline_mid{ + (stopline_p1.x() + stopline_p2.x()) / 2.0, (stopline_p1.y() + stopline_p2.y()) / 2.0}; + const auto attention_lane_end = attention_lanelet.centerline().back(); + const tier4_autoware_utils::LineString2d attention_lane_later_part( + {tier4_autoware_utils::Point2d{stopline_mid.x(), stopline_mid.y()}, + tier4_autoware_utils::Point2d{attention_lane_end.x(), attention_lane_end.y()}}); + std::vector ego_collision_points; + bg::intersection( + attention_lane_later_part, ego_lane.centerline2d().basicLineString(), ego_collision_points); + if (ego_collision_points.empty()) { + return false; + } + const auto expected_collision_point = ego_collision_points.front(); + // distance from object expected stop position to collision point + const double stopline_to_object = -1.0 * dist_to_stopline + braking_distance; + const double stopline_to_ego_path = std::hypot( + expected_collision_point.x() - stopline_mid.x(), + expected_collision_point.y() - stopline_mid.y()); + const double object_to_ego_path = stopline_to_ego_path - stopline_to_object; + // NOTE: if object_to_ego_path < 0, object passed ego path + return object_to_ego_path > tolerable_overshoot; +} + +bool ObjectInfo::before_stopline_by(const double margin) const +{ + if (!dist_to_stopline_opt) { + return false; + } + const double dist_to_stopline = dist_to_stopline_opt.value(); + return dist_to_stopline < margin; +} + +std::shared_ptr ObjectInfoManager::registerObject( + const unique_identifier_msgs::msg::UUID & uuid, const bool belong_attention_area, + const bool belong_intersection_area, const bool is_parked_vehicle) +{ + if (objects_info_.count(uuid) == 0) { + auto object = std::make_shared(uuid); + objects_info_[uuid] = object; + } + auto object = objects_info_[uuid]; + if (belong_attention_area) { + attention_area_objects_.push_back(object); + } else if (belong_intersection_area) { + intersection_area_objects_.push_back(object); + } + if (is_parked_vehicle) { + parked_objects_.push_back(object); + } + return object; +} + +void ObjectInfoManager::registerExistingObject( + const unique_identifier_msgs::msg::UUID & uuid, const bool belong_attention_area, + const bool belong_intersection_area, const bool is_parked_vehicle, + std::shared_ptr object) +{ + objects_info_[uuid] = object; + if (belong_attention_area) { + attention_area_objects_.push_back(object); + } else if (belong_intersection_area) { + intersection_area_objects_.push_back(object); + } + if (is_parked_vehicle) { + parked_objects_.push_back(object); + } +} + +void ObjectInfoManager::clearObjects() +{ + objects_info_.clear(); + attention_area_objects_.clear(); + intersection_area_objects_.clear(); + parked_objects_.clear(); +}; + +std::vector> ObjectInfoManager::allObjects() const +{ + std::vector> all_objects = attention_area_objects_; + all_objects.insert( + all_objects.end(), intersection_area_objects_.begin(), intersection_area_objects_.end()); + all_objects.insert(all_objects.end(), parked_objects_.begin(), parked_objects_.end()); + return all_objects; +} + +std::optional findPassageInterval( + const autoware_auto_perception_msgs::msg::PredictedPath & predicted_path, + const autoware_auto_perception_msgs::msg::Shape & shape, + const lanelet::BasicPolygon2d & ego_lane_poly, + const std::optional & first_attention_lane_opt, + const std::optional & second_attention_lane_opt) +{ + const auto first_itr = std::adjacent_find( + predicted_path.path.cbegin(), predicted_path.path.cend(), [&](const auto & a, const auto & b) { + return bg::intersects(ego_lane_poly, ::createOneStepPolygon(a, b, shape)); + }); + if (first_itr == predicted_path.path.cend()) { + // even the predicted path end does not collide with the beginning of ego_lane_poly + return std::nullopt; + } + const auto last_itr = std::adjacent_find( + predicted_path.path.crbegin(), predicted_path.path.crend(), + [&](const auto & a, const auto & b) { + return bg::intersects(ego_lane_poly, ::createOneStepPolygon(a, b, shape)); + }); + if (last_itr == predicted_path.path.crend()) { + // even the predicted path start does not collide with the end of ego_lane_poly + return std::nullopt; + } + + const size_t enter_idx = static_cast(first_itr - predicted_path.path.begin()); + const double object_enter_time = + static_cast(enter_idx) * rclcpp::Duration(predicted_path.time_step).seconds(); + const size_t exit_idx = std::distance(predicted_path.path.begin(), last_itr.base()) - 1; + const double object_exit_time = + static_cast(exit_idx) * rclcpp::Duration(predicted_path.time_step).seconds(); + const auto lane_position = [&]() { + if (first_attention_lane_opt) { + if (lanelet::geometry::inside( + first_attention_lane_opt.value(), + lanelet::BasicPoint2d(first_itr->position.x, first_itr->position.y))) { + return intersection::CollisionInterval::LanePosition::FIRST; + } + } + if (second_attention_lane_opt) { + if (lanelet::geometry::inside( + second_attention_lane_opt.value(), + lanelet::BasicPoint2d(first_itr->position.x, first_itr->position.y))) { + return intersection::CollisionInterval::LanePosition::SECOND; + } + } + return intersection::CollisionInterval::LanePosition::ELSE; + }(); + + std::vector path; + for (const auto & pose : predicted_path.path) { + path.push_back(pose); + } + return intersection::CollisionInterval{ + lane_position, path, {enter_idx, exit_idx}, {object_enter_time, object_exit_time}}; +} + +} // namespace behavior_velocity_planner::intersection diff --git a/planning/behavior_velocity_intersection_module/src/object_manager.hpp b/planning/behavior_velocity_intersection_module/src/object_manager.hpp new file mode 100644 index 0000000000000..77e39637523a9 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/object_manager.hpp @@ -0,0 +1,294 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OBJECT_MANAGER_HPP_ +#define OBJECT_MANAGER_HPP_ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace std +{ +template <> +struct hash +{ + size_t operator()(const unique_identifier_msgs::msg::UUID & uid) const + { + const auto & ids = uid.uuid; + boost::uuids::uuid u = {ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], + ids[8], ids[9], ids[10], ids[11], ids[12], ids[13], ids[14], ids[15]}; + return boost::hash()(u); + } +}; +} // namespace std + +namespace behavior_velocity_planner::intersection +{ + +/** + * @brief store collision information + */ +struct CollisionInterval +{ + enum LanePosition { + FIRST, + SECOND, + ELSE, + }; + LanePosition lane_position{LanePosition::ELSE}; + + //! original predicted path + std::vector path; + + //! possible collision interval position index on path + std::pair interval_position; + + //! possible collision interval time(without TTC margin) + std::pair interval_time; +}; + +struct CollisionKnowledge +{ + //! the time when the expected collision is judged + rclcpp::Time stamp; + + enum SafeType { + UNSAFE, + SAFE, + SAFE_UNDER_TRAFFIC_CONTROL, + }; + SafeType safe_type{SafeType::UNSAFE}; + + //! if !safe, this has value, and it safe, this maybe null if the predicted path does not + //! intersect with ego path + std::optional interval{std::nullopt}; + + double observed_velocity; +}; + +/** + * @brief store collision information of object on the attention area + */ +class ObjectInfo +{ +public: + explicit ObjectInfo(const unique_identifier_msgs::msg::UUID & uuid); + + const autoware_auto_perception_msgs::msg::PredictedObject & predicted_object() const + { + return predicted_object_; + }; + + std::optional unsafe_info() const + { + if (safe_under_traffic_control_) { + return std::nullopt; + } + if (!unsafe_interval_) { + return std::nullopt; + } + return unsafe_interval_; + } + + bool is_safe_under_traffic_control() const { return safe_under_traffic_control_; } + + /** + * @brief update predicted_object_, attention_lanelet, stopline, dist_to_stopline + */ + void initialize( + const autoware_auto_perception_msgs::msg::PredictedObject & predicted_object, + std::optional attention_lanelet_opt, + std::optional stopline_opt); + + /** + * @brief update unsafe_knowledge + */ + void update_safety( + const std::optional & unsafe_interval_opt, + const std::optional & safe_interval_opt, + const bool safe_under_traffic_control); + + /** + * @brief find the estimated position of the object in the past + */ + std::optional estimated_past_position( + const double past_duration) const; + + /** + * @brief check if object can stop before stopline under the deceleration. return false if + * stopline is null for conservative collision checking + */ + bool can_stop_before_stopline(const double brake_deceleration) const; + + /** + * @brief check if object can stop before stopline within the overshoot margin. return false if + * stopline is null for conservative collision checking + */ + bool can_stop_before_ego_lane( + const double brake_deceleration, const double tolerable_overshoot, + lanelet::ConstLanelet ego_lane) const; + + /** + * @brief check if the object is before the stopline within the specified margin + */ + bool before_stopline_by(const double margin) const; + + void setDecisionAt1stPassJudgeLinePassage(const CollisionKnowledge & knowledge) + { + decision_at_1st_pass_judge_line_passage_ = knowledge; + } + + void setDecisionAt2ndPassJudgeLinePassage(const CollisionKnowledge & knowledge) + { + decision_at_2nd_pass_judge_line_passage_ = knowledge; + } + + const std::optional & unsafe_interval() const { return unsafe_interval_; } + + double observed_velocity() const + { + return predicted_object_.kinematics.initial_twist_with_covariance.twist.linear.x; + } + + const std::optional & decision_at_1st_pass_judge_line_passage() const + { + return decision_at_1st_pass_judge_line_passage_; + } + + const std::optional & decision_at_2nd_pass_judge_line_passage() const + { + return decision_at_2nd_pass_judge_line_passage_; + } + + const std::string uuid_str; + +private: + autoware_auto_perception_msgs::msg::PredictedObject predicted_object_; + + //! null if the object in intersection_area but not in attention_area + std::optional attention_lanelet_opt{std::nullopt}; + + //! null if the object in intersection_area but not in attention_area + std::optional stopline_opt{std::nullopt}; + + //! null if the object in intersection_area but not in attention_area + std::optional dist_to_stopline_opt{std::nullopt}; + + //! store the information if judged as UNSAFE + std::optional unsafe_interval_{std::nullopt}; + + //! store the information if judged as SAFE + std::optional safe_interval_{std::nullopt}; + + //! true if the object is judged as negligible given traffic light color + bool safe_under_traffic_control_{false}; + + std::optional decision_at_1st_pass_judge_line_passage_{std::nullopt}; + std::optional decision_at_2nd_pass_judge_line_passage_{std::nullopt}; + + /** + * @brief calculate/update the distance to corresponding stopline + */ + void calc_dist_to_stopline(); +}; + +/** + * @brief store predicted objects for intersection + */ +class ObjectInfoManager +{ +public: + std::shared_ptr registerObject( + const unique_identifier_msgs::msg::UUID & uuid, const bool belong_attention_area, + const bool belong_intersection_area, const bool is_parked); + + void registerExistingObject( + const unique_identifier_msgs::msg::UUID & uuid, const bool belong_attention_area, + const bool belong_intersection_area, const bool is_parked, + std::shared_ptr object); + + void clearObjects(); + + const std::vector> & attentionObjects() const + { + return attention_area_objects_; + } + + const std::vector> & parkedObjects() const { return parked_objects_; } + + std::vector> allObjects() const; + + const std::unordered_map> & + getObjectsMap() + { + return objects_info_; + } + + void setPassed1stPassJudgeLineFirstTime(const rclcpp::Time & time) + { + passed_1st_judge_line_first_time_ = time; + } + void setPassed2ndPassJudgeLineFirstTime(const rclcpp::Time & time) + { + passed_2nd_judge_line_first_time_ = time; + } + +private: + std::unordered_map> objects_info_; + + //! belong to attention area + std::vector> attention_area_objects_; + + //! does not belong to attention area but to intersection area + std::vector> intersection_area_objects_; + + //! parked objects on attention_area/intersection_area + std::vector> parked_objects_; + + std::optional passed_1st_judge_line_first_time_{std::nullopt}; + std::optional passed_2nd_judge_line_first_time_{std::nullopt}; +}; + +/** + * @brief return the CollisionInterval struct if the predicted path collides ego path geometrically + */ +std::optional findPassageInterval( + const autoware_auto_perception_msgs::msg::PredictedPath & predicted_path, + const autoware_auto_perception_msgs::msg::Shape & shape, + const lanelet::BasicPolygon2d & ego_lane_poly, + const std::optional & first_attention_lane_opt, + const std::optional & second_attention_lane_opt); + +} // namespace behavior_velocity_planner::intersection + +#endif // OBJECT_MANAGER_HPP_ diff --git a/planning/behavior_velocity_intersection_module/src/result.hpp b/planning/behavior_velocity_intersection_module/src/result.hpp new file mode 100644 index 0000000000000..5d82183cee2fb --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/result.hpp @@ -0,0 +1,53 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RESULT_HPP_ +#define RESULT_HPP_ + +#include +#include + +namespace behavior_velocity_planner::intersection +{ + +template +class Result +{ +public: + explicit Result(const Ok & ok) : data_(ok) {} + explicit Result(const Error & err) : data_(err) {} + explicit operator bool() const noexcept { return std::holds_alternative(data_); } + bool operator!() const noexcept { return !static_cast(*this); } + const Ok & ok() const { return std::get(data_); } + const Error & err() const { return std::get(data_); } + +private: + std::variant data_; +}; + +template +Result make_ok(Args &&... args) +{ + return Result(Ok{std::forward(args)...}); +} + +template +Result make_err(Args &&... args) +{ + return Result(Error{std::forward(args)...}); +} + +} // namespace behavior_velocity_planner::intersection + +#endif // RESULT_HPP_ diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index e036fac13fd3b..abbaa802229f7 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -16,102 +16,50 @@ #include "util.hpp" -#include +#include // for toGeomPoly #include +#include #include #include -#include -#include +#include // for toPolygon2d #include #include -#include -#include -#include +#include -#include -#include #include #include -#include +#include +#include #include #include -#include #include -#include #include + namespace behavior_velocity_planner { namespace bg = boost::geometry; -namespace -{ -Polygon2d createOneStepPolygon( - const geometry_msgs::msg::Pose & prev_pose, const geometry_msgs::msg::Pose & next_pose, - const autoware_auto_perception_msgs::msg::Shape & shape) -{ - const auto prev_poly = tier4_autoware_utils::toPolygon2d(prev_pose, shape); - const auto next_poly = tier4_autoware_utils::toPolygon2d(next_pose, shape); - - Polygon2d one_step_poly; - for (const auto & point : prev_poly.outer()) { - one_step_poly.outer().push_back(point); - } - for (const auto & point : next_poly.outer()) { - one_step_poly.outer().push_back(point); - } - - bg::correct(one_step_poly); - - Polygon2d convex_one_step_poly; - bg::convex_hull(one_step_poly, convex_one_step_poly); - - return convex_one_step_poly; -} -} // namespace - -static bool isTargetCollisionVehicleType( - const autoware_auto_perception_msgs::msg::PredictedObject & object) -{ - if ( - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::CAR || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::BUS || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::TRUCK || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::TRAILER || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::MOTORCYCLE || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::BICYCLE) { - return true; - } - return false; -} +using intersection::make_err; +using intersection::make_ok; +using intersection::Result; IntersectionModule::IntersectionModule( const int64_t module_id, const int64_t lane_id, [[maybe_unused]] std::shared_ptr planner_data, const PlannerParam & planner_param, const std::set & associative_ids, - const std::string & turn_direction, const bool has_traffic_light, - const bool enable_occlusion_detection, const bool is_private_area, rclcpp::Node & node, + const std::string & turn_direction, const bool has_traffic_light, rclcpp::Node & node, const rclcpp::Logger logger, const rclcpp::Clock::SharedPtr clock) : SceneModuleInterface(module_id, logger, clock), - node_(node), + planner_param_(planner_param), lane_id_(lane_id), associative_ids_(associative_ids), turn_direction_(turn_direction), has_traffic_light_(has_traffic_light), - enable_occlusion_detection_(enable_occlusion_detection), - occlusion_attention_divisions_(std::nullopt), - is_private_area_(is_private_area), occlusion_uuid_(tier4_autoware_utils::generateUUID()) { velocity_factor_.init(PlanningBehavior::INTERSECTION); - planner_param_ = planner_param; { collision_state_machine_.setMarginTime( @@ -138,14 +86,35 @@ IntersectionModule::IntersectionModule( static_occlusion_timeout_state_machine_.setState(StateMachine::State::STOP); } - decision_state_pub_ = - node_.create_publisher("~/debug/intersection/decision_state", 1); - ego_ttc_pub_ = node_.create_publisher( + ego_ttc_pub_ = node.create_publisher( "~/debug/intersection/ego_ttc", 1); - object_ttc_pub_ = node_.create_publisher( + object_ttc_pub_ = node.create_publisher( "~/debug/intersection/object_ttc", 1); } +bool IntersectionModule::modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) +{ + debug_data_ = DebugData(); + *stop_reason = planning_utils::initializeStopReason(StopReason::INTERSECTION); + + initializeRTCStatus(); + + const auto decision_result = modifyPathVelocityDetail(path, stop_reason); + prev_decision_result_ = decision_result; + + { + const std::string decision_type = "intersection" + std::to_string(module_id_) + " : " + + intersection::formatDecisionResult(decision_result); + internal_debug_data_.decision_type = decision_type; + } + + prepareRTCStatus(decision_result, *path); + + reactRTCApproval(decision_result, path, stop_reason); + + return true; +} + void IntersectionModule::initializeRTCStatus() { setSafe(true); @@ -157,6 +126,362 @@ void IntersectionModule::initializeRTCStatus() // activated_ and occlusion_activated_ must be set from manager's RTC callback } +static std::string formatOcclusionType(const IntersectionModule::OcclusionType & type) +{ + if (std::holds_alternative(type)) { + return "NotOccluded and the best occlusion clearance is " + + std::to_string(std::get(type).best_clearance_distance); + } + if (std::holds_alternative(type)) { + return "StaticallyOccluded and the best occlusion clearance is " + + std::to_string( + std::get(type).best_clearance_distance); + } + if (std::holds_alternative(type)) { + return "DynamicallyOccluded and the best occlusion clearance is " + + std::to_string( + std::get(type).best_clearance_distance); + } + return "RTCOccluded"; +} + +intersection::DecisionResult IntersectionModule::modifyPathVelocityDetail( + PathWithLaneId * path, [[maybe_unused]] StopReason * stop_reason) +{ + const auto prepare_data = prepareIntersectionData(path); + if (!prepare_data) { + return prepare_data.err(); + } + const auto [interpolated_path_info, intersection_stoplines, path_lanelets] = prepare_data.ok(); + const auto & intersection_lanelets = intersection_lanelets_.value(); + + // NOTE: this level is based on the updateTrafficSignalObservation() which is latest + const auto traffic_prioritized_level = getTrafficPrioritizedLevel(); + const bool is_prioritized = + traffic_prioritized_level == TrafficPrioritizedLevel::FULLY_PRIORITIZED; + + // ========================================================================================== + // stuck detection + // + // stuck vehicle detection is viable even if attention area is empty + // so this needs to be checked before attention area validation + // ========================================================================================== + const auto is_stuck_status = isStuckStatus(*path, intersection_stoplines, path_lanelets); + if (is_stuck_status) { + return is_stuck_status.value(); + } + + // ========================================================================================== + // basic data validation + // + // if attention area is empty, collision/occlusion detection is impossible + // + // if attention area is not null but default stop line is not available, ego/backward-path has + // already passed the stop line, so ego is already in the middle of the intersection, or the + // end of the ego path has just entered the entry of this intersection + // + // occlusion stop line is generated from the intersection of ego footprint along the path with the + // attention area, so if this is null, ego has already passed the intersection, or the end of the + // ego path has just entered the entry of this intersection + // ========================================================================================== + if (!intersection_lanelets.first_attention_area()) { + return intersection::InternalError{"attention area is empty"}; + } + const auto first_attention_area = intersection_lanelets.first_attention_area().value(); + const auto default_stopline_idx_opt = intersection_stoplines.default_stopline; + if (!default_stopline_idx_opt) { + return intersection::InternalError{"default stop line is null"}; + } + const auto default_stopline_idx = default_stopline_idx_opt.value(); + const auto first_attention_stopline_idx_opt = intersection_stoplines.first_attention_stopline; + const auto occlusion_peeking_stopline_idx_opt = intersection_stoplines.occlusion_peeking_stopline; + if (!first_attention_stopline_idx_opt || !occlusion_peeking_stopline_idx_opt) { + return intersection::InternalError{"occlusion stop line is null"}; + } + const auto first_attention_stopline_idx = first_attention_stopline_idx_opt.value(); + const auto occlusion_stopline_idx = occlusion_peeking_stopline_idx_opt.value(); + + // ========================================================================================== + // classify the objects to attention_area/intersection_area and update their position, velocity, + // belonging attention lanelet, distance to corresponding stopline + // ========================================================================================== + updateObjectInfoManagerArea(); + + // ========================================================================================== + // occlusion_status is type of occlusion, + // is_occlusion_cleared_with_margin indicates if occlusion is physically detected + // is_occlusion_state indicates if occlusion is detected. OR occlusion is not detected but RTC for + // intersection_occlusion is disapproved, which means ego is virtually occluded + // + // so is_occlusion_cleared_with_margin should be sent to RTC as module decision + // and is_occlusion_status should be only used to decide ego action + // !is_occlusion_state == !physically_occluded && !externally_occluded, so even if occlusion is + // not detected but not approved, SAFE is not sent. + // ========================================================================================== + const auto [occlusion_status, is_occlusion_cleared_with_margin, is_occlusion_state] = + getOcclusionStatus(traffic_prioritized_level, interpolated_path_info); + + const auto + [is_over_1st_pass_judge_line, is_over_2nd_pass_judge_line, safely_passed_1st_judge_line, + safely_passed_2nd_judge_line] = + isOverPassJudgeLinesStatus(*path, is_occlusion_state, intersection_stoplines); + + // ========================================================================================== + // calculate the expected vehicle speed and obtain the spatiotemporal profile of ego to the + // exit of intersection + // ========================================================================================== + tier4_debug_msgs::msg::Float64MultiArrayStamped ego_ttc_time_array; + const auto time_distance_array = + calcIntersectionPassingTime(*path, is_prioritized, intersection_stoplines, &ego_ttc_time_array); + + // ========================================================================================== + // run collision checking for each objects considering traffic light level. Also if ego just + // passed each pass judge line for the first time, save current collision status for late + // diagnosis + // ========================================================================================== + tier4_debug_msgs::msg::Float64MultiArrayStamped object_ttc_time_array; + updateObjectInfoManagerCollision( + path_lanelets, time_distance_array, traffic_prioritized_level, safely_passed_1st_judge_line, + safely_passed_2nd_judge_line, &object_ttc_time_array); + { + const auto & debug = planner_param_.debug.ttc; + if ( + std::find(debug.begin(), debug.end(), lane_id_) != debug.end() || + std::find(debug.begin(), debug.end(), -1) != debug.end()) { + ego_ttc_pub_->publish(ego_ttc_time_array); + object_ttc_pub_->publish(object_ttc_time_array); + } + } + + for (const auto & object_info : object_info_manager_.attentionObjects()) { + if (!object_info->unsafe_info()) { + continue; + } + setObjectsOfInterestData( + object_info->predicted_object().kinematics.initial_pose_with_covariance.pose, + object_info->predicted_object().shape, ColorName::RED); + } + + const auto [has_collision, collision_position, too_late_detect_objects, misjudge_objects] = + detectCollision(is_over_1st_pass_judge_line, is_over_2nd_pass_judge_line); + const std::string safety_diag = + generateDetectionBlameDiagnosis(too_late_detect_objects, misjudge_objects); + const std::string occlusion_diag = formatOcclusionType(occlusion_status); + + if (is_permanent_go_) { + if (has_collision) { + const auto closest_idx = intersection_stoplines.closest_idx; + const std::string evasive_diag = generateEgoRiskEvasiveDiagnosis( + *path, closest_idx, time_distance_array, too_late_detect_objects, misjudge_objects); + return intersection::OverPassJudge{safety_diag, evasive_diag}; + } + return intersection::OverPassJudge{ + "no collision is detected", "ego can safely pass the intersection at this rate"}; + } + + // ========================================================================================== + // this state is very dangerous because ego is very close/over the boundary of 1st attention lane + // and collision is detected on the 1st lane. Since the 2nd attention lane also exists in this + // case, possible another collision may be expected on the 2nd attention lane too. + // ========================================================================================== + std::string safety_report = safety_diag; + if (const bool collision_on_1st_attention_lane = + has_collision && + (collision_position == intersection::CollisionInterval::LanePosition::FIRST); + is_over_1st_pass_judge_line && is_over_2nd_pass_judge_line.has_value() && + !is_over_2nd_pass_judge_line.value() && collision_on_1st_attention_lane) { + safety_report += + "\nego is between the 1st and 2nd pass judge line but collision is expected on the 1st " + "attention lane, which is dangerous."; + } + + const auto closest_idx = intersection_stoplines.closest_idx; + const bool is_over_default_stopline = util::isOverTargetIndex( + *path, closest_idx, planner_data_->current_odometry->pose, default_stopline_idx); + const auto collision_stopline_idx = is_over_default_stopline ? closest_idx : default_stopline_idx; + + // ========================================================================================== + // pseudo collision detection on green light + // ========================================================================================== + const auto is_green_pseudo_collision_status = + isGreenPseudoCollisionStatus(closest_idx, collision_stopline_idx, intersection_stoplines); + if (is_green_pseudo_collision_status) { + return is_green_pseudo_collision_status.value(); + } + + // ========================================================================================== + // yield stuck detection + // ========================================================================================== + const auto is_yield_stuck_status = + isYieldStuckStatus(*path, interpolated_path_info, intersection_stoplines); + if (is_yield_stuck_status) { + auto yield_stuck = is_yield_stuck_status.value(); + yield_stuck.occlusion_report = occlusion_diag; + return yield_stuck; + } + + collision_state_machine_.setStateWithMarginTime( + has_collision ? StateMachine::State::STOP : StateMachine::State::GO, + logger_.get_child("collision state_machine"), *clock_); + const bool has_collision_with_margin = + collision_state_machine_.getState() == StateMachine::State::STOP; + + if (is_prioritized) { + return intersection::FullyPrioritized{ + has_collision_with_margin, closest_idx, collision_stopline_idx, occlusion_stopline_idx, + safety_report}; + } + + // Safe + if (!is_occlusion_state && !has_collision_with_margin) { + return intersection::Safe{ + closest_idx, collision_stopline_idx, occlusion_stopline_idx, occlusion_diag}; + } + // Only collision + if (!is_occlusion_state && has_collision_with_margin) { + return intersection::NonOccludedCollisionStop{ + closest_idx, collision_stopline_idx, occlusion_stopline_idx, occlusion_diag}; + } + // Occluded + // utility functions + auto fromEgoDist = [&](const size_t index) { + return motion_utils::calcSignedArcLength(path->points, closest_idx, index); + }; + auto stoppedForDuration = + [&](const size_t pos, const double duration, StateMachine & state_machine) { + const double dist_stopline = fromEgoDist(pos); + const bool approached_dist_stopline = + (std::fabs(dist_stopline) < planner_param_.common.stopline_overshoot_margin); + const bool over_stopline = (dist_stopline < 0.0); + const bool is_stopped_duration = planner_data_->isVehicleStopped(duration); + if (over_stopline) { + state_machine.setState(StateMachine::State::GO); + } else if (is_stopped_duration && approached_dist_stopline) { + state_machine.setState(StateMachine::State::GO); + } + return state_machine.getState() == StateMachine::State::GO; + }; + auto stoppedAtPosition = [&](const size_t pos, const double duration) { + const double dist_stopline = fromEgoDist(pos); + const bool approached_dist_stopline = + (std::fabs(dist_stopline) < planner_param_.common.stopline_overshoot_margin); + const bool over_stopline = (dist_stopline < -planner_param_.common.stopline_overshoot_margin); + const bool is_stopped = planner_data_->isVehicleStopped(duration); + if (over_stopline) { + return true; + } else if (is_stopped && approached_dist_stopline) { + return true; + } + return false; + }; + + const auto occlusion_wo_tl_pass_judge_line_idx = + intersection_stoplines.occlusion_wo_tl_pass_judge_line; + const bool stopped_at_default_line = stoppedForDuration( + default_stopline_idx, planner_param_.occlusion.temporal_stop_time_before_peeking, + before_creep_state_machine_); + if (stopped_at_default_line) { + // ========================================================================================== + // if specified the parameter occlusion.temporal_stop_before_attention_area OR + // has_no_traffic_light_, ego will temporarily stop before entering attention area + // ========================================================================================== + const bool temporal_stop_before_attention_required = + (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) + ? !stoppedForDuration( + first_attention_stopline_idx, + planner_param_.occlusion.temporal_stop_time_before_peeking, + temporal_stop_before_attention_state_machine_) + : false; + if (!has_traffic_light_) { + if (fromEgoDist(occlusion_wo_tl_pass_judge_line_idx) < 0) { + if (has_collision) { + const auto closest_idx = intersection_stoplines.closest_idx; + const std::string evasive_diag = generateEgoRiskEvasiveDiagnosis( + *path, closest_idx, time_distance_array, too_late_detect_objects, misjudge_objects); + return intersection::OverPassJudge{ + "already passed maximum peeking line in the absence of traffic light.\n" + + safety_report, + evasive_diag}; + } + return intersection::OverPassJudge{ + "already passed maximum peeking line in the absence of traffic light safely", + "no evasive action required"}; + } + return intersection::OccludedAbsenceTrafficLight{ + is_occlusion_cleared_with_margin, + has_collision_with_margin, + temporal_stop_before_attention_required, + closest_idx, + first_attention_stopline_idx, + occlusion_wo_tl_pass_judge_line_idx, + occlusion_diag}; + } + + // ========================================================================================== + // following remaining block is "has_traffic_light_" + // + // if ego is stuck by static occlusion in the presence of traffic light, start timeout count + // ========================================================================================== + const bool is_static_occlusion = std::holds_alternative(occlusion_status); + const bool is_stuck_by_static_occlusion = + stoppedAtPosition( + occlusion_stopline_idx, planner_param_.occlusion.temporal_stop_time_before_peeking) && + is_static_occlusion; + if (has_collision_with_margin) { + // if collision is detected, timeout is reset + static_occlusion_timeout_state_machine_.setState(StateMachine::State::STOP); + } else if (is_stuck_by_static_occlusion) { + static_occlusion_timeout_state_machine_.setStateWithMarginTime( + StateMachine::State::GO, logger_.get_child("static_occlusion"), *clock_); + } + const bool release_static_occlusion_stuck = + (static_occlusion_timeout_state_machine_.getState() == StateMachine::State::GO); + if (!has_collision_with_margin && release_static_occlusion_stuck) { + return intersection::Safe{ + closest_idx, collision_stopline_idx, occlusion_stopline_idx, occlusion_diag}; + } + // occlusion_status is either STATICALLY_OCCLUDED or DYNAMICALLY_OCCLUDED + const double max_timeout = + planner_param_.occlusion.static_occlusion_with_traffic_light_timeout + + planner_param_.occlusion.occlusion_detection_hold_time; + const std::optional static_occlusion_timeout = + is_stuck_by_static_occlusion + ? std::make_optional( + max_timeout - static_occlusion_timeout_state_machine_.getDuration() - + occlusion_stop_state_machine_.getDuration()) + : (is_static_occlusion ? std::make_optional(max_timeout) : std::nullopt); + if (has_collision_with_margin) { + return intersection::OccludedCollisionStop{ + is_occlusion_cleared_with_margin, + temporal_stop_before_attention_required, + closest_idx, + collision_stopline_idx, + first_attention_stopline_idx, + occlusion_stopline_idx, + static_occlusion_timeout, + occlusion_diag}; + } else { + return intersection::PeekingTowardOcclusion{ + is_occlusion_cleared_with_margin, + temporal_stop_before_attention_required, + closest_idx, + collision_stopline_idx, + first_attention_stopline_idx, + occlusion_stopline_idx, + static_occlusion_timeout, + occlusion_diag}; + } + } else { + const auto occlusion_stopline = + (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) + ? first_attention_stopline_idx + : occlusion_stopline_idx; + return intersection::FirstWaitBeforeOcclusion{ + is_occlusion_cleared_with_margin, closest_idx, default_stopline_idx, occlusion_stopline, + occlusion_diag}; + } +} + // template-specification based visitor pattern // https://en.cppreference.com/w/cpp/utility/variant/visit template @@ -179,7 +504,17 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - [[maybe_unused]] const IntersectionModule::Indecisive & result, + [[maybe_unused]] const intersection::InternalError & result, + [[maybe_unused]] const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + [[maybe_unused]] bool * default_safety, [[maybe_unused]] double * default_distance, + [[maybe_unused]] bool * occlusion_safety, [[maybe_unused]] double * occlusion_distance) +{ + return; +} + +template <> +void prepareRTCByDecisionResult( + [[maybe_unused]] const intersection::OverPassJudge & result, [[maybe_unused]] const autoware_auto_planning_msgs::msg::PathWithLaneId & path, [[maybe_unused]] bool * default_safety, [[maybe_unused]] double * default_distance, [[maybe_unused]] bool * occlusion_safety, [[maybe_unused]] double * occlusion_distance) @@ -189,7 +524,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::StuckStop & result, + const intersection::StuckStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -209,7 +544,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::YieldStuckStop & result, + const intersection::YieldStuckStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, [[maybe_unused]] double * occlusion_distance) { @@ -224,7 +559,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::NonOccludedCollisionStop & result, + const intersection::NonOccludedCollisionStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -243,7 +578,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::FirstWaitBeforeOcclusion & result, + const intersection::FirstWaitBeforeOcclusion & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -262,7 +597,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::PeekingTowardOcclusion & result, + const intersection::PeekingTowardOcclusion & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -281,7 +616,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::OccludedAbsenceTrafficLight & result, + const intersection::OccludedAbsenceTrafficLight & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -298,7 +633,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::OccludedCollisionStop & result, + const intersection::OccludedCollisionStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -317,9 +652,9 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::Safe & result, - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance) + const intersection::Safe & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + bool * default_safety, double * default_distance, bool * occlusion_safety, + double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "Safe"); const auto closest_idx = result.closest_idx; @@ -336,7 +671,7 @@ void prepareRTCByDecisionResult( template <> void prepareRTCByDecisionResult( - const IntersectionModule::FullyPrioritized & result, + const intersection::FullyPrioritized & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, double * occlusion_distance) { @@ -354,7 +689,7 @@ void prepareRTCByDecisionResult( } void IntersectionModule::prepareRTCStatus( - const DecisionResult & decision_result, + const intersection::DecisionResult & decision_result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path) { bool default_safety = true; @@ -369,7 +704,7 @@ void IntersectionModule::prepareRTCStatus( setSafe(default_safety); setDistance(default_distance); occlusion_first_stop_required_ = - std::holds_alternative(decision_result); + std::holds_alternative(decision_result); } template @@ -377,7 +712,7 @@ void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, const T & decision_result, const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, StopReason * stop_reason, - VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + VelocityFactorInterface * velocity_factor, IntersectionModule::DebugData * debug_data) { static_assert("Unsupported type passed to reactRTCByDecisionResult"); return; @@ -387,13 +722,28 @@ template <> void reactRTCApprovalByDecisionResult( [[maybe_unused]] const bool rtc_default_approved, [[maybe_unused]] const bool rtc_occlusion_approved, - [[maybe_unused]] const IntersectionModule::Indecisive & decision_result, + [[maybe_unused]] const intersection::InternalError & decision_result, + [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, + [[maybe_unused]] const double baselink2front, + [[maybe_unused]] autoware_auto_planning_msgs::msg::PathWithLaneId * path, + [[maybe_unused]] StopReason * stop_reason, + [[maybe_unused]] VelocityFactorInterface * velocity_factor, + [[maybe_unused]] IntersectionModule::DebugData * debug_data) +{ + return; +} + +template <> +void reactRTCApprovalByDecisionResult( + [[maybe_unused]] const bool rtc_default_approved, + [[maybe_unused]] const bool rtc_occlusion_approved, + [[maybe_unused]] const intersection::OverPassJudge & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, [[maybe_unused]] const double baselink2front, [[maybe_unused]] autoware_auto_planning_msgs::msg::PathWithLaneId * path, [[maybe_unused]] StopReason * stop_reason, [[maybe_unused]] VelocityFactorInterface * velocity_factor, - [[maybe_unused]] util::DebugData * debug_data) + [[maybe_unused]] IntersectionModule::DebugData * debug_data) { return; } @@ -401,10 +751,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::StuckStop & decision_result, + const intersection::StuckStop & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -420,16 +771,14 @@ void reactRTCApprovalByDecisionResult( { tier4_planning_msgs::msg::StopFactor stop_factor; stop_factor.stop_pose = path->points.at(stopline_idx).point.pose; - stop_factor.stop_factor_points = planning_utils::toRosPoints(debug_data->conflicting_targets); + stop_factor.stop_factor_points = planning_utils::toRosPoints(debug_data->unsafe_targets); planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( path->points, path->points.at(closest_idx).point.pose, path->points.at(stopline_idx).point.pose, VelocityFactor::UNKNOWN); } } - if ( - !rtc_occlusion_approved && decision_result.occlusion_stopline_idx && - planner_param.occlusion.enable) { + if (!rtc_occlusion_approved && decision_result.occlusion_stopline_idx) { const auto occlusion_stopline_idx = decision_result.occlusion_stopline_idx.value(); planning_utils::setVelocityFromIndex(occlusion_stopline_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = @@ -449,10 +798,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::YieldStuckStop & decision_result, + const intersection::YieldStuckStop & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -468,7 +818,7 @@ void reactRTCApprovalByDecisionResult( { tier4_planning_msgs::msg::StopFactor stop_factor; stop_factor.stop_pose = path->points.at(stopline_idx).point.pose; - stop_factor.stop_factor_points = planning_utils::toRosPoints(debug_data->conflicting_targets); + stop_factor.stop_factor_points = planning_utils::toRosPoints(debug_data->unsafe_targets); planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( path->points, path->points.at(closest_idx).point.pose, @@ -481,10 +831,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::NonOccludedCollisionStop & decision_result, + const intersection::NonOccludedCollisionStop & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -504,7 +855,7 @@ void reactRTCApprovalByDecisionResult( path->points.at(stopline_idx).point.pose, VelocityFactor::UNKNOWN); } } - if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + if (!rtc_occlusion_approved) { const auto stopline_idx = decision_result.occlusion_stopline_idx; planning_utils::setVelocityFromIndex(stopline_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = @@ -524,10 +875,10 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::FirstWaitBeforeOcclusion & decision_result, + const intersection::FirstWaitBeforeOcclusion & decision_result, const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, StopReason * stop_reason, - VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + VelocityFactorInterface * velocity_factor, IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -547,7 +898,7 @@ void reactRTCApprovalByDecisionResult( path->points.at(stopline_idx).point.pose, VelocityFactor::UNKNOWN); } } - if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + if (!rtc_occlusion_approved) { if (planner_param.occlusion.creep_during_peeking.enable) { const size_t occlusion_peeking_stopline = decision_result.occlusion_stopline_idx; const size_t closest_idx = decision_result.closest_idx; @@ -575,18 +926,17 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::PeekingTowardOcclusion & decision_result, + const intersection::PeekingTowardOcclusion & decision_result, const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, StopReason * stop_reason, - VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + VelocityFactorInterface * velocity_factor, IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), "PeekingTowardOcclusion, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); // NOTE: creep_velocity should be inserted first at closest_idx if !rtc_default_approved - - if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + if (!rtc_occlusion_approved) { const size_t occlusion_peeking_stopline = decision_result.temporal_stop_before_attention_required ? decision_result.first_attention_stopline_idx @@ -632,10 +982,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::OccludedCollisionStop & decision_result, + const intersection::OccludedCollisionStop & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -655,7 +1006,7 @@ void reactRTCApprovalByDecisionResult( path->points.at(stopline_idx).point.pose, VelocityFactor::UNKNOWN); } } - if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + if (!rtc_occlusion_approved) { const auto stopline_idx = decision_result.temporal_stop_before_attention_required ? decision_result.first_attention_stopline_idx : decision_result.occlusion_stopline_idx; @@ -679,10 +1030,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::OccludedAbsenceTrafficLight & decision_result, + const intersection::OccludedAbsenceTrafficLight & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -732,10 +1084,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::Safe & decision_result, + const intersection::Safe & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -754,7 +1107,7 @@ void reactRTCApprovalByDecisionResult( path->points.at(stopline_idx).point.pose, VelocityFactor::UNKNOWN); } } - if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + if (!rtc_occlusion_approved) { const auto stopline_idx = decision_result.occlusion_stopline_idx; planning_utils::setVelocityFromIndex(stopline_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = @@ -774,10 +1127,11 @@ void reactRTCApprovalByDecisionResult( template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, - const IntersectionModule::FullyPrioritized & decision_result, + const intersection::FullyPrioritized & decision_result, [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, - StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, + IntersectionModule::DebugData * debug_data) { RCLCPP_DEBUG( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), @@ -797,7 +1151,7 @@ void reactRTCApprovalByDecisionResult( path->points.at(stopline_idx).point.pose, VelocityFactor::UNKNOWN); } } - if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + if (!rtc_occlusion_approved) { const auto stopline_idx = decision_result.occlusion_stopline_idx; planning_utils::setVelocityFromIndex(stopline_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = @@ -814,1356 +1168,260 @@ void reactRTCApprovalByDecisionResult( return; } -void reactRTCApproval( - const bool rtc_default_approval, const bool rtc_occlusion_approval, - const IntersectionModule::DecisionResult & decision_result, - const IntersectionModule::PlannerParam & planner_param, const double baselink2front, - autoware_auto_planning_msgs::msg::PathWithLaneId * path, StopReason * stop_reason, - VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) +void IntersectionModule::reactRTCApproval( + const intersection::DecisionResult & decision_result, + autoware_auto_planning_msgs::msg::PathWithLaneId * path, StopReason * stop_reason) { + const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; std::visit( VisitorSwitch{[&](const auto & decision) { reactRTCApprovalByDecisionResult( - rtc_default_approval, rtc_occlusion_approval, decision, planner_param, baselink2front, path, - stop_reason, velocity_factor, debug_data); + activated_, occlusion_activated_, decision, planner_param_, baselink2front, path, + stop_reason, &velocity_factor_, &debug_data_); }}, decision_result); return; } -static std::string formatDecisionResult(const IntersectionModule::DecisionResult & decision_result) +bool IntersectionModule::isGreenSolidOn() const { - if (std::holds_alternative(decision_result)) { - const auto indecisive = std::get(decision_result); - return "Indecisive because " + indecisive.error; - } - if (std::holds_alternative(decision_result)) { - return "Safe"; - } - if (std::holds_alternative(decision_result)) { - return "StuckStop"; - } - if (std::holds_alternative(decision_result)) { - return "YieldStuckStop"; - } - if (std::holds_alternative(decision_result)) { - return "NonOccludedCollisionStop"; - } - if (std::holds_alternative(decision_result)) { - return "FirstWaitBeforeOcclusion"; - } - if (std::holds_alternative(decision_result)) { - return "PeekingTowardOcclusion"; - } - if (std::holds_alternative(decision_result)) { - return "OccludedCollisionStop"; - } - if (std::holds_alternative(decision_result)) { - return "OccludedAbsenceTrafficLight"; + using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; + + if (!last_tl_valid_observation_) { + return false; } - if (std::holds_alternative(decision_result)) { - return "FullyPrioritized"; + const auto & tl_info = last_tl_valid_observation_.value(); + for (auto && tl_light : tl_info.signal.elements) { + if ( + tl_light.color == TrafficSignalElement::GREEN && + tl_light.shape == TrafficSignalElement::CIRCLE) { + return true; + } } - return ""; + return false; } -bool IntersectionModule::modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) +IntersectionModule::TrafficPrioritizedLevel IntersectionModule::getTrafficPrioritizedLevel() const { - debug_data_ = util::DebugData(); - *stop_reason = planning_utils::initializeStopReason(StopReason::INTERSECTION); - - // set default RTC - initializeRTCStatus(); + using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; - // calculate the - const auto decision_result = modifyPathVelocityDetail(path, stop_reason); - prev_decision_result_ = decision_result; - - const std::string decision_type = - "intersection" + std::to_string(module_id_) + " : " + formatDecisionResult(decision_result); - std_msgs::msg::String decision_result_msg; - decision_result_msg.data = decision_type; - decision_state_pub_->publish(decision_result_msg); - - prepareRTCStatus(decision_result, *path); - - const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; - reactRTCApproval( - activated_, occlusion_activated_, decision_result, planner_param_, baselink2front, path, - stop_reason, &velocity_factor_, &debug_data_); - - if (!activated_ || !occlusion_activated_) { - is_go_out_ = false; - } else { - is_go_out_ = true; - } - RCLCPP_DEBUG(logger_, "===== plan end ====="); - return true; -} - -static bool isGreenSolidOn( - lanelet::ConstLanelet lane, const std::map & tl_infos) -{ - using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; - - std::optional tl_id = std::nullopt; - for (auto && tl_reg_elem : lane.regulatoryElementsAs()) { - tl_id = tl_reg_elem->id(); - break; - } - if (!tl_id) { - // this lane has no traffic light - return false; - } - const auto tl_info_it = tl_infos.find(tl_id.value()); - if (tl_info_it == tl_infos.end()) { - // the info of this traffic light is not available - return false; - } - const auto & tl_info = tl_info_it->second; - for (auto && tl_light : tl_info.signal.elements) { - if ( - tl_light.color == TrafficSignalElement::GREEN && - tl_light.shape == TrafficSignalElement::CIRCLE) { + auto corresponding_arrow = [&](const TrafficSignalElement & element) { + if (turn_direction_ == "straight" && element.shape == TrafficSignalElement::UP_ARROW) { return true; } - } - return false; -} - -IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( - PathWithLaneId * path, [[maybe_unused]] StopReason * stop_reason) -{ - const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); - const auto routing_graph_ptr = planner_data_->route_handler_->getRoutingGraphPtr(); - const auto & assigned_lanelet = lanelet_map_ptr->laneletLayer.get(lane_id_); - const std::string turn_direction = assigned_lanelet.attributeOr("turn_direction", "else"); - const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; - - // spline interpolation - const auto interpolated_path_info_opt = util::generateInterpolatedPath( - lane_id_, associative_ids_, *path, planner_param_.common.path_interpolation_ds, logger_); - if (!interpolated_path_info_opt) { - return IntersectionModule::Indecisive{"splineInterpolate failed"}; - } - const auto & interpolated_path_info = interpolated_path_info_opt.value(); - if (!interpolated_path_info.lane_id_interval) { - return IntersectionModule::Indecisive{ - "Path has no interval on intersection lane " + std::to_string(lane_id_)}; - } - - // cache intersection lane information because it is invariant - const auto & current_pose = planner_data_->current_odometry->pose; - const auto lanelets_on_path = - planning_utils::getLaneletsOnPath(*path, lanelet_map_ptr, current_pose); - if (!intersection_lanelets_) { - intersection_lanelets_ = util::getObjectiveLanelets( - lanelet_map_ptr, routing_graph_ptr, assigned_lanelet, lanelets_on_path, associative_ids_, - planner_param_.common.attention_area_length, - planner_param_.occlusion.occlusion_attention_area_length, - planner_param_.collision_detection.consider_wrong_direction_vehicle); - } - auto & intersection_lanelets = intersection_lanelets_.value(); - - // at the very first time of registration of this module, the path may not be conflicting with the - // attention area, so update() is called to update the internal data as well as traffic light info - const auto traffic_prioritized_level = - util::getTrafficPrioritizedLevel(assigned_lanelet, planner_data_->traffic_light_id_map); - const bool is_prioritized = - traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED; - const auto footprint = planner_data_->vehicle_info_.createFootprint(0.0, 0.0); - intersection_lanelets.update(is_prioritized, interpolated_path_info, footprint, baselink2front); - - // this is abnormal - const auto & conflicting_lanelets = intersection_lanelets.conflicting(); - const auto & first_conflicting_area_opt = intersection_lanelets.first_conflicting_area(); - const auto & first_conflicting_lane_opt = intersection_lanelets.first_conflicting_lane(); - if (conflicting_lanelets.empty() || !first_conflicting_area_opt || !first_conflicting_lane_opt) { - return IntersectionModule::Indecisive{"conflicting area is empty"}; - } - const auto & first_conflicting_lane = first_conflicting_lane_opt.value(); - const auto & first_conflicting_area = first_conflicting_area_opt.value(); - - // generate all stop line candidates - // see the doc for struct IntersectionStopLines - const auto & first_attention_area_opt = intersection_lanelets.first_attention_area(); - /// even if the attention area is null, stuck vehicle stop line needs to be generated from - /// conflicting lanes - const auto & dummy_first_attention_area = - first_attention_area_opt ? first_attention_area_opt.value() : first_conflicting_area; - const auto & dummy_first_attention_lane_centerline = - intersection_lanelets.first_attention_lane() - ? intersection_lanelets.first_attention_lane().value().centerline2d() - : first_conflicting_lane.centerline2d(); - const auto intersection_stoplines_opt = util::generateIntersectionStopLines( - first_conflicting_area, dummy_first_attention_area, dummy_first_attention_lane_centerline, - planner_data_, interpolated_path_info, planner_param_.stuck_vehicle.use_stuck_stopline, - planner_param_.common.default_stopline_margin, planner_param_.common.max_accel, - planner_param_.common.max_jerk, planner_param_.common.delay_response_time, - planner_param_.occlusion.peeking_offset, path); - if (!intersection_stoplines_opt) { - return IntersectionModule::Indecisive{"failed to generate intersection_stoplines"}; - } - const auto & intersection_stoplines = intersection_stoplines_opt.value(); - const auto - [closest_idx, stuck_stopline_idx_opt, default_stopline_idx_opt, - first_attention_stopline_idx_opt, occlusion_peeking_stopline_idx_opt, - default_pass_judge_line_idx, occlusion_wo_tl_pass_judge_line_idx] = intersection_stoplines; - - // see the doc for struct PathLanelets - const auto & conflicting_area = intersection_lanelets.conflicting_area(); - const auto path_lanelets_opt = util::generatePathLanelets( - lanelets_on_path, interpolated_path_info, associative_ids_, first_conflicting_area, - conflicting_area, first_attention_area_opt, intersection_lanelets.attention_area(), closest_idx, - planner_data_->vehicle_info_.vehicle_width_m); - if (!path_lanelets_opt.has_value()) { - return IntersectionModule::Indecisive{"failed to generate PathLanelets"}; - } - const auto path_lanelets = path_lanelets_opt.value(); - - // utility functions - auto fromEgoDist = [&](const size_t index) { - return motion_utils::calcSignedArcLength(path->points, closest_idx, index); - }; - auto stoppedForDuration = - [&](const size_t pos, const double duration, StateMachine & state_machine) { - const double dist_stopline = fromEgoDist(pos); - const bool approached_dist_stopline = - (std::fabs(dist_stopline) < planner_param_.common.stopline_overshoot_margin); - const bool over_stopline = (dist_stopline < 0.0); - const bool is_stopped_duration = planner_data_->isVehicleStopped(duration); - if (over_stopline) { - state_machine.setState(StateMachine::State::GO); - } else if (is_stopped_duration && approached_dist_stopline) { - state_machine.setState(StateMachine::State::GO); - } - return state_machine.getState() == StateMachine::State::GO; - }; - auto stoppedAtPosition = [&](const size_t pos, const double duration) { - const double dist_stopline = fromEgoDist(pos); - const bool approached_dist_stopline = - (std::fabs(dist_stopline) < planner_param_.common.stopline_overshoot_margin); - const bool over_stopline = (dist_stopline < -planner_param_.common.stopline_overshoot_margin); - const bool is_stopped = planner_data_->isVehicleStopped(duration); - if (over_stopline) { + if (turn_direction_ == "left" && element.shape == TrafficSignalElement::LEFT_ARROW) { return true; - } else if (is_stopped && approached_dist_stopline) { + } + if (turn_direction_ == "right" && element.shape == TrafficSignalElement::RIGHT_ARROW) { return true; } return false; }; - // stuck vehicle detection is viable even if attention area is empty - // so this needs to be checked before attention area validation - const bool stuck_detected = checkStuckVehicle(planner_data_, path_lanelets); - if (stuck_detected) { - if (is_private_area_ && planner_param_.stuck_vehicle.enable_private_area_stuck_disregard) { - } else { - std::optional stopline_idx = std::nullopt; - if (stuck_stopline_idx_opt) { - const bool is_over_stuck_stopline = fromEgoDist(stuck_stopline_idx_opt.value()) < - -planner_param_.common.stopline_overshoot_margin; - if (!is_over_stuck_stopline) { - stopline_idx = stuck_stopline_idx_opt.value(); - } - } - if (!stopline_idx) { - if (default_stopline_idx_opt && fromEgoDist(default_stopline_idx_opt.value()) >= 0.0) { - stopline_idx = default_stopline_idx_opt.value(); - } else if ( - first_attention_stopline_idx_opt && - fromEgoDist(first_attention_stopline_idx_opt.value()) >= 0.0) { - stopline_idx = closest_idx; - } + // ========================================================================================== + // if no traffic light information has been available, it is UNKNOWN state which is treated as + // NOT_PRIORITIZED + // + // if there has been any information available in the past more than once, the last valid + // information is kept and used for planning + // ========================================================================================== + auto level = TrafficPrioritizedLevel::NOT_PRIORITIZED; + if (last_tl_valid_observation_) { + auto color = TrafficSignalElement::GREEN; + const auto & tl_info = last_tl_valid_observation_.value(); + bool has_amber_signal{false}; + for (auto && tl_light : tl_info.signal.elements) { + if ( + tl_light.color == TrafficSignalElement::AMBER && + tl_light.shape == TrafficSignalElement::CIRCLE) { + has_amber_signal = true; } - if (stopline_idx) { - return IntersectionModule::StuckStop{ - closest_idx, stopline_idx.value(), occlusion_peeking_stopline_idx_opt}; + if ( + (tl_light.color == TrafficSignalElement::RED && + tl_light.shape == TrafficSignalElement::CIRCLE) || + (tl_light.color == TrafficSignalElement::GREEN && corresponding_arrow(tl_light))) { + // NOTE: Return here since the red signal has the highest priority. + level = TrafficPrioritizedLevel::FULLY_PRIORITIZED; + color = TrafficSignalElement::RED; + break; } } + if (has_amber_signal) { + level = TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED; + color = TrafficSignalElement::AMBER; + } + if (tl_id_and_point_) { + const auto [tl_id, point] = tl_id_and_point_.value(); + debug_data_.traffic_light_observation = + std::make_tuple(planner_data_->current_odometry->pose, point, tl_id, color); + } } + return level; +} - // if attention area is empty, collision/occlusion detection is impossible - if (!first_attention_area_opt) { - return IntersectionModule::Indecisive{"attention area is empty"}; - } - const auto first_attention_area = first_attention_area_opt.value(); - - // if attention area is not null but default stop line is not available, ego/backward-path has - // already passed the stop line - if (!default_stopline_idx_opt) { - return IntersectionModule::Indecisive{"default stop line is null"}; - } - // occlusion stop line is generated from the intersection of ego footprint along the path with the - // attention area, so if this is null, eog has already passed the intersection - if (!first_attention_stopline_idx_opt || !occlusion_peeking_stopline_idx_opt) { - return IntersectionModule::Indecisive{"occlusion stop line is null"}; - } - const auto default_stopline_idx = default_stopline_idx_opt.value(); - const bool is_over_default_stopline = - util::isOverTargetIndex(*path, closest_idx, current_pose, default_stopline_idx); - const auto collision_stopline_idx = is_over_default_stopline ? closest_idx : default_stopline_idx; - const auto first_attention_stopline_idx = first_attention_stopline_idx_opt.value(); - const auto occlusion_stopline_idx = occlusion_peeking_stopline_idx_opt.value(); - - const auto & adjacent_lanelets = intersection_lanelets.adjacent(); - const auto & occlusion_attention_lanelets = intersection_lanelets.occlusion_attention(); - const auto & occlusion_attention_area = intersection_lanelets.occlusion_attention_area(); - debug_data_.attention_area = intersection_lanelets.attention_area(); - debug_data_.occlusion_attention_area = occlusion_attention_area; - debug_data_.adjacent_area = intersection_lanelets.adjacent_area(); - - // check occlusion on detection lane - if (!occlusion_attention_divisions_) { - occlusion_attention_divisions_ = util::generateDetectionLaneDivisions( - occlusion_attention_lanelets, routing_graph_ptr, - planner_data_->occupancy_grid->info.resolution, - planner_param_.occlusion.attention_lane_crop_curvature_threshold, - planner_param_.occlusion.attention_lane_curvature_calculation_ds); - } - const auto & occlusion_attention_divisions = occlusion_attention_divisions_.value(); - - // get intersection area - const auto intersection_area = util::getIntersectionArea(assigned_lanelet, lanelet_map_ptr); - // filter objects - auto target_objects = generateTargetObjects(intersection_lanelets, intersection_area); - - const bool yield_stuck_detected = checkYieldStuckVehicle( - target_objects, interpolated_path_info, intersection_lanelets.attention_non_preceding_); - if (yield_stuck_detected) { - std::optional stopline_idx = std::nullopt; - const bool is_before_default_stopline = fromEgoDist(default_stopline_idx) >= 0.0; - const bool is_before_first_attention_stopline = - fromEgoDist(first_attention_stopline_idx) >= 0.0; - if (stuck_stopline_idx_opt) { - const bool is_over_stuck_stopline = fromEgoDist(stuck_stopline_idx_opt.value()) < - -planner_param_.common.stopline_overshoot_margin; - if (!is_over_stuck_stopline) { - stopline_idx = stuck_stopline_idx_opt.value(); - } - } - if (!stopline_idx) { - if (is_before_default_stopline) { - stopline_idx = default_stopline_idx; - } else if (is_before_first_attention_stopline) { - stopline_idx = closest_idx; +void IntersectionModule::updateTrafficSignalObservation() +{ + const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); + const auto & lane = lanelet_map_ptr->laneletLayer.get(lane_id_); + + if (!tl_id_and_point_) { + for (auto && tl_reg_elem : + lane.regulatoryElementsAs()) { + for (const auto & ls : tl_reg_elem->lightBulbs()) { + if (ls.hasAttribute("traffic_light_id")) { + tl_id_and_point_ = std::make_pair(tl_reg_elem->id(), ls.front()); + break; + } } } - if (stopline_idx) { - return IntersectionModule::YieldStuckStop{closest_idx, stopline_idx.value()}; - } } - - const double is_amber_or_red = - (traffic_prioritized_level == util::TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED) || - (traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED); - auto occlusion_status = - (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !is_amber_or_red) - ? getOcclusionStatus( - *planner_data_->occupancy_grid, occlusion_attention_area, adjacent_lanelets, - first_attention_area, interpolated_path_info, occlusion_attention_divisions, - target_objects, current_pose, - planner_param_.occlusion.occlusion_required_clearance_distance) - : OcclusionType::NOT_OCCLUDED; - occlusion_stop_state_machine_.setStateWithMarginTime( - occlusion_status == OcclusionType::NOT_OCCLUDED ? StateMachine::State::GO : StateMachine::STOP, - logger_.get_child("occlusion_stop"), *clock_); - const bool is_occlusion_cleared_with_margin = - (occlusion_stop_state_machine_.getState() == StateMachine::State::GO); - // distinguish if ego detected occlusion or RTC detects occlusion - const bool ext_occlusion_requested = (is_occlusion_cleared_with_margin && !occlusion_activated_); - if (ext_occlusion_requested) { - occlusion_status = OcclusionType::RTC_OCCLUDED; + if (!tl_id_and_point_) { + // this lane has no traffic light + return; } - const bool is_occlusion_state = (!is_occlusion_cleared_with_margin || ext_occlusion_requested); - if (is_occlusion_state && occlusion_status == OcclusionType::NOT_OCCLUDED) { - occlusion_status = prev_occlusion_status_; - } else { - prev_occlusion_status_ = occlusion_status; + const auto [tl_id, point] = tl_id_and_point_.value(); + const auto tl_info_opt = planner_data_->getTrafficSignal(tl_id); + if (!tl_info_opt) { + // the info of this traffic light is not available + return; } + last_tl_valid_observation_ = *tl_info_opt; + internal_debug_data_.tl_observation = *tl_info_opt; +} - // TODO(Mamoru Sobue): this part needs more formal handling - const size_t pass_judge_line_idx = [=]() { - if (enable_occlusion_detection_) { - // if occlusion detection is enabled, pass_judge position is beyond the boundary of first - // attention area +IntersectionModule::PassJudgeStatus IntersectionModule::isOverPassJudgeLinesStatus( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const bool is_occlusion_state, + const intersection::IntersectionStopLines & intersection_stoplines) +{ + const auto & current_pose = planner_data_->current_odometry->pose; + const auto closest_idx = intersection_stoplines.closest_idx; + const auto default_stopline_idx = intersection_stoplines.default_stopline.value(); + const auto first_pass_judge_line_idx = intersection_stoplines.first_pass_judge_line; + const auto occlusion_wo_tl_pass_judge_line_idx = + intersection_stoplines.occlusion_wo_tl_pass_judge_line; + const auto occlusion_stopline_idx = intersection_stoplines.occlusion_peeking_stopline.value(); + const size_t pass_judge_line_idx = [&]() { + if (planner_param_.occlusion.enable) { if (has_traffic_light_) { - return occlusion_stopline_idx; + // ========================================================================================== + // if ego passed the first_pass_judge_line while it is peeking to occlusion, then its + // position is changed to occlusion_stopline_idx. even if occlusion is cleared by peeking, + // its position should be occlusion_stopline_idx as before + // ========================================================================================== + if (passed_1st_judge_line_while_peeking_) { + return occlusion_stopline_idx; + } + const bool is_over_first_pass_judge_line = + util::isOverTargetIndex(path, closest_idx, current_pose, first_pass_judge_line_idx); + if (is_occlusion_state && is_over_first_pass_judge_line) { + passed_1st_judge_line_while_peeking_ = true; + return occlusion_stopline_idx; + } + // ========================================================================================== + // Otherwise it is first_pass_judge_line + // ========================================================================================== + return first_pass_judge_line_idx; } else if (is_occlusion_state) { + // ========================================================================================== // if there is no traffic light and occlusion is detected, pass_judge position is beyond // the boundary of first attention area + // ========================================================================================== return occlusion_wo_tl_pass_judge_line_idx; } else { + // ========================================================================================== // if there is no traffic light and occlusion is not detected, pass_judge position is - // default - return default_pass_judge_line_idx; + // default position + // ========================================================================================== + return first_pass_judge_line_idx; } } - return default_pass_judge_line_idx; + return first_pass_judge_line_idx; }(); - debug_data_.pass_judge_wall_pose = - planning_utils::getAheadPose(pass_judge_line_idx, baselink2front, *path); - const bool is_over_pass_judge_line = - util::isOverTargetIndex(*path, closest_idx, current_pose, pass_judge_line_idx); - const double vel_norm = std::hypot( - planner_data_->current_velocity->twist.linear.x, - planner_data_->current_velocity->twist.linear.y); - const bool keep_detection = - (vel_norm < planner_param_.collision_detection.keep_detection_velocity_threshold); - const bool was_safe = std::holds_alternative(prev_decision_result_); - // if ego is over the pass judge line and not stopped - if (is_over_default_stopline && !is_over_pass_judge_line && keep_detection) { - RCLCPP_DEBUG(logger_, "is_over_default_stopline && !is_over_pass_judge_line && keep_detection"); - // do nothing - } else if ( - (was_safe && is_over_default_stopline && is_over_pass_judge_line && is_go_out_) || - is_permanent_go_) { - // is_go_out_: previous RTC approval - // activated_: current RTC approval - is_permanent_go_ = true; - return IntersectionModule::Indecisive{"over the pass judge line. no plan needed"}; - } - // If there are any vehicles on the attention area when ego entered the intersection on green - // light, do pseudo collision detection because the vehicles are very slow and no collisions may - // be detected. check if ego vehicle entered assigned lanelet - const bool is_green_solid_on = - isGreenSolidOn(assigned_lanelet, planner_data_->traffic_light_id_map); - if (is_green_solid_on) { - if (!initial_green_light_observed_time_) { - const auto assigned_lane_begin_point = assigned_lanelet.centerline().front(); - const bool approached_assigned_lane = - motion_utils::calcSignedArcLength( - path->points, closest_idx, - tier4_autoware_utils::createPoint( - assigned_lane_begin_point.x(), assigned_lane_begin_point.y(), - assigned_lane_begin_point.z())) < - planner_param_.collision_detection.yield_on_green_traffic_light - .distance_to_assigned_lanelet_start; - if (approached_assigned_lane) { - initial_green_light_observed_time_ = clock_->now(); - } - } - if (initial_green_light_observed_time_) { - const auto now = clock_->now(); - const bool exist_close_vehicles = std::any_of( - target_objects.all_attention_objects.begin(), target_objects.all_attention_objects.end(), - [&](const auto & object) { - return object.dist_to_stopline.has_value() && - object.dist_to_stopline.value() < - planner_param_.collision_detection.yield_on_green_traffic_light - .object_dist_to_stopline; - }); - if ( - exist_close_vehicles && - rclcpp::Duration((now - initial_green_light_observed_time_.value())).seconds() < - planner_param_.collision_detection.yield_on_green_traffic_light.duration) { - return IntersectionModule::NonOccludedCollisionStop{ - closest_idx, collision_stopline_idx, occlusion_stopline_idx}; - } - } - } - - // calculate dynamic collision around attention area - const double time_to_restart = - (is_go_out_ || is_prioritized) - ? 0.0 - : (planner_param_.collision_detection.collision_detection_hold_time - - collision_state_machine_.getDuration()); - - const bool has_collision = checkCollision( - *path, &target_objects, path_lanelets, closest_idx, - std::min(occlusion_stopline_idx, path->points.size() - 1), time_to_restart, - traffic_prioritized_level); - collision_state_machine_.setStateWithMarginTime( - has_collision ? StateMachine::State::STOP : StateMachine::State::GO, - logger_.get_child("collision state_machine"), *clock_); - const bool has_collision_with_margin = - collision_state_machine_.getState() == StateMachine::State::STOP; - - if (is_prioritized) { - return FullyPrioritized{ - has_collision_with_margin, closest_idx, collision_stopline_idx, occlusion_stopline_idx}; - } - - // Safe - if (!is_occlusion_state && !has_collision_with_margin) { - return IntersectionModule::Safe{closest_idx, collision_stopline_idx, occlusion_stopline_idx}; - } - // Only collision - if (!is_occlusion_state && has_collision_with_margin) { - return IntersectionModule::NonOccludedCollisionStop{ - closest_idx, collision_stopline_idx, occlusion_stopline_idx}; - } - // Occluded - // occlusion_status is assured to be not NOT_OCCLUDED - const bool stopped_at_default_line = stoppedForDuration( - default_stopline_idx, planner_param_.occlusion.temporal_stop_time_before_peeking, - before_creep_state_machine_); - if (stopped_at_default_line) { - // if specified the parameter occlusion.temporal_stop_before_attention_area OR - // has_no_traffic_light_, ego will temporarily stop before entering attention area - const bool temporal_stop_before_attention_required = - (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) - ? !stoppedForDuration( - first_attention_stopline_idx, - planner_param_.occlusion.temporal_stop_time_before_peeking, - temporal_stop_before_attention_state_machine_) - : false; - if (!has_traffic_light_) { - if (fromEgoDist(occlusion_wo_tl_pass_judge_line_idx) < 0) { - return IntersectionModule::Indecisive{ - "already passed maximum peeking line in the absence of traffic light"}; - } - return IntersectionModule::OccludedAbsenceTrafficLight{ - is_occlusion_cleared_with_margin, - has_collision_with_margin, - temporal_stop_before_attention_required, - closest_idx, - first_attention_stopline_idx, - occlusion_wo_tl_pass_judge_line_idx}; + // ========================================================================================== + // at intersection without traffic light, this module ignores occlusion even if occlusion is + // detected for real, so if collision is not detected in that context, that should be interpreted + // as "was_safe" + // ========================================================================================== + const bool was_safe = [&]() { + if (std::holds_alternative(prev_decision_result_)) { + return true; } - // following remaining block is "has_traffic_light_" - // if ego is stuck by static occlusion in the presence of traffic light, start timeout count - const bool is_static_occlusion = occlusion_status == OcclusionType::STATICALLY_OCCLUDED; - const bool is_stuck_by_static_occlusion = - stoppedAtPosition( - occlusion_stopline_idx, planner_param_.occlusion.temporal_stop_time_before_peeking) && - is_static_occlusion; - if (has_collision_with_margin) { - // if collision is detected, timeout is reset - static_occlusion_timeout_state_machine_.setState(StateMachine::State::STOP); - } else if (is_stuck_by_static_occlusion) { - static_occlusion_timeout_state_machine_.setStateWithMarginTime( - StateMachine::State::GO, logger_.get_child("static_occlusion"), *clock_); + if (std::holds_alternative(prev_decision_result_)) { + const auto & state = + std::get(prev_decision_result_); + return !state.collision_detected; } - const bool release_static_occlusion_stuck = - (static_occlusion_timeout_state_machine_.getState() == StateMachine::State::GO); - if (!has_collision_with_margin && release_static_occlusion_stuck) { - return IntersectionModule::Safe{closest_idx, collision_stopline_idx, occlusion_stopline_idx}; + if (std::holds_alternative(prev_decision_result_)) { + const auto & prev_decision = std::get(prev_decision_result_); + return !prev_decision.collision_detected; } - // occlusion_status is either STATICALLY_OCCLUDED or DYNAMICALLY_OCCLUDED - const double max_timeout = - planner_param_.occlusion.static_occlusion_with_traffic_light_timeout + - planner_param_.occlusion.occlusion_detection_hold_time; - const std::optional static_occlusion_timeout = - is_stuck_by_static_occlusion - ? std::make_optional( - max_timeout - static_occlusion_timeout_state_machine_.getDuration() - - occlusion_stop_state_machine_.getDuration()) - : (is_static_occlusion ? std::make_optional(max_timeout) : std::nullopt); - if (has_collision_with_margin) { - return IntersectionModule::OccludedCollisionStop{ - is_occlusion_cleared_with_margin, - temporal_stop_before_attention_required, - closest_idx, - collision_stopline_idx, - first_attention_stopline_idx, - occlusion_stopline_idx, - static_occlusion_timeout}; - } else { - return IntersectionModule::PeekingTowardOcclusion{ - is_occlusion_cleared_with_margin, - temporal_stop_before_attention_required, - closest_idx, - collision_stopline_idx, - first_attention_stopline_idx, - occlusion_stopline_idx, - static_occlusion_timeout}; - } - } else { - const auto occlusion_stopline = - (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) - ? first_attention_stopline_idx - : occlusion_stopline_idx; - return IntersectionModule::FirstWaitBeforeOcclusion{ - is_occlusion_cleared_with_margin, closest_idx, default_stopline_idx, occlusion_stopline}; - } -} - -bool IntersectionModule::checkStuckVehicle( - const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets) -{ - const bool stuck_detection_direction = [&]() { - return (turn_direction_ == "left" && planner_param_.stuck_vehicle.turn_direction.left) || - (turn_direction_ == "right" && planner_param_.stuck_vehicle.turn_direction.right) || - (turn_direction_ == "straight" && planner_param_.stuck_vehicle.turn_direction.straight); - }(); - if (!stuck_detection_direction) { return false; - } - - const auto & objects_ptr = planner_data->predicted_objects; - - // considering lane change in the intersection, these lanelets are generated from the path - const auto stuck_vehicle_detect_area = util::generateStuckVehicleDetectAreaPolygon( - path_lanelets, planner_param_.stuck_vehicle.stuck_vehicle_detect_dist); - debug_data_.stuck_vehicle_detect_area = toGeomPoly(stuck_vehicle_detect_area); - - return util::checkStuckVehicleInIntersection( - objects_ptr, stuck_vehicle_detect_area, - planner_param_.stuck_vehicle.stuck_vehicle_velocity_threshold, &debug_data_); -} - -bool IntersectionModule::checkYieldStuckVehicle( - const util::TargetObjects & target_objects, - const util::InterpolatedPathInfo & interpolated_path_info, - const lanelet::ConstLanelets & attention_lanelets) -{ - const bool yield_stuck_detection_direction = [&]() { - return (turn_direction_ == "left" && planner_param_.yield_stuck.turn_direction.left) || - (turn_direction_ == "right" && planner_param_.yield_stuck.turn_direction.right) || - (turn_direction_ == "straight" && planner_param_.yield_stuck.turn_direction.straight); }(); - if (!yield_stuck_detection_direction) { - return false; - } - - return util::checkYieldStuckVehicleInIntersection( - target_objects, interpolated_path_info, attention_lanelets, turn_direction_, - planner_data_->vehicle_info_.vehicle_width_m, - planner_param_.stuck_vehicle.stuck_vehicle_velocity_threshold, - planner_param_.yield_stuck.distance_threshold, &debug_data_); -} - -util::TargetObjects IntersectionModule::generateTargetObjects( - const util::IntersectionLanelets & intersection_lanelets, - const std::optional & intersection_area) const -{ - const auto & objects_ptr = planner_data_->predicted_objects; - // extract target objects - util::TargetObjects target_objects; - target_objects.header = objects_ptr->header; - const auto & attention_lanelets = intersection_lanelets.attention(); - const auto & attention_lanelet_stoplines = intersection_lanelets.attention_stoplines(); - const auto & adjacent_lanelets = intersection_lanelets.adjacent(); - for (const auto & object : objects_ptr->objects) { - // ignore non-vehicle type objects, such as pedestrian. - if (!isTargetCollisionVehicleType(object)) { - continue; - } - - // check direction of objects - const auto object_direction = util::getObjectPoseWithVelocityDirection(object.kinematics); - const auto belong_adjacent_lanelet_id = util::checkAngleForTargetLanelets( - object_direction, adjacent_lanelets, planner_param_.common.attention_area_angle_threshold, - planner_param_.collision_detection.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin, false); - if (belong_adjacent_lanelet_id) { - continue; - } - const auto is_parked_vehicle = - std::fabs(object.kinematics.initial_twist_with_covariance.twist.linear.x) < - planner_param_.occlusion.ignore_parked_vehicle_speed_threshold; - auto & container = is_parked_vehicle ? target_objects.parked_attention_objects - : target_objects.attention_objects; - if (intersection_area) { - const auto & obj_pos = object.kinematics.initial_pose_with_covariance.pose.position; - const auto obj_poly = tier4_autoware_utils::toPolygon2d(object); - const auto intersection_area_2d = intersection_area.value(); - const auto belong_attention_lanelet_id = util::checkAngleForTargetLanelets( - object_direction, attention_lanelets, planner_param_.common.attention_area_angle_threshold, - planner_param_.collision_detection.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin, is_parked_vehicle); - if (belong_attention_lanelet_id) { - const auto id = belong_attention_lanelet_id.value(); - util::TargetObject target_object; - target_object.object = object; - target_object.attention_lanelet = attention_lanelets.at(id); - target_object.stopline = attention_lanelet_stoplines.at(id); - container.push_back(target_object); - } else if (bg::within(Point2d{obj_pos.x, obj_pos.y}, intersection_area_2d)) { - util::TargetObject target_object; - target_object.object = object; - target_object.attention_lanelet = std::nullopt; - target_object.stopline = std::nullopt; - target_objects.intersection_area_objects.push_back(target_object); - } - } else if (const auto belong_attention_lanelet_id = util::checkAngleForTargetLanelets( - object_direction, attention_lanelets, - planner_param_.common.attention_area_angle_threshold, - planner_param_.collision_detection.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin, is_parked_vehicle); - belong_attention_lanelet_id.has_value()) { - // intersection_area is not available, use detection_area_with_margin as before - const auto id = belong_attention_lanelet_id.value(); - util::TargetObject target_object; - target_object.object = object; - target_object.attention_lanelet = attention_lanelets.at(id); - target_object.stopline = attention_lanelet_stoplines.at(id); - container.push_back(target_object); - } + const bool is_over_1st_pass_judge_line = + util::isOverTargetIndex(path, closest_idx, current_pose, pass_judge_line_idx); + bool safely_passed_1st_judge_line_first_time = false; + if (is_over_1st_pass_judge_line && was_safe && !safely_passed_1st_judge_line_time_) { + safely_passed_1st_judge_line_time_ = std::make_pair(clock_->now(), current_pose); + safely_passed_1st_judge_line_first_time = true; } - for (const auto & object : target_objects.attention_objects) { - target_objects.all_attention_objects.push_back(object); - } - for (const auto & object : target_objects.parked_attention_objects) { - target_objects.all_attention_objects.push_back(object); - } - for (auto & object : target_objects.all_attention_objects) { - object.calc_dist_to_stopline(); - } - return target_objects; -} - -bool IntersectionModule::checkCollision( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - util::TargetObjects * target_objects, const util::PathLanelets & path_lanelets, - const size_t closest_idx, const size_t last_intersection_stopline_candidate_idx, - const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level) -{ - using lanelet::utils::getArcCoordinates; - using lanelet::utils::getPolygonFromArcLength; - - // check collision between target_objects predicted path and ego lane - // cut the predicted path at passing_time - tier4_debug_msgs::msg::Float64MultiArrayStamped ego_ttc_time_array; - const auto time_distance_array = util::calcIntersectionPassingTime( - path, planner_data_, lane_id_, associative_ids_, closest_idx, - last_intersection_stopline_candidate_idx, time_delay, - planner_param_.collision_detection.velocity_profile.default_velocity, - planner_param_.collision_detection.velocity_profile.minimum_default_velocity, - planner_param_.collision_detection.velocity_profile.use_upstream, - planner_param_.collision_detection.velocity_profile.minimum_upstream_velocity, - &ego_ttc_time_array); - + const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; + debug_data_.first_pass_judge_wall_pose = + planning_utils::getAheadPose(pass_judge_line_idx, baselink2front, path); + debug_data_.passed_first_pass_judge = safely_passed_1st_judge_line_time_.has_value(); + const auto second_pass_judge_line_idx_opt = intersection_stoplines.second_pass_judge_line; + const std::optional is_over_2nd_pass_judge_line = + second_pass_judge_line_idx_opt + ? std::make_optional(util::isOverTargetIndex( + path, closest_idx, current_pose, second_pass_judge_line_idx_opt.value())) + : std::nullopt; + bool safely_passed_2nd_judge_line_first_time = false; if ( - std::find(planner_param_.debug.ttc.begin(), planner_param_.debug.ttc.end(), lane_id_) != - planner_param_.debug.ttc.end()) { - ego_ttc_time_array.stamp = path.header.stamp; - ego_ttc_pub_->publish(ego_ttc_time_array); + is_over_2nd_pass_judge_line && is_over_2nd_pass_judge_line.value() && was_safe && + !safely_passed_2nd_judge_line_time_) { + safely_passed_2nd_judge_line_time_ = std::make_pair(clock_->now(), current_pose); + safely_passed_2nd_judge_line_first_time = true; } - - const double passing_time = time_distance_array.back().first; - util::cutPredictPathWithDuration(target_objects, clock_, passing_time); - - const auto & concat_lanelets = path_lanelets.all; - const auto closest_arc_coords = getArcCoordinates( - concat_lanelets, tier4_autoware_utils::getPose(path.points.at(closest_idx).point)); - const auto & ego_lane = path_lanelets.ego_or_entry2exit; - debug_data_.ego_lane = ego_lane.polygon3d(); - const auto ego_poly = ego_lane.polygon2d().basicPolygon(); - - // change TTC margin based on ego traffic light color - const auto [collision_start_margin_time, collision_end_margin_time] = [&]() { - if (traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED) { - return std::make_pair( - planner_param_.collision_detection.fully_prioritized.collision_start_margin_time, - planner_param_.collision_detection.fully_prioritized.collision_end_margin_time); - } - if (traffic_prioritized_level == util::TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED) { - return std::make_pair( - planner_param_.collision_detection.partially_prioritized.collision_start_margin_time, - planner_param_.collision_detection.partially_prioritized.collision_end_margin_time); - } - return std::make_pair( - planner_param_.collision_detection.not_prioritized.collision_start_margin_time, - planner_param_.collision_detection.not_prioritized.collision_end_margin_time); - }(); - const auto expectedToStopBeforeStopLine = [&](const util::TargetObject & target_object) { - if (!target_object.dist_to_stopline) { - return false; - } - const double dist_to_stopline = target_object.dist_to_stopline.value(); - if (dist_to_stopline < 0) { - return false; - } - const double v = target_object.object.kinematics.initial_twist_with_covariance.twist.linear.x; - const double braking_distance = - v * v / - (2.0 * std::fabs(planner_param_.collision_detection.ignore_on_amber_traffic_light - .object_expected_deceleration)); - return dist_to_stopline > braking_distance; - }; - const auto isTolerableOvershoot = [&](const util::TargetObject & target_object) { - if ( - !target_object.attention_lanelet || !target_object.dist_to_stopline || - !target_object.stopline) { - return false; - } - const double dist_to_stopline = target_object.dist_to_stopline.value(); - const double v = target_object.object.kinematics.initial_twist_with_covariance.twist.linear.x; - const double braking_distance = - v * v / - (2.0 * std::fabs(planner_param_.collision_detection.ignore_on_amber_traffic_light - .object_expected_deceleration)); - if (dist_to_stopline > braking_distance) { - return false; - } - const auto stopline_front = target_object.stopline.value().front(); - const auto stopline_back = target_object.stopline.value().back(); - tier4_autoware_utils::LineString2d object_line; - object_line.emplace_back( - (stopline_front.x() + stopline_back.x()) / 2.0, - (stopline_front.y() + stopline_back.y()) / 2.0); - const auto stopline_mid = object_line.front(); - const auto endpoint = target_object.attention_lanelet.value().centerline().back(); - object_line.emplace_back(endpoint.x(), endpoint.y()); - std::vector intersections; - bg::intersection(object_line, ego_lane.centerline2d().basicLineString(), intersections); - if (intersections.empty()) { - return false; - } - const auto collision_point = intersections.front(); - // distance from object expected stop position to collision point - const double stopline_to_object = -1.0 * dist_to_stopline + braking_distance; - const double stopline_to_collision = - std::hypot(collision_point.x() - stopline_mid.x(), collision_point.y() - stopline_mid.y()); - const double object2collision = stopline_to_collision - stopline_to_object; - const double margin = - planner_param_.collision_detection.ignore_on_red_traffic_light.object_margin_to_path; - return (object2collision > margin) || (object2collision < 0); - }; - // check collision between predicted_path and ego_area - tier4_debug_msgs::msg::Float64MultiArrayStamped object_ttc_time_array; - object_ttc_time_array.layout.dim.resize(3); - object_ttc_time_array.layout.dim.at(0).label = "objects"; - object_ttc_time_array.layout.dim.at(0).size = 1; // incremented in the loop, first row is lane_id - object_ttc_time_array.layout.dim.at(1).label = - "[x, y, th, length, width, speed, dangerous, ref_obj_enter_time, ref_obj_exit_time, " - "start_time, start_dist, " - "end_time, end_dist, first_collision_x, first_collision_y, last_collision_x, last_collision_y, " - "prd_x[0], ... pred_x[19], pred_y[0], ... pred_y[19]]"; - object_ttc_time_array.layout.dim.at(1).size = 57; - for (unsigned i = 0; i < object_ttc_time_array.layout.dim.at(1).size; ++i) { - object_ttc_time_array.data.push_back(lane_id_); + if (second_pass_judge_line_idx_opt) { + debug_data_.second_pass_judge_wall_pose = + planning_utils::getAheadPose(second_pass_judge_line_idx_opt.value(), baselink2front, path); } - bool collision_detected = false; - for (const auto & target_object : target_objects->all_attention_objects) { - const auto & object = target_object.object; - // If the vehicle is expected to stop before their stopline, ignore - const bool expected_to_stop_before_stopline = expectedToStopBeforeStopLine(target_object); - if ( - traffic_prioritized_level == util::TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED && - expected_to_stop_before_stopline) { - debug_data_.amber_ignore_targets.objects.push_back(object); - continue; - } - const bool is_tolerable_overshoot = isTolerableOvershoot(target_object); - if ( - traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED && - !expected_to_stop_before_stopline && is_tolerable_overshoot) { - debug_data_.red_overshoot_ignore_targets.objects.push_back(object); - continue; - } - for (const auto & predicted_path : object.kinematics.predicted_paths) { - if ( - predicted_path.confidence < - planner_param_.collision_detection.min_predicted_path_confidence) { - // ignore the predicted path with too low confidence - continue; - } + debug_data_.passed_second_pass_judge = safely_passed_2nd_judge_line_time_.has_value(); - // collision point - const auto first_itr = std::adjacent_find( - predicted_path.path.cbegin(), predicted_path.path.cend(), - [&ego_poly, &object](const auto & a, const auto & b) { - return bg::intersects(ego_poly, createOneStepPolygon(a, b, object.shape)); - }); - if (first_itr == predicted_path.path.cend()) continue; - const auto last_itr = std::adjacent_find( - predicted_path.path.crbegin(), predicted_path.path.crend(), - [&ego_poly, &object](const auto & a, const auto & b) { - return bg::intersects(ego_poly, createOneStepPolygon(a, b, object.shape)); - }); - if (last_itr == predicted_path.path.crend()) continue; - - // possible collision time interval - const double ref_object_enter_time = - static_cast(first_itr - predicted_path.path.begin()) * - rclcpp::Duration(predicted_path.time_step).seconds(); - auto start_time_distance_itr = time_distance_array.begin(); - if (ref_object_enter_time - collision_start_margin_time > 0) { - // start of possible ego position in the intersection - start_time_distance_itr = std::lower_bound( - time_distance_array.begin(), time_distance_array.end(), - ref_object_enter_time - collision_start_margin_time, - [](const auto & a, const double b) { return a.first < b; }); - if (start_time_distance_itr == time_distance_array.end()) { - // ego is already at the exit of intersection when npc is at collision point even if npc - // accelerates so ego's position interval is empty - continue; - } - } - const double ref_object_exit_time = - static_cast(last_itr.base() - predicted_path.path.begin()) * - rclcpp::Duration(predicted_path.time_step).seconds(); - auto end_time_distance_itr = std::lower_bound( - time_distance_array.begin(), time_distance_array.end(), - ref_object_exit_time + collision_end_margin_time, - [](const auto & a, const double b) { return a.first < b; }); - if (end_time_distance_itr == time_distance_array.end()) { - // ego is already passing the intersection, when npc is is at collision point - // so ego's position interval is up to the end of intersection lane - end_time_distance_itr = time_distance_array.end() - 1; - } - const double start_arc_length = std::max( - 0.0, closest_arc_coords.length + (*start_time_distance_itr).second - - planner_data_->vehicle_info_.rear_overhang_m); - const double end_arc_length = std::min( - closest_arc_coords.length + (*end_time_distance_itr).second + - planner_data_->vehicle_info_.max_longitudinal_offset_m, - lanelet::utils::getLaneletLength2d(concat_lanelets)); - - const auto trimmed_ego_polygon = - getPolygonFromArcLength(concat_lanelets, start_arc_length, end_arc_length); - - if (trimmed_ego_polygon.empty()) { - continue; - } - - Polygon2d polygon{}; - for (const auto & p : trimmed_ego_polygon) { - polygon.outer().emplace_back(p.x(), p.y()); - } - bg::correct(polygon); - debug_data_.candidate_collision_ego_lane_polygon = toGeomPoly(polygon); - - for (auto itr = first_itr; itr != last_itr.base(); ++itr) { - const auto footprint_polygon = tier4_autoware_utils::toPolygon2d(*itr, object.shape); - if (bg::intersects(polygon, footprint_polygon)) { - collision_detected = true; - break; - } - } - object_ttc_time_array.layout.dim.at(0).size++; - const auto & pos = object.kinematics.initial_pose_with_covariance.pose.position; - const auto & shape = object.shape; - object_ttc_time_array.data.insert( - object_ttc_time_array.data.end(), - {pos.x, pos.y, tf2::getYaw(object.kinematics.initial_pose_with_covariance.pose.orientation), - shape.dimensions.x, shape.dimensions.y, - object.kinematics.initial_twist_with_covariance.twist.linear.x, - 1.0 * static_cast(collision_detected), ref_object_enter_time, ref_object_exit_time, - start_time_distance_itr->first, start_time_distance_itr->second, - end_time_distance_itr->first, end_time_distance_itr->second, first_itr->position.x, - first_itr->position.y, last_itr->position.x, last_itr->position.y}); - for (unsigned i = 0; i < 20; i++) { - const auto & pos = - predicted_path.path.at(std::min(i, predicted_path.path.size() - 1)).position; - object_ttc_time_array.data.push_back(pos.x); - object_ttc_time_array.data.push_back(pos.y); - } - if (collision_detected) { - debug_data_.conflicting_targets.objects.push_back(object); - break; - } - } - } + const bool is_over_default_stopline = + util::isOverTargetIndex(path, closest_idx, current_pose, default_stopline_idx); + const bool over_default_stopline_for_pass_judge = + is_over_default_stopline || planner_param_.common.enable_pass_judge_before_default_stopline; + const bool over_pass_judge_line_overall = + is_over_2nd_pass_judge_line ? is_over_2nd_pass_judge_line.value() : is_over_1st_pass_judge_line; if ( - std::find(planner_param_.debug.ttc.begin(), planner_param_.debug.ttc.end(), lane_id_) != - planner_param_.debug.ttc.end()) { - object_ttc_time_array.stamp = path.header.stamp; - object_ttc_pub_->publish(object_ttc_time_array); - } - - return collision_detected; -} - -IntersectionModule::OcclusionType IntersectionModule::getOcclusionStatus( - const nav_msgs::msg::OccupancyGrid & occ_grid, - const std::vector & attention_areas, - const lanelet::ConstLanelets & adjacent_lanelets, - const lanelet::CompoundPolygon3d & first_attention_area, - const util::InterpolatedPathInfo & interpolated_path_info, - const std::vector & lane_divisions, - const util::TargetObjects & target_objects, const geometry_msgs::msg::Pose & current_pose, - const double occlusion_dist_thr) -{ - const auto & path_ip = interpolated_path_info.path; - const auto & lane_interval_ip = interpolated_path_info.lane_id_interval.value(); - - const auto first_attention_area_idx = - util::getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_attention_area); - if (!first_attention_area_idx) { - return OcclusionType::NOT_OCCLUDED; - } - - const auto first_inside_attention_idx_ip_opt = - util::getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_attention_area); - const std::pair lane_attention_interval_ip = - first_inside_attention_idx_ip_opt - ? std::make_pair(first_inside_attention_idx_ip_opt.value(), std::get<1>(lane_interval_ip)) - : lane_interval_ip; - const auto [lane_start_idx, lane_end_idx] = lane_attention_interval_ip; - - const int width = occ_grid.info.width; - const int height = occ_grid.info.height; - const double resolution = occ_grid.info.resolution; - const auto & origin = occ_grid.info.origin.position; - auto coord2index = [&](const double x, const double y) { - const int idx_x = (x - origin.x) / resolution; - const int idx_y = (y - origin.y) / resolution; - if (idx_x < 0 || idx_x >= width) return std::make_tuple(false, -1, -1); - if (idx_y < 0 || idx_y >= height) return std::make_tuple(false, -1, -1); - return std::make_tuple(true, idx_x, idx_y); - }; - - Polygon2d grid_poly; - grid_poly.outer().emplace_back(origin.x, origin.y); - grid_poly.outer().emplace_back(origin.x + (width - 1) * resolution, origin.y); - grid_poly.outer().emplace_back( - origin.x + (width - 1) * resolution, origin.y + (height - 1) * resolution); - grid_poly.outer().emplace_back(origin.x, origin.y + (height - 1) * resolution); - grid_poly.outer().emplace_back(origin.x, origin.y); - bg::correct(grid_poly); - - auto findCommonCvPolygons = - [&](const auto & area2d, std::vector> & cv_polygons) -> void { - tier4_autoware_utils::Polygon2d area2d_poly; - for (const auto & p : area2d) { - area2d_poly.outer().emplace_back(p.x(), p.y()); - } - area2d_poly.outer().push_back(area2d_poly.outer().front()); - bg::correct(area2d_poly); - std::vector common_areas; - bg::intersection(area2d_poly, grid_poly, common_areas); - if (common_areas.empty()) { - return; - } - for (size_t i = 0; i < common_areas.size(); ++i) { - common_areas[i].outer().push_back(common_areas[i].outer().front()); - bg::correct(common_areas[i]); - } - for (const auto & common_area : common_areas) { - std::vector cv_polygon; - for (const auto & p : common_area.outer()) { - const int idx_x = static_cast((p.x() - origin.x) / resolution); - const int idx_y = static_cast((p.y() - origin.y) / resolution); - cv_polygon.emplace_back(idx_x, height - 1 - idx_y); - } - cv_polygons.push_back(cv_polygon); - } - }; - - // (1) prepare detection area mask - // attention: 255 - // non-attention: 0 - // NOTE: interesting area is set to 255 for later masking - cv::Mat attention_mask(width, height, CV_8UC1, cv::Scalar(0)); - std::vector> attention_area_cv_polygons; - for (const auto & attention_area : attention_areas) { - const auto area2d = lanelet::utils::to2D(attention_area); - findCommonCvPolygons(area2d, attention_area_cv_polygons); - } - for (const auto & poly : attention_area_cv_polygons) { - cv::fillPoly(attention_mask, poly, cv::Scalar(255), cv::LINE_AA); - } - // (1.1) - // reset adjacent_lanelets area to 0 on attention_mask - std::vector> adjacent_lane_cv_polygons; - for (const auto & adjacent_lanelet : adjacent_lanelets) { - const auto area2d = adjacent_lanelet.polygon2d().basicPolygon(); - findCommonCvPolygons(area2d, adjacent_lane_cv_polygons); - } - for (const auto & poly : adjacent_lane_cv_polygons) { - cv::fillPoly(attention_mask, poly, cv::Scalar(0), cv::LINE_AA); - } - - // (2) prepare unknown mask - // In OpenCV the pixel at (X=x, Y=y) (with left-upper origin) is accessed by img[y, x] - // unknown: 255 - // not-unknown: 0 - cv::Mat unknown_mask_raw(width, height, CV_8UC1, cv::Scalar(0)); - cv::Mat unknown_mask(width, height, CV_8UC1, cv::Scalar(0)); - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - const int idx = y * width + x; - const unsigned char intensity = occ_grid.data.at(idx); - if ( - planner_param_.occlusion.free_space_max <= intensity && - intensity < planner_param_.occlusion.occupied_min) { - unknown_mask_raw.at(height - 1 - y, x) = 255; - } - } - } - // (2.1) apply morphologyEx - const int morph_size = static_cast(planner_param_.occlusion.denoise_kernel / resolution); - cv::morphologyEx( - unknown_mask_raw, unknown_mask, cv::MORPH_OPEN, - cv::getStructuringElement(cv::MORPH_RECT, cv::Size(morph_size, morph_size))); - - // (3) occlusion mask - static constexpr unsigned char OCCLUDED = 255; - static constexpr unsigned char BLOCKED = 127; - cv::Mat occlusion_mask(width, height, CV_8UC1, cv::Scalar(0)); - cv::bitwise_and(attention_mask, unknown_mask, occlusion_mask); - // re-use attention_mask - attention_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); - // (3.1) draw all cells on attention_mask behind blocking vehicles as not occluded - const auto & blocking_attention_objects = target_objects.parked_attention_objects; - for (const auto & blocking_attention_object : blocking_attention_objects) { - debug_data_.blocking_attention_objects.objects.push_back(blocking_attention_object.object); - } - std::vector> blocking_polygons; - for (const auto & blocking_attention_object : blocking_attention_objects) { - const Polygon2d obj_poly = tier4_autoware_utils::toPolygon2d(blocking_attention_object.object); - findCommonCvPolygons(obj_poly.outer(), blocking_polygons); - } - for (const auto & blocking_polygon : blocking_polygons) { - cv::fillPoly(attention_mask, blocking_polygon, cv::Scalar(BLOCKED), cv::LINE_AA); - } - for (const auto & division : lane_divisions) { - bool blocking_vehicle_found = false; - for (const auto & point_it : division) { - const auto [valid, idx_x, idx_y] = coord2index(point_it.x(), point_it.y()); - if (!valid) continue; - if (blocking_vehicle_found) { - occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; - continue; - } - if (attention_mask.at(height - 1 - idx_y, idx_x) == BLOCKED) { - blocking_vehicle_found = true; - occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; - } - } - } - - // (4) extract occlusion polygons - const auto & possible_object_bbox = planner_param_.occlusion.possible_object_bbox; - const double possible_object_bbox_x = possible_object_bbox.at(0) / resolution; - const double possible_object_bbox_y = possible_object_bbox.at(1) / resolution; - const double possible_object_area = possible_object_bbox_x * possible_object_bbox_y; - std::vector> contours; - cv::findContours(occlusion_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); - std::vector> valid_contours; - for (const auto & contour : contours) { - if (contour.size() <= 2) { - continue; - } - std::vector approx_contour; - cv::approxPolyDP( - contour, approx_contour, - std::round(std::min(possible_object_bbox_x, possible_object_bbox_y) / std::sqrt(2.0)), true); - if (approx_contour.size() <= 2) continue; - // check area - const double poly_area = cv::contourArea(approx_contour); - if (poly_area < possible_object_area) continue; - // check bounding box size - const auto bbox = cv::minAreaRect(approx_contour); - if (const auto size = bbox.size; std::min(size.height, size.width) < - std::min(possible_object_bbox_x, possible_object_bbox_y) || - std::max(size.height, size.width) < - std::max(possible_object_bbox_x, possible_object_bbox_y)) { - continue; - } - valid_contours.push_back(approx_contour); - geometry_msgs::msg::Polygon polygon_msg; - geometry_msgs::msg::Point32 point_msg; - for (const auto & p : approx_contour) { - const double glob_x = (p.x + 0.5) * resolution + origin.x; - const double glob_y = (height - 0.5 - p.y) * resolution + origin.y; - point_msg.x = glob_x; - point_msg.y = glob_y; - point_msg.z = origin.z; - polygon_msg.points.push_back(point_msg); - } - debug_data_.occlusion_polygons.push_back(polygon_msg); - } - // (4.1) re-draw occluded cells using valid_contours - occlusion_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); - for (const auto & valid_contour : valid_contours) { - // NOTE: drawContour does not work well - cv::fillPoly(occlusion_mask, valid_contour, cv::Scalar(OCCLUDED), cv::LINE_AA); - } - - // (5) find distance - // (5.1) discretize path_ip with resolution for computational cost - LineString2d path_linestring; - path_linestring.emplace_back( - path_ip.points.at(lane_start_idx).point.pose.position.x, - path_ip.points.at(lane_start_idx).point.pose.position.y); - { - auto prev_path_linestring_point = path_ip.points.at(lane_start_idx).point.pose.position; - for (auto i = lane_start_idx + 1; i <= lane_end_idx; i++) { - const auto path_linestring_point = path_ip.points.at(i).point.pose.position; - if ( - tier4_autoware_utils::calcDistance2d(prev_path_linestring_point, path_linestring_point) < - 1.0 /* rough tick for computational cost */) { - continue; - } - path_linestring.emplace_back(path_linestring_point.x, path_linestring_point.y); - prev_path_linestring_point = path_linestring_point; - } - } - - auto findNearestPointToProjection = []( - const lanelet::ConstLineString3d & division, - const Point2d & projection, const double dist_thresh) { - double min_dist = std::numeric_limits::infinity(); - auto nearest = division.end(); - for (auto it = division.begin(); it != division.end(); it++) { - const double dist = std::hypot(it->x() - projection.x(), it->y() - projection.y()); - if (dist < min_dist) { - min_dist = dist; - nearest = it; - } - if (dist < dist_thresh) { - break; - } - } - return nearest; - }; - struct NearestOcclusionPoint - { - int64 division_index{0}; - int64 point_index{0}; - double dist{0.0}; - geometry_msgs::msg::Point point; - geometry_msgs::msg::Point projection; - } nearest_occlusion_point; - double min_dist = std::numeric_limits::infinity(); - for (unsigned division_index = 0; division_index < lane_divisions.size(); ++division_index) { - const auto & division = lane_divisions.at(division_index); - LineString2d division_linestring; - auto division_point_it = division.begin(); - division_linestring.emplace_back(division_point_it->x(), division_point_it->y()); - for (auto point_it = division.begin(); point_it != division.end(); point_it++) { - if ( - std::hypot(point_it->x() - division_point_it->x(), point_it->y() - division_point_it->y()) < - 3.0 /* rough tick for computational cost */) { - continue; - } - division_linestring.emplace_back(point_it->x(), point_it->y()); - division_point_it = point_it; - } - - // find the intersection point of lane_line and path - std::vector intersection_points; - boost::geometry::intersection(division_linestring, path_linestring, intersection_points); - if (intersection_points.empty()) { - continue; - } - const auto & projection_point = intersection_points.at(0); - const auto projection_it = findNearestPointToProjection(division, projection_point, resolution); - if (projection_it == division.end()) { - continue; - } - double acc_dist = 0.0; - auto acc_dist_it = projection_it; - for (auto point_it = projection_it; point_it != division.end(); point_it++) { - const double dist = - std::hypot(point_it->x() - acc_dist_it->x(), point_it->y() - acc_dist_it->y()); - acc_dist += dist; - acc_dist_it = point_it; - const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); - if (!valid) continue; - const auto pixel = occlusion_mask.at(height - 1 - idx_y, idx_x); - if (pixel == BLOCKED) { - break; - } - if (pixel == OCCLUDED) { - if (acc_dist < min_dist) { - min_dist = acc_dist; - nearest_occlusion_point = { - division_index, std::distance(division.begin(), point_it), acc_dist, - tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z), - tier4_autoware_utils::createPoint(projection_it->x(), projection_it->y(), origin.z)}; - } - } - } - } - - if (min_dist == std::numeric_limits::infinity() || min_dist > occlusion_dist_thr) { - return OcclusionType::NOT_OCCLUDED; - } - - debug_data_.nearest_occlusion_projection = - std::make_pair(nearest_occlusion_point.point, nearest_occlusion_point.projection); - LineString2d ego_occlusion_line; - ego_occlusion_line.emplace_back(current_pose.position.x, current_pose.position.y); - ego_occlusion_line.emplace_back(nearest_occlusion_point.point.x, nearest_occlusion_point.point.y); - for (const auto & attention_object : target_objects.all_attention_objects) { - const auto obj_poly = tier4_autoware_utils::toPolygon2d(attention_object.object); - if (bg::intersects(obj_poly, ego_occlusion_line)) { - return OcclusionType::DYNAMICALLY_OCCLUDED; - } - } - for (const auto & attention_object : target_objects.intersection_area_objects) { - const auto obj_poly = tier4_autoware_utils::toPolygon2d(attention_object.object); - if (bg::intersects(obj_poly, ego_occlusion_line)) { - return OcclusionType::DYNAMICALLY_OCCLUDED; - } + (over_default_stopline_for_pass_judge && over_pass_judge_line_overall && was_safe) || + is_permanent_go_) { + // ========================================================================================== + // this body is active if ego is + // - over the default stopline AND + // - over the 1st && 2nd pass judge line AND + // - previously safe + // , + // which means ego can stop even if it is over the 1st pass judge line but + // - before default stopline OR + // - before the 2nd pass judge line OR + // - or previously unsafe + // . + // + // in order for ego to continue peeking or collision detection when occlusion is detected + // after ego passed the 1st pass judge line, it needs to be + // - before the default stopline OR + // - before the 2nd pass judge line OR + // - previously unsafe + // ========================================================================================== + is_permanent_go_ = true; } - return OcclusionType::STATICALLY_OCCLUDED; + return { + is_over_1st_pass_judge_line, is_over_2nd_pass_judge_line, + safely_passed_1st_judge_line_first_time, safely_passed_2nd_judge_line_first_time}; } -/* - bool IntersectionModule::checkFrontVehicleDeceleration( - lanelet::ConstLanelets & ego_lane_with_next_lane, lanelet::ConstLanelet & closest_lanelet, - const Polygon2d & stuck_vehicle_detect_area, - const autoware_auto_perception_msgs::msg::PredictedObject & object, - const double assumed_front_car_decel) - { - const auto & object_pose = object.kinematics.initial_pose_with_covariance.pose; - // consider vehicle in ego-lane && in front of ego - const auto lon_vel = object.kinematics.initial_twist_with_covariance.twist.linear.x; - const double object_decel = - planner_param_.stuck_vehicle.assumed_front_car_decel; // NOTE: this is positive - const double stopping_distance = lon_vel * lon_vel / (2 * object_decel); - - std::vector center_points; - for (auto && p : ego_lane_with_next_lane[0].centerline()) - center_points.push_back(std::move(lanelet::utils::conversion::toGeomMsgPt(p))); - for (auto && p : ego_lane_with_next_lane[1].centerline()) - center_points.push_back(std::move(lanelet::utils::conversion::toGeomMsgPt(p))); - const double lat_offset = - std::fabs(motion_utils::calcLateralOffset(center_points, object_pose.position)); - // get the nearest centerpoint to object - std::vector dist_obj_center_points; - for (const auto & p : center_points) - dist_obj_center_points.push_back(tier4_autoware_utils::calcDistance2d(object_pose.position, - p)); const int obj_closest_centerpoint_idx = std::distance( dist_obj_center_points.begin(), - std::min_element(dist_obj_center_points.begin(), dist_obj_center_points.end())); - // find two center_points whose distances from `closest_centerpoint` cross stopping_distance - double acc_dist_prev = 0.0, acc_dist = 0.0; - auto p1 = center_points[obj_closest_centerpoint_idx]; - auto p2 = center_points[obj_closest_centerpoint_idx]; - for (unsigned i = obj_closest_centerpoint_idx; i < center_points.size() - 1; ++i) { - p1 = center_points[i]; - p2 = center_points[i + 1]; - acc_dist_prev = acc_dist; - const auto arc_position_p1 = - lanelet::utils::getArcCoordinates(ego_lane_with_next_lane, toPose(p1)); - const auto arc_position_p2 = - lanelet::utils::getArcCoordinates(ego_lane_with_next_lane, toPose(p2)); - const double delta = arc_position_p2.length - arc_position_p1.length; - acc_dist += delta; - if (acc_dist > stopping_distance) { - break; - } - } - // if stopping_distance >= center_points, stopping_point is center_points[end] - const double ratio = (acc_dist <= stopping_distance) - ? 0.0 - : (acc_dist - stopping_distance) / (stopping_distance - acc_dist_prev); - // linear interpolation - geometry_msgs::msg::Point stopping_point; - stopping_point.x = (p1.x * ratio + p2.x) / (1 + ratio); - stopping_point.y = (p1.y * ratio + p2.y) / (1 + ratio); - stopping_point.z = (p1.z * ratio + p2.z) / (1 + ratio); - const double lane_yaw = lanelet::utils::getLaneletAngle(closest_lanelet, stopping_point); - stopping_point.x += lat_offset * std::cos(lane_yaw + M_PI / 2.0); - stopping_point.y += lat_offset * std::sin(lane_yaw + M_PI / 2.0); - - // calculate footprint of predicted stopping pose - autoware_auto_perception_msgs::msg::PredictedObject predicted_object = object; - predicted_object.kinematics.initial_pose_with_covariance.pose.position = stopping_point; - predicted_object.kinematics.initial_pose_with_covariance.pose.orientation = - tier4_autoware_utils::createQuaternionFromRPY(0, 0, lane_yaw); - auto predicted_obj_footprint = tier4_autoware_utils::toPolygon2d(predicted_object); - const bool is_in_stuck_area = !bg::disjoint(predicted_obj_footprint, stuck_vehicle_detect_area); - debug_data_.predicted_obj_pose.position = stopping_point; - debug_data_.predicted_obj_pose.orientation = - tier4_autoware_utils::createQuaternionFromRPY(0, 0, lane_yaw); - - if (is_in_stuck_area) { - return true; - } - return false; - } -*/ - } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 7366bdc1bc0e6..8c8874156f07b 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -15,7 +15,12 @@ #ifndef SCENE_INTERSECTION_HPP_ #define SCENE_INTERSECTION_HPP_ -#include "util_type.hpp" +#include "decision_result.hpp" +#include "interpolated_path_info.hpp" +#include "intersection_lanelets.hpp" +#include "intersection_stoplines.hpp" +#include "object_manager.hpp" +#include "result.hpp" #include #include @@ -23,16 +28,17 @@ #include #include -#include #include -#include -#include +#include +#include +#include -#include #include +#include #include #include +#include #include #include #include @@ -40,8 +46,6 @@ namespace behavior_velocity_planner { -using TimeDistanceArray = std::vector>; - class IntersectionModule : public SceneModuleInterface { public: @@ -59,6 +63,7 @@ class IntersectionModule : public SceneModuleInterface double max_accel; double max_jerk; double delay_response_time; + bool enable_pass_judge_before_default_stopline; } common; struct TurnDirection @@ -68,18 +73,30 @@ class IntersectionModule : public SceneModuleInterface bool straight; }; + struct TargetType + { + bool car; + bool bus; + bool truck; + bool trailer; + bool motorcycle; + bool bicycle; + bool unknown; + }; + struct StuckVehicle { + TargetType target_type; TurnDirection turn_direction; bool use_stuck_stopline; double stuck_vehicle_detect_dist; double stuck_vehicle_velocity_threshold; - double timeout_private_area; - bool enable_private_area_stuck_disregard; + bool disable_against_private_lane; } stuck_vehicle; struct YieldStuck { + TargetType target_type; TurnDirection turn_direction; double distance_threshold; } yield_stuck; @@ -89,7 +106,7 @@ class IntersectionModule : public SceneModuleInterface bool consider_wrong_direction_vehicle; double collision_detection_hold_time; double min_predicted_path_confidence; - double keep_detection_velocity_threshold; + TargetType target_type; struct VelocityProfile { bool use_upstream; @@ -120,12 +137,20 @@ class IntersectionModule : public SceneModuleInterface } yield_on_green_traffic_light; struct IgnoreOnAmberTrafficLight { - double object_expected_deceleration; + struct ObjectExpectedDeceleration + { + double car; + double bike; + } object_expected_deceleration; } ignore_on_amber_traffic_light; struct IgnoreOnRedTrafficLight { double object_margin_to_path; } ignore_on_red_traffic_light; + struct AvoidCollisionByAcceleration + { + double object_time_margin_to_collision_point; + } avoid_collision_by_acceleration; } collision_detection; struct Occlusion @@ -159,118 +184,147 @@ class IntersectionModule : public SceneModuleInterface } debug; }; - enum OcclusionType { - NOT_OCCLUDED, - STATICALLY_OCCLUDED, - DYNAMICALLY_OCCLUDED, - RTC_OCCLUDED, // actual occlusion does not exist, only disapproved by RTC - }; - - struct Indecisive - { - std::string error; - }; - struct StuckStop + //! occlusion is not detected + struct NotOccluded { - size_t closest_idx{0}; - size_t stuck_stopline_idx{0}; - std::optional occlusion_stopline_idx{std::nullopt}; + double best_clearance_distance{-1.0}; }; - struct YieldStuckStop + //! occlusion is not caused by dynamic objects + struct StaticallyOccluded { - size_t closest_idx{0}; - size_t stuck_stopline_idx{0}; + double best_clearance_distance{0.0}; }; - struct NonOccludedCollisionStop + //! occlusion is caused by dynamic objects + struct DynamicallyOccluded { - size_t closest_idx{0}; - size_t collision_stopline_idx{0}; - size_t occlusion_stopline_idx{0}; + double best_clearance_distance{0.0}; }; - struct FirstWaitBeforeOcclusion - { - bool is_actually_occlusion_cleared{false}; - size_t closest_idx{0}; - size_t first_stopline_idx{0}; - size_t occlusion_stopline_idx{0}; - }; - // A state peeking to occlusion limit line in the presence of traffic light - struct PeekingTowardOcclusion + //! actual occlusion does not exist, only disapproved by RTC + using RTCOccluded = std::monostate; + using OcclusionType = + std::variant; + + struct DebugData { - // NOTE: if intersection_occlusion is disapproved externally through RTC, - // it indicates "is_forcefully_occluded" - bool is_actually_occlusion_cleared{false}; - bool temporal_stop_before_attention_required{false}; - size_t closest_idx{0}; - size_t collision_stopline_idx{0}; - size_t first_attention_stopline_idx{0}; - size_t occlusion_stopline_idx{0}; - // if null, it is dynamic occlusion and shows up intersection_occlusion(dyn) - // if valid, it contains the remaining time to release the static occlusion stuck and shows up - // intersection_occlusion(x.y) - std::optional static_occlusion_timeout{std::nullopt}; + std::optional collision_stop_wall_pose{std::nullopt}; + std::optional occlusion_stop_wall_pose{std::nullopt}; + std::optional occlusion_first_stop_wall_pose{std::nullopt}; + std::optional first_pass_judge_wall_pose{std::nullopt}; + std::optional second_pass_judge_wall_pose{std::nullopt}; + bool passed_first_pass_judge{false}; + bool passed_second_pass_judge{false}; + std::optional absence_traffic_light_creep_wall{std::nullopt}; + + std::optional> attention_area{std::nullopt}; + std::optional> occlusion_attention_area{std::nullopt}; + std::optional> adjacent_area{std::nullopt}; + std::optional first_attention_area{std::nullopt}; + std::optional second_attention_area{std::nullopt}; + std::optional ego_lane{std::nullopt}; + std::optional stuck_vehicle_detect_area{std::nullopt}; + std::optional> yield_stuck_detect_area{std::nullopt}; + + std::optional candidate_collision_ego_lane_polygon{std::nullopt}; + autoware_auto_perception_msgs::msg::PredictedObjects safe_under_traffic_control_targets; + autoware_auto_perception_msgs::msg::PredictedObjects unsafe_targets; + autoware_auto_perception_msgs::msg::PredictedObjects misjudge_targets; + autoware_auto_perception_msgs::msg::PredictedObjects too_late_detect_targets; + autoware_auto_perception_msgs::msg::PredictedObjects stuck_targets; + autoware_auto_perception_msgs::msg::PredictedObjects yield_stuck_targets; + autoware_auto_perception_msgs::msg::PredictedObjects parked_targets; + std::vector occlusion_polygons; + std::optional> + nearest_occlusion_projection{std::nullopt}; + std::optional< + std::tuple> + nearest_occlusion_triangle{std::nullopt}; + bool static_occlusion{false}; + std::optional static_occlusion_with_traffic_light_timeout{std::nullopt}; + + std::optional> + traffic_light_observation{std::nullopt}; }; - // A state detecting both collision and occlusion in the presence of traffic light - struct OccludedCollisionStop + + struct InternalDebugData { - bool is_actually_occlusion_cleared{false}; - bool temporal_stop_before_attention_required{false}; - size_t closest_idx{0}; - size_t collision_stopline_idx{0}; - size_t first_attention_stopline_idx{0}; - size_t occlusion_stopline_idx{0}; - // if null, it is dynamic occlusion and shows up intersection_occlusion(dyn) - // if valid, it contains the remaining time to release the static occlusion stuck - std::optional static_occlusion_timeout{std::nullopt}; + double distance{0.0}; + std::string decision_type{}; + std::optional tl_observation{std::nullopt}; }; - struct OccludedAbsenceTrafficLight - { - bool is_actually_occlusion_cleared{false}; - bool collision_detected{false}; - bool temporal_stop_before_attention_required{false}; - size_t closest_idx{0}; - size_t first_attention_area_stopline_idx{0}; - size_t peeking_limit_line_idx{0}; + + using TimeDistanceArray = std::vector>; + + /** + * @brief categorize traffic light priority + */ + enum class TrafficPrioritizedLevel { + //! The target lane's traffic signal is red or the ego's traffic signal has an arrow. + FULLY_PRIORITIZED = 0, + //! The target lane's traffic signal is amber + PARTIALLY_PRIORITIZED, + //! The target lane's traffic signal is green + NOT_PRIORITIZED }; - struct Safe + /** @} */ + + /** + * @brief + */ + struct PassJudgeStatus { - // NOTE: if RTC is disapproved status, default stop lines are still needed. - size_t closest_idx{0}; - size_t collision_stopline_idx{0}; - size_t occlusion_stopline_idx{0}; + //! true if ego is over the 1st pass judge line + const bool is_over_1st_pass_judge; + + //! true if second_attention_lane exists and ego is over the 2nd pass judge line + const std::optional is_over_2nd_pass_judge; + + //! true only when ego passed 1st pass judge line safely for the first time + const bool safely_passed_1st_judge_line; + + //! true only when ego passed 2nd pass judge line safely for the first time + const bool safely_passed_2nd_judge_line; }; - struct FullyPrioritized + + /** + * @brief + */ + struct CollisionStatus { - bool collision_detected{false}; - size_t closest_idx{0}; - size_t collision_stopline_idx{0}; - size_t occlusion_stopline_idx{0}; + enum BlameType { + BLAME_AT_FIRST_PASS_JUDGE, + BLAME_AT_SECOND_PASS_JUDGE, + }; + const bool collision_detected; + const intersection::CollisionInterval::LanePosition collision_position; + const std::vector>> + too_late_detect_objects; + const std::vector>> + misjudge_objects; }; - using DecisionResult = std::variant< - Indecisive, // internal process error, or over the pass judge line - StuckStop, // detected stuck vehicle - YieldStuckStop, // detected yield stuck vehicle - NonOccludedCollisionStop, // detected collision while FOV is clear - FirstWaitBeforeOcclusion, // stop for a while before peeking to occlusion - PeekingTowardOcclusion, // peeking into occlusion while collision is not detected - OccludedCollisionStop, // occlusion and collision are both detected - OccludedAbsenceTrafficLight, // occlusion is detected in the absence of traffic light - Safe, // judge as safe - FullyPrioritized // only detect vehicles violating traffic rules - >; IntersectionModule( const int64_t module_id, const int64_t lane_id, std::shared_ptr planner_data, const PlannerParam & planner_param, const std::set & associative_ids, - const std::string & turn_direction, const bool has_traffic_light, - const bool enable_occlusion_detection, const bool is_private_area, rclcpp::Node & node, + const std::string & turn_direction, const bool has_traffic_light, rclcpp::Node & node, const rclcpp::Logger logger, const rclcpp::Clock::SharedPtr clock); /** - * @brief plan go-stop velocity at traffic crossing with collision check between reference path - * and object predicted path + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup primary-function [fn] primary functions + * the entrypoint of this module is modifyPathVelocity() function that calculates safety decision + * of latest context and send it to RTC and then react to RTC approval. The reaction to RTC + * approval may not be based on the latest decision of this module depending on the auto-mode + * configuration. For module side it is not visible if the module is operating in auto-mode or + * manual-module. At first, initializeRTCStatus() is called to reset the safety value of + * INTERSECTION and INTERSECTION_OCCLUSION. Then modifyPathVelocityDetail() is called to analyze + * the context. Then prepareRTCStatus() is called to set the safety value of INTERSECTION and + * INTERSECTION_OCCLUSION. + * @{ */ bool modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) override; + /** @}*/ visualization_msgs::msg::MarkerArray createDebugMarkerArray() override; motion_utils::VirtualWalls createVirtualWalls() override; @@ -281,95 +335,496 @@ class IntersectionModule : public SceneModuleInterface bool getOcclusionSafety() const { return occlusion_safety_; } double getOcclusionDistance() const { return occlusion_stop_distance_; } void setOcclusionActivation(const bool activation) { occlusion_activated_ = activation; } - bool isOcclusionFirstStopRequired() { return occlusion_first_stop_required_; } + bool isOcclusionFirstStopRequired() const { return occlusion_first_stop_required_; } + InternalDebugData & getInternalDebugData() const { return internal_debug_data_; } private: - rclcpp::Node & node_; - const int64_t lane_id_; + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup const-variables [var] const variables + * following variables are unique to this intersection lanelet or to this module + * @{ + */ + + const PlannerParam planner_param_; + + //! lanelet of this intersection + const lanelet::Id lane_id_; + + //! associative(sibling) lanelets ids const std::set associative_ids_; + + //! turn_direction of this lane const std::string turn_direction_; + + //! flag if this intersection is traffic controlled const bool has_traffic_light_; - bool is_go_out_{false}; - bool is_permanent_go_{false}; - DecisionResult prev_decision_result_{Indecisive{""}}; - OcclusionType prev_occlusion_status_; + //! RTC uuid for INTERSECTION_OCCLUSION + const UUID occlusion_uuid_; + /** @}*/ - // Parameter - PlannerParam planner_param_; +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup semi-const-variables [var] semi-const variables + * following variables are immutable once initialized + * @{ + */ - std::optional intersection_lanelets_{std::nullopt}; + //! cache IntersectionLanelets struct + std::optional intersection_lanelets_{std::nullopt}; - // for occlusion detection - const bool enable_occlusion_detection_; + //! cache discretized occlusion detection lanelets std::optional> occlusion_attention_divisions_{ std::nullopt}; - StateMachine collision_state_machine_; //! for stable collision checking - StateMachine before_creep_state_machine_; //! for two phase stop - StateMachine occlusion_stop_state_machine_; - StateMachine temporal_stop_before_attention_state_machine_; - StateMachine static_occlusion_timeout_state_machine_; - // for pseudo-collision detection when ego just entered intersection on green light and upcoming - // vehicles are very slow + //! save the time when ego observed green traffic light before entering the intersection std::optional initial_green_light_observed_time_{std::nullopt}; + /** @}*/ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup pass-judge-variable [var] pass judge variables + * following variables are state variables that depends on how the vehicle passed the intersection + * @{ + */ + //! if true, this module never commands to STOP anymore + bool is_permanent_go_{false}; - // for stuck vehicle detection - const bool is_private_area_; + //! for checking if ego is over the pass judge lines because previously the situation was SAFE + intersection::DecisionResult prev_decision_result_{intersection::InternalError{""}}; - // for RTC - const UUID occlusion_uuid_; + //! flag if ego passed the 1st_pass_judge_line while peeking. If this is true, 1st_pass_judge_line + //! is treated as the same position as occlusion_peeking_stopline + bool passed_1st_judge_line_while_peeking_{false}; + + //! save the time and ego position when ego passed the 1st/2nd_pass_judge_line with safe + //! decision. If collision is expected after these variables are non-null, then it is the fault of + //! past perception failure at these time. + std::optional> + safely_passed_1st_judge_line_time_{std::nullopt}; + std::optional> + safely_passed_2nd_judge_line_time_{std::nullopt}; + /** @}*/ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup collision-variables [var] collision detection + * @{ + */ + //! debouncing for stable SAFE decision + StateMachine collision_state_machine_; + + //! container for storing safety status of objects on the attention area + intersection::ObjectInfoManager object_info_manager_; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup collision-variables [var] collision detection + * @{ + */ + //! save the last UNKNOWN traffic light information of this intersection(keep even if the info got + //! unavailable) + std::optional> tl_id_and_point_; + std::optional last_tl_valid_observation_{std::nullopt}; + + //! save previous priority level to detect change from NotPrioritized to Prioritized + TrafficPrioritizedLevel previous_prioritized_level_{TrafficPrioritizedLevel::NOT_PRIORITIZED}; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup occlusion-variables [var] occlusion detection variables + * @{ + */ + OcclusionType prev_occlusion_status_{NotOccluded{}}; + + //! debouncing for the first brief stop at the default stopline + StateMachine before_creep_state_machine_; + + //! debouncing for stable CLEARED decision + StateMachine occlusion_stop_state_machine_; + + //! debouncing for the brief stop at the boundary of attention area(if required by the flag) + StateMachine temporal_stop_before_attention_state_machine_; + + //! time counter for the stuck detection due to occlusion caused static objects + StateMachine static_occlusion_timeout_state_machine_; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup RTC-variables [var] RTC variables + * + * intersection module has additional rtc_interface_ for INTERSECTION_OCCLUSION in addition to the + * default rtc_interface of SceneModuleManagerInterfaceWithRTC. activated_ is the derived member + * of this module which is updated by the RTC config/service, so it should be read-only in this + * module. occlusion_safety_ and occlusion_stop_distance_ are the corresponding RTC value for + * INTERSECTION_OCCLUSION. + * @{ + */ bool occlusion_safety_{true}; double occlusion_stop_distance_{0.0}; bool occlusion_activated_{true}; - // for first stop in two-phase stop bool occlusion_first_stop_required_{false}; + /** @}*/ +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @ingroup primary-functions + * @{ + */ + /** + * @brief set all RTC variable to true(safe) and -INF + */ void initializeRTCStatus(); + + /** + * @brief analyze traffic_light/occupancy/objects context and return DecisionResult + */ + intersection::DecisionResult modifyPathVelocityDetail( + PathWithLaneId * path, StopReason * stop_reason); + + /** + * @brief set RTC value according to calculated DecisionResult + */ void prepareRTCStatus( - const DecisionResult &, const autoware_auto_planning_msgs::msg::PathWithLaneId & path); + const intersection::DecisionResult &, + const autoware_auto_planning_msgs::msg::PathWithLaneId & path); - DecisionResult modifyPathVelocityDetail(PathWithLaneId * path, StopReason * stop_reason); + /** + * @brief act based on current RTC approval + */ + void reactRTCApproval( + const intersection::DecisionResult & decision_result, + autoware_auto_planning_msgs::msg::PathWithLaneId * path, StopReason * stop_reason); + /** @}*/ - bool checkStuckVehicle( - const std::shared_ptr & planner_data, - const util::PathLanelets & path_lanelets); +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup prepare-data [fn] basic data construction + * @{ + */ + /** + * @struct + */ + struct BasicData + { + intersection::InterpolatedPathInfo interpolated_path_info; + intersection::IntersectionStopLines intersection_stoplines; + intersection::PathLanelets path_lanelets; + }; - bool checkYieldStuckVehicle( - const util::TargetObjects & target_objects, - const util::InterpolatedPathInfo & interpolated_path_info, - const lanelet::ConstLanelets & attention_lanelets); + /** + * @brief prepare basic data structure + * @return return IntersectionStopLines if all data is valid, otherwise InternalError + * @note if successful, it is ensure that intersection_lanelets_, + * intersection_lanelets.first_conflicting_lane are not null + * + * To simplify modifyPathVelocityDetail(), this function is used at first + */ + intersection::Result prepareIntersectionData( + PathWithLaneId * path); - util::TargetObjects generateTargetObjects( - const util::IntersectionLanelets & intersection_lanelets, - const std::optional & intersection_area) const; + /** + * @brief find the associated stopline road marking of assigned lanelet + */ + std::optional getStopLineIndexFromMap( + const intersection::InterpolatedPathInfo & interpolated_path_info, + lanelet::ConstLanelet assigned_lanelet) const; - bool checkCollision( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - util::TargetObjects * target_objects, const util::PathLanelets & path_lanelets, - const size_t closest_idx, const size_t last_intersection_stopline_candidate_idx, - const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level); + /** + * @brief generate IntersectionStopLines + */ + std::optional generateIntersectionStopLines( + lanelet::ConstLanelet assigned_lanelet, + const lanelet::CompoundPolygon3d & first_conflicting_area, + const lanelet::ConstLanelet & first_attention_lane, + const std::optional & second_attention_area_opt, + const intersection::InterpolatedPathInfo & interpolated_path_info, + autoware_auto_planning_msgs::msg::PathWithLaneId * original_path) const; + + /** + * @brief generate IntersectionLanelets + */ + intersection::IntersectionLanelets generateObjectiveLanelets( + lanelet::LaneletMapConstPtr lanelet_map_ptr, + lanelet::routing::RoutingGraphPtr routing_graph_ptr, + const lanelet::ConstLanelet assigned_lanelet) const; - OcclusionType getOcclusionStatus( - const nav_msgs::msg::OccupancyGrid & occ_grid, + /** + * @brief generate PathLanelets + */ + std::optional generatePathLanelets( + const lanelet::ConstLanelets & lanelets_on_path, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const lanelet::CompoundPolygon3d & first_conflicting_area, + const std::vector & conflicting_areas, + const std::optional & first_attention_area, const std::vector & attention_areas, - const lanelet::ConstLanelets & adjacent_lanelets, - const lanelet::CompoundPolygon3d & first_attention_area, - const util::InterpolatedPathInfo & interpolated_path_info, - const std::vector & lane_divisions, - const util::TargetObjects & target_objects, const geometry_msgs::msg::Pose & current_pose, - const double occlusion_dist_thr); - - /* - bool IntersectionModule::checkFrontVehicleDeceleration( - lanelet::ConstLanelets & ego_lane_with_next_lane, lanelet::ConstLanelet & closest_lanelet, - const Polygon2d & stuck_vehicle_detect_area, - const autoware_auto_perception_msgs::msg::PredictedObject & object, - const double assumed_front_car_decel); - */ - - util::DebugData debug_data_; - rclcpp::Publisher::SharedPtr decision_state_pub_; + const size_t closest_idx) const; + + /** + * @brief generate discretized detection lane linestring. + */ + std::vector generateDetectionLaneDivisions( + lanelet::ConstLanelets detection_lanelets, + const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution) const; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup get-traffic-light [fn] traffic light + * @{ + */ + /** + * @brief check if associated traffic light is green + */ + bool isGreenSolidOn() const; + + /** + * @brief find TrafficPrioritizedLevel + */ + TrafficPrioritizedLevel getTrafficPrioritizedLevel() const; + + /** + * @brief update the valid traffic signal information if still available, otherwise keep last + * observation + */ + void updateTrafficSignalObservation(); + + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup yield [fn] check stuck + * @{ + */ + /** + * @brief check stuck status + * @attention this function has access to value() of intersection_lanelets_, + * intersection_lanelets.first_conflicting_lane(). They are ensured in prepareIntersectionData() + */ + std::optional isStuckStatus( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const intersection::IntersectionStopLines & intersection_stoplines, + const intersection::PathLanelets & path_lanelets) const; + + bool isTargetStuckVehicleType( + const autoware_auto_perception_msgs::msg::PredictedObject & object) const; + + bool isTargetYieldStuckVehicleType( + const autoware_auto_perception_msgs::msg::PredictedObject & object) const; + + /** + * @brief check stuck + */ + bool checkStuckVehicleInIntersection(const intersection::PathLanelets & path_lanelets) const; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup yield [fn] check yield stuck + * @{ + */ + /** + * @brief check yield stuck status + * @attention this function has access to value() of intersection_lanelets_, + * intersection_stoplines.default_stopline, intersection_stoplines.first_attention_stopline + */ + std::optional isYieldStuckStatus( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const intersection::IntersectionStopLines & intersection_stoplines) const; + + /** + * @brief check yield stuck + */ + bool checkYieldStuckVehicleInIntersection( + const intersection::InterpolatedPathInfo & interpolated_path_info, + const lanelet::ConstLanelets & attention_lanelets) const; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup occlusion [fn] check occlusion + * @{ + */ + /** + * @brief check occlusion status + * @attention this function has access to value() of occlusion_attention_divisions_, + * intersection_lanelets_ intersection_lanelets.first_attention_area() + */ + std::tuple< + OcclusionType, bool /* module detection with margin */, + bool /* reconciled occlusion disapproval */> + getOcclusionStatus( + const TrafficPrioritizedLevel & traffic_prioritized_level, + const intersection::InterpolatedPathInfo & interpolated_path_info); + + /** + * @brief calculate detected occlusion status(NOT | STATICALLY | DYNAMICALLY) + * @attention this function has access to value() of intersection_lanelets_, + * intersection_lanelets.first_attention_area(), occlusion_attention_divisions_ + */ + OcclusionType detectOcclusion( + const intersection::InterpolatedPathInfo & interpolated_path_info) const; + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup pass-judge-decision [fn] pass judge decision + * @{ + */ + /** + * @brief check if ego is already over the pass judge line + * @return if ego is over both 1st/2nd pass judge lines, return InternalError, else return + * (is_over_1st_pass_judge, is_over_2nd_pass_judge) + * @attention this function has access to value() of intersection_stoplines.default_stopline, + * intersection_stoplines.occlusion_stopline + */ + PassJudgeStatus isOverPassJudgeLinesStatus( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const bool is_occlusion_state, + const intersection::IntersectionStopLines & intersection_stoplines); + /** @} */ + +private: + /** + *********************************************************** + *********************************************************** + *********************************************************** + * @defgroup collision-detection [fn] check collision + * @{ + */ + bool isTargetCollisionVehicleType( + const autoware_auto_perception_msgs::msg::PredictedObject & object) const; + + /** + * @brief find the objects on attention_area/intersection_area and update positional information + * @attention this function has access to value() of intersection_lanelets_ + */ + void updateObjectInfoManagerArea(); + + /** + * @brief find the collision Interval/CollisionKnowledge of registered objects + * @attention this function has access to value() of intersection_lanelets_ + */ + void updateObjectInfoManagerCollision( + const intersection::PathLanelets & path_lanelets, const TimeDistanceArray & time_distance_array, + const TrafficPrioritizedLevel & traffic_prioritized_level, + const bool passed_1st_judge_line_first_time, const bool passed_2nd_judge_line_first_time, + tier4_debug_msgs::msg::Float64MultiArrayStamped * object_ttc_time_array); + + void cutPredictPathWithinDuration( + const builtin_interfaces::msg::Time & object_stamp, const double time_thr, + autoware_auto_perception_msgs::msg::PredictedPath * predicted_path) const; + + /** + * @brief check if there are any objects around the stoplines on the attention areas when ego + * entered the intersection on green light + * @return return NonOccludedCollisionStop if there are vehicle within the margin for some + * duration from ego's entry to yield + * @attention this function has access to value() of + * intersection_stoplines.occlusion_peeking_stopline + */ + std::optional isGreenPseudoCollisionStatus( + const size_t closest_idx, const size_t collision_stopline_idx, + const intersection::IntersectionStopLines & intersection_stoplines) const; + + /** + * @brief generate the message explaining why too_late_detect_objects/misjudge_objects exist and + * blame past perception fault + */ + std::string generateDetectionBlameDiagnosis( + const std::vector< + std::pair>> & + too_late_detect_objects, + const std::vector< + std::pair>> & + misjudge_objects) const; + + /** + * @brief generate the message explaining how much ego should accelerate to avoid future dangerous + * situation + */ + std::string generateEgoRiskEvasiveDiagnosis( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx, + const TimeDistanceArray & ego_time_distance_array, + const std::vector< + std::pair>> & + too_late_detect_objects, + const std::vector< + std::pair>> & + misjudge_objects) const; + + /** + * @brief return if collision is detected and the collision position + */ + CollisionStatus detectCollision( + const bool is_over_1st_pass_judge_line, + const std::optional is_over_2nd_pass_judge_line) const; + + std::optional checkAngleForTargetLanelets( + const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, + const bool is_parked_vehicle) const; + + /** + * @brief calculate ego vehicle profile along the path inside the intersection as the sequence of + * (time of arrival, traveled distance) from current ego position + * @attention this function has access to value() of + * intersection_stoplines.occlusion_peeking_stopline, + * intersection_stoplines.first_attention_stopline + */ + TimeDistanceArray calcIntersectionPassingTime( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const bool is_prioritized, + const intersection::IntersectionStopLines & intersection_stoplines, + tier4_debug_msgs::msg::Float64MultiArrayStamped * debug_ttc_array) const; + /** @} */ + + mutable DebugData debug_data_; + mutable InternalDebugData internal_debug_data_{}; rclcpp::Publisher::SharedPtr ego_ttc_pub_; rclcpp::Publisher::SharedPtr object_ttc_pub_; }; diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection_collision.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection_collision.cpp new file mode 100644 index 0000000000000..8388bc15492a3 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection_collision.cpp @@ -0,0 +1,977 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "scene_intersection.hpp" +#include "util.hpp" + +#include // for toGeomPoly +#include // for smoothPath +#include +#include +#include +#include // for toPolygon2d +#include + +#include +#include +#include + +#include +#include + +namespace behavior_velocity_planner +{ +namespace bg = boost::geometry; + +bool IntersectionModule::isTargetCollisionVehicleType( + const autoware_auto_perception_msgs::msg::PredictedObject & object) const +{ + const auto label = object.classification.at(0).label; + const auto & p = planner_param_.collision_detection.target_type; + + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::CAR && p.car) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::BUS && p.bus) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::TRUCK && p.truck) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::TRAILER && p.trailer) { + return true; + } + if ( + label == autoware_auto_perception_msgs::msg::ObjectClassification::MOTORCYCLE && p.motorcycle) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::BICYCLE && p.bicycle) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::UNKNOWN && p.unknown) { + return true; + } + return false; +} + +void IntersectionModule::updateObjectInfoManagerArea() +{ + const auto & intersection_lanelets = intersection_lanelets_.value(); + const auto & attention_lanelets = intersection_lanelets.attention(); + const auto & attention_lanelet_stoplines = intersection_lanelets.attention_stoplines(); + const auto & adjacent_lanelets = intersection_lanelets.adjacent(); + const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); + const auto & assigned_lanelet = lanelet_map_ptr->laneletLayer.get(lane_id_); + const auto intersection_area = util::getIntersectionArea(assigned_lanelet, lanelet_map_ptr); + + // ========================================================================================== + // entries that are not observed in this iteration need to be cleared + // + // NOTE: old_map is not referenced because internal data of object_info_manager is cleared + // ========================================================================================== + const auto old_map = object_info_manager_.getObjectsMap(); + object_info_manager_.clearObjects(); + + for (const auto & predicted_object : planner_data_->predicted_objects->objects) { + if (!isTargetCollisionVehicleType(predicted_object)) { + continue; + } + + // ========================================================================================== + // NOTE: is_parked_vehicle is used because sometimes slow vehicle direction is + // incorrect/reversed/flipped due to tracking. if is_parked_vehicle is true, object direction + // is not checked + // ========================================================================================== + const auto object_direction = + util::getObjectPoseWithVelocityDirection(predicted_object.kinematics); + const auto is_parked_vehicle = + std::fabs(predicted_object.kinematics.initial_twist_with_covariance.twist.linear.x) < + planner_param_.occlusion.ignore_parked_vehicle_speed_threshold; + + const auto belong_adjacent_lanelet_id = + checkAngleForTargetLanelets(object_direction, adjacent_lanelets, false); + if (belong_adjacent_lanelet_id) { + continue; + } + const auto belong_attention_lanelet_id = + checkAngleForTargetLanelets(object_direction, attention_lanelets, is_parked_vehicle); + const auto & obj_pos = predicted_object.kinematics.initial_pose_with_covariance.pose.position; + const bool in_intersection_area = [&]() { + if (!intersection_area) { + return false; + } + return bg::within( + tier4_autoware_utils::Point2d{obj_pos.x, obj_pos.y}, intersection_area.value()); + }(); + std::optional attention_lanelet{std::nullopt}; + std::optional stopline{std::nullopt}; + if (!belong_attention_lanelet_id && !in_intersection_area) { + continue; + } else if (belong_attention_lanelet_id) { + const auto idx = belong_attention_lanelet_id.value(); + attention_lanelet = attention_lanelets.at(idx); + stopline = attention_lanelet_stoplines.at(idx); + } + + const auto object_it = old_map.find(predicted_object.object_id); + if (object_it != old_map.end()) { + auto object_info = object_it->second; + object_info_manager_.registerExistingObject( + predicted_object.object_id, belong_attention_lanelet_id.has_value(), in_intersection_area, + is_parked_vehicle, object_info); + object_info->initialize(predicted_object, attention_lanelet, stopline); + } else { + auto object_info = object_info_manager_.registerObject( + predicted_object.object_id, belong_attention_lanelet_id.has_value(), in_intersection_area, + is_parked_vehicle); + object_info->initialize(predicted_object, attention_lanelet, stopline); + } + } +} + +void IntersectionModule::updateObjectInfoManagerCollision( + const intersection::PathLanelets & path_lanelets, + const IntersectionModule::TimeDistanceArray & time_distance_array, + const IntersectionModule::TrafficPrioritizedLevel & traffic_prioritized_level, + const bool passed_1st_judge_line_first_time, const bool passed_2nd_judge_line_first_time, + tier4_debug_msgs::msg::Float64MultiArrayStamped * object_ttc_time_array) +{ + const auto & intersection_lanelets = intersection_lanelets_.value(); + + if (passed_1st_judge_line_first_time) { + object_info_manager_.setPassed1stPassJudgeLineFirstTime(clock_->now()); + } + if (passed_2nd_judge_line_first_time) { + object_info_manager_.setPassed2ndPassJudgeLineFirstTime(clock_->now()); + } + + const double passing_time = time_distance_array.back().first; + const auto & concat_lanelets = path_lanelets.all; + const auto closest_arc_coords = + lanelet::utils::getArcCoordinates(concat_lanelets, planner_data_->current_odometry->pose); + const auto & ego_lane = path_lanelets.ego_or_entry2exit; + debug_data_.ego_lane = ego_lane.polygon3d(); + const auto ego_poly = ego_lane.polygon2d().basicPolygon(); + + // ========================================================================================== + // dynamically change TTC margin according to traffic light color to gradually relax from green to + // red + // ========================================================================================== + const auto [collision_start_margin_time, collision_end_margin_time] = [&]() { + if (traffic_prioritized_level == TrafficPrioritizedLevel::FULLY_PRIORITIZED) { + return std::make_pair( + planner_param_.collision_detection.fully_prioritized.collision_start_margin_time, + planner_param_.collision_detection.fully_prioritized.collision_end_margin_time); + } + if (traffic_prioritized_level == TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED) { + return std::make_pair( + planner_param_.collision_detection.partially_prioritized.collision_start_margin_time, + planner_param_.collision_detection.partially_prioritized.collision_end_margin_time); + } + return std::make_pair( + planner_param_.collision_detection.not_prioritized.collision_start_margin_time, + planner_param_.collision_detection.not_prioritized.collision_end_margin_time); + }(); + + constexpr size_t object_debug_size = 57; + { + object_ttc_time_array->stamp = clock_->now(); + object_ttc_time_array->layout.dim.resize(3); + object_ttc_time_array->layout.dim.at(0).label = "objects"; + object_ttc_time_array->layout.dim.at(0).size = + 1; // incremented in the loop, first row is lane_id + object_ttc_time_array->layout.dim.at(1).label = + "[x, y, th, length, width, speed, dangerous, ref_obj_enter_time, ref_obj_exit_time, " + "start_time, start_dist, " + "end_time, end_dist, first_collision_x, first_collision_y, last_collision_x, " + "last_collision_y, " + "prd_x[0], ... pred_x[19], pred_y[0], ... pred_y[19]]"; + object_ttc_time_array->layout.dim.at(1).size = object_debug_size; + for (unsigned i = 0; i < object_debug_size; ++i) { + object_ttc_time_array->data.push_back(lane_id_); + } + } + + for (auto & object_info : object_info_manager_.attentionObjects()) { + const auto & predicted_object = object_info->predicted_object(); + bool safe_under_traffic_control = false; + const auto label = predicted_object.classification.at(0).label; + const auto expected_deceleration = + (label == autoware_auto_perception_msgs::msg::ObjectClassification::MOTORCYCLE || + label == autoware_auto_perception_msgs::msg::ObjectClassification::BICYCLE) + ? planner_param_.collision_detection.ignore_on_amber_traffic_light + .object_expected_deceleration.bike + : planner_param_.collision_detection.ignore_on_amber_traffic_light + .object_expected_deceleration.car; + if ( + traffic_prioritized_level == TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED && + object_info->can_stop_before_stopline(expected_deceleration)) { + safe_under_traffic_control = true; + } + if ( + traffic_prioritized_level == TrafficPrioritizedLevel::FULLY_PRIORITIZED && + object_info->can_stop_before_ego_lane( + expected_deceleration, + planner_param_.collision_detection.ignore_on_red_traffic_light.object_margin_to_path, + ego_lane)) { + safe_under_traffic_control = true; + } + + // ========================================================================================== + // check the PredictedPath in the ascending order of its confidence to save the safe/unsafe + // CollisionKnowledge for most probable path + // ========================================================================================== + std::list sorted_predicted_paths; + for (unsigned i = 0; i < predicted_object.kinematics.predicted_paths.size(); ++i) { + sorted_predicted_paths.push_back(&predicted_object.kinematics.predicted_paths.at(i)); + } + sorted_predicted_paths.sort( + [](const auto path1, const auto path2) { return path1->confidence > path2->confidence; }); + + // ========================================================================================== + // if all of the predicted path is lower confidence/geometrically does not intersect with ego + // path, both will be null, which is interpreted as SAFE. if any of the path is "normal", either + // of them has value, not both + // ========================================================================================== + std::optional unsafe_interval{std::nullopt}; + std::optional safe_interval{std::nullopt}; + std::optional> object_debug_info{std::nullopt}; + + for (const auto & predicted_path_ptr : sorted_predicted_paths) { + auto predicted_path = *predicted_path_ptr; + if ( + predicted_path.confidence < + planner_param_.collision_detection.min_predicted_path_confidence) { + continue; + } + cutPredictPathWithinDuration( + planner_data_->predicted_objects->header.stamp, passing_time, &predicted_path); + const auto object_passage_interval_opt = intersection::findPassageInterval( + predicted_path, predicted_object.shape, ego_poly, + intersection_lanelets.first_attention_lane(), + intersection_lanelets.second_attention_lane()); + if (!object_passage_interval_opt) { + // there is no chance of geometric collision for the entire prediction horizon + continue; + } + const auto & object_passage_interval = object_passage_interval_opt.value(); + const auto [object_enter_time, object_exit_time] = object_passage_interval.interval_time; + const auto ego_start_itr = std::lower_bound( + time_distance_array.begin(), time_distance_array.end(), + object_enter_time - collision_start_margin_time, + [](const auto & a, const double b) { return a.first < b; }); + if (ego_start_itr == time_distance_array.end()) { + // ========================================================================================== + // this is the case where at time "object_enter_time - collision_start_margin_time", ego is + // arriving at the exit of the intersection, which means even if we assume that the object + // accelerates and the first collision happens faster by the TTC margin, ego will be already + // arriving at the exist of the intersection. + // ========================================================================================== + continue; + } + auto ego_end_itr = std::lower_bound( + time_distance_array.begin(), time_distance_array.end(), + object_exit_time + collision_end_margin_time, + [](const auto & a, const double b) { return a.first < b; }); + if (ego_end_itr == time_distance_array.end()) { + ego_end_itr = time_distance_array.end() - 1; + } + const double ego_start_arc_length = std::max( + 0.0, closest_arc_coords.length + ego_start_itr->second - + planner_data_->vehicle_info_.rear_overhang_m); + const double ego_end_arc_length = std::min( + closest_arc_coords.length + ego_end_itr->second + + planner_data_->vehicle_info_.max_longitudinal_offset_m, + lanelet::utils::getLaneletLength2d(concat_lanelets)); + const auto trimmed_ego_polygon = lanelet::utils::getPolygonFromArcLength( + concat_lanelets, ego_start_arc_length, ego_end_arc_length); + if (trimmed_ego_polygon.empty()) { + continue; + } + Polygon2d polygon{}; + for (const auto & p : trimmed_ego_polygon) { + polygon.outer().emplace_back(p.x(), p.y()); + } + bg::correct(polygon); + debug_data_.candidate_collision_ego_lane_polygon = toGeomPoly(polygon); + + const auto & object_path = object_passage_interval.path; + const auto [begin, end] = object_passage_interval.interval_position; + bool collision_detected = false; + for (auto i = begin; i <= end; ++i) { + if (bg::intersects( + polygon, + tier4_autoware_utils::toPolygon2d(object_path.at(i), predicted_object.shape))) { + collision_detected = true; + break; + } + } + auto get_object_info = [&]() { + // debug info + const auto & pose = predicted_object.kinematics.initial_pose_with_covariance.pose; + const auto & shape = predicted_object.shape; + const auto [object_start_itr, object_end_itr] = object_passage_interval.interval_position; + const auto & object_start_pos = object_passage_interval.path.at(object_start_itr).position; + const auto & object_end_pos = object_passage_interval.path.at(object_end_itr).position; + std::vector debug; + debug.reserve(object_debug_size); + + debug.insert( + debug.end(), + {pose.position.x, pose.position.y, tf2::getYaw(pose.orientation), shape.dimensions.x, + shape.dimensions.y, + predicted_object.kinematics.initial_twist_with_covariance.twist.linear.x, + 1.0 * static_cast(collision_detected), object_enter_time, object_exit_time, + ego_start_itr->first, ego_start_itr->second, ego_end_itr->first, ego_end_itr->second, + object_start_pos.x, object_start_pos.y, object_end_pos.x, object_end_pos.y}); + for (unsigned i = 0; i < 20; ++i) { + const auto & pos = object_passage_interval.path + .at(std::min(i, object_passage_interval.path.size() - 1)) + .position; + debug.push_back(pos.x); + debug.push_back(pos.y); + } + return debug; + }; + + if (collision_detected) { + // if judged as UNSAFE, return + safe_interval = std::nullopt; + unsafe_interval = object_passage_interval; + object_debug_info = get_object_info(); + break; + } + if (!safe_interval) { + // ========================================================================================== + // save the safe_decision_knowledge for the most probable path. this value is nullified if + // judged UNSAFE during the iteration + // ========================================================================================== + safe_interval = object_passage_interval; + object_debug_info = get_object_info(); + } + } + object_info->update_safety(unsafe_interval, safe_interval, safe_under_traffic_control); + if (passed_1st_judge_line_first_time) { + object_info->setDecisionAt1stPassJudgeLinePassage(intersection::CollisionKnowledge{ + clock_->now(), // stamp + unsafe_interval + ? intersection::CollisionKnowledge::SafeType::UNSAFE + : (safe_under_traffic_control + ? intersection::CollisionKnowledge::SafeType::SAFE_UNDER_TRAFFIC_CONTROL + : intersection::CollisionKnowledge::SafeType::SAFE), // safe + unsafe_interval ? unsafe_interval : safe_interval, // interval + predicted_object.kinematics.initial_twist_with_covariance.twist.linear + .x // observed_velocity + }); + } + if (passed_2nd_judge_line_first_time) { + object_info->setDecisionAt2ndPassJudgeLinePassage(intersection::CollisionKnowledge{ + clock_->now(), // stamp + unsafe_interval + ? intersection::CollisionKnowledge::SafeType::UNSAFE + : (safe_under_traffic_control + ? intersection::CollisionKnowledge::SafeType::SAFE_UNDER_TRAFFIC_CONTROL + : intersection::CollisionKnowledge::SafeType::SAFE), // safe + unsafe_interval ? unsafe_interval : safe_interval, // interval + predicted_object.kinematics.initial_twist_with_covariance.twist.linear + .x // observed_velocity + }); + } + + // debug + if (object_debug_info) { + const auto & data = object_debug_info.value(); + object_ttc_time_array->layout.dim.at(0).size++; + std::copy(data.begin(), data.end(), std::back_inserter(object_ttc_time_array->data)); + } + } +} + +void IntersectionModule::cutPredictPathWithinDuration( + const builtin_interfaces::msg::Time & object_stamp, const double time_thr, + autoware_auto_perception_msgs::msg::PredictedPath * path) const +{ + const rclcpp::Time current_time = clock_->now(); + const auto original_path = path->path; + path->path.clear(); + + for (size_t k = 0; k < original_path.size(); ++k) { // each path points + const auto & predicted_pose = original_path.at(k); + const auto predicted_time = + rclcpp::Time(object_stamp) + rclcpp::Duration(path->time_step) * static_cast(k); + if ((predicted_time - current_time).seconds() < time_thr) { + path->path.push_back(predicted_pose); + } + } +} + +std::optional +IntersectionModule::isGreenPseudoCollisionStatus( + const size_t closest_idx, const size_t collision_stopline_idx, + const intersection::IntersectionStopLines & intersection_stoplines) const +{ + // ========================================================================================== + // if there are any vehicles on the attention area when ego entered the intersection on green + // light, do pseudo collision detection because collision is likely to happen. + // ========================================================================================== + if (initial_green_light_observed_time_) { + const auto now = clock_->now(); + const bool still_wait = + (rclcpp::Duration((now - initial_green_light_observed_time_.value())).seconds() < + planner_param_.collision_detection.yield_on_green_traffic_light.duration); + if (!still_wait) { + return std::nullopt; + } + const auto & attention_objects = object_info_manager_.attentionObjects(); + const bool exist_close_vehicles = std::any_of( + attention_objects.begin(), attention_objects.end(), [&](const auto & object_info) { + return object_info->before_stopline_by( + planner_param_.collision_detection.yield_on_green_traffic_light.object_dist_to_stopline); + }); + if (exist_close_vehicles) { + const auto occlusion_stopline_idx = intersection_stoplines.occlusion_peeking_stopline.value(); + return intersection::NonOccludedCollisionStop{ + closest_idx, collision_stopline_idx, occlusion_stopline_idx, std::string("")}; + } + } + return std::nullopt; +} + +std::string IntersectionModule::generateDetectionBlameDiagnosis( + const std::vector>> & + too_late_detect_objects, + const std::vector>> & + misjudge_objects) const +{ + std::string diag; + if (!safely_passed_1st_judge_line_time_) { + return diag; + } + const auto [passed_1st_judge_line_time, passed_1st_judge_line_pose] = + safely_passed_1st_judge_line_time_.value(); + const auto passed_1st_judge_line_time_double = + static_cast(passed_1st_judge_line_time.nanoseconds()) / 1e+9; + + const auto now = clock_->now(); + const auto now_double = static_cast(now.nanoseconds()) / 1e+9; + + // CAVEAT: format library causes runtime fault if the # of placeholders is more than the # of + // vargs + for (const auto & [blame_type, object_info] : too_late_detect_objects) { + if ( + blame_type == CollisionStatus::BLAME_AT_FIRST_PASS_JUDGE && object_info->unsafe_interval()) { + const auto & unsafe_interval = object_info->unsafe_interval().value(); + const double time_diff = now_double - passed_1st_judge_line_time_double; + diag += fmt::format( + "object {0} was not detected when ego passed the 1st pass judge line at {1}, but now at " + "{2}, collision is detected after {3}~{4} seconds on the first attention lanelet of type " + "{5}.\n", + object_info->uuid_str, // 0 + passed_1st_judge_line_time_double, // 1 + now_double, // 2 + unsafe_interval.interval_time.first, // 3 + unsafe_interval.interval_time.second, // 4 + magic_enum::enum_name(unsafe_interval.lane_position) // 5 + ); + const auto past_position_opt = object_info->estimated_past_position(time_diff); + if (past_position_opt) { + const auto & past_position = past_position_opt.value(); + diag += fmt::format( + "this object is estimated to have been at x = {0}, y = {1} when ego passed the 1st pass " + "judge line({2} seconds before from now) given the estimated current velocity {3}[m/s]. " + "ego was at x = {4}, y = {5} when it passed the 1st pass judge line so it is the fault " + "of detection side that failed to detect around {6}[m] range at that time.\n", + past_position.x, // 0 + past_position.y, // 1 + time_diff, // 2 + object_info->observed_velocity(), // 3 + passed_1st_judge_line_pose.position.x, // 4 + passed_1st_judge_line_pose.position.y, // 5 + tier4_autoware_utils::calcDistance2d(passed_1st_judge_line_pose, past_position) // 6 + ); + } + } + if ( + safely_passed_2nd_judge_line_time_ && + blame_type == CollisionStatus::BLAME_AT_SECOND_PASS_JUDGE && object_info->unsafe_interval()) { + const auto [passed_2nd_judge_line_time, passed_2nd_judge_line_pose] = + safely_passed_2nd_judge_line_time_.value(); + const auto passed_2nd_judge_line_time_double = + static_cast(passed_2nd_judge_line_time.nanoseconds()) / 1e+9; + + const auto & unsafe_interval = object_info->unsafe_interval().value(); + const double time_diff = now_double - passed_2nd_judge_line_time_double; + diag += fmt::format( + "object {0} was not detected when ego passed the 2nd pass judge line at {1}, but now at " + "{2}, collision is detected after {3}~{4} seconds on the lanelet of type {5}.\n", + object_info->uuid_str, // 0 + passed_2nd_judge_line_time_double, // 1 + now_double, // 2 + unsafe_interval.interval_time.first, // 3 + unsafe_interval.interval_time.second, // 4 + magic_enum::enum_name(unsafe_interval.lane_position) // 5 + ); + const auto past_position_opt = object_info->estimated_past_position(time_diff); + if (past_position_opt) { + const auto & past_position = past_position_opt.value(); + diag += fmt::format( + "this object is estimated to have been at x = {0}, y = {1} when ego passed the 2nd pass " + "judge line({2} seconds before from now) given the estimated current velocity {3}[m/s]. " + "ego was at x = {4}, y = {5} when it passed the 2nd pass judge line so it is the fault " + "of detection side that failed to detect around {6}[m] range at that time.\n", + past_position.x, // 0 + past_position.y, // 1 + time_diff, // 2 + object_info->observed_velocity(), // 3 + passed_2nd_judge_line_pose.position.x, // 4 + passed_2nd_judge_line_pose.position.y, // 5 + tier4_autoware_utils::calcDistance2d(passed_2nd_judge_line_pose, past_position) // 6 + ); + } + } + } + for (const auto & [blame_type, object_info] : misjudge_objects) { + if ( + blame_type == CollisionStatus::BLAME_AT_FIRST_PASS_JUDGE && object_info->unsafe_interval() && + object_info->decision_at_1st_pass_judge_line_passage()) { + const auto & decision_at_1st_pass_judge_line = + object_info->decision_at_1st_pass_judge_line_passage().value(); + const auto decision_at_1st_pass_judge_line_time = + static_cast(decision_at_1st_pass_judge_line.stamp.nanoseconds()) / 1e+9; + const auto & unsafe_interval = object_info->unsafe_interval().value(); + diag += fmt::format( + "object {0} was judged as {1} when ego passed the 1st pass judge line at time {2} " + "previously with the estimated velocity {3}[m/s], but now at {4} collision is detected " + "after {5}~{6} seconds on the first attention lanelet of type {7} with the estimated " + "current velocity {8}[m/s]\n", + object_info->uuid_str, // 0 + magic_enum::enum_name(decision_at_1st_pass_judge_line.safe_type), // 1 + decision_at_1st_pass_judge_line_time, // 2 + decision_at_1st_pass_judge_line.observed_velocity, // 3 + now_double, // 4 + unsafe_interval.interval_time.first, // 5 + unsafe_interval.interval_time.second, // 6 + magic_enum::enum_name(unsafe_interval.lane_position), // 7 + object_info->observed_velocity() // 8 + ); + } + if ( + blame_type == CollisionStatus::BLAME_AT_SECOND_PASS_JUDGE && object_info->unsafe_interval() && + object_info->decision_at_2nd_pass_judge_line_passage()) { + const auto & decision_at_2nd_pass_judge_line = + object_info->decision_at_2nd_pass_judge_line_passage().value(); + const auto decision_at_2nd_pass_judge_line_time = + static_cast(decision_at_2nd_pass_judge_line.stamp.nanoseconds()) / 1e+9; + const auto & unsafe_interval = object_info->unsafe_interval().value(); + diag += fmt::format( + "object {0} was judged as {1} when ego passed the 2nd pass judge line at time {2} " + "previously with the estimated velocity {3}[m/s], but now at {4} collision is detected " + "after {5}~{6} seconds on the lanelet of type {7} with the estimated current velocity " + "{8}[m/s]\n", + object_info->uuid_str, // 0 + magic_enum::enum_name(decision_at_2nd_pass_judge_line.safe_type), // 1 + decision_at_2nd_pass_judge_line_time, // 2 + decision_at_2nd_pass_judge_line.observed_velocity, // 3 + now_double, // 4 + unsafe_interval.interval_time.first, // 5 + unsafe_interval.interval_time.second, // 6 + magic_enum::enum_name(unsafe_interval.lane_position), // 7 + object_info->observed_velocity() // 8 + ); + } + } + return diag; +} + +std::string IntersectionModule::generateEgoRiskEvasiveDiagnosis( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx, + const IntersectionModule::TimeDistanceArray & ego_time_distance_array, + const std::vector>> & + too_late_detect_objects, + [[maybe_unused]] const std::vector>> & + misjudge_objects) const +{ + static constexpr double min_vel = 1e-2; + std::string diag; + const double ego_vel = planner_data_->current_velocity->twist.linear.x; + for (const auto & [blame_type, object_info] : too_late_detect_objects) { + if (!object_info->unsafe_interval()) { + continue; + } + // object side + const auto & unsafe_interval = object_info->unsafe_interval().value(); + const auto [begin, end] = unsafe_interval.interval_position; + const auto &p1 = unsafe_interval.path.at(begin).position, + p2 = unsafe_interval.path.at(end).position; + const auto collision_pos = + tier4_autoware_utils::createPoint((p1.x + p2.x) / 2, (p1.y + p2.y) / 2, (p1.z + p2.z) / 2); + const auto object_dist_to_margin_point = + tier4_autoware_utils::calcDistance2d( + object_info->predicted_object().kinematics.initial_pose_with_covariance.pose.position, + collision_pos) - + planner_param_.collision_detection.avoid_collision_by_acceleration + .object_time_margin_to_collision_point * + object_info->observed_velocity(); + if (object_dist_to_margin_point <= 0.0) { + continue; + } + const auto object_eta_to_margin_point = + object_dist_to_margin_point / std::max(min_vel, object_info->observed_velocity()); + // ego side + const double ego_dist_to_collision_pos = + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_pos); + const auto ego_eta_to_collision_pos_it = std::lower_bound( + ego_time_distance_array.begin(), ego_time_distance_array.end(), ego_dist_to_collision_pos, + [](const auto & a, const double b) { return a.second < b; }); + if (ego_eta_to_collision_pos_it == ego_time_distance_array.end()) { + continue; + } + const double ego_eta_to_collision_pos = ego_eta_to_collision_pos_it->first; + if (ego_eta_to_collision_pos < object_eta_to_margin_point) { + // this case, ego will pass the collision point before this object get close to the collision + // point within margin just by keeping current plan, so no need to accelerate + continue; + } + diag += fmt::format( + "the object is expected to approach the collision point by the TTC margin in {0} seconds, " + "while ego will arrive there in {1} seconds, so ego needs to accelerate from current " + "velocity {2}[m/s] to {3}[m/s]\n", + object_eta_to_margin_point, // 0 + ego_eta_to_collision_pos, // 1 + ego_vel, // 2 + ego_dist_to_collision_pos / object_eta_to_margin_point // 3 + ); + } + return diag; +} + +IntersectionModule::CollisionStatus IntersectionModule::detectCollision( + const bool is_over_1st_pass_judge_line, + const std::optional is_over_2nd_pass_judge_line) const +{ + // ========================================================================================== + // if collision is detected for multiple objects, we prioritize collision on the first + // attention lanelet + // ========================================================================================== + bool collision_at_first_lane = false; + bool collision_at_non_first_lane = false; + + // ========================================================================================== + // find the objects which are judged as UNSAFE after ego passed pass judge lines. + // + // misjudge_objects are those that were once judged as safe when ego passed the pass judge line + // + // too_late_detect_objects are those that (1) were not detected when ego passed the pass judge + // line (2) were judged as dangerous at the same time when ego passed the pass judge line, which + // means they were expected to have been detected when ego passed the pass judge lines or in the + // prior iteration, because ego could have judged them as UNSAFE if their information was + // available at that time. + // + // that case is both "too late to stop" and "too late to go" for the planner. and basically + // detection side is responsible for this fault + // ========================================================================================== + std::vector>> + misjudge_objects; + std::vector>> + too_late_detect_objects; + for (const auto & object_info : object_info_manager_.attentionObjects()) { + if (object_info->is_safe_under_traffic_control()) { + debug_data_.safe_under_traffic_control_targets.objects.push_back( + object_info->predicted_object()); + continue; + } + if (!object_info->unsafe_info()) { + continue; + } + const auto & unsafe_info = object_info->unsafe_info().value(); + // ========================================================================================== + // if ego is over the pass judge lines, then the visualization as "too_late_objects" or + // "misjudge_objects" is more important than that for "unsafe" + // + // NOTE: consider a vehicle which was not detected at 1st_pass_judge_passage, and now collision + // detected on the 1st lane, which is "too_late" for 1st lane passage, but once it decelerated + // or yielded, so it turned safe, and ego passed the 2nd pass judge line, but at the same it + // accelerated again, which is "misjudge" for 2nd lane passage. In this case this vehicle is + // visualized as "misjudge" + // ========================================================================================== + auto * debug_container = &debug_data_.unsafe_targets.objects; + if (unsafe_info.lane_position == intersection::CollisionInterval::LanePosition::FIRST) { + collision_at_first_lane = true; + } else { + collision_at_non_first_lane = true; + } + if ( + is_over_1st_pass_judge_line && + unsafe_info.lane_position == intersection::CollisionInterval::LanePosition::FIRST) { + const auto & decision_at_1st_pass_judge_opt = + object_info->decision_at_1st_pass_judge_line_passage(); + if (!decision_at_1st_pass_judge_opt) { + too_late_detect_objects.emplace_back( + CollisionStatus::BlameType::BLAME_AT_FIRST_PASS_JUDGE, object_info); + debug_container = &debug_data_.too_late_detect_targets.objects; + } else { + const auto & decision_at_1st_pass_judge = decision_at_1st_pass_judge_opt.value(); + if ( + decision_at_1st_pass_judge.safe_type != + intersection::CollisionKnowledge::SafeType::UNSAFE) { + misjudge_objects.emplace_back( + CollisionStatus::BlameType::BLAME_AT_FIRST_PASS_JUDGE, object_info); + debug_container = &debug_data_.misjudge_targets.objects; + } else { + too_late_detect_objects.emplace_back( + CollisionStatus::BlameType::BLAME_AT_FIRST_PASS_JUDGE, object_info); + debug_container = &debug_data_.too_late_detect_targets.objects; + } + } + } + if (is_over_2nd_pass_judge_line && is_over_2nd_pass_judge_line.value()) { + const auto & decision_at_2nd_pass_judge_opt = + object_info->decision_at_2nd_pass_judge_line_passage(); + if (!decision_at_2nd_pass_judge_opt) { + too_late_detect_objects.emplace_back( + CollisionStatus::BlameType::BLAME_AT_SECOND_PASS_JUDGE, object_info); + debug_container = &debug_data_.too_late_detect_targets.objects; + } else { + const auto & decision_at_2nd_pass_judge = decision_at_2nd_pass_judge_opt.value(); + if ( + decision_at_2nd_pass_judge.safe_type != + intersection::CollisionKnowledge::SafeType::UNSAFE) { + misjudge_objects.emplace_back( + CollisionStatus::BlameType::BLAME_AT_SECOND_PASS_JUDGE, object_info); + debug_container = &debug_data_.misjudge_targets.objects; + } else { + too_late_detect_objects.emplace_back( + CollisionStatus::BlameType::BLAME_AT_SECOND_PASS_JUDGE, object_info); + debug_container = &debug_data_.too_late_detect_targets.objects; + } + } + } + debug_container->emplace_back(object_info->predicted_object()); + } + if (collision_at_first_lane) { + return { + true, intersection::CollisionInterval::FIRST, too_late_detect_objects, misjudge_objects}; + } else if (collision_at_non_first_lane) { + return {true, intersection::CollisionInterval::ELSE, too_late_detect_objects, misjudge_objects}; + } + return {false, intersection::CollisionInterval::ELSE, too_late_detect_objects, misjudge_objects}; +} + +std::optional IntersectionModule::checkAngleForTargetLanelets( + + const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, + const bool is_parked_vehicle) const +{ + const double detection_area_angle_thr = planner_param_.common.attention_area_angle_threshold; + const bool consider_wrong_direction_vehicle = + planner_param_.common.attention_area_angle_threshold; + const double dist_margin = planner_param_.common.attention_area_margin; + + for (unsigned i = 0; i < target_lanelets.size(); ++i) { + const auto & ll = target_lanelets.at(i); + if (!lanelet::utils::isInLanelet(pose, ll, dist_margin)) { + continue; + } + const double ll_angle = lanelet::utils::getLaneletAngle(ll, pose.position); + const double pose_angle = tf2::getYaw(pose.orientation); + const double angle_diff = tier4_autoware_utils::normalizeRadian(ll_angle - pose_angle, -M_PI); + if (consider_wrong_direction_vehicle) { + if (std::fabs(angle_diff) > 1.57 || std::fabs(angle_diff) < detection_area_angle_thr) { + return std::make_optional(i); + } + } else { + if (std::fabs(angle_diff) < detection_area_angle_thr) { + return std::make_optional(i); + } + // NOTE: sometimes parked vehicle direction is reversed even if its longitudinal velocity is + // positive + if ( + is_parked_vehicle && (std::fabs(angle_diff) < detection_area_angle_thr || + (std::fabs(angle_diff + M_PI) < detection_area_angle_thr))) { + return std::make_optional(i); + } + } + } + return std::nullopt; +} + +IntersectionModule::TimeDistanceArray IntersectionModule::calcIntersectionPassingTime( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const bool is_prioritized, + const intersection::IntersectionStopLines & intersection_stoplines, + tier4_debug_msgs::msg::Float64MultiArrayStamped * ego_ttc_array) const +{ + const double intersection_velocity = + planner_param_.collision_detection.velocity_profile.default_velocity; + const double minimum_ego_velocity = + planner_param_.collision_detection.velocity_profile.minimum_default_velocity; + const bool use_upstream_velocity = + planner_param_.collision_detection.velocity_profile.use_upstream; + const double minimum_upstream_velocity = + planner_param_.collision_detection.velocity_profile.minimum_upstream_velocity; + const double current_velocity = planner_data_->current_velocity->twist.linear.x; + + // ========================================================================================== + // if ego is waiting for collision detection, the entry time into the intersection + // is a bit delayed for the chattering hold, so we need to "shift" the TimeDistanceArray by + // this delay + // ========================================================================================== + const bool is_go_out = (activated_ && occlusion_activated_); + const double time_delay = (is_go_out || is_prioritized) + ? 0.0 + : (planner_param_.collision_detection.collision_detection_hold_time - + collision_state_machine_.getDuration()); + + // ========================================================================================== + // to account for the stopline generated by upstream behavior_velocity modules (like walkway, + // crosswalk), if use_upstream flag is true, the raw velocity of path points after + // last_intersection_stopline_candidate_idx is used, which maybe almost-zero. at those almost-zero + // velocity path points, ego future profile is almost "fixed" there. + // + // last_intersection_stopline_candidate_idx must be carefully decided especially when ego + // velocity is almost zero, because if last_intersection_stopline_candidate_idx is at the + // closest_idx for example, ego is almost "fixed" at current position for the entire + // spatiotemporal profile, which is judged as SAFE because that profile does not collide + // with the predicted paths of objects. + // + // if second_attention_lane exists, second_attention_stopline_idx is used. if not, + // max(occlusion_stopline_idx, first_attention_stopline_idx) is used because + // occlusion_stopline_idx varies depending on the peeking offset parameter + // ========================================================================================== + const auto second_attention_stopline_idx = intersection_stoplines.second_attention_stopline; + const auto occlusion_stopline_idx = intersection_stoplines.occlusion_peeking_stopline.value(); + const auto first_attention_stopline_idx = intersection_stoplines.first_attention_stopline.value(); + const auto closest_idx = intersection_stoplines.closest_idx; + const auto last_intersection_stopline_candidate_idx = + second_attention_stopline_idx ? second_attention_stopline_idx.value() + : std::max(occlusion_stopline_idx, first_attention_stopline_idx); + + bool assigned_lane_found = false; + // crop intersection part of the path, and set the reference velocity to intersection_velocity + // for ego's ttc + PathWithLaneId reference_path; + std::optional upstream_stopline{std::nullopt}; + for (size_t i = 0; i + 1 < path.points.size(); ++i) { + auto reference_point = path.points.at(i); + // assume backward velocity is current ego velocity + if (i < closest_idx) { + reference_point.point.longitudinal_velocity_mps = current_velocity; + } + if ( + i > last_intersection_stopline_candidate_idx && + std::fabs(reference_point.point.longitudinal_velocity_mps) < + std::numeric_limits::epsilon() && + !upstream_stopline) { + upstream_stopline = i; + } + if (!use_upstream_velocity) { + reference_point.point.longitudinal_velocity_mps = intersection_velocity; + } + reference_path.points.push_back(reference_point); + bool has_objective_lane_id = util::hasLaneIds(path.points.at(i), associative_ids_); + if (assigned_lane_found && !has_objective_lane_id) { + break; + } + assigned_lane_found = has_objective_lane_id; + } + if (!assigned_lane_found) { + return {{0.0, 0.0}}; // has already passed the intersection. + } + + // apply smoother to reference velocity + PathWithLaneId smoothed_reference_path = reference_path; + if (!smoothPath(reference_path, smoothed_reference_path, planner_data_)) { + smoothed_reference_path = reference_path; + } + + // calculate when ego is going to reach each (interpolated) points on the path + TimeDistanceArray time_distance_array{}; + double dist_sum = 0.0; + double passing_time = time_delay; + time_distance_array.emplace_back(passing_time, dist_sum); + + // NOTE: `reference_path` is resampled in `reference_smoothed_path`, so + // `last_intersection_stopline_candidate_idx` makes no sense + const auto smoothed_path_closest_idx = motion_utils::findFirstNearestIndexWithSoftConstraints( + smoothed_reference_path.points, path.points.at(closest_idx).point.pose, + planner_data_->ego_nearest_dist_threshold, planner_data_->ego_nearest_yaw_threshold); + + const std::optional upstream_stopline_idx_opt = [&]() -> std::optional { + if (upstream_stopline) { + const auto upstream_stopline_point = + reference_path.points.at(upstream_stopline.value()).point.pose; + return motion_utils::findFirstNearestIndexWithSoftConstraints( + smoothed_reference_path.points, upstream_stopline_point, + planner_data_->ego_nearest_dist_threshold, planner_data_->ego_nearest_yaw_threshold); + } else { + return std::nullopt; + } + }(); + + for (size_t i = smoothed_path_closest_idx; i + 1 < smoothed_reference_path.points.size(); ++i) { + const auto & p1 = smoothed_reference_path.points.at(i); + const auto & p2 = smoothed_reference_path.points.at(i + 1); + + const double dist = tier4_autoware_utils::calcDistance2d(p1, p2); + dist_sum += dist; + + // use average velocity between p1 and p2 + const double average_velocity = + (p1.point.longitudinal_velocity_mps + p2.point.longitudinal_velocity_mps) / 2.0; + const double passing_velocity = [=]() { + if (use_upstream_velocity) { + if (upstream_stopline_idx_opt && i > upstream_stopline_idx_opt.value()) { + return minimum_upstream_velocity; + } + return std::max(average_velocity, minimum_ego_velocity); + } else { + return std::max(average_velocity, minimum_ego_velocity); + } + }(); + passing_time += (dist / passing_velocity); + + time_distance_array.emplace_back(passing_time, dist_sum); + } + ego_ttc_array->stamp = clock_->now(); + ego_ttc_array->layout.dim.resize(3); + ego_ttc_array->layout.dim.at(0).label = "lane_id_@[0][0], ttc_time, ttc_dist, path_x, path_y"; + constexpr size_t ego_debug_size = 5; + ego_ttc_array->layout.dim.at(0).size = ego_debug_size; + ego_ttc_array->layout.dim.at(1).label = "values"; + ego_ttc_array->layout.dim.at(1).size = time_distance_array.size(); + ego_ttc_array->data.reserve(time_distance_array.size() * ego_debug_size); + for (unsigned i = 0; i < time_distance_array.size(); ++i) { + ego_ttc_array->data.push_back(lane_id_); + } + for (const auto & [t, d] : time_distance_array) { + ego_ttc_array->data.push_back(t); + } + for (const auto & [t, d] : time_distance_array) { + ego_ttc_array->data.push_back(d); + } + for (size_t i = smoothed_path_closest_idx; i < smoothed_reference_path.points.size(); ++i) { + const auto & p = smoothed_reference_path.points.at(i).point.pose.position; + ego_ttc_array->data.push_back(p.x); + } + for (size_t i = smoothed_path_closest_idx; i < smoothed_reference_path.points.size(); ++i) { + const auto & p = smoothed_reference_path.points.at(i).point.pose.position; + ego_ttc_array->data.push_back(p.y); + } + return time_distance_array; +} + +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection_occlusion.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection_occlusion.cpp new file mode 100644 index 0000000000000..b741d43bb025a --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection_occlusion.cpp @@ -0,0 +1,452 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "scene_intersection.hpp" +#include "util.hpp" + +#include +#include // for toPolygon2d + +#include +#include + +#include + +#include + +namespace behavior_velocity_planner +{ +namespace bg = boost::geometry; + +std::tuple< + IntersectionModule::OcclusionType, bool /* module detection with margin */, + bool /* reconciled occlusion disapproval */> +IntersectionModule::getOcclusionStatus( + const TrafficPrioritizedLevel & traffic_prioritized_level, + const intersection::InterpolatedPathInfo & interpolated_path_info) +{ + const auto & intersection_lanelets = intersection_lanelets_.value(); + const auto & occlusion_attention_lanelets = intersection_lanelets.occlusion_attention(); + + // ========================================================================================== + // for the convenience of Psim user, this module ignores occlusion if there has not been any + // information published for the associated traffic light even if occlusion.enable is true, + // and only runs collision checking on that intersection lane. + // + // this is because Psim-users/scenario-files do not set traffic light information perfectly + // most of the times, and they just set bare minimum traffic information only for traffic lights + // they are interested in or want to test. + // + // no_tl_info_ever variable is defined for that purpose. if there has been any + // information published for the associated traffic light in the real world through perception/V2I + // or in the simulation, then it should be kept in last_tl_valid_observation_ and this variable + // becomes false + // ========================================================================================== + const bool no_tl_info_ever = (has_traffic_light_ && !last_tl_valid_observation_.has_value()); + const bool is_amber_or_red_or_no_tl_info_ever = + (traffic_prioritized_level == TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED) || + (traffic_prioritized_level == TrafficPrioritizedLevel::FULLY_PRIORITIZED) || no_tl_info_ever; + // check occlusion on detection lane + auto occlusion_status = + (planner_param_.occlusion.enable && !occlusion_attention_lanelets.empty() && + !is_amber_or_red_or_no_tl_info_ever) + ? detectOcclusion(interpolated_path_info) + : NotOccluded{}; + + // ========================================================================================== + // if the traffic light changed from green to yellow/red, hysteresis time for occlusion is + // unnecessary + // ========================================================================================== + const auto transition_to_prioritized = + (previous_prioritized_level_ == TrafficPrioritizedLevel::NOT_PRIORITIZED && + traffic_prioritized_level != TrafficPrioritizedLevel::NOT_PRIORITIZED); + if (transition_to_prioritized) { + occlusion_stop_state_machine_.setState(StateMachine::State::GO); + } else { + occlusion_stop_state_machine_.setStateWithMarginTime( + std::holds_alternative(occlusion_status) ? StateMachine::State::GO + : StateMachine::STOP, + logger_.get_child("occlusion_stop"), *clock_); + } + + const bool is_occlusion_cleared_with_margin = + (occlusion_stop_state_machine_.getState() == StateMachine::State::GO); // module's detection + // distinguish if ego detected occlusion or RTC detects occlusion + const bool ext_occlusion_requested = + (is_occlusion_cleared_with_margin && !occlusion_activated_); // RTC's detection + if (ext_occlusion_requested) { + occlusion_status = RTCOccluded{}; + } + const bool is_occlusion_state = + (!is_occlusion_cleared_with_margin || ext_occlusion_requested); // including approval + if (is_occlusion_state && std::holds_alternative(occlusion_status)) { + occlusion_status = prev_occlusion_status_; + } else { + prev_occlusion_status_ = occlusion_status; + } + return {occlusion_status, is_occlusion_cleared_with_margin, is_occlusion_state}; +} + +IntersectionModule::OcclusionType IntersectionModule::detectOcclusion( + const intersection::InterpolatedPathInfo & interpolated_path_info) const +{ + const auto & intersection_lanelets = intersection_lanelets_.value(); + const auto & adjacent_lanelets = intersection_lanelets.adjacent(); + const auto & attention_areas = intersection_lanelets.occlusion_attention_area(); + const auto first_attention_area = intersection_lanelets.first_attention_area().value(); + const auto & lane_divisions = occlusion_attention_divisions_.value(); + + const auto & occ_grid = *planner_data_->occupancy_grid; + const auto & current_pose = planner_data_->current_odometry->pose; + const double occlusion_dist_thr = planner_param_.occlusion.occlusion_required_clearance_distance; + + const auto & path_ip = interpolated_path_info.path; + const auto & lane_interval_ip = interpolated_path_info.lane_id_interval.value(); + + const auto first_attention_area_idx = + util::getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_attention_area); + if (!first_attention_area_idx) { + return NotOccluded{}; + } + + const auto first_inside_attention_idx_ip_opt = + util::getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_attention_area); + const std::pair lane_attention_interval_ip = + first_inside_attention_idx_ip_opt + ? std::make_pair(first_inside_attention_idx_ip_opt.value(), std::get<1>(lane_interval_ip)) + : lane_interval_ip; + const auto [lane_start_idx, lane_end_idx] = lane_attention_interval_ip; + + const int width = occ_grid.info.width; + const int height = occ_grid.info.height; + const double resolution = occ_grid.info.resolution; + const auto & origin = occ_grid.info.origin.position; + auto coord2index = [&](const double x, const double y) { + const int idx_x = (x - origin.x) / resolution; + const int idx_y = (y - origin.y) / resolution; + if (idx_x < 0 || idx_x >= width) return std::make_tuple(false, -1, -1); + if (idx_y < 0 || idx_y >= height) return std::make_tuple(false, -1, -1); + return std::make_tuple(true, idx_x, idx_y); + }; + + Polygon2d grid_poly; + grid_poly.outer().emplace_back(origin.x, origin.y); + grid_poly.outer().emplace_back(origin.x + (width - 1) * resolution, origin.y); + grid_poly.outer().emplace_back( + origin.x + (width - 1) * resolution, origin.y + (height - 1) * resolution); + grid_poly.outer().emplace_back(origin.x, origin.y + (height - 1) * resolution); + grid_poly.outer().emplace_back(origin.x, origin.y); + bg::correct(grid_poly); + + auto findCommonCvPolygons = + [&](const auto & area2d, std::vector> & cv_polygons) -> void { + tier4_autoware_utils::Polygon2d area2d_poly; + for (const auto & p : area2d) { + area2d_poly.outer().emplace_back(p.x(), p.y()); + } + area2d_poly.outer().push_back(area2d_poly.outer().front()); + bg::correct(area2d_poly); + std::vector common_areas; + bg::intersection(area2d_poly, grid_poly, common_areas); + if (common_areas.empty()) { + return; + } + for (size_t i = 0; i < common_areas.size(); ++i) { + common_areas[i].outer().push_back(common_areas[i].outer().front()); + bg::correct(common_areas[i]); + } + for (const auto & common_area : common_areas) { + std::vector cv_polygon; + for (const auto & p : common_area.outer()) { + const int idx_x = static_cast((p.x() - origin.x) / resolution); + const int idx_y = static_cast((p.y() - origin.y) / resolution); + cv_polygon.emplace_back(idx_x, height - 1 - idx_y); + } + cv_polygons.push_back(cv_polygon); + } + }; + + // (1) prepare detection area mask + // attention: 255 + // non-attention: 0 + // NOTE: interesting area is set to 255 for later masking + cv::Mat attention_mask(width, height, CV_8UC1, cv::Scalar(0)); + std::vector> attention_area_cv_polygons; + for (const auto & attention_area : attention_areas) { + const auto area2d = lanelet::utils::to2D(attention_area); + findCommonCvPolygons(area2d, attention_area_cv_polygons); + } + for (const auto & poly : attention_area_cv_polygons) { + cv::fillPoly(attention_mask, poly, cv::Scalar(255), cv::LINE_AA); + } + // (1.1) + // reset adjacent_lanelets area to 0 on attention_mask + std::vector> adjacent_lane_cv_polygons; + for (const auto & adjacent_lanelet : adjacent_lanelets) { + const auto area2d = adjacent_lanelet.polygon2d().basicPolygon(); + findCommonCvPolygons(area2d, adjacent_lane_cv_polygons); + } + for (const auto & poly : adjacent_lane_cv_polygons) { + cv::fillPoly(attention_mask, poly, cv::Scalar(0), cv::LINE_AA); + } + + // (2) prepare unknown mask + // In OpenCV the pixel at (X=x, Y=y) (with left-upper origin) is accessed by img[y, x] + // unknown: 255 + // not-unknown: 0 + cv::Mat unknown_mask_raw(width, height, CV_8UC1, cv::Scalar(0)); + cv::Mat unknown_mask(width, height, CV_8UC1, cv::Scalar(0)); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + const int idx = y * width + x; + const unsigned char intensity = occ_grid.data.at(idx); + if ( + planner_param_.occlusion.free_space_max <= intensity && + intensity < planner_param_.occlusion.occupied_min) { + unknown_mask_raw.at(height - 1 - y, x) = 255; + } + } + } + // (2.1) apply morphologyEx + const int morph_size = static_cast(planner_param_.occlusion.denoise_kernel / resolution); + cv::morphologyEx( + unknown_mask_raw, unknown_mask, cv::MORPH_OPEN, + cv::getStructuringElement(cv::MORPH_RECT, cv::Size(morph_size, morph_size))); + + // (3) occlusion mask + static constexpr unsigned char OCCLUDED = 255; + static constexpr unsigned char BLOCKED = 127; + cv::Mat occlusion_mask(width, height, CV_8UC1, cv::Scalar(0)); + cv::bitwise_and(attention_mask, unknown_mask, occlusion_mask); + // re-use attention_mask + attention_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); + // (3.1) draw all cells on attention_mask behind blocking vehicles as not occluded + const auto & blocking_attention_objects = object_info_manager_.parkedObjects(); + for (const auto & blocking_attention_object_info : blocking_attention_objects) { + debug_data_.parked_targets.objects.push_back( + blocking_attention_object_info->predicted_object()); + } + std::vector> blocking_polygons; + for (const auto & blocking_attention_object_info : blocking_attention_objects) { + const Polygon2d obj_poly = + tier4_autoware_utils::toPolygon2d(blocking_attention_object_info->predicted_object()); + findCommonCvPolygons(obj_poly.outer(), blocking_polygons); + } + for (const auto & blocking_polygon : blocking_polygons) { + cv::fillPoly(attention_mask, blocking_polygon, cv::Scalar(BLOCKED), cv::LINE_AA); + } + for (const auto & division : lane_divisions) { + bool blocking_vehicle_found = false; + for (const auto & point_it : division) { + const auto [valid, idx_x, idx_y] = coord2index(point_it.x(), point_it.y()); + if (!valid) continue; + if (blocking_vehicle_found) { + occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; + continue; + } + if (attention_mask.at(height - 1 - idx_y, idx_x) == BLOCKED) { + blocking_vehicle_found = true; + occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; + } + } + } + + // (4) extract occlusion polygons + const auto & possible_object_bbox = planner_param_.occlusion.possible_object_bbox; + const double possible_object_bbox_x = possible_object_bbox.at(0) / resolution; + const double possible_object_bbox_y = possible_object_bbox.at(1) / resolution; + const double possible_object_area = possible_object_bbox_x * possible_object_bbox_y; + std::vector> contours; + cv::findContours(occlusion_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); + std::vector> valid_contours; + for (const auto & contour : contours) { + if (contour.size() <= 2) { + continue; + } + std::vector approx_contour; + cv::approxPolyDP( + contour, approx_contour, + std::round(std::min(possible_object_bbox_x, possible_object_bbox_y) / std::sqrt(2.0)), true); + if (approx_contour.size() <= 2) continue; + // check area + const double poly_area = cv::contourArea(approx_contour); + if (poly_area < possible_object_area) continue; + // check bounding box size + const auto bbox = cv::minAreaRect(approx_contour); + if (const auto size = bbox.size; std::min(size.height, size.width) < + std::min(possible_object_bbox_x, possible_object_bbox_y) || + std::max(size.height, size.width) < + std::max(possible_object_bbox_x, possible_object_bbox_y)) { + continue; + } + valid_contours.push_back(approx_contour); + geometry_msgs::msg::Polygon polygon_msg; + geometry_msgs::msg::Point32 point_msg; + for (const auto & p : approx_contour) { + const double glob_x = (p.x + 0.5) * resolution + origin.x; + const double glob_y = (height - 0.5 - p.y) * resolution + origin.y; + point_msg.x = glob_x; + point_msg.y = glob_y; + point_msg.z = origin.z; + polygon_msg.points.push_back(point_msg); + } + debug_data_.occlusion_polygons.push_back(polygon_msg); + } + // (4.1) re-draw occluded cells using valid_contours + occlusion_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); + for (const auto & valid_contour : valid_contours) { + // NOTE: drawContour does not work well + cv::fillPoly(occlusion_mask, valid_contour, cv::Scalar(OCCLUDED), cv::LINE_AA); + } + + // (5) find distance + // (5.1) discretize path_ip with resolution for computational cost + LineString2d path_linestring; + path_linestring.emplace_back( + path_ip.points.at(lane_start_idx).point.pose.position.x, + path_ip.points.at(lane_start_idx).point.pose.position.y); + { + auto prev_path_linestring_point = path_ip.points.at(lane_start_idx).point.pose.position; + for (auto i = lane_start_idx + 1; i <= lane_end_idx; i++) { + const auto path_linestring_point = path_ip.points.at(i).point.pose.position; + if ( + tier4_autoware_utils::calcDistance2d(prev_path_linestring_point, path_linestring_point) < + 1.0 /* rough tick for computational cost */) { + continue; + } + path_linestring.emplace_back(path_linestring_point.x, path_linestring_point.y); + prev_path_linestring_point = path_linestring_point; + } + } + + auto findNearestPointToProjection = []( + const lanelet::ConstLineString3d & division, + const Point2d & projection, const double dist_thresh) { + double min_dist = std::numeric_limits::infinity(); + auto nearest = division.end(); + for (auto it = division.begin(); it != division.end(); it++) { + const double dist = std::hypot(it->x() - projection.x(), it->y() - projection.y()); + if (dist < min_dist) { + min_dist = dist; + nearest = it; + } + if (dist < dist_thresh) { + break; + } + } + return nearest; + }; + struct NearestOcclusionInterval + { + int64 division_index{0}; + int64 point_index{0}; + double dist{0.0}; + geometry_msgs::msg::Point point; + geometry_msgs::msg::Point projection; + geometry_msgs::msg::Point visible_end; + } nearest_occlusion_point; + double min_dist = std::numeric_limits::infinity(); + for (unsigned division_index = 0; division_index < lane_divisions.size(); ++division_index) { + const auto & division = lane_divisions.at(division_index); + LineString2d division_linestring; + auto division_point_it = division.begin(); + division_linestring.emplace_back(division_point_it->x(), division_point_it->y()); + for (auto point_it = division.begin(); point_it != division.end(); point_it++) { + if ( + std::hypot(point_it->x() - division_point_it->x(), point_it->y() - division_point_it->y()) < + 3.0 /* rough tick for computational cost */) { + continue; + } + division_linestring.emplace_back(point_it->x(), point_it->y()); + division_point_it = point_it; + } + + // find the intersection point of lane_line and path + std::vector intersection_points; + boost::geometry::intersection(division_linestring, path_linestring, intersection_points); + if (intersection_points.empty()) { + continue; + } + const auto & projection_point = intersection_points.at(0); + const auto projection_it = findNearestPointToProjection(division, projection_point, resolution); + if (projection_it == division.end()) { + continue; + } + double acc_dist = 0.0; + bool found_min_dist_for_this_division = false; + bool is_prev_occluded = false; + auto acc_dist_it = projection_it; + for (auto point_it = projection_it; point_it != division.end(); point_it++) { + const double dist = + std::hypot(point_it->x() - acc_dist_it->x(), point_it->y() - acc_dist_it->y()); + acc_dist += dist; + acc_dist_it = point_it; + const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); + if (!valid) continue; + const auto pixel = occlusion_mask.at(height - 1 - idx_y, idx_x); + if (pixel == BLOCKED) { + break; + } + if (pixel == OCCLUDED) { + if (acc_dist < min_dist) { + min_dist = acc_dist; + nearest_occlusion_point = { + division_index, + std::distance(division.begin(), point_it), + acc_dist, + tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z), + tier4_autoware_utils::createPoint(projection_it->x(), projection_it->y(), origin.z), + tier4_autoware_utils::createPoint( + projection_it->x(), projection_it->y(), + origin.z) /* initialize with projection point at first*/}; + found_min_dist_for_this_division = true; + } else if (found_min_dist_for_this_division && is_prev_occluded) { + // although this cell is not "nearest" cell, we have found the "nearest" cell on this + // division previously in this iteration, and the iterated cells are still OCCLUDED since + // then + nearest_occlusion_point.visible_end = + tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z); + } + } + is_prev_occluded = (pixel == OCCLUDED); + } + } + + if (min_dist == std::numeric_limits::infinity() || min_dist > occlusion_dist_thr) { + return NotOccluded{min_dist}; + } + + debug_data_.nearest_occlusion_projection = + std::make_pair(nearest_occlusion_point.point, nearest_occlusion_point.projection); + debug_data_.nearest_occlusion_triangle = std::make_tuple( + current_pose.position, nearest_occlusion_point.point, nearest_occlusion_point.visible_end); + Polygon2d ego_occlusion_triangle; + ego_occlusion_triangle.outer().emplace_back(current_pose.position.x, current_pose.position.y); + ego_occlusion_triangle.outer().emplace_back( + nearest_occlusion_point.point.x, nearest_occlusion_point.point.y); + ego_occlusion_triangle.outer().emplace_back( + nearest_occlusion_point.visible_end.x, nearest_occlusion_point.visible_end.y); + bg::correct(ego_occlusion_triangle); + for (const auto & attention_object_info : object_info_manager_.allObjects()) { + const auto obj_poly = + tier4_autoware_utils::toPolygon2d(attention_object_info->predicted_object()); + if (bg::intersects(obj_poly, ego_occlusion_triangle)) { + debug_data_.static_occlusion = false; + return DynamicallyOccluded{min_dist}; + } + } + debug_data_.static_occlusion = true; + return StaticallyOccluded{min_dist}; +} +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection_prepare_data.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection_prepare_data.cpp new file mode 100644 index 0000000000000..97d05aef26137 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection_prepare_data.cpp @@ -0,0 +1,918 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "scene_intersection.hpp" +#include "util.hpp" + +#include // for to_bg2d +#include // for planning_utils:: +#include +#include // for lanelet::autoware::RoadMarking +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace tier4_autoware_utils +{ + +template <> +inline geometry_msgs::msg::Point getPoint(const lanelet::ConstPoint3d & p) +{ + geometry_msgs::msg::Point point; + point.x = p.x(); + point.y = p.y(); + point.z = p.z(); + return point; +} + +} // namespace tier4_autoware_utils + +namespace +{ +namespace bg = boost::geometry; + +lanelet::ConstLanelets getPrevLanelets( + const lanelet::ConstLanelets & lanelets_on_path, const std::set & associative_ids) +{ + lanelet::ConstLanelets previous_lanelets; + for (const auto & ll : lanelets_on_path) { + if (associative_ids.find(ll.id()) != associative_ids.end()) { + return previous_lanelets; + } + previous_lanelets.push_back(ll); + } + return previous_lanelets; +} + +// end inclusive +lanelet::ConstLanelet generatePathLanelet( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t start_idx, + const size_t end_idx, const double width, const double interval) +{ + lanelet::Points3d lefts; + lanelet::Points3d rights; + size_t prev_idx = start_idx; + for (size_t i = start_idx; i <= end_idx; ++i) { + const auto & p = path.points.at(i).point.pose; + const auto & p_prev = path.points.at(prev_idx).point.pose; + if (i != start_idx && tier4_autoware_utils::calcDistance2d(p_prev, p) < interval) { + continue; + } + prev_idx = i; + const double yaw = tf2::getYaw(p.orientation); + const double x = p.position.x; + const double y = p.position.y; + // NOTE: maybe this is opposite + const double left_x = x + width / 2 * std::sin(yaw); + const double left_y = y - width / 2 * std::cos(yaw); + const double right_x = x - width / 2 * std::sin(yaw); + const double right_y = y + width / 2 * std::cos(yaw); + lefts.emplace_back(lanelet::InvalId, left_x, left_y, p.position.z); + rights.emplace_back(lanelet::InvalId, right_x, right_y, p.position.z); + } + lanelet::LineString3d left = lanelet::LineString3d(lanelet::InvalId, lefts); + lanelet::LineString3d right = lanelet::LineString3d(lanelet::InvalId, rights); + + return lanelet::Lanelet(lanelet::InvalId, left, right); +} + +std::optional> getFirstPointInsidePolygons( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const std::pair lane_interval, + const std::vector & polygons, const bool search_forward = true) +{ + if (search_forward) { + for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { + bool is_in_lanelet = false; + const auto & p = path.points.at(i).point.pose.position; + for (const auto & polygon : polygons) { + const auto polygon_2d = lanelet::utils::to2D(polygon); + is_in_lanelet = bg::within(behavior_velocity_planner::to_bg2d(p), polygon_2d); + if (is_in_lanelet) { + return std::make_optional>( + i, polygon); + } + } + if (is_in_lanelet) { + break; + } + } + } else { + for (size_t i = lane_interval.second; i >= lane_interval.first; --i) { + bool is_in_lanelet = false; + const auto & p = path.points.at(i).point.pose.position; + for (const auto & polygon : polygons) { + const auto polygon_2d = lanelet::utils::to2D(polygon); + is_in_lanelet = bg::within(behavior_velocity_planner::to_bg2d(p), polygon_2d); + if (is_in_lanelet) { + return std::make_optional>( + i, polygon); + } + } + if (is_in_lanelet) { + break; + } + if (i == 0) { + break; + } + } + } + return std::nullopt; +} + +double getHighestCurvature(const lanelet::ConstLineString3d & centerline) +{ + std::vector points; + for (auto point = centerline.begin(); point != centerline.end(); point++) { + points.push_back(*point); + } + + SplineInterpolationPoints2d interpolation(points); + const std::vector curvatures = interpolation.getSplineInterpolatedCurvatures(); + std::vector curvatures_positive; + for (const auto & curvature : curvatures) { + curvatures_positive.push_back(std::fabs(curvature)); + } + return *std::max_element(curvatures_positive.begin(), curvatures_positive.end()); +} + +} // namespace + +namespace behavior_velocity_planner +{ +namespace bg = boost::geometry; + +using intersection::make_err; +using intersection::make_ok; +using intersection::Result; + +Result +IntersectionModule::prepareIntersectionData(PathWithLaneId * path) +{ + const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); + const auto routing_graph_ptr = planner_data_->route_handler_->getRoutingGraphPtr(); + const auto & assigned_lanelet = lanelet_map_ptr->laneletLayer.get(lane_id_); + const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; + const auto footprint = planner_data_->vehicle_info_.createFootprint(0.0, 0.0); + const auto & current_pose = planner_data_->current_odometry->pose; + + // ========================================================================================== + // update traffic light information + // updateTrafficSignalObservation() must be called at first because other traffic signal + // fuctions use last_valid_observation_ + // ========================================================================================== + // save previous information before calling updateTrafficSignalObservation() + previous_prioritized_level_ = getTrafficPrioritizedLevel(); + updateTrafficSignalObservation(); + const auto traffic_prioritized_level = getTrafficPrioritizedLevel(); + const bool is_prioritized = + traffic_prioritized_level == TrafficPrioritizedLevel::FULLY_PRIORITIZED; + + // spline interpolation + const auto interpolated_path_info_opt = util::generateInterpolatedPath( + lane_id_, associative_ids_, *path, planner_param_.common.path_interpolation_ds, logger_); + if (!interpolated_path_info_opt) { + return make_err( + "splineInterpolate failed"); + } + + const auto & interpolated_path_info = interpolated_path_info_opt.value(); + if (!interpolated_path_info.lane_id_interval) { + return make_err( + "Path has no interval on intersection lane " + std::to_string(lane_id_)); + } + + const auto & path_ip = interpolated_path_info.path; + const auto & path_ip_intersection_end = interpolated_path_info.lane_id_interval.value().second; + internal_debug_data_.distance = motion_utils::calcSignedArcLength( + path->points, current_pose.position, + path_ip.points.at(path_ip_intersection_end).point.pose.position); + + if (!intersection_lanelets_) { + intersection_lanelets_ = + generateObjectiveLanelets(lanelet_map_ptr, routing_graph_ptr, assigned_lanelet); + } + auto & intersection_lanelets = intersection_lanelets_.value(); + debug_data_.attention_area = intersection_lanelets.attention_area(); + debug_data_.first_attention_area = intersection_lanelets.first_attention_area(); + debug_data_.second_attention_area = intersection_lanelets.second_attention_area(); + debug_data_.occlusion_attention_area = intersection_lanelets.occlusion_attention_area(); + debug_data_.adjacent_area = intersection_lanelets.adjacent_area(); + + // ========================================================================================== + // at the very first time of registration of this module, the path may not be conflicting with + // the attention area, so update() is called to update the internal data as well as traffic + // light info + // ========================================================================================== + intersection_lanelets.update( + is_prioritized, interpolated_path_info, footprint, baselink2front, routing_graph_ptr); + + const auto & conflicting_lanelets = intersection_lanelets.conflicting(); + const auto & first_conflicting_area_opt = intersection_lanelets.first_conflicting_area(); + const auto & first_conflicting_lane_opt = intersection_lanelets.first_conflicting_lane(); + if (conflicting_lanelets.empty() || !first_conflicting_area_opt || !first_conflicting_lane_opt) { + // this is abnormal + return make_err( + "conflicting area is empty"); + } + const auto & first_conflicting_lane = first_conflicting_lane_opt.value(); + const auto & first_conflicting_area = first_conflicting_area_opt.value(); + const auto & second_attention_area_opt = intersection_lanelets.second_attention_area(); + + // ========================================================================================== + // even if the attention area is null, stuck vehicle stop line needs to be generated from + // conflicting lanes, so dummy_first_attention_lane is used + // ========================================================================================== + const auto & dummy_first_attention_lane = intersection_lanelets.first_attention_lane() + ? intersection_lanelets.first_attention_lane().value() + : first_conflicting_lane; + + const auto intersection_stoplines_opt = generateIntersectionStopLines( + assigned_lanelet, first_conflicting_area, dummy_first_attention_lane, second_attention_area_opt, + interpolated_path_info, path); + if (!intersection_stoplines_opt) { + return make_err( + "failed to generate intersection_stoplines"); + } + const auto & intersection_stoplines = intersection_stoplines_opt.value(); + const auto closest_idx = intersection_stoplines.closest_idx; + + const auto & first_attention_area_opt = intersection_lanelets.first_attention_area(); + const auto & conflicting_area = intersection_lanelets.conflicting_area(); + const auto lanelets_on_path = + planning_utils::getLaneletsOnPath(*path, lanelet_map_ptr, current_pose); + // see the doc for struct PathLanelets + const auto path_lanelets_opt = generatePathLanelets( + lanelets_on_path, interpolated_path_info, first_conflicting_area, conflicting_area, + first_attention_area_opt, intersection_lanelets.attention_area(), closest_idx); + if (!path_lanelets_opt.has_value()) { + return make_err( + "failed to generate PathLanelets"); + } + const auto & path_lanelets = path_lanelets_opt.value(); + + if (!occlusion_attention_divisions_) { + occlusion_attention_divisions_ = generateDetectionLaneDivisions( + intersection_lanelets.occlusion_attention(), routing_graph_ptr, + planner_data_->occupancy_grid->info.resolution); + } + + if (has_traffic_light_) { + const bool is_green_solid_on = isGreenSolidOn(); + if (is_green_solid_on && !initial_green_light_observed_time_) { + const auto assigned_lane_begin_point = assigned_lanelet.centerline().front(); + const bool approached_assigned_lane = + motion_utils::calcSignedArcLength( + path->points, closest_idx, + tier4_autoware_utils::createPoint( + assigned_lane_begin_point.x(), assigned_lane_begin_point.y(), + assigned_lane_begin_point.z())) < + planner_param_.collision_detection.yield_on_green_traffic_light + .distance_to_assigned_lanelet_start; + if (approached_assigned_lane) { + initial_green_light_observed_time_ = clock_->now(); + } + } + } + + return make_ok( + interpolated_path_info, intersection_stoplines, path_lanelets); +} + +std::optional IntersectionModule::getStopLineIndexFromMap( + const intersection::InterpolatedPathInfo & interpolated_path_info, + lanelet::ConstLanelet assigned_lanelet) const +{ + const auto & path = interpolated_path_info.path; + const auto & lane_interval = interpolated_path_info.lane_id_interval.value(); + + const auto road_markings = + assigned_lanelet.regulatoryElementsAs(); + lanelet::ConstLineStrings3d stopline; + for (const auto & road_marking : road_markings) { + const std::string type = + road_marking->roadMarking().attributeOr(lanelet::AttributeName::Type, "none"); + if (type == lanelet::AttributeValueString::StopLine) { + stopline.push_back(road_marking->roadMarking()); + break; // only one stopline exists. + } + } + if (stopline.empty()) { + return std::nullopt; + } + + const auto p_start = stopline.front().front(); + const auto p_end = stopline.front().back(); + const LineString2d extended_stopline = + planning_utils::extendLine(p_start, p_end, planner_data_->stop_line_extend_length); + + for (size_t i = lane_interval.first; i < lane_interval.second; i++) { + const auto & p_front = path.points.at(i).point.pose.position; + const auto & p_back = path.points.at(i + 1).point.pose.position; + + const LineString2d path_segment = {{p_front.x, p_front.y}, {p_back.x, p_back.y}}; + std::vector collision_points; + bg::intersection(extended_stopline, path_segment, collision_points); + + if (collision_points.empty()) { + continue; + } + + return i; + } + + geometry_msgs::msg::Pose stop_point_from_map; + stop_point_from_map.position.x = 0.5 * (p_start.x() + p_end.x()); + stop_point_from_map.position.y = 0.5 * (p_start.y() + p_end.y()); + stop_point_from_map.position.z = 0.5 * (p_start.z() + p_end.z()); + + return motion_utils::findFirstNearestIndexWithSoftConstraints( + path.points, stop_point_from_map, planner_data_->ego_nearest_dist_threshold, + planner_data_->ego_nearest_yaw_threshold); +} + +std::optional +IntersectionModule::generateIntersectionStopLines( + lanelet::ConstLanelet assigned_lanelet, const lanelet::CompoundPolygon3d & first_conflicting_area, + const lanelet::ConstLanelet & first_attention_lane, + const std::optional & second_attention_area_opt, + const intersection::InterpolatedPathInfo & interpolated_path_info, + autoware_auto_planning_msgs::msg::PathWithLaneId * original_path) const +{ + const bool use_stuck_stopline = planner_param_.stuck_vehicle.use_stuck_stopline; + const double stopline_margin = planner_param_.common.default_stopline_margin; + const double max_accel = planner_param_.common.max_accel; + const double max_jerk = planner_param_.common.max_jerk; + const double delay_response_time = planner_param_.common.delay_response_time; + const double peeking_offset = planner_param_.occlusion.peeking_offset; + + const auto first_attention_area = first_attention_lane.polygon3d(); + const auto first_attention_lane_centerline = first_attention_lane.centerline2d(); + const auto & path_ip = interpolated_path_info.path; + const double ds = interpolated_path_info.ds; + const auto & lane_interval_ip = interpolated_path_info.lane_id_interval.value(); + const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; + + const int stopline_margin_idx_dist = std::ceil(stopline_margin / ds); + const int base2front_idx_dist = std::ceil(baselink2front / ds); + + // find the index of the first point whose vehicle footprint on it intersects with attention_area + const auto local_footprint = planner_data_->vehicle_info_.createFootprint(0.0, 0.0); + const std::optional first_footprint_inside_1st_attention_ip_opt = + util::getFirstPointInsidePolygonByFootprint( + first_attention_area, interpolated_path_info, local_footprint, baselink2front); + if (!first_footprint_inside_1st_attention_ip_opt) { + return std::nullopt; + } + const auto first_footprint_inside_1st_attention_ip = + first_footprint_inside_1st_attention_ip_opt.value(); + + std::optional first_footprint_attention_centerline_ip_opt = std::nullopt; + for (auto i = std::get<0>(lane_interval_ip); i < std::get<1>(lane_interval_ip); ++i) { + const auto & base_pose = path_ip.points.at(i).point.pose; + const auto path_footprint = tier4_autoware_utils::transformVector( + local_footprint, tier4_autoware_utils::pose2transform(base_pose)); + if (bg::intersects(path_footprint, first_attention_lane_centerline.basicLineString())) { + // NOTE: maybe consideration of braking dist is necessary + first_footprint_attention_centerline_ip_opt = i; + break; + } + } + if (!first_footprint_attention_centerline_ip_opt) { + return std::nullopt; + } + const size_t first_footprint_attention_centerline_ip = + first_footprint_attention_centerline_ip_opt.value(); + + // (1) default stop line position on interpolated path + bool default_stopline_valid = true; + int stop_idx_ip_int = -1; + if (const auto map_stop_idx_ip = + getStopLineIndexFromMap(interpolated_path_info, assigned_lanelet); + map_stop_idx_ip) { + stop_idx_ip_int = static_cast(map_stop_idx_ip.value()) - base2front_idx_dist; + } + if (stop_idx_ip_int < 0) { + stop_idx_ip_int = first_footprint_inside_1st_attention_ip - stopline_margin_idx_dist; + } + if (stop_idx_ip_int < 0) { + default_stopline_valid = false; + } + const auto default_stopline_ip = stop_idx_ip_int >= 0 ? static_cast(stop_idx_ip_int) : 0; + + // (2) ego front stop line position on interpolated path + const geometry_msgs::msg::Pose & current_pose = planner_data_->current_odometry->pose; + const auto closest_idx_ip = motion_utils::findFirstNearestIndexWithSoftConstraints( + path_ip.points, current_pose, planner_data_->ego_nearest_dist_threshold, + planner_data_->ego_nearest_yaw_threshold); + + // (3) occlusion peeking stop line position on interpolated path + int occlusion_peeking_line_ip_int = static_cast(default_stopline_ip); + bool occlusion_peeking_line_valid = true; + // NOTE: if footprints[0] is already inside the attention area, invalid + { + const auto & base_pose0 = path_ip.points.at(default_stopline_ip).point.pose; + const auto path_footprint0 = tier4_autoware_utils::transformVector( + local_footprint, tier4_autoware_utils::pose2transform(base_pose0)); + if (bg::intersects( + path_footprint0, lanelet::utils::to2D(first_attention_area).basicPolygon())) { + occlusion_peeking_line_valid = false; + } + } + if (occlusion_peeking_line_valid) { + occlusion_peeking_line_ip_int = + first_footprint_inside_1st_attention_ip + std::ceil(peeking_offset / ds); + } + const auto occlusion_peeking_line_ip = static_cast( + std::clamp(occlusion_peeking_line_ip_int, 0, static_cast(path_ip.points.size()) - 1)); + + // (4) first attention stopline position on interpolated path + const auto first_attention_stopline_ip = first_footprint_inside_1st_attention_ip; + const bool first_attention_stopline_valid = true; + + // (5) 1st pass judge line position on interpolated path + const double velocity = planner_data_->current_velocity->twist.linear.x; + const double acceleration = planner_data_->current_acceleration->accel.accel.linear.x; + const double braking_dist = planning_utils::calcJudgeLineDistWithJerkLimit( + velocity, acceleration, max_accel, max_jerk, delay_response_time); + int first_pass_judge_ip_int = + static_cast(first_footprint_inside_1st_attention_ip) - std::ceil(braking_dist / ds); + const auto first_pass_judge_line_ip = static_cast( + std::clamp(first_pass_judge_ip_int, 0, static_cast(path_ip.points.size()) - 1)); + const auto occlusion_wo_tl_pass_judge_line_ip = static_cast(std::max( + 0, static_cast(first_footprint_attention_centerline_ip) - std::ceil(braking_dist / ds))); + + // (6) stuck vehicle stopline position on interpolated path + int stuck_stopline_ip_int = 0; + bool stuck_stopline_valid = true; + if (use_stuck_stopline) { + // ========================================================================================== + // NOTE: when ego vehicle is approaching attention area and already passed + // first_conflicting_area, this could be null. + // ========================================================================================== + const auto stuck_stopline_idx_ip_opt = util::getFirstPointInsidePolygonByFootprint( + first_conflicting_area, interpolated_path_info, local_footprint, baselink2front); + if (!stuck_stopline_idx_ip_opt) { + stuck_stopline_valid = false; + stuck_stopline_ip_int = 0; + } else { + stuck_stopline_ip_int = stuck_stopline_idx_ip_opt.value() - stopline_margin_idx_dist; + } + } else { + stuck_stopline_ip_int = + std::get<0>(lane_interval_ip) - (stopline_margin_idx_dist + base2front_idx_dist); + } + if (stuck_stopline_ip_int < 0) { + stuck_stopline_valid = false; + } + const auto stuck_stopline_ip = static_cast(std::max(0, stuck_stopline_ip_int)); + + // (7) second attention stopline position on interpolated path + int second_attention_stopline_ip_int = -1; + bool second_attention_stopline_valid = false; + if (second_attention_area_opt) { + const auto & second_attention_area = second_attention_area_opt.value(); + std::optional first_footprint_inside_2nd_attention_ip_opt = + util::getFirstPointInsidePolygonByFootprint( + second_attention_area, interpolated_path_info, local_footprint, baselink2front); + if (first_footprint_inside_2nd_attention_ip_opt) { + second_attention_stopline_ip_int = first_footprint_inside_2nd_attention_ip_opt.value(); + second_attention_stopline_valid = true; + } + } + const auto second_attention_stopline_ip = + second_attention_stopline_ip_int >= 0 ? static_cast(second_attention_stopline_ip_int) + : 0; + + // (8) second pass judge line position on interpolated path. It is null if second_attention_lane + // is null + size_t second_pass_judge_line_ip = occlusion_wo_tl_pass_judge_line_ip; + bool second_pass_judge_line_valid = false; + if (second_attention_area_opt) { + second_pass_judge_line_valid = true; + } + + struct IntersectionStopLinesTemp + { + size_t closest_idx{0}; + size_t stuck_stopline{0}; + size_t default_stopline{0}; + size_t first_attention_stopline{0}; + size_t second_attention_stopline{0}; + size_t occlusion_peeking_stopline{0}; + size_t first_pass_judge_line{0}; + size_t second_pass_judge_line{0}; + size_t occlusion_wo_tl_pass_judge_line{0}; + }; + + IntersectionStopLinesTemp intersection_stoplines_temp; + std::list> stoplines = { + {&closest_idx_ip, &intersection_stoplines_temp.closest_idx}, + {&stuck_stopline_ip, &intersection_stoplines_temp.stuck_stopline}, + {&default_stopline_ip, &intersection_stoplines_temp.default_stopline}, + {&first_attention_stopline_ip, &intersection_stoplines_temp.first_attention_stopline}, + {&second_attention_stopline_ip, &intersection_stoplines_temp.second_attention_stopline}, + {&occlusion_peeking_line_ip, &intersection_stoplines_temp.occlusion_peeking_stopline}, + {&first_pass_judge_line_ip, &intersection_stoplines_temp.first_pass_judge_line}, + {&second_pass_judge_line_ip, &intersection_stoplines_temp.second_pass_judge_line}, + {&occlusion_wo_tl_pass_judge_line_ip, + &intersection_stoplines_temp.occlusion_wo_tl_pass_judge_line}}; + stoplines.sort( + [](const auto & it1, const auto & it2) { return *(std::get<0>(it1)) < *(std::get<0>(it2)); }); + for (const auto & [stop_idx_ip, stop_idx] : stoplines) { + const auto & insert_point = path_ip.points.at(*stop_idx_ip).point.pose; + const auto insert_idx = util::insertPointIndex( + insert_point, original_path, planner_data_->ego_nearest_dist_threshold, + planner_data_->ego_nearest_yaw_threshold); + if (!insert_idx) { + return std::nullopt; + } + *stop_idx = insert_idx.value(); + } + if ( + intersection_stoplines_temp.occlusion_peeking_stopline < + intersection_stoplines_temp.default_stopline) { + intersection_stoplines_temp.occlusion_peeking_stopline = + intersection_stoplines_temp.default_stopline; + } + + intersection::IntersectionStopLines intersection_stoplines; + intersection_stoplines.closest_idx = intersection_stoplines_temp.closest_idx; + if (stuck_stopline_valid) { + intersection_stoplines.stuck_stopline = intersection_stoplines_temp.stuck_stopline; + } + if (default_stopline_valid) { + intersection_stoplines.default_stopline = intersection_stoplines_temp.default_stopline; + } + if (first_attention_stopline_valid) { + intersection_stoplines.first_attention_stopline = + intersection_stoplines_temp.first_attention_stopline; + } + if (second_attention_stopline_valid) { + intersection_stoplines.second_attention_stopline = + intersection_stoplines_temp.second_attention_stopline; + } + if (occlusion_peeking_line_valid) { + intersection_stoplines.occlusion_peeking_stopline = + intersection_stoplines_temp.occlusion_peeking_stopline; + } + if (second_pass_judge_line_valid) { + intersection_stoplines.second_pass_judge_line = + intersection_stoplines_temp.second_pass_judge_line; + } + intersection_stoplines.first_pass_judge_line = intersection_stoplines_temp.first_pass_judge_line; + intersection_stoplines.occlusion_wo_tl_pass_judge_line = + intersection_stoplines_temp.occlusion_wo_tl_pass_judge_line; + return intersection_stoplines; +} + +intersection::IntersectionLanelets IntersectionModule::generateObjectiveLanelets( + lanelet::LaneletMapConstPtr lanelet_map_ptr, lanelet::routing::RoutingGraphPtr routing_graph_ptr, + const lanelet::ConstLanelet assigned_lanelet) const +{ + const double detection_area_length = planner_param_.common.attention_area_length; + const double occlusion_detection_area_length = + planner_param_.occlusion.occlusion_attention_area_length; + const bool consider_wrong_direction_vehicle = + planner_param_.collision_detection.consider_wrong_direction_vehicle; + + // retrieve a stopline associated with a traffic light + bool has_traffic_light = false; + if (const auto tl_reg_elems = assigned_lanelet.regulatoryElementsAs(); + tl_reg_elems.size() != 0) { + const auto tl_reg_elem = tl_reg_elems.front(); + const auto stopline_opt = tl_reg_elem->stopLine(); + if (!!stopline_opt) has_traffic_light = true; + } + + // for low priority lane + // if ego_lane has right of way (i.e. is high priority), + // ignore yieldLanelets (i.e. low priority lanes) + lanelet::ConstLanelets yield_lanelets{}; + const auto right_of_ways = assigned_lanelet.regulatoryElementsAs(); + for (const auto & right_of_way : right_of_ways) { + if (lanelet::utils::contains(right_of_way->rightOfWayLanelets(), assigned_lanelet)) { + for (const auto & yield_lanelet : right_of_way->yieldLanelets()) { + yield_lanelets.push_back(yield_lanelet); + for (const auto & previous_lanelet : routing_graph_ptr->previous(yield_lanelet)) { + yield_lanelets.push_back(previous_lanelet); + } + } + } + } + + // get all following lanes of previous lane + lanelet::ConstLanelets ego_lanelets{}; + for (const auto & previous_lanelet : routing_graph_ptr->previous(assigned_lanelet)) { + ego_lanelets.push_back(previous_lanelet); + for (const auto & following_lanelet : routing_graph_ptr->following(previous_lanelet)) { + if (lanelet::utils::contains(ego_lanelets, following_lanelet)) { + continue; + } + ego_lanelets.push_back(following_lanelet); + } + } + + // get conflicting lanes on assigned lanelet + const auto & conflicting_lanelets = + lanelet::utils::getConflictingLanelets(routing_graph_ptr, assigned_lanelet); + std::vector adjacent_followings; + + for (const auto & conflicting_lanelet : conflicting_lanelets) { + for (const auto & following_lanelet : routing_graph_ptr->following(conflicting_lanelet)) { + adjacent_followings.push_back(following_lanelet); + } + for (const auto & following_lanelet : routing_graph_ptr->previous(conflicting_lanelet)) { + adjacent_followings.push_back(following_lanelet); + } + } + + // final objective lanelets + lanelet::ConstLanelets detection_lanelets; + lanelet::ConstLanelets conflicting_ex_ego_lanelets; + // conflicting lanes is necessary to get stopline for stuck vehicle + for (auto && conflicting_lanelet : conflicting_lanelets) { + if (!lanelet::utils::contains(ego_lanelets, conflicting_lanelet)) + conflicting_ex_ego_lanelets.push_back(conflicting_lanelet); + } + + // exclude yield lanelets and ego lanelets from detection_lanelets + if (turn_direction_ == std::string("straight") && has_traffic_light) { + // if assigned lanelet is "straight" with traffic light, detection area is not necessary + } else { + if (consider_wrong_direction_vehicle) { + for (const auto & conflicting_lanelet : conflicting_lanelets) { + if (lanelet::utils::contains(yield_lanelets, conflicting_lanelet)) { + continue; + } + detection_lanelets.push_back(conflicting_lanelet); + } + for (const auto & adjacent_following : adjacent_followings) { + detection_lanelets.push_back(adjacent_following); + } + } else { + // otherwise we need to know the priority from RightOfWay + for (const auto & conflicting_lanelet : conflicting_lanelets) { + if ( + lanelet::utils::contains(yield_lanelets, conflicting_lanelet) || + lanelet::utils::contains(ego_lanelets, conflicting_lanelet)) { + continue; + } + detection_lanelets.push_back(conflicting_lanelet); + } + } + } + + // get possible lanelet path that reaches conflicting_lane longer than given length + lanelet::ConstLanelets detection_and_preceding_lanelets; + { + const double length = detection_area_length; + std::set detection_ids; + for (const auto & ll : detection_lanelets) { + // Preceding lanes does not include detection_lane so add them at the end + const auto & inserted = detection_ids.insert(ll.id()); + if (inserted.second) detection_and_preceding_lanelets.push_back(ll); + // get preceding lanelets without ego_lanelets + // to prevent the detection area from including the ego lanes and its' preceding lanes. + const auto lanelet_sequences = lanelet::utils::query::getPrecedingLaneletSequences( + routing_graph_ptr, ll, length, ego_lanelets); + for (const auto & ls : lanelet_sequences) { + for (const auto & l : ls) { + const auto & inserted = detection_ids.insert(l.id()); + if (inserted.second) detection_and_preceding_lanelets.push_back(l); + } + } + } + } + + lanelet::ConstLanelets occlusion_detection_and_preceding_lanelets; + { + const double length = occlusion_detection_area_length; + std::set detection_ids; + for (const auto & ll : detection_lanelets) { + // Preceding lanes does not include detection_lane so add them at the end + const auto & inserted = detection_ids.insert(ll.id()); + if (inserted.second) occlusion_detection_and_preceding_lanelets.push_back(ll); + // get preceding lanelets without ego_lanelets + // to prevent the detection area from including the ego lanes and its' preceding lanes. + const auto lanelet_sequences = lanelet::utils::query::getPrecedingLaneletSequences( + routing_graph_ptr, ll, length, ego_lanelets); + for (const auto & ls : lanelet_sequences) { + for (const auto & l : ls) { + const auto & inserted = detection_ids.insert(l.id()); + if (inserted.second) occlusion_detection_and_preceding_lanelets.push_back(l); + } + } + } + } + lanelet::ConstLanelets occlusion_detection_and_preceding_lanelets_wo_turn_direction; + for (const auto & ll : occlusion_detection_and_preceding_lanelets) { + const std::string turn_direction = ll.attributeOr("turn_direction", "else"); + if (turn_direction == "left" || turn_direction == "right") { + continue; + } + occlusion_detection_and_preceding_lanelets_wo_turn_direction.push_back(ll); + } + + auto [attention_lanelets, original_attention_lanelet_sequences] = + util::mergeLaneletsByTopologicalSort(detection_and_preceding_lanelets, routing_graph_ptr); + + intersection::IntersectionLanelets result; + result.attention_ = std::move(attention_lanelets); + for (const auto & original_attention_lanelet_seq : original_attention_lanelet_sequences) { + // NOTE: in mergeLaneletsByTopologicalSort(), sub_ids are empty checked, so it is ensured that + // back() exists. + std::optional stopline{std::nullopt}; + for (auto it = original_attention_lanelet_seq.rbegin(); + it != original_attention_lanelet_seq.rend(); ++it) { + const auto traffic_lights = it->regulatoryElementsAs(); + for (const auto & traffic_light : traffic_lights) { + const auto stopline_opt = traffic_light->stopLine(); + if (!stopline_opt) continue; + stopline = stopline_opt.get(); + break; + } + if (stopline) break; + } + result.attention_stoplines_.push_back(stopline); + } + result.attention_non_preceding_ = std::move(detection_lanelets); + for (unsigned i = 0; i < result.attention_non_preceding_.size(); ++i) { + std::optional stopline = std::nullopt; + const auto & ll = result.attention_non_preceding_.at(i); + const auto traffic_lights = ll.regulatoryElementsAs(); + for (const auto & traffic_light : traffic_lights) { + const auto stopline_opt = traffic_light->stopLine(); + if (!stopline_opt) continue; + stopline = stopline_opt.get(); + } + result.attention_non_preceding_stoplines_.push_back(stopline); + } + result.conflicting_ = std::move(conflicting_ex_ego_lanelets); + result.adjacent_ = planning_utils::getConstLaneletsFromIds(lanelet_map_ptr, associative_ids_); + // NOTE: occlusion_attention is not inverted here + // TODO(Mamoru Sobue): apply mergeLaneletsByTopologicalSort for occlusion lanelets as well and + // then trim part of them based on curvature threshold + result.occlusion_attention_ = + std::move(occlusion_detection_and_preceding_lanelets_wo_turn_direction); + + // NOTE: to properly update(), each element in conflicting_/conflicting_area_, + // attention_non_preceding_/attention_non_preceding_area_ need to be matched + result.attention_area_ = util::getPolygon3dFromLanelets(result.attention_); + result.attention_non_preceding_area_ = + util::getPolygon3dFromLanelets(result.attention_non_preceding_); + result.conflicting_area_ = util::getPolygon3dFromLanelets(result.conflicting_); + result.adjacent_area_ = util::getPolygon3dFromLanelets(result.adjacent_); + result.occlusion_attention_area_ = util::getPolygon3dFromLanelets(result.occlusion_attention_); + return result; +} + +std::optional IntersectionModule::generatePathLanelets( + const lanelet::ConstLanelets & lanelets_on_path, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const lanelet::CompoundPolygon3d & first_conflicting_area, + const std::vector & conflicting_areas, + const std::optional & first_attention_area, + const std::vector & attention_areas, const size_t closest_idx) const +{ + const double width = planner_data_->vehicle_info_.vehicle_width_m; + static constexpr double path_lanelet_interval = 1.5; + + const auto & assigned_lane_interval_opt = interpolated_path_info.lane_id_interval; + if (!assigned_lane_interval_opt) { + return std::nullopt; + } + const auto assigned_lane_interval = assigned_lane_interval_opt.value(); + const auto & path = interpolated_path_info.path; + + intersection::PathLanelets path_lanelets; + // prev + path_lanelets.prev = ::getPrevLanelets(lanelets_on_path, associative_ids_); + path_lanelets.all = path_lanelets.prev; + + // entry2ego if exist + const auto [assigned_lane_start, assigned_lane_end] = assigned_lane_interval; + if (closest_idx > assigned_lane_start) { + path_lanelets.all.push_back( + ::generatePathLanelet(path, assigned_lane_start, closest_idx, width, path_lanelet_interval)); + } + + // ego_or_entry2exit + const auto ego_or_entry_start = std::max(closest_idx, assigned_lane_start); + path_lanelets.ego_or_entry2exit = + generatePathLanelet(path, ego_or_entry_start, assigned_lane_end, width, path_lanelet_interval); + path_lanelets.all.push_back(path_lanelets.ego_or_entry2exit); + + // next + if (assigned_lane_end < path.points.size() - 1) { + const int next_id = path.points.at(assigned_lane_end).lane_ids.at(0); + const auto next_lane_interval_opt = util::findLaneIdsInterval(path, {next_id}); + if (next_lane_interval_opt) { + const auto [next_start, next_end] = next_lane_interval_opt.value(); + path_lanelets.next = + generatePathLanelet(path, next_start, next_end, width, path_lanelet_interval); + path_lanelets.all.push_back(path_lanelets.next.value()); + } + } + + const auto first_inside_conflicting_idx_opt = + first_attention_area.has_value() + ? util::getFirstPointInsidePolygon(path, assigned_lane_interval, first_attention_area.value()) + : util::getFirstPointInsidePolygon(path, assigned_lane_interval, first_conflicting_area); + const auto last_inside_conflicting_idx_opt = + first_attention_area.has_value() + ? ::getFirstPointInsidePolygons(path, assigned_lane_interval, attention_areas, false) + : ::getFirstPointInsidePolygons(path, assigned_lane_interval, conflicting_areas, false); + if (first_inside_conflicting_idx_opt && last_inside_conflicting_idx_opt) { + const auto first_inside_conflicting_idx = first_inside_conflicting_idx_opt.value(); + const auto last_inside_conflicting_idx = last_inside_conflicting_idx_opt.value().first; + lanelet::ConstLanelet conflicting_interval = generatePathLanelet( + path, first_inside_conflicting_idx, last_inside_conflicting_idx, width, + path_lanelet_interval); + path_lanelets.conflicting_interval_and_remaining.push_back(std::move(conflicting_interval)); + if (last_inside_conflicting_idx < assigned_lane_end) { + lanelet::ConstLanelet remaining_interval = generatePathLanelet( + path, last_inside_conflicting_idx, assigned_lane_end, width, path_lanelet_interval); + path_lanelets.conflicting_interval_and_remaining.push_back(std::move(remaining_interval)); + } + } + return path_lanelets; +} + +std::vector IntersectionModule::generateDetectionLaneDivisions( + lanelet::ConstLanelets detection_lanelets_all, + const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution) const +{ + const double curvature_threshold = + planner_param_.occlusion.attention_lane_crop_curvature_threshold; + const double curvature_calculation_ds = + planner_param_.occlusion.attention_lane_curvature_calculation_ds; + + using lanelet::utils::getCenterlineWithOffset; + + // (0) remove left/right lanelet + lanelet::ConstLanelets detection_lanelets; + for (const auto & detection_lanelet : detection_lanelets_all) { + // TODO(Mamoru Sobue): instead of ignoring, only trim straight part of lanelet + const auto fine_centerline = + lanelet::utils::generateFineCenterline(detection_lanelet, curvature_calculation_ds); + const double highest_curvature = ::getHighestCurvature(fine_centerline); + if (highest_curvature > curvature_threshold) { + continue; + } + detection_lanelets.push_back(detection_lanelet); + } + + // (1) tsort detection_lanelets + const auto [merged_detection_lanelets, originals] = + util::mergeLaneletsByTopologicalSort(detection_lanelets, routing_graph_ptr); + + // (2) merge each branch to one lanelet + // NOTE: somehow bg::area() for merged lanelet does not work, so calculate it here + std::vector> merged_lanelet_with_area; + for (unsigned i = 0; i < merged_detection_lanelets.size(); ++i) { + const auto & merged_detection_lanelet = merged_detection_lanelets.at(i); + const auto & original = originals.at(i); + double area = 0; + for (const auto & partition : original) { + area += bg::area(partition.polygon2d().basicPolygon()); + } + merged_lanelet_with_area.emplace_back(merged_detection_lanelet, area); + } + + // (3) discretize each merged lanelet + std::vector detection_divisions; + for (const auto & [merged_lanelet, area] : merged_lanelet_with_area) { + const double length = bg::length(merged_lanelet.centerline()); + const double width = area / length; + for (int i = 0; i < static_cast(width / resolution); ++i) { + const double offset = resolution * i - width / 2; + detection_divisions.push_back( + getCenterlineWithOffset(merged_lanelet, offset, resolution).invert()); + } + detection_divisions.push_back( + getCenterlineWithOffset(merged_lanelet, width / 2, resolution).invert()); + } + return detection_divisions; +} + +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection_stuck.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection_stuck.cpp new file mode 100644 index 0000000000000..b26f960ec28f9 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection_stuck.cpp @@ -0,0 +1,430 @@ +// Copyright 2024 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "scene_intersection.hpp" +#include "util.hpp" + +#include // for toGeomPoly +#include +#include + +#include +#include +#include + +#include +#include + +namespace +{ +lanelet::LineString3d getLineStringFromArcLength( + const lanelet::ConstLineString3d & linestring, const double s1, const double s2) +{ + lanelet::Points3d points; + double accumulated_length = 0; + size_t start_index = linestring.size(); + if (start_index == 0) { + return lanelet::LineString3d{lanelet::InvalId, points}; + } + for (size_t i = 0; i < linestring.size() - 1; i++) { + const auto & p1 = linestring[i]; + const auto & p2 = linestring[i + 1]; + const double length = boost::geometry::distance(p1.basicPoint(), p2.basicPoint()); + if (accumulated_length + length > s1) { + start_index = i; + break; + } + accumulated_length += length; + } + if (start_index < linestring.size() - 1) { + const auto & p1 = linestring[start_index]; + const auto & p2 = linestring[start_index + 1]; + const double residue = s1 - accumulated_length; + const auto direction_vector = (p2.basicPoint() - p1.basicPoint()).normalized(); + const auto start_basic_point = p1.basicPoint() + residue * direction_vector; + const auto start_point = lanelet::Point3d(lanelet::InvalId, start_basic_point); + points.push_back(start_point); + } + + accumulated_length = 0; + size_t end_index = linestring.size(); + for (size_t i = 0; i < linestring.size() - 1; i++) { + const auto & p1 = linestring[i]; + const auto & p2 = linestring[i + 1]; + const double length = boost::geometry::distance(p1.basicPoint(), p2.basicPoint()); + if (accumulated_length + length > s2) { + end_index = i; + break; + } + accumulated_length += length; + } + + for (size_t i = start_index + 1; i < end_index; i++) { + const auto p = lanelet::Point3d(linestring[i]); + points.push_back(p); + } + if (end_index < linestring.size() - 1) { + const auto & p1 = linestring[end_index]; + const auto & p2 = linestring[end_index + 1]; + const double residue = s2 - accumulated_length; + const auto direction_vector = (p2.basicPoint() - p1.basicPoint()).normalized(); + const auto end_basic_point = p1.basicPoint() + residue * direction_vector; + const auto end_point = lanelet::Point3d(lanelet::InvalId, end_basic_point); + points.push_back(end_point); + } + return lanelet::LineString3d{lanelet::InvalId, points}; +} + +lanelet::ConstLanelet createLaneletFromArcLength( + const lanelet::ConstLanelet & lanelet, const double s1, const double s2) +{ + const double total_length = boost::geometry::length(lanelet.centerline2d().basicLineString()); + // make sure that s1, and s2 are between [0, lane_length] + const auto s1_saturated = std::max(0.0, std::min(s1, total_length)); + const auto s2_saturated = std::max(0.0, std::min(s2, total_length)); + + const auto ratio_s1 = s1_saturated / total_length; + const auto ratio_s2 = s2_saturated / total_length; + + const auto s1_left = + static_cast(ratio_s1 * boost::geometry::length(lanelet.leftBound().basicLineString())); + const auto s2_left = + static_cast(ratio_s2 * boost::geometry::length(lanelet.leftBound().basicLineString())); + const auto s1_right = + static_cast(ratio_s1 * boost::geometry::length(lanelet.rightBound().basicLineString())); + const auto s2_right = + static_cast(ratio_s2 * boost::geometry::length(lanelet.rightBound().basicLineString())); + + const auto left_bound = getLineStringFromArcLength(lanelet.leftBound(), s1_left, s2_left); + const auto right_bound = getLineStringFromArcLength(lanelet.rightBound(), s1_right, s2_right); + + return lanelet::Lanelet(lanelet::InvalId, left_bound, right_bound); +} + +} // namespace + +namespace behavior_velocity_planner +{ +namespace bg = boost::geometry; + +std::optional IntersectionModule::isStuckStatus( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const intersection::IntersectionStopLines & intersection_stoplines, + const intersection::PathLanelets & path_lanelets) const +{ + const auto closest_idx = intersection_stoplines.closest_idx; + auto fromEgoDist = [&](const size_t index) { + return motion_utils::calcSignedArcLength(path.points, closest_idx, index); + }; + + const auto & intersection_lanelets = intersection_lanelets_.value(); // this is OK + const bool stuck_detected = checkStuckVehicleInIntersection(path_lanelets); + const auto first_conflicting_lane = + intersection_lanelets.first_conflicting_lane().value(); // this is OK + const bool is_first_conflicting_lane_private = + (std::string(first_conflicting_lane.attributeOr("location", "else")).compare("private") == 0); + const auto stuck_stopline_idx_opt = intersection_stoplines.stuck_stopline; + const auto default_stopline_idx_opt = intersection_stoplines.default_stopline; + const auto first_attention_stopline_idx_opt = intersection_stoplines.first_attention_stopline; + const auto occlusion_peeking_stopline_idx_opt = intersection_stoplines.occlusion_peeking_stopline; + if (stuck_detected) { + if ( + is_first_conflicting_lane_private && + planner_param_.stuck_vehicle.disable_against_private_lane) { + // do nothing + } else { + std::optional stopline_idx = std::nullopt; + if (stuck_stopline_idx_opt) { + const bool is_over_stuck_stopline = fromEgoDist(stuck_stopline_idx_opt.value()) < + -planner_param_.common.stopline_overshoot_margin; + if (!is_over_stuck_stopline) { + stopline_idx = stuck_stopline_idx_opt.value(); + } + } + if (!stopline_idx) { + if (default_stopline_idx_opt && fromEgoDist(default_stopline_idx_opt.value()) >= 0.0) { + stopline_idx = default_stopline_idx_opt.value(); + } else if ( + first_attention_stopline_idx_opt && + fromEgoDist(first_attention_stopline_idx_opt.value()) >= 0.0) { + stopline_idx = closest_idx; + } + } + if (stopline_idx) { + return intersection::StuckStop{ + closest_idx, stopline_idx.value(), occlusion_peeking_stopline_idx_opt}; + } + } + } + return std::nullopt; +} + +bool IntersectionModule::isTargetStuckVehicleType( + const autoware_auto_perception_msgs::msg::PredictedObject & object) const +{ + const auto label = object.classification.at(0).label; + const auto & p = planner_param_.stuck_vehicle.target_type; + + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::CAR && p.car) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::BUS && p.bus) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::TRUCK && p.truck) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::TRAILER && p.trailer) { + return true; + } + if ( + label == autoware_auto_perception_msgs::msg::ObjectClassification::MOTORCYCLE && p.motorcycle) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::BICYCLE && p.bicycle) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::UNKNOWN && p.unknown) { + return true; + } + return false; +} + +bool IntersectionModule::isTargetYieldStuckVehicleType( + const autoware_auto_perception_msgs::msg::PredictedObject & object) const +{ + const auto label = object.classification.at(0).label; + const auto & p = planner_param_.yield_stuck.target_type; + + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::CAR && p.car) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::BUS && p.bus) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::TRUCK && p.truck) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::TRAILER && p.trailer) { + return true; + } + if ( + label == autoware_auto_perception_msgs::msg::ObjectClassification::MOTORCYCLE && p.motorcycle) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::BICYCLE && p.bicycle) { + return true; + } + if (label == autoware_auto_perception_msgs::msg::ObjectClassification::UNKNOWN && p.unknown) { + return true; + } + return false; +} + +bool IntersectionModule::checkStuckVehicleInIntersection( + const intersection::PathLanelets & path_lanelets) const +{ + using lanelet::utils::getArcCoordinates; + using lanelet::utils::getLaneletLength3d; + using lanelet::utils::getPolygonFromArcLength; + using lanelet::utils::to2D; + + const bool stuck_detection_direction = [&]() { + return (turn_direction_ == "left" && planner_param_.stuck_vehicle.turn_direction.left) || + (turn_direction_ == "right" && planner_param_.stuck_vehicle.turn_direction.right) || + (turn_direction_ == "straight" && planner_param_.stuck_vehicle.turn_direction.straight); + }(); + if (!stuck_detection_direction) { + return false; + } + + const auto & objects_ptr = planner_data_->predicted_objects; + + // considering lane change in the intersection, these lanelets are generated from the path + const double stuck_vehicle_detect_dist = planner_param_.stuck_vehicle.stuck_vehicle_detect_dist; + Polygon2d stuck_vehicle_detect_area{}; + if (path_lanelets.conflicting_interval_and_remaining.size() == 0) { + return false; + } + + double target_polygon_length = + getLaneletLength3d(path_lanelets.conflicting_interval_and_remaining); + lanelet::ConstLanelets targets = path_lanelets.conflicting_interval_and_remaining; + if (path_lanelets.next) { + targets.push_back(path_lanelets.next.value()); + const double next_arc_length = + std::min(stuck_vehicle_detect_dist, getLaneletLength3d(path_lanelets.next.value())); + target_polygon_length += next_arc_length; + } + const auto target_polygon = + to2D(getPolygonFromArcLength(targets, 0, target_polygon_length)).basicPolygon(); + + if (target_polygon.empty()) { + return false; + } + + for (const auto & p : target_polygon) { + stuck_vehicle_detect_area.outer().emplace_back(p.x(), p.y()); + } + + stuck_vehicle_detect_area.outer().emplace_back(stuck_vehicle_detect_area.outer().front()); + bg::correct(stuck_vehicle_detect_area); + + debug_data_.stuck_vehicle_detect_area = toGeomPoly(stuck_vehicle_detect_area); + + for (const auto & object : objects_ptr->objects) { + if (!isTargetStuckVehicleType(object)) { + continue; // not target vehicle type + } + const auto obj_v_norm = std::hypot( + object.kinematics.initial_twist_with_covariance.twist.linear.x, + object.kinematics.initial_twist_with_covariance.twist.linear.y); + if (obj_v_norm > planner_param_.stuck_vehicle.stuck_vehicle_velocity_threshold) { + continue; // not stop vehicle + } + + // check if the footprint is in the stuck detect area + const auto obj_footprint = tier4_autoware_utils::toPolygon2d(object); + // NOTE: in order not to stop too much + const bool is_in_stuck_area = bg::within( + to_bg2d(object.kinematics.initial_pose_with_covariance.pose.position), + stuck_vehicle_detect_area); + if (is_in_stuck_area) { + debug_data_.stuck_targets.objects.push_back(object); + return true; + } + } + return false; +} + +std::optional IntersectionModule::isYieldStuckStatus( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const intersection::IntersectionStopLines & intersection_stoplines) const +{ + const auto closest_idx = intersection_stoplines.closest_idx; + auto fromEgoDist = [&](const size_t index) { + return motion_utils::calcSignedArcLength(path.points, closest_idx, index); + }; + const auto & intersection_lanelets = intersection_lanelets_.value(); + const auto default_stopline_idx = intersection_stoplines.default_stopline.value(); + const auto first_attention_stopline_idx = intersection_stoplines.first_attention_stopline.value(); + const auto stuck_stopline_idx_opt = intersection_stoplines.stuck_stopline; + + const bool yield_stuck_detected = checkYieldStuckVehicleInIntersection( + interpolated_path_info, intersection_lanelets.attention_non_preceding()); + if (yield_stuck_detected) { + std::optional stopline_idx = std::nullopt; + const bool is_before_default_stopline = fromEgoDist(default_stopline_idx) >= 0.0; + const bool is_before_first_attention_stopline = + fromEgoDist(first_attention_stopline_idx) >= 0.0; + if (stuck_stopline_idx_opt) { + const bool is_over_stuck_stopline = fromEgoDist(stuck_stopline_idx_opt.value()) < + -planner_param_.common.stopline_overshoot_margin; + if (!is_over_stuck_stopline) { + stopline_idx = stuck_stopline_idx_opt.value(); + } + } + if (!stopline_idx) { + if (is_before_default_stopline) { + stopline_idx = default_stopline_idx; + } else if (is_before_first_attention_stopline) { + stopline_idx = closest_idx; + } + } + if (stopline_idx) { + return intersection::YieldStuckStop{closest_idx, stopline_idx.value(), std::string("")}; + } + } + return std::nullopt; +} + +bool IntersectionModule::checkYieldStuckVehicleInIntersection( + const intersection::InterpolatedPathInfo & interpolated_path_info, + const lanelet::ConstLanelets & attention_lanelets) const +{ + const bool yield_stuck_detection_direction = [&]() { + return (turn_direction_ == "left" && planner_param_.yield_stuck.turn_direction.left) || + (turn_direction_ == "right" && planner_param_.yield_stuck.turn_direction.right) || + (turn_direction_ == "straight" && planner_param_.yield_stuck.turn_direction.straight); + }(); + if (!yield_stuck_detection_direction) { + return false; + } + + const double width = planner_data_->vehicle_info_.vehicle_width_m; + const double stuck_vehicle_vel_thr = + planner_param_.stuck_vehicle.stuck_vehicle_velocity_threshold; + const double yield_stuck_distance_thr = planner_param_.yield_stuck.distance_threshold; + + LineString2d sparse_intersection_path; + const auto [start, end] = interpolated_path_info.lane_id_interval.value(); + for (unsigned i = start; i < end; ++i) { + const auto & point = interpolated_path_info.path.points.at(i).point.pose.position; + const auto yaw = tf2::getYaw(interpolated_path_info.path.points.at(i).point.pose.orientation); + if (turn_direction_ == "right") { + const double right_x = point.x - width / 2 * std::sin(yaw); + const double right_y = point.y + width / 2 * std::cos(yaw); + sparse_intersection_path.emplace_back(right_x, right_y); + } else if (turn_direction_ == "left") { + const double left_x = point.x + width / 2 * std::sin(yaw); + const double left_y = point.y - width / 2 * std::cos(yaw); + sparse_intersection_path.emplace_back(left_x, left_y); + } else { + // straight + sparse_intersection_path.emplace_back(point.x, point.y); + } + } + lanelet::ConstLanelets yield_stuck_detect_lanelets; + for (const auto & attention_lanelet : attention_lanelets) { + const auto centerline = attention_lanelet.centerline2d().basicLineString(); + std::vector intersects; + bg::intersection(sparse_intersection_path, centerline, intersects); + if (intersects.empty()) { + continue; + } + const auto intersect = intersects.front(); + const auto intersect_arc_coords = lanelet::geometry::toArcCoordinates( + centerline, lanelet::BasicPoint2d(intersect.x(), intersect.y())); + const double yield_stuck_start = + std::max(0.0, intersect_arc_coords.length - yield_stuck_distance_thr); + const double yield_stuck_end = intersect_arc_coords.length; + yield_stuck_detect_lanelets.push_back( + ::createLaneletFromArcLength(attention_lanelet, yield_stuck_start, yield_stuck_end)); + } + debug_data_.yield_stuck_detect_area = util::getPolygon3dFromLanelets(yield_stuck_detect_lanelets); + for (const auto & object_info : object_info_manager_.attentionObjects()) { + const auto & object = object_info->predicted_object(); + if (!isTargetYieldStuckVehicleType(object)) { + continue; + } + const auto obj_v_norm = std::hypot( + object.kinematics.initial_twist_with_covariance.twist.linear.x, + object.kinematics.initial_twist_with_covariance.twist.linear.y); + + if (obj_v_norm > stuck_vehicle_vel_thr) { + continue; + } + for (const auto & yield_stuck_detect_lanelet : yield_stuck_detect_lanelets) { + const bool is_in_lanelet = lanelet::utils::isInLanelet( + object.kinematics.initial_pose_with_covariance.pose, yield_stuck_detect_lanelet); + if (is_in_lanelet) { + debug_data_.yield_stuck_targets.objects.push_back(object); + return true; + } + } + } + return false; +} +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp index b373f2cbc1c8a..67da3c7a759fe 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp @@ -16,17 +16,17 @@ #include "util.hpp" -#include #include #include #include -#include #include #include #include #include +#include +#include #include #include #include @@ -50,6 +50,32 @@ MergeFromPrivateRoadModule::MergeFromPrivateRoadModule( state_machine_.setState(StateMachine::State::STOP); } +static std::optional getFirstConflictingLanelet( + const lanelet::ConstLanelets & conflicting_lanelets, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length) +{ + const auto & path_ip = interpolated_path_info.path; + const auto [lane_start, end] = interpolated_path_info.lane_id_interval.value(); + const size_t vehicle_length_idx = static_cast(vehicle_length / interpolated_path_info.ds); + const size_t start = + static_cast(std::max(0, static_cast(lane_start) - vehicle_length_idx)); + + for (size_t i = start; i <= end; ++i) { + const auto & pose = path_ip.points.at(i).point.pose; + const auto path_footprint = + tier4_autoware_utils::transformVector(footprint, tier4_autoware_utils::pose2transform(pose)); + for (const auto & conflicting_lanelet : conflicting_lanelets) { + const auto polygon_2d = conflicting_lanelet.polygon2d().basicPolygon(); + const bool intersects = bg::intersects(polygon_2d, path_footprint); + if (intersects) { + return std::make_optional(conflicting_lanelet); + } + } + } + return std::nullopt; +} + bool MergeFromPrivateRoadModule::modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) { debug_data_ = DebugData(); @@ -83,44 +109,41 @@ bool MergeFromPrivateRoadModule::modifyPathVelocity(PathWithLaneId * path, StopR return false; } - /* get detection area */ - if (!intersection_lanelets_) { - const auto & assigned_lanelet = - planner_data_->route_handler_->getLaneletMapPtr()->laneletLayer.get(lane_id_); - const auto lanelets_on_path = planning_utils::getLaneletsOnPath( - *path, lanelet_map_ptr, planner_data_->current_odometry->pose); - intersection_lanelets_ = util::getObjectiveLanelets( - lanelet_map_ptr, routing_graph_ptr, assigned_lanelet, lanelets_on_path, associative_ids_, - planner_param_.attention_area_length, planner_param_.occlusion_attention_area_length, - planner_param_.consider_wrong_direction_vehicle); - } - auto & intersection_lanelets = intersection_lanelets_.value(); + const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; const auto local_footprint = planner_data_->vehicle_info_.createFootprint(0.0, 0.0); - intersection_lanelets.update( - false, interpolated_path_info, local_footprint, - planner_data_->vehicle_info_.max_longitudinal_offset_m); - const auto & first_conflicting_area = intersection_lanelets.first_conflicting_area(); - if (!first_conflicting_area) { + if (!first_conflicting_lanelet_) { + const auto conflicting_lanelets = getAttentionLanelets(); + first_conflicting_lanelet_ = getFirstConflictingLanelet( + conflicting_lanelets, interpolated_path_info, local_footprint, baselink2front); + } + if (!first_conflicting_lanelet_) { return false; } + const auto first_conflicting_lanelet = first_conflicting_lanelet_.value(); - /* set stop-line and stop-judgement-line for base_link */ - const auto stopline_idx_opt = util::generateStuckStopLine( - first_conflicting_area.value(), planner_data_, interpolated_path_info, - planner_param_.stopline_margin, false, path); - if (!stopline_idx_opt.has_value()) { - RCLCPP_WARN_SKIPFIRST_THROTTLE(logger_, *clock_, 1000 /* ms */, "setStopLineIdx fail"); + const auto first_conflicting_idx_opt = util::getFirstPointInsidePolygonByFootprint( + first_conflicting_lanelet.polygon3d(), interpolated_path_info, local_footprint, baselink2front); + if (!first_conflicting_idx_opt) { return false; } - - const size_t stopline_idx = stopline_idx_opt.value(); - if (stopline_idx == 0) { - RCLCPP_DEBUG(logger_, "stop line is at path[0], ignore planning."); + // ========================================================================================== + // first_conflicting_idx is calculated considering baselink2front already, so there is no need + // to subtract baselink2front/ds here + // ========================================================================================== + const auto stopline_idx_ip = static_cast(std::max( + 0, static_cast(first_conflicting_idx_opt.value()) - + static_cast(planner_param_.stopline_margin / planner_param_.path_interpolation_ds))); + + const auto stopline_idx_opt = util::insertPointIndex( + interpolated_path_info.path.points.at(stopline_idx_ip).point.pose, path, + planner_data_->ego_nearest_dist_threshold, planner_data_->ego_nearest_yaw_threshold); + if (!stopline_idx_opt) { + RCLCPP_DEBUG(logger_, "failed to insert stopline, ignore planning."); return true; } + const auto stopline_idx = stopline_idx_opt.value(); - debug_data_.virtual_wall_pose = planning_utils::getAheadPose( - stopline_idx, planner_data_->vehicle_info_.max_longitudinal_offset_m, *path); + debug_data_.virtual_wall_pose = planning_utils::getAheadPose(stopline_idx, baselink2front, *path); debug_data_.stop_point_pose = path->points.at(stopline_idx).point.pose; /* set stop speed */ @@ -154,45 +177,33 @@ bool MergeFromPrivateRoadModule::modifyPathVelocity(PathWithLaneId * path, StopR return true; } -autoware_auto_planning_msgs::msg::PathWithLaneId -MergeFromPrivateRoadModule::extractPathNearExitOfPrivateRoad( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const double extend_length) +lanelet::ConstLanelets MergeFromPrivateRoadModule::getAttentionLanelets() const { - if (path.points.size() < 2) { - return path; - } - - autoware_auto_planning_msgs::msg::PathWithLaneId private_path = path; - private_path.points.clear(); + const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); + const auto routing_graph_ptr = planner_data_->route_handler_->getRoutingGraphPtr(); - double sum_dist = 0.0; - bool prev_has_target_lane_id = false; - for (int i = static_cast(path.points.size()) - 2; i >= 0; i--) { - bool has_target_lane_id = false; - for (const auto path_id : path.points.at(i).lane_ids) { - if (path_id == lane_id_) { - has_target_lane_id = true; + const auto & assigned_lanelet = lanelet_map_ptr->laneletLayer.get(lane_id_); + const auto conflicting_lanelets = + lanelet::utils::getConflictingLanelets(routing_graph_ptr, assigned_lanelet); + lanelet::ConstLanelets sibling_lanelets; + for (const auto & previous_lanelet : routing_graph_ptr->previous(assigned_lanelet)) { + sibling_lanelets.push_back(previous_lanelet); + for (const auto & following_lanelet : routing_graph_ptr->following(previous_lanelet)) { + if (lanelet::utils::contains(sibling_lanelets, following_lanelet)) { + continue; } + sibling_lanelets.push_back(following_lanelet); } - if (has_target_lane_id) { - // add path point with target lane id - // (lanelet with target lane id is exit of private road) - private_path.points.emplace_back(path.points.at(i)); - prev_has_target_lane_id = true; + } + + lanelet::ConstLanelets attention_lanelets; + for (const auto & conflicting_lanelet : conflicting_lanelets) { + if (lanelet::utils::contains(sibling_lanelets, conflicting_lanelet)) { continue; } - if (prev_has_target_lane_id) { - // extend path to the front - private_path.points.emplace_back(path.points.at(i)); - sum_dist += tier4_autoware_utils::calcDistance2d( - path.points.at(i).point.pose, path.points.at(i + 1).point.pose); - if (sum_dist > extend_length) { - break; - } - } + attention_lanelets.push_back(conflicting_lanelet); } - - std::reverse(private_path.points.begin(), private_path.points.end()); - return private_path; + return attention_lanelets; } + } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp index fab0303640700..a44b99c97457d 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp @@ -15,10 +15,7 @@ #ifndef SCENE_MERGE_FROM_PRIVATE_ROAD_HPP_ #define SCENE_MERGE_FROM_PRIVATE_ROAD_HPP_ -#include "scene_intersection.hpp" - #include -#include #include #include @@ -27,9 +24,6 @@ #include #include -#include -#include - #include #include #include @@ -79,17 +73,15 @@ class MergeFromPrivateRoadModule : public SceneModuleInterface motion_utils::VirtualWalls createVirtualWalls() override; const std::set & getAssociativeIds() const { return associative_ids_; } + lanelet::ConstLanelets getAttentionLanelets() const; private: const int64_t lane_id_; const std::set associative_ids_; - autoware_auto_planning_msgs::msg::PathWithLaneId extractPathNearExitOfPrivateRoad( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const double extend_length); - // Parameter PlannerParam planner_param_; - std::optional intersection_lanelets_; + std::optional first_conflicting_lanelet_; StateMachine state_machine_; //! for state diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index e491d2ce7c5ce..dfdfb01fb2234 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -14,19 +14,13 @@ #include "util.hpp" -#include "util_type.hpp" +#include "interpolated_path_info.hpp" #include #include -#include #include -#include -#include -#include #include #include -#include -#include #include #include @@ -34,40 +28,22 @@ #include #include #include +#include #include #include #include -#include +#include #include #include #include -#include #include #include -namespace tier4_autoware_utils -{ - -template <> -inline geometry_msgs::msg::Point getPoint(const lanelet::ConstPoint3d & p) -{ - geometry_msgs::msg::Point point; - point.x = p.x(); - point.y = p.y(); - point.z = p.z(); - return point; -} - -} // namespace tier4_autoware_utils - -namespace behavior_velocity_planner +namespace behavior_velocity_planner::util { namespace bg = boost::geometry; -namespace util -{ - static std::optional getDuplicatedPointIdx( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const geometry_msgs::msg::Point & point) @@ -84,7 +60,7 @@ static std::optional getDuplicatedPointIdx( return std::nullopt; } -static std::optional insertPointIndex( +std::optional insertPointIndex( const geometry_msgs::msg::Pose & in_pose, autoware_auto_planning_msgs::msg::PathWithLaneId * inout_path, const double ego_nearest_dist_threshold, const double ego_nearest_yaw_threshold) @@ -156,67 +132,9 @@ std::optional> findLaneIdsInterval( return found ? std::make_optional(std::make_pair(start, end)) : std::nullopt; } -/** - * @brief Get stop point from map if exists - * @param stop_pose stop point defined on map - * @return true when the stop point is defined on map. - */ -static std::optional getStopLineIndexFromMap( - const InterpolatedPathInfo & interpolated_path_info, - const std::shared_ptr & planner_data) -{ - const auto & path = interpolated_path_info.path; - const auto & lane_interval = interpolated_path_info.lane_id_interval.value(); - - lanelet::ConstLanelet lanelet = - planner_data->route_handler_->getLaneletMapPtr()->laneletLayer.get( - interpolated_path_info.lane_id); - const auto road_markings = lanelet.regulatoryElementsAs(); - lanelet::ConstLineStrings3d stopline; - for (const auto & road_marking : road_markings) { - const std::string type = - road_marking->roadMarking().attributeOr(lanelet::AttributeName::Type, "none"); - if (type == lanelet::AttributeValueString::StopLine) { - stopline.push_back(road_marking->roadMarking()); - break; // only one stopline exists. - } - } - if (stopline.empty()) { - return std::nullopt; - } - - const auto p_start = stopline.front().front(); - const auto p_end = stopline.front().back(); - const LineString2d extended_stopline = - planning_utils::extendLine(p_start, p_end, planner_data->stop_line_extend_length); - - for (size_t i = lane_interval.first; i < lane_interval.second; i++) { - const auto & p_front = path.points.at(i).point.pose.position; - const auto & p_back = path.points.at(i + 1).point.pose.position; - - const LineString2d path_segment = {{p_front.x, p_front.y}, {p_back.x, p_back.y}}; - std::vector collision_points; - bg::intersection(extended_stopline, path_segment, collision_points); - - if (collision_points.empty()) { - continue; - } - - return i; - } - - geometry_msgs::msg::Pose stop_point_from_map; - stop_point_from_map.position.x = 0.5 * (p_start.x() + p_end.x()); - stop_point_from_map.position.y = 0.5 * (p_start.y() + p_end.y()); - stop_point_from_map.position.z = 0.5 * (p_start.z() + p_end.z()); - - return motion_utils::findFirstNearestIndexWithSoftConstraints( - path.points, stop_point_from_map, planner_data->ego_nearest_dist_threshold, - planner_data->ego_nearest_yaw_threshold); -} - -static std::optional getFirstPointInsidePolygonByFootprint( - const lanelet::CompoundPolygon3d & polygon, const InterpolatedPathInfo & interpolated_path_info, +std::optional getFirstPointInsidePolygonByFootprint( + const lanelet::CompoundPolygon3d & polygon, + const intersection::InterpolatedPathInfo & interpolated_path_info, const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length) { const auto & path_ip = interpolated_path_info.path; @@ -236,11 +154,11 @@ static std::optional getFirstPointInsidePolygonByFootprint( return std::nullopt; } -static std::optional> getFirstPointInsidePolygonsByFootprint( const std::vector & polygons, - const InterpolatedPathInfo & interpolated_path_info, + const intersection::InterpolatedPathInfo & interpolated_path_info, const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length) { const auto & path_ip = interpolated_path_info.path; @@ -264,194 +182,6 @@ getFirstPointInsidePolygonsByFootprint( return std::nullopt; } -std::optional generateIntersectionStopLines( - const lanelet::CompoundPolygon3d & first_conflicting_area, - const lanelet::CompoundPolygon3d & first_attention_area, - const lanelet::ConstLineString2d & first_attention_lane_centerline, - const std::shared_ptr & planner_data, - const InterpolatedPathInfo & interpolated_path_info, const bool use_stuck_stopline, - const double stopline_margin, const double max_accel, const double max_jerk, - const double delay_response_time, const double peeking_offset, - autoware_auto_planning_msgs::msg::PathWithLaneId * original_path) -{ - const auto & path_ip = interpolated_path_info.path; - const double ds = interpolated_path_info.ds; - const auto & lane_interval_ip = interpolated_path_info.lane_id_interval.value(); - const double baselink2front = planner_data->vehicle_info_.max_longitudinal_offset_m; - - const int stopline_margin_idx_dist = std::ceil(stopline_margin / ds); - const int base2front_idx_dist = - std::ceil(planner_data->vehicle_info_.max_longitudinal_offset_m / ds); - - // find the index of the first point whose vehicle footprint on it intersects with detection_area - const auto local_footprint = planner_data->vehicle_info_.createFootprint(0.0, 0.0); - std::optional first_footprint_inside_detection_ip_opt = - getFirstPointInsidePolygonByFootprint( - first_attention_area, interpolated_path_info, local_footprint, baselink2front); - if (!first_footprint_inside_detection_ip_opt) { - return std::nullopt; - } - const auto first_footprint_inside_detection_ip = first_footprint_inside_detection_ip_opt.value(); - - std::optional first_footprint_attention_centerline_ip_opt = std::nullopt; - for (auto i = std::get<0>(lane_interval_ip); i < std::get<1>(lane_interval_ip); ++i) { - const auto & base_pose = path_ip.points.at(i).point.pose; - const auto path_footprint = tier4_autoware_utils::transformVector( - local_footprint, tier4_autoware_utils::pose2transform(base_pose)); - if (bg::intersects(path_footprint, first_attention_lane_centerline.basicLineString())) { - // NOTE: maybe consideration of braking dist is necessary - first_footprint_attention_centerline_ip_opt = i; - break; - } - } - if (!first_footprint_attention_centerline_ip_opt) { - return std::nullopt; - } - const size_t first_footprint_attention_centerline_ip = - first_footprint_attention_centerline_ip_opt.value(); - - // (1) default stop line position on interpolated path - bool default_stopline_valid = true; - int stop_idx_ip_int = -1; - if (const auto map_stop_idx_ip = getStopLineIndexFromMap(interpolated_path_info, planner_data); - map_stop_idx_ip) { - stop_idx_ip_int = static_cast(map_stop_idx_ip.value()) - base2front_idx_dist; - } - if (stop_idx_ip_int < 0) { - stop_idx_ip_int = first_footprint_inside_detection_ip - stopline_margin_idx_dist; - } - if (stop_idx_ip_int < 0) { - default_stopline_valid = false; - } - const auto default_stopline_ip = stop_idx_ip_int >= 0 ? static_cast(stop_idx_ip_int) : 0; - - // (2) ego front stop line position on interpolated path - const geometry_msgs::msg::Pose & current_pose = planner_data->current_odometry->pose; - const auto closest_idx_ip = motion_utils::findFirstNearestIndexWithSoftConstraints( - path_ip.points, current_pose, planner_data->ego_nearest_dist_threshold, - planner_data->ego_nearest_yaw_threshold); - - // (3) occlusion peeking stop line position on interpolated path - int occlusion_peeking_line_ip_int = static_cast(default_stopline_ip); - bool occlusion_peeking_line_valid = true; - // NOTE: if footprints[0] is already inside the detection area, invalid - { - const auto & base_pose0 = path_ip.points.at(default_stopline_ip).point.pose; - const auto path_footprint0 = tier4_autoware_utils::transformVector( - local_footprint, tier4_autoware_utils::pose2transform(base_pose0)); - if (bg::intersects( - path_footprint0, lanelet::utils::to2D(first_attention_area).basicPolygon())) { - occlusion_peeking_line_valid = false; - } - } - if (occlusion_peeking_line_valid) { - occlusion_peeking_line_ip_int = - first_footprint_inside_detection_ip + std::ceil(peeking_offset / ds); - } - - const auto occlusion_peeking_line_ip = static_cast( - std::clamp(occlusion_peeking_line_ip_int, 0, static_cast(path_ip.points.size()) - 1)); - const auto first_attention_stopline_ip = first_footprint_inside_detection_ip; - const bool first_attention_stopline_valid = true; - - // (4) pass judge line position on interpolated path - const double velocity = planner_data->current_velocity->twist.linear.x; - const double acceleration = planner_data->current_acceleration->accel.accel.linear.x; - const double braking_dist = planning_utils::calcJudgeLineDistWithJerkLimit( - velocity, acceleration, max_accel, max_jerk, delay_response_time); - int pass_judge_ip_int = - static_cast(first_footprint_inside_detection_ip) - std::ceil(braking_dist / ds); - const auto pass_judge_line_ip = static_cast( - std::clamp(pass_judge_ip_int, 0, static_cast(path_ip.points.size()) - 1)); - // TODO(Mamoru Sobue): maybe braking dist should be considered - const auto occlusion_wo_tl_pass_judge_line_ip = - static_cast(first_footprint_attention_centerline_ip); - - // (5) stuck vehicle stop line - int stuck_stopline_ip_int = 0; - bool stuck_stopline_valid = true; - if (use_stuck_stopline) { - // NOTE: when ego vehicle is approaching detection area and already passed - // first_conflicting_area, this could be null. - const auto stuck_stopline_idx_ip_opt = getFirstPointInsidePolygonByFootprint( - first_conflicting_area, interpolated_path_info, local_footprint, baselink2front); - if (!stuck_stopline_idx_ip_opt) { - stuck_stopline_valid = false; - stuck_stopline_ip_int = 0; - } else { - stuck_stopline_ip_int = stuck_stopline_idx_ip_opt.value() - stopline_margin_idx_dist; - } - } else { - stuck_stopline_ip_int = - std::get<0>(lane_interval_ip) - (stopline_margin_idx_dist + base2front_idx_dist); - } - if (stuck_stopline_ip_int < 0) { - stuck_stopline_valid = false; - } - const auto stuck_stopline_ip = static_cast(std::max(0, stuck_stopline_ip_int)); - - struct IntersectionStopLinesTemp - { - size_t closest_idx{0}; - size_t stuck_stopline{0}; - size_t default_stopline{0}; - size_t first_attention_stopline{0}; - size_t occlusion_peeking_stopline{0}; - size_t pass_judge_line{0}; - size_t occlusion_wo_tl_pass_judge_line{0}; - }; - - IntersectionStopLinesTemp intersection_stoplines_temp; - std::list> stoplines = { - {&closest_idx_ip, &intersection_stoplines_temp.closest_idx}, - {&stuck_stopline_ip, &intersection_stoplines_temp.stuck_stopline}, - {&default_stopline_ip, &intersection_stoplines_temp.default_stopline}, - {&first_attention_stopline_ip, &intersection_stoplines_temp.first_attention_stopline}, - {&occlusion_peeking_line_ip, &intersection_stoplines_temp.occlusion_peeking_stopline}, - {&pass_judge_line_ip, &intersection_stoplines_temp.pass_judge_line}, - {&occlusion_wo_tl_pass_judge_line_ip, - &intersection_stoplines_temp.occlusion_wo_tl_pass_judge_line}}; - stoplines.sort( - [](const auto & it1, const auto & it2) { return *(std::get<0>(it1)) < *(std::get<0>(it2)); }); - for (const auto & [stop_idx_ip, stop_idx] : stoplines) { - const auto & insert_point = path_ip.points.at(*stop_idx_ip).point.pose; - const auto insert_idx = insertPointIndex( - insert_point, original_path, planner_data->ego_nearest_dist_threshold, - planner_data->ego_nearest_yaw_threshold); - if (!insert_idx) { - return std::nullopt; - } - *stop_idx = insert_idx.value(); - } - if ( - intersection_stoplines_temp.occlusion_peeking_stopline < - intersection_stoplines_temp.default_stopline) { - intersection_stoplines_temp.occlusion_peeking_stopline = - intersection_stoplines_temp.default_stopline; - } - - IntersectionStopLines intersection_stoplines; - intersection_stoplines.closest_idx = intersection_stoplines_temp.closest_idx; - if (stuck_stopline_valid) { - intersection_stoplines.stuck_stopline = intersection_stoplines_temp.stuck_stopline; - } - if (default_stopline_valid) { - intersection_stoplines.default_stopline = intersection_stoplines_temp.default_stopline; - } - if (first_attention_stopline_valid) { - intersection_stoplines.first_attention_stopline = - intersection_stoplines_temp.first_attention_stopline; - } - if (occlusion_peeking_line_valid) { - intersection_stoplines.occlusion_peeking_stopline = - intersection_stoplines_temp.occlusion_peeking_stopline; - } - intersection_stoplines.pass_judge_line = intersection_stoplines_temp.pass_judge_line; - intersection_stoplines.occlusion_wo_tl_pass_judge_line = - intersection_stoplines_temp.occlusion_wo_tl_pass_judge_line; - return intersection_stoplines; -} - std::optional getFirstPointInsidePolygon( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon, @@ -490,105 +220,7 @@ std::optional getFirstPointInsidePolygon( return std::nullopt; } -static std::optional> -getFirstPointInsidePolygons( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::pair lane_interval, - const std::vector & polygons, const bool search_forward = true) -{ - if (search_forward) { - for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { - bool is_in_lanelet = false; - const auto & p = path.points.at(i).point.pose.position; - for (const auto & polygon : polygons) { - const auto polygon_2d = lanelet::utils::to2D(polygon); - is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); - if (is_in_lanelet) { - return std::make_optional>( - i, polygon); - } - } - if (is_in_lanelet) { - break; - } - } - } else { - for (size_t i = lane_interval.second; i >= lane_interval.first; --i) { - bool is_in_lanelet = false; - const auto & p = path.points.at(i).point.pose.position; - for (const auto & polygon : polygons) { - const auto polygon_2d = lanelet::utils::to2D(polygon); - is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); - if (is_in_lanelet) { - return std::make_optional>( - i, polygon); - } - } - if (is_in_lanelet) { - break; - } - if (i == 0) { - break; - } - } - } - return std::nullopt; -} - -std::optional generateStuckStopLine( - const lanelet::CompoundPolygon3d & conflicting_area, - const std::shared_ptr & planner_data, - const InterpolatedPathInfo & interpolated_path_info, const double stopline_margin, - const bool use_stuck_stopline, autoware_auto_planning_msgs::msg::PathWithLaneId * original_path) -{ - const auto & path_ip = interpolated_path_info.path; - const double ds = interpolated_path_info.ds; - const auto & lane_interval_ip = interpolated_path_info.lane_id_interval.value(); - const auto lane_interval_ip_start = std::get<0>(lane_interval_ip); - size_t stuck_stopline_idx_ip = 0; - if (use_stuck_stopline) { - stuck_stopline_idx_ip = lane_interval_ip_start; - } else { - const auto stuck_stopline_idx_ip_opt = - getFirstPointInsidePolygon(path_ip, lane_interval_ip, conflicting_area); - if (!stuck_stopline_idx_ip_opt) { - return std::nullopt; - } - stuck_stopline_idx_ip = stuck_stopline_idx_ip_opt.value(); - } - - const int stopline_margin_idx_dist = std::ceil(stopline_margin / ds); - const int base2front_idx_dist = - std::ceil(planner_data->vehicle_info_.max_longitudinal_offset_m / ds); - const size_t insert_idx_ip = static_cast(std::max( - static_cast(stuck_stopline_idx_ip) - 1 - stopline_margin_idx_dist - base2front_idx_dist, - 0)); - const auto & insert_point = path_ip.points.at(insert_idx_ip).point.pose; - return insertPointIndex( - insert_point, original_path, planner_data->ego_nearest_dist_threshold, - planner_data->ego_nearest_yaw_threshold); -} - -static std::vector getPolygon3dFromLanelets( - const lanelet::ConstLanelets & ll_vec) -{ - std::vector polys; - for (auto && ll : ll_vec) { - polys.push_back(ll.polygon3d()); - } - return polys; -} - -static std::string getTurnDirection(lanelet::ConstLanelet lane) -{ - return lane.attributeOr("turn_direction", "else"); -} - -/** - * @param pair lanelets and the vector of original lanelets in topological order (not reversed as - *in generateDetectionLaneDivisions()) - **/ -static std::pair> +std::pair> mergeLaneletsByTopologicalSort( const lanelet::ConstLanelets & lanelets, const lanelet::routing::RoutingGraphPtr routing_graph_ptr) @@ -683,207 +315,9 @@ mergeLaneletsByTopologicalSort( return {merged, originals}; } -IntersectionLanelets getObjectiveLanelets( - lanelet::LaneletMapConstPtr lanelet_map_ptr, lanelet::routing::RoutingGraphPtr routing_graph_ptr, - const lanelet::ConstLanelet assigned_lanelet, const lanelet::ConstLanelets & lanelets_on_path, - const std::set & associative_ids, const double detection_area_length, - const double occlusion_detection_area_length, const bool consider_wrong_direction_vehicle) -{ - const auto turn_direction = assigned_lanelet.attributeOr("turn_direction", "else"); - - // retrieve a stopline associated with a traffic light - bool has_traffic_light = false; - if (const auto tl_reg_elems = assigned_lanelet.regulatoryElementsAs(); - tl_reg_elems.size() != 0) { - const auto tl_reg_elem = tl_reg_elems.front(); - const auto stopline_opt = tl_reg_elem->stopLine(); - if (!!stopline_opt) has_traffic_light = true; - } - - // for low priority lane - // If ego_lane has right of way (i.e. is high priority), - // ignore yieldLanelets (i.e. low priority lanes) - lanelet::ConstLanelets yield_lanelets{}; - const auto right_of_ways = assigned_lanelet.regulatoryElementsAs(); - for (const auto & right_of_way : right_of_ways) { - if (lanelet::utils::contains(right_of_way->rightOfWayLanelets(), assigned_lanelet)) { - for (const auto & yield_lanelet : right_of_way->yieldLanelets()) { - yield_lanelets.push_back(yield_lanelet); - for (const auto & previous_lanelet : routing_graph_ptr->previous(yield_lanelet)) { - yield_lanelets.push_back(previous_lanelet); - } - } - } - } - - // get all following lanes of previous lane - lanelet::ConstLanelets ego_lanelets = lanelets_on_path; - for (const auto & previous_lanelet : routing_graph_ptr->previous(assigned_lanelet)) { - ego_lanelets.push_back(previous_lanelet); - for (const auto & following_lanelet : routing_graph_ptr->following(previous_lanelet)) { - if (lanelet::utils::contains(ego_lanelets, following_lanelet)) { - continue; - } - ego_lanelets.push_back(following_lanelet); - } - } - - // get conflicting lanes on assigned lanelet - const auto & conflicting_lanelets = - lanelet::utils::getConflictingLanelets(routing_graph_ptr, assigned_lanelet); - std::vector adjacent_followings; - - for (const auto & conflicting_lanelet : conflicting_lanelets) { - for (const auto & following_lanelet : routing_graph_ptr->following(conflicting_lanelet)) { - adjacent_followings.push_back(following_lanelet); - } - for (const auto & following_lanelet : routing_graph_ptr->previous(conflicting_lanelet)) { - adjacent_followings.push_back(following_lanelet); - } - } - - // final objective lanelets - lanelet::ConstLanelets detection_lanelets; - lanelet::ConstLanelets conflicting_ex_ego_lanelets; - // conflicting lanes is necessary to get stopline for stuck vehicle - for (auto && conflicting_lanelet : conflicting_lanelets) { - if (!lanelet::utils::contains(ego_lanelets, conflicting_lanelet)) - conflicting_ex_ego_lanelets.push_back(conflicting_lanelet); - } - - // exclude yield lanelets and ego lanelets from detection_lanelets - if (turn_direction == std::string("straight") && has_traffic_light) { - // if assigned lanelet is "straight" with traffic light, detection area is not necessary - } else { - if (consider_wrong_direction_vehicle) { - for (const auto & conflicting_lanelet : conflicting_lanelets) { - if (lanelet::utils::contains(yield_lanelets, conflicting_lanelet)) { - continue; - } - detection_lanelets.push_back(conflicting_lanelet); - } - for (const auto & adjacent_following : adjacent_followings) { - detection_lanelets.push_back(adjacent_following); - } - } else { - // otherwise we need to know the priority from RightOfWay - for (const auto & conflicting_lanelet : conflicting_lanelets) { - if ( - lanelet::utils::contains(yield_lanelets, conflicting_lanelet) || - lanelet::utils::contains(ego_lanelets, conflicting_lanelet)) { - continue; - } - detection_lanelets.push_back(conflicting_lanelet); - } - } - } - - // get possible lanelet path that reaches conflicting_lane longer than given length - lanelet::ConstLanelets detection_and_preceding_lanelets; - { - const double length = detection_area_length; - std::set detection_ids; - for (const auto & ll : detection_lanelets) { - // Preceding lanes does not include detection_lane so add them at the end - const auto & inserted = detection_ids.insert(ll.id()); - if (inserted.second) detection_and_preceding_lanelets.push_back(ll); - // get preceding lanelets without ego_lanelets - // to prevent the detection area from including the ego lanes and its' preceding lanes. - const auto lanelet_sequences = lanelet::utils::query::getPrecedingLaneletSequences( - routing_graph_ptr, ll, length, ego_lanelets); - for (const auto & ls : lanelet_sequences) { - for (const auto & l : ls) { - const auto & inserted = detection_ids.insert(l.id()); - if (inserted.second) detection_and_preceding_lanelets.push_back(l); - } - } - } - } - - lanelet::ConstLanelets occlusion_detection_and_preceding_lanelets; - { - const double length = occlusion_detection_area_length; - std::set detection_ids; - for (const auto & ll : detection_lanelets) { - // Preceding lanes does not include detection_lane so add them at the end - const auto & inserted = detection_ids.insert(ll.id()); - if (inserted.second) occlusion_detection_and_preceding_lanelets.push_back(ll); - // get preceding lanelets without ego_lanelets - // to prevent the detection area from including the ego lanes and its' preceding lanes. - const auto lanelet_sequences = lanelet::utils::query::getPrecedingLaneletSequences( - routing_graph_ptr, ll, length, ego_lanelets); - for (const auto & ls : lanelet_sequences) { - for (const auto & l : ls) { - const auto & inserted = detection_ids.insert(l.id()); - if (inserted.second) occlusion_detection_and_preceding_lanelets.push_back(l); - } - } - } - } - lanelet::ConstLanelets occlusion_detection_and_preceding_lanelets_wo_turn_direction; - for (const auto & ll : occlusion_detection_and_preceding_lanelets) { - const auto turn_direction = getTurnDirection(ll); - if (turn_direction == "left" || turn_direction == "right") { - continue; - } - occlusion_detection_and_preceding_lanelets_wo_turn_direction.push_back(ll); - } - - auto [attention_lanelets, original_attention_lanelet_sequences] = - mergeLaneletsByTopologicalSort(detection_and_preceding_lanelets, routing_graph_ptr); - - IntersectionLanelets result; - result.attention_ = std::move(attention_lanelets); - for (const auto & original_attention_lanelet_seq : original_attention_lanelet_sequences) { - // NOTE: in mergeLaneletsByTopologicalSort(), sub_ids are empty checked, so it is ensured that - // back() exists. - std::optional stopline{std::nullopt}; - for (auto it = original_attention_lanelet_seq.rbegin(); - it != original_attention_lanelet_seq.rend(); ++it) { - const auto traffic_lights = it->regulatoryElementsAs(); - for (const auto & traffic_light : traffic_lights) { - const auto stopline_opt = traffic_light->stopLine(); - if (!stopline_opt) continue; - stopline = stopline_opt.get(); - break; - } - if (stopline) break; - } - result.attention_stoplines_.push_back(stopline); - } - result.attention_non_preceding_ = std::move(detection_lanelets); - for (unsigned i = 0; i < result.attention_non_preceding_.size(); ++i) { - std::optional stopline = std::nullopt; - const auto & ll = result.attention_non_preceding_.at(i); - const auto traffic_lights = ll.regulatoryElementsAs(); - for (const auto & traffic_light : traffic_lights) { - const auto stopline_opt = traffic_light->stopLine(); - if (!stopline_opt) continue; - stopline = stopline_opt.get(); - } - result.attention_non_preceding_stoplines_.push_back(stopline); - } - result.conflicting_ = std::move(conflicting_ex_ego_lanelets); - result.adjacent_ = planning_utils::getConstLaneletsFromIds(lanelet_map_ptr, associative_ids); - // NOTE: occlusion_attention is not inverted here - // TODO(Mamoru Sobue): apply mergeLaneletsByTopologicalSort for occlusion lanelets as well and - // then trim part of them based on curvature threshold - result.occlusion_attention_ = - std::move(occlusion_detection_and_preceding_lanelets_wo_turn_direction); - - // NOTE: to properly update(), each element in conflicting_/conflicting_area_, - // attention_non_preceding_/attention_non_preceding_area_ need to be matched - result.attention_area_ = getPolygon3dFromLanelets(result.attention_); - result.attention_non_preceding_area_ = getPolygon3dFromLanelets(result.attention_non_preceding_); - result.conflicting_area_ = getPolygon3dFromLanelets(result.conflicting_); - result.adjacent_area_ = getPolygon3dFromLanelets(result.adjacent_); - result.occlusion_attention_area_ = getPolygon3dFromLanelets(result.occlusion_attention_); - return result; -} - bool isOverTargetIndex( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int closest_idx, - const geometry_msgs::msg::Pose & current_pose, const int target_idx) + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx, + const geometry_msgs::msg::Pose & current_pose, const size_t target_idx) { if (closest_idx == target_idx) { const geometry_msgs::msg::Pose target_pose = path.points.at(target_idx).point.pose; @@ -893,8 +327,8 @@ bool isOverTargetIndex( } bool isBeforeTargetIndex( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int closest_idx, - const geometry_msgs::msg::Pose & current_pose, const int target_idx) + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx, + const geometry_msgs::msg::Pose & current_pose, const size_t target_idx) { if (closest_idx == target_idx) { const geometry_msgs::msg::Pose target_pose = path.points.at(target_idx).point.pose; @@ -903,81 +337,13 @@ bool isBeforeTargetIndex( return static_cast(target_idx > closest_idx); } -/* -static std::vector getAllAdjacentLanelets( - const lanelet::routing::RoutingGraphPtr routing_graph, lanelet::ConstLanelet lane) -{ - std::set results; - - results.insert(lane.id()); - - auto it = routing_graph->adjacentRight(lane); - // take all lane on the right side - while (!!it) { - results.insert(it.get().id()); - it = routing_graph->adjacentRight(it.get()); - } - // take all lane on the left side - it = routing_graph->adjacentLeft(lane); - while (!!it) { - results.insert(it.get().id()); - it = routing_graph->adjacentLeft(it.get()); - - } - return std::vector(results.begin(), results.end()); -} -*/ - -/* -lanelet::ConstLanelets extendedAdjacentDirectionLanes( - lanelet::LaneletMapConstPtr map, const lanelet::routing::RoutingGraphPtr routing_graph, - lanelet::ConstLanelet lane) -{ - // some of the intersections are not well-formed, and "adjacent" turning - // lanelets are not sharing the LineStrings - const std::string turn_direction = getTurnDirection(lane); - if (turn_direction != "left" && turn_direction != "right" && turn_direction != "straight") - return {}; - - std::set previous_lanelet_ids; - for (auto && previous_lanelet : routing_graph->previous(lane)) { - previous_lanelet_ids.insert(previous_lanelet.id()); - } - - std::set besides_previous_lanelet_ids; - for (auto && previous_lanelet_id : previous_lanelet_ids) { - lanelet::ConstLanelet previous_lanelet = map->laneletLayer.get(previous_lanelet_id); - for (auto && beside_lanelet : getAllAdjacentLanelets(routing_graph, previous_lanelet)) { - besides_previous_lanelet_ids.insert(beside_lanelet); - } - } - - std::set following_turning_lanelets; - following_turning_lanelets.insert(lane.id()); - for (auto && besides_previous_lanelet_id : besides_previous_lanelet_ids) { - lanelet::ConstLanelet besides_previous_lanelet = - map->laneletLayer.get(besides_previous_lanelet_id); - for (auto && following_lanelet : routing_graph->following(besides_previous_lanelet)) { - // if this has {"turn_direction", "${turn_direction}"}, take this - if (getTurnDirection(following_lanelet) == turn_direction) - following_turning_lanelets.insert(following_lanelet.id()); - } - } - lanelet::ConstLanelets ret{}; - for (auto && id : following_turning_lanelets) { - ret.push_back(map->laneletLayer.get(id)); - } - return ret; -} -*/ - -std::optional getIntersectionArea( +std::optional getIntersectionArea( lanelet::ConstLanelet assigned_lane, lanelet::LaneletMapConstPtr lanelet_map_ptr) { const std::string area_id_str = assigned_lane.attributeOr("intersection_area", "else"); if (area_id_str == "else") return std::nullopt; - const int area_id = std::atoi(area_id_str.c_str()); + const lanelet::Id area_id = std::atoi(area_id_str.c_str()); const auto poly_3d = lanelet_map_ptr->polygonLayer.get(area_id); Polygon2d poly{}; for (const auto & p : poly_3d) poly.outer().emplace_back(p.x(), p.y()); @@ -994,117 +360,12 @@ bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane) return tl_id.has_value(); } -TrafficPrioritizedLevel getTrafficPrioritizedLevel( - lanelet::ConstLanelet lane, const std::map & tl_infos) -{ - using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; - - std::optional tl_id = std::nullopt; - for (auto && tl_reg_elem : lane.regulatoryElementsAs()) { - tl_id = tl_reg_elem->id(); - break; - } - if (!tl_id) { - // this lane has no traffic light - return TrafficPrioritizedLevel::NOT_PRIORITIZED; - } - const auto tl_info_it = tl_infos.find(tl_id.value()); - if (tl_info_it == tl_infos.end()) { - // the info of this traffic light is not available - return TrafficPrioritizedLevel::NOT_PRIORITIZED; - } - const auto & tl_info = tl_info_it->second; - bool has_amber_signal{false}; - for (auto && tl_light : tl_info.signal.elements) { - if (tl_light.color == TrafficSignalElement::AMBER) { - has_amber_signal = true; - } - if (tl_light.color == TrafficSignalElement::RED) { - // NOTE: Return here since the red signal has the highest priority. - return TrafficPrioritizedLevel::FULLY_PRIORITIZED; - } - } - if (has_amber_signal) { - return TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED; - } - return TrafficPrioritizedLevel::NOT_PRIORITIZED; -} - -double getHighestCurvature(const lanelet::ConstLineString3d & centerline) -{ - std::vector points; - for (auto point = centerline.begin(); point != centerline.end(); point++) { - points.push_back(*point); - } - - SplineInterpolationPoints2d interpolation(points); - const std::vector curvatures = interpolation.getSplineInterpolatedCurvatures(); - std::vector curvatures_positive; - for (const auto & curvature : curvatures) { - curvatures_positive.push_back(std::fabs(curvature)); - } - return *std::max_element(curvatures_positive.begin(), curvatures_positive.end()); -} - -std::vector generateDetectionLaneDivisions( - lanelet::ConstLanelets detection_lanelets_all, - const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution, - const double curvature_threshold, const double curvature_calculation_ds) -{ - using lanelet::utils::getCenterlineWithOffset; - - // (0) remove left/right lanelet - lanelet::ConstLanelets detection_lanelets; - for (const auto & detection_lanelet : detection_lanelets_all) { - // TODO(Mamoru Sobue): instead of ignoring, only trim straight part of lanelet - const auto fine_centerline = - lanelet::utils::generateFineCenterline(detection_lanelet, curvature_calculation_ds); - const double highest_curvature = getHighestCurvature(fine_centerline); - if (highest_curvature > curvature_threshold) { - continue; - } - detection_lanelets.push_back(detection_lanelet); - } - - // (1) tsort detection_lanelets - const auto [merged_detection_lanelets, originals] = - mergeLaneletsByTopologicalSort(detection_lanelets, routing_graph_ptr); - - // (2) merge each branch to one lanelet - // NOTE: somehow bg::area() for merged lanelet does not work, so calculate it here - std::vector> merged_lanelet_with_area; - for (unsigned i = 0; i < merged_detection_lanelets.size(); ++i) { - const auto & merged_detection_lanelet = merged_detection_lanelets.at(i); - const auto & original = originals.at(i); - double area = 0; - for (const auto & partition : original) { - area += bg::area(partition.polygon2d().basicPolygon()); - } - merged_lanelet_with_area.emplace_back(merged_detection_lanelet, area); - } - - // (3) discretize each merged lanelet - std::vector detection_divisions; - for (const auto & [merged_lanelet, area] : merged_lanelet_with_area) { - const double length = bg::length(merged_lanelet.centerline()); - const double width = area / length; - for (int i = 0; i < static_cast(width / resolution); ++i) { - const double offset = resolution * i - width / 2; - detection_divisions.push_back( - getCenterlineWithOffset(merged_lanelet, offset, resolution).invert()); - } - detection_divisions.push_back( - getCenterlineWithOffset(merged_lanelet, width / 2, resolution).invert()); - } - return detection_divisions; -} - -std::optional generateInterpolatedPath( +std::optional generateInterpolatedPath( const lanelet::Id lane_id, const std::set & associative_lane_ids, const autoware_auto_planning_msgs::msg::PathWithLaneId & input_path, const double ds, const rclcpp::Logger logger) { - InterpolatedPathInfo interpolated_path_info; + intersection::InterpolatedPathInfo interpolated_path_info; if (!splineInterpolate(input_path, ds, interpolated_path_info.path, logger)) { return std::nullopt; } @@ -1116,7 +377,6 @@ std::optional generateInterpolatedPath( return interpolated_path_info; } -// from here geometry_msgs::msg::Pose getObjectPoseWithVelocityDirection( const autoware_auto_perception_msgs::msg::PredictedObjectKinematics & obj_state) { @@ -1134,609 +394,14 @@ geometry_msgs::msg::Pose getObjectPoseWithVelocityDirection( return obj_pose; } -static bool isTargetStuckVehicleType( - const autoware_auto_perception_msgs::msg::PredictedObject & object) -{ - if ( - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::CAR || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::BUS || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::TRUCK || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::TRAILER || - object.classification.at(0).label == - autoware_auto_perception_msgs::msg::ObjectClassification::MOTORCYCLE) { - return true; - } - return false; -} - -bool checkStuckVehicleInIntersection( - const autoware_auto_perception_msgs::msg::PredictedObjects::ConstSharedPtr objects_ptr, - const Polygon2d & stuck_vehicle_detect_area, const double stuck_vehicle_vel_thr, - DebugData * debug_data) -{ - for (const auto & object : objects_ptr->objects) { - if (!isTargetStuckVehicleType(object)) { - continue; // not target vehicle type - } - const auto obj_v_norm = std::hypot( - object.kinematics.initial_twist_with_covariance.twist.linear.x, - object.kinematics.initial_twist_with_covariance.twist.linear.y); - if (obj_v_norm > stuck_vehicle_vel_thr) { - continue; // not stop vehicle - } - - // check if the footprint is in the stuck detect area - const auto obj_footprint = tier4_autoware_utils::toPolygon2d(object); - const bool is_in_stuck_area = !bg::disjoint(obj_footprint, stuck_vehicle_detect_area); - if (is_in_stuck_area && debug_data) { - debug_data->stuck_targets.objects.push_back(object); - return true; - } - } - return false; -} - -static lanelet::LineString3d getLineStringFromArcLength( - const lanelet::ConstLineString3d & linestring, const double s1, const double s2) -{ - lanelet::Points3d points; - double accumulated_length = 0; - size_t start_index = linestring.size(); - for (size_t i = 0; i < linestring.size() - 1; i++) { - const auto & p1 = linestring[i]; - const auto & p2 = linestring[i + 1]; - const double length = boost::geometry::distance(p1.basicPoint(), p2.basicPoint()); - if (accumulated_length + length > s1) { - start_index = i; - break; - } - accumulated_length += length; - } - if (start_index < linestring.size() - 1) { - const auto & p1 = linestring[start_index]; - const auto & p2 = linestring[start_index + 1]; - const double residue = s1 - accumulated_length; - const auto direction_vector = (p2.basicPoint() - p1.basicPoint()).normalized(); - const auto start_basic_point = p1.basicPoint() + residue * direction_vector; - const auto start_point = lanelet::Point3d(lanelet::InvalId, start_basic_point); - points.push_back(start_point); - } - - accumulated_length = 0; - size_t end_index = linestring.size(); - for (size_t i = 0; i < linestring.size() - 1; i++) { - const auto & p1 = linestring[i]; - const auto & p2 = linestring[i + 1]; - const double length = boost::geometry::distance(p1.basicPoint(), p2.basicPoint()); - if (accumulated_length + length > s2) { - end_index = i; - break; - } - accumulated_length += length; - } - - for (size_t i = start_index + 1; i < end_index; i++) { - const auto p = lanelet::Point3d(linestring[i]); - points.push_back(p); - } - if (end_index < linestring.size() - 1) { - const auto & p1 = linestring[end_index]; - const auto & p2 = linestring[end_index + 1]; - const double residue = s2 - accumulated_length; - const auto direction_vector = (p2.basicPoint() - p1.basicPoint()).normalized(); - const auto end_basic_point = p1.basicPoint() + residue * direction_vector; - const auto end_point = lanelet::Point3d(lanelet::InvalId, end_basic_point); - points.push_back(end_point); - } - return lanelet::LineString3d{lanelet::InvalId, points}; -} - -static lanelet::ConstLanelet createLaneletFromArcLength( - const lanelet::ConstLanelet & lanelet, const double s1, const double s2) -{ - const double total_length = boost::geometry::length(lanelet.centerline2d().basicLineString()); - // make sure that s1, and s2 are between [0, lane_length] - const auto s1_saturated = std::max(0.0, std::min(s1, total_length)); - const auto s2_saturated = std::max(0.0, std::min(s2, total_length)); - - const auto ratio_s1 = s1_saturated / total_length; - const auto ratio_s2 = s2_saturated / total_length; - - const auto s1_left = - static_cast(ratio_s1 * boost::geometry::length(lanelet.leftBound().basicLineString())); - const auto s2_left = - static_cast(ratio_s2 * boost::geometry::length(lanelet.leftBound().basicLineString())); - const auto s1_right = - static_cast(ratio_s1 * boost::geometry::length(lanelet.rightBound().basicLineString())); - const auto s2_right = - static_cast(ratio_s2 * boost::geometry::length(lanelet.rightBound().basicLineString())); - - const auto left_bound = getLineStringFromArcLength(lanelet.leftBound(), s1_left, s2_left); - const auto right_bound = getLineStringFromArcLength(lanelet.rightBound(), s1_right, s2_right); - - return lanelet::Lanelet(lanelet::InvalId, left_bound, right_bound); -} - -bool checkYieldStuckVehicleInIntersection( - const util::TargetObjects & target_objects, - const util::InterpolatedPathInfo & interpolated_path_info, - const lanelet::ConstLanelets & attention_lanelets, const std::string & turn_direction, - const double width, const double stuck_vehicle_vel_thr, const double yield_stuck_distance_thr, - DebugData * debug_data) -{ - LineString2d sparse_intersection_path; - const auto [start, end] = interpolated_path_info.lane_id_interval.value(); - for (unsigned i = start; i < end; ++i) { - const auto & point = interpolated_path_info.path.points.at(i).point.pose.position; - const auto yaw = tf2::getYaw(interpolated_path_info.path.points.at(i).point.pose.orientation); - if (turn_direction == "right") { - const double right_x = point.x - width / 2 * std::sin(yaw); - const double right_y = point.y + width / 2 * std::cos(yaw); - sparse_intersection_path.emplace_back(right_x, right_y); - } else if (turn_direction == "left") { - const double left_x = point.x + width / 2 * std::sin(yaw); - const double left_y = point.y - width / 2 * std::cos(yaw); - sparse_intersection_path.emplace_back(left_x, left_y); - } else { - // straight - sparse_intersection_path.emplace_back(point.x, point.y); - } - } - lanelet::ConstLanelets yield_stuck_detect_lanelets; - for (const auto & attention_lanelet : attention_lanelets) { - const auto centerline = attention_lanelet.centerline2d().basicLineString(); - std::vector intersects; - bg::intersection(sparse_intersection_path, centerline, intersects); - if (intersects.empty()) { - continue; - } - const auto intersect = intersects.front(); - const auto intersect_arc_coords = lanelet::geometry::toArcCoordinates( - centerline, lanelet::BasicPoint2d(intersect.x(), intersect.y())); - const double yield_stuck_start = - std::max(0.0, intersect_arc_coords.length - yield_stuck_distance_thr); - const double yield_stuck_end = intersect_arc_coords.length; - yield_stuck_detect_lanelets.push_back( - createLaneletFromArcLength(attention_lanelet, yield_stuck_start, yield_stuck_end)); - } - debug_data->yield_stuck_detect_area = getPolygon3dFromLanelets(yield_stuck_detect_lanelets); - for (const auto & object : target_objects.all_attention_objects) { - const auto obj_v_norm = std::hypot( - object.object.kinematics.initial_twist_with_covariance.twist.linear.x, - object.object.kinematics.initial_twist_with_covariance.twist.linear.y); - - if (obj_v_norm > stuck_vehicle_vel_thr) { - continue; - } - for (const auto & yield_stuck_detect_lanelet : yield_stuck_detect_lanelets) { - const bool is_in_lanelet = lanelet::utils::isInLanelet( - object.object.kinematics.initial_pose_with_covariance.pose, yield_stuck_detect_lanelet); - if (is_in_lanelet) { - debug_data->yield_stuck_targets.objects.push_back(object.object); - return true; - } - } - } - return false; -} - -Polygon2d generateStuckVehicleDetectAreaPolygon( - const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist) -{ - using lanelet::utils::getArcCoordinates; - using lanelet::utils::getLaneletLength3d; - using lanelet::utils::getPolygonFromArcLength; - using lanelet::utils::to2D; - - Polygon2d polygon{}; - if (path_lanelets.conflicting_interval_and_remaining.size() == 0) { - return polygon; - } - - double target_polygon_length = - getLaneletLength3d(path_lanelets.conflicting_interval_and_remaining); - lanelet::ConstLanelets targets = path_lanelets.conflicting_interval_and_remaining; - if (path_lanelets.next) { - targets.push_back(path_lanelets.next.value()); - const double next_arc_length = - std::min(stuck_vehicle_detect_dist, getLaneletLength3d(path_lanelets.next.value())); - target_polygon_length += next_arc_length; - } - const auto target_polygon = - to2D(getPolygonFromArcLength(targets, 0, target_polygon_length)).basicPolygon(); - - if (target_polygon.empty()) { - return polygon; - } - - for (const auto & p : target_polygon) { - polygon.outer().emplace_back(p.x(), p.y()); - } - - polygon.outer().emplace_back(polygon.outer().front()); - bg::correct(polygon); - - return polygon; -} - -std::optional checkAngleForTargetLanelets( - const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, - const double detection_area_angle_thr, const bool consider_wrong_direction_vehicle, - const double dist_margin, const bool is_parked_vehicle) -{ - for (unsigned i = 0; i < target_lanelets.size(); ++i) { - const auto & ll = target_lanelets.at(i); - if (!lanelet::utils::isInLanelet(pose, ll, dist_margin)) { - continue; - } - const double ll_angle = lanelet::utils::getLaneletAngle(ll, pose.position); - const double pose_angle = tf2::getYaw(pose.orientation); - const double angle_diff = tier4_autoware_utils::normalizeRadian(ll_angle - pose_angle, -M_PI); - if (consider_wrong_direction_vehicle) { - if (std::fabs(angle_diff) > 1.57 || std::fabs(angle_diff) < detection_area_angle_thr) { - return std::make_optional(i); - } - } else { - if (std::fabs(angle_diff) < detection_area_angle_thr) { - return std::make_optional(i); - } - // NOTE: sometimes parked vehicle direction is reversed even if its longitudinal velocity is - // positive - if ( - is_parked_vehicle && (std::fabs(angle_diff) < detection_area_angle_thr || - (std::fabs(angle_diff + M_PI) < detection_area_angle_thr))) { - return std::make_optional(i); - } - } - } - return std::nullopt; -} - -void cutPredictPathWithDuration( - util::TargetObjects * target_objects, const rclcpp::Clock::SharedPtr clock, const double time_thr) -{ - const rclcpp::Time current_time = clock->now(); - for (auto & target_object : target_objects->all_attention_objects) { // each objects - for (auto & predicted_path : - target_object.object.kinematics.predicted_paths) { // each predicted paths - const auto origin_path = predicted_path; - predicted_path.path.clear(); - - for (size_t k = 0; k < origin_path.path.size(); ++k) { // each path points - const auto & predicted_pose = origin_path.path.at(k); - const auto predicted_time = - rclcpp::Time(target_objects->header.stamp) + - rclcpp::Duration(origin_path.time_step) * static_cast(k); - if ((predicted_time - current_time).seconds() < time_thr) { - predicted_path.path.push_back(predicted_pose); - } - } - } - } -} - -TimeDistanceArray calcIntersectionPassingTime( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::shared_ptr & planner_data, const lanelet::Id lane_id, - const std::set & associative_ids, const size_t closest_idx, - const size_t last_intersection_stopline_candidate_idx, const double time_delay, - const double intersection_velocity, const double minimum_ego_velocity, - const bool use_upstream_velocity, const double minimum_upstream_velocity, - tier4_debug_msgs::msg::Float64MultiArrayStamped * debug_ttc_array) -{ - const double current_velocity = planner_data->current_velocity->twist.linear.x; - - int assigned_lane_found = false; - - // crop intersection part of the path, and set the reference velocity to intersection_velocity - // for ego's ttc - PathWithLaneId reference_path; - std::optional upstream_stopline{std::nullopt}; - for (size_t i = 0; i < path.points.size() - 1; ++i) { - auto reference_point = path.points.at(i); - // assume backward velocity is current ego velocity - if (i < closest_idx) { - reference_point.point.longitudinal_velocity_mps = current_velocity; - } - if ( - i > last_intersection_stopline_candidate_idx && - std::fabs(reference_point.point.longitudinal_velocity_mps) < - std::numeric_limits::epsilon() && - !upstream_stopline) { - upstream_stopline = i; - } - if (!use_upstream_velocity) { - reference_point.point.longitudinal_velocity_mps = intersection_velocity; - } - reference_path.points.push_back(reference_point); - bool has_objective_lane_id = hasLaneIds(path.points.at(i), associative_ids); - if (assigned_lane_found && !has_objective_lane_id) { - break; - } - assigned_lane_found = has_objective_lane_id; - } - if (!assigned_lane_found) { - return {{0.0, 0.0}}; // has already passed the intersection. - } - - std::vector> original_path_xy; - for (size_t i = 0; i < reference_path.points.size(); ++i) { - const auto & p = reference_path.points.at(i).point.pose.position; - original_path_xy.emplace_back(p.x, p.y); - } - - // apply smoother to reference velocity - PathWithLaneId smoothed_reference_path = reference_path; - if (!smoothPath(reference_path, smoothed_reference_path, planner_data)) { - smoothed_reference_path = reference_path; - } - - // calculate when ego is going to reach each (interpolated) points on the path - TimeDistanceArray time_distance_array{}; - double dist_sum = 0.0; - double passing_time = time_delay; - time_distance_array.emplace_back(passing_time, dist_sum); - - // NOTE: `reference_path` is resampled in `reference_smoothed_path`, so - // `last_intersection_stopline_candidate_idx` makes no sense - const auto smoothed_path_closest_idx = motion_utils::findFirstNearestIndexWithSoftConstraints( - smoothed_reference_path.points, path.points.at(closest_idx).point.pose, - planner_data->ego_nearest_dist_threshold, planner_data->ego_nearest_yaw_threshold); - - const std::optional upstream_stopline_idx_opt = [&]() -> std::optional { - if (upstream_stopline) { - const auto upstream_stopline_point = path.points.at(upstream_stopline.value()).point.pose; - return motion_utils::findFirstNearestIndexWithSoftConstraints( - smoothed_reference_path.points, upstream_stopline_point, - planner_data->ego_nearest_dist_threshold, planner_data->ego_nearest_yaw_threshold); - } else { - return std::nullopt; - } - }(); - const bool has_upstream_stopline = upstream_stopline_idx_opt.has_value(); - const size_t upstream_stopline_ind = upstream_stopline_idx_opt.value_or(0); - - for (size_t i = smoothed_path_closest_idx; i < smoothed_reference_path.points.size() - 1; ++i) { - const auto & p1 = smoothed_reference_path.points.at(i); - const auto & p2 = smoothed_reference_path.points.at(i + 1); - - const double dist = tier4_autoware_utils::calcDistance2d(p1, p2); - dist_sum += dist; - - // use average velocity between p1 and p2 - const double average_velocity = - (p1.point.longitudinal_velocity_mps + p2.point.longitudinal_velocity_mps) / 2.0; - const double passing_velocity = [=]() { - if (use_upstream_velocity) { - if (has_upstream_stopline && i > upstream_stopline_ind) { - return minimum_upstream_velocity; - } - return std::max(average_velocity, minimum_ego_velocity); - } else { - return std::max(average_velocity, minimum_ego_velocity); - } - }(); - passing_time += (dist / passing_velocity); - - time_distance_array.emplace_back(passing_time, dist_sum); - } - debug_ttc_array->layout.dim.resize(3); - debug_ttc_array->layout.dim.at(0).label = "lane_id_@[0][0], ttc_time, ttc_dist, path_x, path_y"; - debug_ttc_array->layout.dim.at(0).size = 5; - debug_ttc_array->layout.dim.at(1).label = "values"; - debug_ttc_array->layout.dim.at(1).size = time_distance_array.size(); - debug_ttc_array->data.reserve( - time_distance_array.size() * debug_ttc_array->layout.dim.at(0).size); - for (unsigned i = 0; i < time_distance_array.size(); ++i) { - debug_ttc_array->data.push_back(lane_id); - } - for (const auto & [t, d] : time_distance_array) { - debug_ttc_array->data.push_back(t); - } - for (const auto & [t, d] : time_distance_array) { - debug_ttc_array->data.push_back(d); - } - for (size_t i = smoothed_path_closest_idx; i < smoothed_reference_path.points.size(); ++i) { - const auto & p = smoothed_reference_path.points.at(i).point.pose.position; - debug_ttc_array->data.push_back(p.x); - } - for (size_t i = smoothed_path_closest_idx; i < smoothed_reference_path.points.size(); ++i) { - const auto & p = smoothed_reference_path.points.at(i).point.pose.position; - debug_ttc_array->data.push_back(p.y); - } - return time_distance_array; -} - -double calcDistanceUntilIntersectionLanelet( - const lanelet::ConstLanelet & assigned_lanelet, - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx) -{ - const auto lane_id = assigned_lanelet.id(); - const auto intersection_first_itr = - std::find_if(path.points.cbegin(), path.points.cend(), [&](const auto & p) { - return std::find(p.lane_ids.begin(), p.lane_ids.end(), lane_id) != p.lane_ids.end(); - }); - if ( - intersection_first_itr == path.points.begin() || intersection_first_itr == path.points.end()) { - return 0.0; - } - const auto dst_idx = std::distance(path.points.begin(), intersection_first_itr) - 1; - - if (closest_idx > static_cast(dst_idx)) { - return 0.0; - } - - double distance = std::abs(motion_utils::calcSignedArcLength(path.points, closest_idx, dst_idx)); - const auto & lane_first_point = assigned_lanelet.centerline2d().front(); - distance += std::hypot( - path.points.at(dst_idx).point.pose.position.x - lane_first_point.x(), - path.points.at(dst_idx).point.pose.position.y - lane_first_point.y()); - return distance; -} - -void IntersectionLanelets::update( - const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info, - const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length) -{ - is_prioritized_ = is_prioritized; - // find the first conflicting/detection area polygon intersecting the path - if (!first_conflicting_area_) { - auto first = getFirstPointInsidePolygonsByFootprint( - conflicting_area_, interpolated_path_info, footprint, vehicle_length); - if (first) { - first_conflicting_lane_ = conflicting_.at(first.value().second); - first_conflicting_area_ = conflicting_area_.at(first.value().second); - } - } - if (!first_attention_area_) { - auto first = getFirstPointInsidePolygonsByFootprint( - attention_non_preceding_area_, interpolated_path_info, footprint, vehicle_length); - if (first) { - first_attention_lane_ = attention_non_preceding_.at(first.value().second); - first_attention_area_ = attention_non_preceding_area_.at(first.value().second); - } - } -} - -static lanelet::ConstLanelets getPrevLanelets( - const lanelet::ConstLanelets & lanelets_on_path, const std::set & associative_ids) -{ - lanelet::ConstLanelets previous_lanelets; - for (const auto & ll : lanelets_on_path) { - if (associative_ids.find(ll.id()) != associative_ids.end()) { - return previous_lanelets; - } - previous_lanelets.push_back(ll); - } - return previous_lanelets; -} - -// end inclusive -lanelet::ConstLanelet generatePathLanelet( - const PathWithLaneId & path, const size_t start_idx, const size_t end_idx, const double width, - const double interval) -{ - lanelet::Points3d lefts; - lanelet::Points3d rights; - size_t prev_idx = start_idx; - for (size_t i = start_idx; i <= end_idx; ++i) { - const auto & p = path.points.at(i).point.pose; - const auto & p_prev = path.points.at(prev_idx).point.pose; - if (i != start_idx && tier4_autoware_utils::calcDistance2d(p_prev, p) < interval) { - continue; - } - prev_idx = i; - const double yaw = tf2::getYaw(p.orientation); - const double x = p.position.x; - const double y = p.position.y; - // NOTE: maybe this is opposite - const double left_x = x + width / 2 * std::sin(yaw); - const double left_y = y - width / 2 * std::cos(yaw); - const double right_x = x - width / 2 * std::sin(yaw); - const double right_y = y + width / 2 * std::cos(yaw); - lefts.emplace_back(lanelet::InvalId, left_x, left_y, p.position.z); - rights.emplace_back(lanelet::InvalId, right_x, right_y, p.position.z); - } - lanelet::LineString3d left = lanelet::LineString3d(lanelet::InvalId, lefts); - lanelet::LineString3d right = lanelet::LineString3d(lanelet::InvalId, rights); - - return lanelet::Lanelet(lanelet::InvalId, left, right); -} - -std::optional generatePathLanelets( - const lanelet::ConstLanelets & lanelets_on_path, - const util::InterpolatedPathInfo & interpolated_path_info, - const std::set & associative_ids, - const lanelet::CompoundPolygon3d & first_conflicting_area, - const std::vector & conflicting_areas, - const std::optional & first_attention_area, - const std::vector & attention_areas, const size_t closest_idx, - const double width) -{ - static constexpr double path_lanelet_interval = 1.5; - - const auto & assigned_lane_interval_opt = interpolated_path_info.lane_id_interval; - if (!assigned_lane_interval_opt) { - return std::nullopt; - } - const auto assigned_lane_interval = assigned_lane_interval_opt.value(); - const auto & path = interpolated_path_info.path; - - PathLanelets path_lanelets; - // prev - path_lanelets.prev = getPrevLanelets(lanelets_on_path, associative_ids); - path_lanelets.all = path_lanelets.prev; - - // entry2ego if exist - const auto [assigned_lane_start, assigned_lane_end] = assigned_lane_interval; - if (closest_idx > assigned_lane_start) { - path_lanelets.all.push_back( - generatePathLanelet(path, assigned_lane_start, closest_idx, width, path_lanelet_interval)); - } - - // ego_or_entry2exit - const auto ego_or_entry_start = std::max(closest_idx, assigned_lane_start); - path_lanelets.ego_or_entry2exit = - generatePathLanelet(path, ego_or_entry_start, assigned_lane_end, width, path_lanelet_interval); - path_lanelets.all.push_back(path_lanelets.ego_or_entry2exit); - - // next - if (assigned_lane_end < path.points.size() - 1) { - const int next_id = path.points.at(assigned_lane_end).lane_ids.at(0); - const auto next_lane_interval_opt = findLaneIdsInterval(path, {next_id}); - if (next_lane_interval_opt) { - const auto [next_start, next_end] = next_lane_interval_opt.value(); - path_lanelets.next = - generatePathLanelet(path, next_start, next_end, width, path_lanelet_interval); - path_lanelets.all.push_back(path_lanelets.next.value()); - } - } - - const auto first_inside_conflicting_idx_opt = - first_attention_area.has_value() - ? getFirstPointInsidePolygon(path, assigned_lane_interval, first_attention_area.value()) - : getFirstPointInsidePolygon(path, assigned_lane_interval, first_conflicting_area); - const auto last_inside_conflicting_idx_opt = - first_attention_area.has_value() - ? getFirstPointInsidePolygons(path, assigned_lane_interval, attention_areas, false) - : getFirstPointInsidePolygons(path, assigned_lane_interval, conflicting_areas, false); - if (first_inside_conflicting_idx_opt && last_inside_conflicting_idx_opt) { - const auto first_inside_conflicting_idx = first_inside_conflicting_idx_opt.value(); - const auto last_inside_conflicting_idx = last_inside_conflicting_idx_opt.value().first; - lanelet::ConstLanelet conflicting_interval = generatePathLanelet( - path, first_inside_conflicting_idx, last_inside_conflicting_idx, width, - path_lanelet_interval); - path_lanelets.conflicting_interval_and_remaining.push_back(std::move(conflicting_interval)); - if (last_inside_conflicting_idx < assigned_lane_end) { - lanelet::ConstLanelet remaining_interval = generatePathLanelet( - path, last_inside_conflicting_idx, assigned_lane_end, width, path_lanelet_interval); - path_lanelets.conflicting_interval_and_remaining.push_back(std::move(remaining_interval)); - } - } - return path_lanelets; -} - -void TargetObject::calc_dist_to_stopline() +std::vector getPolygon3dFromLanelets( + const lanelet::ConstLanelets & ll_vec) { - if (!attention_lanelet || !stopline) { - return; + std::vector polys; + for (auto && ll : ll_vec) { + polys.push_back(ll.polygon3d()); } - const auto attention_lanelet_val = attention_lanelet.value(); - const auto object_arc_coords = lanelet::utils::getArcCoordinates( - {attention_lanelet_val}, object.kinematics.initial_pose_with_covariance.pose); - const auto stopline_val = stopline.value(); - geometry_msgs::msg::Pose stopline_center; - stopline_center.position.x = (stopline_val.front().x() + stopline_val.back().x()) / 2.0; - stopline_center.position.y = (stopline_val.front().y() + stopline_val.back().y()) / 2.0; - stopline_center.position.z = (stopline_val.front().z() + stopline_val.back().z()) / 2.0; - const auto stopline_arc_coords = - lanelet::utils::getArcCoordinates({attention_lanelet_val}, stopline_center); - dist_to_stopline = (stopline_arc_coords.length - object_arc_coords.length); + return polys; } -} // namespace util -} // namespace behavior_velocity_planner +} // namespace behavior_velocity_planner::util diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 33e511d911b82..e37bb3ee88cc1 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -15,69 +15,53 @@ #ifndef UTIL_HPP_ #define UTIL_HPP_ -#include "scene_intersection.hpp" -#include "util_type.hpp" +#include "interpolated_path_info.hpp" -#include +#include +#include -#include +#include + +#include +#include -#include -#include #include #include -#include -#include #include #include -namespace behavior_velocity_planner -{ -namespace util +namespace behavior_velocity_planner::util { -std::optional insertPoint( + +/** + * @brief insert a new pose to the path and return its index + * @return if insertion was successful return the inserted point index + */ +std::optional insertPointIndex( const geometry_msgs::msg::Pose & in_pose, - autoware_auto_planning_msgs::msg::PathWithLaneId * inout_path); + autoware_auto_planning_msgs::msg::PathWithLaneId * inout_path, + const double ego_nearest_dist_threshold, const double ego_nearest_yaw_threshold); +/** + * @brief check if a PathPointWithLaneId contains any of the given lane ids + */ bool hasLaneIds( const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p, const std::set & ids); -std::optional> findLaneIdsInterval( - const autoware_auto_planning_msgs::msg::PathWithLaneId & p, const std::set & ids); /** - * @brief get objective polygons for detection area + * @brief find the first contiguous interval of the path points that contains the specified lane ids + * @return if no interval is found, return null. if the interval [start, end] (inclusive range) is + * found, returns the pair (start-1, end) */ -IntersectionLanelets getObjectiveLanelets( - lanelet::LaneletMapConstPtr lanelet_map_ptr, lanelet::routing::RoutingGraphPtr routing_graph_ptr, - const lanelet::ConstLanelet assigned_lanelet, const lanelet::ConstLanelets & lanelets_on_path, - const std::set & associative_ids, const double detection_area_length, - const double occlusion_detection_area_length, const bool consider_wrong_direction_vehicle); +std::optional> findLaneIdsInterval( + const autoware_auto_planning_msgs::msg::PathWithLaneId & p, const std::set & ids); /** - * @brief Generate a stop line for stuck vehicle - * @param conflicting_areas used to generate stop line for stuck vehicle - * @param original_path ego-car lane - * @param target_path target lane to insert stop point (part of ego-car lane or same to ego-car - * lane) - " @param use_stuck_stopline if true, a stop line is generated at the beginning of intersection lane + * @brief return the index of the first point which is inside the given polygon + * @param[in] lane_interval the interval of the path points on the intersection + * @param[in] search_forward flag for search direction */ -std::optional generateStuckStopLine( - const lanelet::CompoundPolygon3d & first_conflicting_area, - const std::shared_ptr & planner_data, - const InterpolatedPathInfo & interpolated_path_info, const double stopline_margin, - const bool use_stuck_stopline, autoware_auto_planning_msgs::msg::PathWithLaneId * original_path); - -std::optional generateIntersectionStopLines( - const lanelet::CompoundPolygon3d & first_conflicting_area, - const lanelet::CompoundPolygon3d & first_attention_area, - const lanelet::ConstLineString2d & first_attention_lane_centerline, - const std::shared_ptr & planner_data, - const InterpolatedPathInfo & interpolated_path_info, const bool use_stuck_stopline, - const double stopline_margin, const double max_accel, const double max_jerk, - const double delay_response_time, const double peeking_offset, - autoware_auto_planning_msgs::msg::PathWithLaneId * original_path); - std::optional getFirstPointInsidePolygon( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon, @@ -85,39 +69,38 @@ std::optional getFirstPointInsidePolygon( /** * @brief check if ego is over the target_idx. If the index is same, compare the exact pose - * @param path path - * @param closest_idx ego's closest index on the path - * @param current_pose ego's exact pose + * @param[in] path path + * @param[in] closest_idx ego's closest index on the path + * @param[in] current_pose ego's exact pose * @return true if ego is over the target_idx */ bool isOverTargetIndex( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int closest_idx, - const geometry_msgs::msg::Pose & current_pose, const int target_idx); + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx, + const geometry_msgs::msg::Pose & current_pose, const size_t target_idx); +/** + * @brief check if ego is before the target_idx. If the index is same, compare the exact pose + * @param[in] path path + * @param[in] closest_idx ego's closest index on the path + * @param[in] current_pose ego's exact pose + * @return true if ego is over the target_idx + */ bool isBeforeTargetIndex( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int closest_idx, - const geometry_msgs::msg::Pose & current_pose, const int target_idx); + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx, + const geometry_msgs::msg::Pose & current_pose, const size_t target_idx); -/* -lanelet::ConstLanelets extendedAdjacentDirectionLanes( -lanelet::LaneletMapConstPtr map, const lanelet::routing::RoutingGraphPtr routing_graph, -lanelet::ConstLanelet lane); -*/ - -std::optional getIntersectionArea( +std::optional getIntersectionArea( lanelet::ConstLanelet assigned_lane, lanelet::LaneletMapConstPtr lanelet_map_ptr); +/** + * @brief check if the given lane has related traffic light + */ bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane); -TrafficPrioritizedLevel getTrafficPrioritizedLevel( - lanelet::ConstLanelet lane, const std::map & tl_infos); - -std::vector generateDetectionLaneDivisions( - lanelet::ConstLanelets detection_lanelets, - const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution, - const double curvature_threshold, const double curvature_calculation_ds); - -std::optional generateInterpolatedPath( +/** + * @brief interpolate PathWithLaneId + */ +std::optional generateInterpolatedPath( const lanelet::Id lane_id, const std::set & associative_lane_ids, const autoware_auto_planning_msgs::msg::PathWithLaneId & input_path, const double ds, const rclcpp::Logger logger); @@ -125,58 +108,42 @@ std::optional generateInterpolatedPath( geometry_msgs::msg::Pose getObjectPoseWithVelocityDirection( const autoware_auto_perception_msgs::msg::PredictedObjectKinematics & obj_state); -bool checkStuckVehicleInIntersection( - const autoware_auto_perception_msgs::msg::PredictedObjects::ConstSharedPtr objects_ptr, - const Polygon2d & stuck_vehicle_detect_area, const double stuck_vehicle_vel_thr, - DebugData * debug_data); - -bool checkYieldStuckVehicleInIntersection( - const util::TargetObjects & target_objects, - const util::InterpolatedPathInfo & interpolated_path_info, - const lanelet::ConstLanelets & attention_lanelets, const std::string & turn_direction, - const double width, const double stuck_vehicle_vel_thr, const double yield_stuck_distance_thr, - DebugData * debug_data); +/** + * @brief this function sorts the set of lanelets topologically using topological sort and merges + * the lanelets from each root to each end. each branch is merged and returned with the original + * lanelets + * @param[in] lanelets the set of lanelets + * @param[in] routing_graph_ptr the routing graph + * @return the pair of merged lanelets and their corresponding original lanelets + */ +std::pair> +mergeLaneletsByTopologicalSort( + const lanelet::ConstLanelets & lanelets, + const lanelet::routing::RoutingGraphPtr routing_graph_ptr); -Polygon2d generateStuckVehicleDetectAreaPolygon( - const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist); +/** + * @brief find the index of the first point where vehicle footprint intersects with the given + * polygon + */ +std::optional getFirstPointInsidePolygonByFootprint( + const lanelet::CompoundPolygon3d & polygon, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length); -std::optional checkAngleForTargetLanelets( - const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, - const double detection_area_angle_thr, const bool consider_wrong_direction_vehicle, - const double dist_margin, const bool is_parked_vehicle); +/** + * @brief find the index of the first point where vehicle footprint intersects with the given + * polygons + */ +std::optional> +getFirstPointInsidePolygonsByFootprint( + const std::vector & polygons, + const intersection::InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint, const double vehicle_length); -void cutPredictPathWithDuration( - util::TargetObjects * target_objects, const rclcpp::Clock::SharedPtr clock, - const double time_thr); +std::vector getPolygon3dFromLanelets( + const lanelet::ConstLanelets & ll_vec); -TimeDistanceArray calcIntersectionPassingTime( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::shared_ptr & planner_data, const lanelet::Id lane_id, - const std::set & associative_ids, const size_t closest_idx, - const size_t last_intersection_stopline_candidate_idx, const double time_delay, - const double intersection_velocity, const double minimum_ego_velocity, - const bool use_upstream_velocity, const double minimum_upstream_velocity, - tier4_debug_msgs::msg::Float64MultiArrayStamped * debug_ttc_array); - -double calcDistanceUntilIntersectionLanelet( - const lanelet::ConstLanelet & assigned_lanelet, - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t closest_idx); - -lanelet::ConstLanelet generatePathLanelet( - const PathWithLaneId & path, const size_t start_idx, const size_t end_idx, const double width, - const double interval); - -std::optional generatePathLanelets( - const lanelet::ConstLanelets & lanelets_on_path, - const util::InterpolatedPathInfo & interpolated_path_info, - const std::set & associative_ids, - const lanelet::CompoundPolygon3d & first_conflicting_area, - const std::vector & conflicting_areas, - const std::optional & first_attention_area, - const std::vector & attention_areas, const size_t closest_idx, - const double width); - -} // namespace util -} // namespace behavior_velocity_planner +} // namespace behavior_velocity_planner::util #endif // UTIL_HPP_