Module scenariogeneration.xodr.geometry

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.

"""

import xml.etree.ElementTree as ET

import numpy as np
import pyclothoids as pcloth
from .exceptions import (
    NotEnoughInputArguments,
    ToManyOptionalArguments,
    MixOfGeometryAddition,
)
from scipy.integrate import quad
from .utils import XodrBase


def wrap_pi(angle):
    return angle % (2 * np.pi)


class AdjustablePlanview:
    """AdjustablePlanview can be used to fit a geometry between two fixed roads.
    Especially useful when connecting larger networks with Spiral geometries included.
    """

    def __init__(
        self,
        left_lane_defs=None,
        right_lane_defs=None,
        center_road_mark=None,
        lane_width=None,
        lane_width_end=None,
    ):
        self.fixed = False
        self.adjusted = False
        self.left_lane_defs = left_lane_defs
        self.right_lane_defs = right_lane_defs
        self.center_road_mark = center_road_mark
        self.lane_width = lane_width
        self.lane_width_end = lane_width_end


class PlanView(XodrBase):
    """the PlanView is the geometrical description of a road,

    Parameters
    ----------
        x_start (float): start x coordinate of the first geometry
            Default: None

        y_start (float): start y coordinate of the first geometry
            Default: None

        h_start (float): starting heading of the first geometry
            Default: None

    Attributes
    ----------
        present_x (float): the start x coordinate of the next geometry added

        present_y (float): the y coordinate of the next geometry added

        present_h (float): the heading coordinate of the next geometry added

        present_s (float): the along road measure of the next geometry added

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

        get_total_length()
            Returns the full length of the PlanView

        add_geometry(geom,lenght)
            adds a new geometry entry to the planeview

        set_start_point(x_start,y_start,h_start)
            sets the start point and heading of the planview

        adjust_geometries()
            based on the start point, it will adjust all geometries in the planview

    """

    def __init__(self, x_start=None, y_start=None, h_start=None):
        """initalizes the PlanView
        Note: if multiple roads are used, the start values can be recalculated.


        """
        super().__init__()
        self.present_x = 0
        self.present_y = 0
        self.present_h = 0
        self.present_s = 0
        self.fixed = False
        if all([x_start != None, y_start != None, h_start != None]):
            self.set_start_point(x_start, y_start, h_start)
        elif any([x_start != None, y_start != None, h_start != None]):
            raise NotEnoughInputArguments(
                "If a start position is wanted for the PlanView, all inputs must be used."
            )

        self.x_start = None
        self.y_start = None
        self.h_start = None

        self.x_end = None
        self.y_end = None
        self.h_end = None

        self._raw_geometries = []
        self._adjusted_geometries = []
        self._overridden_headings = []

        self.adjusted = False
        # variable to track what mode of adding geometries are used

        self._addition_mode = None

    def __eq__(self, other):
        if isinstance(other, PlanView) and super().__eq__(other):
            if self.adjusted and other.adjusted:
                if self._adjusted_geometries == other._adjusted_geometries:
                    return True
            elif not self.adjusted and not other.adjusted:
                Warning(
                    "Comparing non adjusted geometries, default value will always be False"
                )
                return False

        return False

    def add_geometry(self, geom, heading=None):
        """add_geometry adds a geometry to the planview and will stich together all geometries (in order the order added)

            Should be used together with "adjust_roads_and_lanes" in the OpenDrive class.

            NOTE: DO NOT MIX WITH with add_fixed_geometry

        Parameters
        ----------
            geom (Line, Spiral, ParamPoly3, or Arc): the type of geometry

            heading (float): override the previous heading (optional), not recommended
                if used, use for ALL geometries

        """
        if self._addition_mode == "add_fixed_geometry":
            raise MixOfGeometryAddition(
                "A fixed geometry has already been added, please use either add_geometry or add_fixed_geometry"
            )

        if heading is not None:
            self._overridden_headings.append(heading)
        self._raw_geometries.append(geom)
        self._addition_mode = "add_geometry"
        return self

    def add_fixed_geometry(self, geom, x_start, y_start, h_start, s=None):
        """add_fixed_geometry adds a geometry to a certain point to the planview

            if s is used, the values will be coded and is up to the user to make correct, not for a correct opendrive file please add the geometires in order
            if s is not used, the geometries are supposed to be added in order (and s will be calculated)

            NOTE: DO NOT MIX WITH the method add_geometry

        Parameters
        ----------
            geom (Line, Spiral, ParamPoly3, or Arc): the geometry to add

            x_start (float): start x position of the geometry

            y_start (float): start y position of the geometry

            h_start (float): start heading of the geometry

            s (float): start s value of the geometry (optional)
                Default: None

        """
        if self._addition_mode == "add_geometry":
            raise MixOfGeometryAddition(
                "A geometry has already been added with add_geometry, please use either add_geometry, or add_fixed_geometry not both"
            )

        if s != None:
            pres_s = s
        else:
            pres_s = self.present_s

        if not self.fixed:
            self.x_start = x_start
            self.y_start = y_start
            self.h_start = h_start
            self.fixed = True

        newgeom = _Geometry(pres_s, x_start, y_start, h_start, geom)
        self._adjusted_geometries.append(newgeom)
        self.x_end, self.y_end, self.h_end, length = newgeom.get_end_data()
        self.present_s += length
        self.adjusted = True
        self._addition_mode = "add_fixed_geometry"
        return self

    def set_start_point(self, x_start=0, y_start=0, h_start=0):
        """sets the start point of the planview

        Parameters
        ----------
        x_start (float): start x coordinate of the first geometry
            Default: 0

        y_start (float): start y coordinate of the first geometry
            Default: 0

        h_start (float): starting heading of the first geometry
            Default: 0
        """

        self.present_x = x_start
        self.present_y = y_start
        self.present_h = h_start
        self.fixed = True

    def get_start_point(self):
        """returns the start point of the planview

        Parameters
        ----------
        """

        return self.x_start, self.y_start, self.h_start
        # return self._adjusted_geometries[-1].get_end_point

    def get_end_point(self):
        """sets the start point of the planview

        Parameters
        ----------
        x_start (float): start x coordinate of the first geometry
            Default: 0

        y_start (float): start y coordinate of the first geometry
            Default: 0

        h_start (float): starting heading of the first geometry
            Default: 0
        """

        return self.x_end, self.y_end, self.h_end

    def adjust_geometries(self, from_end=False):
        """Adjusts all geometries to have the correct start point and heading

        Parameters
        ----------
        from_end ([optional]bool): states if (self.present_x, self.present_y, self.present_h) are being interpreted as starting point or ending point of the geometry

        """
        if from_end == False:
            self.x_start = self.present_x
            self.y_start = self.present_y
            self.h_start = self.present_h

            for i in range(len(self._raw_geometries)):
                if len(self._overridden_headings) > 0:
                    self.present_h = self._overridden_headings[i]

                newgeom = _Geometry(
                    self.present_s,
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    self._raw_geometries[i],
                )
                (
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    length,
                ) = newgeom.get_end_data()
                self.present_s += length

                self._adjusted_geometries.append(newgeom)
            self.x_end = self.present_x
            self.y_end = self.present_y
            self.h_end = wrap_pi(self.present_h)

        else:
            self.x_end = self.present_x
            self.y_end = self.present_y
            self.h_end = self.present_h + np.pi

            lengths = []
            for i in range(len(self._raw_geometries) - 1, -1, -1):
                newgeom = _Geometry(
                    self.present_s,
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    self._raw_geometries[i],
                )
                (
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    partial_length,
                ) = newgeom.get_start_data()
                lengths.append(partial_length)
                self._adjusted_geometries.append(newgeom)

            self.x_start = self.present_x
            self.y_start = self.present_y
            self.h_start = wrap_pi(self.present_h + np.pi)

            length = sum(lengths)
            self.present_s = 0

            for i in range(len(self._adjusted_geometries) - 1, -1, -1):
                self._adjusted_geometries[i].set_s(self.present_s)
                self.present_s += lengths[i]
            self._adjusted_geometries.reverse()
        self.h_start = wrap_pi(self.h_start)
        self.h_end = wrap_pi(self.h_end)
        self.adjusted = True

    def get_total_length(self):
        """returns the total length of the planView"""
        if self.adjusted:
            return self.present_s
        else:
            return sum([x.length for x in self._raw_geometries])

    def get_element(self):
        """returns the elementTree of the WorldPostion"""

        element = ET.Element("planView")
        self._add_additional_data_to_element(element)
        for geom in self._adjusted_geometries:
            element.append(geom.get_element())
        return element


class _Geometry(XodrBase):
    """the _Geometry describes the geometry entry of open drive

    Parameters
    ----------
        s (float): the start s value (along road) of the geometry

        x (float): start x coordinate of the geometry

        y (float):  start y coordinate of the geometry

        heading (float): heading of the geometry

        geom_type (Line, Spiral,ParamPoly3, or Arc): the type of geometry

    Attributes
    ----------
        s (float): the start s value (along road) of the geometry

        x (float): start x coordinate of the geometry

        y (float):  start y coordinate of the geometry

        heading (float): heading of the geometry

        geom_type (Line, Spiral,ParamPoly3, or Arc): the type of geometry

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

        get_attributes()
            Returns a dictionary of all attributes of the class
    """

    def __init__(self, s, x, y, heading, geom_type):
        """initalizes the PlanView

        Parameters
        ----------
            s (float): the start s value (along road) of the geometry

            x (float): start x coordinate of the geometry

            y (float):  start y coordinate of the geometry

            heading (float): heading of the geometry

            geom_type (Line, Spiral,ParamPoly3, or Arc): the type of geometry

        """
        super().__init__()
        self.s = s
        self.x = x
        self.y = y

        self.heading = heading
        self.geom_type = geom_type
        _, _, _, self.length = self.geom_type.get_end_data(self.x, self.y, self.heading)

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

    def get_end_data(self):
        return self.geom_type.get_end_data(self.x, self.y, self.heading)

    def get_start_data(self):
        x, y, heading, self.length = self.geom_type.get_start_data(
            self.x, self.y, self.heading
        )
        self.x = x
        self.y = y
        self.heading = heading + np.pi
        self.s = None
        return x, y, heading, self.length

    def set_s(self, s):
        self.s = s

    def get_attributes(self):
        """returns the attributes of the _Geometry as a dict"""
        retdict = {}
        retdict["s"] = str(self.s)
        retdict["x"] = str(self.x)
        retdict["y"] = str(self.y)
        retdict["hdg"] = str(self.heading)
        retdict["length"] = str(self.length)
        return retdict

    def get_element(self):
        """returns the elementTree of the _Geometry"""
        element = ET.Element("geometry", attrib=self.get_attributes())
        self._add_additional_data_to_element(element)
        element.append(self.geom_type.get_element())
        return element


class Line(XodrBase):
    """the line class creates a line type of geometry

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

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

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

        get_end_data(x,y,h)
            Returns the end point of the geometry

    """

    def __init__(self, length):
        super().__init__()
        self.length = length

    def __eq__(self, other):
        return super().__eq__(other)

    def get_end_data(self, x, y, h):
        """Returns the end point of the geometry

        Parameters
        ----------
            x (float): x start point of the geometry

            y (float): y start point of the geometry

            h (float): start heading of the geometry

        Returns
        ----------
            x (float): the final x point

            y (float): the final y point

            h (float): the final heading

            length (float): length of the road

        """

        new_x = self.length * np.cos(h) + x
        new_y = self.length * np.sin(h) + y
        new_h = h

        return new_x, new_y, new_h, self.length

    def get_start_data(self, end_x, end_y, end_h):
        """Returns the end point of the geometry

        Parameters
        ----------
            end_x (float): x end point of the geometry

            end_y (float): y end point of the geometry

            end_h (float): end heading of the geometry

        Returns
        ----------
            x (float): the start x point

            y (float): the start y point

            h (float): the start heading

            length (float): length of the road

        """
        start_x = self.length * np.cos(end_h) + end_x
        start_y = self.length * np.sin(end_h) + end_y
        start_h = end_h

        return start_x, start_y, start_h, self.length

    def get_element(self):
        """returns the elementTree of the Line"""
        element = ET.Element("line")
        self._add_additional_data_to_element(element)
        return element


class Arc(XodrBase):
    """the Arc creates a arc type of geometry

    Parameters
    ----------
        curvature (float): curvature of the arc

        length (float): length of the arc (optional or use angle)

        angle (float): angle of the arc (optional or use length)

    Attributes
    ----------
        curvature (float): curvature of the arc

        length (float): length of the arc

        angle (float): angle of the arc

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

        get_attributes()
            Returns a dictionary of all attributes of the class

        get_end_data(x,y,h)
            Returns the end point of the geometry
    """

    def __init__(self, curvature, length=None, angle=None):
        """initalizes the Arc

        Parameters
        ----------
            curvature (float): curvature of the arc

            length (float): length of the arc (optional or use angle)

            angle (float): angle of the arc (optional or use length)

        """
        super().__init__()
        if length == None and angle == None:
            raise NotEnoughInputArguments("neither length nor angle defined, for arc")

        if length != None and angle != None:
            raise ToManyOptionalArguments(
                "both length and angle set, only one is requiered"
            )

        self.length = length
        self.angle = angle
        if curvature == 0:
            raise ValueError(
                "You are creating a straight line, please use Line instead"
            )
        self.curvature = curvature

        if self.length:
            self.angle = self.length * self.curvature

        if self.angle:
            _, _, _, self.length = self.get_end_data(0, 0, 0)

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

    def get_end_data(self, x, y, h):
        """Returns information about the end point of the geometry

        Parameters
        ----------
            x (float): x start point of the geometry

            y (float): y start point of the geometry

            h (float): start heading of the geometry

        Returns
        ---------

            x (float): the final x point

            y (float): the final y point

            h (float): the final heading

            length (float): length of the element

        """
        radius = 1 / np.abs(self.curvature)
        if self.curvature < 0:
            phi_0 = h + np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        else:
            phi_0 = h - np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        if self.length:
            self.angle = self.length * self.curvature

        new_ang = self.angle + phi_0
        if self.angle:
            self.length = np.abs(radius * self.angle)

        new_ang = self.angle + phi_0
        new_h = h + self.angle
        new_x = np.cos(new_ang) * radius + x_0
        new_y = np.sin(new_ang) * radius + y_0

        return new_x, new_y, new_h, self.length

    def get_start_data(self, end_x, end_y, end_h):
        """Returns information about the end point of the geometry

        Parameters
        ----------
            end_x (float): x final point of the geometry

            end_y (float): y final point of the geometry

            end_h (float): final heading of the geometry

        Returns
        ---------

            x (float): the start x point

            y (float): the start y point

            h (float): the start heading of the inverse geometry

            length (float): length of the element

        """
        x = end_x
        y = end_y
        h = end_h
        inv_curv = -self.curvature
        radius = 1 / np.abs(inv_curv)
        if inv_curv < 0:
            phi_0 = h + np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        else:
            phi_0 = h - np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        if self.length:
            self.angle = self.length * inv_curv

        new_ang = self.angle + phi_0
        if self.angle:
            self.length = np.abs(radius * self.angle)

        new_h = h + self.angle
        new_x = np.cos(new_ang) * radius + x_0
        new_y = np.sin(new_ang) * radius + y_0
        return new_x, new_y, new_h, self.length

    def get_attributes(self):
        """returns the attributes of the Arc as a dict"""
        return {"curvature": str(self.curvature)}

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


class ParamPoly3(XodrBase):
    """the ParamPoly3 class creates a parampoly3 type of geometry, in the coordinate systeme U (along road), V (normal to the road)

    the polynomials are on the form
    uv(p) = a + b*p + c*p^2 + d*p^3

    Parameters
    ----------
        au (float): coefficient a of the u polynomial

        bu (float): coefficient b of the u polynomial

        cu (float): coefficient c of the u polynomial

        du (float): coefficient d of the u polynomial

        av (float): coefficient a of the v polynomial

        bv (float): coefficient b of the v polynomial

        cv (float): coefficient c of the v polynomial

        dv (float): coefficient d of the v polynomial

        prange (str): "normalized" or "arcLength"
            Default: "normalized"

        length (float): total length of arc, used if prange == arcLength

    Attributes
    ----------
        au (float): coefficient a of the u polynomial

        bu (float): coefficient b of the u polynomial

        cu (float): coefficient c of the u polynomial

        du (float): coefficient d of the u polynomial

        av (float): coefficient a of the v polynomial

        bv (float): coefficient b of the v polynomial

        cv (float): coefficient c of the v polynomial

        dv (float): coefficient d of the v polynomial

        prange (str): "normalized" or "arcLength"
            Default: "normalized"

        length (float): total length of arc, used if prange == arcLength

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

        get_attributes()
            Returns a dictionary of all attributes of the class

        get_end_coordinate(length,x,y,h)
            Returns the end point of the geometry
    """

    def __init__(
        self, au, bu, cu, du, av, bv, cv, dv, prange="normalized", length=None
    ):
        """initalizes the ParamPoly3

        Parameters
        ----------
            au (float): coefficient a of the u polynomial

            bu (float): coefficient b of the u polynomial

            cu (float): coefficient c of the u polynomial

            du (float): coefficient d of the u polynomial

            av (float): coefficient a of the v polynomial

            bv (float): coefficient b of the v polynomial

            cv (float): coefficient c of the v polynomial

            dv (float): coefficient d of the v polynomial

            prange (str): "normalized" or "arcLength"
                Default: "normalized"

            length (float): total length of arc, used if prange == arcLength
        """
        super().__init__()
        self.au = au
        self.bu = bu
        self.cu = cu
        self.du = du
        self.av = av
        self.bv = bv
        self.cv = cv
        self.dv = dv
        self.prange = prange
        if prange == "arcLength" and length == None:
            raise ValueError(
                "No length was provided for ParamPoly3 with arcLength option"
            )
        if length:
            self.length = length
        else:
            _, _, _, self.length = self.get_end_data(0, 0, 0)

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

    def _integrand(self, p):
        """integral function to calulate length of polynomial,
        #TODO: This is not tested or verified...
        """
        return np.sqrt(
            (abs(3 * self.du * p**2 + 2 * self.cu * p + self.bu)) ** 2
            + (abs(3 * self.dv * p**2 + 2 * self.cv * p + self.bv)) ** 2
        )

    def get_start_data(self, x, y, h):
        """Returns the start point of the geometry

        Parameters
        ----------
            x (float): x end point of the geometry

            y (float): y end point of the geometry

            h (float): end heading of the geometry

        Returns
        ---------
            x (float): the start x point
            y (float): the start y point
            h (float): the start heading
            length (float): the length of the geometry

        """
        if self.prange == "normalized":
            p = 1
            I = quad(self._integrand, 0, 1)
            self.length = I[0]
        else:
            p = self.length
        newu = self.au + self.bu * p + self.cu * p**2 + self.du * p**3
        newv = self.av + self.bv * p + self.cv * p**2 + self.dv * p**3

        new_x = x - (newu * np.cos(h) - np.sin(h) * newv)
        new_y = y - (newu * np.sin(h) + np.cos(h) * newv)
        new_h = h - np.arctan2(
            self.bv + 2 * self.cv * p + 3 * self.dv * p**2,
            self.bu + 2 * self.cu * p + 3 * self.du * p**2,
        )

        return new_x, new_y, new_h, self.length

    def get_end_data(self, x, y, h):
        """Returns the end point of the geometry

        Parameters
        ----------
            x (float): x final point of the geometry

            y (float): y final point of the geometry

            h (float): final heading of the geometry

        Returns
        ---------
            x (float): the start x point
            y (float): the start y point
            h (float): the start heading of the inverse geometry
            length (float): length of the polynomial

        """
        if self.prange == "normalized":
            p = 1
            I = quad(self._integrand, 0, 1)
            self.length = I[0]
        else:
            p = self.length
        newu = self.au + self.bu * p + self.cu * p**2 + self.du * p**3
        newv = self.av + self.bv * p + self.cv * p**2 + self.dv * p**3

        new_x = x + newu * np.cos(h) - np.sin(h) * newv
        new_y = y + newu * np.sin(h) + np.cos(h) * newv
        new_h = h + np.arctan2(
            self.bv + 2 * self.cv * p + 3 * self.dv * p**2,
            self.bu + 2 * self.cu * p + 3 * self.du * p**2,
        )

        return new_x, new_y, new_h, self.length

    def get_attributes(self):
        """returns the attributes of the ParamPoly3 as a dict"""
        retdict = {}
        retdict["aU"] = str(self.au)
        retdict["bU"] = str(self.bu)
        retdict["cU"] = str(self.cu)
        retdict["dU"] = str(self.du)
        retdict["aV"] = str(self.av)
        retdict["bV"] = str(self.bv)
        retdict["cV"] = str(self.cv)
        retdict["dV"] = str(self.dv)
        retdict["pRange"] = self.prange
        return retdict

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


class Spiral(XodrBase):
    """the Spiral (Clothoid) creates a spiral type of geometry

    Parameters
    ----------
        curvstart (float): starting curvature of the Spiral

        curvend (float): final curvature of the Spiral

        length (float): length of the spiral (optional, or use, angle, or cdot)

        angle (float): the angle of the spiral (optional, or use length, or cdot)

        cdot (float): the curvature change of the spiral (optional, or use length, or angle)

    Attributes
    ----------
        curvstart (float): starting curvature of the Spiral

        curvend (float): final curvature of the Spiral

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

        get_attributes()
            Returns a dictionary of all attributes of the class

        get_end_data(x,y,h)
            Returns the end point of the geometry
    """

    def __init__(self, curvstart, curvend, length=None, angle=None, cdot=None):
        """initalizes the Spline

        Parameters
        ----------
            curvstart (float): starting curvature of the Spiral

            curvend (float): final curvature of the Spiral

            length (float): length of the spiral (optional, or use, angle, or cdot)

            angle (float): the angle of the spiral (optional, or use length, or cdot)

            cdot (float): the curvature change of the spiral (optional, or use length, or angle)
        """
        super().__init__()
        self.curvstart = curvstart
        self.curvend = curvend
        if length == None and angle == None and cdot == None:
            raise NotEnoughInputArguments("Spiral is underdefined")
        if sum([x != None for x in [length, angle, cdot]]) > 1:
            raise ToManyOptionalArguments(
                "Spiral is overdefined, please use only one of the optional inputs"
            )
        if angle:
            self.length = 2 * abs(angle) / np.maximum(abs(curvend), abs(curvstart))

        elif cdot:
            self.length = (self.curvend - self.curvstart) / cdot
        else:
            self.length = length

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

    def get_end_data(self, x, y, h):
        """Returns the end point of the geometry

        Parameters
        ----------
            x (float): x start point of the geometry

            y (float): y start point of the geometry

            h (float): start heading of the geometry

        Returns
        ---------

            x (float): the final x point
            y (float): the final y point
            h (float): the final heading
            l (float): length of the spiral
        """

        cloth = pcloth.Clothoid.StandardParams(
            x,
            y,
            h,
            self.curvstart,
            (self.curvend - self.curvstart) / self.length,
            self.length,
        )

        return cloth.XEnd, cloth.YEnd, cloth.ThetaEnd, cloth.length

    def get_start_data(self, end_x, end_y, end_h):
        """Returns the end point of the geometry

        Parameters
        ----------
            end_x (float): x end point of the geometry

            end_y (float): y end point of the geometry

            end_h (float): end heading of the geometry

        Returns
        ---------

            x (float): the start x point
            y (float): the start y point
            h (float): the start heading of the inverse geometry
            l (float): length of the spiral

        """
        cloth = pcloth.Clothoid.StandardParams(
            end_x,
            end_y,
            end_h,
            -self.curvend,
            -(self.curvstart - self.curvend) / self.length,
            self.length,
        )

        return cloth.XEnd, cloth.YEnd, cloth.ThetaEnd, cloth.length

    def get_attributes(self):
        """returns the attributes of the Line as a dict"""
        return {"curvStart": str(self.curvstart), "curvEnd": str(self.curvend)}

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

Functions

def wrap_pi(angle)
Expand source code
def wrap_pi(angle):
    return angle % (2 * np.pi)

Classes

class AdjustablePlanview (left_lane_defs=None, right_lane_defs=None, center_road_mark=None, lane_width=None, lane_width_end=None)

AdjustablePlanview can be used to fit a geometry between two fixed roads. Especially useful when connecting larger networks with Spiral geometries included.

Expand source code
class AdjustablePlanview:
    """AdjustablePlanview can be used to fit a geometry between two fixed roads.
    Especially useful when connecting larger networks with Spiral geometries included.
    """

    def __init__(
        self,
        left_lane_defs=None,
        right_lane_defs=None,
        center_road_mark=None,
        lane_width=None,
        lane_width_end=None,
    ):
        self.fixed = False
        self.adjusted = False
        self.left_lane_defs = left_lane_defs
        self.right_lane_defs = right_lane_defs
        self.center_road_mark = center_road_mark
        self.lane_width = lane_width
        self.lane_width_end = lane_width_end
class Arc (curvature, length=None, angle=None)

the Arc creates a arc type of geometry

Parameters

curvature (float): curvature of the arc

length (float): length of the arc (optional or use angle)

angle (float): angle of the arc (optional or use length)

Attributes

curvature (float): curvature of the arc

length (float): length of the arc

angle (float): angle of the arc

Methods

get_element()
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of the class

get_end_data(x,y,h)
    Returns the end point of the geometry

initalizes the Arc

Parameters

curvature (float): curvature of the arc

length (float): length of the arc (optional or use angle)

angle (float): angle of the arc (optional or use length)
Expand source code
class Arc(XodrBase):
    """the Arc creates a arc type of geometry

    Parameters
    ----------
        curvature (float): curvature of the arc

        length (float): length of the arc (optional or use angle)

        angle (float): angle of the arc (optional or use length)

    Attributes
    ----------
        curvature (float): curvature of the arc

        length (float): length of the arc

        angle (float): angle of the arc

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

        get_attributes()
            Returns a dictionary of all attributes of the class

        get_end_data(x,y,h)
            Returns the end point of the geometry
    """

    def __init__(self, curvature, length=None, angle=None):
        """initalizes the Arc

        Parameters
        ----------
            curvature (float): curvature of the arc

            length (float): length of the arc (optional or use angle)

            angle (float): angle of the arc (optional or use length)

        """
        super().__init__()
        if length == None and angle == None:
            raise NotEnoughInputArguments("neither length nor angle defined, for arc")

        if length != None and angle != None:
            raise ToManyOptionalArguments(
                "both length and angle set, only one is requiered"
            )

        self.length = length
        self.angle = angle
        if curvature == 0:
            raise ValueError(
                "You are creating a straight line, please use Line instead"
            )
        self.curvature = curvature

        if self.length:
            self.angle = self.length * self.curvature

        if self.angle:
            _, _, _, self.length = self.get_end_data(0, 0, 0)

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

    def get_end_data(self, x, y, h):
        """Returns information about the end point of the geometry

        Parameters
        ----------
            x (float): x start point of the geometry

            y (float): y start point of the geometry

            h (float): start heading of the geometry

        Returns
        ---------

            x (float): the final x point

            y (float): the final y point

            h (float): the final heading

            length (float): length of the element

        """
        radius = 1 / np.abs(self.curvature)
        if self.curvature < 0:
            phi_0 = h + np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        else:
            phi_0 = h - np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        if self.length:
            self.angle = self.length * self.curvature

        new_ang = self.angle + phi_0
        if self.angle:
            self.length = np.abs(radius * self.angle)

        new_ang = self.angle + phi_0
        new_h = h + self.angle
        new_x = np.cos(new_ang) * radius + x_0
        new_y = np.sin(new_ang) * radius + y_0

        return new_x, new_y, new_h, self.length

    def get_start_data(self, end_x, end_y, end_h):
        """Returns information about the end point of the geometry

        Parameters
        ----------
            end_x (float): x final point of the geometry

            end_y (float): y final point of the geometry

            end_h (float): final heading of the geometry

        Returns
        ---------

            x (float): the start x point

            y (float): the start y point

            h (float): the start heading of the inverse geometry

            length (float): length of the element

        """
        x = end_x
        y = end_y
        h = end_h
        inv_curv = -self.curvature
        radius = 1 / np.abs(inv_curv)
        if inv_curv < 0:
            phi_0 = h + np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        else:
            phi_0 = h - np.pi / 2
            x_0 = x - np.cos(phi_0) * radius
            y_0 = y - np.sin(phi_0) * radius

        if self.length:
            self.angle = self.length * inv_curv

        new_ang = self.angle + phi_0
        if self.angle:
            self.length = np.abs(radius * self.angle)

        new_h = h + self.angle
        new_x = np.cos(new_ang) * radius + x_0
        new_y = np.sin(new_ang) * radius + y_0
        return new_x, new_y, new_h, self.length

    def get_attributes(self):
        """returns the attributes of the Arc as a dict"""
        return {"curvature": str(self.curvature)}

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

Ancestors

Methods

def get_attributes(self)

returns the attributes of the Arc as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the Arc as a dict"""
    return {"curvature": str(self.curvature)}
def get_element(self)

returns the elementTree of the Arc

Expand source code
def get_element(self):
    """returns the elementTree of the Arc"""
    element = ET.Element("arc", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    return element
def get_end_data(self, x, y, h)

Returns information about the end point of the geometry

Parameters

x (float): x start point of the geometry

y (float): y start point of the geometry

h (float): start heading of the geometry

Returns

x (float): the final x point

y (float): the final y point

h (float): the final heading

length (float): length of the element
Expand source code
def get_end_data(self, x, y, h):
    """Returns information about the end point of the geometry

    Parameters
    ----------
        x (float): x start point of the geometry

        y (float): y start point of the geometry

        h (float): start heading of the geometry

    Returns
    ---------

        x (float): the final x point

        y (float): the final y point

        h (float): the final heading

        length (float): length of the element

    """
    radius = 1 / np.abs(self.curvature)
    if self.curvature < 0:
        phi_0 = h + np.pi / 2
        x_0 = x - np.cos(phi_0) * radius
        y_0 = y - np.sin(phi_0) * radius

    else:
        phi_0 = h - np.pi / 2
        x_0 = x - np.cos(phi_0) * radius
        y_0 = y - np.sin(phi_0) * radius

    if self.length:
        self.angle = self.length * self.curvature

    new_ang = self.angle + phi_0
    if self.angle:
        self.length = np.abs(radius * self.angle)

    new_ang = self.angle + phi_0
    new_h = h + self.angle
    new_x = np.cos(new_ang) * radius + x_0
    new_y = np.sin(new_ang) * radius + y_0

    return new_x, new_y, new_h, self.length
def get_start_data(self, end_x, end_y, end_h)

Returns information about the end point of the geometry

Parameters

end_x (float): x final point of the geometry

end_y (float): y final point of the geometry

end_h (float): final heading of the geometry

Returns

x (float): the start x point

y (float): the start y point

h (float): the start heading of the inverse geometry

length (float): length of the element
Expand source code
def get_start_data(self, end_x, end_y, end_h):
    """Returns information about the end point of the geometry

    Parameters
    ----------
        end_x (float): x final point of the geometry

        end_y (float): y final point of the geometry

        end_h (float): final heading of the geometry

    Returns
    ---------

        x (float): the start x point

        y (float): the start y point

        h (float): the start heading of the inverse geometry

        length (float): length of the element

    """
    x = end_x
    y = end_y
    h = end_h
    inv_curv = -self.curvature
    radius = 1 / np.abs(inv_curv)
    if inv_curv < 0:
        phi_0 = h + np.pi / 2
        x_0 = x - np.cos(phi_0) * radius
        y_0 = y - np.sin(phi_0) * radius

    else:
        phi_0 = h - np.pi / 2
        x_0 = x - np.cos(phi_0) * radius
        y_0 = y - np.sin(phi_0) * radius

    if self.length:
        self.angle = self.length * inv_curv

    new_ang = self.angle + phi_0
    if self.angle:
        self.length = np.abs(radius * self.angle)

    new_h = h + self.angle
    new_x = np.cos(new_ang) * radius + x_0
    new_y = np.sin(new_ang) * radius + y_0
    return new_x, new_y, new_h, self.length

Inherited members

class Line (length)

the line class creates a line type of geometry

Parameters

length (float): length of the line

Attributes

length (float): length of the line

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_end_data(x,y,h)
    Returns the end point of the geometry

initalize the UserData

Expand source code
class Line(XodrBase):
    """the line class creates a line type of geometry

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

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

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

        get_end_data(x,y,h)
            Returns the end point of the geometry

    """

    def __init__(self, length):
        super().__init__()
        self.length = length

    def __eq__(self, other):
        return super().__eq__(other)

    def get_end_data(self, x, y, h):
        """Returns the end point of the geometry

        Parameters
        ----------
            x (float): x start point of the geometry

            y (float): y start point of the geometry

            h (float): start heading of the geometry

        Returns
        ----------
            x (float): the final x point

            y (float): the final y point

            h (float): the final heading

            length (float): length of the road

        """

        new_x = self.length * np.cos(h) + x
        new_y = self.length * np.sin(h) + y
        new_h = h

        return new_x, new_y, new_h, self.length

    def get_start_data(self, end_x, end_y, end_h):
        """Returns the end point of the geometry

        Parameters
        ----------
            end_x (float): x end point of the geometry

            end_y (float): y end point of the geometry

            end_h (float): end heading of the geometry

        Returns
        ----------
            x (float): the start x point

            y (float): the start y point

            h (float): the start heading

            length (float): length of the road

        """
        start_x = self.length * np.cos(end_h) + end_x
        start_y = self.length * np.sin(end_h) + end_y
        start_h = end_h

        return start_x, start_y, start_h, self.length

    def get_element(self):
        """returns the elementTree of the Line"""
        element = ET.Element("line")
        self._add_additional_data_to_element(element)
        return element

Ancestors

Methods

def get_element(self)

returns the elementTree of the Line

Expand source code
def get_element(self):
    """returns the elementTree of the Line"""
    element = ET.Element("line")
    self._add_additional_data_to_element(element)
    return element
def get_end_data(self, x, y, h)

Returns the end point of the geometry

Parameters

x (float): x start point of the geometry

y (float): y start point of the geometry

h (float): start heading of the geometry

Returns

x (float): the final x point

y (float): the final y point

h (float): the final heading

length (float): length of the road
Expand source code
def get_end_data(self, x, y, h):
    """Returns the end point of the geometry

    Parameters
    ----------
        x (float): x start point of the geometry

        y (float): y start point of the geometry

        h (float): start heading of the geometry

    Returns
    ----------
        x (float): the final x point

        y (float): the final y point

        h (float): the final heading

        length (float): length of the road

    """

    new_x = self.length * np.cos(h) + x
    new_y = self.length * np.sin(h) + y
    new_h = h

    return new_x, new_y, new_h, self.length
def get_start_data(self, end_x, end_y, end_h)

Returns the end point of the geometry

Parameters

end_x (float): x end point of the geometry

end_y (float): y end point of the geometry

end_h (float): end heading of the geometry

Returns

x (float): the start x point

y (float): the start y point

h (float): the start heading

length (float): length of the road
Expand source code
def get_start_data(self, end_x, end_y, end_h):
    """Returns the end point of the geometry

    Parameters
    ----------
        end_x (float): x end point of the geometry

        end_y (float): y end point of the geometry

        end_h (float): end heading of the geometry

    Returns
    ----------
        x (float): the start x point

        y (float): the start y point

        h (float): the start heading

        length (float): length of the road

    """
    start_x = self.length * np.cos(end_h) + end_x
    start_y = self.length * np.sin(end_h) + end_y
    start_h = end_h

    return start_x, start_y, start_h, self.length

Inherited members

class ParamPoly3 (au, bu, cu, du, av, bv, cv, dv, prange='normalized', length=None)

the ParamPoly3 class creates a parampoly3 type of geometry, in the coordinate systeme U (along road), V (normal to the road)

the polynomials are on the form uv(p) = a + bp + cp^2 + d*p^3

Parameters

au (float): coefficient a of the u polynomial

bu (float): coefficient b of the u polynomial

cu (float): coefficient c of the u polynomial

du (float): coefficient d of the u polynomial

av (float): coefficient a of the v polynomial

bv (float): coefficient b of the v polynomial

cv (float): coefficient c of the v polynomial

dv (float): coefficient d of the v polynomial

prange (str): "normalized" or "arcLength"
    Default: "normalized"

length (float): total length of arc, used if prange == arcLength

Attributes

au (float): coefficient a of the u polynomial

bu (float): coefficient b of the u polynomial

cu (float): coefficient c of the u polynomial

du (float): coefficient d of the u polynomial

av (float): coefficient a of the v polynomial

bv (float): coefficient b of the v polynomial

cv (float): coefficient c of the v polynomial

dv (float): coefficient d of the v polynomial

prange (str): "normalized" or "arcLength"
    Default: "normalized"

length (float): total length of arc, used if prange == arcLength

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of the class

get_end_coordinate(length,x,y,h)
    Returns the end point of the geometry

initalizes the ParamPoly3

Parameters

au (float): coefficient a of the u polynomial

bu (float): coefficient b of the u polynomial

cu (float): coefficient c of the u polynomial

du (float): coefficient d of the u polynomial

av (float): coefficient a of the v polynomial

bv (float): coefficient b of the v polynomial

cv (float): coefficient c of the v polynomial

dv (float): coefficient d of the v polynomial

prange (str): "normalized" or "arcLength"
    Default: "normalized"

length (float): total length of arc, used if prange == arcLength
Expand source code
class ParamPoly3(XodrBase):
    """the ParamPoly3 class creates a parampoly3 type of geometry, in the coordinate systeme U (along road), V (normal to the road)

    the polynomials are on the form
    uv(p) = a + b*p + c*p^2 + d*p^3

    Parameters
    ----------
        au (float): coefficient a of the u polynomial

        bu (float): coefficient b of the u polynomial

        cu (float): coefficient c of the u polynomial

        du (float): coefficient d of the u polynomial

        av (float): coefficient a of the v polynomial

        bv (float): coefficient b of the v polynomial

        cv (float): coefficient c of the v polynomial

        dv (float): coefficient d of the v polynomial

        prange (str): "normalized" or "arcLength"
            Default: "normalized"

        length (float): total length of arc, used if prange == arcLength

    Attributes
    ----------
        au (float): coefficient a of the u polynomial

        bu (float): coefficient b of the u polynomial

        cu (float): coefficient c of the u polynomial

        du (float): coefficient d of the u polynomial

        av (float): coefficient a of the v polynomial

        bv (float): coefficient b of the v polynomial

        cv (float): coefficient c of the v polynomial

        dv (float): coefficient d of the v polynomial

        prange (str): "normalized" or "arcLength"
            Default: "normalized"

        length (float): total length of arc, used if prange == arcLength

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

        get_attributes()
            Returns a dictionary of all attributes of the class

        get_end_coordinate(length,x,y,h)
            Returns the end point of the geometry
    """

    def __init__(
        self, au, bu, cu, du, av, bv, cv, dv, prange="normalized", length=None
    ):
        """initalizes the ParamPoly3

        Parameters
        ----------
            au (float): coefficient a of the u polynomial

            bu (float): coefficient b of the u polynomial

            cu (float): coefficient c of the u polynomial

            du (float): coefficient d of the u polynomial

            av (float): coefficient a of the v polynomial

            bv (float): coefficient b of the v polynomial

            cv (float): coefficient c of the v polynomial

            dv (float): coefficient d of the v polynomial

            prange (str): "normalized" or "arcLength"
                Default: "normalized"

            length (float): total length of arc, used if prange == arcLength
        """
        super().__init__()
        self.au = au
        self.bu = bu
        self.cu = cu
        self.du = du
        self.av = av
        self.bv = bv
        self.cv = cv
        self.dv = dv
        self.prange = prange
        if prange == "arcLength" and length == None:
            raise ValueError(
                "No length was provided for ParamPoly3 with arcLength option"
            )
        if length:
            self.length = length
        else:
            _, _, _, self.length = self.get_end_data(0, 0, 0)

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

    def _integrand(self, p):
        """integral function to calulate length of polynomial,
        #TODO: This is not tested or verified...
        """
        return np.sqrt(
            (abs(3 * self.du * p**2 + 2 * self.cu * p + self.bu)) ** 2
            + (abs(3 * self.dv * p**2 + 2 * self.cv * p + self.bv)) ** 2
        )

    def get_start_data(self, x, y, h):
        """Returns the start point of the geometry

        Parameters
        ----------
            x (float): x end point of the geometry

            y (float): y end point of the geometry

            h (float): end heading of the geometry

        Returns
        ---------
            x (float): the start x point
            y (float): the start y point
            h (float): the start heading
            length (float): the length of the geometry

        """
        if self.prange == "normalized":
            p = 1
            I = quad(self._integrand, 0, 1)
            self.length = I[0]
        else:
            p = self.length
        newu = self.au + self.bu * p + self.cu * p**2 + self.du * p**3
        newv = self.av + self.bv * p + self.cv * p**2 + self.dv * p**3

        new_x = x - (newu * np.cos(h) - np.sin(h) * newv)
        new_y = y - (newu * np.sin(h) + np.cos(h) * newv)
        new_h = h - np.arctan2(
            self.bv + 2 * self.cv * p + 3 * self.dv * p**2,
            self.bu + 2 * self.cu * p + 3 * self.du * p**2,
        )

        return new_x, new_y, new_h, self.length

    def get_end_data(self, x, y, h):
        """Returns the end point of the geometry

        Parameters
        ----------
            x (float): x final point of the geometry

            y (float): y final point of the geometry

            h (float): final heading of the geometry

        Returns
        ---------
            x (float): the start x point
            y (float): the start y point
            h (float): the start heading of the inverse geometry
            length (float): length of the polynomial

        """
        if self.prange == "normalized":
            p = 1
            I = quad(self._integrand, 0, 1)
            self.length = I[0]
        else:
            p = self.length
        newu = self.au + self.bu * p + self.cu * p**2 + self.du * p**3
        newv = self.av + self.bv * p + self.cv * p**2 + self.dv * p**3

        new_x = x + newu * np.cos(h) - np.sin(h) * newv
        new_y = y + newu * np.sin(h) + np.cos(h) * newv
        new_h = h + np.arctan2(
            self.bv + 2 * self.cv * p + 3 * self.dv * p**2,
            self.bu + 2 * self.cu * p + 3 * self.du * p**2,
        )

        return new_x, new_y, new_h, self.length

    def get_attributes(self):
        """returns the attributes of the ParamPoly3 as a dict"""
        retdict = {}
        retdict["aU"] = str(self.au)
        retdict["bU"] = str(self.bu)
        retdict["cU"] = str(self.cu)
        retdict["dU"] = str(self.du)
        retdict["aV"] = str(self.av)
        retdict["bV"] = str(self.bv)
        retdict["cV"] = str(self.cv)
        retdict["dV"] = str(self.dv)
        retdict["pRange"] = self.prange
        return retdict

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

Ancestors

Methods

def get_attributes(self)

returns the attributes of the ParamPoly3 as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the ParamPoly3 as a dict"""
    retdict = {}
    retdict["aU"] = str(self.au)
    retdict["bU"] = str(self.bu)
    retdict["cU"] = str(self.cu)
    retdict["dU"] = str(self.du)
    retdict["aV"] = str(self.av)
    retdict["bV"] = str(self.bv)
    retdict["cV"] = str(self.cv)
    retdict["dV"] = str(self.dv)
    retdict["pRange"] = self.prange
    return retdict
def get_element(self)

returns the elementTree of the ParamPoly3

Expand source code
def get_element(self):
    """returns the elementTree of the ParamPoly3"""
    element = ET.Element("paramPoly3", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    return element
def get_end_data(self, x, y, h)

Returns the end point of the geometry

Parameters

x (float): x final point of the geometry

y (float): y final point of the geometry

h (float): final heading of the geometry

Returns

x (float): the start x point
y (float): the start y point
h (float): the start heading of the inverse geometry
length (float): length of the polynomial
Expand source code
def get_end_data(self, x, y, h):
    """Returns the end point of the geometry

    Parameters
    ----------
        x (float): x final point of the geometry

        y (float): y final point of the geometry

        h (float): final heading of the geometry

    Returns
    ---------
        x (float): the start x point
        y (float): the start y point
        h (float): the start heading of the inverse geometry
        length (float): length of the polynomial

    """
    if self.prange == "normalized":
        p = 1
        I = quad(self._integrand, 0, 1)
        self.length = I[0]
    else:
        p = self.length
    newu = self.au + self.bu * p + self.cu * p**2 + self.du * p**3
    newv = self.av + self.bv * p + self.cv * p**2 + self.dv * p**3

    new_x = x + newu * np.cos(h) - np.sin(h) * newv
    new_y = y + newu * np.sin(h) + np.cos(h) * newv
    new_h = h + np.arctan2(
        self.bv + 2 * self.cv * p + 3 * self.dv * p**2,
        self.bu + 2 * self.cu * p + 3 * self.du * p**2,
    )

    return new_x, new_y, new_h, self.length
def get_start_data(self, x, y, h)

Returns the start point of the geometry

Parameters

x (float): x end point of the geometry

y (float): y end point of the geometry

h (float): end heading of the geometry

Returns

x (float): the start x point
y (float): the start y point
h (float): the start heading
length (float): the length of the geometry
Expand source code
def get_start_data(self, x, y, h):
    """Returns the start point of the geometry

    Parameters
    ----------
        x (float): x end point of the geometry

        y (float): y end point of the geometry

        h (float): end heading of the geometry

    Returns
    ---------
        x (float): the start x point
        y (float): the start y point
        h (float): the start heading
        length (float): the length of the geometry

    """
    if self.prange == "normalized":
        p = 1
        I = quad(self._integrand, 0, 1)
        self.length = I[0]
    else:
        p = self.length
    newu = self.au + self.bu * p + self.cu * p**2 + self.du * p**3
    newv = self.av + self.bv * p + self.cv * p**2 + self.dv * p**3

    new_x = x - (newu * np.cos(h) - np.sin(h) * newv)
    new_y = y - (newu * np.sin(h) + np.cos(h) * newv)
    new_h = h - np.arctan2(
        self.bv + 2 * self.cv * p + 3 * self.dv * p**2,
        self.bu + 2 * self.cu * p + 3 * self.du * p**2,
    )

    return new_x, new_y, new_h, self.length

Inherited members

class PlanView (x_start=None, y_start=None, h_start=None)

the PlanView is the geometrical description of a road,

Parameters

x_start (float): start x coordinate of the first geometry
    Default: None

y_start (float): start y coordinate of the first geometry
    Default: None

h_start (float): starting heading of the first geometry
    Default: None

Attributes

present_x (float): the start x coordinate of the next geometry added

present_y (float): the y coordinate of the next geometry added

present_h (float): the heading coordinate of the next geometry added

present_s (float): the along road measure of the next geometry added

Methods

get_element(elementname)
    Returns the full ElementTree of the class

get_total_length()
    Returns the full length of the PlanView

add_geometry(geom,lenght)
    adds a new geometry entry to the planeview

set_start_point(x_start,y_start,h_start)
    sets the start point and heading of the planview

adjust_geometries()
    based on the start point, it will adjust all geometries in the planview

initalizes the PlanView Note: if multiple roads are used, the start values can be recalculated.

Expand source code
class PlanView(XodrBase):
    """the PlanView is the geometrical description of a road,

    Parameters
    ----------
        x_start (float): start x coordinate of the first geometry
            Default: None

        y_start (float): start y coordinate of the first geometry
            Default: None

        h_start (float): starting heading of the first geometry
            Default: None

    Attributes
    ----------
        present_x (float): the start x coordinate of the next geometry added

        present_y (float): the y coordinate of the next geometry added

        present_h (float): the heading coordinate of the next geometry added

        present_s (float): the along road measure of the next geometry added

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

        get_total_length()
            Returns the full length of the PlanView

        add_geometry(geom,lenght)
            adds a new geometry entry to the planeview

        set_start_point(x_start,y_start,h_start)
            sets the start point and heading of the planview

        adjust_geometries()
            based on the start point, it will adjust all geometries in the planview

    """

    def __init__(self, x_start=None, y_start=None, h_start=None):
        """initalizes the PlanView
        Note: if multiple roads are used, the start values can be recalculated.


        """
        super().__init__()
        self.present_x = 0
        self.present_y = 0
        self.present_h = 0
        self.present_s = 0
        self.fixed = False
        if all([x_start != None, y_start != None, h_start != None]):
            self.set_start_point(x_start, y_start, h_start)
        elif any([x_start != None, y_start != None, h_start != None]):
            raise NotEnoughInputArguments(
                "If a start position is wanted for the PlanView, all inputs must be used."
            )

        self.x_start = None
        self.y_start = None
        self.h_start = None

        self.x_end = None
        self.y_end = None
        self.h_end = None

        self._raw_geometries = []
        self._adjusted_geometries = []
        self._overridden_headings = []

        self.adjusted = False
        # variable to track what mode of adding geometries are used

        self._addition_mode = None

    def __eq__(self, other):
        if isinstance(other, PlanView) and super().__eq__(other):
            if self.adjusted and other.adjusted:
                if self._adjusted_geometries == other._adjusted_geometries:
                    return True
            elif not self.adjusted and not other.adjusted:
                Warning(
                    "Comparing non adjusted geometries, default value will always be False"
                )
                return False

        return False

    def add_geometry(self, geom, heading=None):
        """add_geometry adds a geometry to the planview and will stich together all geometries (in order the order added)

            Should be used together with "adjust_roads_and_lanes" in the OpenDrive class.

            NOTE: DO NOT MIX WITH with add_fixed_geometry

        Parameters
        ----------
            geom (Line, Spiral, ParamPoly3, or Arc): the type of geometry

            heading (float): override the previous heading (optional), not recommended
                if used, use for ALL geometries

        """
        if self._addition_mode == "add_fixed_geometry":
            raise MixOfGeometryAddition(
                "A fixed geometry has already been added, please use either add_geometry or add_fixed_geometry"
            )

        if heading is not None:
            self._overridden_headings.append(heading)
        self._raw_geometries.append(geom)
        self._addition_mode = "add_geometry"
        return self

    def add_fixed_geometry(self, geom, x_start, y_start, h_start, s=None):
        """add_fixed_geometry adds a geometry to a certain point to the planview

            if s is used, the values will be coded and is up to the user to make correct, not for a correct opendrive file please add the geometires in order
            if s is not used, the geometries are supposed to be added in order (and s will be calculated)

            NOTE: DO NOT MIX WITH the method add_geometry

        Parameters
        ----------
            geom (Line, Spiral, ParamPoly3, or Arc): the geometry to add

            x_start (float): start x position of the geometry

            y_start (float): start y position of the geometry

            h_start (float): start heading of the geometry

            s (float): start s value of the geometry (optional)
                Default: None

        """
        if self._addition_mode == "add_geometry":
            raise MixOfGeometryAddition(
                "A geometry has already been added with add_geometry, please use either add_geometry, or add_fixed_geometry not both"
            )

        if s != None:
            pres_s = s
        else:
            pres_s = self.present_s

        if not self.fixed:
            self.x_start = x_start
            self.y_start = y_start
            self.h_start = h_start
            self.fixed = True

        newgeom = _Geometry(pres_s, x_start, y_start, h_start, geom)
        self._adjusted_geometries.append(newgeom)
        self.x_end, self.y_end, self.h_end, length = newgeom.get_end_data()
        self.present_s += length
        self.adjusted = True
        self._addition_mode = "add_fixed_geometry"
        return self

    def set_start_point(self, x_start=0, y_start=0, h_start=0):
        """sets the start point of the planview

        Parameters
        ----------
        x_start (float): start x coordinate of the first geometry
            Default: 0

        y_start (float): start y coordinate of the first geometry
            Default: 0

        h_start (float): starting heading of the first geometry
            Default: 0
        """

        self.present_x = x_start
        self.present_y = y_start
        self.present_h = h_start
        self.fixed = True

    def get_start_point(self):
        """returns the start point of the planview

        Parameters
        ----------
        """

        return self.x_start, self.y_start, self.h_start
        # return self._adjusted_geometries[-1].get_end_point

    def get_end_point(self):
        """sets the start point of the planview

        Parameters
        ----------
        x_start (float): start x coordinate of the first geometry
            Default: 0

        y_start (float): start y coordinate of the first geometry
            Default: 0

        h_start (float): starting heading of the first geometry
            Default: 0
        """

        return self.x_end, self.y_end, self.h_end

    def adjust_geometries(self, from_end=False):
        """Adjusts all geometries to have the correct start point and heading

        Parameters
        ----------
        from_end ([optional]bool): states if (self.present_x, self.present_y, self.present_h) are being interpreted as starting point or ending point of the geometry

        """
        if from_end == False:
            self.x_start = self.present_x
            self.y_start = self.present_y
            self.h_start = self.present_h

            for i in range(len(self._raw_geometries)):
                if len(self._overridden_headings) > 0:
                    self.present_h = self._overridden_headings[i]

                newgeom = _Geometry(
                    self.present_s,
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    self._raw_geometries[i],
                )
                (
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    length,
                ) = newgeom.get_end_data()
                self.present_s += length

                self._adjusted_geometries.append(newgeom)
            self.x_end = self.present_x
            self.y_end = self.present_y
            self.h_end = wrap_pi(self.present_h)

        else:
            self.x_end = self.present_x
            self.y_end = self.present_y
            self.h_end = self.present_h + np.pi

            lengths = []
            for i in range(len(self._raw_geometries) - 1, -1, -1):
                newgeom = _Geometry(
                    self.present_s,
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    self._raw_geometries[i],
                )
                (
                    self.present_x,
                    self.present_y,
                    self.present_h,
                    partial_length,
                ) = newgeom.get_start_data()
                lengths.append(partial_length)
                self._adjusted_geometries.append(newgeom)

            self.x_start = self.present_x
            self.y_start = self.present_y
            self.h_start = wrap_pi(self.present_h + np.pi)

            length = sum(lengths)
            self.present_s = 0

            for i in range(len(self._adjusted_geometries) - 1, -1, -1):
                self._adjusted_geometries[i].set_s(self.present_s)
                self.present_s += lengths[i]
            self._adjusted_geometries.reverse()
        self.h_start = wrap_pi(self.h_start)
        self.h_end = wrap_pi(self.h_end)
        self.adjusted = True

    def get_total_length(self):
        """returns the total length of the planView"""
        if self.adjusted:
            return self.present_s
        else:
            return sum([x.length for x in self._raw_geometries])

    def get_element(self):
        """returns the elementTree of the WorldPostion"""

        element = ET.Element("planView")
        self._add_additional_data_to_element(element)
        for geom in self._adjusted_geometries:
            element.append(geom.get_element())
        return element

Ancestors

Methods

def add_fixed_geometry(self, geom, x_start, y_start, h_start, s=None)

add_fixed_geometry adds a geometry to a certain point to the planview

if s is used, the values will be coded and is up to the user to make correct, not for a correct opendrive file please add the geometires in order
if s is not used, the geometries are supposed to be added in order (and s will be calculated)

NOTE: DO NOT MIX WITH the method add_geometry

Parameters

geom (Line, Spiral, ParamPoly3, or Arc): the geometry to add

x_start (float): start x position of the geometry

y_start (float): start y position of the geometry

h_start (float): start heading of the geometry

s (float): start s value of the geometry (optional)
    Default: None
Expand source code
def add_fixed_geometry(self, geom, x_start, y_start, h_start, s=None):
    """add_fixed_geometry adds a geometry to a certain point to the planview

        if s is used, the values will be coded and is up to the user to make correct, not for a correct opendrive file please add the geometires in order
        if s is not used, the geometries are supposed to be added in order (and s will be calculated)

        NOTE: DO NOT MIX WITH the method add_geometry

    Parameters
    ----------
        geom (Line, Spiral, ParamPoly3, or Arc): the geometry to add

        x_start (float): start x position of the geometry

        y_start (float): start y position of the geometry

        h_start (float): start heading of the geometry

        s (float): start s value of the geometry (optional)
            Default: None

    """
    if self._addition_mode == "add_geometry":
        raise MixOfGeometryAddition(
            "A geometry has already been added with add_geometry, please use either add_geometry, or add_fixed_geometry not both"
        )

    if s != None:
        pres_s = s
    else:
        pres_s = self.present_s

    if not self.fixed:
        self.x_start = x_start
        self.y_start = y_start
        self.h_start = h_start
        self.fixed = True

    newgeom = _Geometry(pres_s, x_start, y_start, h_start, geom)
    self._adjusted_geometries.append(newgeom)
    self.x_end, self.y_end, self.h_end, length = newgeom.get_end_data()
    self.present_s += length
    self.adjusted = True
    self._addition_mode = "add_fixed_geometry"
    return self
def add_geometry(self, geom, heading=None)

add_geometry adds a geometry to the planview and will stich together all geometries (in order the order added)

Should be used together with "adjust_roads_and_lanes" in the OpenDrive class.

NOTE: DO NOT MIX WITH with add_fixed_geometry

Parameters

geom (Line, Spiral, ParamPoly3, or Arc): the type of geometry

heading (float): override the previous heading (optional), not recommended
    if used, use for ALL geometries
Expand source code
def add_geometry(self, geom, heading=None):
    """add_geometry adds a geometry to the planview and will stich together all geometries (in order the order added)

        Should be used together with "adjust_roads_and_lanes" in the OpenDrive class.

        NOTE: DO NOT MIX WITH with add_fixed_geometry

    Parameters
    ----------
        geom (Line, Spiral, ParamPoly3, or Arc): the type of geometry

        heading (float): override the previous heading (optional), not recommended
            if used, use for ALL geometries

    """
    if self._addition_mode == "add_fixed_geometry":
        raise MixOfGeometryAddition(
            "A fixed geometry has already been added, please use either add_geometry or add_fixed_geometry"
        )

    if heading is not None:
        self._overridden_headings.append(heading)
    self._raw_geometries.append(geom)
    self._addition_mode = "add_geometry"
    return self
def adjust_geometries(self, from_end=False)

Adjusts all geometries to have the correct start point and heading

Parameters

from_end ([optional]bool): states if (self.present_x, self.present_y, self.present_h) are being interpreted as starting point or ending point of the geometry

Expand source code
def adjust_geometries(self, from_end=False):
    """Adjusts all geometries to have the correct start point and heading

    Parameters
    ----------
    from_end ([optional]bool): states if (self.present_x, self.present_y, self.present_h) are being interpreted as starting point or ending point of the geometry

    """
    if from_end == False:
        self.x_start = self.present_x
        self.y_start = self.present_y
        self.h_start = self.present_h

        for i in range(len(self._raw_geometries)):
            if len(self._overridden_headings) > 0:
                self.present_h = self._overridden_headings[i]

            newgeom = _Geometry(
                self.present_s,
                self.present_x,
                self.present_y,
                self.present_h,
                self._raw_geometries[i],
            )
            (
                self.present_x,
                self.present_y,
                self.present_h,
                length,
            ) = newgeom.get_end_data()
            self.present_s += length

            self._adjusted_geometries.append(newgeom)
        self.x_end = self.present_x
        self.y_end = self.present_y
        self.h_end = wrap_pi(self.present_h)

    else:
        self.x_end = self.present_x
        self.y_end = self.present_y
        self.h_end = self.present_h + np.pi

        lengths = []
        for i in range(len(self._raw_geometries) - 1, -1, -1):
            newgeom = _Geometry(
                self.present_s,
                self.present_x,
                self.present_y,
                self.present_h,
                self._raw_geometries[i],
            )
            (
                self.present_x,
                self.present_y,
                self.present_h,
                partial_length,
            ) = newgeom.get_start_data()
            lengths.append(partial_length)
            self._adjusted_geometries.append(newgeom)

        self.x_start = self.present_x
        self.y_start = self.present_y
        self.h_start = wrap_pi(self.present_h + np.pi)

        length = sum(lengths)
        self.present_s = 0

        for i in range(len(self._adjusted_geometries) - 1, -1, -1):
            self._adjusted_geometries[i].set_s(self.present_s)
            self.present_s += lengths[i]
        self._adjusted_geometries.reverse()
    self.h_start = wrap_pi(self.h_start)
    self.h_end = wrap_pi(self.h_end)
    self.adjusted = True
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("planView")
    self._add_additional_data_to_element(element)
    for geom in self._adjusted_geometries:
        element.append(geom.get_element())
    return element
def get_end_point(self)

sets the start point of the planview

Parameters

x_start (float): start x coordinate of the first geometry Default: 0

y_start (float): start y coordinate of the first geometry Default: 0

h_start (float): starting heading of the first geometry Default: 0

Expand source code
def get_end_point(self):
    """sets the start point of the planview

    Parameters
    ----------
    x_start (float): start x coordinate of the first geometry
        Default: 0

    y_start (float): start y coordinate of the first geometry
        Default: 0

    h_start (float): starting heading of the first geometry
        Default: 0
    """

    return self.x_end, self.y_end, self.h_end
def get_start_point(self)

returns the start point of the planview

Parameters

Expand source code
def get_start_point(self):
    """returns the start point of the planview

    Parameters
    ----------
    """

    return self.x_start, self.y_start, self.h_start
    # return self._adjusted_geometries[-1].get_end_point
def get_total_length(self)

returns the total length of the planView

Expand source code
def get_total_length(self):
    """returns the total length of the planView"""
    if self.adjusted:
        return self.present_s
    else:
        return sum([x.length for x in self._raw_geometries])
def set_start_point(self, x_start=0, y_start=0, h_start=0)

sets the start point of the planview

Parameters

x_start (float): start x coordinate of the first geometry Default: 0

y_start (float): start y coordinate of the first geometry Default: 0

h_start (float): starting heading of the first geometry Default: 0

Expand source code
def set_start_point(self, x_start=0, y_start=0, h_start=0):
    """sets the start point of the planview

    Parameters
    ----------
    x_start (float): start x coordinate of the first geometry
        Default: 0

    y_start (float): start y coordinate of the first geometry
        Default: 0

    h_start (float): starting heading of the first geometry
        Default: 0
    """

    self.present_x = x_start
    self.present_y = y_start
    self.present_h = h_start
    self.fixed = True

Inherited members

class Spiral (curvstart, curvend, length=None, angle=None, cdot=None)

the Spiral (Clothoid) creates a spiral type of geometry

Parameters

curvstart (float): starting curvature of the Spiral

curvend (float): final curvature of the Spiral

length (float): length of the spiral (optional, or use, angle, or cdot)

angle (float): the angle of the spiral (optional, or use length, or cdot)

cdot (float): the curvature change of the spiral (optional, or use length, or angle)

Attributes

curvstart (float): starting curvature of the Spiral

curvend (float): final curvature of the Spiral

Methods

get_element()
    Returns the full ElementTree of the class

get_attributes()
    Returns a dictionary of all attributes of the class

get_end_data(x,y,h)
    Returns the end point of the geometry

initalizes the Spline

Parameters

curvstart (float): starting curvature of the Spiral

curvend (float): final curvature of the Spiral

length (float): length of the spiral (optional, or use, angle, or cdot)

angle (float): the angle of the spiral (optional, or use length, or cdot)

cdot (float): the curvature change of the spiral (optional, or use length, or angle)
Expand source code
class Spiral(XodrBase):
    """the Spiral (Clothoid) creates a spiral type of geometry

    Parameters
    ----------
        curvstart (float): starting curvature of the Spiral

        curvend (float): final curvature of the Spiral

        length (float): length of the spiral (optional, or use, angle, or cdot)

        angle (float): the angle of the spiral (optional, or use length, or cdot)

        cdot (float): the curvature change of the spiral (optional, or use length, or angle)

    Attributes
    ----------
        curvstart (float): starting curvature of the Spiral

        curvend (float): final curvature of the Spiral

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

        get_attributes()
            Returns a dictionary of all attributes of the class

        get_end_data(x,y,h)
            Returns the end point of the geometry
    """

    def __init__(self, curvstart, curvend, length=None, angle=None, cdot=None):
        """initalizes the Spline

        Parameters
        ----------
            curvstart (float): starting curvature of the Spiral

            curvend (float): final curvature of the Spiral

            length (float): length of the spiral (optional, or use, angle, or cdot)

            angle (float): the angle of the spiral (optional, or use length, or cdot)

            cdot (float): the curvature change of the spiral (optional, or use length, or angle)
        """
        super().__init__()
        self.curvstart = curvstart
        self.curvend = curvend
        if length == None and angle == None and cdot == None:
            raise NotEnoughInputArguments("Spiral is underdefined")
        if sum([x != None for x in [length, angle, cdot]]) > 1:
            raise ToManyOptionalArguments(
                "Spiral is overdefined, please use only one of the optional inputs"
            )
        if angle:
            self.length = 2 * abs(angle) / np.maximum(abs(curvend), abs(curvstart))

        elif cdot:
            self.length = (self.curvend - self.curvstart) / cdot
        else:
            self.length = length

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

    def get_end_data(self, x, y, h):
        """Returns the end point of the geometry

        Parameters
        ----------
            x (float): x start point of the geometry

            y (float): y start point of the geometry

            h (float): start heading of the geometry

        Returns
        ---------

            x (float): the final x point
            y (float): the final y point
            h (float): the final heading
            l (float): length of the spiral
        """

        cloth = pcloth.Clothoid.StandardParams(
            x,
            y,
            h,
            self.curvstart,
            (self.curvend - self.curvstart) / self.length,
            self.length,
        )

        return cloth.XEnd, cloth.YEnd, cloth.ThetaEnd, cloth.length

    def get_start_data(self, end_x, end_y, end_h):
        """Returns the end point of the geometry

        Parameters
        ----------
            end_x (float): x end point of the geometry

            end_y (float): y end point of the geometry

            end_h (float): end heading of the geometry

        Returns
        ---------

            x (float): the start x point
            y (float): the start y point
            h (float): the start heading of the inverse geometry
            l (float): length of the spiral

        """
        cloth = pcloth.Clothoid.StandardParams(
            end_x,
            end_y,
            end_h,
            -self.curvend,
            -(self.curvstart - self.curvend) / self.length,
            self.length,
        )

        return cloth.XEnd, cloth.YEnd, cloth.ThetaEnd, cloth.length

    def get_attributes(self):
        """returns the attributes of the Line as a dict"""
        return {"curvStart": str(self.curvstart), "curvEnd": str(self.curvend)}

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

Ancestors

Methods

def get_attributes(self)

returns the attributes of the Line as a dict

Expand source code
def get_attributes(self):
    """returns the attributes of the Line as a dict"""
    return {"curvStart": str(self.curvstart), "curvEnd": str(self.curvend)}
def get_element(self)

returns the elementTree of the Line

Expand source code
def get_element(self):
    """returns the elementTree of the Line"""
    element = ET.Element("spiral", attrib=self.get_attributes())
    self._add_additional_data_to_element(element)
    return element
def get_end_data(self, x, y, h)

Returns the end point of the geometry

Parameters

x (float): x start point of the geometry

y (float): y start point of the geometry

h (float): start heading of the geometry

Returns

x (float): the final x point
y (float): the final y point
h (float): the final heading
l (float): length of the spiral
Expand source code
def get_end_data(self, x, y, h):
    """Returns the end point of the geometry

    Parameters
    ----------
        x (float): x start point of the geometry

        y (float): y start point of the geometry

        h (float): start heading of the geometry

    Returns
    ---------

        x (float): the final x point
        y (float): the final y point
        h (float): the final heading
        l (float): length of the spiral
    """

    cloth = pcloth.Clothoid.StandardParams(
        x,
        y,
        h,
        self.curvstart,
        (self.curvend - self.curvstart) / self.length,
        self.length,
    )

    return cloth.XEnd, cloth.YEnd, cloth.ThetaEnd, cloth.length
def get_start_data(self, end_x, end_y, end_h)

Returns the end point of the geometry

Parameters

end_x (float): x end point of the geometry

end_y (float): y end point of the geometry

end_h (float): end heading of the geometry

Returns

x (float): the start x point
y (float): the start y point
h (float): the start heading of the inverse geometry
l (float): length of the spiral
Expand source code
def get_start_data(self, end_x, end_y, end_h):
    """Returns the end point of the geometry

    Parameters
    ----------
        end_x (float): x end point of the geometry

        end_y (float): y end point of the geometry

        end_h (float): end heading of the geometry

    Returns
    ---------

        x (float): the start x point
        y (float): the start y point
        h (float): the start heading of the inverse geometry
        l (float): length of the spiral

    """
    cloth = pcloth.Clothoid.StandardParams(
        end_x,
        end_y,
        end_h,
        -self.curvend,
        -(self.curvstart - self.curvend) / self.length,
        self.length,
    )

    return cloth.XEnd, cloth.YEnd, cloth.ThetaEnd, cloth.length

Inherited members