routing: implement build_routers() (with RoomRouter caching)
This commit is contained in:
parent
55cac31bbe
commit
3269394c42
4 changed files with 117 additions and 27 deletions
|
@ -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', ))
|
||||||
|
|
|
@ -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', ))
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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', ))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue