Skip to content

Commit 18ac00d

Browse files
committed
fix the dubins planning bug and code clean up
1 parent 4e32321 commit 18ac00d

File tree

4 files changed

+143
-109
lines changed

4 files changed

+143
-109
lines changed

PathPlanning/DubinsPath/dubins_path_planning.py

Lines changed: 105 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def pi_2_pi(angle):
2121
return (angle + math.pi) % (2 * math.pi) - math.pi
2222

2323

24-
def LSL(alpha, beta, d):
24+
def left_straight_left(alpha, beta, d):
2525
sa = math.sin(alpha)
2626
sb = math.sin(beta)
2727
ca = math.cos(alpha)
@@ -38,12 +38,11 @@ def LSL(alpha, beta, d):
3838
t = mod2pi(-alpha + tmp1)
3939
p = math.sqrt(p_squared)
4040
q = mod2pi(beta - tmp1)
41-
# print(np.rad2deg(t), p, np.rad2deg(q))
4241

4342
return t, p, q, mode
4443

4544

46-
def RSR(alpha, beta, d):
45+
def right_straight_right(alpha, beta, d):
4746
sa = math.sin(alpha)
4847
sb = math.sin(beta)
4948
ca = math.cos(alpha)
@@ -63,7 +62,7 @@ def RSR(alpha, beta, d):
6362
return t, p, q, mode
6463

6564

66-
def LSR(alpha, beta, d):
65+
def left_straight_right(alpha, beta, d):
6766
sa = math.sin(alpha)
6867
sb = math.sin(beta)
6968
ca = math.cos(alpha)
@@ -82,7 +81,7 @@ def LSR(alpha, beta, d):
8281
return t, p, q, mode
8382

8483

85-
def RSL(alpha, beta, d):
84+
def right_straight_left(alpha, beta, d):
8685
sa = math.sin(alpha)
8786
sb = math.sin(beta)
8887
ca = math.cos(alpha)
@@ -101,7 +100,7 @@ def RSL(alpha, beta, d):
101100
return t, p, q, mode
102101

103102

104-
def RLR(alpha, beta, d):
103+
def right_left_right(alpha, beta, d):
105104
sa = math.sin(alpha)
106105
sb = math.sin(beta)
107106
ca = math.cos(alpha)
@@ -119,7 +118,7 @@ def RLR(alpha, beta, d):
119118
return t, p, q, mode
120119

121120

122-
def LRL(alpha, beta, d):
121+
def left_right_left(alpha, beta, d):
123122
sa = math.sin(alpha)
124123
sb = math.sin(beta)
125124
ca = math.cos(alpha)
@@ -137,41 +136,70 @@ def LRL(alpha, beta, d):
137136
return t, p, q, mode
138137

139138

140-
def dubins_path_planning_from_origin(ex, ey, eyaw, c, D_ANGLE):
141-
# normalize
142-
dx = ex
143-
dy = ey
139+
def dubins_path_planning_from_origin(end_x, end_y, end_yaw, curvature, step_size):
140+
dx = end_x
141+
dy = end_y
144142
D = math.hypot(dx, dy)
145-
d = D * c
146-
# print(dx, dy, D, d)
143+
d = D * curvature
147144

148145
theta = mod2pi(math.atan2(dy, dx))
149146
alpha = mod2pi(- theta)
150-
beta = mod2pi(eyaw - theta)
151-
# print(theta, alpha, beta, d)
147+
beta = mod2pi(end_yaw - theta)
152148

153-
planners = [LSL, RSR, LSR, RSL, RLR, LRL]
149+
planners = [left_straight_left, right_straight_right, left_straight_right, right_straight_left, right_left_right,
150+
left_right_left]
154151

155-
bcost = float("inf")
156-
bt, bp, bq, bmode = None, None, None, None
152+
best_cost = float("inf")
153+
bt, bp, bq, best_mode = None, None, None, None
157154

158155
for planner in planners:
159156
t, p, q, mode = planner(alpha, beta, d)
160157
if t is None:
161158
continue
162159

163160
cost = (abs(t) + abs(p) + abs(q))
164-
if bcost > cost:
165-
bt, bp, bq, bmode = t, p, q, mode
166-
bcost = cost
167-
168-
# print(bmode)
169-
px, py, pyaw = generate_course([bt, bp, bq], bmode, c, D_ANGLE)
161+
if best_cost > cost:
162+
bt, bp, bq, best_mode = t, p, q, mode
163+
best_cost = cost
164+
lengths = [bt, bp, bq]
165+
166+
px, py, pyaw, directions = generate_local_course(
167+
sum(lengths), lengths, best_mode, curvature, step_size)
168+
169+
return px, py, pyaw, best_mode, best_cost
170+
171+
172+
def interpolate(ind, length, mode, max_curvature, origin_x, origin_y, origin_yaw, path_x, path_y, path_yaw, directions):
173+
if mode == "S":
174+
path_x[ind] = origin_x + length / max_curvature * math.cos(origin_yaw)
175+
path_y[ind] = origin_y + length / max_curvature * math.sin(origin_yaw)
176+
path_yaw[ind] = origin_yaw
177+
else: # curve
178+
ldx = math.sin(length) / max_curvature
179+
ldy = 0.0
180+
if mode == "L": # left turn
181+
ldy = (1.0 - math.cos(length)) / max_curvature
182+
elif mode == "R": # right turn
183+
ldy = (1.0 - math.cos(length)) / -max_curvature
184+
gdx = math.cos(-origin_yaw) * ldx + math.sin(-origin_yaw) * ldy
185+
gdy = -math.sin(-origin_yaw) * ldx + math.cos(-origin_yaw) * ldy
186+
path_x[ind] = origin_x + gdx
187+
path_y[ind] = origin_y + gdy
188+
189+
if mode == "L": # left turn
190+
path_yaw[ind] = origin_yaw + length
191+
elif mode == "R": # right turn
192+
path_yaw[ind] = origin_yaw - length
193+
194+
if length > 0.0:
195+
directions[ind] = 1
196+
else:
197+
directions[ind] = -1
170198

171-
return px, py, pyaw, bmode, bcost
199+
return path_x, path_y, path_yaw, directions
172200

173201

174-
def dubins_path_planning(sx, sy, syaw, ex, ey, eyaw, c, D_ANGLE=np.deg2rad(10.0)):
202+
def dubins_path_planning(sx, sy, syaw, ex, ey, eyaw, c, step_size=0.1):
175203
"""
176204
Dubins path plannner
177205
@@ -200,7 +228,7 @@ def dubins_path_planning(sx, sy, syaw, ex, ey, eyaw, c, D_ANGLE=np.deg2rad(10.0)
200228
leyaw = eyaw - syaw
201229

202230
lpx, lpy, lpyaw, mode, clen = dubins_path_planning_from_origin(
203-
lex, ley, leyaw, c, D_ANGLE)
231+
lex, ley, leyaw, c, step_size)
204232

205233
px = [math.cos(-syaw) * x + math.sin(-syaw)
206234
* y + sx for x, y in zip(lpx, lpy)]
@@ -211,44 +239,60 @@ def dubins_path_planning(sx, sy, syaw, ex, ey, eyaw, c, D_ANGLE=np.deg2rad(10.0)
211239
return px, py, pyaw, mode, clen
212240

213241

214-
def generate_course(length, mode, c, D_ANGLE):
215-
px = [0.0]
216-
py = [0.0]
217-
pyaw = [0.0]
218-
219-
for m, l in zip(mode, length):
220-
pd = 0.0
221-
if m == "S":
222-
d = 1.0 * c
223-
else: # turning couse
224-
d = D_ANGLE
225-
226-
while pd < abs(l - d):
227-
# print(pd, l)
228-
px.append(px[-1] + d / c * math.cos(pyaw[-1]))
229-
py.append(py[-1] + d / c * math.sin(pyaw[-1]))
230-
231-
if m == "L": # left turn
232-
pyaw.append(pyaw[-1] + d)
233-
elif m == "S": # Straight
234-
pyaw.append(pyaw[-1])
235-
elif m == "R": # right turn
236-
pyaw.append(pyaw[-1] - d)
242+
def generate_local_course(total_length, lengths, mode, max_curvature, step_size):
243+
n_point = math.trunc(total_length / step_size) + len(lengths) + 4
244+
245+
path_x = [0.0 for _ in range(n_point)]
246+
path_y = [0.0 for _ in range(n_point)]
247+
path_yaw = [0.0 for _ in range(n_point)]
248+
directions = [0.0 for _ in range(n_point)]
249+
ind = 1
250+
251+
if lengths[0] > 0.0:
252+
directions[0] = 1
253+
else:
254+
directions[0] = -1
255+
256+
ll = 0.0
257+
258+
for (m, l, i) in zip(mode, lengths, range(len(mode))):
259+
if l > 0.0:
260+
d = step_size
261+
else:
262+
d = -step_size
263+
264+
# set origin state
265+
origin_x, origin_y, origin_yaw = path_x[ind], path_y[ind], path_yaw[ind]
266+
267+
ind -= 1
268+
if i >= 1 and (lengths[i - 1] * lengths[i]) > 0:
269+
pd = - d - ll
270+
else:
271+
pd = d - ll
272+
273+
while abs(pd) <= abs(l):
274+
ind += 1
275+
path_x, path_y, path_yaw, directions = interpolate(
276+
ind, pd, m, max_curvature, origin_x, origin_y, origin_yaw, path_x, path_y, path_yaw, directions)
237277
pd += d
238278

239-
d = l - pd
240-
px.append(px[-1] + d / c * math.cos(pyaw[-1]))
241-
py.append(py[-1] + d / c * math.sin(pyaw[-1]))
279+
ll = l - pd - d # calc remain length
280+
281+
ind += 1
282+
path_x, path_y, path_yaw, directions = interpolate(
283+
ind, l, m, max_curvature, origin_x, origin_y, origin_yaw, path_x, path_y, path_yaw, directions)
284+
285+
if len(path_x) <= 1:
286+
return [], [], [], []
242287

243-
if m == "L": # left turn
244-
pyaw.append(pyaw[-1] + d)
245-
elif m == "S": # Straight
246-
pyaw.append(pyaw[-1])
247-
elif m == "R": # right turn
248-
pyaw.append(pyaw[-1] - d)
249-
pd += d
288+
# remove unused data
289+
while len(path_x) >= 1 and path_x[-1] == 0.0:
290+
path_x.pop()
291+
path_y.pop()
292+
path_yaw.pop()
293+
directions.pop()
250294

251-
return px, py, pyaw
295+
return path_x, path_y, path_yaw, directions
252296

253297

254298
def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): # pragma: no cover
@@ -288,53 +332,11 @@ def main():
288332
plot_arrow(start_x, start_y, start_yaw)
289333
plot_arrow(end_x, end_y, end_yaw)
290334

291-
# for (ix, iy, iyaw) in zip(px, py, pyaw):
292-
# plot_arrow(ix, iy, iyaw, fc="b")
293-
294335
plt.legend()
295336
plt.grid(True)
296337
plt.axis("equal")
297338
plt.show()
298339

299340

300-
def test():
301-
NTEST = 5
302-
303-
for i in range(NTEST):
304-
start_x = (np.random.rand() - 0.5) * 10.0 # [m]
305-
start_y = (np.random.rand() - 0.5) * 10.0 # [m]
306-
start_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad]
307-
308-
end_x = (np.random.rand() - 0.5) * 10.0 # [m]
309-
end_y = (np.random.rand() - 0.5) * 10.0 # [m]
310-
end_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad]
311-
312-
curvature = 1.0 / (np.random.rand() * 5.0)
313-
314-
px, py, pyaw, mode, clen = dubins_path_planning(
315-
start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature)
316-
317-
if show_animation:
318-
plt.cla()
319-
# for stopping simulation with the esc key.
320-
plt.gcf().canvas.mpl_connect('key_release_event',
321-
lambda event: [exit(0) if event.key == 'escape' else None])
322-
plt.plot(px, py, label="final course " + str(mode))
323-
324-
# plotting
325-
plot_arrow(start_x, start_y, start_yaw)
326-
plot_arrow(end_x, end_y, end_yaw)
327-
328-
plt.legend()
329-
plt.grid(True)
330-
plt.axis("equal")
331-
plt.xlim(-10, 10)
332-
plt.ylim(-10, 10)
333-
plt.pause(1.0)
334-
335-
print("Test done")
336-
337-
338341
if __name__ == '__main__':
339-
test()
340342
main()

PathPlanning/RRTDubins/rrt_dubins.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ def steer(self, from_node, to_node):
137137
from_node.x, from_node.y, from_node.yaw,
138138
to_node.x, to_node.y, to_node.yaw, self.curvature)
139139

140+
if len(px) <= 1: # cannot find a dubins path
141+
return None
142+
140143
new_node = copy.deepcopy(from_node)
141144
new_node.x = px[-1]
142145
new_node.y = py[-1]

PathPlanning/RRTStarDubins/rrt_star_dubins.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def draw_graph(self, rnd=None):
114114
plt.clf()
115115
# for stopping simulation with the esc key.
116116
plt.gcf().canvas.mpl_connect('key_release_event',
117-
lambda event: [exit(0) if event.key == 'escape' else None])
117+
lambda event: [exit(0) if event.key == 'escape' else None])
118118
if rnd is not None:
119119
plt.plot(rnd.x, rnd.y, "^k")
120120
for node in self.node_list:
@@ -143,6 +143,9 @@ def steer(self, from_node, to_node):
143143
from_node.x, from_node.y, from_node.yaw,
144144
to_node.x, to_node.y, to_node.yaw, self.curvature)
145145

146+
if len(px) <= 1: # cannot find a dubins path
147+
return None
148+
146149
new_node = copy.deepcopy(from_node)
147150
new_node.x = px[-1]
148151
new_node.y = py[-1]

tests/test_dubins_path_planning.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77

88
class Test(TestCase):
99

10+
@staticmethod
11+
def check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, end_y, end_yaw):
12+
assert (abs(px[0] - start_x) <= 0.01)
13+
assert (abs(py[0] - start_y) <= 0.01)
14+
assert (abs(pyaw[0] - start_yaw) <= 0.01)
15+
print("x", px[-1], end_x)
16+
assert (abs(px[-1] - end_x) <= 0.01)
17+
print("y", py[-1], end_y)
18+
assert (abs(py[-1] - end_y) <= 0.01)
19+
print("yaw", pyaw[-1], end_yaw)
20+
assert (abs(pyaw[-1] - end_yaw) <= 0.01)
21+
1022
def test1(self):
1123
start_x = 1.0 # [m]
1224
start_y = 1.0 # [m]
@@ -21,14 +33,28 @@ def test1(self):
2133
px, py, pyaw, mode, clen = dubins_path_planning.dubins_path_planning(
2234
start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature)
2335

24-
assert (abs(px[-1] - end_x) <= 0.5)
25-
assert (abs(py[-1] - end_y) <= 0.5)
26-
assert(abs(pyaw[-1] - end_yaw) <= 0.1)
36+
self.check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, end_y, end_yaw)
2737

2838
def test2(self):
2939
dubins_path_planning.show_animation = False
3040
dubins_path_planning.main()
3141

3242
def test3(self):
33-
dubins_path_planning.show_animation = False
34-
dubins_path_planning.test()
43+
N_TEST = 10
44+
45+
for i in range(N_TEST):
46+
start_x = (np.random.rand() - 0.5) * 10.0 # [m]
47+
start_y = (np.random.rand() - 0.5) * 10.0 # [m]
48+
start_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad]
49+
50+
end_x = (np.random.rand() - 0.5) * 10.0 # [m]
51+
end_y = (np.random.rand() - 0.5) * 10.0 # [m]
52+
end_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad]
53+
54+
curvature = 1.0 / (np.random.rand() * 5.0)
55+
56+
px, py, pyaw, mode, clen = dubins_path_planning.dubins_path_planning(
57+
start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature)
58+
59+
self.check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, end_y, end_yaw)
60+

0 commit comments

Comments
 (0)