@@ -27,16 +27,98 @@ class CubicSpline2D:
27
27
cubic spline for y coordinates.
28
28
"""
29
29
30
- def __init__ (self , x , y ):
31
- self .points = np .c_ [x , y ]
32
- if not np .all (self .points [- 1 ] == self .points [0 ]):
30
+ def __init__ (self , x , y ,
31
+ psis : Optional [np .ndarray ] = None ,
32
+ ks : Optional [np .ndarray ] = None ,
33
+ vxs : Optional [np .ndarray ] = None ,
34
+ axs : Optional [np .ndarray ] = None ,
35
+ ss : Optional [np .ndarray ] = None ,
36
+ ):
37
+ self .xs = x
38
+ self .ys = y
39
+ input_vals = [x , y , psis , ks , vxs , axs , ss ]
40
+
41
+ # Only close the path if for the input values from the user,
42
+ # the first and last points are not the same => the path is not closed
43
+ # Otherwise, the constructed values can mess up the s calculation and closure
44
+ need_closure = False
45
+ for input_val in input_vals :
46
+ if input_val is not None :
47
+ if not (input_val [- 1 ] == input_val [0 ]):
48
+ need_closure = True
49
+ break
50
+
51
+ def close_with_constructor (input_val , constructor , closed_path ):
52
+ '''
53
+ If the input value is not None, return it.
54
+ Otherwise, return the constructor, with closure if necessary.
55
+
56
+ Parameters
57
+ ----------
58
+ input_val : np.ndarray | None
59
+ The input value from the user.
60
+ constructor : np.ndarray
61
+ The constructor to use if the input value is None.
62
+ closed_path : bool
63
+ Indicator whether the orirignal path is closed.
64
+ '''
65
+ if input_val is not None :
66
+ return input_val
67
+ else :
68
+ temp_ret = constructor
69
+ if closed_path :
70
+ temp_ret [- 1 ] = temp_ret [0 ]
71
+ return temp_ret
72
+
73
+ self .psis = close_with_constructor (psis , self ._calc_yaw_from_xy (x , y ), not need_closure )
74
+ self .ks = close_with_constructor (ks , self ._calc_kappa_from_xy (x , y ), not need_closure )
75
+ self .vxs = close_with_constructor (vxs , np .ones_like (x ), not need_closure )
76
+ self .axs = close_with_constructor (axs , np .zeros_like (x ), not need_closure )
77
+ self .ss = close_with_constructor (ss , self .__calc_s (x , y ), not need_closure )
78
+ psis_spline = close_with_constructor (psis , self ._calc_yaw_from_xy (x , y ), not need_closure )
79
+
80
+ # If yaw is provided, interpolate cosines and sines of yaw for continuity
81
+ cosines_spline = np .cos (psis_spline )
82
+ sines_spline = np .sin (psis_spline )
83
+
84
+ ks_spline = close_with_constructor (ks , self ._calc_kappa_from_xy (x , y ), not need_closure )
85
+ vxs_spline = close_with_constructor (vxs , np .zeros_like (x ), not need_closure )
86
+ axs_spline = close_with_constructor (axs , np .zeros_like (x ), not need_closure )
87
+
88
+ self .points = np .c_ [self .xs , self .ys ,
89
+ cosines_spline , sines_spline ,
90
+ ks_spline , vxs_spline , axs_spline ]
91
+
92
+ if need_closure :
33
93
self .points = np .vstack (
34
94
(self .points , self .points [0 ])
35
95
) # Ensure the path is closed
36
- self .s = self .__calc_s (self .points [:, 0 ], self .points [:, 1 ])
96
+
97
+ if ss is not None :
98
+ self .s = ss
99
+ else :
100
+ self .s = self .__calc_s (self .points [:, 0 ], self .points [:, 1 ])
101
+ self .s_interval = (self .s [- 1 ] - self .s [0 ]) / len (self .s )
102
+
37
103
# Use scipy CubicSpline to interpolate the points with periodic boundary conditions
38
- # This is necessary to ensure the path is continuous
104
+ # This is necesaxsry to ensure the path is continuous
39
105
self .spline = interpolate .CubicSpline (self .s , self .points , bc_type = "periodic" )
106
+ self .spline_x = np .array (self .spline .x )
107
+ self .spline_c = np .array (self .spline .c )
108
+
109
+
110
+ def find_segment_for_s (self , x ):
111
+ # Find the segment of the spline that x is in
112
+ return (x / (self .spline .x [- 1 ] + self .s_interval ) * (len (self .spline_x ) - 1 )).astype (int )
113
+
114
+ def predict_with_spline (self , point , segment , state_index = 0 ):
115
+ # A (4, 100) array, where the rows contain (x-x[i])**3, (x-x[i])**2 etc.
116
+ # exp_x = (point - self.spline.x[[segment]])[None, :] ** np.arange(4)[::-1, None]
117
+ exp_x = ((point - self .spline .x [segment % len (self .spline .x )]) ** np .arange (4 )[::- 1 ])[:, None ]
118
+ vec = self .spline .c [:, segment % self .spline .c .shape [1 ], state_index ]
119
+ # Sum over the rows of exp_x weighted by coefficients in the ith column of s.c
120
+ point = vec .dot (exp_x )
121
+ return np .asarray (point )
40
122
41
123
def __calc_s (self , x : np .ndarray , y : np .ndarray ) -> np .ndarray :
42
124
"""
@@ -60,6 +142,20 @@ def __calc_s(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:
60
142
s = [0 ]
61
143
s .extend (np .cumsum (self .ds ))
62
144
return np .array (s )
145
+
146
+ def _calc_yaw_from_xy (self , x , y ):
147
+ dx_dt = np .gradient (x )
148
+ dy_dt = np .gradient (y )
149
+ heading = np .arctan2 (dy_dt , dx_dt )
150
+ return heading
151
+
152
+ def _calc_kappa_from_xy (self , x , y ):
153
+ dx_dt = np .gradient (x , 2 )
154
+ dy_dt = np .gradient (y , 2 )
155
+ d2x_dt2 = np .gradient (dx_dt , 2 )
156
+ d2y_dt2 = np .gradient (dy_dt , 2 )
157
+ curvature = - (d2x_dt2 * dy_dt - dx_dt * d2y_dt2 ) / (dx_dt * dx_dt + dy_dt * dy_dt )** 1.5
158
+ return curvature
63
159
64
160
def calc_position (self , s : float ) -> np .ndarray :
65
161
"""
@@ -78,7 +174,10 @@ def calc_position(self, s: float) -> np.ndarray:
78
174
y : float | None
79
175
y position for given s.
80
176
"""
81
- return self .spline (s )
177
+ segment = self .find_segment_for_s (s )
178
+ x = self .predict_with_spline (s , segment , 0 )[0 ]
179
+ y = self .predict_with_spline (s , segment , 1 )[0 ]
180
+ return x ,y
82
181
83
182
def calc_curvature (self , s : float ) -> Optional [float ]:
84
183
"""
@@ -95,11 +194,29 @@ def calc_curvature(self, s: float) -> Optional[float]:
95
194
k : float
96
195
curvature for given s.
97
196
"""
98
- dx , dy = self .spline (s , 1 )
99
- ddx , ddy = self .spline (s , 2 )
100
- k = (ddy * dx - ddx * dy ) / ((dx ** 2 + dy ** 2 ) ** (3 / 2 ))
197
+ segment = self .find_segment_for_s (s )
198
+ k = self .predict_with_spline (s , segment , 4 )[0 ]
101
199
return k
102
200
201
+ def find_curvature (self , s : float ) -> Optional [float ]:
202
+ """
203
+ Find curvature at the given s by the segment.
204
+
205
+ Parameters
206
+ ----------
207
+ s : float
208
+ distance from the start point. if `s` is outside the data point's
209
+ range, return None.
210
+
211
+ Returns
212
+ -------
213
+ k : float
214
+ curvature for given s.
215
+ """
216
+ segment = self .find_segment_for_s (s )
217
+ k = self .points [segment , 4 ]
218
+ return k
219
+
103
220
def calc_yaw (self , s : float ) -> Optional [float ]:
104
221
"""
105
222
Calc yaw angle at the given s.
@@ -114,11 +231,11 @@ def calc_yaw(self, s: float) -> Optional[float]:
114
231
yaw : float
115
232
yaw angle (tangent vector) for given s.
116
233
"""
117
- dx , dy = self .spline ( s , 1 )
118
- yaw = math . atan2 ( dy , dx )
119
- # Convert yaw to [0, 2pi ]
120
- yaw = yaw % (2 * math .pi )
121
-
234
+ segment = self .find_segment_for_s ( s )
235
+ cos = self . predict_with_spline ( s , segment , 2 )[ 0 ]
236
+ sin = self . predict_with_spline ( s , segment , 3 )[ 0 ]
237
+ # yaw = (math.atan2(sin, cos) + 2 * math.pi) % (2 * math.pi)
238
+ yaw = np . arctan2 ( sin , cos )
122
239
return yaw
123
240
124
241
def calc_arclength (
@@ -145,15 +262,15 @@ def calc_arclength(
145
262
"""
146
263
147
264
def distance_to_spline (s ):
148
- x_eval , y_eval = self .spline (s )[0 ]
265
+ x_eval , y_eval = self .spline (s )[0 , : 2 ]
149
266
return np .sqrt ((x - x_eval ) ** 2 + (y - y_eval ) ** 2 )
150
267
151
268
output = so .fmin (distance_to_spline , s_guess , full_output = True , disp = False )
152
269
closest_s = float (output [0 ][0 ])
153
270
absolute_distance = output [1 ]
154
271
return closest_s , absolute_distance
155
272
156
- def calc_arclength_inaccurate (self , x : float , y : float ) -> tuple [float , float ]:
273
+ def calc_arclength_inaccurate (self , x : float , y : float , s_inds = None ) -> tuple [float , float ]:
157
274
"""
158
275
Fast calculation of arclength for a given point (x, y) on the trajectory.
159
276
Less accuarate and less smooth than calc_arclength but much faster.
@@ -173,15 +290,16 @@ def calc_arclength_inaccurate(self, x: float, y: float) -> tuple[float, float]:
173
290
ey : float
174
291
lateral deviation for given x, y.
175
292
"""
293
+ if s_inds is None :
294
+ s_inds = np .arange (self .points .shape [0 ])
176
295
_ , ey , t , min_dist_segment = nearest_point_on_trajectory (
177
- np .array ([x , y ]), self .points
296
+ np .array ([x , y ]). astype ( np . float32 ) , self .points [ s_inds , : 2 ]
178
297
)
179
- # s = s at closest_point + t
298
+ min_dist_segment_s_ind = s_inds [ min_dist_segment ]
180
299
s = float (
181
- self .s [min_dist_segment ]
182
- + t * (self .s [min_dist_segment + 1 ] - self .s [min_dist_segment ])
300
+ self .s [min_dist_segment_s_ind ]
301
+ + t * (self .s [min_dist_segment_s_ind + 1 ] - self .s [min_dist_segment_s_ind ])
183
302
)
184
-
185
303
return s , ey
186
304
187
305
def _calc_tangent (self , s : float ) -> np .ndarray :
@@ -199,7 +317,7 @@ def _calc_tangent(self, s: float) -> np.ndarray:
199
317
tangent : float
200
318
tangent vector for given s.
201
319
"""
202
- dx , dy = self .spline (s , 1 )
320
+ dx , dy = self .spline (s , 1 )[: 2 ]
203
321
tangent = np .array ([dx , dy ])
204
322
return tangent
205
323
@@ -218,6 +336,6 @@ def _calc_normal(self, s: float) -> np.ndarray:
218
336
normal : float
219
337
normal vector for given s.
220
338
"""
221
- dx , dy = self .spline (s , 1 )
339
+ dx , dy = self .spline (s , 1 )[: 2 ]
222
340
normal = np .array ([- dy , dx ])
223
341
return normal
0 commit comments