Module scenariogeneration.xodr.lane_def

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.

This is a collection methods and classes not related to OpenDRIVE, but relates to automation of lane creations

Functions

def create_lanes_merge_split(right_lane_def: list['LaneDef'] | int,
left_lane_def: list['LaneDef'] | int,
road_length: float,
center_road_mark: RoadMark,
lane_width: float,
lane_width_end: float | None = None) ‑> Lanes
Expand source code
def create_lanes_merge_split(
    right_lane_def: Union[list["LaneDef"], int],
    left_lane_def: Union[list["LaneDef"], int],
    road_length: float,
    center_road_mark: RoadMark,
    lane_width: float,
    lane_width_end: Optional[float] = None,
) -> Lanes:
    """Create lanes for a road with one or more lane merges or splits.

    This function generates the lanes of a road that can contain one or
    more lane merges or splits. It has the following constraints:
    - Left and right merges must occur at the same location (or one per
    lane).
      This will be fixed with the `singleSide` attribute in the future.
    - The lane change is modeled as a 3rd-degree polynomial with zero
      derivatives at both the start and end.

    Note
    ----
    The merges and splits are defined in the road direction, not the
    driving direction.

    Parameters
    ----------
    right_lane_def : list of LaneDef or int
        A list of lane definitions for the right side of the road, or an
        integer specifying a constant number of lanes.
    left_lane_def : list of LaneDef or int
        A list of lane definitions for the left side of the road, or an
        integer specifying a constant number of lanes.
    road_length : float
        The total length of the road.
    center_road_mark : RoadMark
        The roadmark for the center line.
    lane_width : float
        The width of the lanes.
    lane_width_end : float, optional
        The width of the lanes at the end of the road. Default is None.

    Returns
    -------
    Lanes
        A `Lanes` object representing the lanes of the road.
    """

    lanesections = []
    # expand the lane list
    right_lane, left_lane = _create_lane_lists(
        right_lane_def, left_lane_def, road_length, lane_width
    )

    # create the lanesections needed
    for ls in range(len(left_lane)):
        lc = Lane(a=0)
        lc.add_roadmark(copy.deepcopy(center_road_mark))
        lsec = LaneSection(left_lane[ls].s_start, lc)
        # do the right lanes
        for i in range(
            max(right_lane[ls].n_lanes_start, right_lane[ls].n_lanes_end)
        ):
            # add broken roadmarks for all lanes, except for the outer lane where a solid line is added
            if (
                i
                == max(
                    right_lane[ls].n_lanes_start, right_lane[ls].n_lanes_end
                )
                - 1
            ):
                rm = std_roadmark_solid()
            else:
                rm = std_roadmark_broken()

            # check if the number of lanes should change or not
            if (
                right_lane[ls].n_lanes_start > right_lane[ls].n_lanes_end
                and i == np.abs(right_lane[ls].sub_lane) - 1
            ):
                # lane merge
                coeff = get_coeffs_for_poly3(
                    right_lane[ls].s_end - right_lane[ls].s_start,
                    right_lane[ls].lane_start_widths[i],
                    False,
                    right_lane[ls].lane_end_widths[i],
                )
                rightlane = Lane(
                    a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3]
                )
                rightlane.add_roadmark(rm)
            elif (
                right_lane[ls].n_lanes_start < right_lane[ls].n_lanes_end
                and i == np.abs(right_lane[ls].sub_lane) - 1
            ):
                # lane split
                coeff = get_coeffs_for_poly3(
                    right_lane[ls].s_end - right_lane[ls].s_start,
                    right_lane[ls].lane_start_widths[i],
                    True,
                    right_lane[ls].lane_end_widths[i],
                )
                rightlane = Lane(
                    a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3]
                )
                rightlane.add_roadmark(rm)
            elif (lane_width_end is not None) and (
                lane_width != lane_width_end
            ):
                coeff = get_coeffs_for_poly3(
                    right_lane[ls].s_end - right_lane[ls].s_start,
                    lane_width,
                    False,
                    lane_width_end=lane_width_end,
                )
                rightlane = Lane(
                    a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3]
                )
                rightlane.add_roadmark(rm)
            elif right_lane[ls].lane_start_widths:
                coeff = get_coeffs_for_poly3(
                    right_lane[ls].s_end - right_lane[ls].s_start,
                    right_lane[ls].lane_start_widths[i],
                    False,
                    lane_width_end=right_lane[ls].lane_end_widths[i],
                )
                rightlane = Lane(
                    a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3]
                )
                rightlane.add_roadmark(rm)
            else:
                rightlane = Lane(lane_width)
                rightlane.add_roadmark(rm)
            lsec.add_right_lane(rightlane)

        # do the left lanes
        for i in range(
            max(left_lane[ls].n_lanes_start, left_lane[ls].n_lanes_end)
        ):
            # add broken roadmarks for all lanes, except for the outer lane where a solid line is added
            if (
                i
                == max(left_lane[ls].n_lanes_start, left_lane[ls].n_lanes_end)
                - 1
            ):
                rm = std_roadmark_solid()
            else:
                rm = std_roadmark_broken()

            # check if the number of lanes should change or not
            if (
                left_lane[ls].n_lanes_start < left_lane[ls].n_lanes_end
                and i == left_lane[ls].sub_lane - 1
            ):
                # lane split
                coeff = get_coeffs_for_poly3(
                    left_lane[ls].s_end - left_lane[ls].s_start,
                    left_lane[ls].lane_start_widths[i],
                    True,
                    left_lane[ls].lane_end_widths[i],
                )
                leftlane = Lane(a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3])
                leftlane.add_roadmark(rm)
            elif (
                left_lane[ls].n_lanes_start > left_lane[ls].n_lanes_end
                and i == left_lane[ls].sub_lane - 1
            ):
                # lane merge
                coeff = get_coeffs_for_poly3(
                    left_lane[ls].s_end - left_lane[ls].s_start,
                    left_lane[ls].lane_start_widths[i],
                    False,
                    left_lane[ls].lane_end_widths[i],
                )
                leftlane = Lane(a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3])
                leftlane.add_roadmark(rm)
            elif (lane_width_end is not None) and (
                lane_width != lane_width_end
            ):
                coeff = get_coeffs_for_poly3(
                    left_lane[ls].s_end - left_lane[ls].s_start,
                    lane_width,
                    False,
                    lane_width_end=lane_width_end,
                )
                leftlane = Lane(a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3])
                leftlane.add_roadmark(rm)
            elif left_lane[ls].lane_start_widths:
                coeff = get_coeffs_for_poly3(
                    left_lane[ls].s_end - left_lane[ls].s_start,
                    left_lane[ls].lane_start_widths[i],
                    False,
                    lane_width_end=left_lane[ls].lane_end_widths[i],
                )
                leftlane = Lane(a=coeff[0], b=coeff[1], c=coeff[2], d=coeff[3])
                leftlane.add_roadmark(rm)
            else:
                leftlane = Lane(lane_width)
                leftlane.add_roadmark(rm)
            lsec.add_left_lane(leftlane)

        lanesections.append(lsec)

    # create the lane linker to link the lanes correctly
    lanelinker = LaneLinker()
    for i in range(1, len(right_lane)):
        if right_lane[i].n_lanes_end > right_lane[i].n_lanes_start:
            # lane split
            for j in range(0, right_lane[i - 1].n_lanes_end + 1):
                # adjust for the new lane
                if right_lane[i].sub_lane < -(j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].rightlanes[j],
                        lanesections[i].rightlanes[j],
                    )
                elif right_lane[i].sub_lane > -(j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].rightlanes[j - 1],
                        lanesections[i].rightlanes[j],
                    )
        elif right_lane[i - 1].n_lanes_end < right_lane[i - 1].n_lanes_start:
            # lane merge
            for j in range(0, right_lane[i - 1].n_lanes_end + 1):
                # adjust for the lost lane
                if right_lane[i - 1].sub_lane < -(j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].rightlanes[j],
                        lanesections[i].rightlanes[j],
                    )
                elif right_lane[i - 1].sub_lane > -(j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].rightlanes[j],
                        lanesections[i].rightlanes[j - 1],
                    )

        else:
            # same number of lanes, just add the links
            for j in range(right_lane[i - 1].n_lanes_end):
                lanelinker.add_link(
                    lanesections[i - 1].rightlanes[j],
                    lanesections[i].rightlanes[j],
                )

    for i in range(1, len(left_lane)):
        if left_lane[i].n_lanes_end > left_lane[i].n_lanes_start:
            # lane split
            for j in range(0, left_lane[i - 1].n_lanes_end + 1):
                # adjust for the new lane
                if left_lane[i].sub_lane < (j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].leftlanes[j - 1],
                        lanesections[i].leftlanes[j],
                    )
                elif left_lane[i].sub_lane > (j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].leftlanes[j],
                        lanesections[i].leftlanes[j],
                    )
        elif left_lane[i - 1].n_lanes_end < left_lane[i - 1].n_lanes_start:
            # lane merge
            for j in range(0, left_lane[i - 1].n_lanes_end + 1):
                # adjust for the lost lane
                if left_lane[i - 1].sub_lane < (j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].leftlanes[j],
                        lanesections[i].leftlanes[j - 1],
                    )
                elif left_lane[i - 1].sub_lane > (j + 1):
                    lanelinker.add_link(
                        lanesections[i - 1].leftlanes[j],
                        lanesections[i].leftlanes[j],
                    )

        else:
            # same number of lanes, just add the links
            for j in range(left_lane[i - 1].n_lanes_end):
                lanelinker.add_link(
                    lanesections[i - 1].leftlanes[j],
                    lanesections[i].leftlanes[j],
                )

    # Add the lanesections to the lanes struct together the lanelinker
    lanes = Lanes()
    for ls in lanesections:
        lanes.add_lanesection(ls, lanelinker)
    return lanes

Create lanes for a road with one or more lane merges or splits.

This function generates the lanes of a road that can contain one or more lane merges or splits. It has the following constraints: - Left and right merges must occur at the same location (or one per lane). This will be fixed with the singleSide attribute in the future. - The lane change is modeled as a 3rd-degree polynomial with zero derivatives at both the start and end.

Note

The merges and splits are defined in the road direction, not the driving direction.

Parameters

right_lane_def : list of LaneDef or int
A list of lane definitions for the right side of the road, or an integer specifying a constant number of lanes.
left_lane_def : list of LaneDef or int
A list of lane definitions for the left side of the road, or an integer specifying a constant number of lanes.
road_length : float
The total length of the road.
center_road_mark : RoadMark
The roadmark for the center line.
lane_width : float
The width of the lanes.
lane_width_end : float, optional
The width of the lanes at the end of the road. Default is None.

Returns

Lanes
A Lanes object representing the lanes of the road.
def std_roadmark_broken() ‑> RoadMark
Expand source code
def std_roadmark_broken() -> RoadMark:
    """Create a standard broken roadmark.

    This function generates a broken roadmark with a default width of 0.2
    and a specific road line pattern.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a broken roadmark.
    """
    roadmark = RoadMark(RoadMarkType.broken, 0.2)
    roadmark.add_specific_road_line(RoadLine(0.15, 3, 9, 0, 0))
    return roadmark

Create a standard broken roadmark.

This function generates a broken roadmark with a default width of 0.2 and a specific road line pattern.

Returns

RoadMark
A RoadMark object representing a broken roadmark.
def std_roadmark_broken_broken() ‑> RoadMark
Expand source code
def std_roadmark_broken_broken() -> RoadMark:
    """Create a standard broken-broken roadmark.

    This function generates a broken-broken roadmark with two parallel
    broken lines, each with a default width of 0.2.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a broken-broken roadmark.
    """
    roadmark = RoadMark(RoadMarkType.broken_broken)
    roadmark.add_specific_road_line(RoadLine(0.2, 3, 3, 0.2, 0))
    roadmark.add_specific_road_line(RoadLine(0.2, 3, 3, -0.2, 0))
    return roadmark

Create a standard broken-broken roadmark.

This function generates a broken-broken roadmark with two parallel broken lines, each with a default width of 0.2.

Returns

RoadMark
A RoadMark object representing a broken-broken roadmark.
def std_roadmark_broken_long_line() ‑> RoadMark
Expand source code
def std_roadmark_broken_long_line() -> RoadMark:
    """Create a standard broken roadmark with a long line pattern.

    This function generates a broken roadmark with a default width of 0.2
    and a specific road line pattern where the line is longer than the
    gap.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a broken roadmark with a long
        line.
    """
    roadmark = RoadMark(RoadMarkType.broken, 0.2)
    roadmark.add_specific_road_line(RoadLine(0.15, 9, 3, 0, 0))
    return roadmark

Create a standard broken roadmark with a long line pattern.

This function generates a broken roadmark with a default width of 0.2 and a specific road line pattern where the line is longer than the gap.

Returns

RoadMark
A RoadMark object representing a broken roadmark with a long line.
def std_roadmark_broken_solid() ‑> RoadMark
Expand source code
def std_roadmark_broken_solid() -> RoadMark:
    """Create a standard broken-solid roadmark.

    This function generates a broken-solid roadmark with two paralle
    lines: one broken and one solid, each with a default width of 0.2.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a broken-solid roadmark.
    """
    roadmark = RoadMark(RoadMarkType.broken_solid)
    roadmark.add_specific_road_line(RoadLine(0.2, 3, 3, 0.2, 0))
    roadmark.add_specific_road_line(RoadLine(0.2, 0, 0, -0.2, 0))
    return roadmark

Create a standard broken-solid roadmark.

This function generates a broken-solid roadmark with two paralle lines: one broken and one solid, each with a default width of 0.2.

Returns

RoadMark
A RoadMark object representing a broken-solid roadmark.
def std_roadmark_broken_tight() ‑> RoadMark
Expand source code
def std_roadmark_broken_tight() -> RoadMark:
    """Create a standard broken roadmark with a tight line pattern.

    This function generates a broken roadmark with a default width of 0.2
    and a specific road line pattern where the line and gap are of equal
    length.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a broken roadmark with a tight
        line pattern.
    """
    roadmark = RoadMark(RoadMarkType.broken, 0.2)
    roadmark.add_specific_road_line(RoadLine(0.15, 3, 3, 0, 0))
    return roadmark

Create a standard broken roadmark with a tight line pattern.

This function generates a broken roadmark with a default width of 0.2 and a specific road line pattern where the line and gap are of equal length.

Returns

RoadMark
A RoadMark object representing a broken roadmark with a tight line pattern.
def std_roadmark_solid() ‑> RoadMark
Expand source code
def std_roadmark_solid() -> RoadMark:
    """Create a standard solid roadmark.

    This function generates a solid roadmark with a default width of 0.2.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a solid roadmark.
    """
    return RoadMark(RoadMarkType.solid, 0.2)

Create a standard solid roadmark.

This function generates a solid roadmark with a default width of 0.2.

Returns

RoadMark
A RoadMark object representing a solid roadmark.
def std_roadmark_solid_broken() ‑> RoadMark
Expand source code
def std_roadmark_solid_broken() -> RoadMark:
    """Create a standard solid-broken roadmark.

    This function generates a solid-broken roadmark with two parallel
    lines: one solid and one broken, each with a default width of 0.2.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a solid-broken roadmark.
    """
    roadmark = RoadMark(RoadMarkType.solid_broken)
    roadmark.add_specific_road_line(RoadLine(0.2, 0, 0, 0.2, 0))
    roadmark.add_specific_road_line(RoadLine(0.2, 3, 3, -0.2, 0))
    return roadmark

Create a standard solid-broken roadmark.

This function generates a solid-broken roadmark with two parallel lines: one solid and one broken, each with a default width of 0.2.

Returns

RoadMark
A RoadMark object representing a solid-broken roadmark.
def std_roadmark_solid_solid() ‑> RoadMark
Expand source code
def std_roadmark_solid_solid() -> RoadMark:
    """Create a standard solid-solid roadmark.

    This function generates a solid-solid roadmark with two parallel solid
    lines, each with a default width of 0.2.

    Returns
    -------
    RoadMark
        A `RoadMark` object representing a solid-solid roadmark.
    """
    roadmark = RoadMark(RoadMarkType.solid_solid)
    roadmark.add_specific_road_line(RoadLine(0.2, 0, 0, 0.2, 0))
    roadmark.add_specific_road_line(RoadLine(0.2, 0, 0, -0.2, 0))
    return roadmark

Create a standard solid-solid roadmark.

This function generates a solid-solid roadmark with two parallel solid lines, each with a default width of 0.2.

Returns

RoadMark
A RoadMark object representing a solid-solid roadmark.

Classes

class LaneDef (s_start: float,
s_end: float,
n_lanes_start: int,
n_lanes_end: int,
sub_lane: int | None = None,
lane_start_widths: list[float] | None = [],
lane_end_widths: list[float] | None = [])
Expand source code
class LaneDef:
    """Helper class to define a lane merge or split.

    This class is not part of the OpenDRIVE standard but is used as a
    helper for the xodr module to handle lane merges or splits.

    Parameters
    ----------
    s_start : float
        The `s` coordinate of the start of the change.
    s_end : float
        The `s` coordinate of the end of the change.
    n_lanes_start : int
        The number of lanes at `s_start`.
    n_lanes_end : int
        The number of lanes at `s_end`.
    sub_lane : int, optional
        The lane that should be created (split) or removed (merge).
        Default is None.
    lane_start_widths : list of float, optional
        The widths of lanes at the start. Must be empty or the same length
        as `n_lanes_start`. Default is an empty list.
    lane_end_widths : list of float, optional
        The widths of lanes at the end. Must be empty or the same length
        as `n_lanes_end`. Default is the same as `lane_start_widths`.

    Attributes
    ----------
    s_start : float
        The `s` coordinate of the start of the change.
    s_end : float
        The `s` coordinate of the end of the change.
    n_lanes_start : int
        The number of lanes at `s_start`.
    n_lanes_end : int
        The number of lanes at `s_end`.
    sub_lane : int
        The lane that should be created (split) or removed (merge).
    lane_start_widths : list of float
        The widths of lanes at the start.
    lane_end_widths : list of float
        The widths of lanes at the end.
    """

    def __init__(
        self,
        s_start: float,
        s_end: float,
        n_lanes_start: int,
        n_lanes_end: int,
        sub_lane: Optional[int] = None,
        lane_start_widths: Optional[list[float]] = [],
        lane_end_widths: Optional[list[float]] = [],
    ) -> None:
        """Initialize a `LaneDef` instance.

        Parameters
        ----------
        s_start : float
            The `s` coordinate of the start of the change.
        s_end : float
            The `s` coordinate of the end of the change.
        n_lanes_start : int
            The number of lanes at `s_start`.
        n_lanes_end : int
            The number of lanes at `s_end`.
        sub_lane : int, optional
            The lane that should be created (split) or removed (merge).
            Default is None.
        lane_start_widths : list of float, optional
            The widths of lanes at the start. Must be empty or the same
            length as `n_lanes_start`. Default is an empty list.
        lane_end_widths : list of float, optional
            The widths of lanes at the end. Must be empty or the same
            length as `n_lanes_end`. Default is the same as
            `lane_start_widths`.

        Returns
        -------
        None
        """
        self.s_start = s_start
        self.s_end = s_end
        self.n_lanes_start = n_lanes_start
        self.n_lanes_end = n_lanes_end
        self.sub_lane = sub_lane
        self.lane_start_widths = lane_start_widths
        if lane_end_widths == []:
            self.lane_end_widths = self.lane_start_widths.copy()
        else:
            self.lane_end_widths = lane_end_widths

    def _adjust_lane_widths(self) -> None:
        """Adjust lane widths for merges or splits.

        This method ensures that the lane widths are consistent when a
        laneis created (split) or removed (merge).

        Returns
        -------
        None
        """
        if self.sub_lane:
            if (
                self.lane_end_widths
                and len(self.lane_end_widths) < self.n_lanes_start
            ):
                # mergeo
                self.lane_end_widths.insert(abs(self.sub_lane) - 1, 0)
            elif (
                self.lane_start_widths
                and len(self.lane_start_widths) < self.n_lanes_end
            ):
                # split
                self.lane_start_widths.insert(abs(self.sub_lane) - 1, 0)
        # TODO: add some checks here?

Helper class to define a lane merge or split.

This class is not part of the OpenDRIVE standard but is used as a helper for the xodr module to handle lane merges or splits.

Parameters

s_start : float
The s coordinate of the start of the change.
s_end : float
The s coordinate of the end of the change.
n_lanes_start : int
The number of lanes at s_start.
n_lanes_end : int
The number of lanes at s_end.
sub_lane : int, optional
The lane that should be created (split) or removed (merge). Default is None.
lane_start_widths : list of float, optional
The widths of lanes at the start. Must be empty or the same length as n_lanes_start. Default is an empty list.
lane_end_widths : list of float, optional
The widths of lanes at the end. Must be empty or the same length as n_lanes_end. Default is the same as lane_start_widths.

Attributes

s_start : float
The s coordinate of the start of the change.
s_end : float
The s coordinate of the end of the change.
n_lanes_start : int
The number of lanes at s_start.
n_lanes_end : int
The number of lanes at s_end.
sub_lane : int
The lane that should be created (split) or removed (merge).
lane_start_widths : list of float
The widths of lanes at the start.
lane_end_widths : list of float
The widths of lanes at the end.

Initialize a LaneDef instance.

Parameters

s_start : float
The s coordinate of the start of the change.
s_end : float
The s coordinate of the end of the change.
n_lanes_start : int
The number of lanes at s_start.
n_lanes_end : int
The number of lanes at s_end.
sub_lane : int, optional
The lane that should be created (split) or removed (merge). Default is None.
lane_start_widths : list of float, optional
The widths of lanes at the start. Must be empty or the same length as n_lanes_start. Default is an empty list.
lane_end_widths : list of float, optional
The widths of lanes at the end. Must be empty or the same length as n_lanes_end. Default is the same as lane_start_widths.

Returns

None