"""
Grid based Dijkstra planning
author: Atsushi Sakai(@Atsushi_twi)
"""
import matplotlib.pyplot as plt
import math
show_animation = True
class Dijkstra:
# 将传进来的值进行初始化,resolution就是传进来的grid_size
def __init__(self, ox, oy, resolution, robot_radius):
"""
Initialize map for planning 初始化地图以进行规划
ox: x position list of Obstacles [m] ox:x障碍物位置列表[m]
oy: y position list of Obstacles [m] oy:y障碍物位置列表[m]
resolution: grid resolution [m] 分辨率:栅格分辨率[m]
rr: robot radius[m] rr:机器人半径[m]
"""
self.min_x = None
self.min_y = None
self.max_x = None
self.max_y = None
self.x_width = None
self.y_width = None
self.obstacle_map = None
# resolution就是传进来的grid_size,传进来之后赋值给类里面的成员
self.resolution = resolution # 传进来栅格的大小 grid_size
self.robot_radius = robot_radius # 机器人半径
self.calc_obstacle_map(ox, oy) # 构建栅格地图: calc_obstacle_map函数
self.motion = self.get_motion_model() # 定义机器人的运动方:get_motion_model函数
class Node: # 把每一个栅格定义为结点
def __init__(self, x, y, cost, parent_index):
# 存储栅格坐标x,y; 路径值;parent_index用来查找来源,用来寻找路径
self.x = x # index of grid
self.y = y # index of grid
self.cost = cost # g(n) 代价值就是路径权值
self.parent_index = parent_index # index of previous Node 记录自己的上一个栅格,用来存储路径
def __str__(self):
return str(self.x) + "," + str(self.y) + "," + str(self.cost) + "," + str(self.parent_index)
# 定义方法
def planning(self, sx, sy, gx, gy):
"""
dijkstra path search dijkstra路径搜索
input:
s_x: start x position [m]
s_y: start y position [m]
gx: goal x position [m]
gy: goal y position [m]
output:
rx: x position list of the final path 最终路径的x位置列表
ry: y position list of the final path
"""
start_node = self.Node(self.calc_xy_index(sx, self.min_x),
self.calc_xy_index(sy, self.min_y), 0.0, -1)
# calc_xy_index函数 round((position - minp) / self.resolution)
goal_node = self.Node(self.calc_xy_index(gx, self.min_x),
self.calc_xy_index(gy, self.min_y), 0.0, -1)
open_set, closed_set = dict(), dict() # key -value: hash表
open_set[self.calc_index(start_node)] = start_node # 函数 calc_index
# node.y * self.x_width + node.x
while 1: # 伪代码 调用的是内置的函数,具体比较数值是open_set.cost,取得当前cost值最小的节点
# 取open_set中cost最小的节点,得到cost值最小的节点下标c_id min采用匿名函数 返回的使 key=lambda o
c_id = min(open_set, key=lambda o: open_set[o].cost) # 取cost最小的节点
current = open_set[c_id] # current表示cost值最小的节点,open_set[c_id]类似g(n),c_id表示g,cost和n是一致的
# show graph 动画仿真
if show_animation: # pragma: no cover
plt.plot(self.calc_position(current.x, self.min_x),
self.calc_position(current.y, self.min_y), "xc")
# for stopping simulation with the esc key.
plt.gcf().canvas.mpl_connect(
'key_release_event',
lambda event: [exit(0) if event.key == 'escape' else None])
if len(closed_set.keys()) % 10 == 0:
plt.pause(0.001)
# 判断是否是终点
if current.x == goal_node.x and current.y == goal_node.y:
print("Find goal")
goal_node.parent_index = current.parent_index
goal_node.cost = current.cost
break
# Remove the item from the open set
del open_set[c_id] # 将该节点在open_set中删掉
# Add it to the closed set
closed_set[c_id] = current # 将删除的节点加入到closed_set
# expand search grid based on motion model #基于运动模型的扩展搜索网格
for move_x, move_y, move_cost in self.motion: # 遍历邻接节点,以运动方式进行遍历,这里要遍历8种情况
node = self.Node(current.x + move_x, # 先求邻接节点,根据当前x,y与运动方式得到的邻接节点的x,y
current.y + move_y, # 路径权值 = 当前的路径值+移动的代价
current.cost + move_cost, c_id) # c_id表示当前父节点的下标 栅格的位置
n_id = self.calc_index(node) # 计算出当前新节点的索引值n_id,这里的node和n_id循环生成8个
# n_id与c_id的区别在于前者存储新产生的节点,后者存储cost值最小的结点
if n_id in closed_set:
continue
# 计算邻接结点是否可行,有没有超出范围,如果为false,则跳出下面操作重新循环
if not self.verify_node(node):
continue
if n_id not in open_set: # 如果新的n_id不在open_set中,就添加进去
open_set[n_id] = node # Discover a new node
else: # 如果n_id在open_set中,现存的值比新的值大的话,就更新到起点的距离
if open_set[n_id].cost >= node.cost: # open_set的值是不是大于当前结点的值,大的话就进行更新
# This path is the best until now. record it!
open_set[n_id] = node
rx, ry = self.calc_final_path(goal_node, closed_set) # 计算最后的路径
return rx, ry
def calc_final_path(self, goal_node, closed_set): # 计算最后的路径
# generate final course
rx, ry = [self.calc_position(goal_node.x, self.min_x)], [
self.calc_position(goal_node.y, self.min_y)] # 反向计算真实位置,保存到列表里
parent_index = goal_node.parent_index # 首先得到终点的父节点,node的
while parent_index != -1: # 初始节点的parent_index设置为-1
n = closed_set[parent_index] # 这里的parent_index就是node里面的c_id
rx.append(self.calc_position(n.x, self.min_x))
ry.append(self.calc_position(n.y, self.min_y))
parent_index = n.parent_index # n节点的父节点取出来,直到取到最初始的结点
return rx, ry # 得到rx和ry就可以得到路径
def calc_position(self, index, minp): # 计算栅格位置
pos = index * self.resolution + minp
return pos
# 把 位置转换为栅格点
def calc_xy_index(self, position, minp): # 当前结点和父节点,得到x和y的索引
# 当前位置-最小值,再除以栅格大小2,得到x索引
return round((position - minp) / self.resolution)
def calc_index(self, node): # 计算索引
return node.y * self.x_width + node.x
# def verify_node 计算邻接结点是否可行,是否超出范围
def verify_node(self, node):
px = self.calc_position(node.x, self.min_x) # 计算出px和py的位置
py = self.calc_position(node.y, self.min_y)
if px < self.min_x:
return False
if py < self.min_y:
return False
if px >= self.max_x:
return False
if py >= self.max_y:
return False
if self.obstacle_map[node.x][node.y]: # 判断是不是障碍物
return False
return True
def calc_obstacle_map(self, ox, oy):
""" 第1步:构建栅格地图
round(参数,保留的小数位),
该函数遵循四舍六入五成偶原则,被约的5前面的数值若为偶数时,
则会舍弃5,若5前面的数值为奇数时则进一,若5后面有非0数值时则都会进一,
round()中默认保留小数位是0,即当只传一位参数时默认返回的是整数
"""
self.min_x = round(min(ox)) # 取ox的最小值
self.min_y = round(min(oy)) # 取oy的最小值
self.max_x = round(max(ox)) # 取ox的最大值
self.max_y = round(max(oy)) # 取oy的最大值
print("min_x:", self.min_x) # min_x: -10
print("min_y:", self.min_y) # min_y: -10
print("max_x:", self.max_x) # max_x: 60
print("max_y:", self.max_y) # max_y: 60
# x的最大值减最小值,再除以栅格大小2,得到x方向的栅格个数有35,y方向也有35个栅格
self.x_width = round((self.max_x - self.min_x) / self.resolution) # [60-(-10)]/2=35
self.y_width = round((self.max_y - self.min_y) / self.resolution) # [60-(-10)]/2=35
print("x_width:", self.x_width)
print("y_width:", self.y_width)
# obstacle map generation 障碍地图生成
# 初始化地图
# false 表示还没有设置障碍物
# 初始化地图,使用二维向量表示,采用两层列表,内层是y方向的栅格个数,外层是x方向的栅格个数
self.obstacle_map = [[False for _ in range(self.y_width)] # 所有值都初始化为false,内层循环是y方向栅格数
for _ in range(self.x_width)] # 采用两层的列表表示,for_in range表示循环
# 设置障碍物 range是从0开始,range默认步长为1,前两层for循环用来遍历栅格,range(35)->0--34
for ix in range(self.x_width): # ix表示栅格在x方向的下标, ix 范围是 0~34
x = self.calc_position(ix, self.min_x) # 通过栅格下标计算位置 x = -10 ~ 58(每个值间隔为2)
for iy in range(self.y_width): # iy表示栅格在y方向的下标, iy 范围是 0~34
y = self.calc_position(iy, self.min_y) # 通过栅格下标计算位置 y = -10 ~ 58(每个值间隔为2)
# 遍历障碍物, zip相当于是组合函数, 组合成各个点(iox,ioy), (ox,oy)障碍物坐标
for iox, ioy in zip(ox, oy):
# hypot用来计算两点之间的距离 def hypot(x, y): sqrt(x*x + y*y)
d = math.hypot(iox - x, ioy - y)
# 如果距离比机器人半径小,说明机器人不可以通行。这里当x和y不与ox,oy重合即可
if d <= self.robot_radius:
self.obstacle_map[ix][iy] = True # 将障碍物所在栅格设置为true
break
@staticmethod
# 定义机器人的运动方
def get_motion_model(): # 机器人运动模式
# dx, dy, cost, 前两个是行走方式,cost行走的代价
motion = [[1, 0, 1], # 表示x方向增加1,y方向不变,权值为1,就是说“往右走"
[0, 1, 1], # 往上走
[-1, 0, 1], # 往左走
[0, -1, 1], # 往下走
[-1, -1, math.sqrt(2)], # 往左下
[-1, 1, math.sqrt(2)], # 往左上
[1, -1, math.sqrt(2)], # 往右下
[1, 1, math.sqrt(2)]] # 往右上
return motion
def main():
# start and goal position 设置机器人的起点和终点.
sx = -5.0 # [m] 设置机器人的起点 x
sy = -5.0 # [m] 设置机器人的起点 y
gx = 50.0 # [m] 设置机器人的终点 x
gy = 50.0 # [m] 设置机器人的终点 y
grid_size = 2.0 # [m] 设置栅格的大小
# 就是将地图分为一个一个2*2的方格,方格的4个角可以用坐标表示
robot_radius = 1.0 # [m] 机器人的半径
# set obstacle positions 设置障碍物的位置
ox, oy = [], [] # 设置障碍物的位置,图中的黑点 空列表
for i in range(-10, 60): # 下方的墙 y = -10 x = -10 ~ 59
ox.append(i)
oy.append(-10.0)
for i in range(-10, 60): # 右方的墙 x = 60 y = -10 ~ 59
ox.append(60.0)
oy.append(i)
for i in range(-10, 61): # 上方的墙 y = 60 x = -10 ~ 60
ox.append(i)
oy.append(60.0)
for i in range(-10, 61): # 左边的墙 x = -10 y = -10 ~ 60
ox.append(-10.0)
oy.append(i)
for i in range(-10, 40): # 左下的墙
ox.append(20.0)
oy.append(i)
for i in range(0, 40): # 右上的墙
ox.append(40.0)
oy.append(60.0 - i)
# 画图,起点终点障碍物
if show_animation: # pragma: no cover
plt.plot(ox, oy, ".k") # 障碍物ox、oy是点,黑色
plt.plot(sx, sy, "og") # 起点sx,sy是实心圈标记,填充绿色
plt.plot(gx, gy, "xb") # 终点gx,gy是x标记,蓝色
plt.grid(True)
plt.axis("equal")
dijkstra = Dijkstra(ox, oy, grid_size, robot_radius)
rx, ry = dijkstra.planning(sx, sy, gx, gy)
if show_animation: # pragma: no cover
plt.plot(rx, ry, "-r")
plt.pause(0.01)
plt.show()
if __name__ == '__main__':
main()