Dubins¶
src.python_motion_planning.curve_generation.dubins_curve.Dubins
¶
Bases: Curve
Class for Dubins curve generation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
step
|
float
|
Simulation or interpolation size |
required |
max_curv
|
float
|
The maximum curvature of the curve |
required |
Examples:
>>> from python_motion_planning.curve_generation import Dubins
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
>>> generator = Dubins(step, max_curv)
>>> generator.run(points)
References
[1] On curves of minimal length with a constraint on average curvature, and with prescribed initial and terminal positions and tangents
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
class Dubins(Curve):
"""
Class for Dubins curve generation.
Parameters:
step (float): Simulation or interpolation size
max_curv (float): The maximum curvature of the curve
Examples:
>>> from python_motion_planning.curve_generation import Dubins
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
>>> generator = Dubins(step, max_curv)
>>> generator.run(points)
References:
[1] On curves of minimal length with a constraint on average curvature, and with prescribed initial and terminal positions and tangents
"""
def __init__(self, step: float, max_curv: float) -> None:
super().__init__(step)
self.max_curv = max_curv
def __str__(self) -> str:
return "Dubins Curve"
def LSL(self, alpha: float, beta: float, dist: float):
"""
Left-Straight-Left generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_lsl = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_a - sin_b)
if p_lsl < 0:
return None, None, None, ["L", "S", "L"]
else:
p_lsl = math.sqrt(p_lsl)
t_lsl = self.mod2pi(-alpha + math.atan2(cos_b - cos_a, dist + sin_a - sin_b))
q_lsl = self.mod2pi(beta - math.atan2(cos_b - cos_a, dist + sin_a - sin_b))
return t_lsl, p_lsl, q_lsl, ["L", "S", "L"]
def RSR(self, alpha: float, beta: float, dist: float):
"""
Right-Straight-Right generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_rsr = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_b - sin_a)
if p_rsr < 0:
return None, None, None, ["R", "S", "R"]
else:
p_rsr = math.sqrt(p_rsr)
t_rsr = self.mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b))
q_rsr = self.mod2pi(-beta + math.atan2(cos_a - cos_b, dist - sin_a + sin_b))
return t_rsr, p_rsr, q_rsr, ["R", "S", "R"]
def LSR(self, alpha: float, beta: float, dist: float):
"""
Left-Straight-Right generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_lsr = -2 + dist ** 2 + 2 * cos_a_b + 2 * dist * (sin_a + sin_b)
if p_lsr < 0:
return None, None, None, ["L", "S", "R"]
else:
p_lsr = math.sqrt(p_lsr)
t_lsr = self.mod2pi(-alpha + math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr))
q_lsr = self.mod2pi(-beta + math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr))
return t_lsr, p_lsr, q_lsr, ["L", "S", "R"]
def RSL(self, alpha: float, beta: float, dist: float):
"""
Right-Straight-Left generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_rsl = -2 + dist ** 2 + 2 * cos_a_b - 2 * dist * (sin_a + sin_b)
if p_rsl < 0:
return None, None, None, ["R", "S", "L"]
else:
p_rsl = math.sqrt(p_rsl)
t_rsl = self.mod2pi(alpha - math.atan2(cos_a + cos_b, dist - sin_a - sin_b) + math.atan2(2.0, p_rsl))
q_rsl = self.mod2pi(beta - math.atan2(cos_a + cos_b, dist - sin_a - sin_b) + math.atan2(2.0, p_rsl))
return t_rsl, p_rsl, q_rsl, ["R", "S", "L"]
def RLR(self, alpha: float, beta: float, dist: float):
"""
Right-Left-Right generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_rlr = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0
if abs(p_rlr) > 1.0:
return None, None, None, ["R", "L", "R"]
else:
p_rlr = self.mod2pi(2 * math.pi - math.acos(p_rlr))
t_rlr = self.mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b) + p_rlr / 2.0)
q_rlr = self.mod2pi(alpha - beta - t_rlr + p_rlr)
return t_rlr, p_rlr, q_rlr, ["R", "L", "R"]
def LRL(self, alpha: float, beta: float, dist: float):
"""
Left-Right-Left generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_lrl = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0
if abs(p_lrl) > 1.0:
return None, None, None, ["L", "R", "L"]
else:
p_lrl = self.mod2pi(2 * math.pi - math.acos(p_lrl))
t_lrl = self.mod2pi(-alpha + math.atan2(-cos_a + cos_b, dist + sin_a - sin_b) + p_lrl / 2.0)
q_lrl = self.mod2pi(beta - alpha - t_lrl + p_lrl)
return t_lrl, p_lrl, q_lrl, ["L", "R", "L"]
def interpolate(self, mode: str, length: float, init_pose: tuple):
"""
Planning path interpolation.
Parameters:
mode (str): motion, e.g., L, S, R
length (float): Single step motion path length
init_pose (tuple): Initial pose (x, y, yaw)
Returns:
new_pose (tuple): New pose (new_x, new_y, new_yaw) after moving
"""
x, y, yaw = init_pose
if mode == "S":
new_x = x + length / self.max_curv * math.cos(yaw)
new_y = y + length / self.max_curv * math.sin(yaw)
new_yaw = yaw
elif mode == "L":
new_x = x + (math.sin(yaw + length) - math.sin(yaw)) / self.max_curv
new_y = y - (math.cos(yaw + length) - math.cos(yaw)) / self.max_curv
new_yaw = yaw + length
elif mode == "R":
new_x = x - (math.sin(yaw - length) - math.sin(yaw)) / self.max_curv
new_y = y + (math.cos(yaw - length) - math.cos(yaw)) / self.max_curv
new_yaw = yaw - length
else:
raise NotImplementedError
return new_x, new_y, new_yaw
def generation(self, start_pose: tuple, goal_pose: tuple):
"""
Generate the Dubins Curve.
Parameters:
start_pose (tuple): Initial pose (x, y, yaw)
goal_pose (tuple): Target pose (x, y, yaw)
Returns:
best_cost (float): Best planning path length
best_mode (list): Best motion modes
x_list (list): Trajectory of x
y_list (list): Trajectory of y
yaw_list (list): Trajectory of yaw
"""
sx, sy, syaw = start_pose
gx, gy, gyaw = goal_pose
# coordinate transformation
gx, gy = gx - sx, gy - sy
theta = self.mod2pi(math.atan2(gy, gx))
dist = math.hypot(gx, gy) * self.max_curv
alpha = self.mod2pi(syaw - theta)
beta = self.mod2pi(gyaw - theta)
# select the best motion
planners = [self.LSL, self.RSR, self.LSR, self.RSL, self.RLR, self.LRL]
best_t, best_p, best_q, best_mode, best_cost = None, None, None, None, float("inf")
for planner in planners:
t, p, q, mode = planner(alpha, beta, dist)
if t is None:
continue
cost = (abs(t) + abs(p) + abs(q))
if best_cost > cost:
best_t, best_p, best_q, best_mode, best_cost = t, p, q, mode, cost
# interpolation
segments = [best_t, best_p, best_q]
points_num = int(sum(segments) / self.step) + len(segments) + 3
x_list = [0.0 for _ in range(points_num)]
y_list = [0.0 for _ in range(points_num)]
yaw_list = [alpha for _ in range(points_num)]
i = 0
for mode_, seg_length in zip(best_mode, segments):
# path increment
d_length = self.step if seg_length > 0.0 else -self.step
x, y, yaw = x_list[i], y_list[i], yaw_list[i]
# current path length
length = d_length
while abs(length) <= abs(seg_length):
i += 1
x_list[i], y_list[i], yaw_list[i] = self.interpolate(mode_, length, (x, y, yaw))
length += d_length
i += 1
x_list[i], y_list[i], yaw_list[i] = self.interpolate(mode_, seg_length, (x, y, yaw))
# failed
if len(x_list) <= 1:
return None, None, [], [], []
# remove unused data
while len(x_list) >= 1 and x_list[-1] == 0.0:
x_list.pop()
y_list.pop()
yaw_list.pop()
# coordinate transformation
rot = Rot.from_euler('z', theta).as_matrix()[0:2, 0:2]
converted_xy = rot @ np.stack([x_list, y_list])
x_list = converted_xy[0, :] + sx
y_list = converted_xy[1, :] + sy
yaw_list = [self.pi2pi(i_yaw + theta) for i_yaw in yaw_list]
return best_cost, best_mode, x_list, y_list, yaw_list
def run(self, points: list):
"""
Running both generation and animation.
Parameters:
points (list[tuple]): path points
"""
assert len(points) >= 2, "Number of points should be at least 2."
import matplotlib.pyplot as plt
# generation
path_x, path_y, path_yaw = [], [], []
for i in range(len(points) - 1):
_, _, x_list, y_list, yaw_list = self.generation(
(points[i][0], points[i][1], np.deg2rad(points[i][2])),
(points[i + 1][0], points[i + 1][1], np.deg2rad(points[i + 1][2])))
for j in range(len(x_list)):
path_x.append(x_list[j])
path_y.append(y_list[j])
path_yaw.append(yaw_list[j])
# animation
plt.figure("curve generation")
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
for x, y, theta in points:
Plot.plotArrow(x, y, np.deg2rad(theta), 2, 'blueviolet')
plt.axis("equal")
plt.title(str(self))
plt.show()
LRL(alpha, beta, dist)
¶
Left-Right-Left generation mode.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alpha
|
float
|
Initial pose of (0, 0, alpha) |
required |
beta
|
float
|
Goal pose of (dist, 0, beta) |
required |
dist
|
float
|
The distance between the initial and goal pose |
required |
Returns:
Name | Type | Description |
---|---|---|
t |
float
|
Moving lenght of segments |
p |
float
|
Moving lenght of segments |
q |
float
|
Moving lenght of segments |
mode |
list
|
Motion mode |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def LRL(self, alpha: float, beta: float, dist: float):
"""
Left-Right-Left generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_lrl = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0
if abs(p_lrl) > 1.0:
return None, None, None, ["L", "R", "L"]
else:
p_lrl = self.mod2pi(2 * math.pi - math.acos(p_lrl))
t_lrl = self.mod2pi(-alpha + math.atan2(-cos_a + cos_b, dist + sin_a - sin_b) + p_lrl / 2.0)
q_lrl = self.mod2pi(beta - alpha - t_lrl + p_lrl)
return t_lrl, p_lrl, q_lrl, ["L", "R", "L"]
LSL(alpha, beta, dist)
¶
Left-Straight-Left generation mode.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alpha
|
float
|
Initial pose of (0, 0, alpha) |
required |
beta
|
float
|
Goal pose of (dist, 0, beta) |
required |
dist
|
float
|
The distance between the initial and goal pose |
required |
Returns:
Name | Type | Description |
---|---|---|
t |
float
|
Moving lenght of segments |
p |
float
|
Moving lenght of segments |
q |
float
|
Moving lenght of segments |
mode |
list
|
Motion mode |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def LSL(self, alpha: float, beta: float, dist: float):
"""
Left-Straight-Left generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_lsl = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_a - sin_b)
if p_lsl < 0:
return None, None, None, ["L", "S", "L"]
else:
p_lsl = math.sqrt(p_lsl)
t_lsl = self.mod2pi(-alpha + math.atan2(cos_b - cos_a, dist + sin_a - sin_b))
q_lsl = self.mod2pi(beta - math.atan2(cos_b - cos_a, dist + sin_a - sin_b))
return t_lsl, p_lsl, q_lsl, ["L", "S", "L"]
LSR(alpha, beta, dist)
¶
Left-Straight-Right generation mode.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alpha
|
float
|
Initial pose of (0, 0, alpha) |
required |
beta
|
float
|
Goal pose of (dist, 0, beta) |
required |
dist
|
float
|
The distance between the initial and goal pose |
required |
Returns:
Name | Type | Description |
---|---|---|
t |
float
|
Moving lenght of segments |
p |
float
|
Moving lenght of segments |
q |
float
|
Moving lenght of segments |
mode |
list
|
Motion mode |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def LSR(self, alpha: float, beta: float, dist: float):
"""
Left-Straight-Right generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_lsr = -2 + dist ** 2 + 2 * cos_a_b + 2 * dist * (sin_a + sin_b)
if p_lsr < 0:
return None, None, None, ["L", "S", "R"]
else:
p_lsr = math.sqrt(p_lsr)
t_lsr = self.mod2pi(-alpha + math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr))
q_lsr = self.mod2pi(-beta + math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr))
return t_lsr, p_lsr, q_lsr, ["L", "S", "R"]
RLR(alpha, beta, dist)
¶
Right-Left-Right generation mode.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alpha
|
float
|
Initial pose of (0, 0, alpha) |
required |
beta
|
float
|
Goal pose of (dist, 0, beta) |
required |
dist
|
float
|
The distance between the initial and goal pose |
required |
Returns:
Name | Type | Description |
---|---|---|
t |
float
|
Moving lenght of segments |
p |
float
|
Moving lenght of segments |
q |
float
|
Moving lenght of segments |
mode |
list
|
Motion mode |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def RLR(self, alpha: float, beta: float, dist: float):
"""
Right-Left-Right generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_rlr = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0
if abs(p_rlr) > 1.0:
return None, None, None, ["R", "L", "R"]
else:
p_rlr = self.mod2pi(2 * math.pi - math.acos(p_rlr))
t_rlr = self.mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b) + p_rlr / 2.0)
q_rlr = self.mod2pi(alpha - beta - t_rlr + p_rlr)
return t_rlr, p_rlr, q_rlr, ["R", "L", "R"]
RSL(alpha, beta, dist)
¶
Right-Straight-Left generation mode.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alpha
|
float
|
Initial pose of (0, 0, alpha) |
required |
beta
|
float
|
Goal pose of (dist, 0, beta) |
required |
dist
|
float
|
The distance between the initial and goal pose |
required |
Returns:
Name | Type | Description |
---|---|---|
t |
float
|
Moving lenght of segments |
p |
float
|
Moving lenght of segments |
q |
float
|
Moving lenght of segments |
mode |
list
|
Motion mode |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def RSL(self, alpha: float, beta: float, dist: float):
"""
Right-Straight-Left generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
dist (float): The distance between the initial and goal pose
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_rsl = -2 + dist ** 2 + 2 * cos_a_b - 2 * dist * (sin_a + sin_b)
if p_rsl < 0:
return None, None, None, ["R", "S", "L"]
else:
p_rsl = math.sqrt(p_rsl)
t_rsl = self.mod2pi(alpha - math.atan2(cos_a + cos_b, dist - sin_a - sin_b) + math.atan2(2.0, p_rsl))
q_rsl = self.mod2pi(beta - math.atan2(cos_a + cos_b, dist - sin_a - sin_b) + math.atan2(2.0, p_rsl))
return t_rsl, p_rsl, q_rsl, ["R", "S", "L"]
RSR(alpha, beta, dist)
¶
Right-Straight-Right generation mode.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alpha
|
float
|
Initial pose of (0, 0, alpha) |
required |
beta
|
float
|
Goal pose of (dist, 0, beta) |
required |
Returns:
Name | Type | Description |
---|---|---|
t |
float
|
Moving lenght of segments |
p |
float
|
Moving lenght of segments |
q |
float
|
Moving lenght of segments |
mode |
list
|
Motion mode |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def RSR(self, alpha: float, beta: float, dist: float):
"""
Right-Straight-Right generation mode.
Parameters:
alpha (float): Initial pose of (0, 0, alpha)
beta (float): Goal pose of (dist, 0, beta)
Returns:
t (float): Moving lenght of segments
p (float): Moving lenght of segments
q (float): Moving lenght of segments
mode (list): Motion mode
"""
sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
p_rsr = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_b - sin_a)
if p_rsr < 0:
return None, None, None, ["R", "S", "R"]
else:
p_rsr = math.sqrt(p_rsr)
t_rsr = self.mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b))
q_rsr = self.mod2pi(-beta + math.atan2(cos_a - cos_b, dist - sin_a + sin_b))
return t_rsr, p_rsr, q_rsr, ["R", "S", "R"]
generation(start_pose, goal_pose)
¶
Generate the Dubins Curve.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
start_pose
|
tuple
|
Initial pose (x, y, yaw) |
required |
goal_pose
|
tuple
|
Target pose (x, y, yaw) |
required |
Returns:
Name | Type | Description |
---|---|---|
best_cost |
float
|
Best planning path length |
best_mode |
list
|
Best motion modes |
x_list |
list
|
Trajectory of x |
y_list |
list
|
Trajectory of y |
yaw_list |
list
|
Trajectory of yaw |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def generation(self, start_pose: tuple, goal_pose: tuple):
"""
Generate the Dubins Curve.
Parameters:
start_pose (tuple): Initial pose (x, y, yaw)
goal_pose (tuple): Target pose (x, y, yaw)
Returns:
best_cost (float): Best planning path length
best_mode (list): Best motion modes
x_list (list): Trajectory of x
y_list (list): Trajectory of y
yaw_list (list): Trajectory of yaw
"""
sx, sy, syaw = start_pose
gx, gy, gyaw = goal_pose
# coordinate transformation
gx, gy = gx - sx, gy - sy
theta = self.mod2pi(math.atan2(gy, gx))
dist = math.hypot(gx, gy) * self.max_curv
alpha = self.mod2pi(syaw - theta)
beta = self.mod2pi(gyaw - theta)
# select the best motion
planners = [self.LSL, self.RSR, self.LSR, self.RSL, self.RLR, self.LRL]
best_t, best_p, best_q, best_mode, best_cost = None, None, None, None, float("inf")
for planner in planners:
t, p, q, mode = planner(alpha, beta, dist)
if t is None:
continue
cost = (abs(t) + abs(p) + abs(q))
if best_cost > cost:
best_t, best_p, best_q, best_mode, best_cost = t, p, q, mode, cost
# interpolation
segments = [best_t, best_p, best_q]
points_num = int(sum(segments) / self.step) + len(segments) + 3
x_list = [0.0 for _ in range(points_num)]
y_list = [0.0 for _ in range(points_num)]
yaw_list = [alpha for _ in range(points_num)]
i = 0
for mode_, seg_length in zip(best_mode, segments):
# path increment
d_length = self.step if seg_length > 0.0 else -self.step
x, y, yaw = x_list[i], y_list[i], yaw_list[i]
# current path length
length = d_length
while abs(length) <= abs(seg_length):
i += 1
x_list[i], y_list[i], yaw_list[i] = self.interpolate(mode_, length, (x, y, yaw))
length += d_length
i += 1
x_list[i], y_list[i], yaw_list[i] = self.interpolate(mode_, seg_length, (x, y, yaw))
# failed
if len(x_list) <= 1:
return None, None, [], [], []
# remove unused data
while len(x_list) >= 1 and x_list[-1] == 0.0:
x_list.pop()
y_list.pop()
yaw_list.pop()
# coordinate transformation
rot = Rot.from_euler('z', theta).as_matrix()[0:2, 0:2]
converted_xy = rot @ np.stack([x_list, y_list])
x_list = converted_xy[0, :] + sx
y_list = converted_xy[1, :] + sy
yaw_list = [self.pi2pi(i_yaw + theta) for i_yaw in yaw_list]
return best_cost, best_mode, x_list, y_list, yaw_list
interpolate(mode, length, init_pose)
¶
Planning path interpolation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
mode
|
str
|
motion, e.g., L, S, R |
required |
length
|
float
|
Single step motion path length |
required |
init_pose
|
tuple
|
Initial pose (x, y, yaw) |
required |
Returns:
Name | Type | Description |
---|---|---|
new_pose |
tuple
|
New pose (new_x, new_y, new_yaw) after moving |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def interpolate(self, mode: str, length: float, init_pose: tuple):
"""
Planning path interpolation.
Parameters:
mode (str): motion, e.g., L, S, R
length (float): Single step motion path length
init_pose (tuple): Initial pose (x, y, yaw)
Returns:
new_pose (tuple): New pose (new_x, new_y, new_yaw) after moving
"""
x, y, yaw = init_pose
if mode == "S":
new_x = x + length / self.max_curv * math.cos(yaw)
new_y = y + length / self.max_curv * math.sin(yaw)
new_yaw = yaw
elif mode == "L":
new_x = x + (math.sin(yaw + length) - math.sin(yaw)) / self.max_curv
new_y = y - (math.cos(yaw + length) - math.cos(yaw)) / self.max_curv
new_yaw = yaw + length
elif mode == "R":
new_x = x - (math.sin(yaw - length) - math.sin(yaw)) / self.max_curv
new_y = y + (math.cos(yaw - length) - math.cos(yaw)) / self.max_curv
new_yaw = yaw - length
else:
raise NotImplementedError
return new_x, new_y, new_yaw
run(points)
¶
Running both generation and animation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
points
|
list[tuple]
|
path points |
required |
Source code in src\python_motion_planning\curve_generation\dubins_curve.py
def run(self, points: list):
"""
Running both generation and animation.
Parameters:
points (list[tuple]): path points
"""
assert len(points) >= 2, "Number of points should be at least 2."
import matplotlib.pyplot as plt
# generation
path_x, path_y, path_yaw = [], [], []
for i in range(len(points) - 1):
_, _, x_list, y_list, yaw_list = self.generation(
(points[i][0], points[i][1], np.deg2rad(points[i][2])),
(points[i + 1][0], points[i + 1][1], np.deg2rad(points[i + 1][2])))
for j in range(len(x_list)):
path_x.append(x_list[j])
path_y.append(y_list[j])
path_yaw.append(yaw_list[j])
# animation
plt.figure("curve generation")
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
for x, y, theta in points:
Plot.plotArrow(x, y, np.deg2rad(theta), 2, 'blueviolet')
plt.axis("equal")
plt.title(str(self))
plt.show()