Source code for igp2.opendrive.elements.geometry

# -*- coding: utf-8 -*-

import abc
from typing import List, Tuple

import numpy as np
from shapely.geometry import LineString, Point

from igp2.opendrive.elements.eulerspiral import EulerSpiral

__author__ = "Benjamin Orthen, Stefan Urban"
__copyright__ = "TUM Cyber-Physical Systems Group"
__credits__ = ["Priority Program SPP 1835 Cooperative Interacting Automobiles"]
__version__ = "1.2.0"
__maintainer__ = "Sebastian Maierhofer"
__email__ = "commonroad-i06@in.tum.de"
__status__ = "Released"


[docs]def normalise_angle(angle): """ Normalise angle to the range [-pi, pi] in radians """ return np.arctan2(np.sin(angle), np.cos(angle))
[docs]def ramer_douglas(curve: List[Tuple[float, float]], dist: float): """ Ramer-Douglas-Peucker simplification of a curve given a distance threshold. Args: curve: List of coordinate tuples describing the curve dist: Distance threshold to merge points Returns: A simplified curve based on RDP. """ if len(curve) < 3: return curve curve = np.array(curve) begin_end = curve[[0, -1]] if any(curve[0] != curve[-1]) else curve[[0, -2]] begin, end = begin_end[0], begin_end[1] a = np.linalg.norm(begin - curve[1:-1], axis=1) ** 2 b = np.dot(curve[1:-1] - begin, end - begin) ** 2 dists = a - b / np.linalg.norm(begin - end) ** 2 max_dist = dists.max() if max_dist < dist ** 2: return begin_end pos = dists.argmax() s = ramer_douglas(curve[:pos + 2], dist) e = ramer_douglas(curve[pos + 1:], dist)[1:] return np.append(s, e, axis=0)
[docs]class Geometry(abc.ABC): """A road geometry record defines the layout of the road's reference line in the in the x/y-plane (plan view). The geometry information is split into a header which is common to all geometric elements. (Section 5.3.4.1 of OpenDRIVE 1.4) """ __metaclass__ = abc.ABCMeta def __init__(self, start_position: float, heading: float, length: float): self._start_position = np.array(start_position) self._length = length self._heading = heading @property def start_position(self) -> Tuple[float, float]: """Returns the starting position of the geometry in cartesian coordinates """ return self._start_position @property def length(self) -> float: """Returns the overall geometry length""" return self._length @property def heading(self) -> float: """Get heading of geometry. Returns: Heading, in which direction the geometry heads at start. """ return self._heading
[docs] @abc.abstractmethod def calc_position(self, s_pos): """Calculates the position of the geometry as if the starting point is (0/0) Args: s_pos: Returns: """ return
[docs]class Line(Geometry): """This record describes a straight line as part of the road’s reference line. (Section 5.3.4.1.1 of OpenDRIVE 1.4) """
[docs] def calc_position(self, s_pos): """ Args: s_pos: Returns: """ pos = self.start_position + np.array( [s_pos * np.cos(self.heading), s_pos * np.sin(self.heading)] ) tangent = self.heading return pos, tangent
[docs]class Arc(Geometry): """This record describes an arc as part of the road’s reference line. (Section 5.3.4.1.3 of OpenDRIVE 1.4) """ def __init__(self, start_position, heading, length, curvature): self.curvature = curvature super().__init__(start_position=start_position, heading=heading, length=length)
[docs] def calc_position(self, s_pos): """ Args: s_pos: Returns: """ c = self.curvature hdg = self.heading - np.pi / 2 a = 2 / c * np.sin(s_pos * c / 2) alpha = (np.pi - s_pos * c) / 2 - hdg dx = -1 * a * np.cos(alpha) dy = a * np.sin(alpha) pos = self.start_position + np.array([dx, dy]) tangent = self.heading + s_pos * self.curvature return pos, tangent
[docs]class Spiral(Geometry): """This record describes a spiral as part of the road’s reference line. For this type of spiral, the curvature change between start and end of the element is linear. (Section 5.3.4.1.2 of OpenDRIVE 1.4) """ def __init__(self, start_position, heading, length, curv_start, curv_end): self._curv_start = curv_start self._curv_end = curv_end super().__init__(start_position=start_position, heading=heading, length=length) self._spiral = EulerSpiral.createFromLengthAndCurvature( self.length, self._curv_start, self._curv_end )
[docs] def calc_position(self, s_pos): """ Args: s_pos: Returns: """ (x, y, t) = self._spiral.calc( s_pos, self.start_position[0], self.start_position[1], self._curv_start, self.heading, ) return np.array([x, y]), t
[docs]class Poly3(Geometry): """This record describes a cubic polynomial as part of the road’s reference line. (Section 5.3.4.1.4 of OpenDRIVE 1.4) """ def __init__(self, start_position, heading, length, a, b, c, d): self._a = a self._b = b self._c = c self._d = d super().__init__(start_position=start_position, heading=heading, length=length) # raise NotImplementedError()
[docs] def calc_position(self, s_pos): """ Args: s_pos: Returns: """ # TODO untested # Calculate new point in s_pos/t coordinate system coeffs = [self._a, self._b, self._c, self._d] t = np.polynomial.polynomial.polyval(s_pos, coeffs) # Rotate and translate srot = s_pos * np.cos(self.heading) - t * np.sin(self.heading) trot = s_pos * np.sin(self.heading) + t * np.cos(self.heading) # Derivate to get heading change dCoeffs = coeffs[1:] * np.array(np.arange(1, len(coeffs))) tangent = np.polynomial.polynomial.polyval(s_pos, dCoeffs) return self.start_position + np.array([srot, trot]), self.heading + tangent
[docs]class ParamPoly3(Geometry): """This record describes a parametric cubic curve as part of the road’s reference line in a local u/v co-ordinate system. This record describes an arc as part of the road’s reference line. (Section 5.3.4.1.5 of OpenDRIVE 1.4) """ def __init__( self, start_position, heading, length, aU, bU, cU, dU, aV, bV, cV, dV, pRange ): super().__init__(start_position=start_position, heading=heading, length=length) self._aU = aU self._bU = bU self._cU = cU self._dU = dU self._aV = aV self._bV = bV self._cV = cV self._dV = dV if pRange is None: self._pRange = 1.0 else: self._pRange = pRange
[docs] def calc_position(self, s_pos): """ Args: s_pos: Returns: """ # Position pos = (s_pos / self.length) * self._pRange coeffsU = [self._aU, self._bU, self._cU, self._dU] coeffsV = [self._aV, self._bV, self._cV, self._dV] x = np.polynomial.polynomial.polyval(pos, coeffsU) y = np.polynomial.polynomial.polyval(pos, coeffsV) xrot = x * np.cos(self.heading) - y * np.sin(self.heading) yrot = x * np.sin(self.heading) + y * np.cos(self.heading) # Tangent is defined by derivation dCoeffsU = coeffsU[1:] * np.array(np.arange(1, len(coeffsU))) dCoeffsV = coeffsV[1:] * np.array(np.arange(1, len(coeffsV))) dx = np.polynomial.polynomial.polyval(pos, dCoeffsU) dy = np.polynomial.polynomial.polyval(pos, dCoeffsV) tangent = np.arctan2(dy, dx) return self.start_position + np.array([xrot, yrot]), self.heading + tangent