routing: implement build_routers() (with RoomRouter caching)

This commit is contained in:
Laura Klünder 2016-12-16 15:06:35 +01:00
parent 55cac31bbe
commit 3269394c42
4 changed files with 117 additions and 27 deletions

View file

@ -1,22 +1,26 @@
import os import os
import pickle import pickle
from collections import OrderedDict from collections import OrderedDict, namedtuple
import numpy as np import numpy as np
from django.conf import settings from django.conf import settings
from scipy.sparse.csgraph._shortest_path import shortest_path
from scipy.sparse.csgraph._tools import csgraph_from_dense
from c3nav.mapdata.models import Level from c3nav.mapdata.models import Level
from c3nav.mapdata.models.geometry import LevelConnector from c3nav.mapdata.models.geometry import LevelConnector
from c3nav.mapdata.models.locations import AreaLocation, Location, LocationGroup, PointLocation
from c3nav.routing.level import GraphLevel from c3nav.routing.level import GraphLevel
from c3nav.routing.point import GraphPoint from c3nav.routing.point import GraphPoint
class Graph: class Graph:
graph_cached = None graph_cached = None
graph_cached_date = None graph_cached_mtime = None
default_filename = os.path.join(settings.DATA_DIR, 'graph.pickle') default_filename = os.path.join(settings.DATA_DIR, 'graph.pickle')
def __init__(self): def __init__(self, mtime=None):
self.mtime = mtime
self.levels = OrderedDict() self.levels = OrderedDict()
for level in Level.objects.all(): for level in Level.objects.all():
self.levels[level.name] = GraphLevel(self, level) self.levels[level.name] = GraphLevel(self, level)
@ -49,7 +53,7 @@ class Graph:
for i, point in enumerate(self.points): for i, point in enumerate(self.points):
point.i = i point.i = i
self.level_transfer_points = np.array(tuple(point.i for point in self._built_level_transfer_points)) self.level_transfer_points = tuple(point.i for point in self._built_level_transfer_points)
for level in self.levels.values(): for level in self.levels.values():
level.finish_build() level.finish_build()
@ -117,10 +121,10 @@ class Graph:
pickle.dump(self.serialize(), f) pickle.dump(self.serialize(), f)
@classmethod @classmethod
def unserialize(cls, data): def unserialize(cls, data, mtime):
levels, points, level_transfer_points = data levels, points, level_transfer_points = data
graph = cls() graph = cls(mtime=mtime)
for name, level in levels.items(): for name, level in levels.items():
graph.levels[name].unserialize(level) graph.levels[name].unserialize(level)
@ -130,6 +134,12 @@ class Graph:
graph.points = tuple(GraphPoint(x, y, None if room is None else rooms[room]) for x, y, room in points) graph.points = tuple(GraphPoint(x, y, None if room is None else rooms[room]) for x, y, room in points)
graph.level_transfer_points = level_transfer_points graph.level_transfer_points = level_transfer_points
for i, room in enumerate(rooms):
room.i = i
for i, point in enumerate(graph.points):
point.i = i
return graph return graph
@classmethod @classmethod
@ -143,16 +153,15 @@ class Graph:
if do_cache: if do_cache:
graph_mtime = os.path.getmtime(filename) graph_mtime = os.path.getmtime(filename)
if cls.graph_cached is not None: if cls.graph_cached is not None:
if cls.graph_cached_date == graph_mtime: if cls.graph_cached_mtime == graph_mtime:
return cls.graph_cached return cls.graph_cached
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
graph = cls.unserialize(pickle.load(f)) graph = cls.unserialize(pickle.load(f), graph_mtime)
if do_cache: if do_cache:
cls.graph_cached_date = graph_mtime cls.graph_cached_mtime = graph_mtime
cls.graph_cached = graph cls.graph_cached = graph
print(cls.graph_cached, cls.graph_cached_date)
graph.print_stats() graph.print_stats()
return graph return graph
@ -163,6 +172,38 @@ class Graph:
level.draw_png(points, lines) level.draw_png(points, lines)
# Router # Router
def build_router(self): def build_routers(self):
for level in self.levels.values(): level_routers = {}
level.build_router() room_routers = {}
empty_distances = np.empty(shape=(len(self.level_transfer_points),) * 2, dtype=np.float16)
empty_distances[:] = np.inf
sparse_distances = empty_distances.copy()
sparse_levels = np.zeros(shape=(len(self.level_transfer_points),) * 2, dtype=np.int16)
sparse_levels[:] = -1
for i, level in enumerate(self.levels.values()):
router, add_room_routers = level.build_routers()
level_routers[level] = router
room_routers.update(add_room_routers)
level_distances = empty_distances.copy()
in_level_i = np.array(tuple(level.room_transfer_points.index(point)
for point in level.level_transfer_points))
in_graph_i = np.array(tuple(self.level_transfer_points.index(point)
for point in level.level_transfer_points))
level_distances[in_graph_i[:, None], in_graph_i] = router.shortest_paths[in_level_i[:, None], in_level_i]
better = level_distances < sparse_distances
sparse_distances[better.transpose()] = level_distances[better.transpose()]
sparse_levels[better.transpose()] = i
g_sparse = csgraph_from_dense(sparse_distances, null_value=np.inf)
shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True)
return GraphRouter(shortest_paths, predecessors), level_routers, room_routers
GraphRouter = namedtuple('GraphRouter', ('shortest_paths', 'predecessors', ))

View file

@ -1,8 +1,11 @@
import os import os
from collections import namedtuple
import numpy as np import numpy as np
from django.conf import settings from django.conf import settings
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from scipy.sparse.csgraph._shortest_path import shortest_path
from scipy.sparse.csgraph._tools import csgraph_from_dense
from shapely.geometry import JOIN_STYLE from shapely.geometry import JOIN_STYLE
from c3nav.mapdata.utils.geometry import assert_multipolygon from c3nav.mapdata.utils.geometry import assert_multipolygon
@ -134,9 +137,9 @@ class GraphLevel():
def finish_build(self): def finish_build(self):
self.rooms = tuple(self.rooms) self.rooms = tuple(self.rooms)
self.points = np.array(tuple(point.i for point in self._built_points)) self.points = tuple(point.i for point in self._built_points)
self.room_transfer_points = np.array(tuple(point.i for point in self._built_room_transfer_points)) self.room_transfer_points = tuple(point.i for point in self._built_room_transfer_points)
self.level_transfer_points = np.array(tuple(i for i in self.points if i in self.graph.level_transfer_points)) self.level_transfer_points = tuple(i for i in self.points if i in self.graph.level_transfer_points)
self.collect_arealocation_points() self.collect_arealocation_points()
@ -148,9 +151,9 @@ class GraphLevel():
for name, mpl_arealocation in self._built_arealocations.items(): for name, mpl_arealocation in self._built_arealocations.items():
rooms = [room for room in self.rooms rooms = [room for room in self.rooms
if room.mpl_clear.intersects_path(mpl_arealocation.exterior, filled=True)] if room.mpl_clear.intersects_path(mpl_arealocation.exterior, filled=True)]
possible_points = set(sum((room._built_points for room in rooms), [])) possible_points = tuple(point for point in sum((room._built_points for room in rooms), []) if point.room)
self.arealocation_points[name] = np.array(tuple(point.i for point in possible_points self.arealocation_points[name] = tuple(point.i for point in possible_points
if mpl_arealocation.contains_point(point.xy))) if mpl_arealocation.contains_point(point.xy))
# Drawing # Drawing
def draw_png(self, points=True, lines=True): def draw_png(self, points=True, lines=True):
@ -191,6 +194,35 @@ class GraphLevel():
im.save(graph_filename) im.save(graph_filename)
# Routing # Routing
def build_router(self): def build_routers(self):
for room in self.rooms: room_routers = {}
room.build_router()
empty_distances = np.empty(shape=(len(self.room_transfer_points),) * 2, dtype=np.float16)
empty_distances[:] = np.inf
sparse_distances = empty_distances.copy()
room_transfers = np.zeros(shape=(len(self.room_transfer_points),) * 2, dtype=np.int16)
room_transfers[:] = -1
for i, room in enumerate(self.rooms):
router = room.build_router()
room_routers[room] = router
room_distances = empty_distances.copy()
in_room_i = np.array(tuple(room.points.index(point) for point in room.room_transfer_points))
in_level_i = np.array(tuple(self.room_transfer_points.index(point)
for point in room.room_transfer_points))
room_distances[in_level_i[:, None], in_level_i] = router.shortest_paths[in_room_i[:, None], in_room_i]
better = room_distances < sparse_distances
sparse_distances[better.transpose()] = room_distances[better.transpose()]
room_transfers[better.transpose()] = i
g_sparse = csgraph_from_dense(sparse_distances, null_value=np.inf)
shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True)
return LevelRouter(shortest_paths, predecessors, room_transfers), room_routers
LevelRouter = namedtuple('LevelRouter', ('shortest_paths', 'predecessors', 'rooms_transfers', ))

View file

@ -14,5 +14,9 @@ class Command(BaseCommand):
print('Graph loaded in %.4fs' % (time.time() - start)) print('Graph loaded in %.4fs' % (time.time() - start))
start = time.time() start = time.time()
graph.build_router() graph.build_routers()
print('Routers built in %.4fs' % (time.time() - start)) print('Routers built in %.4fs' % (time.time() - start))
start = time.time()
graph.build_routers()
print('Routers built (2nd time, cached) in %.4fs' % (time.time() - start))

View file

@ -1,4 +1,7 @@
from collections import namedtuple
import numpy as np import numpy as np
from django.core.cache import cache
from matplotlib.path import Path from matplotlib.path import Path
from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._shortest_path import shortest_path
from scipy.sparse.csgraph._tools import csgraph_from_dense from scipy.sparse.csgraph._tools import csgraph_from_dense
@ -187,8 +190,8 @@ class GraphRoom():
def finish_build(self): def finish_build(self):
self.areas = tuple(self.areas) self.areas = tuple(self.areas)
self.points = np.array(tuple(point.i for point in self._built_points)) self.points = tuple(point.i for point in self._built_points)
self.room_transfer_points = np.array(tuple(i for i in self.points if i in self.level.room_transfer_points)) self.room_transfer_points = tuple(i for i in self.points if i in self.level.room_transfer_points)
mapping = {point.i: i for i, point in enumerate(self._built_points)} mapping = {point.i: i for i, point in enumerate(self._built_points)}
self.distances = np.empty(shape=(len(self._built_points), len(self._built_points)), dtype=np.float16) self.distances = np.empty(shape=(len(self._built_points), len(self._built_points)), dtype=np.float16)
@ -203,7 +206,17 @@ class GraphRoom():
# Routing # Routing
def build_router(self): def build_router(self):
# noinspection PyTypeChecker cache_key = 'c3nav__graph__roomrouter__%s__%s' % (self.graph.mtime, self.i)
roomrouter = cache.get(cache_key)
if not roomrouter:
roomrouter = self._build_router()
cache.set(cache_key, roomrouter, 600)
return roomrouter
def _build_router(self):
g_sparse = csgraph_from_dense(self.distances, null_value=np.inf) g_sparse = csgraph_from_dense(self.distances, null_value=np.inf)
shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True) shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True)
return shortest_paths, predecessors return RoomRouter(shortest_paths, predecessors)
RoomRouter = namedtuple('RoomRouter', ('shortest_paths', 'predecessors', ))