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 lanesCreate 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
singleSideattribute 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:listofLaneDeforint- A list of lane definitions for the right side of the road, or an integer specifying a constant number of lanes.
left_lane_def:listofLaneDeforint- 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
Lanesobject 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 roadmarkCreate 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
RoadMarkobject 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 roadmarkCreate 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
RoadMarkobject 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 roadmarkCreate 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
RoadMarkobject 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 roadmarkCreate 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
RoadMarkobject 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 roadmarkCreate 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
RoadMarkobject 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
RoadMarkobject 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 roadmarkCreate 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
RoadMarkobject 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 roadmarkCreate 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
RoadMarkobject 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
scoordinate of the start of the change. s_end:float- The
scoordinate 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:listoffloat, 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:listoffloat, optional- The widths of lanes at the end. Must be empty or the same length
as
n_lanes_end. Default is the same aslane_start_widths.
Attributes
s_start:float- The
scoordinate of the start of the change. s_end:float- The
scoordinate 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:listoffloat- The widths of lanes at the start.
lane_end_widths:listoffloat- The widths of lanes at the end.
Initialize a
LaneDefinstance.Parameters
s_start:float- The
scoordinate of the start of the change. s_end:float- The
scoordinate 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:listoffloat, 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:listoffloat, optional- The widths of lanes at the end. Must be empty or the same
length as
n_lanes_end. Default is the same aslane_start_widths.
Returns
None