Skip to content

Commit b896fb1

Browse files
authored
Merge pull request AtsushiSakai#310 from Guilyx/bfs
Breadth-First Search
2 parents 93e2116 + 5ba82c6 commit b896fb1

File tree

1 file changed

+257
-0
lines changed

1 file changed

+257
-0
lines changed
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"""
2+
3+
Breadth-First grid planning
4+
5+
author: Erwin Lejeune (@spida_rwin)
6+
7+
See Wikipedia article (https://en.wikipedia.org/wiki/Breadth-first_search)
8+
9+
"""
10+
11+
import math
12+
13+
import matplotlib.pyplot as plt
14+
15+
show_animation = True
16+
17+
18+
class BreadthFirstSearchPlanner:
19+
20+
def __init__(self, ox, oy, reso, rr):
21+
"""
22+
Initialize grid map for bfs planning
23+
24+
ox: x position list of Obstacles [m]
25+
oy: y position list of Obstacles [m]
26+
reso: grid resolution [m]
27+
rr: robot radius[m]
28+
"""
29+
30+
self.reso = reso
31+
self.rr = rr
32+
self.calc_obstacle_map(ox, oy)
33+
self.motion = self.get_motion_model()
34+
35+
class Node:
36+
def __init__(self, x, y, cost, pind, parent):
37+
self.x = x # index of grid
38+
self.y = y # index of grid
39+
self.cost = cost
40+
self.pind = pind
41+
self.parent = parent
42+
43+
def __str__(self):
44+
return str(self.x) + "," + str(self.y) + "," + str(
45+
self.cost) + "," + str(self.pind)
46+
47+
def planning(self, sx, sy, gx, gy):
48+
"""
49+
Breadth First search based planning
50+
51+
input:
52+
sx: start x position [m]
53+
sy: start y position [m]
54+
gx: goal x position [m]
55+
gy: goal y position [m]
56+
57+
output:
58+
rx: x position list of the final path
59+
ry: y position list of the final path
60+
"""
61+
62+
nstart = self.Node(self.calc_xyindex(sx, self.minx),
63+
self.calc_xyindex(sy, self.miny), 0.0, -1, None)
64+
ngoal = self.Node(self.calc_xyindex(gx, self.minx),
65+
self.calc_xyindex(gy, self.miny), 0.0, -1, None)
66+
67+
open_set, closed_set = dict(), dict()
68+
open_set[self.calc_grid_index(nstart)] = nstart
69+
70+
while 1:
71+
if len(open_set) == 0:
72+
print("Open set is empty..")
73+
break
74+
75+
current = open_set.pop(list(open_set.keys())[0])
76+
77+
c_id = self.calc_grid_index(current)
78+
79+
closed_set[c_id] = current
80+
81+
# show graph
82+
if show_animation: # pragma: no cover
83+
plt.plot(self.calc_grid_position(current.x, self.minx),
84+
self.calc_grid_position(current.y, self.miny), "xc")
85+
# for stopping simulation with the esc key.
86+
plt.gcf().canvas.mpl_connect('key_release_event',
87+
lambda event: [exit(
88+
0) if event.key == 'escape' else None])
89+
if len(closed_set.keys()) % 10 == 0:
90+
plt.pause(0.001)
91+
92+
if current.x == ngoal.x and current.y == ngoal.y:
93+
print("Find goal")
94+
ngoal.pind = current.pind
95+
ngoal.cost = current.cost
96+
break
97+
98+
# expand_grid search grid based on motion model
99+
for i, _ in enumerate(self.motion):
100+
node = self.Node(current.x + self.motion[i][0],
101+
current.y + self.motion[i][1],
102+
current.cost + self.motion[i][2], c_id, None)
103+
n_id = self.calc_grid_index(node)
104+
105+
# If the node is not safe, do nothing
106+
if not self.verify_node(node):
107+
continue
108+
109+
if (n_id not in closed_set) and (n_id not in open_set):
110+
node.parent = current
111+
open_set[n_id] = node
112+
113+
rx, ry = self.calc_final_path(ngoal, closed_set)
114+
return rx, ry
115+
116+
def calc_final_path(self, ngoal, closedset):
117+
# generate final course
118+
rx, ry = [self.calc_grid_position(ngoal.x, self.minx)], [
119+
self.calc_grid_position(ngoal.y, self.miny)]
120+
n = closedset[ngoal.pind]
121+
while n is not None:
122+
rx.append(self.calc_grid_position(n.x, self.minx))
123+
ry.append(self.calc_grid_position(n.y, self.miny))
124+
n = n.parent
125+
126+
return rx, ry
127+
128+
def calc_grid_position(self, index, minp):
129+
"""
130+
calc grid position
131+
132+
:param index:
133+
:param minp:
134+
:return:
135+
"""
136+
pos = index * self.reso + minp
137+
return pos
138+
139+
def calc_xyindex(self, position, min_pos):
140+
return round((position - min_pos) / self.reso)
141+
142+
def calc_grid_index(self, node):
143+
return (node.y - self.miny) * self.xwidth + (node.x - self.minx)
144+
145+
def verify_node(self, node):
146+
px = self.calc_grid_position(node.x, self.minx)
147+
py = self.calc_grid_position(node.y, self.miny)
148+
149+
if px < self.minx:
150+
return False
151+
elif py < self.miny:
152+
return False
153+
elif px >= self.maxx:
154+
return False
155+
elif py >= self.maxy:
156+
return False
157+
158+
# collision check
159+
if self.obmap[node.x][node.y]:
160+
return False
161+
162+
return True
163+
164+
def calc_obstacle_map(self, ox, oy):
165+
166+
self.minx = round(min(ox))
167+
self.miny = round(min(oy))
168+
self.maxx = round(max(ox))
169+
self.maxy = round(max(oy))
170+
print("minx:", self.minx)
171+
print("miny:", self.miny)
172+
print("maxx:", self.maxx)
173+
print("maxy:", self.maxy)
174+
175+
self.xwidth = round((self.maxx - self.minx) / self.reso)
176+
self.ywidth = round((self.maxy - self.miny) / self.reso)
177+
print("xwidth:", self.xwidth)
178+
print("ywidth:", self.ywidth)
179+
180+
# obstacle map generation
181+
self.obmap = [[False for _ in range(self.ywidth)]
182+
for _ in range(self.xwidth)]
183+
for ix in range(self.xwidth):
184+
x = self.calc_grid_position(ix, self.minx)
185+
for iy in range(self.ywidth):
186+
y = self.calc_grid_position(iy, self.miny)
187+
for iox, ioy in zip(ox, oy):
188+
d = math.hypot(iox - x, ioy - y)
189+
if d <= self.rr:
190+
self.obmap[ix][iy] = True
191+
break
192+
193+
@staticmethod
194+
def get_motion_model():
195+
# dx, dy, cost
196+
motion = [[1, 0, 1],
197+
[0, 1, 1],
198+
[-1, 0, 1],
199+
[0, -1, 1],
200+
[-1, -1, math.sqrt(2)],
201+
[-1, 1, math.sqrt(2)],
202+
[1, -1, math.sqrt(2)],
203+
[1, 1, math.sqrt(2)]]
204+
205+
return motion
206+
207+
208+
def main():
209+
print(__file__ + " start!!")
210+
211+
# start and goal position
212+
sx = 10.0 # [m]
213+
sy = 10.0 # [m]
214+
gx = 50.0 # [m]
215+
gy = 50.0 # [m]
216+
grid_size = 2.0 # [m]
217+
robot_radius = 1.0 # [m]
218+
219+
# set obstable positions
220+
ox, oy = [], []
221+
for i in range(-10, 60):
222+
ox.append(i)
223+
oy.append(-10.0)
224+
for i in range(-10, 60):
225+
ox.append(60.0)
226+
oy.append(i)
227+
for i in range(-10, 61):
228+
ox.append(i)
229+
oy.append(60.0)
230+
for i in range(-10, 61):
231+
ox.append(-10.0)
232+
oy.append(i)
233+
for i in range(-10, 40):
234+
ox.append(20.0)
235+
oy.append(i)
236+
for i in range(0, 40):
237+
ox.append(40.0)
238+
oy.append(60.0 - i)
239+
240+
if show_animation: # pragma: no cover
241+
plt.plot(ox, oy, ".k")
242+
plt.plot(sx, sy, "og")
243+
plt.plot(gx, gy, "xb")
244+
plt.grid(True)
245+
plt.axis("equal")
246+
247+
bfs = BreadthFirstSearchPlanner(ox, oy, grid_size, robot_radius)
248+
rx, ry = bfs.planning(sx, sy, gx, gy)
249+
250+
if show_animation: # pragma: no cover
251+
plt.plot(rx, ry, "-r")
252+
plt.pause(0.01)
253+
plt.show()
254+
255+
256+
if __name__ == '__main__':
257+
main()

0 commit comments

Comments
 (0)