Module scenariogeneration.xodr.elevation

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

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

Copyright (c) 2022 The scenariogeneration Authors.

Classes

class ElevationCalculator (main_road: Road)
Expand source code
class ElevationCalculator:
    """ElevationCalculator is a helper class to add elevation profiles to a
    road based on its neighbors' elevations.

    Parameters
    ----------
    main_road : Road
        The road that an elevation should be added to.

    Methods
    -------
    add_successor(road)
        Adds a successor road to the main_road. This can be done
        multiple times for junctions.
    add_predecessor(road)
        Adds a predecessor road to the main_road. This can be done
        multiple times for junctions.
    create_profile(domain)
        Tries to create a profile of the domain (elevation or
        superelevation).
    set_zero_elevation()
        Sets an elevation at zero for the main_road.
    """

    def __init__(self, main_road: "Road") -> None:
        self.main_road = main_road
        self.successors = []
        self.predecessors = []
        self._super_elevation_needed = not self.main_road.is_adjusted(
            "superelevation"
        )
        self._elevation_needed = not self.main_road.is_adjusted("elevation")
        self._extra_elevation_needed = False
        self._reset_active_roads()

    def _reset_active_roads(self) -> None:
        """Resets all active roads for a calculation."""
        self._successor_road = None
        self._predecessor_road = None
        self._predecessor_cp = None
        self._successor_cp = None
        self._successor_lateral_offset = 0
        self._predecessor_lateral_offset = 0

    def set_zero_elevation(self) -> None:
        """Sets an elevation at zero for the main_road."""
        self.main_road.add_elevation(0, 0, 0, 0, 0)
        self._elevation_needed = False

    def _calculate_lateral_elevation_offset(
        self,
        road: _ElevationConnectionHelper,
        lanesection: int,
        offsets: int,
        main_lanesection: int,
    ) -> float:
        """Calculate an elevation offset needed if a direct junction is
        present.

        Parameters
        ----------
        road : _ElevationConnectionHelper
            The road to calculate the offset from.
        lanesection : int
            The connected lanesection.
        offsets : int
            How many lanes the offset is.
        main_lanesection : int
            The lanesection of the main_road.

        Returns
        -------
        float
            The elevation offset.
        """
        s_value = 0
        tvalue = 0
        if offsets == 0:
            return 0
        sign = 1
        # this is to find out if the main_road or the connected road has most lanes to calculate the offset
        if offsets < 0 and len(
            road.road.lanes.lanesections[lanesection].rightlanes
        ) < abs(offsets):
            road = self.main_road
            lanesection = main_lanesection
            offsets = -offsets
        elif offsets > 0 and len(
            road.road.lanes.lanesections[lanesection].leftlanes
        ) < abs(offsets):
            road = self.main_road
            lanesection = main_lanesection
            offsets = -offsets
        else:
            road = road.road

        if lanesection == -1:
            s_value = road.planview.get_total_length()
        if offsets < 0:
            for lane_iter in range(abs(offsets)):
                tvalue += (
                    road.lanes.lanesections[lanesection]
                    .rightlanes[lane_iter]
                    .get_width(s_value)
                )
        else:
            for lane_iter in range(abs(offsets)):
                tvalue -= (
                    road.lanes.lanesections[lanesection]
                    .leftlanes[lane_iter]
                    .get_width(s_value)
                )
        if tvalue != 0:
            self._extra_elevation_needed = True
        return road.lateralprofile.eval_t_superelevation_at_s(s_value, tvalue)

    def _calculate_lateral_offsets_based_on_superelevation(self) -> None:
        """Calculate the elevation offsets based on superelevation.

        This method must be run as soon as any road might have been
        updated to ensure correct offsets.
        """
        for successor_road in self.successors:
            if (
                successor_road.road.predecessor
                and successor_road.road.predecessor.element_type
                == ElementType.junction
                and self.main_road.id
                in list(successor_road.road.pred_direct_junction.keys())
            ):
                lane_offsets = successor_road.road.pred_direct_junction[
                    self.main_road.id
                ]
                successor_road.lateral_offset = (
                    self._calculate_lateral_elevation_offset(
                        successor_road, 0, lane_offsets, -1
                    )
                )

            elif (
                successor_road.road.successor
                and successor_road.road.successor.element_type
                == ElementType.junction
                and self.main_road.id
                in list(successor_road.road.succ_direct_junction.keys())
            ):
                lane_offsets = successor_road.road.succ_direct_junction[
                    self.main_road.id
                ]
                successor_road.lateral_offset = (
                    self._calculate_lateral_elevation_offset(
                        successor_road, -1, lane_offsets, -1
                    )
                )
        for predecessor_road in self.predecessors:
            if (
                predecessor_road.road.successor
                and predecessor_road.road.successor.element_type
                == ElementType.junction
                and self.main_road.id
                in list(predecessor_road.road.succ_direct_junction.keys())
            ):
                lane_offsets = predecessor_road.road.succ_direct_junction[
                    self.main_road.id
                ]
                predecessor_road.lateral_offset = (
                    self._calculate_lateral_elevation_offset(
                        predecessor_road, -1, lane_offsets, 0
                    )
                )
            elif (
                predecessor_road.road.predecessor
                and predecessor_road.road.predecessor.element_type
                == ElementType.junction
                and self.main_road.id
                in list(predecessor_road.road.pred_direct_junction.keys())
            ):
                lane_offsets = predecessor_road.road.pred_direct_junction[
                    self.main_road.id
                ]
                predecessor_road.lateral_offset = (
                    self._calculate_lateral_elevation_offset(
                        predecessor_road, -1, lane_offsets, 0
                    )
                )

    def add_successor(self, successor_road: "Road") -> None:
        """Add a succeeding road to the main_road.

        This method can be called multiple times for junctions.

        Parameters
        ----------
        successor_road : Road
            A road succeeding the main_road.
        """
        successor_lateral_offset = 0
        if successor_road.predecessor is not None and (
            successor_road.predecessor.element_type == ElementType.road
            and successor_road.predecessor.element_id == self.main_road.id
            or successor_road.predecessor.element_type == ElementType.junction
            and successor_road.pred_direct_junction
            and self.main_road.id
            in list(successor_road.pred_direct_junction.keys())
            or successor_road.predecessor.element_type == ElementType.junction
            and not successor_road.pred_direct_junction
            and self.main_road.road_type
            == successor_road.predecessor.element_id
        ):
            successor_cp = ContactPoint.start
        elif successor_road.successor is not None and (
            successor_road.successor.element_type == ElementType.road
            and successor_road.successor.element_id == self.main_road.id
            or successor_road.successor.element_type == ElementType.junction
            and successor_road.succ_direct_junction
            and self.main_road.id
            in list(successor_road.succ_direct_junction.keys())
            or successor_road.successor.element_type == ElementType.junction
            and not successor_road.succ_direct_junction
            and self.main_road.road_type == successor_road.successor.element_id
        ):
            successor_cp = ContactPoint.end
        else:
            raise ValueError("could not figure out the contact point")
        self.successors.append(
            _ElevationConnectionHelper(
                successor_road,
                "successor",
                successor_cp,
                successor_lateral_offset,
            )
        )
        self._calculate_lateral_offsets_based_on_superelevation()

    def add_predecessor(self, predecessor_road: "Road") -> None:
        """Adds a preceding road to the main_road.

        This method can be called multiple times for junctions.

        Parameters
        ----------
        predecessor_road : Road
            A road preceding the main_road.
        """
        predecessor_lateral_offset = 0
        if predecessor_road.predecessor is not None and (
            predecessor_road.predecessor.element_type == ElementType.road
            and predecessor_road.predecessor.element_id == self.main_road.id
            or predecessor_road.predecessor.element_type
            == ElementType.junction
            and predecessor_road.pred_direct_junction
            and self.main_road.id
            in list(predecessor_road.pred_direct_junction.keys())
            or predecessor_road.predecessor.element_type
            == ElementType.junction
            and not predecessor_road.pred_direct_junction
            and self.main_road.road_type
            == predecessor_road.predecessor.element_id
        ):
            predecessor_cp = ContactPoint.start
            if (
                predecessor_road.predecessor.element_type
                == ElementType.junction
            ):
                pass

        elif predecessor_road.successor is not None and (
            predecessor_road.successor.element_type == ElementType.road
            and predecessor_road.successor.element_id == self.main_road.id
            or predecessor_road.successor.element_type == ElementType.junction
            and predecessor_road.succ_direct_junction
            and self.main_road.id
            in list(predecessor_road.succ_direct_junction.keys())
            or predecessor_road.successor.element_type == ElementType.junction
            and not predecessor_road.succ_direct_junction
            and self.main_road.road_type
            == predecessor_road.successor.element_id
        ):
            predecessor_cp = ContactPoint.end
            if predecessor_road.successor.element_type == ElementType.junction:
                pass
        else:
            raise ValueError("could not figure out the contact point")
        self.predecessors.append(
            _ElevationConnectionHelper(
                predecessor_road,
                "predecessor",
                predecessor_cp,
                predecessor_lateral_offset,
            )
        )
        self._calculate_lateral_offsets_based_on_superelevation()

    def _set_active_roads(self, domain: str) -> None:
        """Checks what successors/predecessor roads are adjusted in the wanted
        domain and set those for calculations.

        Parameters
        ----------
        domain : str
            The domain (elevation, or superelevation).
        """
        for successor in self.successors:
            if successor.road.is_adjusted(domain):
                self._successor_road = successor.road
                self._successor_cp = successor.contact_point
                self._successor_lateral_offset = successor.lateral_offset

        for predecessor in self.predecessors:
            if predecessor.road.is_adjusted(domain):
                self._predecessor_road = predecessor.road
                self._predecessor_cp = predecessor.contact_point
                self._predecessor_lateral_offset = predecessor.lateral_offset

    def _create_elevation(self) -> None:
        """Method that calculates and adds the elevation profile to the
        main_road based on the elevations on the predecessor or successor
        roads."""
        self._calculate_lateral_offsets_based_on_superelevation()
        self._set_active_roads("elevation")
        if self._successor_road and self._predecessor_road:
            (
                pre_s,
                pre_sign,
                suc_s,
                suc_sign,
                A,
            ) = self._get_related_data_for_double_connection()
            if self.main_road.road_type != -1:
                warn(
                    "Having automatic elevation adjustment for junction roads will yeild in ambigious results, please set the elevation for the connecting roads."
                )
            B = np.array(
                [
                    self._predecessor_road.elevationprofile.eval_at_s(pre_s)
                    + self._predecessor_lateral_offset,
                    pre_sign
                    * self._predecessor_road.elevationprofile.eval_derivative_at_s(
                        pre_s
                    ),
                    self._successor_road.elevationprofile.eval_at_s(suc_s)
                    + self._successor_lateral_offset,
                    suc_sign
                    * self._successor_road.elevationprofile.eval_derivative_at_s(
                        suc_s
                    ),
                ]
            )
            coeffs = np.linalg.solve(A, B)
            self.main_road.add_elevation(
                0, coeffs[0], coeffs[1], coeffs[2], coeffs[3]
            )
            self._elevation_needed = False
        elif self._successor_road or self._predecessor_road:
            if self.main_road.road_type != -1:
                warn(
                    "Having automatic elevation adjustment for junction roads will yeild in ambigious results, please set the elevation for the connecting roads."
                )

            (
                related_road,
                neighbor_s,
                sign,
                main_s,
            ) = self._get_related_data_for_single_connection()
            b = sign * related_road.elevationprofile.eval_derivative_at_s(
                neighbor_s
            )
            a = (
                related_road.elevationprofile.eval_at_s(neighbor_s)
                - b * main_s
                + self._successor_lateral_offset
                + self._predecessor_lateral_offset
            )
            self.main_road.add_elevation(0, a, b, 0, 0)
            self._elevation_needed = False
        self._reset_active_roads()

    def _get_related_data_for_single_connection(
        self,
    ) -> tuple["Road", float, int, float]:
        """Common functionality for both elevation and superelevation. For a
        road that has elevations on one side to adjust to.

        Returns
        -------
        related_road : Road
            The road to adjust to.
        neighbor_s : float
            S value to be used on the related_road.
        sign : int
            Sign switch.
        main_s : float
            S to be used on the main_road.
        """
        if self._successor_road:
            main_s = self.main_road.planview.get_total_length()
            related_road = self._successor_road
            if self._successor_cp == ContactPoint.start:
                neighbor_s = 0
                sign = 1
            else:
                neighbor_s = related_road.planview.get_total_length()
                sign = -1
        else:
            main_s = 0
            related_road = self._predecessor_road
            if self._predecessor_cp == ContactPoint.start:
                neighbor_s = 0
                sign = -1
            else:
                neighbor_s = related_road.planview.get_total_length()
                sign = 1
        return related_road, neighbor_s, sign, main_s

    def _get_related_data_for_double_connection(
        self,
    ) -> tuple[float, int, float, int, np.ndarray]:
        """Common functionality for both elevation and superelevation. For a
        road that has elevations on both sides to adjust to.

        Returns
        -------
        pre_s : float
            S of the predecessor.
        pre_sign : int
            Sign switch of predecessor.
        suc_s : float
            S of the successor.
        suc_sign : int
            Sign switch of successor.
        A : numpy.ndarray
            Matrix for solving continuous derivative and value.
        """
        if self._predecessor_cp == ContactPoint.start:
            pre_s = 0
            pre_sign = -1
        else:
            pre_s = self._predecessor_road.planview.get_total_length()
            pre_sign = 1
        if self._successor_cp == ContactPoint.start:
            suc_s = 0
            suc_sign = 1
        else:
            suc_s = self._successor_road.planview.get_total_length()
            suc_sign = -1
        main_s = self.main_road.planview.get_total_length()
        A = np.array(
            [
                [1, 0, 0, 0],
                [0, 1, 0, 0],
                [1, main_s, main_s**2, main_s**3],
                [0, 1, 2 * main_s, 3 * main_s**2],
            ]
        )
        return pre_s, pre_sign, suc_s, suc_sign, A

    def _create_super_elevation(self) -> None:
        self._set_active_roads("superelevation")
        if self._successor_road and self._predecessor_road:
            (
                pre_s,
                pre_sign,
                suc_s,
                suc_sign,
                A,
            ) = self._get_related_data_for_double_connection()
            if self.main_road.road_type != -1:
                warn(
                    "Having automatic elevation adjustment for junction roads will yeild in ambigious results, please set the elevation for the connecting roads."
                )
            B = np.array(
                [
                    pre_sign
                    * self._predecessor_road.lateralprofile.eval_superelevation_at_s(
                        pre_s
                    ),
                    self._predecessor_road.lateralprofile.eval_superelevation_derivative_at_s(
                        pre_s
                    ),
                    suc_sign
                    * self._successor_road.lateralprofile.eval_superelevation_at_s(
                        suc_s
                    ),
                    self._successor_road.lateralprofile.eval_superelevation_derivative_at_s(
                        suc_s
                    ),
                ]
            )
            coeffs = np.linalg.solve(A, B)
            self.main_road.add_superelevation(
                0, coeffs[0], coeffs[1], coeffs[2], coeffs[3]
            )
            self._super_elevation_needed = False

        elif self._successor_road or self._predecessor_road:
            (
                related_road,
                neighbor_s,
                sign,
                _,
            ) = self._get_related_data_for_single_connection()
            if self.main_road.road_type != -1:
                warn(
                    "Having automatic elevation adjustment for junction roads will yeild in ambigious results, please set the elevation for the connecting roads."
                )
            a = sign * related_road.lateralprofile.eval_superelevation_at_s(
                neighbor_s
            )
            self.main_road.add_superelevation(0, a, 0, 0, 0)
            self._super_elevation_needed = False

        self._reset_active_roads()

    def create_profile(self, domain: str = "elevation") -> None:
        """Main method to try to calculate an elevation or superelevation.

        Parameters
        ----------
        domain : str
            What domain to calculate (elevation or superelevation).
            Default: elevation.
        """
        if domain == "elevation":
            if self._elevation_needed:
                self._create_elevation()
        elif domain == "superelevation":
            if self._super_elevation_needed:
                self._create_super_elevation()
                self._calculate_lateral_offsets_based_on_superelevation()
        elif domain == "shape":
            raise NotImplementedError(
                "Shape adjustment is not implemented yet."
            )
        else:
            raise ValueError(
                "Domain can only be: elevation, superelevation, or shape, "
                f"not {domain}."
            )

ElevationCalculator is a helper class to add elevation profiles to a road based on its neighbors' elevations.

Parameters

main_road : Road
The road that an elevation should be added to.

Methods

add_successor(road) Adds a successor road to the main_road. This can be done multiple times for junctions. add_predecessor(road) Adds a predecessor road to the main_road. This can be done multiple times for junctions. create_profile(domain) Tries to create a profile of the domain (elevation or superelevation). set_zero_elevation() Sets an elevation at zero for the main_road.

Methods

def add_predecessor(self, predecessor_road: Road)
Expand source code
def add_predecessor(self, predecessor_road: "Road") -> None:
    """Adds a preceding road to the main_road.

    This method can be called multiple times for junctions.

    Parameters
    ----------
    predecessor_road : Road
        A road preceding the main_road.
    """
    predecessor_lateral_offset = 0
    if predecessor_road.predecessor is not None and (
        predecessor_road.predecessor.element_type == ElementType.road
        and predecessor_road.predecessor.element_id == self.main_road.id
        or predecessor_road.predecessor.element_type
        == ElementType.junction
        and predecessor_road.pred_direct_junction
        and self.main_road.id
        in list(predecessor_road.pred_direct_junction.keys())
        or predecessor_road.predecessor.element_type
        == ElementType.junction
        and not predecessor_road.pred_direct_junction
        and self.main_road.road_type
        == predecessor_road.predecessor.element_id
    ):
        predecessor_cp = ContactPoint.start
        if (
            predecessor_road.predecessor.element_type
            == ElementType.junction
        ):
            pass

    elif predecessor_road.successor is not None and (
        predecessor_road.successor.element_type == ElementType.road
        and predecessor_road.successor.element_id == self.main_road.id
        or predecessor_road.successor.element_type == ElementType.junction
        and predecessor_road.succ_direct_junction
        and self.main_road.id
        in list(predecessor_road.succ_direct_junction.keys())
        or predecessor_road.successor.element_type == ElementType.junction
        and not predecessor_road.succ_direct_junction
        and self.main_road.road_type
        == predecessor_road.successor.element_id
    ):
        predecessor_cp = ContactPoint.end
        if predecessor_road.successor.element_type == ElementType.junction:
            pass
    else:
        raise ValueError("could not figure out the contact point")
    self.predecessors.append(
        _ElevationConnectionHelper(
            predecessor_road,
            "predecessor",
            predecessor_cp,
            predecessor_lateral_offset,
        )
    )
    self._calculate_lateral_offsets_based_on_superelevation()

Adds a preceding road to the main_road.

This method can be called multiple times for junctions.

Parameters

predecessor_road : Road
A road preceding the main_road.
def add_successor(self, successor_road: Road)
Expand source code
def add_successor(self, successor_road: "Road") -> None:
    """Add a succeeding road to the main_road.

    This method can be called multiple times for junctions.

    Parameters
    ----------
    successor_road : Road
        A road succeeding the main_road.
    """
    successor_lateral_offset = 0
    if successor_road.predecessor is not None and (
        successor_road.predecessor.element_type == ElementType.road
        and successor_road.predecessor.element_id == self.main_road.id
        or successor_road.predecessor.element_type == ElementType.junction
        and successor_road.pred_direct_junction
        and self.main_road.id
        in list(successor_road.pred_direct_junction.keys())
        or successor_road.predecessor.element_type == ElementType.junction
        and not successor_road.pred_direct_junction
        and self.main_road.road_type
        == successor_road.predecessor.element_id
    ):
        successor_cp = ContactPoint.start
    elif successor_road.successor is not None and (
        successor_road.successor.element_type == ElementType.road
        and successor_road.successor.element_id == self.main_road.id
        or successor_road.successor.element_type == ElementType.junction
        and successor_road.succ_direct_junction
        and self.main_road.id
        in list(successor_road.succ_direct_junction.keys())
        or successor_road.successor.element_type == ElementType.junction
        and not successor_road.succ_direct_junction
        and self.main_road.road_type == successor_road.successor.element_id
    ):
        successor_cp = ContactPoint.end
    else:
        raise ValueError("could not figure out the contact point")
    self.successors.append(
        _ElevationConnectionHelper(
            successor_road,
            "successor",
            successor_cp,
            successor_lateral_offset,
        )
    )
    self._calculate_lateral_offsets_based_on_superelevation()

Add a succeeding road to the main_road.

This method can be called multiple times for junctions.

Parameters

successor_road : Road
A road succeeding the main_road.
def create_profile(self, domain: str = 'elevation') ‑> None
Expand source code
def create_profile(self, domain: str = "elevation") -> None:
    """Main method to try to calculate an elevation or superelevation.

    Parameters
    ----------
    domain : str
        What domain to calculate (elevation or superelevation).
        Default: elevation.
    """
    if domain == "elevation":
        if self._elevation_needed:
            self._create_elevation()
    elif domain == "superelevation":
        if self._super_elevation_needed:
            self._create_super_elevation()
            self._calculate_lateral_offsets_based_on_superelevation()
    elif domain == "shape":
        raise NotImplementedError(
            "Shape adjustment is not implemented yet."
        )
    else:
        raise ValueError(
            "Domain can only be: elevation, superelevation, or shape, "
            f"not {domain}."
        )

Main method to try to calculate an elevation or superelevation.

Parameters

domain : str
What domain to calculate (elevation or superelevation). Default: elevation.
def set_zero_elevation(self) ‑> None
Expand source code
def set_zero_elevation(self) -> None:
    """Sets an elevation at zero for the main_road."""
    self.main_road.add_elevation(0, 0, 0, 0, 0)
    self._elevation_needed = False

Sets an elevation at zero for the main_road.

class ElevationProfile
Expand source code
class ElevationProfile(XodrBase):
    """Create the elevationProfile element of the road in OpenDRIVE.

    Attributes
    ----------
    elevations : list of _Poly3Profile
        List of elevation profiles.

    Methods
    -------
    get_element(elementname)
        Returns the full ElementTree of the class.
    add_elevation(elevation)
        Adds an elevation profile to the road.
    """

    def __init__(self) -> None:
        """Initialize the ElevationProfile class."""
        self.elevations = []
        super().__init__()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, ElevationProfile) and super().__eq__(other):
            if self.elevations == other.elevations:
                return True
        return False

    def eval_at_s(self, s: float) -> float:
        return self.elevations[
            [i for i, x in enumerate(self.elevations) if x.s <= s][-1]
        ].eval_at_s(s)

    def eval_derivative_at_s(self, s: float) -> float:
        return self.elevations[
            [i for i, x in enumerate(self.elevations) if x.s <= s][-1]
        ].eval_derivative_at_s(s)

    def add_elevation(self, elevation: "_Poly3Profile") -> "ElevationProfile":
        """Add an elevation to the ElevationProfile.

        Parameters
        ----------
        elevation : _Poly3Profile
            The elevation profile to add to the ElevationProfile.

        Returns
        -------
        ElevationProfile
            The updated ElevationProfile object.
        """
        if not isinstance(elevation, _Poly3Profile):
            raise TypeError(
                "add_elevation requires an _Poly3Profile as input, not "
                + str(type(elevation))
            )
        self.elevations.append(elevation)
        return self

    def get_element(self) -> ET.Element:
        """Return the ElementTree of the ElevationProfile.

        Returns
        -------
        xml.etree.ElementTree.Element
            The elevationProfile element.
        """
        element = ET.Element("elevationProfile")
        self._add_additional_data_to_element(element)
        for i in self.elevations:
            element.append(i.get_element("elevation"))
        return element

Create the elevationProfile element of the road in OpenDRIVE.

Attributes

elevations : list of _Poly3Profile
List of elevation profiles.

Methods

get_element(elementname) Returns the full ElementTree of the class. add_elevation(elevation) Adds an elevation profile to the road.

Initialize the ElevationProfile class.

Ancestors

Methods

def add_elevation(self, elevation: _Poly3Profile) ‑> ElevationProfile
Expand source code
def add_elevation(self, elevation: "_Poly3Profile") -> "ElevationProfile":
    """Add an elevation to the ElevationProfile.

    Parameters
    ----------
    elevation : _Poly3Profile
        The elevation profile to add to the ElevationProfile.

    Returns
    -------
    ElevationProfile
        The updated ElevationProfile object.
    """
    if not isinstance(elevation, _Poly3Profile):
        raise TypeError(
            "add_elevation requires an _Poly3Profile as input, not "
            + str(type(elevation))
        )
    self.elevations.append(elevation)
    return self

Add an elevation to the ElevationProfile.

Parameters

elevation : _Poly3Profile
The elevation profile to add to the ElevationProfile.

Returns

ElevationProfile
The updated ElevationProfile object.
def eval_at_s(self, s: float) ‑> float
Expand source code
def eval_at_s(self, s: float) -> float:
    return self.elevations[
        [i for i, x in enumerate(self.elevations) if x.s <= s][-1]
    ].eval_at_s(s)
def eval_derivative_at_s(self, s: float) ‑> float
Expand source code
def eval_derivative_at_s(self, s: float) -> float:
    return self.elevations[
        [i for i, x in enumerate(self.elevations) if x.s <= s][-1]
    ].eval_derivative_at_s(s)
def get_element(self) ‑> xml.etree.ElementTree.Element
Expand source code
def get_element(self) -> ET.Element:
    """Return the ElementTree of the ElevationProfile.

    Returns
    -------
    xml.etree.ElementTree.Element
        The elevationProfile element.
    """
    element = ET.Element("elevationProfile")
    self._add_additional_data_to_element(element)
    for i in self.elevations:
        element.append(i.get_element("elevation"))
    return element

Return the ElementTree of the ElevationProfile.

Returns

xml.etree.ElementTree.Element
The elevationProfile element.

Inherited members

class LateralProfile
Expand source code
class LateralProfile(XodrBase):
    """Create the lateralProfile element of the road in OpenDRIVE.

    Attributes
    ----------
    superelevations : list of _Poly3Profile
        List of superelevations of the road.
    shapes : list of _Poly3Profile
        List of shapes for the road.

    Methods
    -------
    get_element(elementname)
        Returns the full ElementTree of the class.
    add_superelevation(superelevation)
        Adds a superelevation profile to the road.
    add_shape(shape)
        Adds a shape to the lateral profile.
    """

    def __init__(self) -> None:
        """Initialize the LateralProfile class."""
        super().__init__()
        self.superelevations = []
        self.shapes = []

    def __eq__(self, other: object) -> bool:
        if isinstance(other, LateralProfile) and super().__eq__(other):
            if (
                self.superelevations == other.superelevations
                and self.shapes == other.shapes
            ):
                return True
        return False

    def add_superelevation(
        self, superelevation: "_Poly3Profile"
    ) -> "LateralProfile":
        """Add a superelevation to the LateralProfile.

        Parameters
        ----------
        superelevation : _Poly3Profile
            The superelevation profile to add to the LateralProfile.

        Returns
        -------
        LateralProfile
            The updated LateralProfile object.
        """
        if not isinstance(superelevation, _Poly3Profile):
            raise TypeError(
                "add_superelevation requires an _Poly3Profile as input, not "
                + str(type(superelevation))
            )
        self.superelevations.append(superelevation)
        return self

    def eval_superelevation_at_s(self, s: float) -> float:
        if self.superelevations:
            return self.superelevations[
                [i for i, x in enumerate(self.superelevations) if x.s <= s][-1]
            ].eval_at_s(s)
        return 0

    def eval_t_superelevation_at_s(self, s: float, t: float) -> float:
        if self.superelevations:
            return self.superelevations[
                [i for i, x in enumerate(self.superelevations) if x.s <= s][-1]
            ].eval_t_at_s(s, t)
        return 0

    def eval_superelevation_derivative_at_s(self, s: float) -> float:
        if self.superelevations:
            return self.superelevations[
                [i for i, x in enumerate(self.superelevations) if x.s <= s][-1]
            ].eval_derivative_at_s(s)
        return 0

    def add_shape(self, shape: "_Poly3Profile") -> "LateralProfile":
        """Add a shape to the LateralProfile.

        Parameters
        ----------
        shape : _Poly3Profile
            The shape profile to add to the LateralProfile.

        Returns
        -------
        LateralProfile
            The updated LateralProfile object.
        """
        if not isinstance(shape, _Poly3Profile):
            raise TypeError(
                "add_shape requires an _Poly3Profile as input, not "
                + str(type(shape))
            )
        self.shapes.append(shape)
        return self

    def get_element(self) -> ET.Element:
        """Return the ElementTree of the LateralProfile.

        Returns
        -------
        xml.etree.ElementTree.Element
            The lateralProfile element.
        """
        element = ET.Element("lateralProfile")
        self._add_additional_data_to_element(element)
        for i in self.superelevations:
            element.append(i.get_element("superelevation"))
        for i in self.shapes:
            element.append(i.get_element("shape"))
        return element

Create the lateralProfile element of the road in OpenDRIVE.

Attributes

superelevations : list of _Poly3Profile
List of superelevations of the road.
shapes : list of _Poly3Profile
List of shapes for the road.

Methods

get_element(elementname) Returns the full ElementTree of the class. add_superelevation(superelevation) Adds a superelevation profile to the road. add_shape(shape) Adds a shape to the lateral profile.

Initialize the LateralProfile class.

Ancestors

Methods

def add_shape(self, shape: _Poly3Profile) ‑> LateralProfile
Expand source code
def add_shape(self, shape: "_Poly3Profile") -> "LateralProfile":
    """Add a shape to the LateralProfile.

    Parameters
    ----------
    shape : _Poly3Profile
        The shape profile to add to the LateralProfile.

    Returns
    -------
    LateralProfile
        The updated LateralProfile object.
    """
    if not isinstance(shape, _Poly3Profile):
        raise TypeError(
            "add_shape requires an _Poly3Profile as input, not "
            + str(type(shape))
        )
    self.shapes.append(shape)
    return self

Add a shape to the LateralProfile.

Parameters

shape : _Poly3Profile
The shape profile to add to the LateralProfile.

Returns

LateralProfile
The updated LateralProfile object.
def add_superelevation(self, superelevation: _Poly3Profile) ‑> LateralProfile
Expand source code
def add_superelevation(
    self, superelevation: "_Poly3Profile"
) -> "LateralProfile":
    """Add a superelevation to the LateralProfile.

    Parameters
    ----------
    superelevation : _Poly3Profile
        The superelevation profile to add to the LateralProfile.

    Returns
    -------
    LateralProfile
        The updated LateralProfile object.
    """
    if not isinstance(superelevation, _Poly3Profile):
        raise TypeError(
            "add_superelevation requires an _Poly3Profile as input, not "
            + str(type(superelevation))
        )
    self.superelevations.append(superelevation)
    return self

Add a superelevation to the LateralProfile.

Parameters

superelevation : _Poly3Profile
The superelevation profile to add to the LateralProfile.

Returns

LateralProfile
The updated LateralProfile object.
def eval_superelevation_at_s(self, s: float) ‑> float
Expand source code
def eval_superelevation_at_s(self, s: float) -> float:
    if self.superelevations:
        return self.superelevations[
            [i for i, x in enumerate(self.superelevations) if x.s <= s][-1]
        ].eval_at_s(s)
    return 0
def eval_superelevation_derivative_at_s(self, s: float) ‑> float
Expand source code
def eval_superelevation_derivative_at_s(self, s: float) -> float:
    if self.superelevations:
        return self.superelevations[
            [i for i, x in enumerate(self.superelevations) if x.s <= s][-1]
        ].eval_derivative_at_s(s)
    return 0
def eval_t_superelevation_at_s(self, s: float, t: float) ‑> float
Expand source code
def eval_t_superelevation_at_s(self, s: float, t: float) -> float:
    if self.superelevations:
        return self.superelevations[
            [i for i, x in enumerate(self.superelevations) if x.s <= s][-1]
        ].eval_t_at_s(s, t)
    return 0
def get_element(self) ‑> xml.etree.ElementTree.Element
Expand source code
def get_element(self) -> ET.Element:
    """Return the ElementTree of the LateralProfile.

    Returns
    -------
    xml.etree.ElementTree.Element
        The lateralProfile element.
    """
    element = ET.Element("lateralProfile")
    self._add_additional_data_to_element(element)
    for i in self.superelevations:
        element.append(i.get_element("superelevation"))
    for i in self.shapes:
        element.append(i.get_element("shape"))
    return element

Return the ElementTree of the LateralProfile.

Returns

xml.etree.ElementTree.Element
The lateralProfile element.

Inherited members