17 from copy
import deepcopy
20 quaternion_about_axis, quaternion_conjugate, \
21 quaternion_from_matrix, euler_from_matrix
22 from visualization_msgs.msg
import MarkerArray, Marker
23 from geometry_msgs.msg
import Point
24 from uuv_waypoints
import Waypoint, WaypointSet
26 from ..trajectory_point
import TrajectoryPoint
27 from .._log
import get_logger
28 from .helical_segment
import HelicalSegment
29 from .bezier_curve
import BezierCurve
30 from .line_segment
import LineSegment
31 from .path_generator
import PathGenerator
34 """3D Dubins path interpolator implemented based on the sources 39 Owen, Mark, Randal W. Beard, and Timothy W. McLain. 40 "Implementing Dubins Airplane Paths on Fixed-Wing UAVs." 41 Handbook of Unmanned Aerial Vehicles (2014): 1677-1701. 43 Cai, Wenyu, Meiyan Zhang, and Yahong Zheng. "Task Assignment 44 and Path Planning for Multiple Autonomous Underwater Vehicles 45 Using 3D Dubins Curves." Sensors 17.7 (2017):1607. 47 Hansen, Karl D., and Anders La Cour-Harbo. "Waypoint Planning 48 with Dubins Curves Using Genetic Algorithms." 2016 European 49 Control Conference (ECC) (2016). 51 Lin, Yucong, and Srikanth Saripalli. "Path Planning Using 52 3D Dubins Curve for Unmanned Aerial Vehicles." 2014 53 International Conference on Unmanned Aircraft Systems (ICUAS) 59 super(DubinsInterpolator, self).
__init__(self)
67 """Initialize the interpolator. To have the path segments generated, 68 `init_waypoints()` must be called beforehand by providing a set of 69 waypoints as `uuv_waypoints.WaypointSet` type. 73 `bool`: `True` if the path segments were successfully generated. 75 if self._waypoints
is None:
76 self._logger.error(
'List of waypoints is empty')
79 if self._waypoints.num_waypoints < 2:
80 self._logger.error(
'At least 2 waypoints are necessary')
88 last_heading = self._waypoints.get_waypoint(0).heading_offset
90 dist =
lambda x, y: np.sqrt(np.sum((x - y)**2))
92 for i
in range(1, self._waypoints.num_waypoints):
97 heading_init = self._waypoints.get_waypoint(i - 1).heading_offset
99 if not np.isclose(dist(self._waypoints.get_waypoint(i - 1).pos[0:2], self._waypoints.get_waypoint(i).pos[0:2]), 0):
100 heading_init = self._waypoints.get_waypoint(i - 1).calculate_heading(self._waypoints.get_waypoint(i))
102 heading_init = last_heading
104 if i == self._waypoints.num_waypoints - 1:
105 if not np.isclose(dist(self._waypoints.get_waypoint(i - 1).pos[0:2], self._waypoints.get_waypoint(i).pos[0:2]), 0):
106 heading_final = self._waypoints.get_waypoint(i - 1).calculate_heading(self._waypoints.get_waypoint(i))
108 heading_final = last_heading
110 if not np.isclose(dist(self._waypoints.get_waypoint(i + 1).pos[0:2], self._waypoints.get_waypoint(i).pos[0:2]), 0):
111 heading_final = self._waypoints.get_waypoint(i).calculate_heading(self._waypoints.get_waypoint(i + 1))
113 heading_final = last_heading
115 last_heading = heading_final
118 self._waypoints.get_waypoint(i - 1), heading_init,
119 self._waypoints.get_waypoint(i), heading_final)
122 for i
in range(len(path) - 1):
123 if not np.isclose(np.sqrt(np.sum((path[i + 1] - path[i])**2)), 0):
124 inter_pnts += [path[i]]
126 if not np.isclose(np.sqrt(np.sum((path[-1] - path[-2])**2)), 0):
127 inter_pnts += [path[-1]]
129 self.
_interp_fcns, tangent = BezierCurve.generate_cubic_curve(inter_pnts)
132 lengths = [seg.get_length()
for seg
in self.
_interp_fcns]
133 lengths = [0] + lengths
134 self.
_s = np.cumsum(lengths) / np.sum(lengths)
136 [self._waypoints.get_waypoint(k).max_forward_speed
for k
in range(self._waypoints.num_waypoints)])
146 """Return the 2D rotation matrix for a desired heading. 150 * `heading` (*type:* `float`): Heading angle in radians 154 `numpy.array`: 2D rotation matrix 156 return np.array([[np.cos(heading), -np.sin(heading)],[np.sin(heading), np.cos(heading)]])
159 """Compute the distance between two points. 163 * `pnt_1` (*type:* `numpy.array`): Point 1 164 * `pnt_2` (*type:* `numpy.array`): Point 2 168 `float`: Distance between points 170 return np.sqrt(np.sum((pnt_1 - pnt_2)**2))
173 """Return the centers of the circles on the left and right of the 174 waypoint with respect to the direction described the desired heading. 178 * `waypoint` (*type:* `uuv_wapoints.Waypoint`): Waypoint 179 * `heading` (*type:* `float`): Desired heading in radians 180 * `radius` (*type:* `float`, *default:* `None`): Desired radius for the 181 circles. If `None` is provided, then the internal radius will be used. 185 `dict` with the left `L` and right `R` center of the circles as `numpy.array` 188 r = self.
_radius if radius
is None else radius
191 msg =
'Radius must be greater than zero' 192 self._logger.error(msg)
193 raise ValueError(
'Radius must be greater than zero')
198 circles = dict(R=waypoint.pos[0:2] - r * frame[:, 1].flatten(),
199 L=waypoint.pos[0:2] + r * frame[:, 1].flatten())
204 return 2 * np.pi * u * delta + heading - delta * np.pi / 2
207 """Compute the parametric input for the circle path. 211 * `angle` (*type:* `float`): Angle in the circle's path 213 * `delta` (*type:* `int`): Generate the points in counter-clockwise 214 direction if `delta` is -1 and clockwise if `delta` is 1 215 * `heading` (*type:* `float`): Heading offset angle in radians 219 `float`: Circle's parametric variable 221 u = (angle - heading + delta * np.pi / 2) / (delta * 2 * np.pi)
227 """Function description 231 * `param` (*type:* `data_type`, *default:* `data`): Parameter description 235 Description of return values 237 return np.cross(np.array([0, 0, 1]),
238 np.array([delta * radius * np.cos(self.
_get_phi(u, delta, heading)),
239 delta * radius * np.sin(self.
_get_phi(u, delta, heading)),
243 """Compute the 2D coordinates for a circle. 247 * `u` (*type:* `float`): Parametric variable in interval [0, 1] 248 * `center` (*type:* `numpy.array`): Center of the circle in meters 249 * `radius` (*type:* `float`): Radius of the circle in meters 250 * `delta` (*type:* `int`): Generate the points in counter-clockwise 251 direction if `delta` is -1 and clockwise if `delta` is 1 252 * `heading` (*type:* `float`): Heading associated to the waypoint 256 Circle coordinates as `numpy.array`. 258 return center + radius * np.array([np.cos(self.
_get_phi(u, delta, heading)),
259 np.sin(self.
_get_phi(u, delta, heading))])
261 def _get_2d_dubins_path(self, center_1, radius_1, heading_1, delta_1, center_2, radius_2, heading_2, delta_2):
262 """Compute the 2D Dubins path algorithm. It computes the shortest curve 263 that connects two points. Given two circles which are tangent to the 264 origin and target waypoints, with a chosen heading to depart the first 265 and reach the second, find the path that will be first tangent 266 on circle tangent to the first waypoint, travel to towards the 267 second in a straight line and reach the closest tangent on the 268 circle around the second waypoint. 272 * `center_1` (*type:* `numpy.array`): 2D center of the circle tangent to 274 * `radius_1` (*type:* `float`): Radius of the circle related to the origin 276 * `heading_1` (*type:* `float`): Desired heading associated to the origin 278 * `delta_1` (*type:* `int`): Direction to travel around the circle, -1 for 279 counter-clockwise and 1 for clockwise 280 * `center_2` (*type:* `numpy.array`): 2D center of the circle tangent to 282 * `radius_2` (*type:* `float`): Radius of the circle related to the target 284 * `heading_2` (*type:* `float`): Desired heading associated to the target 286 * `delta_2` (*type:* `int`): Direction to travel around the circle, -1 for 287 counter-clockwise and 1 for clockwise 291 Description of return values 295 u1_func =
lambda angle: self.
_compute_u(angle, delta_1, heading_1)
296 u2_func =
lambda angle: self.
_compute_u(angle, delta_2, heading_2)
298 tan_func_1 =
lambda u: self.
_get_tangent(u, delta_1, radius_1, heading_1)
299 tan_func_2 =
lambda u: self.
_get_tangent(u, delta_2, radius_2, heading_2)
301 circle_1 =
lambda u: self.
_get_circle(u, center_1, radius_1, delta_1, heading_1)
302 circle_2 =
lambda u: self.
_get_circle(u, center_2, radius_2, delta_2, heading_2)
306 d = center_2 - center_1
308 n = np.dot(self.
_get_frame(np.pi / 2), d / np.linalg.norm(d))
310 n_angle = np.arctan2(n[1], n[0])
313 u1 = u1_func(n_angle)
314 u2 = u2_func(n_angle)
323 t1 /= np.linalg.norm(t1)
325 t2 /= np.linalg.norm(t2)
328 tangent_1 /= np.linalg.norm(tangent_1)
332 diff = np.linalg.norm(tangent_1 - t1) + np.linalg.norm(tangent_1 - t2)
334 if np.isclose(diff, 0):
338 if not np.isclose(u1, 0):
339 u = np.arange(0, u1, u1 / 10.0)
341 output = [circle_1(ui)
for ui
in u]
342 dist = 2 * radius_1 * np.pi * u1
343 output += [circle_1(u1)]
345 dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
347 if not np.isclose(u2, 1):
348 u = np.arange(u2, 1, (1 - u2) / 10.0)
349 output += [circle_2(ui)
for ui
in u]
350 dist += 2 * radius_2 * np.pi * (1 - u2)
352 output += [circle_2(1)]
358 n_angle = np.arctan2(-n[1], -n[0])
360 u1 = u1_func(n_angle)
361 u2 = u2_func(n_angle)
370 t1 /= np.linalg.norm(t1)
372 t2 /= np.linalg.norm(t2)
376 tangent_2 /= np.linalg.norm(tangent_2)
380 diff = np.linalg.norm(tangent_2 - t1) + np.linalg.norm(tangent_2 - t2)
382 if np.isclose(diff, 0):
386 if not np.isclose(u1, 0):
387 u = np.arange(0, u1, u1 / 10.0)
389 output = [circle_1(ui)
for ui
in u]
390 dist = 2 * radius_1 * np.pi * u1
391 output += [circle_1(u1)]
393 dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
395 if not np.isclose(u2, 1):
396 u = np.arange(u2, 1, (1 - u2) / 10.0)
397 output += [circle_2(ui)
for ui
in u]
398 dist += 2 * radius_2 * np.pi * (1 - u2)
400 output += [circle_2(1)]
405 if self.
_get_distance(center_1, center_2) > (radius_1 + radius_2):
407 xp = (center_1[0] * radius_1 + center_2[0] * radius_2) / (radius_1 + radius_2)
408 yp = (center_1[1] * radius_1 + center_2[1] * radius_2) / (radius_1 + radius_2)
411 xt1 = (radius_1**2 * (xp - center_1[0]) + radius_1 * (yp - center_1[1]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[0]
412 xt2 = (radius_1**2 * (xp - center_1[0]) - radius_1 * (yp - center_1[1]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[0]
414 yt1 = ((radius_1**2 * (yp - center_1[1])) - radius_1 * (xp - center_1[0]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[1]
415 yt2 = ((radius_1**2 * (yp - center_1[1])) + radius_1 * (xp - center_1[0]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[1]
417 xt3 = (radius_2**2 * (xp - center_2[0]) + radius_2 * (yp - center_2[1]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[0]
418 xt4 = (radius_2**2 * (xp - center_2[0]) - radius_2 * (yp - center_2[1]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[0]
420 yt3 = ((radius_2**2 * (yp - center_2[1])) - radius_2 * (xp - center_2[0]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[1]
421 yt4 = ((radius_2**2 * (yp - center_2[1])) + radius_2 * (xp - center_2[0]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[1]
425 u1 = u1_func(np.arctan2(yt1 - center_1[1], xt1 - center_1[0]))
426 u2 = u2_func(np.arctan2(yt3 - center_2[1], xt3 - center_2[0]))
435 t1 /= np.linalg.norm(t1)
437 t2 /= np.linalg.norm(t2)
439 tangent_3 = np.array([xt3 - xt1, yt3 - yt1])
440 tangent_3 /= np.linalg.norm(tangent_3)
444 diff = np.linalg.norm(tangent_3 - t1) + np.linalg.norm(tangent_3 - t2)
446 if np.isclose(diff, 0):
450 if not np.isclose(u1, 0):
451 u = np.arange(0, u1, u1 / 10.0)
453 output = [circle_1(ui)
for ui
in u]
454 dist = 2 * radius_1 * np.pi * u1
455 output += [circle_1(u1)]
457 dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
459 if not np.isclose(u2, 1):
460 u = np.arange(u2, 1, (1 - u2) / 10.0)
461 output += [circle_2(ui)
for ui
in u]
462 dist += 2 * radius_2 * np.pi * (1 - u2)
464 output += [circle_2(1)]
470 u1 = u1_func(np.arctan2(yt2 - center_1[1], xt2 - center_1[0]))
471 u2 = u2_func(np.arctan2(yt4 - center_2[1], xt4 - center_2[0]))
480 t1 /= np.linalg.norm(t1)
482 t2 /= np.linalg.norm(t2)
484 tangent_4 = np.array([xt4 - xt2, yt4 - yt2])
485 tangent_4 /= np.linalg.norm(tangent_4)
489 diff = np.linalg.norm(tangent_4 - t1) + np.linalg.norm(tangent_4 - t2)
491 if np.isclose(diff, 0):
495 if not np.isclose(u1, 0):
496 u = np.arange(0, u1, u1 / 10.0)
498 output = [circle_1(ui)
for ui
in u]
499 dist = 2 * radius_1 * np.pi * u1
500 output += [circle_1(u1)]
502 dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
504 if not np.isclose(u2, 1):
505 u = np.arange(u2, 1, (1 - u2) / 10.0)
506 output += [circle_2(ui)
for ui
in u]
507 dist += 2 * radius_2 * np.pi * (1 - u2)
509 output += [circle_2(1)]
517 return wp.pos - self.
_radius * y_vec
519 return wp.pos + self.
_radius * y_vec
522 circle_color=[0, 1, 0]):
525 marker.header.frame_id = frame_id
527 if not rospy.is_shutdown():
528 marker.header.stamp = rospy.Time.now()
534 marker.type = Marker.LINE_STRIP
535 marker.action = Marker.ADD;
537 marker.scale.x = 0.05
541 marker.color.r = circle_color[0]
542 marker.color.g = circle_color[1]
543 marker.color.b = circle_color[2]
545 for i
in np.linspace(0, 1, 50):
546 c_pnt = self.
_get_circle(i, center[0:2], radius, delta, heading)
547 marker.points.append(Point(c_pnt[0], c_pnt[1], center[2]))
550 marker_pnt = Marker()
551 marker_pnt.header.frame_id = frame_id
553 if not rospy.is_shutdown():
554 marker_pnt.header.stamp = rospy.Time.now()
557 marker_pnt.ns =
'dubins' 560 marker_pnt.type = Marker.SPHERE
561 marker_pnt.action = Marker.ADD;
563 marker_pnt.scale.x = 0.2
564 marker_pnt.scale.y = 0.2
565 marker_pnt.scale.z = 0.2
566 marker_pnt.color.a = 1.0
567 marker_pnt.color.r = 1.0
568 marker_pnt.color.g = 0.2
569 marker_pnt.color.b = 0.0
571 c_pnt = self.
_get_circle(0, center[0:2], radius, delta, heading)
572 marker_pnt.pose.position.x = c_pnt[0]
573 marker_pnt.pose.position.y = c_pnt[1]
574 marker_pnt.pose.position.z = center[2]
575 return [marker, marker_pnt]
582 frame_init = np.array([[np.cos(heading_init), -np.sin(heading_init), 0],
583 [np.sin(heading_init), np.cos(heading_init), 0],
586 frame_final = np.array([[np.cos(heading_final), -np.sin(heading_final), 0],
587 [np.sin(heading_final), np.cos(heading_final), 0],
592 heading_diff = (heading_final - heading_init + np.pi) % (2.0 * np.pi) - np.pi
593 dist_xy = np.sqrt(np.sum((wp_init.pos[0:2] - wp_final.pos[0:2])**2))
595 if np.isclose(heading_diff, 0)
or np.isclose(dist_xy, 0):
596 if abs(wp_init.z - wp_final.z) <= max_step_z
and not np.isclose(dist_xy, 0):
597 pnts = [wp_init.pos, wp_final.pos]
599 z = wp_final.z - wp_init.z
601 delta_z = z / (np.floor(abs(z) / max_step_z) + np.ceil(abs(z) % max_step_z))
606 center = self.
_get_center(
'R', frame_final[:, 1].flatten(), wp_final) 607 center[2] = wp_init.z 610 helix = HelicalSegment( 611 center, self._radius, n, wp_final.z - wp_init.z, heading_final - delta * np.pi / 2, False)
613 for i
in np.linspace(0, 1, n * 10):
614 pnts.append(helix.interpolate(i))
621 modes = [
'RSR',
'RSL',
'LSR',
'LSL']
625 c = self.
_get_center(
'R', frame_init[:,1].flatten(), wp_init) 629 c = self._get_center('L', frame_init[:,1].flatten(), wp_init)
633 c = self.
_get_center(
'R', frame_final[:,1].flatten(), wp_final) 637 c = self._get_center('L', frame_final[:,1].flatten(), wp_final)
646 center_1 = self.
_get_center(c[0], frame_init[:,1].flatten(), wp_init)
647 center_2 = self.
_get_center(c[-1], frame_final[:,1].flatten(), wp_final)
649 delta_1 = -1
if c[0] ==
'R' else 1 650 delta_2 = -1 if c[-1] ==
'R' else 1 653 center_1[0:2], self._radius, heading_init, delta_1, 654 center_2[0:2], self._radius, heading_final, delta_2) 669 if np.isclose(abs(wp_init.z - wp_final.z), 0):
671 pnts.append(np.array([pnt[0], pnt[1], wp_final.z]))
672 elif abs(wp_init.z - wp_final.z) <= max_step_z
and not np.isclose(abs(wp_init.z - wp_final.z), 0):
674 d = [0.0] + [np.sqrt(np.sum((path[i] - path[i - 1])**2))
for i
in range(1, len(path))]
675 dz = float(wp_final.z - wp_init.z) * np.cumsum(d) / np.sum(d)
676 for i
in range(len(path)):
677 pnts.append(np.array([path[i][0], path[i][1], wp_init.z + dz[i]]))
679 z = wp_final.z - wp_init.z
680 delta_z = z / (np.floor(abs(z) / max_step_z) + np.ceil(abs(z) % max_step_z))
683 d = [0.0] + [np.sqrt(np.sum((path[i] - path[i - 1])**2))
for i
in range(1, len(path))]
684 dz = delta_z * np.cumsum(d) / np.sum(d)
686 for i
in range(len(path)):
687 pnts.append(np.array([path[i][0], path[i][1], wp_init.z + dz[i]]))
689 center = self.
_get_center(mode[-1], frame_final[:, 1].flatten(), wp_final)
690 center[2] = wp_init.z + delta_z
692 delta = -1
if mode[-1] ==
'R' else 1 693 helix = HelicalSegment( 694 center, self._radius, n - 1, wp_final.z - center[2], heading_final - delta * np.pi / 2, False if mode[-1] ==
'R' else True) 696 for i
in np.linspace(0, 1, (n - 1) * 10):
697 pnts.append(helix.interpolate(i))
702 """Set interpolator's parameters. All the options 703 for the `params` input can be seen below: 712 * `radius` (*type:* `float`): Turning radius 713 * `max_pitch` (*type:* `float`): Max. pitch angle allowed 714 between two waypoints. If the pitch exceeds `max_pitch`, a 715 helical path is computed to perform steep climbs or dives. 719 * `params` (*type:* `dict`): `dict` containing interpolator's 720 configurable elements. 722 if 'radius' in params:
723 assert params[
'radius'] > 0,
'Radius must be greater than zero' 724 self.
_radius = params[
'radius']
725 if 'max_pitch' in params:
726 assert params[
'max_pitch'] > 0
and params[
'max_pitch'] <= np.pi,
'Invalid max. pitch' 731 """Sample the full path for position and quaternion vectors. 732 `step` is represented in the path's parametric space. 736 * `step` (*type:* `float`, *default:* `0.001`): Parameter description 740 List of `uuv_trajectory_generator.TrajectoryPoint`. 742 if self._waypoints
is None:
746 s = np.arange(0, 1 + step, step)
750 pnt = TrajectoryPoint()
757 """Generate a position vector for the path sampled point 758 interpolated on the position related to `s`, `s` being 759 represented in the curve's parametric space. 763 * `s` (*type:* `float`): Curve's parametric input expressed in the 768 3D position vector as a `numpy.array`. 772 idx = self.get_segment_idx(s)
777 u_k = (s - self.
_s[idx - 1]) / (self.
_s[idx] - self.
_s[idx - 1])
782 """Compute a point that belongs to the path on the 783 interpolated space related to `s`, `s` being represented 784 in the curve's parametric space. 788 * `s` (*type:* `float`): Curve's parametric input expressed in the 790 * `t` (*type:* `float`): Trajectory point's timestamp 794 `uuv_trajectory_generator.TrajectoryPoint` including position 795 and quaternion vectors. 797 pnt = TrajectoryPoint()
807 """Compute the quaternion of the path reference for a interpolated 808 point related to `s`, `s` being represented in the curve's parametric 810 The quaternion is computed assuming the heading follows the direction 811 of the path towards the target. Roll and pitch can also be computed 812 in case the `full_dof` is set to `True`. 816 * `s` (*type:* `float`): Curve's parametric input expressed in the 821 Rotation quaternion as a `numpy.array` as `(x, y, z, w)` 828 return self._init_rot
830 last_s = max(0, s - self._s_step)
835 dx = this_pos[0] - last_pos[0]
836 dy = this_pos[1] - last_pos[1]
837 dz = this_pos[2] - last_pos[2]
839 rotq = self._compute_rot_quat(dx, dy, dz)
def generate_pos(self, s)
def set_parameters(self, params)
def _get_frame(self, heading)
def generate_pnt(self, s, t, args)
def _get_tangent(self, u, delta, radius, heading)
def _get_circles_center_pos(self, waypoint, heading, radius=None)
def _generate_path(self, wp_init, heading_init, wp_final, heading_final)
def get_samples(self, max_time, step=0.001)
def _get_circle(self, u, center, radius, delta, heading)
def _get_distance(self, pnt_1, pnt_2)
def _get_2d_dubins_path(self, center_1, radius_1, heading_1, delta_1, center_2, radius_2, heading_2, delta_2)
def _get_phi(self, u, delta, heading)
def init_interpolator(self)
def _get_center(self, side, y_vec, wp)
def generate_quat(self, s)
def _get_circle_marker(self, center, radius, heading, delta, frame_id, circle_color=[0)
def _compute_u(self, angle, delta, heading)