* using enumeration for navigation decisions
* added detailed doc strings * standards update for imports
This commit is contained in:
parent
d0689df5f2
commit
62b603e593
|
@ -6,10 +6,25 @@ This module provides GlobalRoutePlanner implementation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
import carla
|
import carla
|
||||||
|
|
||||||
|
|
||||||
|
class NavEnum(Enum):
|
||||||
|
"""
|
||||||
|
Enumeration class containing possible navigation decisions
|
||||||
|
"""
|
||||||
|
START = "START"
|
||||||
|
GO_STRAIGHT = "GO_STRAIGHT"
|
||||||
|
LEFT = "LEFT"
|
||||||
|
RIGHT = "RIGHT"
|
||||||
|
FOLLOW_LANE = "FOLLOW_LANE"
|
||||||
|
STOP = "STOP"
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GlobalRoutePlanner(object):
|
class GlobalRoutePlanner(object):
|
||||||
"""
|
"""
|
||||||
This class provides a very high level route plan.
|
This class provides a very high level route plan.
|
||||||
|
@ -21,17 +36,20 @@ class GlobalRoutePlanner(object):
|
||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
self.dao = dao
|
self._dao = dao
|
||||||
|
self._topology = None
|
||||||
|
self._graph = None
|
||||||
|
self._id_map = None
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""
|
"""
|
||||||
Perform initial server data lookup for detailed topology
|
Perform initial server data lookup for detailed topology
|
||||||
and builds graph representation of the world map.
|
and builds graph representation of the world map.
|
||||||
"""
|
"""
|
||||||
self.topology = self.dao.get_topology()
|
self._topology = self._dao.get_topology()
|
||||||
# Creating graph of the world map and also a map from
|
# Creating graph of the world map and also a maping from
|
||||||
# node co-ordinates to node id
|
# node co-ordinates to node id
|
||||||
self.graph, self.id_map = self.build_graph()
|
self._graph, self._id_map = self.build_graph()
|
||||||
|
|
||||||
def plan_route(self, origin, destination):
|
def plan_route(self, origin, destination):
|
||||||
"""
|
"""
|
||||||
|
@ -39,19 +57,20 @@ class GlobalRoutePlanner(object):
|
||||||
origin : tuple containing x, y of the route's start position
|
origin : tuple containing x, y of the route's start position
|
||||||
destination : tuple containing x, y of the route's end position
|
destination : tuple containing x, y of the route's end position
|
||||||
|
|
||||||
return : list of turn by turn navigation decision
|
return : list of turn by turn navigation decisions as
|
||||||
possible values (for now) are START, GO_STRAIGHT, LEFT, RIGHT,
|
NavEnum elements
|
||||||
|
Possible values (for now) are START, GO_STRAIGHT, LEFT, RIGHT,
|
||||||
FOLLOW_LANE, STOP
|
FOLLOW_LANE, STOP
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threshold = 0.0523599 # 5 degrees
|
threshold = 0.0523599 # 5 degrees
|
||||||
route = self.path_search(origin, destination)
|
route = self.path_search(origin, destination)
|
||||||
plan = []
|
plan = []
|
||||||
plan.append('START')
|
plan.append(NavEnum.START)
|
||||||
# Compare current edge and next edge to decide on action
|
# Compare current edge and next edge to decide on action
|
||||||
for i in range(len(route) - 2):
|
for i in range(len(route) - 2):
|
||||||
current_edge = self.graph.edges[route[i], route[i + 1]]
|
current_edge = self._graph.edges[route[i], route[i + 1]]
|
||||||
next_edge = self.graph.edges[route[i + 1], route[i + 2]]
|
next_edge = self._graph.edges[route[i + 1], route[i + 2]]
|
||||||
if not current_edge['intersection']:
|
if not current_edge['intersection']:
|
||||||
cv = current_edge['exit_vector']
|
cv = current_edge['exit_vector']
|
||||||
nv = None
|
nv = None
|
||||||
|
@ -67,35 +86,46 @@ class GlobalRoutePlanner(object):
|
||||||
angle_list[i] = -1 * (180 + angle_list[i])
|
angle_list[i] = -1 * (180 + angle_list[i])
|
||||||
ca, na = angle_list
|
ca, na = angle_list
|
||||||
if abs(na - ca) < threshold:
|
if abs(na - ca) < threshold:
|
||||||
action = 'GO_STRAIGHT'
|
action = NavEnum.GO_STRAIGHT
|
||||||
elif na - ca > 0:
|
elif na - ca > 0:
|
||||||
action = 'LEFT'
|
action = NavEnum.LEFT
|
||||||
else:
|
else:
|
||||||
action = 'RIGHT'
|
action = NavEnum.RIGHT
|
||||||
else:
|
else:
|
||||||
action = 'FOLLOW_LANE'
|
action = NavEnum.FOLLOW_LANE
|
||||||
plan.append(action)
|
plan.append(action)
|
||||||
plan.append('STOP')
|
plan.append(NavEnum.STOP)
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
|
def _distance_heuristic(self, n1, n2):
|
||||||
|
"""
|
||||||
|
Distance heuristic calculator for path searching
|
||||||
|
in self._graph
|
||||||
|
"""
|
||||||
|
(x1, y1) = self._graph.nodes[n1]['vertex']
|
||||||
|
(x2, y2) = self._graph.nodes[n2]['vertex']
|
||||||
|
return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5
|
||||||
|
|
||||||
def path_search(self, origin, destination):
|
def path_search(self, origin, destination):
|
||||||
"""
|
"""
|
||||||
This function finds the shortest path connecting origin and destination
|
This function finds the shortest path connecting origin and destination
|
||||||
using A* search with distance heuristic
|
using A* search with distance heuristic.
|
||||||
|
|
||||||
|
origin : tuple containing x, y co-ordinates of start position
|
||||||
|
desitnation : tuple containing x, y co-ordinates of end position
|
||||||
|
|
||||||
|
return : path as list of node ids (as int) of the graph self._graph
|
||||||
|
connecting origin and destination
|
||||||
"""
|
"""
|
||||||
xo, yo = origin
|
xo, yo = origin
|
||||||
xd, yd = destination
|
xd, yd = destination
|
||||||
start = self.localise(xo, yo)
|
start = self.localise(xo, yo)
|
||||||
end = self.localise(xd, yd)
|
end = self.localise(xd, yd)
|
||||||
|
|
||||||
def dist(n1, n2):
|
|
||||||
(x1, y1) = self.graph.nodes[n1]['vertex']
|
|
||||||
(x2, y2) = self.graph.nodes[n2]['vertex']
|
|
||||||
return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5
|
|
||||||
route = nx.astar_path(
|
route = nx.astar_path(
|
||||||
self.graph, source=self.id_map[start['entry']],
|
self._graph, source=self._id_map[start['entry']],
|
||||||
target=self.id_map[end['exit']],
|
target=self._id_map[end['exit']],
|
||||||
heuristic=dist,
|
heuristic=self._distance_heuristic,
|
||||||
weight='length')
|
weight='length')
|
||||||
|
|
||||||
return route
|
return route
|
||||||
|
@ -103,12 +133,16 @@ class GlobalRoutePlanner(object):
|
||||||
def localise(self, x, y):
|
def localise(self, x, y):
|
||||||
"""
|
"""
|
||||||
This function finds the road segment closest to (x, y)
|
This function finds the road segment closest to (x, y)
|
||||||
|
x, y : co-ordinates of the point to be localized
|
||||||
|
|
||||||
|
return : pair of points, tuple of tuples containing co-ordinates
|
||||||
|
of points that represents the road segment closest to x, y
|
||||||
"""
|
"""
|
||||||
distance = float('inf')
|
distance = float('inf')
|
||||||
nearest = (distance, dict())
|
nearest = (distance, dict())
|
||||||
|
|
||||||
# Measuring distances from segment waypoints and (x, y)
|
# Measuring distances from segment waypoints and (x, y)
|
||||||
for segment in self.topology:
|
for segment in self._topology:
|
||||||
entryxy = segment['entry']
|
entryxy = segment['entry']
|
||||||
exitxy = segment['exit']
|
exitxy = segment['exit']
|
||||||
path = segment['path']
|
path = segment['path']
|
||||||
|
@ -122,13 +156,25 @@ class GlobalRoutePlanner(object):
|
||||||
|
|
||||||
def build_graph(self):
|
def build_graph(self):
|
||||||
"""
|
"""
|
||||||
This function builds a graph representation of topology
|
This function builds a networkx graph representation of topology.
|
||||||
|
The topology is read from self._topology.
|
||||||
|
graph node properties:
|
||||||
|
vertex - (x,y) of node's position in world map
|
||||||
|
graph edge properties:
|
||||||
|
entry_vector - unit vector along tangent at entry point
|
||||||
|
exit_vector - unit vector along tangent at exit point
|
||||||
|
net_vector - unit vector of the chord from entry to exit
|
||||||
|
intersection - boolean indicating if the edge belongs to an
|
||||||
|
intersection
|
||||||
|
|
||||||
|
return : graph -> networkx graph representing the world map,
|
||||||
|
id_map-> mapping from (x,y) to node id
|
||||||
"""
|
"""
|
||||||
graph = nx.DiGraph()
|
graph = nx.DiGraph()
|
||||||
# Map with structure {(x,y): id, ... }
|
# Map with structure {(x,y): id, ... }
|
||||||
id_map = dict()
|
id_map = dict()
|
||||||
|
|
||||||
for segment in self.topology:
|
for segment in self._topology:
|
||||||
|
|
||||||
entryxy = segment['entry']
|
entryxy = segment['entry']
|
||||||
exitxy = segment['exit']
|
exitxy = segment['exit']
|
||||||
|
@ -158,6 +204,11 @@ class GlobalRoutePlanner(object):
|
||||||
def distance(self, point1, point2):
|
def distance(self, point1, point2):
|
||||||
"""
|
"""
|
||||||
returns the distance between point1 and point2
|
returns the distance between point1 and point2
|
||||||
|
|
||||||
|
point1 : (x,y) of first point
|
||||||
|
point2 : (x,y) of second point
|
||||||
|
|
||||||
|
return : distance from point1 to point2
|
||||||
"""
|
"""
|
||||||
x1, y1 = point1
|
x1, y1 = point1
|
||||||
x2, y2 = point2
|
x2, y2 = point2
|
||||||
|
@ -166,6 +217,12 @@ class GlobalRoutePlanner(object):
|
||||||
def unit_vector(self, point1, point2):
|
def unit_vector(self, point1, point2):
|
||||||
"""
|
"""
|
||||||
This function returns the unit vector from point1 to point2
|
This function returns the unit vector from point1 to point2
|
||||||
|
|
||||||
|
point1 : (x,y) of first point
|
||||||
|
point2 : (x,y) of second point
|
||||||
|
|
||||||
|
return : tuple containing x and y components of unit vector
|
||||||
|
from point1 to point2
|
||||||
"""
|
"""
|
||||||
x1, y1 = point1
|
x1, y1 = point1
|
||||||
x2, y2 = point2
|
x2, y2 = point2
|
||||||
|
@ -179,6 +236,11 @@ class GlobalRoutePlanner(object):
|
||||||
def dot(self, vector1, vector2):
|
def dot(self, vector1, vector2):
|
||||||
"""
|
"""
|
||||||
This function returns the dot product of vector1 with vector2
|
This function returns the dot product of vector1 with vector2
|
||||||
|
|
||||||
|
vector1 : x, y components of first vector
|
||||||
|
vector2 : x, y components of second vector
|
||||||
|
|
||||||
|
return : dot porduct scalar between vector1 and vector2
|
||||||
"""
|
"""
|
||||||
return vector1[0] * vector2[0] + vector1[1] * vector2[1]
|
return vector1[0] * vector2[0] + vector1[1] * vector2[1]
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,31 @@ class GlobalRoutePlannerDAO(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, wmap):
|
def __init__(self, wmap):
|
||||||
""" Constructor """
|
"""
|
||||||
self.wmap = wmap # Carla world map
|
Constructor
|
||||||
|
|
||||||
|
wmap : carl world map object
|
||||||
|
"""
|
||||||
|
self._wmap = wmap
|
||||||
|
|
||||||
def get_topology(self):
|
def get_topology(self):
|
||||||
""" Accessor for topology """
|
"""
|
||||||
|
Accessor for topology.
|
||||||
|
This function retrieves topology from the server as a list of
|
||||||
|
road segments as pairs of waypoint objects, and processes the
|
||||||
|
topology into a list of dictionary objects.
|
||||||
|
|
||||||
|
return: list of dictionary objects with the following attributes
|
||||||
|
entry - (x,y) of entry point of road segment
|
||||||
|
exit - (x,y) of exit point of road segment
|
||||||
|
path - list of waypoints separated by 1m from entry
|
||||||
|
to exit
|
||||||
|
intersection - Boolean indicating if the road segment
|
||||||
|
is an intersection
|
||||||
|
"""
|
||||||
topology = []
|
topology = []
|
||||||
# Retrieving waypoints to construct a detailed topology
|
# Retrieving waypoints to construct a detailed topology
|
||||||
for segment in self.wmap.get_topology():
|
for segment in self._wmap.get_topology():
|
||||||
x1 = segment[0].transform.location.x
|
x1 = segment[0].transform.location.x
|
||||||
y1 = segment[0].transform.location.y
|
y1 = segment[0].transform.location.y
|
||||||
x2 = segment[1].transform.location.x
|
x2 = segment[1].transform.location.x
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import math
|
import math
|
||||||
import unittest
|
import unittest
|
||||||
from mock import Mock
|
|
||||||
import carla
|
import carla
|
||||||
|
|
||||||
from global_route_planner import GlobalRoutePlanner
|
from global_route_planner import GlobalRoutePlanner
|
||||||
|
from global_route_planner import NavEnum
|
||||||
from global_route_planner_dao import GlobalRoutePlannerDAO
|
from global_route_planner_dao import GlobalRoutePlannerDAO
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +38,8 @@ class Test_GlobalRoutePlanner(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
plan = self.integ_grp.plan_route((-60, -5), (-77.65, 72.72))
|
plan = self.integ_grp.plan_route((-60, -5), (-77.65, 72.72))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
plan, ['START', 'LEFT', 'LEFT', 'GO_STRAIGHT', 'LEFT', 'STOP'])
|
plan, [NavEnum.START, NavEnum.LEFT, NavEnum.LEFT,
|
||||||
|
NavEnum.GO_STRAIGHT, NavEnum.LEFT, NavEnum.STOP])
|
||||||
|
|
||||||
def test_path_search(self):
|
def test_path_search(self):
|
||||||
"""
|
"""
|
||||||
|
@ -55,8 +58,8 @@ class Test_GlobalRoutePlanner(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
x, y = (200, -250)
|
x, y = (200, -250)
|
||||||
segment = self.integ_grp.localise(x, y)
|
segment = self.integ_grp.localise(x, y)
|
||||||
self.assertEqual(self.integ_grp.id_map[segment['entry']], 5)
|
self.assertEqual(self.integ_grp._id_map[segment['entry']], 5)
|
||||||
self.assertEqual(self.integ_grp.id_map[segment['exit']], 225)
|
self.assertEqual(self.integ_grp._id_map[segment['exit']], 225)
|
||||||
|
|
||||||
def test_unit_vector(self):
|
def test_unit_vector(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue