Add obstacle class and function add_obstacles to add obstacles to path.

Restructure class ReferencePath a bit.
Add safety_margin to update_bounds function.
master
matssteinweg 2019-12-01 15:38:17 +01:00
parent 7940964b29
commit 6ec93febd3
1 changed files with 125 additions and 8 deletions

View File

@ -3,6 +3,13 @@ import math
from map import Map from map import Map
from bresenham import bresenham from bresenham import bresenham
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.patches as plt_patches
from scipy.signal import savgol_filter
# Colors
DRIVABLE_AREA = '#BDC3C7'
WAYPOINTS = '#D0D3D4'
OBSTACLE = '#2E4053'
############ ############
@ -41,13 +48,43 @@ class Waypoint:
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5 return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
############
# Obstacle #
############
class Obstacle:
def __init__(self, cx, cy, radius):
"""
Constructor for a circular obstacle to be place on a path.
:param cx: x coordinate of center of obstacle in world coordinates
:param cy: y coordinate of center of obstacle in world coordinates
:param radius: radius of circular obstacle in m
"""
self.cx = cx
self.cy = cy
self.radius = radius
def show(self):
"""
Display obstacle.
"""
# Draw circle
circle = plt_patches.Circle(xy=(self.cx, self.cy), radius=
self.radius, color=OBSTACLE)
ax = plt.gca()
ax.add_patch(circle)
################## ##################
# Reference Path # # Reference Path #
################## ##################
class ReferencePath: class ReferencePath:
def __init__(self, map, wp_x, wp_y, resolution, smoothing_distance, max_width): def __init__(self, map, wp_x, wp_y, resolution, smoothing_distance,
max_width):
""" """
Reference Path object. Create a reference trajectory from specified Reference Path object. Create a reference trajectory from specified
corner points with given resolution. Smoothing around corners can be corner points with given resolution. Smoothing around corners can be
@ -80,6 +117,9 @@ class ReferencePath:
# Compute path width (attribute of each waypoint) # Compute path width (attribute of each waypoint)
self._compute_width(max_width=max_width) self._compute_width(max_width=max_width)
# Obstacles on path
self.obstacles = list()
def _construct_path(self, wp_x, wp_y): def _construct_path(self, wp_x, wp_y):
""" """
Construct path from given waypoints. Construct path from given waypoints.
@ -250,13 +290,17 @@ class ReferencePath:
return min_width, min_cell return min_width, min_cell
def update_bounds(self, wp_id): def update_bounds(self, car, wp_id):
""" """
Compute upper and lower bounds of the drivable area orthogonal to Compute upper and lower bounds of the drivable area orthogonal to
the given waypoint. the given waypoint.
:param car: car model driving on the path
:param wp_id: ID of reference waypoint :param wp_id: ID of reference waypoint
""" """
# Get car-dependent safety margin
safety_margin = car.safety_margin
# Get reference waypoint # Get reference waypoint
wp = self.waypoints[wp_id] wp = self.waypoints[wp_id]
@ -311,6 +355,10 @@ class ReferencePath:
ub = sign_ub * np.sqrt((ub_ls[0]-wp.x)**2+(ub_ls[1]-wp.y)**2) ub = sign_ub * np.sqrt((ub_ls[0]-wp.x)**2+(ub_ls[1]-wp.y)**2)
lb = sign_lb * np.sqrt((lb_ls[0]-wp.x)**2+(lb_ls[1]-wp.y)**2) lb = sign_lb * np.sqrt((lb_ls[0]-wp.x)**2+(lb_ls[1]-wp.y)**2)
# Add safety margin to bounds
ub = ub - (safety_margin[1] + 2 * self.map.resolution)
lb = lb + (safety_margin[1] + 2 * self.map.resolution)
# Update member variables of waypoint # Update member variables of waypoint
wp.ub = ub wp.ub = ub
wp.lb = lb wp.lb = lb
@ -318,6 +366,24 @@ class ReferencePath:
return lb, ub return lb, ub
def add_obstacles(self, obstacles):
"""
Add obstacles to the path.
:param obstacles: list of obstacle objects
"""
# Extend list of obstacles
self.obstacles.extend(obstacles)
# Iterate over list of obstacles
for obstacle in obstacles:
radius_px = int(np.ceil(obstacle.radius / self.map.resolution))
cx_px, cy_px = self.map.w2m(obstacle.cx, obstacle.cy)
y, x = np.ogrid[-radius_px: radius_px, -radius_px: radius_px]
index = x ** 2 + y ** 2 <= radius_px ** 2
self.map.data[cy_px-radius_px:cy_px+radius_px, cx_px-radius_px:
cx_px+radius_px][index] = 0
def show(self, display_drivable_area=True): def show(self, display_drivable_area=True):
""" """
Display path object on current figure. Display path object on current figure.
@ -328,12 +394,18 @@ class ReferencePath:
# Clear figure # Clear figure
plt.clf() plt.clf()
# Disabled ticks
plt.xticks([])
plt.yticks([])
# Plot map in gray-scale and set extent to match world coordinates # Plot map in gray-scale and set extent to match world coordinates
plt.imshow(np.flipud(self.map.data), cmap='gray', canvas = np.ones(self.map.data.shape)
plt.imshow(canvas, cmap='gray',
extent=[self.map.origin[0], self.map.origin[0] + extent=[self.map.origin[0], self.map.origin[0] +
self.map.width * self.map.resolution, self.map.width * self.map.resolution,
self.map.origin[1], self.map.origin[1] + self.map.origin[1], self.map.origin[1] +
self.map.height * self.map.resolution]) self.map.height * self.map.resolution], vmin=0.0,
vmax=1.0)
# Get x and y coordinates for all waypoints # Get x and y coordinates for all waypoints
wp_x = np.array([wp.x for wp in self.waypoints]) wp_x = np.array([wp.x for wp in self.waypoints])
@ -346,22 +418,56 @@ class ReferencePath:
wp_lb_y = np.array([wp.border_cells[1][1] for wp in self.waypoints]) wp_lb_y = np.array([wp.border_cells[1][1] for wp in self.waypoints])
# Plot waypoints # Plot waypoints
plt.scatter(wp_x, wp_y, color='#99A3A4', s=3) plt.scatter(wp_x, wp_y, color=WAYPOINTS, s=3)
# Plot arrows indicating drivable area # Plot arrows indicating drivable area
if display_drivable_area: if display_drivable_area:
plt.quiver(wp_x, wp_y, wp_ub_x - wp_x, wp_ub_y - wp_y, scale=1, plt.quiver(wp_x, wp_y, wp_ub_x - wp_x, wp_ub_y - wp_y, scale=1,
units='xy', width=1.5*self.resolution, color='#2ECC71', units='xy', width=0.2*self.resolution, color=DRIVABLE_AREA,
headwidth=1, headlength=0) headwidth=1, headlength=0)
plt.quiver(wp_x, wp_y, wp_lb_x - wp_x, wp_lb_y - wp_y, scale=1, plt.quiver(wp_x, wp_y, wp_lb_x - wp_x, wp_lb_y - wp_y, scale=1,
units='xy', width=1.5*self.resolution, color='#2ECC71', units='xy', width=0.2*self.resolution, color=DRIVABLE_AREA,
headwidth=1, headlength=0) headwidth=1, headlength=0)
# Plot border of path
bl_x = np.array([wp.border_cells[0][0] for wp in
self.waypoints] +
[self.waypoints[0].border_cells[0][0]])
bl_y = np.array([wp.border_cells[0][1] for wp in
self.waypoints] +
[self.waypoints[0].border_cells[0][1]])
br_x = np.array([wp.border_cells[1][0] for wp in
self.waypoints] +
[self.waypoints[0].border_cells[1][0]])
br_y = np.array([wp.border_cells[1][1] for wp in
self.waypoints] +
[self.waypoints[0].border_cells[1][1]])
# Smooth border
# bl_x = savgol_filter(bl_x, 15, 9)
# bl_y = savgol_filter(bl_y, 15, 9)
# br_x = savgol_filter(br_x, 15, 9)
# br_y = savgol_filter(br_y, 15, 9)
# If circular path, connect start and end point
if np.abs(self.waypoints[-1] - self.waypoints[0]) <= 2*self.resolution:
plt.plot(bl_x, bl_y, color=OBSTACLE)
plt.plot(br_x, br_y, color=OBSTACLE)
# If not circular, close path at start and end
else:
plt.plot(bl_x[:-1], bl_y[:-1], color=OBSTACLE)
plt.plot(br_x[:-1], br_y[:-1], color=OBSTACLE)
plt.plot((bl_x[-2],br_x[-2]), (bl_y[-2], br_y[-2]), color=OBSTACLE)
plt.plot((bl_x[0],br_x[0]), (bl_y[0], br_y[0]), color=OBSTACLE)
# Plot obstacles
for obstacle in self.obstacles:
obstacle.show()
if __name__ == '__main__': if __name__ == '__main__':
# Select Path | 'Race' or 'Q' # Select Path | 'Race' or 'Q'
path = 'Q' path = 'Race'
# Create Map # Create Map
if path == 'Race': if path == 'Race':
@ -388,6 +494,17 @@ if __name__ == '__main__':
print('Invalid path!') print('Invalid path!')
exit(1) exit(1)
obs1 = Obstacle(cx=0.0, cy=0.0, radius=0.05)
obs2 = Obstacle(cx=-0.8, cy=-0.5, radius=0.05)
obs3 = Obstacle(cx=-0.7, cy=-1.5, radius=0.05)
obs4 = Obstacle(cx=-0.3, cy=-1.0, radius=0.05)
obs5 = Obstacle(cx=0.3, cy=-1.0, radius=0.05)
obs6 = Obstacle(cx=0.75, cy=-1.5, radius=0.05)
obs7 = Obstacle(cx=0.7, cy=-0.9, radius=0.05)
obs8 = Obstacle(cx=1.2, cy=0.0, radius=0.05)
reference_path.add_obstacles([obs1, obs2, obs3, obs4, obs5, obs6, obs7,
obs8])
reference_path.show() reference_path.show()
plt.show() plt.show()