From 3269394c42bafb3af90893d3fce999a1f29871cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Fri, 16 Dec 2016 15:06:35 +0100 Subject: [PATCH] routing: implement build_routers() (with RoomRouter caching) --- src/c3nav/routing/graph.py | 67 +++++++++++++++---- src/c3nav/routing/level.py | 50 +++++++++++--- .../routing/management/commands/testgraph.py | 6 +- src/c3nav/routing/room.py | 21 ++++-- 4 files changed, 117 insertions(+), 27 deletions(-) diff --git a/src/c3nav/routing/graph.py b/src/c3nav/routing/graph.py index d2264165..170e56d5 100644 --- a/src/c3nav/routing/graph.py +++ b/src/c3nav/routing/graph.py @@ -1,22 +1,26 @@ import os import pickle -from collections import OrderedDict +from collections import OrderedDict, namedtuple import numpy as np 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.geometry import LevelConnector +from c3nav.mapdata.models.locations import AreaLocation, Location, LocationGroup, PointLocation from c3nav.routing.level import GraphLevel from c3nav.routing.point import GraphPoint class Graph: graph_cached = None - graph_cached_date = None + graph_cached_mtime = None default_filename = os.path.join(settings.DATA_DIR, 'graph.pickle') - def __init__(self): + def __init__(self, mtime=None): + self.mtime = mtime self.levels = OrderedDict() for level in Level.objects.all(): self.levels[level.name] = GraphLevel(self, level) @@ -49,7 +53,7 @@ class Graph: for i, point in enumerate(self.points): 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(): level.finish_build() @@ -117,10 +121,10 @@ class Graph: pickle.dump(self.serialize(), f) @classmethod - def unserialize(cls, data): + def unserialize(cls, data, mtime): levels, points, level_transfer_points = data - graph = cls() + graph = cls(mtime=mtime) for name, level in levels.items(): 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.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 @classmethod @@ -143,16 +153,15 @@ class Graph: if do_cache: graph_mtime = os.path.getmtime(filename) 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 with open(filename, 'rb') as f: - graph = cls.unserialize(pickle.load(f)) + graph = cls.unserialize(pickle.load(f), graph_mtime) if do_cache: - cls.graph_cached_date = graph_mtime + cls.graph_cached_mtime = graph_mtime cls.graph_cached = graph - print(cls.graph_cached, cls.graph_cached_date) graph.print_stats() return graph @@ -163,6 +172,38 @@ class Graph: level.draw_png(points, lines) # Router - def build_router(self): - for level in self.levels.values(): - level.build_router() + def build_routers(self): + level_routers = {} + 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', )) diff --git a/src/c3nav/routing/level.py b/src/c3nav/routing/level.py index 38a56f00..17e42808 100644 --- a/src/c3nav/routing/level.py +++ b/src/c3nav/routing/level.py @@ -1,8 +1,11 @@ import os +from collections import namedtuple import numpy as np from django.conf import settings 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 c3nav.mapdata.utils.geometry import assert_multipolygon @@ -134,9 +137,9 @@ class GraphLevel(): def finish_build(self): self.rooms = tuple(self.rooms) - self.points = np.array(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.level_transfer_points = np.array(tuple(i for i in self.points if i in self.graph.level_transfer_points)) + self.points = tuple(point.i for point in self._built_points) + self.room_transfer_points = tuple(point.i for point in self._built_room_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() @@ -148,9 +151,9 @@ class GraphLevel(): for name, mpl_arealocation in self._built_arealocations.items(): rooms = [room for room in self.rooms if room.mpl_clear.intersects_path(mpl_arealocation.exterior, filled=True)] - possible_points = set(sum((room._built_points for room in rooms), [])) - self.arealocation_points[name] = np.array(tuple(point.i for point in possible_points - if mpl_arealocation.contains_point(point.xy))) + possible_points = tuple(point for point in sum((room._built_points for room in rooms), []) if point.room) + self.arealocation_points[name] = tuple(point.i for point in possible_points + if mpl_arealocation.contains_point(point.xy)) # Drawing def draw_png(self, points=True, lines=True): @@ -191,6 +194,35 @@ class GraphLevel(): im.save(graph_filename) # Routing - def build_router(self): - for room in self.rooms: - room.build_router() + def build_routers(self): + room_routers = {} + + 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', )) diff --git a/src/c3nav/routing/management/commands/testgraph.py b/src/c3nav/routing/management/commands/testgraph.py index ad71d09c..84120783 100644 --- a/src/c3nav/routing/management/commands/testgraph.py +++ b/src/c3nav/routing/management/commands/testgraph.py @@ -14,5 +14,9 @@ class Command(BaseCommand): print('Graph loaded in %.4fs' % (time.time() - start)) start = time.time() - graph.build_router() + graph.build_routers() 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)) diff --git a/src/c3nav/routing/room.py b/src/c3nav/routing/room.py index 43c11e79..7aac300d 100644 --- a/src/c3nav/routing/room.py +++ b/src/c3nav/routing/room.py @@ -1,4 +1,7 @@ +from collections import namedtuple + import numpy as np +from django.core.cache import cache from matplotlib.path import Path from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._tools import csgraph_from_dense @@ -187,8 +190,8 @@ class GraphRoom(): def finish_build(self): self.areas = tuple(self.areas) - self.points = np.array(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.points = tuple(point.i for point in self._built_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)} self.distances = np.empty(shape=(len(self._built_points), len(self._built_points)), dtype=np.float16) @@ -203,7 +206,17 @@ class GraphRoom(): # Routing 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) 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', ))