Skip to content

Junction elements

internal_road

The internal_road route element represents a route within a junction, that connects one incoming one_way_road to one outgoing one_way_road.

Each road position on the map, specified by a longitudinal-offset on a specific msp_road, belongs to either a single one_way_road or a single internal_road.

internal_road and one_way_road occurrences

For interchange junctions that are modeled without overlapping msp_roads, the area of the junction is artificially defined to extend half a meter from the incoming road(s) and half a meter from the outgoing road(s).

internal_roads for an interchange junction with no overlap between internal_roads

OSC definition of internal_road

# enum junction_type:
# A junction is classified as 'highway' if it has either only one entry or only one exit, and 
# speed-limits, road curvatures and entry-exit directions of all incoming, outgoing and internal 
# roads match constrains of highway roads. Otherwise it is classified as 'urban'.
#
enum junction_type: [other = 0, urban, highway]

# enum junction_category_type:
# 'merge' - the junction has a single exit.
# 'split' - the junction has a single entry.
# 'complex' - not merge or split
#
enum junction_category_type: [other = 0, merge, split, complex]

# internal_road represents a junction-internal road connecting one inbound road to one outbound road.
#
struct internal_road inherits road_element:
    # id of the containing junction. This is also the index of the junction in map.junctions.
    #
    junction_id: uint

    # type of the containing junction, see enum junction_type for available values
    #
    junction_kind: junction_type

    # category of the containing junction, see enum junction_category for available values
    #
    junction_category: junction_category_type

    # direction is determined by the angle diff at start and end of the associated route.
    #
    direction: direction

    # min and max curvature along the internal route, in terms of radius.
    # radius is positive for both left and right curvatures.
    # The direction of the curvature is known from 'direction' field.
    # For a straight road interval 'radius' is MAX_DISTANCE
    #
    min_radius: length
    max_radius: length

    # id of the incoming one_way_road connected to this internal_road
    #
    in_one_way_road_id: int

    # id of the outgoing one_way_road connected to this internal_road
    #
    out_one_way_road_id: int

    # id of the logical traffic-light controlling this connection (index in map.traffic_lights). 
    # -1 if none.
    #
    traffic_light_id: int

    # id of the road_with_traffic_light instance controlling this connection. -1 if none.
    #
    road_with_traffic_light_id: int

    # id of the static-sign controlling this connection (e.g., stop or yield). -1 if none.
    #
    sign_id: int

    # id of the road_with_sign instance controlling this connection. -1 if none.
    #
    road_with_sign_id: int

    # flag to indicate if the vehicle can move on the internal road without conflict. The default value is true, in case there is no conflict. 
    #
    can_be_protected: bool

    # flag to indicate if the vehicle can move on the internal road with conflict. The default value is false, in case there is no conflict.
    #
    can_be_unprotected: bool

    # flag to indicate if 'stop' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_stop: bool

    # flag to indicate if 'stop_and_yield' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_stop_and_yield: bool

    # flag to indicate if 'stop_constant' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_stop_constant: bool

    # flag to indicate if 'attention' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_attention: bool

    # flag to indicate if 'caution' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_caution: bool

    # flag to indicate if 'stop_attention' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_stop_attention: bool

    # flag to indicate if 'go' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_go: bool

    # flag to indicate if 'go_exclusive' is a valid semantic state for the traffic light controlling this internal road
    #
    valid_tl_go_exclusive: bool

can_be_protected and can_be_unprotected properties

  • can_be_protected: If this property is true, it means that at least under some circumstances, the rules of the road allow vehicles to cross the junction using this internal road without stopping, slowing down, or yielding to conflicting traffic.

  • can_be_unprotected: If this property is true, it means that att least under some circumstances, the rules of the road allow vehicles to cross, but first they must stop, slow down, or yield to conflicting traffic.

For internal_roads that are not controlled by traffic lights

  • Either can_be_protected and can_be_unprotected is true, but not both.
  • If the internal_road does not have a ‘stop’ or ‘yield’ sign, and all conflicting internal_roads are controlled by ‘stop’ or ‘yield’ signs, then can_be_protected is true, and can_be_unprotected is false.
  • Otherwise can_be_unprotected is true, and can_be_protected is false.

For internal_roads that are are controlled by traffic lights:

  • can_be_protected and can_be_unprotected are determined by the possible traffic light states and how those states are interpreted based on the regional settings of the map.
  • Any combination of values may be valid.
  • The ‘valid_tl_XXX’ properties provide more detailed information about the possible control states. For additional information see Traffic lights modeling and control.

Examples using can_be_protected and can_be_unprotected

The following examples show how can_be_protected and can_be_unprotected values are used in traffic light-controlled internal_roads.

In the following image, a vehicle travelling the internal_road marked by the path (going straight) must either stop at a red light, or cross the junction without stopping or yielding at a green light. It will therefore have can_be_protected == true, and can_be_unprotected == false.

Imagery ©2024 Airbus, Map data ©2024 Google

In the following image, assuming jurisdiction that allows right turns on red, a vehicle travelling the internal_road corresponding to the marked path may either have a protected junction crossing indicated by a green light, or an unprotected junction crossing that requires stopping and yielding to conflicting traffic indicated by a red light. It will therefore have can_be_protected == true and can_be_unprotected == true.

Imagery ©2024 Airbus, Map data ©2024 Google

By US rules, a traffic light-controlled left turn is only considered protected if it has a green left arrow. Otherwise, vehicles must yield to conflicting traffic. In the following image, assuming US rules, a vehicle travelling along the internal_road marked by the path, does not have protected passage because the traffic light does not display a green left arrow. Therefore can_be_protected == false and can_be_unprotected == true.

Imagery ©2024 Airbus, Map data ©2024 Google

Example using one_way_road and internal_road

In this example, we use two triplets of <one_way_road, internal_road, one_way_road> to define two paths crossing a junction, one for the SUT and one for an NPC. Then we use the modifier roads_enter_junction_relation to constrain the NPC’s path so it enters the junction from the left of the SUT’s path, as illustrated in the following image. The SUT and its planned path are green, the NPC and its planned path are blue.

Example scenario using one_way_road and internal_road

OSC2 code: route elements one_way_road and internal_road
import "$FTX/env/basic/exe_platforms/model_ssp/config/model_dummy_config.osc"
import "$FTX/env/basic/msp/open_drive.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/Town04.xodr" 

scenario sut.enter_junction_with_vehicle_from_left:
    npc: vehicle
    sut_entry_road: one_way_road
    sut_exit_road: one_way_road
    sut_internal_road: internal_road with:
        keep(it.in_one_way_road_id == sut_entry_road.id and
             it.out_one_way_road_id == sut_exit_road.id)

    npc_entry_road: one_way_road
    npc_exit_road: one_way_road
    npc_internal_road: internal_road with:
        keep(it.in_one_way_road_id == npc_entry_road.id and
             it.out_one_way_road_id == npc_exit_road.id)

    roads_enter_junction_relation(sut_entry_road, 
                                  npc_entry_road,
                                  relative_direction: any_left)

    do parallel(overlap: equal, duration: [5..10]s):
        serial:
            sut.car.drive() with:
                along(sut_entry_road)
            sut.car.drive() with:
                along(sut_internal_road)
            sut.car.drive() with:
                along(sut_exit_road, run_mode: best_effort)
        serial:
            npc.drive() with:
                along(npc_entry_road)
            npc.drive() with:
                along(npc_internal_road)
            npc.drive() with:
                along(npc_exit_road, run_mode: best_effort)

extend top.main:
    do sut.enter_junction_with_vehicle_from_left()

extend top.info:
    # Downgrading a collision error to info 
    on @top.new_issue:
        call issues.curr.modify(collision_in_manual_control, info, "Downgrading collision severity to 'info'")

roads_follow_in_junction structs

Since routes that cross junctions are a common use case, the roads_follow_in_junction struct groups, together with the triplet <one_way_road, internal_road, one_way_road> are used for this type of route.

# This struct is used to bind together a path over a junction
# It contains:
#    in_road - Incoming one_way_road
#    out_road - Outgoing one_way_road
#    internal_road - internal_road connecting the two one_way_roads
#
struct roads_follow_in_junction:
    in_road: one_way_road
    out_road: one_way_road
    internal_road: internal_road with:
        keep(it.in_one_way_road_id == in_road.id and
             it.out_one_way_road_id == out_road.id)

There are a few derivatives to this class that further constrain the junction-crossing route to be traffic light-controlled, sign-controlled, part of a highway interchange, or part of an urban junction.

roads_follow_in_junction_with_tl

roads_follow_in_junction_with_tl is a junction-crossing route that is controlled by a traffic light.

struct roads_follow_in_junction_with_tl inherits roads_follow_in_junction:
    keep(self.internal_road.traffic_light_id != -1)
    road_with_traffic_light: road_with_traffic_light with:
        keep(it.id == self.internal_road.road_with_traffic_light_id)

roads_follow_in_junction_with_sign

roads_follow_in_junction_with_sign is a junction-crossing route that is controlled by a road sign, such as a ‘yield’ or ‘stop’ sign.

struct roads_follow_in_junction_with_sign inherits roads_follow_in_junction:
    keep(self.internal_road.sign_id != -1)
    road_with_sign: road_with_sign with:
        keep(it.id == self.internal_road.road_with_sign_id)

roads_follow_in_highway_junction

roads_follow_in_highway_junction is a route that crosses a highway junction, meaning that both the in_road and out_road are classified as highway, highway_entry or highway_exit.

struct roads_follow_in_highway_junction inherits roads_follow_in_junction:
    keep (self.internal_road.junction_kind == highway)

roads_follow_in_urban_junction

roads_follow_in_urban_junction is a route that crosses a junction that is not a highway-junction.

struct roads_follow_in_urban_junction inherits roads_follow_in_junction:
    keep (self.internal_road.junction_kind == urban)

Example scenario using roads_follow_in_junction

In this example, we use roads_follow_in_junction instead of one_way_road and internal_road.

OSC2 code: route elements roads_follow_in_junction
import "$FTX/env/basic/exe_platforms/model_ssp/config/model_dummy_config.osc"
import "$FTX/env/basic/msp/open_drive.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/Town04.xodr" 

scenario sut.enter_junction_with_vehicle_from_left:
    npc: vehicle
    sut_route_over_junction: roads_follow_in_junction
    npc_route_over_junction: roads_follow_in_junction

    roads_enter_junction_relation(sut_route_over_junction.in_road, 
                                  npc_route_over_junction.in_road,
                                  relative_direction: any_left)

    do parallel(overlap: equal, duration: [5..10]s):
        serial:
            sut.car.drive() with:
                along(sut_route_over_junction.in_road)
            sut.car.drive() with:
                along(sut_route_over_junction.internal_road)
            sut.car.drive() with:
                along(sut_route_over_junction.out_road, run_mode: best_effort)
        serial:
            npc.drive() with:
                along(npc_route_over_junction.in_road)
            npc.drive() with:
                along(npc_route_over_junction.internal_road)
            npc.drive() with:
                along(npc_route_over_junction.out_road, run_mode: best_effort)

extend top.main:
    do sut.enter_junction_with_vehicle_from_left()

extend top.info:
    # Downgrading a collision error to info 
    on @top.new_issue:
        call issues.curr.modify(collision_in_manual_control, info, "Downgrading collision severity to 'info'")

internal_road_with_conflict

The internal_road_with_conflict route element represents pairs of conflicting internal_road occurrences - internal_roads within the same junction that come from different incoming roads and have some geometrical overlap. Two routes that pass through a pair of conflicting internal_road occurrences pose a potential collision course.

In the following image we see two occurrences of internal_road_with_conflict, each one corresponds to an occurrence of an internal_road, and refers to the other internal_road as the conflicting internal_road.

In the following image, one arbitrary internal_road occurrence is outlined in yellow, and all the internal_road occurrences that conflict with it are outlined in blue. For each pair of yellow-outlined and blue-outlined occurrences, two occurrences of internal_road_with_conflict will be marked - one in which the yellow is the conflicting_road and one in which the blue is the conflicting_road.

OSC definition of internal_road_with_conflict

# internal_road_with_conflict represents a junction internal_road with a overlapping internal_road
# in the junction with a diffrent inbound road
#
struct internal_road_with_conflict inherits road_element:

    # id of the internal_road that this internal_road_with_conflict represents
    #
    internal_road_id: int

    # id of the conflicting internal_road
    #
    conflicting_road_id: int

    # the conflicting internal_road
    #
    conflicting_road: internal_road with:
        keep(it.id == conflicting_road_id)

Example using internal_road_with_conflict

In this example the SUT and NPC are driving simultaneously on conflicting internal_road occurrences that are not controlled by traffic lights, stop signs, or yield signs.

A possible result is shown in the following image. The SUT and its planned path are green, the NPC and its planned path are blue.

Example scenario using internal_road_with_conflict

OSC2 code: route elements internal_road_with_conflict
import "$FTX/env/basic/exe_platforms/model_ssp/config/model_dummy_config.osc"
import "$FTX/env/basic/msp/open_drive.osc"

extend test_config:
    set map = "$FTX/packages/maps/Town04.xodr"
    set min_test_time = 0second

scenario sut.non_signaled_conflict:
    npc: vehicle 
    non_signaled_internal_road: internal_road with:
        # constraint this internal_road to have no sign (stop/yield) control
        #
        keep(it.sign_id == -1) 

        # constraint this internal_road to have no traffic-light control
        #        
        keep(it.traffic_light_id == -1)

    # conflicting_internal_road - 'it.conflicting_road' is an internal_road that conflicts with 
    # 'non_signaled_internal_road', it is also not controlled by sign or traffic-light
    conflicting_internal_road: internal_road_with_conflict with:
        keep(it.internal_road_id == non_signaled_internal_road.id)
        keep(it.conflicting_road.sign_id == -1)
        keep(it.conflicting_road.traffic_light_id == -1)

    do parallel(overlap: equal, duration: [5..10]s):
        serial:
            sut.car.drive()
            c1: sut.car.drive() with:
                along(conflicting_internal_road, start_offset: 0m, at: start)
                s1: speed([10..20]kph, at: start)
                override(s1, run_mode: best_effort)
            sut.car.drive() with:
                along(conflicting_internal_road, end_offset: 0m, at: start)
        serial:
            npc.drive()
            c2: npc.drive() with:
                along(conflicting_internal_road.conflicting_road, start_offset: 0m, at: start)
                s2: speed([10..20]kph, at: start)
            sut.car.drive() with:
                along(conflicting_internal_road, end_offset: 0m, at: start)

        with: sync: synchronize(c1.start, c2.start, offset: [-2..2]s)

extend top.main:
    do sut.non_signaled_conflict()