Multi-Purpose-MPC/reference_path.py

237 lines
8.6 KiB
Python
Raw Normal View History

import numpy as np
import math
from map import Map
from bresenham import bresenham
import matplotlib.pyplot as plt
############
# Waypoint #
############
class Waypoint:
def __init__(self, x, y, psi, kappa):
"""
Waypoint object containing x, y location in global coordinate system,
orientation of waypoint psi and local curvature kappa
:param x: x position in global coordinate system | [m]
:param y: y position in global coordinate system | [m]
:param psi: orientation of waypoint | [rad]
:param kappa: local curvature | [1 / m]
"""
self.x = x
self.y = y
self.psi = psi
self.kappa = kappa
def __sub__(self, other):
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
##################
# Reference Path #
##################
class ReferencePath:
def __init__(self, map, wp_x, wp_y, resolution, smoothing_distance, width_info=False):
"""
Reference Path object. Create a reference trajectory from specified
corner points with given resolution. Smoothing around corners can be
applied.
"""
# precision
self.eps = 1e-12
# map
self.map = map
# resolution of the path
self.resolution = resolution
# look ahead distance for path averaging
self.smoothing_distance = smoothing_distance
# waypoints with x, y, psi, k
self.waypoints = self.construct_path(wp_x, wp_y)
# path width
self.get_width_info = width_info
if self.get_width_info:
self.width_info = self.compute_width()
self.min_width = (np.min(self.width_info[0, :]),
np.min(self.width_info[3, :]))
def construct_path(self, wp_x, wp_y):
# Number of waypoints
n_wp = [int(np.sqrt((wp_x[i + 1] - wp_x[i]) ** 2 +
(wp_y[i + 1] - wp_y[i]) ** 2) /
self.resolution) for i in range(len(wp_x) - 1)]
# Construct waypoints with specified resolution
gp_x, gp_y = wp_x[-1], wp_y[-1]
wp_x = [np.linspace(wp_x[i], wp_x[i+1], n_wp[i], endpoint=False).
tolist() for i in range(len(wp_x)-1)]
wp_x = [wp for segment in wp_x for wp in segment] + [gp_x]
wp_y = [np.linspace(wp_y[i], wp_y[i + 1], n_wp[i], endpoint=False).
tolist() for i in range(len(wp_y) - 1)]
wp_y = [wp for segment in wp_y for wp in segment] + [gp_y]
# smooth path
wp_xs = []
wp_ys = []
for wp_id in range(self.smoothing_distance, len(wp_x) -
self.smoothing_distance):
wp_xs.append(np.mean(wp_x[wp_id - self.smoothing_distance:wp_id
+ self.smoothing_distance + 1]))
wp_ys.append(np.mean(wp_y[wp_id - self.smoothing_distance:wp_id
+ self.smoothing_distance + 1]))
waypoints = list(zip(wp_xs, wp_ys))
waypoints = self.spatial_reformulation(waypoints)
return waypoints
def spatial_reformulation(self, waypoints):
"""
Reformulate conventional waypoints (x, y) coordinates into waypoint
objects containing (x, y, psi, kappa)
:return: list of waypoint objects for entire reference path
"""
waypoints_spatial = []
for wp_id in range(len(waypoints) - 1):
# get start and goal waypoints
current_wp = np.array(waypoints[wp_id])
next_wp = np.array(waypoints[wp_id + 1])
# difference vector
dif_ahead = next_wp - current_wp
# angle ahead
psi = np.arctan2(dif_ahead[1], dif_ahead[0])
# distance to next waypoint
dist_ahead = np.linalg.norm(dif_ahead, 2)
# get x and y coordinates of current waypoint
x = current_wp[0]
y = current_wp[1]
# first waypoint
if wp_id == 0:
kappa = 0
else:
prev_wp = np.array(waypoints[wp_id - 1])
dif_behind = current_wp - prev_wp
angle_behind = np.arctan2(dif_behind[1], dif_behind[0])
angle_dif = np.mod(psi - angle_behind + math.pi, 2 * math.pi) \
- math.pi
kappa = np.abs(angle_dif / (dist_ahead + self.eps))
waypoints_spatial.append(Waypoint(x, y, psi, kappa))
return waypoints_spatial
def compute_width(self, max_dist=2.0):
max_dist = max_dist # m
width_info = np.zeros((6, len(self.waypoints)))
for wp_id, wp in enumerate(self.waypoints):
for i, dir in enumerate(['left', 'right']):
# get pixel coordinates of waypoint
wp_x, wp_y = self.map.w2m(wp.x, wp.y)
# get angle orthogonal to path in current direction
if dir == 'left':
angle = np.mod(wp.psi + math.pi / 2 + math.pi,
2 * math.pi) - math.pi
else:
angle = np.mod(wp.psi - math.pi / 2 + math.pi,
2 * math.pi) - math.pi
# get closest cell to orthogonal vector
t_x, t_y = self.map.w2m(wp.x + max_dist * np.cos(angle), wp.y + max_dist * np.sin(angle))
# compute path between cells
width_info[3*i:3*(i+1), wp_id] = self.get_min_dist(wp_x, wp_y, t_x, t_y, max_dist)
return width_info
def get_min_dist(self, wp_x, wp_y, t_x, t_y, max_dist):
# get neighboring cells (account for discretization)
neighbors_x, neighbors_y = [], []
for i in range(-1, 2, 1):
for j in range(-1, 2, 1):
neighbors_x.append(t_x + i)
neighbors_y.append(t_y + j)
# get bresenham paths to all neighboring cells
paths = []
for t_x, t_y in zip(neighbors_x, neighbors_y):
path = list(bresenham(wp_x, wp_y, t_x, t_y))
paths.append(path)
min_dist = max_dist
min_cell = self.map.m2w(t_x, t_y)
for path in paths:
for cell in path:
t_x = cell[0]
t_y = cell[1]
# if path goes through occupied cell
if self.map.data[t_y, t_x] == 0:
# get world coordinates
x, y = self.map.m2w(wp_x, wp_y)
c_x, c_y = self.map.m2w(t_x, t_y)
cell_dist = np.sqrt((x - c_x) ** 2 + (y - c_y) ** 2)
if cell_dist < min_dist:
min_dist = cell_dist
min_cell = (c_x, c_y)
dist_info = np.array([min_dist, min_cell[0], min_cell[1]])
return dist_info
def show(self):
# plot map
plt.clf()
plt.imshow(np.flipud(self.map.data),cmap='gray',
extent=[self.map.origin[0], self.map.origin[0] +
self.map.width * self.map.resolution,
self.map.origin[1], self.map.origin[1] +
self.map.height * self.map.resolution])
# plot reference path
wp_x = np.array([wp.x for wp in self.waypoints])
wp_y = np.array([wp.y for wp in self.waypoints])
plt.scatter(wp_x, wp_y, color='k', s=5)
if self.get_width_info:
print('Min Width Left: {:f} m'.format(self.min_width[0]))
print('Min Width Right: {:f} m'.format(self.min_width[1]))
plt.quiver(wp_x, wp_y, self.width_info[1, :] - wp_x,
self.width_info[2, :] - wp_y, scale=1, units='xy',
width=0.05, color='#D4AC0D')
plt.quiver(wp_x, wp_y, self.width_info[4, :] - wp_x,
self.width_info[5, :] - wp_y, scale=1, units='xy',
width=0.05, color='#BA4A00')
if __name__ == '__main__':
# Create Map
map = Map(file_path='map_race.png', origin=[-1, -2], resolution=0.005)
# Specify waypoints
# Floor 2
# wp_x = [-9.169, -2.7, 11.9, 7.3, -6.95]
# wp_y = [-15.678, -7.12, 10.9, 14.5, -3.31]
# Race Track
wp_x = [-0.75, -0.25, -0.25, 0.25, 0.25, 1.25, 1.25, 0.75, 0.75, 1.25, 1.25, -0.75, -0.75, -0.25]
wp_y = [-1.5, -1.5, -0.5, -0.5, -1.5, -1.5, -1, -1, -0.5, -0.5, 0, 0, -1.5, -1.5]
# Specify path resolution
path_resolution = 0.05 # m / wp
# Smooth Path
reference_path = ReferencePath(map, wp_x, wp_y, path_resolution,
smoothing_distance=5)
reference_path.show()
plt.show()