16 from typing
import Any, List, Tuple, TypedDict, Union
18 from nav2_smac_planner.lattice_primitives.trajectory
import (FloatNDArray, Path, Trajectory,
19 TrajectoryFloat, TrajectoryParameters)
22 logger = logging.getLogger(__name__)
30 """Handles all the logic for generating trajectories."""
32 def __init__(self, config: TrajectoryGeneratorConfigDict):
33 """Init TrajectoryGenerator using the user supplied config."""
37 self, trajectory_params: TrajectoryParameters, t: TrajectoryFloat
38 ) -> Tuple[float, float, TrajectoryFloat]:
40 Get point on the arc trajectory using the following parameterization.
42 r(t) = <R * cos(t - pi/2) + a, R * sin(t - pi/2) + b>
50 trajectory_params: TrajectoryParameters
51 The parameters that describe the arc to create
53 A value between 0 - 1 that denotes where along the arc
54 to calculate the point
59 x coordinate of generated point
61 y coordinate of generated point
63 angle of tangent line to arc at point (x,y)
66 start_angle = trajectory_params.start_angle
68 arc_dist = t * trajectory_params.arc_length
69 angle_step = arc_dist / trajectory_params.turning_radius
71 if trajectory_params.left_turn:
74 t = start_angle + angle_step
76 trajectory_params.turning_radius * np.cos(t - np.pi / 2)
77 + trajectory_params.x_offset
80 trajectory_params.turning_radius * np.sin(t - np.pi / 2)
81 + trajectory_params.y_offset
89 start_angle = -start_angle
93 t = start_angle + angle_step
95 trajectory_params.turning_radius * -np.cos(t + np.pi / 2)
96 + trajectory_params.x_offset
99 trajectory_params.turning_radius * np.sin(t + np.pi / 2)
100 + trajectory_params.y_offset
108 self, start_point: FloatNDArray, end_point: FloatNDArray, t: float
111 Get point on a line segment using the following parameterization.
113 r(t) = p + t * (q - p)
120 start_point: np.array(2,)
121 Starting point of the line segment
122 end_point: np.array(2,)
123 End point of the line segment
125 A value between 0 - 1 that denotes where along the segment
126 to calculate the point
131 x coordinate of generated point
133 y coordinate of generated point
136 return start_point + t * (end_point - start_point)
139 self, trajectory_params: TrajectoryParameters, primitive_resolution: float
142 Create the full trajectory path from the given trajectory parameters.
146 trajectory_params: TrajectoryParameters
147 The parameters that describe the trajectory to create
148 primitive_resolution: float
149 The desired distance between sampled points along the line.
150 This value is not strictly adhered to as the path may not
151 be neatly divisible, however the spacing will be as close
157 The trajectory path described by the trajectory parameters
160 number_of_steps = np.round(
161 trajectory_params.total_length / primitive_resolution
163 t_step = 1 / number_of_steps
165 start_to_arc_dist = np.linalg.norm(trajectory_params.arc_start_point)
167 transition_points = [
168 start_to_arc_dist / trajectory_params.total_length,
169 (start_to_arc_dist + trajectory_params.arc_length)
170 / trajectory_params.total_length,
177 yaws: List[TrajectoryFloat] = []
179 for i
in range(1, number_of_steps + 1):
182 cur_t = min(cur_t, 1)
185 if cur_t <= transition_points[0]:
186 line_t = cur_t / transition_points[0]
188 np.array([0, 0]), trajectory_params.arc_start_point, line_t
190 yaw: TrajectoryFloat = trajectory_params.start_angle
193 elif cur_t <= transition_points[1]:
194 arc_t = (cur_t - transition_points[0]) / (
195 transition_points[1] - transition_points[0]
197 x, y, yaw = self.
_get_arc_point_get_arc_point(trajectory_params, arc_t)
201 line_t = (cur_t - transition_points[1]) / (1 - transition_points[1])
203 trajectory_params.arc_end_point, trajectory_params.end_point, line_t
205 yaw = trajectory_params.end_angle
216 yaws_np = np.array(yaws)
220 xs_np[-1], ys_np[-1] = trajectory_params.end_point
221 yaws_np[-1] = trajectory_params.end_angle
223 return Path(xs_np, ys_np, yaws_np)
225 def _get_intersection_point(
226 self, m1: float, c1: float, m2: float, c2: float
229 Get the intersection point of two lines.
231 The two lines are described by m1 * x + c1 and m2 * x + c2.
238 y-intercept of line 1
247 The intersection point of line 1 and 2
251 def line1(x: float) -> float:
254 x_point = (c2 - c1) / (m1 - m2)
256 return np.array([x_point, line1(x_point)])
258 def _is_left_turn(self, intersection_point: FloatNDArray, end_point: FloatNDArray) -> Any:
260 Determine if a trajectory will be a left turn.
262 Uses the determinant to determine whether the arc formed by the
263 intersection and end point turns left or right.
267 intersection_point: np.array(2,)
268 The intersection point of the lines formed from the start
270 end_point: np.array(2,)
271 The chosen end point of the trajectory
276 True if curve turns left, false otherwise
279 matrix = np.vstack([intersection_point, end_point])
280 det = np.linalg.det(matrix)
284 def _is_dir_vec_correct(
285 self, point1: FloatNDArray, point2: FloatNDArray, line_angle: float
288 Check that the direction vector agrees with the line angle.
290 The direction vector is defined as the vector from point 1 to
296 The start point of the vector
298 The end point of the vector
300 The angle of a line to compare against the vector
305 True if both line and vector point in same direction
309 m = abs(np.tan(line_angle).round(5))
314 direction_vec_from_points = point2 - point1
316 direction_vec_from_gradient = np.array([1, m])
319 if abs(line_angle) > np.pi / 2:
320 direction_vec_from_gradient = np.array([-1, m])
321 elif abs(line_angle) == np.pi / 2:
322 direction_vec_from_gradient = np.array([0, m])
324 direction_vec_from_gradient = direction_vec_from_gradient.round(5)
325 direction_vec_from_points = direction_vec_from_points.round(5)
328 np.sign(direction_vec_from_points) == np.sign(direction_vec_from_gradient)
334 def _calculate_trajectory_params(
335 self, end_point: FloatNDArray, start_angle: float, end_angle: float
336 ) -> Union[TrajectoryParameters,
None]:
338 Calculate the parameters for a trajectory with the desired constraints.
340 The trajectory may consist of an arc and at most two line segments.
341 A straight trajectory will consist of a single line segment. Similarly,
342 a purely curving trajectory will only consist of an arc.
345 1. Extend a line from (0,0) with angle of start_angle
346 2. Extend a line from end_point with angle of end_angle
347 3. Compute their intersection point, I
348 4. Check that the intersection point leads to a
350 - If I is too close to (0,0) or the end point then
351 no arc greater than the turning radius will reach
352 from (0,0) to end point
354 If two segments from the same exterior point are tangent to
355 a circle then they are congruent
359 end_point: np.array(2,)
360 The desired end point of the trajectory
362 The start angle of the trajectory in radians
364 The end angle of the trajectory in radians
368 TrajectoryParameters or None
369 If a valid trajectory exists then the Trajectory parameters
370 are returned, otherwise None
374 arc_start_point = np.array([0, 0])
375 arc_end_point = end_point
379 m1 = np.tan(start_angle).round(5)
383 m2 = np.tan(end_angle).round(5)
389 if round(-m2 * x2 + y2, 5) == 0:
390 return TrajectoryParameters.no_arc(
391 end_point=end_point, start_angle=start_angle, end_angle=end_angle
396 abs(start_angle) == np.pi / 2
and arc_end_point[0] == arc_start_point[0]
398 return TrajectoryParameters.no_arc(
400 start_angle=start_angle,
406 'No trajectory possible for equivalent start and '
407 + f
'end angles that also passes through p = {x2, y2}'
417 arc_start_point, intersection_point, start_angle
420 'No trajectory possible since intersection point occurs '
421 +
'before start point on line 1'
427 if not self.
_is_dir_vec_correct_is_dir_vec_correct(intersection_point, arc_end_point, end_angle):
429 'No trajectory possible since intersection point occurs '
430 +
'after end point on line 2'
435 dist_a = round(np.linalg.norm(arc_start_point - intersection_point), 5)
438 dist_b = round(np.linalg.norm(arc_end_point - intersection_point), 5)
441 angle_between_lines = np.pi - abs(end_angle - start_angle)
450 min_valid_distance = round(
451 self.
turning_radiusturning_radius / np.tan(angle_between_lines / 2), 5
456 if dist_a < min_valid_distance
or dist_b < min_valid_distance:
458 'No trajectory possible where radius is larger than '
459 +
'minimum turning radius'
466 vec_line2 = arc_end_point - intersection_point
467 vec_line2 /= np.linalg.norm(vec_line2)
468 arc_end_point = intersection_point + dist_a * vec_line2
470 elif dist_a > dist_b:
473 vec_line1 = arc_start_point - intersection_point
474 vec_line1 /= np.linalg.norm(vec_line1)
476 arc_start_point = intersection_point + dist_b * vec_line1
478 x1, y1 = arc_start_point
479 x2, y2 = arc_end_point
486 def perp_line2(x: float) -> Any:
487 return -1 / m2 * (x - x2) + y2
489 circle_center = np.array([x1, perp_line2(x1)])
492 def perp_line1(x: float) -> Any:
493 return -1 / m1 * (x - x1) + y1
495 circle_center = np.array([x2, perp_line1(x2)])
497 perp_m1 = -1 / m1
if m1 != 0
else 0
498 perp_m2 = -1 / m2
if m2 != 0
else 0
501 perp_m1, -perp_m1 * x1 + y1, perp_m2, -perp_m2 * x2 + y2
506 radius = np.linalg.norm(circle_center - arc_end_point).round(5)
507 x_offset = circle_center[0].round(5)
508 y_offset = circle_center[1].round(5)
512 'Calculated circle radius is smaller than allowed turning '
513 + f
'radius: r = {radius}, min_radius = {self.turning_radius}'
517 left_turn = self.
_is_left_turn_is_left_turn(intersection_point, end_point)
519 return TrajectoryParameters(
533 end_point: FloatNDArray,
536 primitive_resolution: float,
537 ) -> Union[Trajectory,
None]:
539 Create a trajectory from (0,0, start_angle) to (end_point, end_angle).
541 The trajectory will consist of a path that contains discrete points
542 that are spaced primitive_resolution apart.
546 end_point: np.array(2,)
547 The desired end point of the trajectory
549 The start angle of the trajectory in radians
551 The end angle of the trajectory in radians
552 primitive_resolution: float
553 The spacing between points along the trajectory
558 If a valid trajectory exists then the Trajectory is returned,
563 end_point, start_angle, end_angle
566 if trajectory_params
is None:
569 logger.debug(
'Trajectory found')
571 trajectory_path = self.
_create_path_create_path(trajectory_params, primitive_resolution)
573 return Trajectory(trajectory_path, trajectory_params)
Tuple[float, float, TrajectoryFloat] _get_arc_point(self, TrajectoryParameters trajectory_params, TrajectoryFloat t)
Union[TrajectoryParameters, None] _calculate_trajectory_params(self, FloatNDArray end_point, float start_angle, float end_angle)
Any _is_left_turn(self, FloatNDArray intersection_point, FloatNDArray end_point)
FloatNDArray _get_intersection_point(self, float m1, float c1, float m2, float c2)
bool _is_dir_vec_correct(self, FloatNDArray point1, FloatNDArray point2, float line_angle)
Path _create_path(self, TrajectoryParameters trajectory_params, float primitive_resolution)
def __init__(self, TrajectoryGeneratorConfigDict config)
FloatNDArray _get_line_point(self, FloatNDArray start_point, FloatNDArray end_point, float t)
Union[Trajectory, None] generate_trajectory(self, FloatNDArray end_point, float start_angle, float end_angle, float primitive_resolution)