Module scenariogeneration.xodr.lane

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.

Expand source code
"""
  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.

"""

from operator import index
import xml.etree.ElementTree as ET
from ..helpers import enum2str
from .enumerations import (
    LaneType,
    LaneChange,
    RoadMarkWeight,
    RoadMarkColor,
    RoadMarkType,
    MarkRule,
    ContactPoint,
)
from .exceptions import ToManyOptionalArguments
from .utils import XodrBase
from .links import _Links, _Link
import numpy as np


class Lanes(XodrBase):
    """creates the Lanes element of opendrive


    Attributes
    ----------
        lane_sections (list of LaneSection): a list of all lanesections

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        add_lanesection(lanesection)
            adds a lane section to Lanes

        add_laneoffset(laneoffset)
            adds a lane offset to Lanes
    """

    def __init__(self):
        super().__init__()
        """initalize Lanes"""
        self.lanesections = []
        self.laneoffsets = []
        self.roadmarks_adjusted = False

    def __eq__(self, other):
        if isinstance(other, Lanes) and super().__eq__(other):
            if (
                self.laneoffsets == other.laneoffsets
                and self.lanesections == other.lanesections
            ):
                return True
        return False

    def add_lanesection(self, lanesection, lanelinks=None):
        """creates the Lanes element of opendrive

        Parameters
        ----------
            lanesection (LaneSection): a LaneSection to add

            lanelink (LaneLinker): (optional) a LaneLink to add

        """
        # add links to the lanes
        if lanelinks:
            # loop over all links
            if not isinstance(lanelinks, list):
                lanelinks = [lanelinks]
            for lanelink in lanelinks:
                for link in lanelink.links:
                    # check if link already added
                    if not link.used:
                        link.predecessor.add_link("successor", link.successor.lane_id)
                        link.successor.add_link("predecessor", link.predecessor.lane_id)
                        link.used = True

        self.lanesections.append(lanesection)
        return self

    def add_laneoffset(self, laneoffset):
        """adds a lane offset to Lanes

        Parameters
        ----------
            laneoffset (LaneOffset): a LaneOffset to add
        """
        if not isinstance(laneoffset, LaneOffset):
            raise TypeError(
                "add_laneoffset requires a LaneOffset as input, not "
                + str(type(laneoffset))
            )
        self.laneoffsets.append(laneoffset)
        return self

    def _check_valid_mark_type(self, lane):
        """simple checker if the lanemark can be adjusted

        Parameters
        ----------
            lane (Lane): the lane which roadmark should be checked
        """
        return (
            lane.roadmark[0].marking_type == RoadMarkType.broken
            or lane.roadmark[0].marking_type == RoadMarkType.broken_broken
        )

    def _adjust_for_missing_line_offset(self, roadmark):
        """adds an explicit line if soofset is less than 0 (for adjusting from start) or longer than the space between lines (for adjusting from end)

        Parameters
        ----------
            roadmark (RoadMark): the roadmark to be adjusted
        """
        for line in roadmark._line:
            if line.soffset < 0 or line.soffset > line.length + line.soffset:
                roadmark.add_explicit_road_line(
                    ExplicitRoadLine(
                        line.width,
                        line.length + line.soffset,
                        line.toffset,
                        0,
                        line.rule,
                    )
                )
            elif line.soffset > line.space:
                roadmark.add_explicit_road_line(
                    ExplicitRoadLine(
                        line.width,
                        line.soffset - line.space,
                        line.toffset,
                        0,
                        line.rule,
                    )
                )
            if line.soffset < 0:
                line.shift_soffset()

    def _validity_check_for_roadmark_adjustment(self):
        """does some simple checks if the the different lanes can be adjusted"""
        self._right_lanes_adjustable = len(self.lanesections[0].rightlanes) > 0
        self._left_lanes_adjustable = len(self.lanesections[0].leftlanes) > 0
        self._center_lane_adjustable = True
        for ls in range(len(self.lanesections) - 1):
            if len(self.lanesections[ls].centerlane.roadmark) != 1:
                self.center_lane_adjustable = False
            if (
                self.lanesections[ls].centerlane.roadmark
                != self.lanesections[ls + 1].centerlane.roadmark
            ):
                self.center_lane_adjustable = False
            if (
                self.lanesections[ls].centerlane.roadmark[0].marking_type
                != RoadMarkType.broken
                and self.lanesections[ls].centerlane.roadmark[0].marking_type
                != RoadMarkType.broken_broken
            ):
                self.center_lane_adjustable = False

            for rl in range(len(self.lanesections[ls].rightlanes)):
                if self._right_lanes_adjustable:
                    if len(self.lanesections[ls].rightlanes[rl].roadmark) != 1:
                        self._right_lanes_adjustable = False
            for ll in range(len(self.lanesections[ls].leftlanes)):
                if self._left_lanes_adjustable:
                    if len(self.lanesections[ls].leftlanes[ll].roadmark) != 1:
                        self._left_lanes_adjustable = False

    def _get_previous_remainder(
        self,
        connected_lane_section,
        i_line,
        lane_side,
        contact_point,
        lane_index,
        lane_section_index,
        start_or_end,
    ):
        """_get_previous_remainder is a helper method to get the remainder of a lanemarking of a connecting lane section (for lenght adjustment)

        Parameters
        ----------
            connected_lane_section (LaneSection): connected lane section (on another road)

            i_line (int): index of the line (roadmark._line)

            lane_side (str): "left", "right", or "center" describing what lane is of interest

            contact_point (ContactPoint): contact point of the connected_lane_section

            lane_index (int): the lane index of the wanted lane

            lane_section_index (int): index of the lane section

            start_or_end (str): if the adjustment is done from the end or from the start of the road

        Return
        ------
            float: remainder of the previous lanesection

        """
        active_lane_sec = self.lanesections[lane_section_index]
        neighbor_lane_sec = None
        if start_or_end == "end":
            on_edge = lane_section_index == len(self.lanesections) - 1
            connection = "successor"
            if not on_edge:
                neighbor_lane_sec = self.lanesections[lane_section_index + 1]
        else:
            on_edge = lane_section_index == 0
            connection = "predecessor"
            if not on_edge:
                neighbor_lane_sec = self.lanesections[lane_section_index - 1]

        linked_lane_id = 0
        found_linked_lane_id = None
        if lane_side == "right":
            found_linked_lane_id = active_lane_sec.rightlanes[
                lane_index
            ].get_linked_lane_id(connection)
            if neighbor_lane_sec:
                neighboring_lane = neighbor_lane_sec.rightlanes[linked_lane_id]
        elif lane_side == "left":
            found_linked_lane_id = active_lane_sec.leftlanes[
                lane_index
            ].get_linked_lane_id(connection)
            if neighbor_lane_sec:
                neighboring_lane = neighbor_lane_sec.leftlanes[linked_lane_id]
        else:  # center
            if neighbor_lane_sec:
                neighboring_lane = neighbor_lane_sec.centerlane
        if found_linked_lane_id:
            linked_lane_id = abs(found_linked_lane_id) - 1

        prev_remainder = 0
        if on_edge:
            if lane_side == "right":
                if (
                    contact_point == ContactPoint.end
                    and connected_lane_section.rightlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.rightlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        ._remainder
                    )
                elif (
                    contact_point == ContactPoint.start
                    and connected_lane_section.leftlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.leftlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        .soffset
                    )

            if lane_side == "left":
                if (
                    contact_point == ContactPoint.end
                    and connected_lane_section.leftlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.leftlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        ._remainder
                    )
                elif (
                    contact_point == ContactPoint.start
                    and connected_lane_section.rightlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.rightlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        .soffset
                    )

            if (
                lane_side == "center"
                and connected_lane_section.centerlane.roadmark[0]._line
            ):
                if contact_point == ContactPoint.end:
                    prev_remainder = (
                        connected_lane_section.centerlane.roadmark[0]
                        ._line[i_line]
                        ._remainder
                    )
                elif contact_point == ContactPoint.start:
                    prev_remainder = (
                        connected_lane_section.centerlane.roadmark[0]
                        ._line[i_line]
                        .soffset
                    )

        else:
            if start_or_end == "start":
                prev_remainder = neighboring_lane.roadmark[0]._line[i_line]._remainder
            else:
                prev_remainder = neighboring_lane.roadmark[0]._line[i_line].soffset
        return prev_remainder

    def _get_seg_length(self, total_road_length, lane_section_index):
        """_get_seg_length is a helper method to figure out how long a lane section is

        Parameters
        ----------
            total_road_length (float): total length of the road

            lane_section_index (int): the index of the wanted lanesection

        Returns
        -------
            float: length of the lanesection

        """
        if len(self.lanesections) == 1:
            seg_length = total_road_length
        elif lane_section_index == 0:
            seg_length = self.lanesections[1].s
        elif lane_section_index == len(self.lanesections) - 1:
            seg_length = total_road_length - self.lanesections[lane_section_index].s
        else:
            seg_length = (
                self.lanesections[lane_section_index + 1].s
                - self.lanesections[lane_section_index].s
            )
        return seg_length

    def adjust_road_marks_from_start(
        self,
        total_road_length,
        connected_lane_section=None,
        contact_point=ContactPoint.end,
    ):
        """Adjusts road marks from the start of the road, based on the connected lane section.
        If connected_lane_section is not provided, the last roadmark will be placed with zero
        distance to the start of the road

        Parameters
        ----------
            total_road_length (float): total length of the road

            connected_lane_section (LaneSection): the lane section connected to the road
                Default: None

            contact_point (ContactPoint)
                Default: ContactPoint.end
        """
        if not self.roadmarks_adjusted:
            self._validity_check_for_roadmark_adjustment()
            self.roadmarks_adjusted = True

            def set_zero_offset_to_lines(lane, seg_length):
                for i_line in range(len(lane.roadmark[0]._line)):
                    lane.roadmark[0]._line[i_line].adjust_remainder(
                        seg_length, soffset=0
                    )

            for ls in range(0, len(self.lanesections)):
                seg_length = self._get_seg_length(total_road_length, ls)
                if self._right_lanes_adjustable:
                    for rl in range(len(self.lanesections[ls].rightlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].rightlanes[rl]
                        ):
                            if ls == 0 and connected_lane_section is None:
                                set_zero_offset_to_lines(
                                    self.lanesections[ls].rightlanes[rl], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .rightlanes[rl]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "right",
                                        contact_point,
                                        rl,
                                        ls,
                                        "start",
                                    )
                                    self.lanesections[ls].rightlanes[rl].roadmark[
                                        0
                                    ]._line[i_line].adjust_remainder(
                                        seg_length, previous_remainder=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].rightlanes[rl].roadmark[0]
                                )
                if self._left_lanes_adjustable:
                    for ll in range(len(self.lanesections[ls].leftlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].leftlanes[ll]
                        ):
                            if ls == 0 and connected_lane_section is None:
                                set_zero_offset_to_lines(
                                    self.lanesections[ls].leftlanes[ll], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .leftlanes[ll]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "left",
                                        contact_point,
                                        ll,
                                        ls,
                                        "start",
                                    )
                                    self.lanesections[ls].leftlanes[ll].roadmark[
                                        0
                                    ]._line[i_line].adjust_remainder(
                                        seg_length, previous_remainder=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].leftlanes[ll].roadmark[0]
                                )
                if self._center_lane_adjustable:
                    if self._check_valid_mark_type(self.lanesections[ls].centerlane):
                        if ls == 0 and connected_lane_section is None:
                            set_zero_offset_to_lines(
                                self.lanesections[ls].centerlane, seg_length
                            )
                        else:
                            for i_line in range(
                                len(self.lanesections[ls].centerlane.roadmark[0]._line)
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "center",
                                    contact_point,
                                    None,
                                    ls,
                                    "start",
                                )
                                self.lanesections[ls].centerlane.roadmark[0]._line[
                                    i_line
                                ].adjust_remainder(
                                    seg_length, previous_remainder=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].centerlane.roadmark[0]
                            )

    def adjust_road_marks_from_end(
        self,
        total_road_length,
        connected_lane_section=None,
        contact_point=ContactPoint.end,
    ):
        """Adjusts road marks from the end of the road, based on the connected lane section.
        If connected_lane_section is not provided, the last roadmark will be placed with zero
        distance to the end of the road

        Parameters
        ----------
            total_road_length (float): total length of the road

            connected_lane_section (LaneSection): the lane section connected to the road
                Default: None

            contact_point (ContactPoint)
                Default: ContactPoint.end
        """
        if not self.roadmarks_adjusted:
            self._validity_check_for_roadmark_adjustment()
            self.roadmarks_adjusted = True

            def set_zero_remainder_to_lines(lane, seg_length):
                for i_line in range(len(lane.roadmark[0]._line)):
                    lane.roadmark[0]._line[i_line].adjust_soffset(
                        seg_length, remainder=0
                    )

            for ls in range(len(self.lanesections) - 1, -1, -1):
                seg_length = self._get_seg_length(total_road_length, ls)
                if self._right_lanes_adjustable:
                    for rl in range(len(self.lanesections[ls].rightlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].rightlanes[rl]
                        ):
                            if (
                                ls == len(self.lanesections) - 1
                                and connected_lane_section is None
                            ):
                                set_zero_remainder_to_lines(
                                    self.lanesections[ls].rightlanes[rl], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .rightlanes[rl]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "right",
                                        contact_point,
                                        rl,
                                        ls,
                                        "end",
                                    )
                                    self.lanesections[ls].rightlanes[rl].roadmark[
                                        0
                                    ]._line[i_line].adjust_soffset(
                                        seg_length, previous_offset=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].rightlanes[rl].roadmark[0]
                                )
                if self._left_lanes_adjustable:
                    for ll in range(len(self.lanesections[ls].leftlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].leftlanes[ll]
                        ):
                            if (
                                ls == len(self.lanesections) - 1
                                and connected_lane_section is None
                            ):
                                set_zero_remainder_to_lines(
                                    self.lanesections[ls].leftlanes[ll], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .leftlanes[ll]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "left",
                                        contact_point,
                                        ll,
                                        ls,
                                        "end",
                                    )
                                    self.lanesections[ls].leftlanes[ll].roadmark[
                                        0
                                    ]._line[i_line].adjust_soffset(
                                        seg_length, previous_offset=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].leftlanes[ll].roadmark[0]
                                )

                if self._center_lane_adjustable:
                    if self._check_valid_mark_type(self.lanesections[ls].centerlane):
                        if (
                            ls == len(self.lanesections) - 1
                            and connected_lane_section is None
                        ):
                            set_zero_remainder_to_lines(
                                self.lanesections[ls].centerlane, seg_length
                            )
                        else:
                            for i_line in range(
                                len(self.lanesections[ls].centerlane.roadmark[0]._line)
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "center",
                                    contact_point,
                                    None,
                                    ls,
                                    "end",
                                )
                                self.lanesections[ls].centerlane.roadmark[0]._line[
                                    i_line
                                ].adjust_soffset(
                                    seg_length, previous_offset=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].centerlane.roadmark[0]
                            )

    def get_element(self):
        """returns the elementTree of Lanes"""
        element = ET.Element("lanes")
        self._add_additional_data_to_element(element)
        for l in self.laneoffsets:
            element.append(l.get_element())
        for l in self.lanesections:
            element.append(l.get_element())
        return element


class LaneOffset(XodrBase):
    """the LaneOffset class defines an overall lateral offset along the road, described as a third degree polynomial

    Parameters
    ----------
        s (float): s start coordinate of the elevation

        a (float): a coefficient of the polynomial

        b (float): b coefficient of the polynomial

        c (float): c coefficient of the polynomial

        d (float): d coefficient of the polynomial

    Attributes
    ----------
        s (float): s start coordinate of the elevation

        a (float): a coefficient of the polynomial

        b (float): b coefficient of the polynomial

        c (float): c coefficient of the polynomial

        d (float): d coefficient of the polynomial

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns the attributes of the class

    """

    def __init__(self, s=0, a=0, b=0, c=0, d=0):
        """initalize the LaneOffset class

        Parameters
        ----------
            s (float): s start coordinate of the LaneOffset

            a (float): a coefficient of the polynomial

            b (float): b coefficient of the polynomial

            c (float): c coefficient of the polynomial

            d (float): d coefficient of the polynomial

        """
        super().__init__()
        self.s = s
        self.a = a
        self.b = b
        self.c = c
        self.d = d

    def __eq__(self, other):
        if isinstance(other, LaneOffset) and super().__eq__(other):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def get_attributes(self):
        """returns the attributes of the LaneOffset"""

        retdict = {}
        retdict["s"] = str(self.s)
        retdict["a"] = str(self.a)
        retdict["b"] = str(self.b)
        retdict["c"] = str(self.c)
        retdict["d"] = str(self.d)
        return retdict

    def get_element(self):
        """returns the elementTree of the LaneOffset"""
        element = ET.Element("laneOffset", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        return element


class LaneSection(XodrBase):
    """Creates the LaneSection element of opendrive

    Parameters
    ----------
        s (float): start of lanesection

        centerlane (Lane): the centerline of the road

    Attributes
    ----------
        s (float): start of lanesection

        centerlane (Lane): the centerline of the road

        leftlanes (list of Lane): the lanes left to the center

        rightlanes (list of Lane): the lanes right to the center

    Methods
    -------
        get_element()
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of class

        add_left_lane(Lane)
            adds a new lane to the left

        add_right_lane(Lane)
            adds a new lane to the right
    """

    def __init__(self, s, centerlane):
        """initalize the LaneSection

        Parameters
        ----------
            s (float): start of lanesection

            centerlane (Lane): the centerline of the road
        """
        super().__init__()
        self.s = s
        self.centerlane = centerlane
        self.centerlane._set_lane_id(0)
        self.leftlanes = []
        self.rightlanes = []
        self._left_id = 1
        self._right_id = -1

    def __eq__(self, other):
        if isinstance(other, LaneSection) and super().__eq__(other):
            if (
                self.get_attributes() == other.get_attributes()
                and self.centerlane == other.centerlane
                and self.leftlanes == other.leftlanes
                and self.rightlanes == other.rightlanes
            ):
                return True
        return False

    def add_left_lane(self, lane):
        """adds a lane to the left of the center, add from center outwards

        Parameters
        ----------
            lane (Lane): the lane to add
        """
        lane._set_lane_id(self._left_id)
        self._left_id += 1
        self.leftlanes.append(lane)
        return self

    def add_right_lane(self, lane):
        """adds a lane to the right of the center, add from center outwards

        Parameters
        ----------
            lane (Lane): the lane to add
        """
        lane._set_lane_id(self._right_id)
        self._right_id -= 1
        self.rightlanes.append(lane)
        return self

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        retdict["s"] = str(self.s)
        return retdict

    def get_element(self):
        """returns the elementTree of the WorldPostion"""
        element = ET.Element("laneSection", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        if self.leftlanes:
            left = ET.SubElement(element, "left")
            for l in reversed(self.leftlanes):
                left.append(l.get_element())

        center = ET.SubElement(element, "center")
        center.append(self.centerlane.get_element())

        if self.rightlanes:
            right = ET.SubElement(element, "right")
            for l in self.rightlanes:
                right.append(l.get_element())

        return element


class _poly3struct:
    def __init__(self, a=0, b=0, c=0, d=0, soffset=0):
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.soffset = soffset

    def __eq__(self, other):
        if isinstance(other, _poly3struct):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def get_width(self, s):
        width = (
            self.a
            + self.b * (s - self.soffset)
            + self.c * (s - self.soffset) ** 2
            + self.d * (s - self.soffset) ** 3
        )
        return width

    def get_attributes(self):
        polynomialdict = {}
        polynomialdict["a"] = str(self.a)
        polynomialdict["b"] = str(self.b)
        polynomialdict["c"] = str(self.c)
        polynomialdict["d"] = str(self.d)
        polynomialdict["sOffset"] = str(self.soffset)
        return polynomialdict


class Lane(XodrBase):
    """creates a Lane of opendrive

    the inputs are on the following format:
        f(s) = a + b*s + c*s^2 + d*s^3

    Parameters
    ----------

        lane_type (LaneType): type of lane
            Default: LaneType.driving

        a (float): a coefficient
            Default: 0

        b (float): b coefficient
            Default: 0

        c (float): c coefficient
            Default: 0

        d (float): d coefficient
            Default: 0

        soffset (float): soffset of lane
            Default: 0


    Attributes
    ----------
        lane_id (int): id of the lane (automatically assigned by LaneSection)

        lane_type (LaneType): type of lane

        a (float): a coefficient

        b (float): b coefficient

        c (float): c coefficient

        d (float): d coefficient

        soffset (float): soffset of lane

        roadmark (RoadMark): roadmarks related to the lane

        links (_Links): Lane links to the lane

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of class

        add_roadmark(roadmark)
            adds a new roadmark to the lane

        add_lane_width(a, b, c, d, soffset)
            adds an additional width element to the lane
    """

    def __init__(self, lane_type=LaneType.driving, a=0, b=0, c=0, d=0, soffset=0):
        """initalizes the Lane

        Parameters
        ----------

            lane_type (LaneType): type of lane
                Default: LaneType.driving

            a (float): a polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            b (float): b polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            c (float): c polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            d (float): d polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            soffset (float): soffset of lane renamed to s in case of centerlane
                Default: 0

        """
        super().__init__()
        self.lane_id = None
        self.lane_type = lane_type
        self.widths = []
        self.add_lane_width(a, b, c, d, soffset)

        self.soffset = soffset
        # TODO: enable multiple widths records per lane (only then soffset really makes sense! ASAM requires one width record to have sOffset=0)
        self.heights = (
            []
        )  # height entries to elevate the lane independent from the road elevation
        self.roadmark = []
        self.links = _Links()

    def __eq__(self, other):
        if isinstance(other, Lane) and super().__eq__(other):
            if (
                self.links == other.links
                and self.get_attributes() == other.get_attributes()
                and self.widths == other.widths
                and self.heights == other.heights
                and self.roadmark == other.roadmark
            ):
                return True
        return False

        # TODO: add more features to add for lane

    def add_lane_width(self, a=0, b=0, c=0, d=0, soffset=0):
        """adds an additional width element to the lane

        Parameters
        ----------
            a (float): a polynomial coefficient for width
                Default: 0

            b (float): b polynomial coefficient for width
                Default: 0

            c (float): c polynomial coefficient for width
                Default: 0

            d (float): d polynomial coefficient for width
                Default: 0

            soffset (float): soffset of lane renamed to s in case of centerlane
                Default: 0

        """
        self.widths.append(_poly3struct(a, b, c, d, soffset))

    def get_width(self, s):
        """function that calculates the width of a lane at a point s

        Note: no check that s is on the road can be made, that has to be taken care of by the user

        Parameters
        ----------
            s (float): the point where the width is wished

        Returns
        -------
            width (float): the width at point s
        """
        index_to_calc = 0
        for i in range(len(self.widths)):
            if s >= self.widths[i].soffset:
                index_to_calc = i
            else:
                break
        return self.widths[index_to_calc].get_width(s)

    def add_link(self, link_type, id):
        """adds a link to the lane section

        Parameters
        ----------
            link_type (str): type of link, successor or predecessor

            id (str/id): id of the linked lane
        """
        self.links.add_link(_Link(link_type, str(id)))
        return self

    def get_linked_lane_id(self, link_type):
        """adds a link to the lane section

        Parameters
        ----------
            link_type (str): type of link, successor or predecessor
        """
        for link in self.links.links:
            if link.link_type == link_type:
                return int(link.element_id)
        return None

    def _set_lane_id(self, lane_id):
        """set the lane id of the lane and set lane type to 'none' in case of centerlane"""
        self.lane_id = lane_id
        if self.lane_id == 0:
            self.lane_type = LaneType.none

    def add_roadmark(self, roadmark):
        """add_roadmark adds a roadmark to the lane

        Parameters
        ----------
            roadmark (RoadMark): roadmark of the lane

        """
        if roadmark is not None:
            self.roadmark.append(roadmark)
        return self

    def add_height(self, inner, outer=None, soffset=0):
        """add_height adds a height entry to the lane to elevate it independent from the road elevation

        Parameters
        ----------
            inner (float): inner height

            outer (float): outer height (if not provided, inner height is used)
                Default: None

            s_offset (float): s offset of the height record
                Default: 0

        """
        heightdict = {}
        heightdict["inner"] = str(inner)
        if outer is not None:
            heightdict["outer"] = str(outer)
        else:
            heightdict["outer"] = str(inner)
        heightdict["sOffset"] = str(soffset)

        self.heights.append(heightdict)
        return self

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        if self.lane_id == None:
            raise ValueError("lane id is not set correctly.")
        retdict["id"] = str(self.lane_id)
        retdict["type"] = enum2str(self.lane_type)
        retdict["level"] = "false"
        return retdict

    def get_element(self):
        """returns the elementTree of the Lane"""
        element = ET.Element("lane", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        # according to standard if lane is centerlane it should
        # not have a width record and omit the link record
        if self.lane_id != 0:
            element.append(self.links.get_element())
            for w in sorted(self.widths, key=lambda x: x.soffset):
                ET.SubElement(element, "width", attrib=w.get_attributes())
        # use polynomial dict for laneOffset in case of center lane (only if values provided)
        # removed, should not be here..
        # elif any([self.a,self.b,self.c,self.d]):
        #     polynomialdict['s'] = polynomialdict.pop('sOffset')
        #     ET.SubElement(element,'laneOffset',attrib=polynomialdict)

        if self.roadmark:
            for r in sorted(self.roadmark, key=lambda x: x.soffset):
                element.append(r.get_element())

        for height in self.heights:
            ET.SubElement(element, "height", attrib=height)

        return element


class RoadMark(XodrBase):
    """creates a RoadMark of opendrive

    Parameters
    ----------
        marking_type (RoadMarkType): the type of marking

        width (float): with of the line
            Default: None
        length (float): length of the line
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

        color (RoadMarkColor): color of line (optional)

    Attributes
    ----------
        marking_type (str): the type of marking

        width (float): with of the line

        length (float): length of the line
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

        color (RoadMarkColor): color of line (optional)

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of FileHeader

        add_roadmark(roadmark)
            adds a new roadmark to the lane

    """

    def __init__(
        self,
        marking_type,
        width=None,
        length=None,
        space=None,
        toffset=None,
        soffset=0,
        rule=None,
        color=RoadMarkColor.standard,
        marking_weight=RoadMarkWeight.standard,
        height=0.02,
        laneChange=None,
    ):
        """initializes the RoadMark

        Parameters
        ----------
            marking_type (str): the type of marking

            width (float): width of the marking / line
                Default: None
            length (float): length of the visible, marked part of the line (used for broken lines)
                Default: None
            space (float): length of the invisible, unmarked part of the line (used for broken lines)
                Default: None
            toffset (float): offset in t
                Default: None
            soffset (float): offset in s
                Default: 0
            rule (MarkRule): mark rule (optional)
                Default: None
            color (RoadMarkColor): color of marking
                Default: 'standard'
            marking_weight (str): the weight of marking
                Default: standard
            height (float): thickness of marking
                Default: 0.02
            laneChange (LaneChange): indicates direction in which lane change is allowed
                Default: none

        """
        super().__init__()
        # required arguments - must be provided by user
        self.marking_type = marking_type

        # required arguments - must be provided by user or taken from defaults
        self.marking_weight = marking_weight
        self.color = color
        self.soffset = soffset
        self.height = height
        self.laneChange = laneChange

        # optional arguments - roadmark is valid without them being defined
        self.width = width
        self.length = length
        self.space = space
        self.toffset = toffset
        self.rule = rule

        # TODO: there may be more line child elements per roadmark, which is currently unsupported
        self._line = []
        self._explicit_line = []
        # check if arguments were passed that require line child element
        if any([length, space, toffset, rule]):
            # set defaults in case no values were provided
            # values for broken lines
            if marking_type == RoadMarkType.broken:
                self.length = length or 3
                self.space = space or 3
            # values for solid lines
            elif marking_type == RoadMarkType.solid:
                self.length = length or 3
                self.space = space or 0
            # create empty line if arguments are missing
            else:
                self.length = length or 0
                self.space = length or 0
                print(
                    "No defaults for arguments 'space' and 'length' for roadmark type",
                    enum2str(marking_type),
                    "available and no values were passed. Creating an empty roadmark.",
                )
            # set remaining defaults
            self.width = width or 0.2
            self.toffset = toffset or 0
            self.rule = rule or MarkRule.none
            self._line.append(
                RoadLine(
                    self.width,
                    self.length,
                    self.space,
                    self.toffset,
                    0,
                    self.rule,
                    self.color,
                )
            )

    def add_specific_road_line(self, line):
        """function to add your own roadline to the RoadMark, to use for multi line type of roadmarks,

        Parameters
        ----------
            line (RoadLine): the roadline to add

        """
        self._line.append(line)
        return self

    def add_explicit_road_line(self, line):
        """function to add a explicit roadline to the RoadMark,

        Parameters
        ----------
            line (RoadLine): the roadline to add

        """
        self._explicit_line.append(line)
        return self

    def __eq__(self, other):
        if isinstance(other, RoadMark) and super().__eq__(other):
            if (
                self._line == other._line
                and self._explicit_line == other._explicit_line
                and self.get_attributes() == other.get_attributes()
                and self.marking_type == other.marking_type
            ):
                return True
        return False

    def get_attributes(self):
        """returns the attributes of the RoadMark as a dict"""
        retdict = {}
        retdict["sOffset"] = str(self.soffset)
        retdict["type"] = enum2str(self.marking_type)
        retdict["weight"] = enum2str(self.marking_weight)
        retdict["color"] = enum2str(self.color)
        retdict["height"] = str(self.height)
        if self.width is not None:
            retdict["width"] = str(self.width)
        if self.laneChange is not None:
            retdict["laneChange"] = enum2str(self.laneChange)
        return retdict

    def get_element(self):
        """returns the elementTree of the RoadMark"""
        element = ET.Element("roadMark", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        if self._line:
            attribs = {"name": enum2str(self.marking_type)}
            if self.width is not None:
                attribs["width"] = str(self.width)
            else:
                offsets = [x.toffset for x in self._line]

                attribs["width"] = str(
                    max(offsets)
                    - min(offsets)
                    + sum(
                        [
                            x.width
                            for x in self._line
                            if x.toffset in [max(offsets), min(offsets)]
                        ]
                    )
                )
            typeelement = ET.SubElement(
                element,
                "type",
                attrib=attribs,
            )
            for l in self._line:
                typeelement.append(l.get_element())
        if self._explicit_line:
            typeelement = ET.SubElement(
                element,
                "explicit",
            )
            for l in self._explicit_line:
                typeelement.append(l.get_element())
        return element


class RoadLine(XodrBase):
    """creates a Line type of to be used in roadmark

    Parameters
    ----------
        width (float): with of the line
            Default: 0
        length (float): length of the line
            Default: 0
        space (float): length of space between (broken) lines
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

        color (RoadMarkColor): color of line (optional)

    Attributes
    ----------
        length (float): length of the line

        space (float): length of space between (broken) lines

        toffset (float): offset in t

        soffset (float): offset in s

        rule (MarkRule): mark rule

        width (float): with of the line

        color (RoadMarkColor): color of line

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of FileHeader

    """

    # TODO: check this for 1.5
    def __init__(
        self, width=0, length=0, space=0, toffset=0, soffset=0, rule=None, color=None
    ):
        """initalizes the RoadLine

        Parameters
        ----------
            width (float): with of the line
                Default: 0
            length (float): length of the line
                Default: 0
            space (float): length of space between (broken) lines
                Default: 0
            toffset (float): offset in t
                Default: 0
            soffset (float): offset in s
                Default: 0
            rule (MarkRule): mark rule (optional)

            color (RoadMarkColor): color of line (optional)


        """
        super().__init__()
        self.length = length
        self.space = space
        self.toffset = toffset
        self.rule = rule
        self.soffset = soffset
        self.width = width
        self.color = color
        self._remainder = 0

    def __eq__(self, other):
        if isinstance(other, RoadLine) and super().__eq__(other):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def adjust_remainder(self, total_length, soffset=None, previous_remainder=None):
        """adjust_remainder is used to calculated and set the remainer of a broken mark for offset adjustments

        Parameters
        ----------
            total_length (float): the lenght of the lanesection where this line is valid

            soffset (float): the wanted soffset (at beginning of line), use this or previous remainder
                Default: use defined in class

            previous_remainder (float): the remainder of the previous line, use this or soffset
                Default: use defined in class
        """
        if soffset and previous_remainder:
            raise ToManyOptionalArguments(
                "for adjusting line lengths, use only soffset or previous_remainder."
            )
        if soffset is not None:
            self.soffset = soffset
        if previous_remainder is not None:
            self.soffset = self.space - previous_remainder
        self._remainder = self._calculate_remainder_of_line(self.soffset, total_length)

    def shift_soffset(self):
        """shifts the soffset one period"""
        self.soffset += self.space + self.length

    def adjust_soffset(self, total_length, remainder=None, previous_offset=None):
        """adjust_soffset is used to calculated and set the soffset of a broken mark for offset adjustments

        Parameters
        ----------
            total_length (float): the lenght of the lanesection where this line is valid

            remainder (float): the wanted remainder ("soffset" at end of line), use this or previous_offset
                Default: use defined in class

            previous_offset (float): the soffset of the previous line, use this or remainder
                Default: use defined in class
        """
        if remainder and previous_offset:
            raise ToManyOptionalArguments(
                "for adjusting line lengths, use only soffset or previous_remainder."
            )
        if remainder is not None:
            self._remainder = remainder
        if previous_offset is not None:
            self._remainder = self.space - previous_offset
        self.soffset = self._calculate_remainder_of_line(self._remainder, total_length)

    def _calculate_remainder_of_line(self, soffset, total_length):
        n = (total_length - soffset + self.space) / (self.space + self.length)
        return (
            total_length
            - soffset
            - np.floor(n) * (self.space + self.length)
            + self.space
        )

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        retdict["length"] = str(self.length)
        retdict["space"] = str(self.space)
        retdict["tOffset"] = str(self.toffset)
        retdict["width"] = str(self.width)
        retdict["sOffset"] = str(self.soffset)
        # if self.color:
        # retdict['color'] = enum2str(self.color)
        if self.rule:
            retdict["rule"] = enum2str(self.rule)
        return retdict

    def get_element(self):
        """returns the elementTree of the RoadLine"""
        element = ET.Element("line", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        return element


class ExplicitRoadLine(XodrBase):
    """creates a Explicit RoadLine type of to be used in roadmark

    Parameters
    ----------
        width (float): with of the line
            Default: 0
        length (float): length of the line
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

    Attributes
    ----------
        length (float): length of the line

        toffset (float): offset in t

        soffset (float): offset in s

        rule (MarkRule): mark rule

        width (float): with of the line

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of FileHeader

    """

    # TODO: check this for 1.5
    def __init__(self, width=0, length=0, toffset=0, soffset=0, rule=None):
        """initalizes the RoadLine

        Parameters
        ----------
            width (float): with of the line
                Default: 0
            length (float): length of the line
                Default: 0
            toffset (float): offset in t
                Default: 0
            soffset (float): offset in s
                Default: 0
            rule (MarkRule): mark rule (optional)

        """
        super().__init__()
        self.length = length
        self.toffset = toffset
        self.rule = rule
        self.soffset = soffset
        self.width = width
        self._remainder = 0

    def __eq__(self, other):
        if isinstance(other, ExplicitRoadLine) and super().__eq__(other):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        retdict["length"] = str(self.length)
        retdict["tOffset"] = str(self.toffset)
        retdict["width"] = str(self.width)
        retdict["sOffset"] = str(self.soffset)
        if self.rule:
            retdict["rule"] = enum2str(self.rule)
        return retdict

    def get_element(self):
        """returns the elementTree of the WorldPostion"""
        element = ET.Element("line", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        return element

Classes

class ExplicitRoadLine (width=0, length=0, toffset=0, soffset=0, rule=None)

creates a Explicit RoadLine type of to be used in roadmark

Parameters

width (float): with of the line
    Default: 0
length (float): length of the line
    Default: 0
toffset (float): offset in t
    Default: 0
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)

Attributes

length (float): length of the line

toffset (float): offset in t

soffset (float): offset in s

rule (MarkRule): mark rule

width (float): with of the line

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of FileHeader

initalizes the RoadLine

Parameters

width (float): with of the line
    Default: 0
length (float): length of the line
    Default: 0
toffset (float): offset in t
    Default: 0
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)
Expand source code
class ExplicitRoadLine(XodrBase):
    """creates a Explicit RoadLine type of to be used in roadmark

    Parameters
    ----------
        width (float): with of the line
            Default: 0
        length (float): length of the line
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

    Attributes
    ----------
        length (float): length of the line

        toffset (float): offset in t

        soffset (float): offset in s

        rule (MarkRule): mark rule

        width (float): with of the line

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of FileHeader

    """

    # TODO: check this for 1.5
    def __init__(self, width=0, length=0, toffset=0, soffset=0, rule=None):
        """initalizes the RoadLine

        Parameters
        ----------
            width (float): with of the line
                Default: 0
            length (float): length of the line
                Default: 0
            toffset (float): offset in t
                Default: 0
            soffset (float): offset in s
                Default: 0
            rule (MarkRule): mark rule (optional)

        """
        super().__init__()
        self.length = length
        self.toffset = toffset
        self.rule = rule
        self.soffset = soffset
        self.width = width
        self._remainder = 0

    def __eq__(self, other):
        if isinstance(other, ExplicitRoadLine) and super().__eq__(other):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        retdict["length"] = str(self.length)
        retdict["tOffset"] = str(self.toffset)
        retdict["width"] = str(self.width)
        retdict["sOffset"] = str(self.soffset)
        if self.rule:
            retdict["rule"] = enum2str(self.rule)
        return retdict

    def get_element(self):
        """returns the elementTree of the WorldPostion"""
        element = ET.Element("line", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        return element

Ancestors

Methods

def get_attributes(self)

returns the attributes of the Lane as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the Lane as a dict"""
    retdict = {}
    retdict["length"] = str(self.length)
    retdict["tOffset"] = str(self.toffset)
    retdict["width"] = str(self.width)
    retdict["sOffset"] = str(self.soffset)
    if self.rule:
        retdict["rule"] = enum2str(self.rule)
    return retdict
def get_element(self)

returns the elementTree of the WorldPostion

Expand source code
def get_element(self):
    """returns the elementTree of the WorldPostion"""
    element = ET.Element("line", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    return element

Inherited members

class Lane (lane_type=LaneType.driving, a=0, b=0, c=0, d=0, soffset=0)

creates a Lane of opendrive

the inputs are on the following format: f(s) = a + bs + cs^2 + d*s^3

Parameters

lane_type (LaneType): type of lane
    Default: LaneType.driving

a (float): a coefficient
    Default: 0

b (float): b coefficient
    Default: 0

c (float): c coefficient
    Default: 0

d (float): d coefficient
    Default: 0

soffset (float): soffset of lane
    Default: 0

Attributes

lane_id (int): id of the lane (automatically assigned by LaneSection)

lane_type (LaneType): type of lane

a (float): a coefficient

b (float): b coefficient

c (float): c coefficient

d (float): d coefficient

soffset (float): soffset of lane

roadmark (RoadMark): roadmarks related to the lane

links (_Links): Lane links to the lane

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of class

add_roadmark(roadmark)
    adds a new roadmark to the lane

add_lane_width(a, b, c, d, soffset)
    adds an additional width element to the lane

initalizes the Lane

Parameters

lane_type (LaneType): type of lane
    Default: LaneType.driving

a (float): a polynomial coefficient for width (left/right) or laneoffset (center)
    Default: 0

b (float): b polynomial coefficient for width (left/right) or laneoffset (center)
    Default: 0

c (float): c polynomial coefficient for width (left/right) or laneoffset (center)
    Default: 0

d (float): d polynomial coefficient for width (left/right) or laneoffset (center)
    Default: 0

soffset (float): soffset of lane renamed to s in case of centerlane
    Default: 0
Expand source code
class Lane(XodrBase):
    """creates a Lane of opendrive

    the inputs are on the following format:
        f(s) = a + b*s + c*s^2 + d*s^3

    Parameters
    ----------

        lane_type (LaneType): type of lane
            Default: LaneType.driving

        a (float): a coefficient
            Default: 0

        b (float): b coefficient
            Default: 0

        c (float): c coefficient
            Default: 0

        d (float): d coefficient
            Default: 0

        soffset (float): soffset of lane
            Default: 0


    Attributes
    ----------
        lane_id (int): id of the lane (automatically assigned by LaneSection)

        lane_type (LaneType): type of lane

        a (float): a coefficient

        b (float): b coefficient

        c (float): c coefficient

        d (float): d coefficient

        soffset (float): soffset of lane

        roadmark (RoadMark): roadmarks related to the lane

        links (_Links): Lane links to the lane

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of class

        add_roadmark(roadmark)
            adds a new roadmark to the lane

        add_lane_width(a, b, c, d, soffset)
            adds an additional width element to the lane
    """

    def __init__(self, lane_type=LaneType.driving, a=0, b=0, c=0, d=0, soffset=0):
        """initalizes the Lane

        Parameters
        ----------

            lane_type (LaneType): type of lane
                Default: LaneType.driving

            a (float): a polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            b (float): b polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            c (float): c polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            d (float): d polynomial coefficient for width (left/right) or laneoffset (center)
                Default: 0

            soffset (float): soffset of lane renamed to s in case of centerlane
                Default: 0

        """
        super().__init__()
        self.lane_id = None
        self.lane_type = lane_type
        self.widths = []
        self.add_lane_width(a, b, c, d, soffset)

        self.soffset = soffset
        # TODO: enable multiple widths records per lane (only then soffset really makes sense! ASAM requires one width record to have sOffset=0)
        self.heights = (
            []
        )  # height entries to elevate the lane independent from the road elevation
        self.roadmark = []
        self.links = _Links()

    def __eq__(self, other):
        if isinstance(other, Lane) and super().__eq__(other):
            if (
                self.links == other.links
                and self.get_attributes() == other.get_attributes()
                and self.widths == other.widths
                and self.heights == other.heights
                and self.roadmark == other.roadmark
            ):
                return True
        return False

        # TODO: add more features to add for lane

    def add_lane_width(self, a=0, b=0, c=0, d=0, soffset=0):
        """adds an additional width element to the lane

        Parameters
        ----------
            a (float): a polynomial coefficient for width
                Default: 0

            b (float): b polynomial coefficient for width
                Default: 0

            c (float): c polynomial coefficient for width
                Default: 0

            d (float): d polynomial coefficient for width
                Default: 0

            soffset (float): soffset of lane renamed to s in case of centerlane
                Default: 0

        """
        self.widths.append(_poly3struct(a, b, c, d, soffset))

    def get_width(self, s):
        """function that calculates the width of a lane at a point s

        Note: no check that s is on the road can be made, that has to be taken care of by the user

        Parameters
        ----------
            s (float): the point where the width is wished

        Returns
        -------
            width (float): the width at point s
        """
        index_to_calc = 0
        for i in range(len(self.widths)):
            if s >= self.widths[i].soffset:
                index_to_calc = i
            else:
                break
        return self.widths[index_to_calc].get_width(s)

    def add_link(self, link_type, id):
        """adds a link to the lane section

        Parameters
        ----------
            link_type (str): type of link, successor or predecessor

            id (str/id): id of the linked lane
        """
        self.links.add_link(_Link(link_type, str(id)))
        return self

    def get_linked_lane_id(self, link_type):
        """adds a link to the lane section

        Parameters
        ----------
            link_type (str): type of link, successor or predecessor
        """
        for link in self.links.links:
            if link.link_type == link_type:
                return int(link.element_id)
        return None

    def _set_lane_id(self, lane_id):
        """set the lane id of the lane and set lane type to 'none' in case of centerlane"""
        self.lane_id = lane_id
        if self.lane_id == 0:
            self.lane_type = LaneType.none

    def add_roadmark(self, roadmark):
        """add_roadmark adds a roadmark to the lane

        Parameters
        ----------
            roadmark (RoadMark): roadmark of the lane

        """
        if roadmark is not None:
            self.roadmark.append(roadmark)
        return self

    def add_height(self, inner, outer=None, soffset=0):
        """add_height adds a height entry to the lane to elevate it independent from the road elevation

        Parameters
        ----------
            inner (float): inner height

            outer (float): outer height (if not provided, inner height is used)
                Default: None

            s_offset (float): s offset of the height record
                Default: 0

        """
        heightdict = {}
        heightdict["inner"] = str(inner)
        if outer is not None:
            heightdict["outer"] = str(outer)
        else:
            heightdict["outer"] = str(inner)
        heightdict["sOffset"] = str(soffset)

        self.heights.append(heightdict)
        return self

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        if self.lane_id == None:
            raise ValueError("lane id is not set correctly.")
        retdict["id"] = str(self.lane_id)
        retdict["type"] = enum2str(self.lane_type)
        retdict["level"] = "false"
        return retdict

    def get_element(self):
        """returns the elementTree of the Lane"""
        element = ET.Element("lane", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        # according to standard if lane is centerlane it should
        # not have a width record and omit the link record
        if self.lane_id != 0:
            element.append(self.links.get_element())
            for w in sorted(self.widths, key=lambda x: x.soffset):
                ET.SubElement(element, "width", attrib=w.get_attributes())
        # use polynomial dict for laneOffset in case of center lane (only if values provided)
        # removed, should not be here..
        # elif any([self.a,self.b,self.c,self.d]):
        #     polynomialdict['s'] = polynomialdict.pop('sOffset')
        #     ET.SubElement(element,'laneOffset',attrib=polynomialdict)

        if self.roadmark:
            for r in sorted(self.roadmark, key=lambda x: x.soffset):
                element.append(r.get_element())

        for height in self.heights:
            ET.SubElement(element, "height", attrib=height)

        return element

Ancestors

Methods

def add_height(self, inner, outer=None, soffset=0)

add_height adds a height entry to the lane to elevate it independent from the road elevation

Parameters

inner (float): inner height

outer (float): outer height (if not provided, inner height is used)
    Default: None

s_offset (float): s offset of the height record
    Default: 0
Expand source code
def add_height(self, inner, outer=None, soffset=0):
    """add_height adds a height entry to the lane to elevate it independent from the road elevation

    Parameters
    ----------
        inner (float): inner height

        outer (float): outer height (if not provided, inner height is used)
            Default: None

        s_offset (float): s offset of the height record
            Default: 0

    """
    heightdict = {}
    heightdict["inner"] = str(inner)
    if outer is not None:
        heightdict["outer"] = str(outer)
    else:
        heightdict["outer"] = str(inner)
    heightdict["sOffset"] = str(soffset)

    self.heights.append(heightdict)
    return self
def add_lane_width(self, a=0, b=0, c=0, d=0, soffset=0)

adds an additional width element to the lane

Parameters

a (float): a polynomial coefficient for width
    Default: 0

b (float): b polynomial coefficient for width
    Default: 0

c (float): c polynomial coefficient for width
    Default: 0

d (float): d polynomial coefficient for width
    Default: 0

soffset (float): soffset of lane renamed to s in case of centerlane
    Default: 0
Expand source code
def add_lane_width(self, a=0, b=0, c=0, d=0, soffset=0):
    """adds an additional width element to the lane

    Parameters
    ----------
        a (float): a polynomial coefficient for width
            Default: 0

        b (float): b polynomial coefficient for width
            Default: 0

        c (float): c polynomial coefficient for width
            Default: 0

        d (float): d polynomial coefficient for width
            Default: 0

        soffset (float): soffset of lane renamed to s in case of centerlane
            Default: 0

    """
    self.widths.append(_poly3struct(a, b, c, d, soffset))

adds a link to the lane section

Parameters

link_type (str): type of link, successor or predecessor

id (str/id): id of the linked lane
Expand source code
def add_link(self, link_type, id):
    """adds a link to the lane section

    Parameters
    ----------
        link_type (str): type of link, successor or predecessor

        id (str/id): id of the linked lane
    """
    self.links.add_link(_Link(link_type, str(id)))
    return self
def add_roadmark(self, roadmark)

add_roadmark adds a roadmark to the lane

Parameters

roadmark (RoadMark): roadmark of the lane
Expand source code
def add_roadmark(self, roadmark):
    """add_roadmark adds a roadmark to the lane

    Parameters
    ----------
        roadmark (RoadMark): roadmark of the lane

    """
    if roadmark is not None:
        self.roadmark.append(roadmark)
    return self
def get_attributes(self)

returns the attributes of the Lane as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the Lane as a dict"""
    retdict = {}
    if self.lane_id == None:
        raise ValueError("lane id is not set correctly.")
    retdict["id"] = str(self.lane_id)
    retdict["type"] = enum2str(self.lane_type)
    retdict["level"] = "false"
    return retdict
def get_element(self)

returns the elementTree of the Lane

Expand source code
def get_element(self):
    """returns the elementTree of the Lane"""
    element = ET.Element("lane", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    # according to standard if lane is centerlane it should
    # not have a width record and omit the link record
    if self.lane_id != 0:
        element.append(self.links.get_element())
        for w in sorted(self.widths, key=lambda x: x.soffset):
            ET.SubElement(element, "width", attrib=w.get_attributes())
    # use polynomial dict for laneOffset in case of center lane (only if values provided)
    # removed, should not be here..
    # elif any([self.a,self.b,self.c,self.d]):
    #     polynomialdict['s'] = polynomialdict.pop('sOffset')
    #     ET.SubElement(element,'laneOffset',attrib=polynomialdict)

    if self.roadmark:
        for r in sorted(self.roadmark, key=lambda x: x.soffset):
            element.append(r.get_element())

    for height in self.heights:
        ET.SubElement(element, "height", attrib=height)

    return element
def get_linked_lane_id(self, link_type)

adds a link to the lane section

Parameters

link_type (str): type of link, successor or predecessor
Expand source code
def get_linked_lane_id(self, link_type):
    """adds a link to the lane section

    Parameters
    ----------
        link_type (str): type of link, successor or predecessor
    """
    for link in self.links.links:
        if link.link_type == link_type:
            return int(link.element_id)
    return None
def get_width(self, s)

function that calculates the width of a lane at a point s

Note: no check that s is on the road can be made, that has to be taken care of by the user

Parameters

s (float): the point where the width is wished

Returns

width (float): the width at point s
Expand source code
def get_width(self, s):
    """function that calculates the width of a lane at a point s

    Note: no check that s is on the road can be made, that has to be taken care of by the user

    Parameters
    ----------
        s (float): the point where the width is wished

    Returns
    -------
        width (float): the width at point s
    """
    index_to_calc = 0
    for i in range(len(self.widths)):
        if s >= self.widths[i].soffset:
            index_to_calc = i
        else:
            break
    return self.widths[index_to_calc].get_width(s)

Inherited members

class LaneOffset (s=0, a=0, b=0, c=0, d=0)

the LaneOffset class defines an overall lateral offset along the road, described as a third degree polynomial

Parameters

s (float): s start coordinate of the elevation

a (float): a coefficient of the polynomial

b (float): b coefficient of the polynomial

c (float): c coefficient of the polynomial

d (float): d coefficient of the polynomial

Attributes

s (float): s start coordinate of the elevation

a (float): a coefficient of the polynomial

b (float): b coefficient of the polynomial

c (float): c coefficient of the polynomial

d (float): d coefficient of the polynomial

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_attributes()
    Returns the attributes of the class

initalize the LaneOffset class

Parameters

s (float): s start coordinate of the LaneOffset

a (float): a coefficient of the polynomial

b (float): b coefficient of the polynomial

c (float): c coefficient of the polynomial

d (float): d coefficient of the polynomial
Expand source code
class LaneOffset(XodrBase):
    """the LaneOffset class defines an overall lateral offset along the road, described as a third degree polynomial

    Parameters
    ----------
        s (float): s start coordinate of the elevation

        a (float): a coefficient of the polynomial

        b (float): b coefficient of the polynomial

        c (float): c coefficient of the polynomial

        d (float): d coefficient of the polynomial

    Attributes
    ----------
        s (float): s start coordinate of the elevation

        a (float): a coefficient of the polynomial

        b (float): b coefficient of the polynomial

        c (float): c coefficient of the polynomial

        d (float): d coefficient of the polynomial

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns the attributes of the class

    """

    def __init__(self, s=0, a=0, b=0, c=0, d=0):
        """initalize the LaneOffset class

        Parameters
        ----------
            s (float): s start coordinate of the LaneOffset

            a (float): a coefficient of the polynomial

            b (float): b coefficient of the polynomial

            c (float): c coefficient of the polynomial

            d (float): d coefficient of the polynomial

        """
        super().__init__()
        self.s = s
        self.a = a
        self.b = b
        self.c = c
        self.d = d

    def __eq__(self, other):
        if isinstance(other, LaneOffset) and super().__eq__(other):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def get_attributes(self):
        """returns the attributes of the LaneOffset"""

        retdict = {}
        retdict["s"] = str(self.s)
        retdict["a"] = str(self.a)
        retdict["b"] = str(self.b)
        retdict["c"] = str(self.c)
        retdict["d"] = str(self.d)
        return retdict

    def get_element(self):
        """returns the elementTree of the LaneOffset"""
        element = ET.Element("laneOffset", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        return element

Ancestors

Methods

def get_attributes(self)

returns the attributes of the LaneOffset

Expand source code
def get_attributes(self):
    """returns the attributes of the LaneOffset"""

    retdict = {}
    retdict["s"] = str(self.s)
    retdict["a"] = str(self.a)
    retdict["b"] = str(self.b)
    retdict["c"] = str(self.c)
    retdict["d"] = str(self.d)
    return retdict
def get_element(self)

returns the elementTree of the LaneOffset

Expand source code
def get_element(self):
    """returns the elementTree of the LaneOffset"""
    element = ET.Element("laneOffset", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    return element

Inherited members

class LaneSection (s, centerlane)

Creates the LaneSection element of opendrive

Parameters

s (float): start of lanesection

centerlane (Lane): the centerline of the road

Attributes

s (float): start of lanesection

centerlane (Lane): the centerline of the road

leftlanes (list of Lane): the lanes left to the center

rightlanes (list of Lane): the lanes right to the center

Methods

get_element()
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of class

add_left_lane(Lane)
    adds a new lane to the left

add_right_lane(Lane)
    adds a new lane to the right

initalize the LaneSection

Parameters

s (float): start of lanesection

centerlane (Lane): the centerline of the road
Expand source code
class LaneSection(XodrBase):
    """Creates the LaneSection element of opendrive

    Parameters
    ----------
        s (float): start of lanesection

        centerlane (Lane): the centerline of the road

    Attributes
    ----------
        s (float): start of lanesection

        centerlane (Lane): the centerline of the road

        leftlanes (list of Lane): the lanes left to the center

        rightlanes (list of Lane): the lanes right to the center

    Methods
    -------
        get_element()
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of class

        add_left_lane(Lane)
            adds a new lane to the left

        add_right_lane(Lane)
            adds a new lane to the right
    """

    def __init__(self, s, centerlane):
        """initalize the LaneSection

        Parameters
        ----------
            s (float): start of lanesection

            centerlane (Lane): the centerline of the road
        """
        super().__init__()
        self.s = s
        self.centerlane = centerlane
        self.centerlane._set_lane_id(0)
        self.leftlanes = []
        self.rightlanes = []
        self._left_id = 1
        self._right_id = -1

    def __eq__(self, other):
        if isinstance(other, LaneSection) and super().__eq__(other):
            if (
                self.get_attributes() == other.get_attributes()
                and self.centerlane == other.centerlane
                and self.leftlanes == other.leftlanes
                and self.rightlanes == other.rightlanes
            ):
                return True
        return False

    def add_left_lane(self, lane):
        """adds a lane to the left of the center, add from center outwards

        Parameters
        ----------
            lane (Lane): the lane to add
        """
        lane._set_lane_id(self._left_id)
        self._left_id += 1
        self.leftlanes.append(lane)
        return self

    def add_right_lane(self, lane):
        """adds a lane to the right of the center, add from center outwards

        Parameters
        ----------
            lane (Lane): the lane to add
        """
        lane._set_lane_id(self._right_id)
        self._right_id -= 1
        self.rightlanes.append(lane)
        return self

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        retdict["s"] = str(self.s)
        return retdict

    def get_element(self):
        """returns the elementTree of the WorldPostion"""
        element = ET.Element("laneSection", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        if self.leftlanes:
            left = ET.SubElement(element, "left")
            for l in reversed(self.leftlanes):
                left.append(l.get_element())

        center = ET.SubElement(element, "center")
        center.append(self.centerlane.get_element())

        if self.rightlanes:
            right = ET.SubElement(element, "right")
            for l in self.rightlanes:
                right.append(l.get_element())

        return element

Ancestors

Methods

def add_left_lane(self, lane)

adds a lane to the left of the center, add from center outwards

Parameters

lane (Lane): the lane to add
Expand source code
def add_left_lane(self, lane):
    """adds a lane to the left of the center, add from center outwards

    Parameters
    ----------
        lane (Lane): the lane to add
    """
    lane._set_lane_id(self._left_id)
    self._left_id += 1
    self.leftlanes.append(lane)
    return self
def add_right_lane(self, lane)

adds a lane to the right of the center, add from center outwards

Parameters

lane (Lane): the lane to add
Expand source code
def add_right_lane(self, lane):
    """adds a lane to the right of the center, add from center outwards

    Parameters
    ----------
        lane (Lane): the lane to add
    """
    lane._set_lane_id(self._right_id)
    self._right_id -= 1
    self.rightlanes.append(lane)
    return self
def get_attributes(self)

returns the attributes of the Lane as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the Lane as a dict"""
    retdict = {}
    retdict["s"] = str(self.s)
    return retdict
def get_element(self)

returns the elementTree of the WorldPostion

Expand source code
def get_element(self):
    """returns the elementTree of the WorldPostion"""
    element = ET.Element("laneSection", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    if self.leftlanes:
        left = ET.SubElement(element, "left")
        for l in reversed(self.leftlanes):
            left.append(l.get_element())

    center = ET.SubElement(element, "center")
    center.append(self.centerlane.get_element())

    if self.rightlanes:
        right = ET.SubElement(element, "right")
        for l in self.rightlanes:
            right.append(l.get_element())

    return element

Inherited members

class Lanes

creates the Lanes element of opendrive

Attributes

lane_sections (list of LaneSection): a list of all lanesections

Methods

get_element(elementname)
    Returns the full ElementTree of the class

add_lanesection(lanesection)
    adds a lane section to Lanes

add_laneoffset(laneoffset)
    adds a lane offset to Lanes

initalize the UserData

Expand source code
class Lanes(XodrBase):
    """creates the Lanes element of opendrive


    Attributes
    ----------
        lane_sections (list of LaneSection): a list of all lanesections

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        add_lanesection(lanesection)
            adds a lane section to Lanes

        add_laneoffset(laneoffset)
            adds a lane offset to Lanes
    """

    def __init__(self):
        super().__init__()
        """initalize Lanes"""
        self.lanesections = []
        self.laneoffsets = []
        self.roadmarks_adjusted = False

    def __eq__(self, other):
        if isinstance(other, Lanes) and super().__eq__(other):
            if (
                self.laneoffsets == other.laneoffsets
                and self.lanesections == other.lanesections
            ):
                return True
        return False

    def add_lanesection(self, lanesection, lanelinks=None):
        """creates the Lanes element of opendrive

        Parameters
        ----------
            lanesection (LaneSection): a LaneSection to add

            lanelink (LaneLinker): (optional) a LaneLink to add

        """
        # add links to the lanes
        if lanelinks:
            # loop over all links
            if not isinstance(lanelinks, list):
                lanelinks = [lanelinks]
            for lanelink in lanelinks:
                for link in lanelink.links:
                    # check if link already added
                    if not link.used:
                        link.predecessor.add_link("successor", link.successor.lane_id)
                        link.successor.add_link("predecessor", link.predecessor.lane_id)
                        link.used = True

        self.lanesections.append(lanesection)
        return self

    def add_laneoffset(self, laneoffset):
        """adds a lane offset to Lanes

        Parameters
        ----------
            laneoffset (LaneOffset): a LaneOffset to add
        """
        if not isinstance(laneoffset, LaneOffset):
            raise TypeError(
                "add_laneoffset requires a LaneOffset as input, not "
                + str(type(laneoffset))
            )
        self.laneoffsets.append(laneoffset)
        return self

    def _check_valid_mark_type(self, lane):
        """simple checker if the lanemark can be adjusted

        Parameters
        ----------
            lane (Lane): the lane which roadmark should be checked
        """
        return (
            lane.roadmark[0].marking_type == RoadMarkType.broken
            or lane.roadmark[0].marking_type == RoadMarkType.broken_broken
        )

    def _adjust_for_missing_line_offset(self, roadmark):
        """adds an explicit line if soofset is less than 0 (for adjusting from start) or longer than the space between lines (for adjusting from end)

        Parameters
        ----------
            roadmark (RoadMark): the roadmark to be adjusted
        """
        for line in roadmark._line:
            if line.soffset < 0 or line.soffset > line.length + line.soffset:
                roadmark.add_explicit_road_line(
                    ExplicitRoadLine(
                        line.width,
                        line.length + line.soffset,
                        line.toffset,
                        0,
                        line.rule,
                    )
                )
            elif line.soffset > line.space:
                roadmark.add_explicit_road_line(
                    ExplicitRoadLine(
                        line.width,
                        line.soffset - line.space,
                        line.toffset,
                        0,
                        line.rule,
                    )
                )
            if line.soffset < 0:
                line.shift_soffset()

    def _validity_check_for_roadmark_adjustment(self):
        """does some simple checks if the the different lanes can be adjusted"""
        self._right_lanes_adjustable = len(self.lanesections[0].rightlanes) > 0
        self._left_lanes_adjustable = len(self.lanesections[0].leftlanes) > 0
        self._center_lane_adjustable = True
        for ls in range(len(self.lanesections) - 1):
            if len(self.lanesections[ls].centerlane.roadmark) != 1:
                self.center_lane_adjustable = False
            if (
                self.lanesections[ls].centerlane.roadmark
                != self.lanesections[ls + 1].centerlane.roadmark
            ):
                self.center_lane_adjustable = False
            if (
                self.lanesections[ls].centerlane.roadmark[0].marking_type
                != RoadMarkType.broken
                and self.lanesections[ls].centerlane.roadmark[0].marking_type
                != RoadMarkType.broken_broken
            ):
                self.center_lane_adjustable = False

            for rl in range(len(self.lanesections[ls].rightlanes)):
                if self._right_lanes_adjustable:
                    if len(self.lanesections[ls].rightlanes[rl].roadmark) != 1:
                        self._right_lanes_adjustable = False
            for ll in range(len(self.lanesections[ls].leftlanes)):
                if self._left_lanes_adjustable:
                    if len(self.lanesections[ls].leftlanes[ll].roadmark) != 1:
                        self._left_lanes_adjustable = False

    def _get_previous_remainder(
        self,
        connected_lane_section,
        i_line,
        lane_side,
        contact_point,
        lane_index,
        lane_section_index,
        start_or_end,
    ):
        """_get_previous_remainder is a helper method to get the remainder of a lanemarking of a connecting lane section (for lenght adjustment)

        Parameters
        ----------
            connected_lane_section (LaneSection): connected lane section (on another road)

            i_line (int): index of the line (roadmark._line)

            lane_side (str): "left", "right", or "center" describing what lane is of interest

            contact_point (ContactPoint): contact point of the connected_lane_section

            lane_index (int): the lane index of the wanted lane

            lane_section_index (int): index of the lane section

            start_or_end (str): if the adjustment is done from the end or from the start of the road

        Return
        ------
            float: remainder of the previous lanesection

        """
        active_lane_sec = self.lanesections[lane_section_index]
        neighbor_lane_sec = None
        if start_or_end == "end":
            on_edge = lane_section_index == len(self.lanesections) - 1
            connection = "successor"
            if not on_edge:
                neighbor_lane_sec = self.lanesections[lane_section_index + 1]
        else:
            on_edge = lane_section_index == 0
            connection = "predecessor"
            if not on_edge:
                neighbor_lane_sec = self.lanesections[lane_section_index - 1]

        linked_lane_id = 0
        found_linked_lane_id = None
        if lane_side == "right":
            found_linked_lane_id = active_lane_sec.rightlanes[
                lane_index
            ].get_linked_lane_id(connection)
            if neighbor_lane_sec:
                neighboring_lane = neighbor_lane_sec.rightlanes[linked_lane_id]
        elif lane_side == "left":
            found_linked_lane_id = active_lane_sec.leftlanes[
                lane_index
            ].get_linked_lane_id(connection)
            if neighbor_lane_sec:
                neighboring_lane = neighbor_lane_sec.leftlanes[linked_lane_id]
        else:  # center
            if neighbor_lane_sec:
                neighboring_lane = neighbor_lane_sec.centerlane
        if found_linked_lane_id:
            linked_lane_id = abs(found_linked_lane_id) - 1

        prev_remainder = 0
        if on_edge:
            if lane_side == "right":
                if (
                    contact_point == ContactPoint.end
                    and connected_lane_section.rightlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.rightlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        ._remainder
                    )
                elif (
                    contact_point == ContactPoint.start
                    and connected_lane_section.leftlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.leftlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        .soffset
                    )

            if lane_side == "left":
                if (
                    contact_point == ContactPoint.end
                    and connected_lane_section.leftlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.leftlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        ._remainder
                    )
                elif (
                    contact_point == ContactPoint.start
                    and connected_lane_section.rightlanes[linked_lane_id]
                    .roadmark[0]
                    ._line
                ):
                    prev_remainder = (
                        connected_lane_section.rightlanes[linked_lane_id]
                        .roadmark[0]
                        ._line[i_line]
                        .soffset
                    )

            if (
                lane_side == "center"
                and connected_lane_section.centerlane.roadmark[0]._line
            ):
                if contact_point == ContactPoint.end:
                    prev_remainder = (
                        connected_lane_section.centerlane.roadmark[0]
                        ._line[i_line]
                        ._remainder
                    )
                elif contact_point == ContactPoint.start:
                    prev_remainder = (
                        connected_lane_section.centerlane.roadmark[0]
                        ._line[i_line]
                        .soffset
                    )

        else:
            if start_or_end == "start":
                prev_remainder = neighboring_lane.roadmark[0]._line[i_line]._remainder
            else:
                prev_remainder = neighboring_lane.roadmark[0]._line[i_line].soffset
        return prev_remainder

    def _get_seg_length(self, total_road_length, lane_section_index):
        """_get_seg_length is a helper method to figure out how long a lane section is

        Parameters
        ----------
            total_road_length (float): total length of the road

            lane_section_index (int): the index of the wanted lanesection

        Returns
        -------
            float: length of the lanesection

        """
        if len(self.lanesections) == 1:
            seg_length = total_road_length
        elif lane_section_index == 0:
            seg_length = self.lanesections[1].s
        elif lane_section_index == len(self.lanesections) - 1:
            seg_length = total_road_length - self.lanesections[lane_section_index].s
        else:
            seg_length = (
                self.lanesections[lane_section_index + 1].s
                - self.lanesections[lane_section_index].s
            )
        return seg_length

    def adjust_road_marks_from_start(
        self,
        total_road_length,
        connected_lane_section=None,
        contact_point=ContactPoint.end,
    ):
        """Adjusts road marks from the start of the road, based on the connected lane section.
        If connected_lane_section is not provided, the last roadmark will be placed with zero
        distance to the start of the road

        Parameters
        ----------
            total_road_length (float): total length of the road

            connected_lane_section (LaneSection): the lane section connected to the road
                Default: None

            contact_point (ContactPoint)
                Default: ContactPoint.end
        """
        if not self.roadmarks_adjusted:
            self._validity_check_for_roadmark_adjustment()
            self.roadmarks_adjusted = True

            def set_zero_offset_to_lines(lane, seg_length):
                for i_line in range(len(lane.roadmark[0]._line)):
                    lane.roadmark[0]._line[i_line].adjust_remainder(
                        seg_length, soffset=0
                    )

            for ls in range(0, len(self.lanesections)):
                seg_length = self._get_seg_length(total_road_length, ls)
                if self._right_lanes_adjustable:
                    for rl in range(len(self.lanesections[ls].rightlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].rightlanes[rl]
                        ):
                            if ls == 0 and connected_lane_section is None:
                                set_zero_offset_to_lines(
                                    self.lanesections[ls].rightlanes[rl], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .rightlanes[rl]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "right",
                                        contact_point,
                                        rl,
                                        ls,
                                        "start",
                                    )
                                    self.lanesections[ls].rightlanes[rl].roadmark[
                                        0
                                    ]._line[i_line].adjust_remainder(
                                        seg_length, previous_remainder=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].rightlanes[rl].roadmark[0]
                                )
                if self._left_lanes_adjustable:
                    for ll in range(len(self.lanesections[ls].leftlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].leftlanes[ll]
                        ):
                            if ls == 0 and connected_lane_section is None:
                                set_zero_offset_to_lines(
                                    self.lanesections[ls].leftlanes[ll], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .leftlanes[ll]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "left",
                                        contact_point,
                                        ll,
                                        ls,
                                        "start",
                                    )
                                    self.lanesections[ls].leftlanes[ll].roadmark[
                                        0
                                    ]._line[i_line].adjust_remainder(
                                        seg_length, previous_remainder=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].leftlanes[ll].roadmark[0]
                                )
                if self._center_lane_adjustable:
                    if self._check_valid_mark_type(self.lanesections[ls].centerlane):
                        if ls == 0 and connected_lane_section is None:
                            set_zero_offset_to_lines(
                                self.lanesections[ls].centerlane, seg_length
                            )
                        else:
                            for i_line in range(
                                len(self.lanesections[ls].centerlane.roadmark[0]._line)
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "center",
                                    contact_point,
                                    None,
                                    ls,
                                    "start",
                                )
                                self.lanesections[ls].centerlane.roadmark[0]._line[
                                    i_line
                                ].adjust_remainder(
                                    seg_length, previous_remainder=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].centerlane.roadmark[0]
                            )

    def adjust_road_marks_from_end(
        self,
        total_road_length,
        connected_lane_section=None,
        contact_point=ContactPoint.end,
    ):
        """Adjusts road marks from the end of the road, based on the connected lane section.
        If connected_lane_section is not provided, the last roadmark will be placed with zero
        distance to the end of the road

        Parameters
        ----------
            total_road_length (float): total length of the road

            connected_lane_section (LaneSection): the lane section connected to the road
                Default: None

            contact_point (ContactPoint)
                Default: ContactPoint.end
        """
        if not self.roadmarks_adjusted:
            self._validity_check_for_roadmark_adjustment()
            self.roadmarks_adjusted = True

            def set_zero_remainder_to_lines(lane, seg_length):
                for i_line in range(len(lane.roadmark[0]._line)):
                    lane.roadmark[0]._line[i_line].adjust_soffset(
                        seg_length, remainder=0
                    )

            for ls in range(len(self.lanesections) - 1, -1, -1):
                seg_length = self._get_seg_length(total_road_length, ls)
                if self._right_lanes_adjustable:
                    for rl in range(len(self.lanesections[ls].rightlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].rightlanes[rl]
                        ):
                            if (
                                ls == len(self.lanesections) - 1
                                and connected_lane_section is None
                            ):
                                set_zero_remainder_to_lines(
                                    self.lanesections[ls].rightlanes[rl], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .rightlanes[rl]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "right",
                                        contact_point,
                                        rl,
                                        ls,
                                        "end",
                                    )
                                    self.lanesections[ls].rightlanes[rl].roadmark[
                                        0
                                    ]._line[i_line].adjust_soffset(
                                        seg_length, previous_offset=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].rightlanes[rl].roadmark[0]
                                )
                if self._left_lanes_adjustable:
                    for ll in range(len(self.lanesections[ls].leftlanes)):
                        if self._check_valid_mark_type(
                            self.lanesections[ls].leftlanes[ll]
                        ):
                            if (
                                ls == len(self.lanesections) - 1
                                and connected_lane_section is None
                            ):
                                set_zero_remainder_to_lines(
                                    self.lanesections[ls].leftlanes[ll], seg_length
                                )
                            else:
                                for i_line in range(
                                    len(
                                        self.lanesections[ls]
                                        .leftlanes[ll]
                                        .roadmark[0]
                                        ._line
                                    )
                                ):
                                    prev_remainder = self._get_previous_remainder(
                                        connected_lane_section,
                                        i_line,
                                        "left",
                                        contact_point,
                                        ll,
                                        ls,
                                        "end",
                                    )
                                    self.lanesections[ls].leftlanes[ll].roadmark[
                                        0
                                    ]._line[i_line].adjust_soffset(
                                        seg_length, previous_offset=prev_remainder
                                    )
                                self._adjust_for_missing_line_offset(
                                    self.lanesections[ls].leftlanes[ll].roadmark[0]
                                )

                if self._center_lane_adjustable:
                    if self._check_valid_mark_type(self.lanesections[ls].centerlane):
                        if (
                            ls == len(self.lanesections) - 1
                            and connected_lane_section is None
                        ):
                            set_zero_remainder_to_lines(
                                self.lanesections[ls].centerlane, seg_length
                            )
                        else:
                            for i_line in range(
                                len(self.lanesections[ls].centerlane.roadmark[0]._line)
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "center",
                                    contact_point,
                                    None,
                                    ls,
                                    "end",
                                )
                                self.lanesections[ls].centerlane.roadmark[0]._line[
                                    i_line
                                ].adjust_soffset(
                                    seg_length, previous_offset=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].centerlane.roadmark[0]
                            )

    def get_element(self):
        """returns the elementTree of Lanes"""
        element = ET.Element("lanes")
        self._add_additional_data_to_element(element)
        for l in self.laneoffsets:
            element.append(l.get_element())
        for l in self.lanesections:
            element.append(l.get_element())
        return element

Ancestors

Methods

def add_laneoffset(self, laneoffset)

adds a lane offset to Lanes

Parameters

laneoffset (LaneOffset): a LaneOffset to add
Expand source code
def add_laneoffset(self, laneoffset):
    """adds a lane offset to Lanes

    Parameters
    ----------
        laneoffset (LaneOffset): a LaneOffset to add
    """
    if not isinstance(laneoffset, LaneOffset):
        raise TypeError(
            "add_laneoffset requires a LaneOffset as input, not "
            + str(type(laneoffset))
        )
    self.laneoffsets.append(laneoffset)
    return self
def add_lanesection(self, lanesection, lanelinks=None)

creates the Lanes element of opendrive

Parameters

lanesection (LaneSection): a LaneSection to add

lanelink (LaneLinker): (optional) a LaneLink to add
Expand source code
def add_lanesection(self, lanesection, lanelinks=None):
    """creates the Lanes element of opendrive

    Parameters
    ----------
        lanesection (LaneSection): a LaneSection to add

        lanelink (LaneLinker): (optional) a LaneLink to add

    """
    # add links to the lanes
    if lanelinks:
        # loop over all links
        if not isinstance(lanelinks, list):
            lanelinks = [lanelinks]
        for lanelink in lanelinks:
            for link in lanelink.links:
                # check if link already added
                if not link.used:
                    link.predecessor.add_link("successor", link.successor.lane_id)
                    link.successor.add_link("predecessor", link.predecessor.lane_id)
                    link.used = True

    self.lanesections.append(lanesection)
    return self
def adjust_road_marks_from_end(self, total_road_length, connected_lane_section=None, contact_point=ContactPoint.end)

Adjusts road marks from the end of the road, based on the connected lane section. If connected_lane_section is not provided, the last roadmark will be placed with zero distance to the end of the road

Parameters

total_road_length (float): total length of the road

connected_lane_section (LaneSection): the lane section connected to the road
    Default: None

contact_point (ContactPoint)
    Default: ContactPoint.end
Expand source code
def adjust_road_marks_from_end(
    self,
    total_road_length,
    connected_lane_section=None,
    contact_point=ContactPoint.end,
):
    """Adjusts road marks from the end of the road, based on the connected lane section.
    If connected_lane_section is not provided, the last roadmark will be placed with zero
    distance to the end of the road

    Parameters
    ----------
        total_road_length (float): total length of the road

        connected_lane_section (LaneSection): the lane section connected to the road
            Default: None

        contact_point (ContactPoint)
            Default: ContactPoint.end
    """
    if not self.roadmarks_adjusted:
        self._validity_check_for_roadmark_adjustment()
        self.roadmarks_adjusted = True

        def set_zero_remainder_to_lines(lane, seg_length):
            for i_line in range(len(lane.roadmark[0]._line)):
                lane.roadmark[0]._line[i_line].adjust_soffset(
                    seg_length, remainder=0
                )

        for ls in range(len(self.lanesections) - 1, -1, -1):
            seg_length = self._get_seg_length(total_road_length, ls)
            if self._right_lanes_adjustable:
                for rl in range(len(self.lanesections[ls].rightlanes)):
                    if self._check_valid_mark_type(
                        self.lanesections[ls].rightlanes[rl]
                    ):
                        if (
                            ls == len(self.lanesections) - 1
                            and connected_lane_section is None
                        ):
                            set_zero_remainder_to_lines(
                                self.lanesections[ls].rightlanes[rl], seg_length
                            )
                        else:
                            for i_line in range(
                                len(
                                    self.lanesections[ls]
                                    .rightlanes[rl]
                                    .roadmark[0]
                                    ._line
                                )
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "right",
                                    contact_point,
                                    rl,
                                    ls,
                                    "end",
                                )
                                self.lanesections[ls].rightlanes[rl].roadmark[
                                    0
                                ]._line[i_line].adjust_soffset(
                                    seg_length, previous_offset=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].rightlanes[rl].roadmark[0]
                            )
            if self._left_lanes_adjustable:
                for ll in range(len(self.lanesections[ls].leftlanes)):
                    if self._check_valid_mark_type(
                        self.lanesections[ls].leftlanes[ll]
                    ):
                        if (
                            ls == len(self.lanesections) - 1
                            and connected_lane_section is None
                        ):
                            set_zero_remainder_to_lines(
                                self.lanesections[ls].leftlanes[ll], seg_length
                            )
                        else:
                            for i_line in range(
                                len(
                                    self.lanesections[ls]
                                    .leftlanes[ll]
                                    .roadmark[0]
                                    ._line
                                )
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "left",
                                    contact_point,
                                    ll,
                                    ls,
                                    "end",
                                )
                                self.lanesections[ls].leftlanes[ll].roadmark[
                                    0
                                ]._line[i_line].adjust_soffset(
                                    seg_length, previous_offset=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].leftlanes[ll].roadmark[0]
                            )

            if self._center_lane_adjustable:
                if self._check_valid_mark_type(self.lanesections[ls].centerlane):
                    if (
                        ls == len(self.lanesections) - 1
                        and connected_lane_section is None
                    ):
                        set_zero_remainder_to_lines(
                            self.lanesections[ls].centerlane, seg_length
                        )
                    else:
                        for i_line in range(
                            len(self.lanesections[ls].centerlane.roadmark[0]._line)
                        ):
                            prev_remainder = self._get_previous_remainder(
                                connected_lane_section,
                                i_line,
                                "center",
                                contact_point,
                                None,
                                ls,
                                "end",
                            )
                            self.lanesections[ls].centerlane.roadmark[0]._line[
                                i_line
                            ].adjust_soffset(
                                seg_length, previous_offset=prev_remainder
                            )
                        self._adjust_for_missing_line_offset(
                            self.lanesections[ls].centerlane.roadmark[0]
                        )
def adjust_road_marks_from_start(self, total_road_length, connected_lane_section=None, contact_point=ContactPoint.end)

Adjusts road marks from the start of the road, based on the connected lane section. If connected_lane_section is not provided, the last roadmark will be placed with zero distance to the start of the road

Parameters

total_road_length (float): total length of the road

connected_lane_section (LaneSection): the lane section connected to the road
    Default: None

contact_point (ContactPoint)
    Default: ContactPoint.end
Expand source code
def adjust_road_marks_from_start(
    self,
    total_road_length,
    connected_lane_section=None,
    contact_point=ContactPoint.end,
):
    """Adjusts road marks from the start of the road, based on the connected lane section.
    If connected_lane_section is not provided, the last roadmark will be placed with zero
    distance to the start of the road

    Parameters
    ----------
        total_road_length (float): total length of the road

        connected_lane_section (LaneSection): the lane section connected to the road
            Default: None

        contact_point (ContactPoint)
            Default: ContactPoint.end
    """
    if not self.roadmarks_adjusted:
        self._validity_check_for_roadmark_adjustment()
        self.roadmarks_adjusted = True

        def set_zero_offset_to_lines(lane, seg_length):
            for i_line in range(len(lane.roadmark[0]._line)):
                lane.roadmark[0]._line[i_line].adjust_remainder(
                    seg_length, soffset=0
                )

        for ls in range(0, len(self.lanesections)):
            seg_length = self._get_seg_length(total_road_length, ls)
            if self._right_lanes_adjustable:
                for rl in range(len(self.lanesections[ls].rightlanes)):
                    if self._check_valid_mark_type(
                        self.lanesections[ls].rightlanes[rl]
                    ):
                        if ls == 0 and connected_lane_section is None:
                            set_zero_offset_to_lines(
                                self.lanesections[ls].rightlanes[rl], seg_length
                            )
                        else:
                            for i_line in range(
                                len(
                                    self.lanesections[ls]
                                    .rightlanes[rl]
                                    .roadmark[0]
                                    ._line
                                )
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "right",
                                    contact_point,
                                    rl,
                                    ls,
                                    "start",
                                )
                                self.lanesections[ls].rightlanes[rl].roadmark[
                                    0
                                ]._line[i_line].adjust_remainder(
                                    seg_length, previous_remainder=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].rightlanes[rl].roadmark[0]
                            )
            if self._left_lanes_adjustable:
                for ll in range(len(self.lanesections[ls].leftlanes)):
                    if self._check_valid_mark_type(
                        self.lanesections[ls].leftlanes[ll]
                    ):
                        if ls == 0 and connected_lane_section is None:
                            set_zero_offset_to_lines(
                                self.lanesections[ls].leftlanes[ll], seg_length
                            )
                        else:
                            for i_line in range(
                                len(
                                    self.lanesections[ls]
                                    .leftlanes[ll]
                                    .roadmark[0]
                                    ._line
                                )
                            ):
                                prev_remainder = self._get_previous_remainder(
                                    connected_lane_section,
                                    i_line,
                                    "left",
                                    contact_point,
                                    ll,
                                    ls,
                                    "start",
                                )
                                self.lanesections[ls].leftlanes[ll].roadmark[
                                    0
                                ]._line[i_line].adjust_remainder(
                                    seg_length, previous_remainder=prev_remainder
                                )
                            self._adjust_for_missing_line_offset(
                                self.lanesections[ls].leftlanes[ll].roadmark[0]
                            )
            if self._center_lane_adjustable:
                if self._check_valid_mark_type(self.lanesections[ls].centerlane):
                    if ls == 0 and connected_lane_section is None:
                        set_zero_offset_to_lines(
                            self.lanesections[ls].centerlane, seg_length
                        )
                    else:
                        for i_line in range(
                            len(self.lanesections[ls].centerlane.roadmark[0]._line)
                        ):
                            prev_remainder = self._get_previous_remainder(
                                connected_lane_section,
                                i_line,
                                "center",
                                contact_point,
                                None,
                                ls,
                                "start",
                            )
                            self.lanesections[ls].centerlane.roadmark[0]._line[
                                i_line
                            ].adjust_remainder(
                                seg_length, previous_remainder=prev_remainder
                            )
                        self._adjust_for_missing_line_offset(
                            self.lanesections[ls].centerlane.roadmark[0]
                        )
def get_element(self)

returns the elementTree of Lanes

Expand source code
def get_element(self):
    """returns the elementTree of Lanes"""
    element = ET.Element("lanes")
    self._add_additional_data_to_element(element)
    for l in self.laneoffsets:
        element.append(l.get_element())
    for l in self.lanesections:
        element.append(l.get_element())
    return element

Inherited members

class RoadLine (width=0, length=0, space=0, toffset=0, soffset=0, rule=None, color=None)

creates a Line type of to be used in roadmark

Parameters

width (float): with of the line
    Default: 0
length (float): length of the line
    Default: 0
space (float): length of space between (broken) lines
    Default: 0
toffset (float): offset in t
    Default: 0
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)

color (RoadMarkColor): color of line (optional)

Attributes

length (float): length of the line

space (float): length of space between (broken) lines

toffset (float): offset in t

soffset (float): offset in s

rule (MarkRule): mark rule

width (float): with of the line

color (RoadMarkColor): color of line

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of FileHeader

initalizes the RoadLine

Parameters

width (float): with of the line
    Default: 0
length (float): length of the line
    Default: 0
space (float): length of space between (broken) lines
    Default: 0
toffset (float): offset in t
    Default: 0
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)

color (RoadMarkColor): color of line (optional)
Expand source code
class RoadLine(XodrBase):
    """creates a Line type of to be used in roadmark

    Parameters
    ----------
        width (float): with of the line
            Default: 0
        length (float): length of the line
            Default: 0
        space (float): length of space between (broken) lines
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

        color (RoadMarkColor): color of line (optional)

    Attributes
    ----------
        length (float): length of the line

        space (float): length of space between (broken) lines

        toffset (float): offset in t

        soffset (float): offset in s

        rule (MarkRule): mark rule

        width (float): with of the line

        color (RoadMarkColor): color of line

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of FileHeader

    """

    # TODO: check this for 1.5
    def __init__(
        self, width=0, length=0, space=0, toffset=0, soffset=0, rule=None, color=None
    ):
        """initalizes the RoadLine

        Parameters
        ----------
            width (float): with of the line
                Default: 0
            length (float): length of the line
                Default: 0
            space (float): length of space between (broken) lines
                Default: 0
            toffset (float): offset in t
                Default: 0
            soffset (float): offset in s
                Default: 0
            rule (MarkRule): mark rule (optional)

            color (RoadMarkColor): color of line (optional)


        """
        super().__init__()
        self.length = length
        self.space = space
        self.toffset = toffset
        self.rule = rule
        self.soffset = soffset
        self.width = width
        self.color = color
        self._remainder = 0

    def __eq__(self, other):
        if isinstance(other, RoadLine) and super().__eq__(other):
            if self.get_attributes() == other.get_attributes():
                return True
        return False

    def adjust_remainder(self, total_length, soffset=None, previous_remainder=None):
        """adjust_remainder is used to calculated and set the remainer of a broken mark for offset adjustments

        Parameters
        ----------
            total_length (float): the lenght of the lanesection where this line is valid

            soffset (float): the wanted soffset (at beginning of line), use this or previous remainder
                Default: use defined in class

            previous_remainder (float): the remainder of the previous line, use this or soffset
                Default: use defined in class
        """
        if soffset and previous_remainder:
            raise ToManyOptionalArguments(
                "for adjusting line lengths, use only soffset or previous_remainder."
            )
        if soffset is not None:
            self.soffset = soffset
        if previous_remainder is not None:
            self.soffset = self.space - previous_remainder
        self._remainder = self._calculate_remainder_of_line(self.soffset, total_length)

    def shift_soffset(self):
        """shifts the soffset one period"""
        self.soffset += self.space + self.length

    def adjust_soffset(self, total_length, remainder=None, previous_offset=None):
        """adjust_soffset is used to calculated and set the soffset of a broken mark for offset adjustments

        Parameters
        ----------
            total_length (float): the lenght of the lanesection where this line is valid

            remainder (float): the wanted remainder ("soffset" at end of line), use this or previous_offset
                Default: use defined in class

            previous_offset (float): the soffset of the previous line, use this or remainder
                Default: use defined in class
        """
        if remainder and previous_offset:
            raise ToManyOptionalArguments(
                "for adjusting line lengths, use only soffset or previous_remainder."
            )
        if remainder is not None:
            self._remainder = remainder
        if previous_offset is not None:
            self._remainder = self.space - previous_offset
        self.soffset = self._calculate_remainder_of_line(self._remainder, total_length)

    def _calculate_remainder_of_line(self, soffset, total_length):
        n = (total_length - soffset + self.space) / (self.space + self.length)
        return (
            total_length
            - soffset
            - np.floor(n) * (self.space + self.length)
            + self.space
        )

    def get_attributes(self):
        """returns the attributes of the Lane as a dict"""
        retdict = {}
        retdict["length"] = str(self.length)
        retdict["space"] = str(self.space)
        retdict["tOffset"] = str(self.toffset)
        retdict["width"] = str(self.width)
        retdict["sOffset"] = str(self.soffset)
        # if self.color:
        # retdict['color'] = enum2str(self.color)
        if self.rule:
            retdict["rule"] = enum2str(self.rule)
        return retdict

    def get_element(self):
        """returns the elementTree of the RoadLine"""
        element = ET.Element("line", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        return element

Ancestors

Methods

def adjust_remainder(self, total_length, soffset=None, previous_remainder=None)

adjust_remainder is used to calculated and set the remainer of a broken mark for offset adjustments

Parameters

total_length (float): the lenght of the lanesection where this line is valid

soffset (float): the wanted soffset (at beginning of line), use this or previous remainder
    Default: use defined in class

previous_remainder (float): the remainder of the previous line, use this or soffset
    Default: use defined in class
Expand source code
def adjust_remainder(self, total_length, soffset=None, previous_remainder=None):
    """adjust_remainder is used to calculated and set the remainer of a broken mark for offset adjustments

    Parameters
    ----------
        total_length (float): the lenght of the lanesection where this line is valid

        soffset (float): the wanted soffset (at beginning of line), use this or previous remainder
            Default: use defined in class

        previous_remainder (float): the remainder of the previous line, use this or soffset
            Default: use defined in class
    """
    if soffset and previous_remainder:
        raise ToManyOptionalArguments(
            "for adjusting line lengths, use only soffset or previous_remainder."
        )
    if soffset is not None:
        self.soffset = soffset
    if previous_remainder is not None:
        self.soffset = self.space - previous_remainder
    self._remainder = self._calculate_remainder_of_line(self.soffset, total_length)
def adjust_soffset(self, total_length, remainder=None, previous_offset=None)

adjust_soffset is used to calculated and set the soffset of a broken mark for offset adjustments

Parameters

total_length (float): the lenght of the lanesection where this line is valid

remainder (float): the wanted remainder ("soffset" at end of line), use this or previous_offset
    Default: use defined in class

previous_offset (float): the soffset of the previous line, use this or remainder
    Default: use defined in class
Expand source code
def adjust_soffset(self, total_length, remainder=None, previous_offset=None):
    """adjust_soffset is used to calculated and set the soffset of a broken mark for offset adjustments

    Parameters
    ----------
        total_length (float): the lenght of the lanesection where this line is valid

        remainder (float): the wanted remainder ("soffset" at end of line), use this or previous_offset
            Default: use defined in class

        previous_offset (float): the soffset of the previous line, use this or remainder
            Default: use defined in class
    """
    if remainder and previous_offset:
        raise ToManyOptionalArguments(
            "for adjusting line lengths, use only soffset or previous_remainder."
        )
    if remainder is not None:
        self._remainder = remainder
    if previous_offset is not None:
        self._remainder = self.space - previous_offset
    self.soffset = self._calculate_remainder_of_line(self._remainder, total_length)
def get_attributes(self)

returns the attributes of the Lane as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the Lane as a dict"""
    retdict = {}
    retdict["length"] = str(self.length)
    retdict["space"] = str(self.space)
    retdict["tOffset"] = str(self.toffset)
    retdict["width"] = str(self.width)
    retdict["sOffset"] = str(self.soffset)
    # if self.color:
    # retdict['color'] = enum2str(self.color)
    if self.rule:
        retdict["rule"] = enum2str(self.rule)
    return retdict
def get_element(self)

returns the elementTree of the RoadLine

Expand source code
def get_element(self):
    """returns the elementTree of the RoadLine"""
    element = ET.Element("line", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    return element
def shift_soffset(self)

shifts the soffset one period

Expand source code
def shift_soffset(self):
    """shifts the soffset one period"""
    self.soffset += self.space + self.length

Inherited members

class RoadMark (marking_type, width=None, length=None, space=None, toffset=None, soffset=0, rule=None, color=RoadMarkColor.standard, marking_weight=RoadMarkWeight.standard, height=0.02, laneChange=None)

creates a RoadMark of opendrive

Parameters

marking_type (RoadMarkType): the type of marking

width (float): with of the line
    Default: None
length (float): length of the line
    Default: 0
toffset (float): offset in t
    Default: 0
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)

color (RoadMarkColor): color of line (optional)

Attributes

marking_type (str): the type of marking

width (float): with of the line

length (float): length of the line
    Default: 0
toffset (float): offset in t
    Default: 0
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)

color (RoadMarkColor): color of line (optional)

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of FileHeader

add_roadmark(roadmark)
    adds a new roadmark to the lane

initializes the RoadMark

Parameters

marking_type (str): the type of marking

width (float): width of the marking / line
    Default: None
length (float): length of the visible, marked part of the line (used for broken lines)
    Default: None
space (float): length of the invisible, unmarked part of the line (used for broken lines)
    Default: None
toffset (float): offset in t
    Default: None
soffset (float): offset in s
    Default: 0
rule (MarkRule): mark rule (optional)
    Default: None
color (RoadMarkColor): color of marking
    Default: 'standard'
marking_weight (str): the weight of marking
    Default: standard
height (float): thickness of marking
    Default: 0.02
laneChange (LaneChange): indicates direction in which lane change is allowed
    Default: none
Expand source code
class RoadMark(XodrBase):
    """creates a RoadMark of opendrive

    Parameters
    ----------
        marking_type (RoadMarkType): the type of marking

        width (float): with of the line
            Default: None
        length (float): length of the line
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

        color (RoadMarkColor): color of line (optional)

    Attributes
    ----------
        marking_type (str): the type of marking

        width (float): with of the line

        length (float): length of the line
            Default: 0
        toffset (float): offset in t
            Default: 0
        soffset (float): offset in s
            Default: 0
        rule (MarkRule): mark rule (optional)

        color (RoadMarkColor): color of line (optional)

    Methods
    -------
        get_element(elementname)
            Returns the full ElementTree of the class

        get_attributes()
            Returns a dictionary of all attributes of FileHeader

        add_roadmark(roadmark)
            adds a new roadmark to the lane

    """

    def __init__(
        self,
        marking_type,
        width=None,
        length=None,
        space=None,
        toffset=None,
        soffset=0,
        rule=None,
        color=RoadMarkColor.standard,
        marking_weight=RoadMarkWeight.standard,
        height=0.02,
        laneChange=None,
    ):
        """initializes the RoadMark

        Parameters
        ----------
            marking_type (str): the type of marking

            width (float): width of the marking / line
                Default: None
            length (float): length of the visible, marked part of the line (used for broken lines)
                Default: None
            space (float): length of the invisible, unmarked part of the line (used for broken lines)
                Default: None
            toffset (float): offset in t
                Default: None
            soffset (float): offset in s
                Default: 0
            rule (MarkRule): mark rule (optional)
                Default: None
            color (RoadMarkColor): color of marking
                Default: 'standard'
            marking_weight (str): the weight of marking
                Default: standard
            height (float): thickness of marking
                Default: 0.02
            laneChange (LaneChange): indicates direction in which lane change is allowed
                Default: none

        """
        super().__init__()
        # required arguments - must be provided by user
        self.marking_type = marking_type

        # required arguments - must be provided by user or taken from defaults
        self.marking_weight = marking_weight
        self.color = color
        self.soffset = soffset
        self.height = height
        self.laneChange = laneChange

        # optional arguments - roadmark is valid without them being defined
        self.width = width
        self.length = length
        self.space = space
        self.toffset = toffset
        self.rule = rule

        # TODO: there may be more line child elements per roadmark, which is currently unsupported
        self._line = []
        self._explicit_line = []
        # check if arguments were passed that require line child element
        if any([length, space, toffset, rule]):
            # set defaults in case no values were provided
            # values for broken lines
            if marking_type == RoadMarkType.broken:
                self.length = length or 3
                self.space = space or 3
            # values for solid lines
            elif marking_type == RoadMarkType.solid:
                self.length = length or 3
                self.space = space or 0
            # create empty line if arguments are missing
            else:
                self.length = length or 0
                self.space = length or 0
                print(
                    "No defaults for arguments 'space' and 'length' for roadmark type",
                    enum2str(marking_type),
                    "available and no values were passed. Creating an empty roadmark.",
                )
            # set remaining defaults
            self.width = width or 0.2
            self.toffset = toffset or 0
            self.rule = rule or MarkRule.none
            self._line.append(
                RoadLine(
                    self.width,
                    self.length,
                    self.space,
                    self.toffset,
                    0,
                    self.rule,
                    self.color,
                )
            )

    def add_specific_road_line(self, line):
        """function to add your own roadline to the RoadMark, to use for multi line type of roadmarks,

        Parameters
        ----------
            line (RoadLine): the roadline to add

        """
        self._line.append(line)
        return self

    def add_explicit_road_line(self, line):
        """function to add a explicit roadline to the RoadMark,

        Parameters
        ----------
            line (RoadLine): the roadline to add

        """
        self._explicit_line.append(line)
        return self

    def __eq__(self, other):
        if isinstance(other, RoadMark) and super().__eq__(other):
            if (
                self._line == other._line
                and self._explicit_line == other._explicit_line
                and self.get_attributes() == other.get_attributes()
                and self.marking_type == other.marking_type
            ):
                return True
        return False

    def get_attributes(self):
        """returns the attributes of the RoadMark as a dict"""
        retdict = {}
        retdict["sOffset"] = str(self.soffset)
        retdict["type"] = enum2str(self.marking_type)
        retdict["weight"] = enum2str(self.marking_weight)
        retdict["color"] = enum2str(self.color)
        retdict["height"] = str(self.height)
        if self.width is not None:
            retdict["width"] = str(self.width)
        if self.laneChange is not None:
            retdict["laneChange"] = enum2str(self.laneChange)
        return retdict

    def get_element(self):
        """returns the elementTree of the RoadMark"""
        element = ET.Element("roadMark", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        if self._line:
            attribs = {"name": enum2str(self.marking_type)}
            if self.width is not None:
                attribs["width"] = str(self.width)
            else:
                offsets = [x.toffset for x in self._line]

                attribs["width"] = str(
                    max(offsets)
                    - min(offsets)
                    + sum(
                        [
                            x.width
                            for x in self._line
                            if x.toffset in [max(offsets), min(offsets)]
                        ]
                    )
                )
            typeelement = ET.SubElement(
                element,
                "type",
                attrib=attribs,
            )
            for l in self._line:
                typeelement.append(l.get_element())
        if self._explicit_line:
            typeelement = ET.SubElement(
                element,
                "explicit",
            )
            for l in self._explicit_line:
                typeelement.append(l.get_element())
        return element

Ancestors

Methods

def add_explicit_road_line(self, line)

function to add a explicit roadline to the RoadMark,

Parameters

line (RoadLine): the roadline to add
Expand source code
def add_explicit_road_line(self, line):
    """function to add a explicit roadline to the RoadMark,

    Parameters
    ----------
        line (RoadLine): the roadline to add

    """
    self._explicit_line.append(line)
    return self
def add_specific_road_line(self, line)

function to add your own roadline to the RoadMark, to use for multi line type of roadmarks,

Parameters

line (RoadLine): the roadline to add
Expand source code
def add_specific_road_line(self, line):
    """function to add your own roadline to the RoadMark, to use for multi line type of roadmarks,

    Parameters
    ----------
        line (RoadLine): the roadline to add

    """
    self._line.append(line)
    return self
def get_attributes(self)

returns the attributes of the RoadMark as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the RoadMark as a dict"""
    retdict = {}
    retdict["sOffset"] = str(self.soffset)
    retdict["type"] = enum2str(self.marking_type)
    retdict["weight"] = enum2str(self.marking_weight)
    retdict["color"] = enum2str(self.color)
    retdict["height"] = str(self.height)
    if self.width is not None:
        retdict["width"] = str(self.width)
    if self.laneChange is not None:
        retdict["laneChange"] = enum2str(self.laneChange)
    return retdict
def get_element(self)

returns the elementTree of the RoadMark

Expand source code
def get_element(self):
    """returns the elementTree of the RoadMark"""
    element = ET.Element("roadMark", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    if self._line:
        attribs = {"name": enum2str(self.marking_type)}
        if self.width is not None:
            attribs["width"] = str(self.width)
        else:
            offsets = [x.toffset for x in self._line]

            attribs["width"] = str(
                max(offsets)
                - min(offsets)
                + sum(
                    [
                        x.width
                        for x in self._line
                        if x.toffset in [max(offsets), min(offsets)]
                    ]
                )
            )
        typeelement = ET.SubElement(
            element,
            "type",
            attrib=attribs,
        )
        for l in self._line:
            typeelement.append(l.get_element())
    if self._explicit_line:
        typeelement = ET.SubElement(
            element,
            "explicit",
        )
        for l in self._explicit_line:
            typeelement.append(l.get_element())
    return element

Inherited members