Files
PythonRobotics/PathPlanning/ClothoidPath/clothoid_path_planner.py
Atsushi Sakai 38261ec67e clean up clothoidal_paths.py (#638)
* clean up clothoidal_paths.py

* add unit tests for clothoid_paths

* add unit tests for clothoid_paths

* add unit tests for clothoid_paths

* code clean up

* code clean up

* code clean up

* code clean up

* code clean up

* code clean up

* code clean up

* code clean up
2022-04-10 19:30:31 +09:00

193 lines
5.6 KiB
Python

"""
Clothoid Path Planner
Author: Daniel Ingram (daniel-s-ingram)
Atsushi Sakai (AtsushiSakai)
Reference paper: Fast and accurate G1 fitting of clothoid curves
https://www.researchgate.net/publication/237062806
"""
from collections import namedtuple
import matplotlib.pyplot as plt
import numpy as np
import scipy.integrate as integrate
from scipy.optimize import fsolve
from math import atan2, cos, hypot, pi, sin
from matplotlib import animation
Point = namedtuple("Point", ["x", "y"])
show_animation = True
def generate_clothoid_paths(start_point, start_yaw_list,
goal_point, goal_yaw_list,
n_path_points):
"""
Generate clothoid path list. This function generate multiple clothoid paths
from multiple orientations(yaw) at start points to multiple orientations
(yaw) at goal point.
:param start_point: Start point of the path
:param start_yaw_list: Orientation list at start point in radian
:param goal_point: Goal point of the path
:param goal_yaw_list: Orientation list at goal point in radian
:param n_path_points: number of path points
:return: clothoid path list
"""
clothoids = []
for start_yaw in start_yaw_list:
for goal_yaw in goal_yaw_list:
clothoid = generate_clothoid_path(start_point, start_yaw,
goal_point, goal_yaw,
n_path_points)
clothoids.append(clothoid)
return clothoids
def generate_clothoid_path(start_point, start_yaw,
goal_point, goal_yaw, n_path_points):
"""
Generate a clothoid path list.
:param start_point: Start point of the path
:param start_yaw: Orientation at start point in radian
:param goal_point: Goal point of the path
:param goal_yaw: Orientation at goal point in radian
:param n_path_points: number of path points
:return: a clothoid path
"""
dx = goal_point.x - start_point.x
dy = goal_point.y - start_point.y
r = hypot(dx, dy)
phi = atan2(dy, dx)
phi1 = normalize_angle(start_yaw - phi)
phi2 = normalize_angle(goal_yaw - phi)
delta = phi2 - phi1
try:
# Step1: Solve g function
A = solve_g_for_root(phi1, phi2, delta)
# Step2: Calculate path parameters
L = compute_path_length(r, phi1, delta, A)
curvature = compute_curvature(delta, A, L)
curvature_rate = compute_curvature_rate(A, L)
except Exception as e:
print(f"Failed to generate clothoid points: {e}")
return None
# Step3: Construct a path with Fresnel integral
points = []
for s in np.linspace(0, L, n_path_points):
try:
x = start_point.x + s * X(curvature_rate * s ** 2, curvature * s,
start_yaw)
y = start_point.y + s * Y(curvature_rate * s ** 2, curvature * s,
start_yaw)
points.append(Point(x, y))
except Exception as e:
print(f"Skipping failed clothoid point: {e}")
return points
def X(a, b, c):
return integrate.quad(lambda t: cos((a/2)*t**2 + b*t + c), 0, 1)[0]
def Y(a, b, c):
return integrate.quad(lambda t: sin((a/2)*t**2 + b*t + c), 0, 1)[0]
def solve_g_for_root(theta1, theta2, delta):
initial_guess = 3*(theta1 + theta2)
return fsolve(lambda A: Y(2*A, delta - A, theta1), [initial_guess])
def compute_path_length(r, theta1, delta, A):
return r / X(2*A, delta - A, theta1)
def compute_curvature(delta, A, L):
return (delta - A) / L
def compute_curvature_rate(A, L):
return 2 * A / (L**2)
def normalize_angle(angle_rad):
return (angle_rad + pi) % (2 * pi) - pi
def get_axes_limits(clothoids):
x_vals = [p.x for clothoid in clothoids for p in clothoid]
y_vals = [p.y for clothoid in clothoids for p in clothoid]
x_min = min(x_vals)
x_max = max(x_vals)
y_min = min(y_vals)
y_max = max(y_vals)
x_offset = 0.1*(x_max - x_min)
y_offset = 0.1*(y_max - y_min)
x_min = x_min - x_offset
x_max = x_max + x_offset
y_min = y_min - y_offset
y_max = y_max + y_offset
return x_min, x_max, y_min, y_max
def draw_clothoids(start, goal, num_steps, clothoidal_paths,
save_animation=False):
fig = plt.figure(figsize=(10, 10))
x_min, x_max, y_min, y_max = get_axes_limits(clothoidal_paths)
axes = plt.axes(xlim=(x_min, x_max), ylim=(y_min, y_max))
axes.plot(start.x, start.y, 'ro')
axes.plot(goal.x, goal.y, 'ro')
lines = [axes.plot([], [], 'b-')[0] for _ in range(len(clothoidal_paths))]
def animate(i):
for line, clothoid_path in zip(lines, clothoidal_paths):
x = [p.x for p in clothoid_path[:i]]
y = [p.y for p in clothoid_path[:i]]
line.set_data(x, y)
return lines
anim = animation.FuncAnimation(
fig,
animate,
frames=num_steps,
interval=25,
blit=True
)
if save_animation:
anim.save('clothoid.gif', fps=30, writer="imagemagick")
plt.show()
def main():
start_point = Point(0, 0)
start_orientation_list = [0.0]
goal_point = Point(10, 0)
goal_orientation_list = np.linspace(-pi, pi, 75)
num_path_points = 100
clothoid_paths = generate_clothoid_paths(
start_point, start_orientation_list,
goal_point, goal_orientation_list,
num_path_points)
if show_animation:
draw_clothoids(start_point, goal_point,
num_path_points, clothoid_paths,
save_animation=False)
if __name__ == "__main__":
main()