python-motion-planning 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- curve_generation/__init__.py +9 -0
- curve_generation/bezier_curve.py +131 -0
- curve_generation/bspline_curve.py +271 -0
- curve_generation/cubic_spline.py +128 -0
- curve_generation/curve.py +64 -0
- curve_generation/dubins_curve.py +348 -0
- curve_generation/fem_pos_smooth.py +114 -0
- curve_generation/polynomial_curve.py +226 -0
- curve_generation/reeds_shepp.py +736 -0
- global_planner/__init__.py +3 -0
- global_planner/evolutionary_search/__init__.py +4 -0
- global_planner/evolutionary_search/aco.py +186 -0
- global_planner/evolutionary_search/evolutionary_search.py +87 -0
- global_planner/evolutionary_search/pso.py +356 -0
- global_planner/graph_search/__init__.py +28 -0
- global_planner/graph_search/a_star.py +124 -0
- global_planner/graph_search/d_star.py +291 -0
- global_planner/graph_search/d_star_lite.py +188 -0
- global_planner/graph_search/dijkstra.py +77 -0
- global_planner/graph_search/gbfs.py +78 -0
- global_planner/graph_search/graph_search.py +87 -0
- global_planner/graph_search/jps.py +165 -0
- global_planner/graph_search/lazy_theta_star.py +114 -0
- global_planner/graph_search/lpa_star.py +230 -0
- global_planner/graph_search/s_theta_star.py +133 -0
- global_planner/graph_search/theta_star.py +171 -0
- global_planner/graph_search/voronoi.py +200 -0
- global_planner/sample_search/__init__.py +6 -0
- global_planner/sample_search/informed_rrt.py +152 -0
- global_planner/sample_search/rrt.py +151 -0
- global_planner/sample_search/rrt_connect.py +147 -0
- global_planner/sample_search/rrt_star.py +77 -0
- global_planner/sample_search/sample_search.py +135 -0
- local_planner/__init__.py +19 -0
- local_planner/apf.py +144 -0
- local_planner/ddpg.py +630 -0
- local_planner/dqn.py +687 -0
- local_planner/dwa.py +212 -0
- local_planner/local_planner.py +262 -0
- local_planner/lqr.py +146 -0
- local_planner/mpc.py +214 -0
- local_planner/pid.py +158 -0
- local_planner/rpp.py +147 -0
- python_motion_planning-1.0.dist-info/LICENSE +674 -0
- python_motion_planning-1.0.dist-info/METADATA +873 -0
- python_motion_planning-1.0.dist-info/RECORD +65 -0
- python_motion_planning-1.0.dist-info/WHEEL +5 -0
- python_motion_planning-1.0.dist-info/top_level.txt +4 -0
- utils/__init__.py +19 -0
- utils/agent/__init__.py +0 -0
- utils/agent/agent.py +135 -0
- utils/environment/__init__.py +0 -0
- utils/environment/env.py +134 -0
- utils/environment/node.py +85 -0
- utils/environment/point2d.py +96 -0
- utils/environment/pose2d.py +91 -0
- utils/helper/__init__.py +3 -0
- utils/helper/math_helper.py +65 -0
- utils/planner/__init__.py +0 -0
- utils/planner/control_factory.py +31 -0
- utils/planner/curve_factory.py +29 -0
- utils/planner/planner.py +40 -0
- utils/planner/search_factory.py +51 -0
- utils/plot/__init__.py +0 -0
- utils/plot/plot.py +274 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .polynomial_curve import Polynomial
|
|
2
|
+
from .bezier_curve import Bezier
|
|
3
|
+
from .bspline_curve import BSpline
|
|
4
|
+
from .dubins_curve import Dubins
|
|
5
|
+
from .reeds_shepp import ReedsShepp
|
|
6
|
+
from .cubic_spline import CubicSpline
|
|
7
|
+
from .fem_pos_smooth import FemPosSmoother
|
|
8
|
+
|
|
9
|
+
__all__ = ["Polynomial", "Dubins", "ReedsShepp", "Bezier", "CubicSpline", "BSpline", "FemPosSmoother"]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: bezier_curve.py
|
|
3
|
+
@breif: Bezier curve generation
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.7.25
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from scipy.special import comb
|
|
10
|
+
from python_motion_planning.utils import Plot
|
|
11
|
+
from .curve import Curve
|
|
12
|
+
|
|
13
|
+
class Bezier(Curve):
|
|
14
|
+
"""
|
|
15
|
+
Class for Bezier curve generation.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
step (float): Simulation or interpolation size
|
|
19
|
+
offset (float): The offset of control points
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
>>> from python_motion_planning.curve_generation import Bezier
|
|
23
|
+
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
|
|
24
|
+
>>> generator = Bezier(step, offset)
|
|
25
|
+
>>> generator.run(points)
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self, step: float, offset: float) -> None:
|
|
28
|
+
super().__init__(step)
|
|
29
|
+
self.offset = offset
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
return "Bezier Curve"
|
|
33
|
+
|
|
34
|
+
def generation(self, start_pose: tuple, goal_pose: tuple):
|
|
35
|
+
"""
|
|
36
|
+
Generate the Bezier Curve.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
start_pose (tuple): Initial pose (x, y, yaw)
|
|
40
|
+
goal_pose (tuple): Target pose (x, y, yaw)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
x_list (list): x of the trajectory
|
|
44
|
+
y_list (list): y of the trajectory
|
|
45
|
+
yaw_list (list): yaw of the trajectory
|
|
46
|
+
"""
|
|
47
|
+
sx, sy, _ = start_pose
|
|
48
|
+
gx, gy, _ = goal_pose
|
|
49
|
+
n_points = int(np.hypot(sx - gx, sy - gy) / self.step)
|
|
50
|
+
control_points = self.getControlPoints(start_pose, goal_pose)
|
|
51
|
+
|
|
52
|
+
return [self.bezier(t, control_points) for t in np.linspace(0, 1, n_points)], \
|
|
53
|
+
control_points
|
|
54
|
+
|
|
55
|
+
def bezier(self, t: float, control_points: list) ->np.ndarray:
|
|
56
|
+
"""
|
|
57
|
+
Calculate the Bezier curve point.
|
|
58
|
+
|
|
59
|
+
Parameters:
|
|
60
|
+
t (float): scale factor
|
|
61
|
+
control_points (list[tuple]): control points
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
point (np.array): point in Bezier curve with t
|
|
65
|
+
"""
|
|
66
|
+
n = len(control_points) - 1
|
|
67
|
+
control_points = np.array(control_points)
|
|
68
|
+
return np.sum([comb(n, i) * t ** i * (1 - t) ** (n - i) *
|
|
69
|
+
control_points[i] for i in range(n + 1)], axis=0)
|
|
70
|
+
|
|
71
|
+
def getControlPoints(self, start_pose: tuple, goal_pose: tuple):
|
|
72
|
+
"""
|
|
73
|
+
Calculate control points heuristically.
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
start_pose (tuple): Initial pose (x, y, yaw)
|
|
77
|
+
goal_pose (tuple): Target pose (x, y, yaw)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
control_points (list[tuple]): Control points
|
|
81
|
+
"""
|
|
82
|
+
sx, sy, syaw = start_pose
|
|
83
|
+
gx, gy, gyaw = goal_pose
|
|
84
|
+
|
|
85
|
+
dist = np.hypot(sx - gx, sy - gy) / self.offset
|
|
86
|
+
return [(sx, sy),
|
|
87
|
+
(sx + dist * np.cos(syaw), sy + dist * np.sin(syaw)),
|
|
88
|
+
(gx - dist * np.cos(gyaw), gy - dist * np.sin(gyaw)),
|
|
89
|
+
(gx, gy)]
|
|
90
|
+
|
|
91
|
+
def run(self, points: list):
|
|
92
|
+
"""
|
|
93
|
+
Running both generation and animation.
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
points (list[tuple]): path points
|
|
97
|
+
"""
|
|
98
|
+
assert len(points) >= 2, "Number of points should be at least 2."
|
|
99
|
+
import matplotlib.pyplot as plt
|
|
100
|
+
|
|
101
|
+
# generation
|
|
102
|
+
path_x, path_y = [], []
|
|
103
|
+
path_control_x, path_control_y = [], []
|
|
104
|
+
for i in range(len(points) - 1):
|
|
105
|
+
path, control_points = self.generation(
|
|
106
|
+
(points[i][0], points[i][1], np.deg2rad(points[i][2])),
|
|
107
|
+
(points[i + 1][0], points[i + 1][1], np.deg2rad(points[i + 1][2])))
|
|
108
|
+
|
|
109
|
+
for pt in path:
|
|
110
|
+
path_x.append(pt[0])
|
|
111
|
+
path_y.append(pt[1])
|
|
112
|
+
|
|
113
|
+
path_control_x.append(points[i][0])
|
|
114
|
+
path_control_y.append(points[i][1])
|
|
115
|
+
|
|
116
|
+
for pt in control_points:
|
|
117
|
+
path_control_x.append(pt[0])
|
|
118
|
+
path_control_y.append(pt[1])
|
|
119
|
+
|
|
120
|
+
# animation
|
|
121
|
+
plt.figure("curve generation")
|
|
122
|
+
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
|
|
123
|
+
plt.plot(path_control_x, path_control_y, '--o', c='#dddddd', label="Control Points")
|
|
124
|
+
for x, y, theta in points:
|
|
125
|
+
Plot.plotArrow(x, y, np.deg2rad(theta), 2, 'blueviolet')
|
|
126
|
+
|
|
127
|
+
plt.axis("equal")
|
|
128
|
+
plt.legend()
|
|
129
|
+
plt.title(str(self))
|
|
130
|
+
plt.show()
|
|
131
|
+
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: bspline_curve.py
|
|
3
|
+
@breif: B-Spline curve generation
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.7.29
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from .curve import Curve
|
|
11
|
+
|
|
12
|
+
class BSpline(Curve):
|
|
13
|
+
"""
|
|
14
|
+
Class for B-Spline curve generation.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
step (float): Simulation or interpolation size
|
|
18
|
+
k (int): Degree of curve
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> from python_motion_planning.curve_generation import BSpline
|
|
22
|
+
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
|
|
23
|
+
>>> generator = BSpline(step, k)
|
|
24
|
+
>>> generator.run(points)
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, step: float, k: int, param_mode: str="centripetal",
|
|
27
|
+
spline_mode: str="interpolation") -> None:
|
|
28
|
+
super().__init__(step)
|
|
29
|
+
self.k = k
|
|
30
|
+
|
|
31
|
+
assert param_mode == "centripetal" or param_mode == "chord_length" \
|
|
32
|
+
or param_mode == "uniform_spaced", "Parameter selection mode error!"
|
|
33
|
+
self.param_mode = param_mode
|
|
34
|
+
|
|
35
|
+
assert spline_mode == "interpolation" or spline_mode == "approximation", \
|
|
36
|
+
"Spline mode selection error!"
|
|
37
|
+
self.spline_mode = spline_mode
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
return "B-Spline Curve"
|
|
41
|
+
|
|
42
|
+
def baseFunction(self, i: int, k: int, t: float, knot: list):
|
|
43
|
+
"""
|
|
44
|
+
Calculate base function using Cox-deBoor function.
|
|
45
|
+
|
|
46
|
+
Parameters:
|
|
47
|
+
i (int): The index of base function
|
|
48
|
+
k (int): The degree of curve
|
|
49
|
+
t (float): parameter
|
|
50
|
+
knot (list[float]): knot vector
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Nik_t (float): The value of base function Nik(t)
|
|
54
|
+
"""
|
|
55
|
+
Nik_t = 0
|
|
56
|
+
if k == 0:
|
|
57
|
+
Nik_t = 1.0 if t >= knot[i] and t < knot[i + 1] else 0.0
|
|
58
|
+
else:
|
|
59
|
+
length1 = knot[i + k] - knot[i]
|
|
60
|
+
length2 = knot[i + k + 1] - knot[i + 1]
|
|
61
|
+
if not length1 and not length2:
|
|
62
|
+
Nik_t = 0
|
|
63
|
+
elif not length1:
|
|
64
|
+
Nik_t = (knot[i + k + 1] - t) / length2 * self.baseFunction(i + 1, k - 1, t, knot)
|
|
65
|
+
elif not length2:
|
|
66
|
+
Nik_t = (t - knot[i]) / length1 * self.baseFunction(i, k - 1, t, knot)
|
|
67
|
+
else:
|
|
68
|
+
Nik_t = (t - knot[i]) / length1 * self.baseFunction(i, k - 1, t, knot) + \
|
|
69
|
+
(knot[i + k + 1] - t) / length2 * self.baseFunction(i + 1, k - 1, t, knot)
|
|
70
|
+
return Nik_t
|
|
71
|
+
|
|
72
|
+
def paramSelection(self, points: list):
|
|
73
|
+
"""
|
|
74
|
+
Calculate parameters using the `uniform spaced` or `chrod length`
|
|
75
|
+
or `centripetal` method.
|
|
76
|
+
|
|
77
|
+
Parameters:
|
|
78
|
+
points (list[tuple]): path points
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Parameters (list[float]): The parameters of given points
|
|
82
|
+
"""
|
|
83
|
+
n = len(points)
|
|
84
|
+
x_list = [pt[0] for pt in points]
|
|
85
|
+
y_list = [pt[1] for pt in points]
|
|
86
|
+
dx, dy = np.diff(x_list), np.diff(y_list)
|
|
87
|
+
|
|
88
|
+
if self.param_mode == "uniform_spaced":
|
|
89
|
+
return np.linspace(0, 1, n).tolist()
|
|
90
|
+
|
|
91
|
+
elif self.param_mode == "chord_length":
|
|
92
|
+
parameters = np.zeros(n)
|
|
93
|
+
s = np.cumsum([math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)])
|
|
94
|
+
for i in range(1, n):
|
|
95
|
+
parameters[i] = s[i - 1] / s[-1]
|
|
96
|
+
return parameters.tolist()
|
|
97
|
+
|
|
98
|
+
elif self.param_mode == "centripetal":
|
|
99
|
+
alpha = 0.5
|
|
100
|
+
s = np.cumsum([math.pow(math.hypot(idx, idy), alpha) for (idx, idy) in zip(dx, dy)])
|
|
101
|
+
parameters = np.zeros(n)
|
|
102
|
+
for i in range(1, n):
|
|
103
|
+
parameters[i] = s[i - 1] / s[-1]
|
|
104
|
+
return parameters.tolist()
|
|
105
|
+
|
|
106
|
+
def knotGeneration(self, param: list, n: int):
|
|
107
|
+
"""
|
|
108
|
+
Generate knot vector.
|
|
109
|
+
|
|
110
|
+
Parameters:
|
|
111
|
+
param (list[float]): The parameters of given points
|
|
112
|
+
n (int): The number of data points
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
knot (list[float]): The knot vector
|
|
116
|
+
"""
|
|
117
|
+
m = n + self.k + 1
|
|
118
|
+
knot = np.zeros(m)
|
|
119
|
+
for i in range(self.k + 1):
|
|
120
|
+
knot[i] = 0
|
|
121
|
+
for i in range(n, m):
|
|
122
|
+
knot[i] = 1
|
|
123
|
+
for i in range(self.k + 1, n):
|
|
124
|
+
for j in range(i - self.k, i):
|
|
125
|
+
knot[i] = knot[i] + param[j]
|
|
126
|
+
knot[i] = knot[i] / self.k
|
|
127
|
+
return knot.tolist()
|
|
128
|
+
|
|
129
|
+
def interpolation(self, points: list, param: list, knot: list):
|
|
130
|
+
"""
|
|
131
|
+
Given a set of N data points, D0, D1, ..., Dn and a degree k,
|
|
132
|
+
find a B-spline curve of degree k defined by N control points
|
|
133
|
+
that passes all data points in the given order.
|
|
134
|
+
|
|
135
|
+
Parameters:
|
|
136
|
+
points (list[tuple]): path points
|
|
137
|
+
param (list[float]): The parameters of given points
|
|
138
|
+
knot (list[float]): The knot vector
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
control_points (np.ndarray): The control points
|
|
142
|
+
"""
|
|
143
|
+
n = len(points)
|
|
144
|
+
N = np.zeros((n, n))
|
|
145
|
+
|
|
146
|
+
for i in range(n):
|
|
147
|
+
for j in range(n):
|
|
148
|
+
N[i][j] = self.baseFunction(j, self.k, param[i], knot)
|
|
149
|
+
N[n-1][n-1] = 1
|
|
150
|
+
N_inv = np.linalg.inv(N)
|
|
151
|
+
|
|
152
|
+
D = np.array(points)
|
|
153
|
+
|
|
154
|
+
return N_inv @ D
|
|
155
|
+
|
|
156
|
+
def approximation(self, points: list, param: list, knot: list):
|
|
157
|
+
"""
|
|
158
|
+
Given a set of N data points, D0, D1, ..., Dn, a degree k,
|
|
159
|
+
and a number H, where N > H > k >= 1, find a B-spline curve
|
|
160
|
+
of degree k defined by H control points that satisfies the
|
|
161
|
+
following conditions:
|
|
162
|
+
1. this curve contains the first and last data points;
|
|
163
|
+
2. this curve approximates the data polygon in the sense
|
|
164
|
+
of least square
|
|
165
|
+
|
|
166
|
+
Parameters:
|
|
167
|
+
points (list[tuple]): path points
|
|
168
|
+
param (list[float]): The parameters of given points
|
|
169
|
+
knot (list[float]): The knot vector
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
control_points (np.ndarray): The control points
|
|
173
|
+
"""
|
|
174
|
+
n = len(points)
|
|
175
|
+
D = np.array(points)
|
|
176
|
+
|
|
177
|
+
# heuristically setting the number of control points
|
|
178
|
+
h = n - 1
|
|
179
|
+
|
|
180
|
+
N = np.zeros((n, h))
|
|
181
|
+
for i in range(n):
|
|
182
|
+
for j in range(h):
|
|
183
|
+
N[i][j] = self.baseFunction(j, self.k, param[i], knot)
|
|
184
|
+
N_ = N[1 : n - 1, 1 : h - 1]
|
|
185
|
+
|
|
186
|
+
qk = np.zeros((n - 2, 2))
|
|
187
|
+
for i in range(1, n - 1):
|
|
188
|
+
qk[i - 1] = D[i, :] - N[i][0] * D[0, :] - N[i][h - 1] * D[-1, :]
|
|
189
|
+
Q = N_.T @ qk
|
|
190
|
+
|
|
191
|
+
P = np.linalg.inv(N_.T @ N_) @ Q
|
|
192
|
+
P = np.insert(P, 0, D[0, :], axis=0)
|
|
193
|
+
P = np.insert(P, len(P), D[-1, :], axis=0)
|
|
194
|
+
|
|
195
|
+
return P
|
|
196
|
+
|
|
197
|
+
def generation(self, t, k, knot, control_pts):
|
|
198
|
+
"""
|
|
199
|
+
Generate the B-spline curve.
|
|
200
|
+
|
|
201
|
+
Parameters:
|
|
202
|
+
t (np.ndarray): The parameter values
|
|
203
|
+
k (int): The degree of the B-spline curve
|
|
204
|
+
knot (list[float]): The knot vector
|
|
205
|
+
control_pts (np.ndarray): The control points
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
curve (np.ndarray): The B-spline curve
|
|
209
|
+
"""
|
|
210
|
+
N = np.zeros((len(t), len(control_pts)))
|
|
211
|
+
|
|
212
|
+
for i in range(len(t)):
|
|
213
|
+
for j in range(len(control_pts)):
|
|
214
|
+
N[i][j] = self.baseFunction(j, k, t[i], knot)
|
|
215
|
+
N[len(t) - 1][len(control_pts) - 1] = 1
|
|
216
|
+
|
|
217
|
+
return N @ control_pts
|
|
218
|
+
|
|
219
|
+
def run(self, points: list, display: bool = True):
|
|
220
|
+
"""
|
|
221
|
+
Running both generation and animation.
|
|
222
|
+
|
|
223
|
+
Parameters:
|
|
224
|
+
points (list[tuple]): path points
|
|
225
|
+
"""
|
|
226
|
+
assert len(points) >= 2, "Number of points should be at least 2."
|
|
227
|
+
import matplotlib.pyplot as plt
|
|
228
|
+
|
|
229
|
+
if len(points[0]) > 2:
|
|
230
|
+
points = [(points[i][0], points[i][1]) for i in range(len(points))]
|
|
231
|
+
|
|
232
|
+
t = np.linspace(0, 1, int(1 / self.step))
|
|
233
|
+
params = self.paramSelection(points)
|
|
234
|
+
knot = self.knotGeneration(params, len(points))
|
|
235
|
+
|
|
236
|
+
if self.spline_mode == "interpolation":
|
|
237
|
+
control_pts = self.interpolation(points, params, knot)
|
|
238
|
+
elif self.spline_mode == "approximation":
|
|
239
|
+
control_pts = self.approximation(points, params, knot)
|
|
240
|
+
h = len(control_pts)
|
|
241
|
+
new_points = [(control_pts[i][0], control_pts[i][1])
|
|
242
|
+
for i in range(h)]
|
|
243
|
+
params = self.paramSelection(new_points)
|
|
244
|
+
knot = self.knotGeneration(params, h)
|
|
245
|
+
else:
|
|
246
|
+
raise NotImplementedError
|
|
247
|
+
|
|
248
|
+
control_x = control_pts[:, 0].tolist()
|
|
249
|
+
control_y = control_pts[:, 1].tolist()
|
|
250
|
+
|
|
251
|
+
path = self.generation(t, self.k, knot, control_pts)
|
|
252
|
+
path_x = path[:, 0].tolist()
|
|
253
|
+
path_y = path[:, 1].tolist()
|
|
254
|
+
|
|
255
|
+
if display:
|
|
256
|
+
# animation
|
|
257
|
+
plt.figure("curve generation")
|
|
258
|
+
|
|
259
|
+
# static
|
|
260
|
+
plt.figure("curve generation")
|
|
261
|
+
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
|
|
262
|
+
plt.plot(control_x, control_y, '--o', c='#dddddd', label="Control Points")
|
|
263
|
+
for x, y in points:
|
|
264
|
+
plt.plot(x, y, "xr", linewidth=2)
|
|
265
|
+
plt.axis("equal")
|
|
266
|
+
plt.legend()
|
|
267
|
+
plt.title(str(self))
|
|
268
|
+
|
|
269
|
+
plt.show()
|
|
270
|
+
|
|
271
|
+
return [(ix, iy) for (ix, iy) in zip(path_x, path_y)]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: cubic_spline.py
|
|
3
|
+
@breif: Cubic spline generation
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.7.28
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
import bisect
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .curve import Curve
|
|
12
|
+
|
|
13
|
+
class CubicSpline(Curve):
|
|
14
|
+
"""
|
|
15
|
+
Class for cubic spline generation.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
step (float): Simulation or interpolation size
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> from python_motion_planning.curve_generation import CubicSpline
|
|
22
|
+
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
|
|
23
|
+
>>> generator = CubicSpline(step)
|
|
24
|
+
>>> generator.run(points)
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, step: float) -> None:
|
|
27
|
+
super().__init__(step)
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
return "Cubic Spline"
|
|
31
|
+
|
|
32
|
+
def spline(self, x_list: list, y_list: list, t: list):
|
|
33
|
+
"""
|
|
34
|
+
Running both generation and animation.
|
|
35
|
+
|
|
36
|
+
Parameters:
|
|
37
|
+
x_list (list[tuple]): path points x-direction
|
|
38
|
+
y_list (list[tuple]): path points y-direction
|
|
39
|
+
t (list): parameter
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
p (list): The (x, y) of curve with given t
|
|
43
|
+
dp (list): The derivative (dx, dy) of curve with given t
|
|
44
|
+
"""
|
|
45
|
+
# cubic polynomial functions
|
|
46
|
+
a, b, c, d = y_list, [], [], []
|
|
47
|
+
h = np.diff(x_list)
|
|
48
|
+
num = len(x_list)
|
|
49
|
+
|
|
50
|
+
# calculate coefficient matrix
|
|
51
|
+
A = np.zeros((num, num))
|
|
52
|
+
for i in range(1, num - 1):
|
|
53
|
+
A[i, i - 1] = h[i - 1]
|
|
54
|
+
A[i, i] = 2.0 * (h[i - 1] + h[i])
|
|
55
|
+
A[i, i + 1] = h[i]
|
|
56
|
+
A[0, 0] = 1.0
|
|
57
|
+
A[num - 1, num - 1] = 1.0
|
|
58
|
+
|
|
59
|
+
B = np.zeros(num)
|
|
60
|
+
for i in range(1, num - 1):
|
|
61
|
+
B[i] = 3.0 * (a[i + 1] - a[i]) / h[i] - \
|
|
62
|
+
3.0 * (a[i] - a[i - 1]) / h[i - 1]
|
|
63
|
+
|
|
64
|
+
c = np.linalg.solve(A, B)
|
|
65
|
+
for i in range(num - 1):
|
|
66
|
+
d.append((c[i + 1] - c[i]) / (3.0 * h[i]))
|
|
67
|
+
b.append((a[i + 1] - a[i]) / h[i] - h[i] * (c[i + 1] + 2.0 * c[i]) / 3.0)
|
|
68
|
+
|
|
69
|
+
# calculate spline value and its derivative
|
|
70
|
+
p, dp = [], []
|
|
71
|
+
for it in t:
|
|
72
|
+
if it < x_list[0] or it > x_list[-1]:
|
|
73
|
+
continue
|
|
74
|
+
i = bisect.bisect(x_list, it) - 1
|
|
75
|
+
dx = it - x_list[i]
|
|
76
|
+
p.append(a[i] + b[i] * dx + c[i] * dx**2 + d[i] * dx**3)
|
|
77
|
+
dp.append(b[i] + 2.0 * c[i] * dx + 3.0 * d[i] * dx**2)
|
|
78
|
+
|
|
79
|
+
return p, dp
|
|
80
|
+
|
|
81
|
+
def generation(self, start_pose: tuple, goal_pose: tuple):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def run(self, points: list):
|
|
85
|
+
"""
|
|
86
|
+
Running both generation and animation.
|
|
87
|
+
|
|
88
|
+
Parameters:
|
|
89
|
+
points (list[tuple]): path points
|
|
90
|
+
"""
|
|
91
|
+
assert len(points) >= 2, "Number of points should be at least 2."
|
|
92
|
+
import matplotlib.pyplot as plt
|
|
93
|
+
|
|
94
|
+
if len(points[0]) == 2:
|
|
95
|
+
x_list = [ix for (ix, _) in points]
|
|
96
|
+
y_list = [iy for (_, iy) in points]
|
|
97
|
+
elif len(points[0]) == 3:
|
|
98
|
+
x_list = [ix for (ix, _, _) in points]
|
|
99
|
+
y_list = [iy for (_, iy, _) in points]
|
|
100
|
+
else:
|
|
101
|
+
raise NotImplementedError
|
|
102
|
+
|
|
103
|
+
dx, dy = np.diff(x_list), np.diff(y_list)
|
|
104
|
+
ds = [math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)]
|
|
105
|
+
s = [0]
|
|
106
|
+
s.extend(np.cumsum(ds))
|
|
107
|
+
t = np.arange(0, s[-1], self.step)
|
|
108
|
+
|
|
109
|
+
path_x, d_path_x = self.spline(s, x_list, t)
|
|
110
|
+
path_y, d_path_y = self.spline(s, y_list, t)
|
|
111
|
+
path_yaw = [math.atan2(d_path_y[i], d_path_x[i]) for i in range(len(d_path_x))]
|
|
112
|
+
|
|
113
|
+
# animation
|
|
114
|
+
plt.figure("curve generation")
|
|
115
|
+
|
|
116
|
+
# static
|
|
117
|
+
plt.figure("curve generation")
|
|
118
|
+
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
|
|
119
|
+
for x, y, _ in points:
|
|
120
|
+
plt.plot(x, y, "xr", linewidth=2)
|
|
121
|
+
plt.axis("equal")
|
|
122
|
+
plt.title(str(self))
|
|
123
|
+
|
|
124
|
+
plt.figure("yaw")
|
|
125
|
+
plt.plot(t, [math.degrees(iyaw) for iyaw in path_yaw], "-r")
|
|
126
|
+
plt.title("yaw curve")
|
|
127
|
+
|
|
128
|
+
plt.show()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: curve.py
|
|
3
|
+
@breif: Trajectory generation
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.5.31
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
|
|
10
|
+
class Curve(ABC):
|
|
11
|
+
def __init__(self, step: float) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Base class for curve generation.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
step (float): Simulation or interpolation size
|
|
17
|
+
"""
|
|
18
|
+
self.step = step
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def run(self, points: list):
|
|
22
|
+
"""
|
|
23
|
+
Running both generation and animation.
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def generation(self, start_pose: tuple, goal_pose: tuple):
|
|
29
|
+
"""
|
|
30
|
+
Generate the curve.
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def trigonometric(self, alpha: float, beta: float):
|
|
35
|
+
"""
|
|
36
|
+
Calculate some useful trigonometric value with angles.
|
|
37
|
+
"""
|
|
38
|
+
return math.sin(alpha), math.sin(beta), math.cos(alpha), math.cos(beta), \
|
|
39
|
+
math.sin(alpha - beta), math.cos(alpha - beta)
|
|
40
|
+
|
|
41
|
+
def pi2pi(self, theta: float) -> float:
|
|
42
|
+
"""
|
|
43
|
+
Truncate the angle to the interval of -π to π.
|
|
44
|
+
"""
|
|
45
|
+
while theta > math.pi:
|
|
46
|
+
theta -= 2.0 * math.pi
|
|
47
|
+
while theta < -math.pi:
|
|
48
|
+
theta += 2.0 * math.pi
|
|
49
|
+
return theta
|
|
50
|
+
|
|
51
|
+
def mod2pi(self, theta: float) -> float:
|
|
52
|
+
"""
|
|
53
|
+
Perform modulus operation on 2π.
|
|
54
|
+
"""
|
|
55
|
+
return theta - 2.0 * math.pi * math.floor(theta / math.pi / 2.0)
|
|
56
|
+
|
|
57
|
+
def length(self, path: list) -> float:
|
|
58
|
+
"""
|
|
59
|
+
Calculate path or trajectory length with `path` format [(ix, iy)] (i from 0 to N)
|
|
60
|
+
"""
|
|
61
|
+
dist = 0
|
|
62
|
+
for i in range(len(path) - 1):
|
|
63
|
+
dist = dist + math.hypot(path[i + 1][0] - path[i][0], path[i + 1][1] - path[i][1])
|
|
64
|
+
return dist
|