team-3/src/c3nav/routing/router.py
2017-11-27 13:21:53 +01:00

232 lines
8.5 KiB
Python

import os
import pickle
from collections import deque
import numpy as np
from django.conf import settings
from django.utils.functional import cached_property
from shapely import prepared
from shapely.geometry import Point
from shapely.ops import unary_union
from c3nav.mapdata.models import AltitudeArea, GraphEdge, Level, WayType
class Router:
filename = os.path.join(settings.CACHE_ROOT, 'router')
def __init__(self, levels, spaces, areas, nodes, waytypes, graph):
self.levels = levels
self.spaces = spaces
self.areas = areas
self.nodes = nodes
self.waytypes = waytypes
self.graph = graph
@staticmethod
def get_altitude_in_areas(areas, point):
return max(area.get_altitudes(point)[0] for area in areas if area.geometry_prep.intersects(point))
@classmethod
def build(cls):
levels_query = Level.objects.prefetch_related('buildings', 'spaces', 'altitudeareas',
'spaces__holes', 'spaces__columns',
'spaces__obstacles', 'spaces__lineobstacles',
'spaces__areas', 'spaces__graphnodes')
levels = {}
spaces = {}
areas = {}
nodes = deque()
for level in levels_query:
buildings_geom = unary_union(tuple(building.geometry for building in level.buildings.all()))
nodes_before_count = len(nodes)
for space in level.spaces.all():
# create space geometries
accessible_geom = space.geometry.difference(unary_union(
tuple(column.geometry for column in space.columns.all()) +
tuple(hole.geometry for hole in space.holes.all()) +
((buildings_geom, ) if space.outside else ())
))
obstacles_geom = unary_union( # noqa
tuple(obstacle.geometry for obstacle in space.obstacles.all()) +
tuple(lineobstacle.buffered_geometry for lineobstacle in space.lineobstacles.all())
)
# todo: do something with this, then remove #noqa
space_nodes = tuple(RouterNode.from_graph_node(node) for node in space.graphnodes.all())
for i, node in enumerate(space_nodes, start=len(nodes)):
node.i = i
nodes.extend(space_nodes)
for area in space.areas.all():
area = RouterArea(area)
area_nodes = tuple(node for node in space_nodes if area.geometry_prep.intersects(node.point))
area.nodes = set(node.i for node in area_nodes)
for node in area_nodes:
node.areas.add(area.pk)
areas[area.pk] = area
space._prefetched_objects_cache = {}
space = RouterSpace(space)
space.nodes = set(node.i for node in space_nodes)
for area in level.altitudeareas.all():
if not space.geometry_prep.intersects(area.geometry):
continue
area = RouterAltitudeArea(accessible_geom.intersection(area.geometry),
area.altitude, area.altitude2, area.point1, area.point2)
area_nodes = tuple(node for node in space_nodes if area.geometry_prep.intersects(node.point))
area.nodes = set(node.i for node in area_nodes)
for node in area_nodes:
altitude = area.get_altitude(node)
if node.altitude is None or node.altitude < altitude:
node.altitude = altitude
space.altitudeareas.append(area)
spaces[space.pk] = space
level_spaces = set(space.pk for space in level.spaces.all())
level._prefetched_objects_cache = {}
level = RouterLevel(level, spaces=level_spaces)
level.nodes = set(range(nodes_before_count, len(nodes)))
levels[level.pk] = level
# waytypes
waytypes = deque([RouterWayType(None)])
waytypes_lookup = {None: 0}
for i, waytype in enumerate(WayType.objects.all(), start=1):
waytypes.append(RouterWayType(waytype))
waytypes_lookup[waytype.pk] = i
waytypes = tuple(waytypes)
# collect nodes
nodes = tuple(nodes)
nodes_lookup = {node.pk: node.i for node in nodes}
nodes_coords = np.array(tuple((node.x*100, node.y*100) for node in nodes), dtype=np.uint32) # noqa
# todo: remove #noqa when we're ready
# collect edges
edges = tuple(RouterEdge(from_node=nodes[nodes_lookup[edge.from_node_id]],
to_node=nodes[nodes_lookup[edge.to_node_id]],
waytype=waytypes_lookup[edge.waytype_id]) for edge in GraphEdge.objects.all())
edges_lookup = {(edge.from_node.i, edge.to_node.i): edge for edge in edges} # noqa
# todo: remove #noqa when we're ready
# build graph matrix
graph = np.full(shape=(len(nodes), len(nodes)), fill_value=np.inf, dtype=np.float32)
for edge in edges:
index = (edge.from_node.i, edge.to_node.i)
graph[index] = edge.distance
waytype = waytypes[edge.waytype]
(waytype.upwards_indices if edge.rise > 0 else waytype.nonupwards_indices).append(index)
# finalize waytype matrixes
for waytype in waytypes:
waytype.upwards_indices = np.array(waytype.upwards_indices, dtype=np.uint32).reshape((-1, 2))
waytype.nonupwards_indices = np.array(waytype.nonupwards_indices, dtype=np.uint32).reshape((-1, 2))
router = cls(levels, spaces, areas, nodes, waytypes, graph)
pickle.dump(router, open(cls.filename, 'wb'))
return router
@classmethod
def load(cls):
return pickle.load(open(cls.filename, 'rb'))
class BaseRouterProxy:
def __init__(self, src):
self.src = src
self.nodes = set()
@cached_property
def geometry_prep(self):
return prepared.prep(self.src.geometry)
def __getstate__(self):
result = self.__dict__.copy()
result.pop('geometry_prep', None)
return result
def __getattr__(self, name):
return getattr(self.src, name)
class RouterLevel(BaseRouterProxy):
def __init__(self, level, spaces=None):
super().__init__(level)
self.spaces = spaces if spaces else set()
class RouterSpace(BaseRouterProxy):
def __init__(self, space, altitudeareas=None):
super().__init__(space)
self.altitudeareas = altitudeareas if altitudeareas else []
class RouterArea(BaseRouterProxy):
pass
class RouterAltitudeArea:
def __init__(self, geometry, altitude, altitude2, point1, point2):
self.geometry = geometry
self.altitude = altitude
self.altitude2 = altitude2
self.point1 = point1
self.point2 = point2
@cached_property
def geometry_prep(self):
return prepared.prep(self.geometry)
def get_altitude(self, point):
# noinspection PyTypeChecker,PyCallByClass
return AltitudeArea.get_altitudes(self, (point.x, point.y))[0]
def __getstate__(self):
result = self.__dict__.copy()
result.pop('geometry_prep', None)
return result
class RouterNode:
def __init__(self, pk, x, y, space, altitude=None, areas=None):
self.pk = pk
self.x = x
self.y = y
self.space = space
self.altitude = altitude
self.areas = areas if areas else set()
@classmethod
def from_graph_node(cls, node):
return cls(node.pk, node.geometry.x, node.geometry.y, node.space_id)
@cached_property
def point(self):
return Point(self.x, self.y)
@cached_property
def xy(self):
return np.array((self.x, self.y))
class RouterEdge:
def __init__(self, from_node, to_node, waytype, rise=None, distance=None):
self.from_node = from_node
self.to_node = to_node
self.waytype = waytype
self.rise = rise if rise is not None else (self.to_node.altitude - self.from_node.altitude)
self.distance = distance if distance is not None else np.linalg.norm(to_node.xy - from_node.xy)
class RouterWayType:
def __init__(self, waytype):
self.waytype = waytype
self.upwards_indices = deque()
self.nonupwards_indices = deque()