Skip to content

Commit c4086b0

Browse files
committed
RRT for route planning
1 parent c970035 commit c4086b0

1 file changed

Lines changed: 240 additions & 0 deletions

File tree

  • GEMstack/onboard/planning

GEMstack/onboard/planning/RRT.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import matplotlib.pyplot as plt
2+
from matplotlib.backend_bases import MouseButton
3+
import numpy as np
4+
import random
5+
import math
6+
7+
class Obstacle:
8+
def __init__(self,x=0,y=0,r=0.2):
9+
self.x = x
10+
self.y = y
11+
self.radius = r
12+
13+
class Point:
14+
def __init__(self, x=0, y=0, heading = None):
15+
self.x = x
16+
self.y = y
17+
self.heading = heading # in radian
18+
self.parent = None
19+
self.cost = float('inf') # Cost to reach this node
20+
21+
OFFSET = 0.8 # meter
22+
23+
OBSTACLE_LIST = []
24+
25+
# return euclidean distance between two point/obstacle
26+
def distance(a,b):
27+
return math.sqrt(math.pow(a.x-b.x,2) + math.pow(a.y-b.y,2))
28+
29+
# return angel within -pi to pi
30+
def angle_norm(angle):
31+
return (angle + math.pi) % (2 * math.pi) - math.pi
32+
33+
# return absolute value of angle difference within 0 to pi
34+
def angle_diff(a,b):
35+
a = angle_norm(a)
36+
b = angle_norm(b)
37+
diff = a - b
38+
return abs(angle_norm(diff))
39+
40+
# return the angle in oppsite direction
41+
def angle_inverse(angle):
42+
angle = angle_norm(angle)
43+
if angle < 0:
44+
return angle + math.pi
45+
return angle-math.pi
46+
47+
def is_valid(point):
48+
for obstacle in OBSTACLE_LIST:
49+
if distance(point, obstacle) < obstacle.radius + OFFSET:
50+
return False
51+
return True
52+
53+
# return the nearest point in the tree
54+
def Nearest(tree,sample_p):
55+
min = 10000000
56+
nearest_p = None
57+
for point in tree:
58+
d = distance(point,sample_p)
59+
if d < min:
60+
min = d
61+
nearest_p = point
62+
return nearest_p
63+
64+
# calculate the point heading based on parent point
65+
def heading(parent,p2):
66+
delta = np.array([p2.x,p2.y]) - np.array([parent.x,parent.y])
67+
return math.atan2(delta[1],delta[0])
68+
69+
# create a point that is one step size away from nearest point toward the sample point
70+
def LocalPlanner(nearest_p,sample_p, step_size=0.5):
71+
dist = distance(nearest_p,sample_p)
72+
if dist < step_size:
73+
return sample_p
74+
direction = np.array([sample_p.x,sample_p.y]) - np.array([nearest_p.x,nearest_p.y])
75+
delta = (direction / dist) * step_size
76+
new_p = Point()
77+
new_p.x = nearest_p.x + delta[0]
78+
new_p.y = nearest_p.y + delta[1]
79+
new_p.parent = nearest_p
80+
new_p.cost = dist
81+
return new_p
82+
83+
class BiRRT:
84+
def __init__(self, start : list, goal : list, obstacles : list, map : list):
85+
86+
self.path = []
87+
self.tree_from_start = []
88+
self.tree_from_end = []
89+
90+
self.start_point = Point(x = start[0],y = start[1],heading = start[2])
91+
self.end_point = Point(x = goal[0],y = goal[1],heading = goal[2])
92+
93+
OBSTACLE_LIST = []
94+
for i in range(len(obstacles)):
95+
OBSTACLE_LIST.append(Obstacle(obstacles[i][0], obstacles[i][1], r = 0.2))
96+
97+
self.MAX_Iteration = 20000
98+
self.step_size = 0.5 # meter
99+
self.search_r = 1.3 # meter
100+
self.heading_limit = math.pi/6 # limit the heading change in route
101+
self.goal_sample_rate = 0.1 # rate to check area near end-goal
102+
103+
# Map boundary
104+
self.MAP_X_LOW = map[0] # meter
105+
self.MAP_X_HIGH = map[1] # meter
106+
self.MAP_Y_LOW = map[2] # meter
107+
self.MAP_Y_HIGH = map[3] # meter
108+
109+
def search(self):
110+
# initialize two tree
111+
self.tree_from_start.append(self.start_point)
112+
self.tree_from_end.append(self.end_point)
113+
114+
# self.start_time = time.time()
115+
# perform search within max number of iterration
116+
for iterration in range(self.MAX_Iteration):
117+
# uniformly sample a point within in the map
118+
sample_p = Point(random.uniform(self.MAP_X_LOW,self.MAP_X_HIGH),random.uniform(self.MAP_Y_LOW,self.MAP_Y_HIGH))
119+
Direction = None
120+
# update the tree form start or tree from end with 1/2 probability
121+
rand_num = random.uniform(0.0, 1.0)
122+
if rand_num > 0.5:
123+
tree_a = self.tree_from_start
124+
tree_b = self.tree_from_end
125+
Direction = "forward"
126+
else:
127+
tree_a = self.tree_from_end
128+
tree_b = self.tree_from_start
129+
Direction = "backward"
130+
131+
print(Direction + " AT {} Interation".format(iterration))
132+
# find nearest point in the tree
133+
nearest_point_a = Nearest(tree_a, sample_p)
134+
# use local planner to move one step size
135+
new_p = LocalPlanner(nearest_point_a, sample_p, self.step_size)
136+
# check collision
137+
if not is_valid(new_p):
138+
continue
139+
# check if there exist previous point with less cost to new point
140+
neighbor_points = self.Neighbors(new_p,tree_a)
141+
min_cost = nearest_point_a.cost + distance(new_p,nearest_point_a)
142+
parent_p = nearest_point_a
143+
for point in neighbor_points:
144+
curr_cost = point.cost + distance(point, new_p)
145+
if curr_cost < min_cost:
146+
min_cost = curr_cost
147+
parent_p = point
148+
# update point's paraent
149+
new_p.cost = min_cost
150+
new_p.parent = parent_p
151+
new_p.heading = heading(parent_p,new_p)
152+
153+
# check heading limit and collision
154+
if angle_diff(new_p.heading,new_p.parent.heading) > (self.heading_limit):
155+
continue
156+
if not is_valid(new_p):
157+
continue
158+
159+
# point is valid, add to tree
160+
tree_a.append(new_p)
161+
162+
# rewrite tree to smooth the route
163+
for point in neighbor_points:
164+
if point == parent_p:
165+
continue
166+
if new_p.cost + distance(new_p,point) < point.cost:
167+
# check heading limit
168+
if angle_diff(new_p.heading,point.heading) > (self.heading_limit):
169+
continue
170+
if angle_diff(new_p.heading,heading(new_p,point)) > (self.heading_limit):
171+
continue
172+
if angle_diff(point.heading,heading(new_p,point)) > (self.heading_limit):
173+
continue
174+
point.parent = new_p
175+
point.cost = new_p.cost + distance(new_p, point)
176+
point.heading = heading(new_p,point)
177+
178+
# find nearest point in another tree
179+
nearest_point_b = Nearest(tree_b, new_p)
180+
181+
# check if two tree can be connected
182+
if distance(new_p,nearest_point_b) > self.step_size:
183+
continue
184+
# check heading limit
185+
if angle_diff(new_p.heading,angle_inverse(nearest_point_b.heading)) > (self.heading_limit):
186+
continue
187+
if angle_diff(new_p.heading,heading(new_p,nearest_point_b)) > (self.heading_limit):
188+
continue
189+
if angle_diff(nearest_point_b.heading,heading(nearest_point_b,new_p)) > (self.heading_limit):
190+
continue
191+
192+
# check if there exist another point that can connect two tree with less cost
193+
neighbor_points = self.Neighbors(new_p,tree_b)
194+
min_cost = new_p.cost + nearest_point_b.cost + distance(new_p,nearest_point_a)
195+
for point in neighbor_points:
196+
curr_cost = new_p.cost + point.cost + distance(point, new_p)
197+
if curr_cost < min_cost:
198+
if angle_diff(new_p.heading,angle_inverse(point.heading)) > (self.heading_limit):
199+
continue
200+
if angle_diff(new_p.heading,heading(new_p,point)) > (self.heading_limit):
201+
continue
202+
if angle_diff(point.heading,heading(point,new_p)) > (self.heading_limit):
203+
continue
204+
min_cost = curr_cost
205+
nearest_point_b = point
206+
# generate a route from start point to end point
207+
self.trace_path(new_p,nearest_point_b)
208+
return self.path
209+
210+
print("========== route not found ==========")
211+
return []
212+
213+
# if the distance of point in the tree and sample point is less or equal to search radius
214+
# it is considered as a neighbor of sample point
215+
def Neighbors(self,sample_p,tree):
216+
neighbor_points = []
217+
for point in tree:
218+
if distance(point, sample_p) <= self.search_r:
219+
neighbor_points.append(point)
220+
return neighbor_points
221+
222+
# the relation of two tree is opsite, revert one of them
223+
def trace_path(self,point_a,point_b):
224+
path_start = []
225+
path_end = []
226+
227+
if point_a not in self.tree_from_start:
228+
point_temp = point_a
229+
point_a = point_b
230+
point_b = point_temp
231+
232+
while point_a is not None:
233+
path_start.append(point_a)
234+
point_a = point_a.parent
235+
while point_b is not None:
236+
point_b.heading = angle_inverse(point_b.heading)
237+
path_end.append(point_b)
238+
point_b = point_b.parent
239+
240+
self.path = path_start[::-1] + path_end

0 commit comments

Comments
 (0)