Module scenariogeneration.xodr.junction_creator

scenariogeneration https://github.com/pyoscx/scenariogeneration

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright (c) 2022 The scenariogeneration Authors.

Classes

class CommonJunctionCreator (id: int, name: str, startnum: int = 100)
Expand source code
class CommonJunctionCreator:
    """CommonJunctionCreator is a helper class to create custom common
    junctions.

    Parameters
    ----------
    id : int
        The ID of the junction.
    name : str
        Name of the junction.
    startnum : int, optional
        The starting ID of this junction's roads. Default is 100.

    Attributes
    ----------
    id : int
        The ID of the junction.
    startnum : int
        The starting ID of this junction's roads.
    incoming_roads : list of Road
        All incoming roads for the junction.
    junction_roads : list of Road
        All generated connecting roads.
    junction : Junction
        The junction xodr element for the junction.

    Methods
    -------
    add_incoming_road_circular_geometry(road, radius, angle,
    road_connection)
        Adds a road on a circle defining the junction geometry.
        Note: Cannot be used together with
        `add_incoming_road_cartesian_geometry`.
    add_incoming_road_cartesian_geometry(road, x, y, heading,
    road_connection)
        Adds a road on a generic x, y, heading geometry.
        Note: Cannot be used together with
        `add_incoming_road_circular_geometry`.
    add_connection(first_road_id, second_road_id, first_lane_id,
    second_lane_id)
        Adds a connection between two roads.
    """

    def __init__(self, id: int, name: str, startnum: int = 100) -> None:
        """Initialize the `CommonJunctionCreator` instance.

        Parameters
        ----------
        id : int
            The ID of the junction.
        name : str
            Name of the junction.
        startnum : int, optional
            The starting ID of this junction's roads. Default is 100.

        Returns
        -------
        None
        """
        self.id = id
        self.incoming_roads = []
        self._radie = []
        self._angles = []
        self._x = []
        self._y = []
        self._h = []
        self.junction_roads = []
        self._number_of_left_lanes = []
        self._number_of_right_lanes = []
        self.startnum = startnum
        self.junction = Junction(name, id, junction_type=JunctionType.default)
        self._circular_junction = False
        self._generic_junction = False
        self._height = None

    def add_incoming_road_circular_geometry(
        self,
        road: "Road",
        radius: float,
        angle: float,
        road_connection: Optional[str] = None,
    ) -> None:
        """Add an incoming road to a junction, assuming a circular geometry.

        This method places all roads on a circle based on the specified
        radius and angle. The radius can vary for different incoming roads,
        but the origin remains the same.

        Note
        ----
        This method cannot be used together with
        `add_incoming_road_cartesian_geometry`.

        Parameters
        ----------
        road : Road
            The incoming road.
        radius : float
            The radius at which to place the road.
        angle : float
            The angle at which to place the road.
        road_connection : str, optional
            Specifies how the incoming road connects to the junction
            ('predecessor' or 'successor'). Default is None.

        Returns
        -------
        None
        """
        self.incoming_roads.append(road)
        self._handle_connection_input(road, road_connection)
        self._radie.append(radius)
        self._angles.append(angle)
        self._circular_junction = True

    def add_incoming_road_cartesian_geometry(
        self,
        road: "Road",
        x: float,
        y: float,
        heading: float,
        road_connection: Optional[str] = None,
    ) -> None:
        """Add an incoming road to a junction, assuming a Cartesian geometry.

        This method places roads based on a local coordinate system
        defined by x, y, and heading.

        Note
        ----
        This method cannot be used together with
        `add_incoming_road_circular_geometry`.

        Parameters
        ----------
        road : Road
            The incoming road.
        x : float
            The local x-position of the road.
        y : float
            The local y-position of the road.
        heading : float
            The local heading of the road (pointing into the junction).
        road_connection : str, optional
            Specifies how the incoming road connects to the junction
            ('predecessor' or 'successor'). Default is None.

        Returns
        -------
        None
        """
        self.incoming_roads.append(road)
        self._handle_connection_input(road, road_connection)

        self._x.append(x)
        self._y.append(y)
        self._h.append(heading)
        self._generic_junction = True

    def add_constant_elevation(self, height: float) -> None:
        """Add a constant elevation to all connecting roads.

        This method sets both the elevation profile and a zero superelevation
        for all connecting roads in the junction.

        Parameters
        ----------
        height : float
            The height of the junction and all roads.

        Returns
        -------
        None
        """
        self._height = height
        for r in self.junction_roads:
            r.add_elevation(0, self._height, 0, 0, 0)

    def _get_minimum_lanes_to_connect(
        self, incoming_road: "Road", linked_road: "Road"
    ) -> tuple[list[int], list[int]]:
        """Determine the minimum number of lanes to connect between two roads.

        This method calculates the lane IDs for both incoming and linked
        roads based on their lane sections and driving directions.

        Parameters
        ----------
        incoming_road : Road
            The incoming road to the junction.
        linked_road : Road
            The linked road to the junction.

        Returns
        -------
        tuple of (list of int, list of int)
            A tuple containing:
            - incoming_lane_ids : list of int
                Lane IDs for the incoming road.
            - linked_lane_ids : list of int
                Lane IDs for the linked road.
        """
        (
            incoming_connection,
            incoming_sign,
            incoming_lane_section,
        ) = _get_related_lanesection(incoming_road, linked_road)
        (
            linked_connection,
            sign,
            linked_lane_section,
        ) = _get_related_lanesection(linked_road, incoming_road)

        incoming_left_lanes = len(
            incoming_road.lanes.lanesections[incoming_lane_section].leftlanes
        )
        incoming_right_lanes = len(
            incoming_road.lanes.lanesections[incoming_lane_section].rightlanes
        )
        linked_left_lanes = len(
            linked_road.lanes.lanesections[linked_lane_section].leftlanes
        )
        linked_right_lanes = len(
            linked_road.lanes.lanesections[linked_lane_section].rightlanes
        )
        _incoming_lane_ids = []
        _linked_lane_ids = []

        if sign > 0:
            _incoming_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_left_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )
            _linked_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_left_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )

            _incoming_lane_ids.extend(
                [
                    -x
                    for x in range(
                        1, min(incoming_right_lanes, linked_right_lanes) + 1, 1
                    )
                ]
            )
            _linked_lane_ids.extend(
                [
                    -x
                    for x in range(
                        1, min(incoming_right_lanes, linked_right_lanes) + 1, 1
                    )
                ]
            )

        elif sign < 0:
            _incoming_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_left_lanes, linked_right_lanes) + 1, 1
                    )
                ]
            )
            _linked_lane_ids.extend(
                [
                    -x
                    for x in range(
                        1, min(incoming_left_lanes, linked_right_lanes) + 1, 1
                    )
                ]
            )

            _incoming_lane_ids.extend(
                [
                    -x
                    for x in range(
                        1, min(incoming_right_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )
            _linked_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_right_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )
        return _incoming_lane_ids, _linked_lane_ids

    def add_connection(
        self,
        road_one_id: int,
        road_two_id: int,
        lane_one_id: Optional[Union[int, list[int]]] = None,
        lane_two_id: Optional[Union[int, list[int]]] = None,
    ) -> None:
        """Add a connection between two roads.

        This method generates connecting roads between two specified
        roads using either lane IDs or automatically determines the
        lanes to connect if no lane IDs are provided.

        Parameters
        ----------
        first_road_id : int
            The ID of the first road to connect.
        second_road_id : int
            The ID of the second road to connect.
        first_lane_id : int or list of int, optional
            The lane ID(s) on the first road to connect. Default is None.
        second_lane_id : int or list of int, optional
            The lane ID(s) on the second road to connect. Default is None.

        Raises
        ------
        NotEnoughInputArguments
            If lane input is used, both `first_lane_id` and `second_lane_id`
            must be provided.

        Returns
        -------
        None
        """
        if lane_one_id is None and lane_two_id is None:
            # check if the connection is symetric (same number of lanes on both sides)
            if (
                self._number_of_left_lanes[self._get_list_index(road_one_id)]
                == self._number_of_right_lanes[
                    self._get_list_index(road_one_id)
                ]
                and self._number_of_left_lanes[
                    self._get_list_index(road_two_id)
                ]
                == self._number_of_right_lanes[
                    self._get_list_index(road_two_id)
                ]
                and self._number_of_left_lanes[
                    self._get_list_index(road_one_id)
                ]
                == self._number_of_right_lanes[
                    self._get_list_index(road_two_id)
                ]
            ):
                self._create_connecting_roads_with_equal_lanes(
                    road_one_id, road_two_id
                )
            else:
                self._create_connecting_roads_unequal_lanes(
                    road_one_id, road_two_id
                )
        elif lane_one_id is not None and lane_two_id is not None:
            if isinstance(lane_one_id, list):
                for i in range(len(lane_one_id)):
                    self._create_connecting_road_with_lane_input(
                        road_one_id,
                        road_two_id,
                        lane_one_id[i],
                        lane_two_id[i],
                    )
            else:
                self._create_connecting_road_with_lane_input(
                    road_one_id, road_two_id, lane_one_id, lane_two_id
                )
        else:
            raise NotEnoughInputArguments(
                "if lane input is used, both has to be provided"
            )

    def _handle_connection_input(
        self, road: "Road", road_connection: Optional[str]
    ) -> None:
        """Validate and process the connection input for an incoming road.

        This method checks if the provided road connection type is valid
        and ensures the road is properly connected to the junction.

        Parameters
        ----------
        road : Road
            The incoming road.
        road_connection : str, optional
            The connection type ('predecessor' or 'successor').
            Default is None.

        Raises
        ------
        ValueError
            If `road_connection` is not 'predecessor' or 'successor'.
        UndefinedRoadNetwork
            If the road is not connected to the junction.

        Returns
        -------
        None
        """
        # if isinstance(road.planview, AdjustablePlanview):
        if road_connection is not None:
            if road_connection == "successor":
                road.add_successor(ElementType.junction, self.id)
            elif road_connection == "predecessor":
                road.add_predecessor(ElementType.junction, self.id)
            else:
                raise ValueError(
                    "road_connection can only be of the values 'successor', or 'predecessor'. Not "
                    + road_connection
                )
        else:
            if not (
                (road.successor and road.successor.element_id == self.id)
                or (
                    road.predecessor and road.predecessor.element_id == self.id
                )
            ):
                raise UndefinedRoadNetwork(
                    "road : "
                    + str(road.id)
                    + " is not connected to junction: "
                    + str(self.id)
                )
        if road.successor and road.successor.element_id == self.id:
            self._number_of_left_lanes.append(
                len(road.lanes.lanesections[-1].leftlanes)
            )
            self._number_of_right_lanes.append(
                len(road.lanes.lanesections[-1].rightlanes)
            )
        elif road.predecessor and road.predecessor.element_id == self.id:
            self._number_of_left_lanes.append(
                len(road.lanes.lanesections[0].leftlanes)
            )
            self._number_of_right_lanes.append(
                len(road.lanes.lanesections[0].rightlanes)
            )

    def _get_list_index(self, id: int) -> int:
        """Get the index of a road in the incoming roads list based on its ID.

        Parameters
        ----------
        id : int
            The ID of the road.

        Returns
        -------
        int
            The index of the road in the incoming roads list.

        Raises
        ------
        ValueError
            If the road ID is not found in the incoming roads list.
        """
        for i in range(len(self.incoming_roads)):
            if self.incoming_roads[i].id == id:
                return i

    def _set_offset_for_incoming_road(
        self, road_idx: int, connecting_road_id: int, offset: int
    ) -> None:
        """Set the lane offset for an incoming road connected to the junction.

        This method updates the lane offset for the incoming road based on
        whether it is a predecessor or successor of the connecting road.

        Parameters
        ----------
        road_idx : int
            The index of the incoming road in the list.
        connecting_road_id : int
            The ID of the connecting road.
        offset : int
            The lane offset to set.

        Returns
        -------
        None
        """

        if self._get_connection_type(road_idx) == "successor":
            self.incoming_roads[road_idx].lane_offset_suc[
                str(connecting_road_id)
            ] = offset
        else:
            self.incoming_roads[road_idx].lane_offset_pred[
                str(connecting_road_id)
            ] = offset

    def _get_contact_point_connecting_road(self, road_id: int) -> ContactPoint:
        """Get the contact point for a connecting road.

        This method determines whether the connecting road is a
        predecessor or successor of the junction.

        Parameters
        ----------
        road_id : int
            The ID of the incoming road.

        Returns
        -------
        ContactPoint
            The contact point
            (`ContactPoint.start` or `ContactPoint.end`).

        Raises
        ------
        AttributeError
            If the road is not connected to the junction.
        """
        incoming_road = self.incoming_roads[self._get_list_index(road_id)]
        if (
            incoming_road.successor
            and incoming_road.successor.element_id == self.id
        ):
            return ContactPoint.end
        elif (
            incoming_road.predecessor
            and incoming_road.predecessor.element_id == self.id
        ):
            return ContactPoint.start
        else:
            raise AttributeError(
                "road : "
                + str(road_id)
                + " is not connected to junction: "
                + str(self.id)
            )

    def _get_connecting_lane_section(self, idx: int) -> int:
        """Get the lane section index for a road connected to the junction.

        This method determines whether the road is a predecessor or
        successor of the junction and returns the corresponding lane
        section index.

        Parameters
        ----------
        idx : int
            The index of the road in the incoming roads list.

        Returns
        -------
        int
            The lane section index
            (`0` for predecessor, `-1` for successor).

        Raises
        ------
        AttributeError
            If the road is not connected to the junction.
        """
        incoming_road = self.incoming_roads[idx]
        if (
            incoming_road.successor
            and incoming_road.successor.element_id == self.id
        ):
            return -1
        elif (
            incoming_road.predecessor
            and incoming_road.predecessor.element_id == self.id
        ):
            return 0
        else:
            raise AttributeError(
                "road : "
                + str(incoming_road.id)
                + " is not connected to junction: "
                + str(self.id)
            )

    def _create_connecting_roads_unequal_lanes(
        self, road_one_id: int, road_two_id: int
    ) -> None:
        """Create connecting roads between two roads with unequal lane counts.

        This method connects two roads that have different numbers of
        lanes entering the junction. It only connects lanes that are
        common between the two roads.

        Parameters
        ----------
        road_one_id : int
            The ID of the first road.
        road_two_id : int
            The ID of the second road.

        Returns
        -------
        None
        """
        idx1 = self._get_list_index(road_one_id)
        idx2 = self._get_list_index(road_two_id)

        # check if the road has _angles/radius for these roads
        if self._circular_junction:
            roadgeoms = self._create_geometry_from_circular(idx1, idx2)
        elif self._generic_junction:
            roadgeoms = self._create_geometry_from_carthesian(idx1, idx2)
        first_road_lane_ids, _ = self._get_minimum_lanes_to_connect(
            self.incoming_roads[idx1], self.incoming_roads[idx2]
        )

        left_lane_defs, right_lane_defs = self._get_lane_defs(
            idx1, idx2, sum([x.length for x in roadgeoms]), True
        )

        tmp_junc_road = create_road(
            roadgeoms,
            self.startnum,
            left_lanes=left_lane_defs,
            right_lanes=right_lane_defs,
            lane_width=3,
            road_type=self.id,
        )
        if self._height is not None:
            tmp_junc_road.add_elevation(0, self._height, 0, 0, 0)
            tmp_junc_road.add_superelevation(0, 0, 0, 0, 0)
        tmp_junc_road.add_predecessor(
            ElementType.road,
            road_one_id,
            contact_point=self._get_contact_point_connecting_road(road_one_id),
        )
        tmp_junc_road.add_successor(
            ElementType.road,
            road_two_id,
            contact_point=self._get_contact_point_connecting_road(road_two_id),
        )

        (
            first_road_lane_ids,
            connecting_road_lane_ids,
        ) = self._get_minimum_lanes_to_connect(
            self.incoming_roads[idx1], tmp_junc_road
        )
        connection = Connection(
            road_one_id, tmp_junc_road.id, ContactPoint.start
        )
        for i in range(len(first_road_lane_ids)):
            connection.add_lanelink(
                first_road_lane_ids[i], connecting_road_lane_ids[i]
            )
        self.junction.add_connection(connection)

        (
            second_road_lane_ids,
            connecting_road_lane_ids,
        ) = self._get_minimum_lanes_to_connect(
            self.incoming_roads[idx2], tmp_junc_road
        )
        connection = Connection(
            tmp_junc_road.successor.element_id,
            tmp_junc_road.id,
            ContactPoint.end,
        )
        for i in range(len(second_road_lane_ids)):
            connection.add_lanelink(
                second_road_lane_ids[i], connecting_road_lane_ids[i]
            )
        self.junction.add_connection(connection)

        self.junction_roads.append(tmp_junc_road)
        self.startnum += 1

    def _get_lane_defs(
        self,
        idx1: int,
        idx2: int,
        connecting_road_length: float,
        allow_empty_lane: bool = False,
    ) -> tuple[LaneDef, LaneDef]:
        """Generate lane definitions for connecting roads.

        This method calculates the lane definitions for left and right
        lanes based on the geometry and lane widths of the incoming roads.

        Parameters
        ----------
        idx1 : int
            The index of the first road in the incoming roads list.
        idx2 : int
            The index of the second road in the incoming roads list.
        connecting_road_length : float
            The length of the connecting road.
        allow_empty_lane : bool, optional
            Whether to allow empty lanes if the lane counts differ.
            Default is False.

        Returns
        -------
        tuple of (LaneDef, LaneDef)
            A tuple containing:
            - left_lane_defs : LaneDef
                Lane definitions for the left lanes.
            - right_lane_defs : LaneDef
                Lane definitions for the right lanes.
        """

        def _get_lane_widths(idx: int, l_or_r: str) -> list[float]:
            """Get lane widths for a specific side (left or right) of a road.

            This method retrieves the widths of lanes on the specified
            side of the road based on its lane section.

            Parameters
            ----------
            idx : int
                The index of the road in the incoming roads list.
            side : str
                The side of the road ('left' or 'right').

            Returns
            -------
            list of float
                A list of lane widths for the specified side.
            """
            connected_lane_section = self._get_connecting_lane_section(idx)
            lane_widths = []
            if l_or_r == "left":
                lanes = (
                    self.incoming_roads[idx]
                    .lanes.lanesections[connected_lane_section]
                    .leftlanes
                )
            elif l_or_r == "right":
                lanes = (
                    self.incoming_roads[idx]
                    .lanes.lanesections[connected_lane_section]
                    .rightlanes
                )
            for ll in lanes:
                if connected_lane_section == 0:
                    lane_widths.append(ll.get_width(0))
                else:
                    lane_widths.append(
                        ll.get_width(
                            self.incoming_roads[
                                idx
                            ].planview.get_total_length()
                            - self.incoming_roads[idx]
                            .lanes.lanesections[connected_lane_section]
                            .s
                        )
                    )
            return lane_widths

        incomming_connected_lane_section = self._get_connecting_lane_section(
            idx1
        )
        outgoing_connected_lane_section = self._get_connecting_lane_section(
            idx2
        )

        if incomming_connected_lane_section == -1:
            n_l_lanes = len(
                self.incoming_roads[idx1]
                .lanes.lanesections[incomming_connected_lane_section]
                .leftlanes
            )
            n_r_lanes = len(
                self.incoming_roads[idx1]
                .lanes.lanesections[incomming_connected_lane_section]
                .rightlanes
            )

            left_start_widths = _get_lane_widths(idx1, "left")
            right_start_widths = _get_lane_widths(idx1, "right")
        elif incomming_connected_lane_section == 0:
            n_r_lanes = len(
                self.incoming_roads[idx1]
                .lanes.lanesections[incomming_connected_lane_section]
                .leftlanes
            )
            n_l_lanes = len(
                self.incoming_roads[idx1]
                .lanes.lanesections[incomming_connected_lane_section]
                .rightlanes
            )

            left_start_widths = _get_lane_widths(idx1, "right")
            right_start_widths = _get_lane_widths(idx1, "left")

        if outgoing_connected_lane_section == -1:
            left_end_widths = _get_lane_widths(idx2, "right")
            right_end_widths = _get_lane_widths(idx2, "left")
        elif outgoing_connected_lane_section == 0:
            left_end_widths = _get_lane_widths(idx2, "left")
            right_end_widths = _get_lane_widths(idx2, "right")

        left_lanes = 0
        if len(left_start_widths) == len(left_end_widths):
            left_lanes = LaneDef(
                0,
                connecting_road_length,
                n_l_lanes,
                n_l_lanes,
                None,
                left_start_widths,
                left_end_widths,
            )
        elif allow_empty_lane:
            num_lanes_to_connect = min(
                len(left_start_widths), len(left_end_widths)
            )
            left_lanes = LaneDef(
                0,
                connecting_road_length,
                num_lanes_to_connect,
                num_lanes_to_connect,
                None,
                left_start_widths[0:num_lanes_to_connect],
                left_end_widths[0:num_lanes_to_connect],
            )

        right_lanes = 0
        if (len(right_start_widths)) == (len(right_end_widths)):
            right_lanes = LaneDef(
                0,
                connecting_road_length,
                n_r_lanes,
                n_r_lanes,
                None,
                right_start_widths,
                right_end_widths,
            )
        elif allow_empty_lane:
            num_lanes_to_connect = min(
                len(right_start_widths), len(right_end_widths)
            )
            right_lanes = LaneDef(
                0,
                connecting_road_length,
                num_lanes_to_connect,
                num_lanes_to_connect,
                None,
                right_start_widths[0:num_lanes_to_connect],
                right_end_widths[0:num_lanes_to_connect],
            )
        return left_lanes, right_lanes

    def _create_connecting_roads_with_equal_lanes(
        self, road_one_id: int, road_two_id: int
    ) -> None:
        """Create connecting roads between two roads with equal lane counts.

        This method connects two roads that have the same number of left
        and right lanes entering the junction.

        Parameters
        ----------
        road_one_id : int
            The ID of the first road.
        road_two_id : int
            The ID of the second road.

        Returns
        -------
        None
        """
        idx1 = self._get_list_index(road_one_id)
        idx2 = self._get_list_index(road_two_id)

        # check if the road has _angles/radius for these roads
        if self._circular_junction:
            roadgeoms = self._create_geometry_from_circular(idx1, idx2)
        elif self._generic_junction:
            roadgeoms = self._create_geometry_from_carthesian(idx1, idx2)

        left_lane_defs, right_lane_defs = self._get_lane_defs(
            idx1, idx2, sum([x.length for x in roadgeoms])
        )

        tmp_junc_road = create_road(
            roadgeoms,
            self.startnum,
            left_lanes=left_lane_defs,
            right_lanes=right_lane_defs,
            lane_width=1,
            road_type=self.id,
        )
        if self._height is not None:
            tmp_junc_road.add_elevation(0, self._height, 0, 0, 0)
            tmp_junc_road.add_superelevation(0, 0, 0, 0, 0)
        tmp_junc_road.add_predecessor(
            ElementType.road,
            road_one_id,
            contact_point=self._get_contact_point_connecting_road(road_one_id),
        )
        tmp_junc_road.add_successor(
            ElementType.road,
            road_two_id,
            contact_point=self._get_contact_point_connecting_road(road_two_id),
        )
        self._add_connection_full(tmp_junc_road)
        self.junction_roads.append(tmp_junc_road)
        self.startnum += 1

    def _get_connection_type(self, road_idx: int) -> str:
        """Determine the connection type for a road in the junction.

        This method checks whether the road is a predecessor or successor
        of the junction.

        Parameters
        ----------
        road_idx : int
            The index of the road in the incoming roads list.

        Returns
        -------
        str
            The connection type ('predecessor' or 'successor').
        """
        if (
            self.incoming_roads[road_idx].successor
            and self.incoming_roads[road_idx].successor.element_id == self.id
        ):
            return "successor"
        else:
            return "predecessor"

    def _get_lane_width(self, lane_id: int, road_idx: int) -> float:
        """Get the width of a specific lane on a road.

        This method retrieves the width of the specified lane based on its
        lane ID and the road index in the incoming roads list.

        Parameters
        ----------
        lane_id : int
            The ID of the lane
            (positive for left lanes, negative for right lanes).
        road_idx : int
            The index of the road in the incoming roads list.

        Returns
        -------
        float
            The width of the specified lane.
        """
        if np.sign(lane_id) == -1:
            start_width = (
                self.incoming_roads[road_idx]
                .lanes.lanesections[
                    self._get_connecting_lane_section(road_idx)
                ]
                .rightlanes[abs(lane_id) - 1]
                .get_width(0)
            )
        else:
            start_width = (
                self.incoming_roads[road_idx]
                .lanes.lanesections[
                    self._get_connecting_lane_section(road_idx)
                ]
                .leftlanes[abs(lane_id) - 1]
                .get_width(0)
            )
        return start_width

    def _create_connecting_road_with_lane_input(
        self,
        road_one_id: int,
        road_two_id: int,
        lane_one_id: int,
        lane_two_id: int,
    ) -> None:
        """Create a connecting road between two roads with specified lane
        inputs.

        This method connects two roads using the specified lane IDs and
        creates an appropriate geometry for the connecting road based on
        the start and end lane widths.

        Parameters
        ----------
        road_one_id : int
            The ID of the first road to connect.
        road_two_id : int
            The ID of the second road to connect.
        lane_one_id : int
            The lane ID on the first road to connect.
        lane_two_id : int
            The lane ID on the second road to connect.

        Raises
        ------
        MixingDrivingDirection
            If the driving direction is inconsistent between the two
            roads.

        Returns
        -------
        None
        """

        idx1 = self._get_list_index(road_one_id)
        idx2 = self._get_list_index(road_two_id)

        start_offset = 0.0
        end_offset = 0.0
        if (
            self._get_connection_type(idx2) == self._get_connection_type(idx1)
            and np.sign(lane_one_id) == np.sign(lane_two_id)
        ) or (
            self._get_connection_type(idx2) != self._get_connection_type(idx1)
            and np.sign(lane_one_id) != np.sign(lane_two_id)
        ):
            raise MixingDrivingDirection(
                "driving direction not consistent when trying to make connection between roads:"
                + str(road_one_id)
                + " and "
                + str(road_two_id)
            )
        if np.sign(lane_one_id) == -1:
            for lane_iter in range((np.sign(lane_one_id) * lane_one_id) - 1):
                start_offset += (
                    self.incoming_roads[idx1]
                    .lanes.lanesections[
                        self._get_connecting_lane_section(idx1)
                    ]
                    .rightlanes[lane_iter]
                    .get_width(0)
                )
        else:
            for lane_iter in range((np.sign(lane_one_id) * lane_one_id) - 1):
                start_offset += (
                    self.incoming_roads[idx1]
                    .lanes.lanesections[
                        self._get_connecting_lane_section(idx1)
                    ]
                    .leftlanes[lane_iter]
                    .get_width(0)
                )

        if np.sign(lane_two_id) == -1:
            for lane_iter in range((np.sign(lane_two_id) * lane_two_id) - 1):
                end_offset += (
                    self.incoming_roads[idx2]
                    .lanes.lanesections[
                        self._get_connecting_lane_section(idx2)
                    ]
                    .rightlanes[lane_iter]
                    .get_width(0)
                )
        else:
            for lane_iter in range((np.sign(lane_two_id) * lane_two_id) - 1):
                end_offset += (
                    self.incoming_roads[idx2]
                    .lanes.lanesections[
                        self._get_connecting_lane_section(idx2)
                    ]
                    .leftlanes[lane_iter]
                    .get_width(0)
                )

        start_width = self._get_lane_width(lane_one_id, idx1)

        end_width = self._get_lane_width(lane_two_id, idx2)

        if self._get_connection_type(idx1) == "successor":
            angle_offset_start = np.sign(lane_one_id) * np.pi / 2
        else:
            angle_offset_start = -np.sign(lane_one_id) * np.pi / 2
        if self._get_connection_type(idx2) == "successor":
            angle_offset_end = -np.sign(lane_two_id) * np.pi / 2
        else:
            angle_offset_end = np.sign(lane_two_id) * np.pi / 2

        if self._circular_junction:
            an1 = self._angles[idx2] - self._angles[idx1] - np.pi
            # adjust angle if multiple of pi
            if an1 > np.pi:
                an1 = -(2 * np.pi - an1)
            start_x = -self._radie[idx1]
            start_y = start_offset * np.sin(angle_offset_start)
            start_h = 0
            end_x = self._radie[idx2] * np.cos(an1) + end_offset * np.cos(
                an1 + angle_offset_end
            )
            end_y = self._radie[idx2] * np.sin(an1) + end_offset * np.sin(
                an1 + angle_offset_end
            )
            end_h = an1

        elif self._generic_junction:
            start_x = self._x[idx1] + start_offset * np.cos(
                self._h[idx1] + angle_offset_start
            )
            start_y = self._y[idx1] + start_offset * np.sin(
                self._h[idx1] + angle_offset_start
            )
            start_h = self._h[idx1]
            end_x = self._x[idx2] + end_offset * np.cos(
                self._h[idx2] - angle_offset_end
            )
            end_y = self._y[idx2] + end_offset * np.sin(
                self._h[idx2] - angle_offset_end
            )
            end_h = self._h[idx2] - np.pi
        clothoids = pcloth.SolveG2(
            start_x,
            start_y,
            start_h,
            STD_START_CLOTH,
            end_x,
            end_y,
            end_h,
            STD_START_CLOTH,
        )
        roadgeoms = [
            Spiral(x.KappaStart, x.KappaEnd, length=x.length)
            for x in clothoids
        ]
        if self._get_connection_type(idx1) == "successor":
            if lane_one_id < 0:
                num_left_lanes = 0
                num_right_lanes = 1
            else:
                num_left_lanes = 1
                num_right_lanes = 0

        else:
            if lane_one_id < 0:
                num_left_lanes = 1
                num_right_lanes = 0
            else:
                num_left_lanes = 0
                num_right_lanes = 1

        tmp_junc_road = create_road(
            roadgeoms,
            self.startnum,
            left_lanes=num_left_lanes,
            right_lanes=num_right_lanes,
            lane_width=start_width,
            road_type=self.id,
            lane_width_end=end_width,
        )
        if self._height is not None:
            tmp_junc_road.add_elevation(0, self._height, 0, 0, 0)
            tmp_junc_road.add_superelevation(0, 0, 0, 0, 0)
        pred_lane_offset = np.sign(lane_one_id) * (abs(lane_one_id) - 1)
        if self._get_connection_type(idx1) == "predecessor":
            pred_lane_offset = -pred_lane_offset
        succ_lane_offset = np.sign(lane_two_id) * (abs(lane_two_id) - 1)
        if self._get_connection_type(idx2) == "predecessor":
            succ_lane_offset = -succ_lane_offset

        tmp_junc_road.add_predecessor(
            ElementType.road,
            road_one_id,
            contact_point=self._get_contact_point_connecting_road(road_one_id),
            lane_offset=pred_lane_offset,
        )
        tmp_junc_road.add_successor(
            ElementType.road,
            road_two_id,
            contact_point=self._get_contact_point_connecting_road(road_two_id),
            lane_offset=succ_lane_offset,
        )
        # add offsets to the incomming roads
        self._set_offset_for_incoming_road(
            idx1, tmp_junc_road.id, -pred_lane_offset
        )
        self._set_offset_for_incoming_road(
            idx2, tmp_junc_road.id, -succ_lane_offset
        )

        self.junction_roads.append(tmp_junc_road)
        connection = Connection(
            road_one_id, tmp_junc_road.id, ContactPoint.start
        )
        if num_left_lanes:
            connection.add_lanelink(lane_one_id, 1)
        else:
            connection.add_lanelink(lane_one_id, -1)
        self.junction.add_connection(connection)
        self.startnum += 1

    def _add_connection_full(self, connecting_road: "Road") -> None:
        """Create full lane connections for a connecting road.

        This method generates lane connections for both predecessor and
        successor roads, ensuring all lanes are properly linked.

        Parameters
        ----------
        connecting_road : Road
            The connecting road for which lane connections are created.

        Returns
        -------
        None
        """
        conne1 = Connection(
            connecting_road.successor.element_id,
            connecting_road.id,
            ContactPoint.end,
        )
        _, sign, _ = _get_related_lanesection(
            connecting_road,
            self.incoming_roads[
                self._get_list_index(connecting_road.successor.element_id)
            ],
        )

        _create_junction_links(
            conne1,
            len(connecting_road.lanes.lanesections[-1].rightlanes),
            -1,
            sign,
            to_offset=connecting_road.lane_offset_suc[
                str(connecting_road.successor.element_id)
            ],
        )
        _create_junction_links(
            conne1,
            len(connecting_road.lanes.lanesections[-1].leftlanes),
            1,
            sign,
            to_offset=connecting_road.lane_offset_suc[
                str(connecting_road.successor.element_id)
            ],
        )
        self.junction.add_connection(conne1)

        # handle predecessor lanes
        conne2 = Connection(
            connecting_road.predecessor.element_id,
            connecting_road.id,
            ContactPoint.start,
        )
        _, sign, _ = _get_related_lanesection(
            connecting_road,
            self.incoming_roads[
                self._get_list_index(connecting_road.predecessor.element_id)
            ],
        )
        _create_junction_links(
            conne2,
            len(connecting_road.lanes.lanesections[0].rightlanes),
            -1,
            sign,
            from_offset=connecting_road.lane_offset_pred[
                str(connecting_road.predecessor.element_id)
            ],
        )
        _create_junction_links(
            conne2,
            len(connecting_road.lanes.lanesections[0].leftlanes),
            1,
            sign,
            from_offset=connecting_road.lane_offset_pred[
                str(connecting_road.predecessor.element_id)
            ],
        )
        self.junction.add_connection(conne2)

    def _create_geometry_from_carthesian(self, idx1: int, idx2: int) -> list:
        """Create a connecting road between two roads using Cartesian
        coordinates.

        This method generates the geometry for a connecting road based on
        the Cartesian coordinates and headings of the two roads.

        Parameters
        ----------
        idx1 : int
            The index of the first road in the incoming roads list.
        idx2 : int
            The index of the second road in the incoming roads list.

        Returns
        -------
        list of Geometries
            A list of geometry objects representing the connecting road.
        """
        an1 = self._h[idx2] - self._h[idx1] - np.pi
        # adjust angle if multiple of pi
        if an1 > np.pi:
            an1 = -(2 * np.pi - an1)

        clothoids = pcloth.SolveG2(
            self._x[idx1],
            self._y[idx1],
            self._h[idx1],
            STD_START_CLOTH,
            self._x[idx2],
            self._y[idx2],
            self._h[idx2] - np.pi,
            STD_START_CLOTH,
        )
        roadgeoms = [
            Spiral(x.KappaStart, x.KappaEnd, length=x.length)
            for x in clothoids
        ]
        return roadgeoms

    def _create_geometry_from_circular(self, idx1: int, idx2: int) -> list:
        """Create a connecting road between two roads using circular geometry.

        This method generates the geometry for a connecting road based on
        the circular radius and angles of the two roads.

        Parameters
        ----------
        idx1 : int
            The index of the first road in the incoming roads list.
        idx2 : int
            The index of the second road in the incoming roads list.

        Returns
        -------
        list of Geometries
            A list of geometry objects representing the connecting road.
        """
        an1 = self._angles[idx2] - self._angles[idx1] - np.pi
        # adjust angle if multiple of pi
        if an1 > np.pi:
            an1 = -(2 * np.pi - an1)

        if np.sign(an1) == 0:
            roadgeoms = [Line(self._radie[idx1] + self._radie[idx2])]
        else:
            clothoids = pcloth.SolveG2(
                -self._radie[idx1],
                0,
                0,
                STD_START_CLOTH,
                self._radie[idx2] * np.cos(an1),
                self._radie[idx2] * np.sin(an1),
                an1,
                STD_START_CLOTH,
            )
            roadgeoms = [
                Spiral(x.KappaStart, x.KappaEnd, length=x.length)
                for x in clothoids
            ]

        return roadgeoms

    def get_connecting_roads(self) -> list:
        """Retrieve the connecting roads generated for the junction.

        This method returns a list of all connecting roads created for the
        junction.

        Returns
        -------
        list of Road
            A list of connecting roads.
        """
        return self.junction_roads

CommonJunctionCreator is a helper class to create custom common junctions.

Parameters

id : int
The ID of the junction.
name : str
Name of the junction.
startnum : int, optional
The starting ID of this junction's roads. Default is 100.

Attributes

id : int
The ID of the junction.
startnum : int
The starting ID of this junction's roads.
incoming_roads : list of Road
All incoming roads for the junction.
junction_roads : list of Road
All generated connecting roads.
junction : Junction
The junction xodr element for the junction.

Methods

add_incoming_road_circular_geometry(road, radius, angle, road_connection) Adds a road on a circle defining the junction geometry. Note: Cannot be used together with add_incoming_road_cartesian_geometry. add_incoming_road_cartesian_geometry(road, x, y, heading, road_connection) Adds a road on a generic x, y, heading geometry. Note: Cannot be used together with add_incoming_road_circular_geometry. add_connection(first_road_id, second_road_id, first_lane_id, second_lane_id) Adds a connection between two roads.

Initialize the CommonJunctionCreator instance.

Parameters

id : int
The ID of the junction.
name : str
Name of the junction.
startnum : int, optional
The starting ID of this junction's roads. Default is 100.

Returns

None
 

Methods

def add_connection(self,
road_one_id: int,
road_two_id: int,
lane_one_id: int | list[int] | None = None,
lane_two_id: int | list[int] | None = None) ‑> None
Expand source code
def add_connection(
    self,
    road_one_id: int,
    road_two_id: int,
    lane_one_id: Optional[Union[int, list[int]]] = None,
    lane_two_id: Optional[Union[int, list[int]]] = None,
) -> None:
    """Add a connection between two roads.

    This method generates connecting roads between two specified
    roads using either lane IDs or automatically determines the
    lanes to connect if no lane IDs are provided.

    Parameters
    ----------
    first_road_id : int
        The ID of the first road to connect.
    second_road_id : int
        The ID of the second road to connect.
    first_lane_id : int or list of int, optional
        The lane ID(s) on the first road to connect. Default is None.
    second_lane_id : int or list of int, optional
        The lane ID(s) on the second road to connect. Default is None.

    Raises
    ------
    NotEnoughInputArguments
        If lane input is used, both `first_lane_id` and `second_lane_id`
        must be provided.

    Returns
    -------
    None
    """
    if lane_one_id is None and lane_two_id is None:
        # check if the connection is symetric (same number of lanes on both sides)
        if (
            self._number_of_left_lanes[self._get_list_index(road_one_id)]
            == self._number_of_right_lanes[
                self._get_list_index(road_one_id)
            ]
            and self._number_of_left_lanes[
                self._get_list_index(road_two_id)
            ]
            == self._number_of_right_lanes[
                self._get_list_index(road_two_id)
            ]
            and self._number_of_left_lanes[
                self._get_list_index(road_one_id)
            ]
            == self._number_of_right_lanes[
                self._get_list_index(road_two_id)
            ]
        ):
            self._create_connecting_roads_with_equal_lanes(
                road_one_id, road_two_id
            )
        else:
            self._create_connecting_roads_unequal_lanes(
                road_one_id, road_two_id
            )
    elif lane_one_id is not None and lane_two_id is not None:
        if isinstance(lane_one_id, list):
            for i in range(len(lane_one_id)):
                self._create_connecting_road_with_lane_input(
                    road_one_id,
                    road_two_id,
                    lane_one_id[i],
                    lane_two_id[i],
                )
        else:
            self._create_connecting_road_with_lane_input(
                road_one_id, road_two_id, lane_one_id, lane_two_id
            )
    else:
        raise NotEnoughInputArguments(
            "if lane input is used, both has to be provided"
        )

Add a connection between two roads.

This method generates connecting roads between two specified roads using either lane IDs or automatically determines the lanes to connect if no lane IDs are provided.

Parameters

first_road_id : int
The ID of the first road to connect.
second_road_id : int
The ID of the second road to connect.
first_lane_id : int or list of int, optional
The lane ID(s) on the first road to connect. Default is None.
second_lane_id : int or list of int, optional
The lane ID(s) on the second road to connect. Default is None.

Raises

NotEnoughInputArguments
If lane input is used, both first_lane_id and second_lane_id must be provided.

Returns

None
 
def add_constant_elevation(self, height: float) ‑> None
Expand source code
def add_constant_elevation(self, height: float) -> None:
    """Add a constant elevation to all connecting roads.

    This method sets both the elevation profile and a zero superelevation
    for all connecting roads in the junction.

    Parameters
    ----------
    height : float
        The height of the junction and all roads.

    Returns
    -------
    None
    """
    self._height = height
    for r in self.junction_roads:
        r.add_elevation(0, self._height, 0, 0, 0)

Add a constant elevation to all connecting roads.

This method sets both the elevation profile and a zero superelevation for all connecting roads in the junction.

Parameters

height : float
The height of the junction and all roads.

Returns

None
 
def add_incoming_road_cartesian_geometry(self,
road: Road,
x: float,
y: float,
heading: float,
road_connection: str | None = None)
Expand source code
def add_incoming_road_cartesian_geometry(
    self,
    road: "Road",
    x: float,
    y: float,
    heading: float,
    road_connection: Optional[str] = None,
) -> None:
    """Add an incoming road to a junction, assuming a Cartesian geometry.

    This method places roads based on a local coordinate system
    defined by x, y, and heading.

    Note
    ----
    This method cannot be used together with
    `add_incoming_road_circular_geometry`.

    Parameters
    ----------
    road : Road
        The incoming road.
    x : float
        The local x-position of the road.
    y : float
        The local y-position of the road.
    heading : float
        The local heading of the road (pointing into the junction).
    road_connection : str, optional
        Specifies how the incoming road connects to the junction
        ('predecessor' or 'successor'). Default is None.

    Returns
    -------
    None
    """
    self.incoming_roads.append(road)
    self._handle_connection_input(road, road_connection)

    self._x.append(x)
    self._y.append(y)
    self._h.append(heading)
    self._generic_junction = True

Add an incoming road to a junction, assuming a Cartesian geometry.

This method places roads based on a local coordinate system defined by x, y, and heading.

Note

This method cannot be used together with add_incoming_road_circular_geometry.

Parameters

road : Road
The incoming road.
x : float
The local x-position of the road.
y : float
The local y-position of the road.
heading : float
The local heading of the road (pointing into the junction).
road_connection : str, optional
Specifies how the incoming road connects to the junction ('predecessor' or 'successor'). Default is None.

Returns

None
 
def add_incoming_road_circular_geometry(self, road: Road, radius: float, angle: float, road_connection: str | None = None)
Expand source code
def add_incoming_road_circular_geometry(
    self,
    road: "Road",
    radius: float,
    angle: float,
    road_connection: Optional[str] = None,
) -> None:
    """Add an incoming road to a junction, assuming a circular geometry.

    This method places all roads on a circle based on the specified
    radius and angle. The radius can vary for different incoming roads,
    but the origin remains the same.

    Note
    ----
    This method cannot be used together with
    `add_incoming_road_cartesian_geometry`.

    Parameters
    ----------
    road : Road
        The incoming road.
    radius : float
        The radius at which to place the road.
    angle : float
        The angle at which to place the road.
    road_connection : str, optional
        Specifies how the incoming road connects to the junction
        ('predecessor' or 'successor'). Default is None.

    Returns
    -------
    None
    """
    self.incoming_roads.append(road)
    self._handle_connection_input(road, road_connection)
    self._radie.append(radius)
    self._angles.append(angle)
    self._circular_junction = True

Add an incoming road to a junction, assuming a circular geometry.

This method places all roads on a circle based on the specified radius and angle. The radius can vary for different incoming roads, but the origin remains the same.

Note

This method cannot be used together with add_incoming_road_cartesian_geometry.

Parameters

road : Road
The incoming road.
radius : float
The radius at which to place the road.
angle : float
The angle at which to place the road.
road_connection : str, optional
Specifies how the incoming road connects to the junction ('predecessor' or 'successor'). Default is None.

Returns

None
 
def get_connecting_roads(self) ‑> list
Expand source code
def get_connecting_roads(self) -> list:
    """Retrieve the connecting roads generated for the junction.

    This method returns a list of all connecting roads created for the
    junction.

    Returns
    -------
    list of Road
        A list of connecting roads.
    """
    return self.junction_roads

Retrieve the connecting roads generated for the junction.

This method returns a list of all connecting roads created for the junction.

Returns

list of Road
A list of connecting roads.
class DirectJunctionCreator (id: int, name: str)
Expand source code
class DirectJunctionCreator:
    """DirectJunctionCreator is a helper class to create custom direct
    junctions.

    Parameters
    ----------
    id : int
        The ID of the junction.
    name : str
        Name of the junction.

    Attributes
    ----------
    id : int
        The ID of the junction.
    junction : Junction
        The junction xodr element for the junction.

    Methods
    -------
    add_connection(incoming_road, linked_road, incoming_lane_ids=None,
    linked_lane_ids=None)
        Adds a connection between an incoming road and a linked road.
        If no lane information is provided, it connects all lanes that the
        two roads have in common.
    """

    def __init__(self, id: int, name: str) -> None:
        """Initialize the `DirectJunctionCreator` instance.

        Parameters
        ----------
        id : int
            The ID of the junction.
        name : str
            Name of the junction.

        Returns
        -------
        None
        """
        self.id = id
        self.junction = Junction(name, id, JunctionType.direct)
        self._incoming_lane_ids = []
        self._linked_lane_ids = []

    def _get_minimum_lanes_to_connect(
        self, incoming_road: "Road", linked_road: "Road"
    ) -> tuple[list[int], list[int]]:
        """Determine the minimum number of lanes to connect between two roads.

        This method calculates the lane IDs for both incoming and linked
        roads based on their lane sections and driving directions.

        Parameters
        ----------
        incoming_road : Road
            The incoming road to the junction.
        linked_road : Road
            The linked road to the junction.

        Returns
        -------
        tuple of (list of int, list of int)
            A tuple containing:
            - incoming_lane_ids : list of int
                Lane IDs for the incoming road.
            - linked_lane_ids : list of int
                Lane IDs for the linked road.
        """
        (
            incoming_connection,
            _,
            incoming_lane_section,
        ) = _get_related_lanesection(incoming_road, linked_road)
        (
            linked_connection,
            sign,
            linked_lane_section,
        ) = _get_related_lanesection(linked_road, incoming_road)

        incoming_left_lanes = len(
            incoming_road.lanes.lanesections[incoming_lane_section].leftlanes
        )
        incoming_right_lanes = len(
            incoming_road.lanes.lanesections[incoming_lane_section].rightlanes
        )
        linked_left_lanes = len(
            linked_road.lanes.lanesections[linked_lane_section].leftlanes
        )
        linked_right_lanes = len(
            linked_road.lanes.lanesections[linked_lane_section].rightlanes
        )
        self._incoming_lane_ids = []
        self._linked_lane_ids = []
        # if incoming_connection == "successor" and linked_connection == "predecessor" or incoming_connection == "predecessor" and linked_connection == "successor":
        if sign > 0:
            self._incoming_lane_ids.extend(
                [
                    x
                    for x in range(
                        -min(incoming_right_lanes, linked_right_lanes), 0, 1
                    )
                ]
            )
            self._linked_lane_ids.extend(
                [
                    x
                    for x in range(
                        -min(incoming_right_lanes, linked_right_lanes), 0, 1
                    )
                ]
            )

            self._incoming_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_left_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )
            self._linked_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_left_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )

        elif (
            sign < 0
        ):  # incoming_connection == "successor" and linked_connection == "successor" or incoming_connection == "predecessor" and linked_connection == "predecessor":
            self._incoming_lane_ids.extend(
                [
                    -x
                    for x in range(
                        -min(incoming_left_lanes, linked_right_lanes), 0, 1
                    )
                ]
            )
            self._linked_lane_ids.extend(
                [
                    x
                    for x in range(
                        -min(incoming_left_lanes, linked_right_lanes), 0, 1
                    )
                ]
            )

            self._incoming_lane_ids.extend(
                [
                    -x
                    for x in range(
                        1, min(incoming_right_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )
            self._linked_lane_ids.extend(
                [
                    x
                    for x in range(
                        1, min(incoming_right_lanes, linked_left_lanes) + 1, 1
                    )
                ]
            )

    def _get_contact_point_linked_road(
        self, incoming_road: "Road"
    ) -> ContactPoint:
        """Get the contact point for a linked road.

        This method determines whether the linked road is a predecessor
        or successor of the junction.

        Parameters
        ----------
        incoming_road : Road
            The incoming road to the junction.

        Returns
        -------
        ContactPoint
            The contact point (`ContactPoint.start` or `ContactPoint.end`).

        Raises
        ------
        AttributeError
            If the road is not connected to the junction.
        """
        if (
            incoming_road.successor
            and incoming_road.successor.element_id == self.id
        ):
            return ContactPoint.end
        elif (
            incoming_road.predecessor
            and incoming_road.predecessor.element_id == self.id
        ):
            return ContactPoint.start
        else:
            raise AttributeError(
                "road : "
                + str(incoming_road.id)
                + " is not connected to junction: "
                + str(self.id)
            )

    def add_connection(
        self,
        incoming_road: "Road",
        linked_road: "Road",
        incoming_lane_ids: Optional[Union[int, list[int]]] = None,
        linked_lane_ids: Optional[Union[int, list[int]]] = None,
    ) -> None:
        """Add a connection between an incoming road and a linked road.

        This method connects two roads using specified lane IDs or
        automatically determines the lanes to connect if no lane IDs are
        provided.

        Parameters
        ----------
        incoming_road : Road
            The incoming road to the junction.
        linked_road : Road
            The linked road to the junction.
        incoming_lane_ids : int or list of int, optional
            The lane ID(s) on the incoming road to connect.
            Default is None.
        linked_lane_ids : int or list of int, optional
            The lane ID(s) on the linked road to connect.
            Default is None.

        Raises
        ------
        MixingDrivingDirection
            If the driving direction is inconsistent between the two
            roads.
        NotSameAmountOfLanesError
            If the number of incoming and linked lane IDs are not the
            same.

        Returns
        -------
        None
        """

        linked_lane_offset = 0
        inc_lane_offset = 0
        incoming_main_road = False
        if incoming_lane_ids == None and linked_lane_ids == None:
            self._get_minimum_lanes_to_connect(incoming_road, linked_road)

        elif incoming_lane_ids is not None and linked_lane_ids is not None:
            if not isinstance(incoming_lane_ids, list):
                self._incoming_lane_ids = [incoming_lane_ids]
            else:
                self._incoming_lane_ids = incoming_lane_ids

            if not isinstance(linked_lane_ids, list):
                self._linked_lane_ids = [linked_lane_ids]

                if abs(linked_lane_ids) == 1:
                    incoming_main_road = True
            else:
                self._linked_lane_ids = linked_lane_ids
                if min([abs(x) for x in self._linked_lane_ids]) == 1:
                    incoming_main_road = True
            # sanity check
            for i in range(len(self._incoming_lane_ids)):
                if self._get_contact_point_linked_road(
                    incoming_road
                ) == self._get_contact_point_linked_road(linked_road):
                    if np.sign(self._incoming_lane_ids[i]) == np.sign(
                        self._linked_lane_ids[i]
                    ):
                        raise MixingDrivingDirection(
                            "driving direction not consistent when trying to make connection between roads:"
                            + str(incoming_road.id)
                            + " and "
                            + str(linked_road.id)
                        )
                else:
                    if np.sign(self._incoming_lane_ids[i]) != np.sign(
                        self._linked_lane_ids[i]
                    ):
                        raise MixingDrivingDirection(
                            "driving direction not consistent when trying to make connection between roads:"
                            + str(incoming_road.id)
                            + " and "
                            + str(linked_road.id)
                        )
            if len(self._linked_lane_ids) != len(self._linked_lane_ids):
                raise NotSameAmountOfLanesError(
                    "the incoming_lane_ids and linked_lane_ids are not the same length"
                )

            if abs(self._incoming_lane_ids[0]) != abs(
                self._linked_lane_ids[0]
            ):
                lane_offset = abs(
                    abs(self._incoming_lane_ids[0])
                    - abs(self._linked_lane_ids[0])
                )

                if incoming_main_road:
                    linked_lane_offset = (
                        np.sign(self._linked_lane_ids[0]) * lane_offset
                    )
                    inc_lane_offset = (
                        -1
                        * np.sign(
                            self._incoming_lane_ids[0]
                            * self._linked_lane_ids[0]
                        )
                        * linked_lane_offset
                    )
                else:
                    inc_lane_offset = (
                        np.sign(self._incoming_lane_ids[0]) * lane_offset
                    )
                    linked_lane_offset = (
                        -1
                        * np.sign(
                            self._incoming_lane_ids[0]
                            * self._linked_lane_ids[0]
                        )
                        * inc_lane_offset
                    )
        if (
            incoming_road.predecessor
            and incoming_road.predecessor.element_id == self.id
        ):
            incoming_road.pred_direct_junction[linked_road.id] = (
                inc_lane_offset
            )
        else:
            incoming_road.succ_direct_junction[linked_road.id] = (
                inc_lane_offset
            )

        if (
            linked_road.predecessor
            and linked_road.predecessor.element_id == self.id
        ):
            linked_road.pred_direct_junction[incoming_road.id] = (
                linked_lane_offset
            )
        else:
            linked_road.succ_direct_junction[incoming_road.id] = (
                linked_lane_offset
            )

        connection = Connection(
            incoming_road.id,
            linked_road.id,
            self._get_contact_point_linked_road(linked_road),
        )
        for i in range(len(self._incoming_lane_ids)):
            connection.add_lanelink(
                self._incoming_lane_ids[i], self._linked_lane_ids[i]
            )
        self.junction.add_connection(connection)

DirectJunctionCreator is a helper class to create custom direct junctions.

Parameters

id : int
The ID of the junction.
name : str
Name of the junction.

Attributes

id : int
The ID of the junction.
junction : Junction
The junction xodr element for the junction.

Methods

add_connection(incoming_road, linked_road, incoming_lane_ids=None, linked_lane_ids=None) Adds a connection between an incoming road and a linked road. If no lane information is provided, it connects all lanes that the two roads have in common.

Initialize the DirectJunctionCreator instance.

Parameters

id : int
The ID of the junction.
name : str
Name of the junction.

Returns

None
 

Methods

def add_connection(self,
incoming_road: Road,
linked_road: Road,
incoming_lane_ids: int | list[int] | None = None,
linked_lane_ids: int | list[int] | None = None)
Expand source code
def add_connection(
    self,
    incoming_road: "Road",
    linked_road: "Road",
    incoming_lane_ids: Optional[Union[int, list[int]]] = None,
    linked_lane_ids: Optional[Union[int, list[int]]] = None,
) -> None:
    """Add a connection between an incoming road and a linked road.

    This method connects two roads using specified lane IDs or
    automatically determines the lanes to connect if no lane IDs are
    provided.

    Parameters
    ----------
    incoming_road : Road
        The incoming road to the junction.
    linked_road : Road
        The linked road to the junction.
    incoming_lane_ids : int or list of int, optional
        The lane ID(s) on the incoming road to connect.
        Default is None.
    linked_lane_ids : int or list of int, optional
        The lane ID(s) on the linked road to connect.
        Default is None.

    Raises
    ------
    MixingDrivingDirection
        If the driving direction is inconsistent between the two
        roads.
    NotSameAmountOfLanesError
        If the number of incoming and linked lane IDs are not the
        same.

    Returns
    -------
    None
    """

    linked_lane_offset = 0
    inc_lane_offset = 0
    incoming_main_road = False
    if incoming_lane_ids == None and linked_lane_ids == None:
        self._get_minimum_lanes_to_connect(incoming_road, linked_road)

    elif incoming_lane_ids is not None and linked_lane_ids is not None:
        if not isinstance(incoming_lane_ids, list):
            self._incoming_lane_ids = [incoming_lane_ids]
        else:
            self._incoming_lane_ids = incoming_lane_ids

        if not isinstance(linked_lane_ids, list):
            self._linked_lane_ids = [linked_lane_ids]

            if abs(linked_lane_ids) == 1:
                incoming_main_road = True
        else:
            self._linked_lane_ids = linked_lane_ids
            if min([abs(x) for x in self._linked_lane_ids]) == 1:
                incoming_main_road = True
        # sanity check
        for i in range(len(self._incoming_lane_ids)):
            if self._get_contact_point_linked_road(
                incoming_road
            ) == self._get_contact_point_linked_road(linked_road):
                if np.sign(self._incoming_lane_ids[i]) == np.sign(
                    self._linked_lane_ids[i]
                ):
                    raise MixingDrivingDirection(
                        "driving direction not consistent when trying to make connection between roads:"
                        + str(incoming_road.id)
                        + " and "
                        + str(linked_road.id)
                    )
            else:
                if np.sign(self._incoming_lane_ids[i]) != np.sign(
                    self._linked_lane_ids[i]
                ):
                    raise MixingDrivingDirection(
                        "driving direction not consistent when trying to make connection between roads:"
                        + str(incoming_road.id)
                        + " and "
                        + str(linked_road.id)
                    )
        if len(self._linked_lane_ids) != len(self._linked_lane_ids):
            raise NotSameAmountOfLanesError(
                "the incoming_lane_ids and linked_lane_ids are not the same length"
            )

        if abs(self._incoming_lane_ids[0]) != abs(
            self._linked_lane_ids[0]
        ):
            lane_offset = abs(
                abs(self._incoming_lane_ids[0])
                - abs(self._linked_lane_ids[0])
            )

            if incoming_main_road:
                linked_lane_offset = (
                    np.sign(self._linked_lane_ids[0]) * lane_offset
                )
                inc_lane_offset = (
                    -1
                    * np.sign(
                        self._incoming_lane_ids[0]
                        * self._linked_lane_ids[0]
                    )
                    * linked_lane_offset
                )
            else:
                inc_lane_offset = (
                    np.sign(self._incoming_lane_ids[0]) * lane_offset
                )
                linked_lane_offset = (
                    -1
                    * np.sign(
                        self._incoming_lane_ids[0]
                        * self._linked_lane_ids[0]
                    )
                    * inc_lane_offset
                )
    if (
        incoming_road.predecessor
        and incoming_road.predecessor.element_id == self.id
    ):
        incoming_road.pred_direct_junction[linked_road.id] = (
            inc_lane_offset
        )
    else:
        incoming_road.succ_direct_junction[linked_road.id] = (
            inc_lane_offset
        )

    if (
        linked_road.predecessor
        and linked_road.predecessor.element_id == self.id
    ):
        linked_road.pred_direct_junction[incoming_road.id] = (
            linked_lane_offset
        )
    else:
        linked_road.succ_direct_junction[incoming_road.id] = (
            linked_lane_offset
        )

    connection = Connection(
        incoming_road.id,
        linked_road.id,
        self._get_contact_point_linked_road(linked_road),
    )
    for i in range(len(self._incoming_lane_ids)):
        connection.add_lanelink(
            self._incoming_lane_ids[i], self._linked_lane_ids[i]
        )
    self.junction.add_connection(connection)

Add a connection between an incoming road and a linked road.

This method connects two roads using specified lane IDs or automatically determines the lanes to connect if no lane IDs are provided.

Parameters

incoming_road : Road
The incoming road to the junction.
linked_road : Road
The linked road to the junction.
incoming_lane_ids : int or list of int, optional
The lane ID(s) on the incoming road to connect. Default is None.
linked_lane_ids : int or list of int, optional
The lane ID(s) on the linked road to connect. Default is None.

Raises

MixingDrivingDirection
If the driving direction is inconsistent between the two roads.
NotSameAmountOfLanesError
If the number of incoming and linked lane IDs are not the same.

Returns

None