add graph router

This commit is contained in:
Laura Klünder 2016-12-05 13:39:22 +01:00
parent 89ed7e25f7
commit 762cb6e317
8 changed files with 115 additions and 4 deletions

View file

@ -10,6 +10,7 @@ from c3nav.routing.graph.connection import GraphConnection
from c3nav.routing.graph.level import GraphLevel from c3nav.routing.graph.level import GraphLevel
from c3nav.routing.graph.point import GraphPoint from c3nav.routing.graph.point import GraphPoint
from c3nav.routing.graph.room import GraphRoom from c3nav.routing.graph.room import GraphRoom
from c3nav.routing.graph.router import Router
class Graph(): class Graph():
@ -25,6 +26,9 @@ class Graph():
self.rooms = [] self.rooms = []
self.levelconnector_points = {} self.levelconnector_points = {}
self.transfer_points = []
self.router = Router()
def build(self): def build(self):
for level in self.levels.values(): for level in self.levels.values():
level.build() level.build()
@ -88,9 +92,16 @@ class Graph():
graph = cls.unserialize(pickle.load(f)) graph = cls.unserialize(pickle.load(f))
return graph return graph
def draw_pngs(self, points=True, lines=True): def build_router(self):
for room in self.rooms:
room.build_router()
self.transfer_points.extend(room.router.transfer_points)
self.router.build(self.transfer_points, global_routing=True)
def draw_pngs(self, points=True, lines=True, transfer_points=False, transfer_lines=False):
for level in self.levels.values(): for level in self.levels.values():
level.draw_png(points=points, lines=lines) level.draw_png(points, lines, transfer_points, transfer_lines)
def add_levelconnector_point(self, levelconnector, point): def add_levelconnector_point(self, levelconnector, point):
self.levelconnector_points.setdefault(levelconnector.name, []).append(point) self.levelconnector_points.setdefault(levelconnector.name, []).append(point)

View file

@ -78,7 +78,7 @@ class GraphLevel():
print('%d points' % len(self.points)) print('%d points' % len(self.points))
print() print()
def draw_png(self, points=True, lines=True): def draw_png(self, points=True, lines=True, transfer_points=False, transfer_lines=False):
filename = os.path.join(settings.RENDER_ROOT, 'level-%s.base.png' % self.level.name) filename = os.path.join(settings.RENDER_ROOT, 'level-%s.base.png' % self.level.name)
graph_filename = os.path.join(settings.RENDER_ROOT, 'level-%s.graph.png' % self.level.name) graph_filename = os.path.join(settings.RENDER_ROOT, 'level-%s.graph.png' % self.level.name)
@ -94,4 +94,15 @@ class GraphLevel():
for point in self.points: for point in self.points:
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (200, 0, 0)) draw.ellipse(_ellipse_bbox(point.x, point.y, height), (200, 0, 0))
if transfer_lines:
for point in self.points:
if point.in_room_transfer_distances is not None:
for otherpoint, distance in point.in_room_transfer_distances.items():
draw.line(_line_coords(point, otherpoint, height), fill=(100, 100, 255))
if transfer_points:
for point in self.points:
if point.in_room_transfer_distances is not None:
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (0, 0, 200))
im.save(graph_filename) im.save(graph_filename)

View file

@ -10,6 +10,7 @@ class GraphPoint():
self.xy = (x, y) self.xy = (x, y)
self.connections = {} self.connections = {}
self.connections_in = {} self.connections_in = {}
self.in_room_transfer_distances = None
@cached_property @cached_property
def ellipse_bbox(self): def ellipse_bbox(self):

View file

@ -6,6 +6,7 @@ from shapely.geometry import JOIN_STYLE, LineString
from c3nav.mapdata.utils import assert_multipolygon from c3nav.mapdata.utils import assert_multipolygon
from c3nav.routing.graph.point import GraphPoint from c3nav.routing.graph.point import GraphPoint
from c3nav.routing.graph.router import Router
from c3nav.routing.utils.coords import get_coords_angles from c3nav.routing.utils.coords import get_coords_angles
from c3nav.routing.utils.mpl import polygon_to_mpl_paths from c3nav.routing.utils.mpl import polygon_to_mpl_paths
@ -19,6 +20,8 @@ class GraphRoom():
self.clear_geometry = geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre) self.clear_geometry = geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre)
self.empty = self.clear_geometry.is_empty self.empty = self.clear_geometry.is_empty
self.router = Router()
if mpl_paths is not None: if mpl_paths is not None:
self.mpl_paths = mpl_paths self.mpl_paths = mpl_paths
elif not self.empty: elif not self.empty:
@ -63,6 +66,10 @@ class GraphRoom():
for from_point, to_point in permutations(points, 2): for from_point, to_point in permutations(points, 2):
from_point.connect_to(to_point) from_point.connect_to(to_point)
# noinspection PyTypeChecker
def build_router(self):
self.router.build(self.points)
def _add_ring(self, geom, want_left): def _add_ring(self, geom, want_left):
""" """
add the points of a ring, but only those that have a specific direction change. add the points of a ring, but only those that have a specific direction change.

View file

@ -0,0 +1,52 @@
import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import shortest_path
class Router():
def __init__(self):
self.points = []
self.points_pk = None
self.transfer_points = set()
self.shortest_paths = None
self.predecessors = None
self._built = False
# noinspection PyTypeChecker
def build(self, points, global_routing=False):
if self._built:
raise RuntimeError('already built.')
self._built = True
self.points = points
self.points_pk = dict(zip(self.points, range(len(self.points))))
matrix = np.zeros((len(self.points), len(self.points)))
for point, pk in self.points_pk.items():
for to_point, connection in point.connections.items():
if to_point not in self.points_pk:
if not global_routing:
self.transfer_points.add(point)
continue
matrix[pk, self.points_pk[to_point]] = 1
if global_routing:
for to_point, distance in point.in_room_transfer_distances.items():
matrix[pk, self.points_pk[to_point]] = distance
g_sparse = csr_matrix(np.ma.masked_values(np.fromstring(matrix).reshape(matrix.shape), 0))
self.shortest_paths, self.predecessors = shortest_path(g_sparse, return_predecessors=True)
if not global_routing:
for from_point in self.transfer_points:
from_point.in_room_transfer_distances = {}
connections = self.shortest_paths[self.points_pk[from_point], ]
for to_point_pk in np.argwhere(connections != np.inf).flatten():
to_point = self.points[to_point_pk]
if to_point not in self.transfer_points:
continue
from_point.in_room_transfer_distances[to_point] = connections[to_point_pk]
def get_distance(self, from_point, to_point):
return self.shortest_paths[self.points_pk[from_point], self.points_pk[from_point]]

View file

@ -13,6 +13,15 @@ class Command(BaseCommand):
parser.add_argument('--no-lines', action='store_const', dest='lines', const=False, default=True, parser.add_argument('--no-lines', action='store_const', dest='lines', const=False, default=True,
help='dont draw lines on the graph image') help='dont draw lines on the graph image')
parser.add_argument('--transfer-points', action='store_const', const=True, default=False,
help='highlight transfer points')
parser.add_argument('--transfer-lines', action='store_const', const=True, default=False,
help='draw in-room transfer lines')
def handle(self, *args, **options): def handle(self, *args, **options):
graph = Graph.load() graph = Graph.load()
graph.draw_pngs(points=options['points'], lines=options['lines']) if options['transfer_points'] or options['transfer_lines']:
graph.build_router()
graph.draw_pngs(points=options['points'], lines=options['lines'],
transfer_points=options['transfer_points'], transfer_lines=options['transfer_lines'])

View file

@ -0,0 +1,19 @@
import time
from django.core.management.base import BaseCommand
from c3nav.routing.graph import Graph
class Command(BaseCommand):
help = 'check how long it takes to build the routers for the routing graph'
def handle(self, *args, **options):
start = time.time()
graph = Graph.load()
print('Graph loaded in %.4fs' % (time.time() - start))
start = time.time()
graph.build_router()
print('Routers built in %.4fs' % (time.time() - start))
print('%s transfer points' % len(graph.transfer_points))

View file

@ -9,3 +9,4 @@ celery>=3.1,<3.2
requests>=2.11,<2.12 requests>=2.11,<2.12
Pillow>=3.4.2,<3.5 Pillow>=3.4.2,<3.5
matplotlib>=1.5.3,<1.6 matplotlib>=1.5.3,<1.6
scipy>=0.18.1,<0.19