start implementing routing by creating new router
This commit is contained in:
parent
587b08a1e4
commit
dd3a885749
14 changed files with 219 additions and 1735 deletions
|
@ -162,7 +162,7 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
|
||||||
|
|
||||||
slope = np.array(self.point2) - np.array(self.point1)
|
slope = np.array(self.point2) - np.array(self.point1)
|
||||||
distances = (np.sum(((points - np.array(self.point1)) * slope), axis=1) / (slope ** 2).sum()).clip(0, 1)
|
distances = (np.sum(((points - np.array(self.point1)) * slope), axis=1) / (slope ** 2).sum()).clip(0, 1)
|
||||||
return self.altitude + distances*(self.altitude2-self.altitude)
|
return float(self.altitude) + distances*(float(self.altitude2)-float(self.altitude))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def recalculate(cls):
|
def recalculate(cls):
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
from itertools import combinations
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from matplotlib.path import Path
|
|
||||||
|
|
||||||
from c3nav.routing.utils.coords import coord_angle
|
|
||||||
|
|
||||||
|
|
||||||
class GraphArea():
|
|
||||||
def __init__(self, room, mpl_clear, mpl_stairs, escalators, points=None):
|
|
||||||
self.room = room
|
|
||||||
self.graph = room.graph
|
|
||||||
|
|
||||||
self.mpl_clear = mpl_clear
|
|
||||||
self.mpl_stairs = mpl_stairs
|
|
||||||
self.escalators = escalators
|
|
||||||
|
|
||||||
self.points = points
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
return (
|
|
||||||
self.mpl_clear,
|
|
||||||
self.mpl_stairs,
|
|
||||||
self.escalators,
|
|
||||||
self.points,
|
|
||||||
)
|
|
||||||
|
|
||||||
def prepare_build(self):
|
|
||||||
self._built_points = []
|
|
||||||
|
|
||||||
def build_connections(self):
|
|
||||||
for point1, point2 in combinations(self._built_points, 2):
|
|
||||||
|
|
||||||
there, back, distance = self.check_connection(point1.xy, point2.xy)
|
|
||||||
|
|
||||||
if there is not None:
|
|
||||||
point1.connect_to(point2, distance=distance, ctype=there)
|
|
||||||
|
|
||||||
if back is not None:
|
|
||||||
point2.connect_to(point1, distance=distance, ctype=back)
|
|
||||||
|
|
||||||
def check_connection(self, point1, point2):
|
|
||||||
path = Path(np.vstack((point1, point2)))
|
|
||||||
distance = abs(np.linalg.norm(point1 - point2))
|
|
||||||
|
|
||||||
# lies within room
|
|
||||||
if self.mpl_clear.intersects_path(path):
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
if self.room.stuffedareas.intersects_path(path, filled=True):
|
|
||||||
distance *= 2.5
|
|
||||||
|
|
||||||
# stair checker
|
|
||||||
angle = coord_angle(point1, point2)
|
|
||||||
stair_direction_up = None
|
|
||||||
for stair_path, stair_angle in self.mpl_stairs:
|
|
||||||
if not path.intersects_path(stair_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
angle_diff = ((stair_angle - angle + 180) % 360) - 180
|
|
||||||
|
|
||||||
new_direction_up = (angle_diff > 0)
|
|
||||||
if stair_direction_up is None:
|
|
||||||
stair_direction_up = new_direction_up
|
|
||||||
elif stair_direction_up != new_direction_up:
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
if not (40 < abs(angle_diff) < 150):
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
# escalator checker
|
|
||||||
angle = coord_angle(point1, point2)
|
|
||||||
escalator_direction_up = None
|
|
||||||
escalator_swap_direction = False
|
|
||||||
for escalator in self.escalators:
|
|
||||||
if not escalator.mpl_geom.intersects_path(path, filled=True):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if escalator_direction_up is not None:
|
|
||||||
# only one escalator per connection
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
angle_diff = ((escalator.angle - angle + 180) % 360) - 180
|
|
||||||
|
|
||||||
escalator_direction_up = (angle_diff > 0)
|
|
||||||
escalator_swap_direction = (escalator_direction_up != escalator.direction_up)
|
|
||||||
|
|
||||||
if stair_direction_up is not None:
|
|
||||||
return (
|
|
||||||
('stairs_up' if stair_direction_up else 'stairs_down'),
|
|
||||||
('stairs_down' if stair_direction_up else 'stairs_up'),
|
|
||||||
distance,
|
|
||||||
)
|
|
||||||
elif escalator_direction_up is not None:
|
|
||||||
if not escalator_swap_direction:
|
|
||||||
return ('escalator_up' if escalator_direction_up else 'escalator_down'), None, distance
|
|
||||||
else:
|
|
||||||
return None, ('escalator_down' if escalator_direction_up else 'escalator_up'), distance
|
|
||||||
|
|
||||||
return '', '', distance
|
|
||||||
|
|
||||||
def add_point(self, point):
|
|
||||||
if not self.mpl_clear.contains_point(point.xy):
|
|
||||||
return False
|
|
||||||
self._built_points.append(point)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def finish_build(self):
|
|
||||||
self.points = np.array(tuple(point.i for point in self._built_points))
|
|
||||||
|
|
||||||
set_points = set(self.points)
|
|
||||||
if len(self.points) != len(set_points):
|
|
||||||
print('ERROR: POINTS DOUBLE-ADDED (AREA)', len(self.points), len(set_points))
|
|
||||||
|
|
||||||
def contains_point(self, point):
|
|
||||||
return self.mpl_clear.contains_point(point)
|
|
||||||
|
|
||||||
def connected_points(self, point, mode):
|
|
||||||
connections = {}
|
|
||||||
for point_i in self.points:
|
|
||||||
other_point = self.graph.points[point_i]
|
|
||||||
|
|
||||||
there, back, distance = self.check_connection(point, other_point.xy)
|
|
||||||
ctype = there if mode == 'orig' else back
|
|
||||||
if ctype is not None:
|
|
||||||
connections[point_i] = (distance, ctype)
|
|
||||||
return connections
|
|
|
@ -1,20 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
from django.utils.functional import cached_property
|
|
||||||
|
|
||||||
from c3nav.routing.utils.coords import coord_angle
|
|
||||||
|
|
||||||
|
|
||||||
class GraphConnection():
|
|
||||||
def __init__(self, from_point, to_point, distance=None, ctype=''):
|
|
||||||
self.from_point = from_point
|
|
||||||
self.to_point = to_point
|
|
||||||
self.distance = distance if distance is not None else abs(np.linalg.norm(from_point.xy - to_point.xy))
|
|
||||||
self.ctype = ctype
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def angle(self):
|
|
||||||
return coord_angle(self.from_point.xy, self.to_point.xy)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<GraphConnection %r %r distance=%f ctype=%s>' %
|
|
||||||
(self.from_point, self.to_point, self.distance, self.ctype))
|
|
|
@ -1,543 +0,0 @@
|
||||||
# flake8: noqa
|
|
||||||
import os
|
|
||||||
import pickle
|
|
||||||
from collections import OrderedDict, namedtuple
|
|
||||||
from itertools import combinations
|
|
||||||
|
|
||||||
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.level import Level
|
|
||||||
from c3nav.mapdata.models.locations import Location, LocationGroup
|
|
||||||
from c3nav.routing.connection import GraphConnection
|
|
||||||
from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable
|
|
||||||
from c3nav.routing.level import GraphLevel
|
|
||||||
from c3nav.routing.point import GraphPoint
|
|
||||||
from c3nav.routing.route import NoRoute, Route
|
|
||||||
from c3nav.routing.routesegments import (GraphRouteSegment, LevelRouteSegment, RoomRouteSegment, SegmentRoute,
|
|
||||||
SegmentRouteWrapper)
|
|
||||||
|
|
||||||
|
|
||||||
class Graph:
|
|
||||||
graph_cached = None
|
|
||||||
graph_cached_mtime = None
|
|
||||||
default_filename = os.path.join(settings.DATA_DIR, 'graph.pickle')
|
|
||||||
|
|
||||||
def __init__(self, mtime=None):
|
|
||||||
self.mtime = mtime
|
|
||||||
self.levels = OrderedDict()
|
|
||||||
for level in Level.objects.all():
|
|
||||||
self.levels[level.name] = GraphLevel(self, level)
|
|
||||||
|
|
||||||
self.points = []
|
|
||||||
self.level_transfer_points = None
|
|
||||||
self.elevatorlevel_points = None
|
|
||||||
|
|
||||||
# Building the Graph
|
|
||||||
def build(self):
|
|
||||||
self._built_level_transfer_points = []
|
|
||||||
self._built_levelconnector_points = {}
|
|
||||||
|
|
||||||
self._built_elevatorlevel_points = {}
|
|
||||||
|
|
||||||
for level in self.levels.values():
|
|
||||||
level.build()
|
|
||||||
|
|
||||||
# collect rooms and points
|
|
||||||
rooms = sum((level.rooms for level in self.levels.values()), [])
|
|
||||||
self.points = sum((level._built_points for level in self.levels.values()), [])
|
|
||||||
|
|
||||||
# create connections between levels
|
|
||||||
print()
|
|
||||||
self.connect_levelconnectors()
|
|
||||||
self.connect_elevators()
|
|
||||||
|
|
||||||
# finishing build: creating numpy arrays and convert everything else to tuples
|
|
||||||
self.points = tuple(set(self.points))
|
|
||||||
|
|
||||||
for i, room in enumerate(rooms):
|
|
||||||
room.i = i
|
|
||||||
|
|
||||||
for i, point in enumerate(self.points):
|
|
||||||
point.i = i
|
|
||||||
|
|
||||||
self.level_transfer_points = tuple(point.i for point in self._built_level_transfer_points)
|
|
||||||
|
|
||||||
for level in self.levels.values():
|
|
||||||
level.finish_build()
|
|
||||||
|
|
||||||
print()
|
|
||||||
print('Total:')
|
|
||||||
self.print_stats()
|
|
||||||
|
|
||||||
print()
|
|
||||||
print('Points per room:')
|
|
||||||
for name, level in self.levels.items():
|
|
||||||
print(('Level %s:' % name), *(sorted((len(room.points) for room in level.rooms), reverse=True)))
|
|
||||||
|
|
||||||
def print_stats(self):
|
|
||||||
print('%d points' % len(self.points))
|
|
||||||
print('%d rooms' % sum(len(level.rooms) for level in self.levels.values()))
|
|
||||||
print('%d level transfer points' % len(self.level_transfer_points))
|
|
||||||
print('%d connections' % sum(level.connection_count() for level in self.levels.values()))
|
|
||||||
|
|
||||||
def add_levelconnector_point(self, levelconnector, point):
|
|
||||||
self._built_levelconnector_points.setdefault(levelconnector.name, []).append(point)
|
|
||||||
|
|
||||||
def add_elevatorlevel_point(self, elevatorlevel, point):
|
|
||||||
self._built_elevatorlevel_points[elevatorlevel.name] = point
|
|
||||||
|
|
||||||
def connect_levelconnectors(self):
|
|
||||||
from c3nav.mapdata.models.geometry import LevelConnector
|
|
||||||
for levelconnector in LevelConnector.objects.all():
|
|
||||||
center = levelconnector.geometry.centroid
|
|
||||||
points = self._built_levelconnector_points.get(levelconnector.name, [])
|
|
||||||
rooms = set(point.room for point in points if point.room is not None)
|
|
||||||
connected_levels = set(room.level for room in rooms)
|
|
||||||
|
|
||||||
should_levels = tuple(level.name for level in levelconnector.levels.all())
|
|
||||||
missing_levels = set(should_levels) - set(level.level.name for level in connected_levels)
|
|
||||||
|
|
||||||
if missing_levels:
|
|
||||||
print('levelconnector %s on levels %s at (%.2f, %.2f) is not connected to levels %s!' %
|
|
||||||
(levelconnector.name, ', '.join(should_levels), center.x, center.y, ', '.join(missing_levels)))
|
|
||||||
continue
|
|
||||||
|
|
||||||
center_point = GraphPoint(center.x, center.y, None)
|
|
||||||
self.points.append(center_point)
|
|
||||||
self._built_level_transfer_points.append(center_point)
|
|
||||||
|
|
||||||
for level in connected_levels:
|
|
||||||
level._built_room_transfer_points.append(center_point)
|
|
||||||
level._built_points.append(center_point)
|
|
||||||
|
|
||||||
for room in rooms:
|
|
||||||
room._built_points.append(center_point)
|
|
||||||
|
|
||||||
for point in points:
|
|
||||||
center_point.connect_to(point)
|
|
||||||
point.connect_to(center_point)
|
|
||||||
|
|
||||||
def connect_elevators(self):
|
|
||||||
for elevator in Elevator.objects.all(): # noqa
|
|
||||||
elevatorlevels = tuple(elevator.elevatorlevels.all())
|
|
||||||
for level1, level2 in combinations(elevatorlevels, 2):
|
|
||||||
point1 = self._built_elevatorlevel_points[level1.name]
|
|
||||||
point2 = self._built_elevatorlevel_points[level2.name]
|
|
||||||
|
|
||||||
center = GraphPoint((point1.x+point2.x)/2, (point1.y+point2.y)/2, None)
|
|
||||||
self.points.append(center)
|
|
||||||
self._built_level_transfer_points.append(center)
|
|
||||||
|
|
||||||
for room in (point1.room, point2.room):
|
|
||||||
room._built_points.append(center)
|
|
||||||
room.level._built_room_transfer_points.append(center)
|
|
||||||
room.level._built_points.append(center)
|
|
||||||
|
|
||||||
direction_up = level2.altitude > level1.altitude
|
|
||||||
|
|
||||||
dist = abs(level2.altitude-level1.altitude)
|
|
||||||
|
|
||||||
point1.connect_to(center, ctype=('elevator_up' if direction_up else 'elevator_down'), distance=dist)
|
|
||||||
center.connect_to(point2, ctype=('elevator_up' if direction_up else 'elevator_down'), distance=dist)
|
|
||||||
|
|
||||||
point2.connect_to(center, ctype=('elevator_down' if direction_up else 'elevator_up'), distance=dist)
|
|
||||||
center.connect_to(point1, ctype=('elevator_down' if direction_up else 'elevator_up'), distance=dist)
|
|
||||||
|
|
||||||
# Loading/Saving the Graph
|
|
||||||
def serialize(self):
|
|
||||||
return (
|
|
||||||
{name: level.serialize() for name, level in self.levels.items()},
|
|
||||||
[point.serialize() for point in self.points],
|
|
||||||
self.level_transfer_points,
|
|
||||||
)
|
|
||||||
|
|
||||||
def save(self, filename=None):
|
|
||||||
if filename is None:
|
|
||||||
filename = self.default_filename
|
|
||||||
with open(filename, 'wb') as f:
|
|
||||||
pickle.dump(self.serialize(), f)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def unserialize(cls, data, mtime):
|
|
||||||
levels, points, level_transfer_points = data
|
|
||||||
|
|
||||||
graph = cls(mtime=mtime)
|
|
||||||
|
|
||||||
for name, level in levels.items():
|
|
||||||
graph.levels[name].unserialize(level)
|
|
||||||
|
|
||||||
rooms = sum((level.rooms for level in graph.levels.values()), ())
|
|
||||||
|
|
||||||
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
|
|
||||||
def load(cls, filename=None):
|
|
||||||
do_cache = False
|
|
||||||
if filename is None:
|
|
||||||
do_cache = True
|
|
||||||
filename = cls.default_filename
|
|
||||||
|
|
||||||
graph_mtime = None
|
|
||||||
if do_cache:
|
|
||||||
graph_mtime = os.path.getmtime(filename)
|
|
||||||
if cls.graph_cached is not None:
|
|
||||||
if cls.graph_cached_mtime == graph_mtime:
|
|
||||||
return cls.graph_cached
|
|
||||||
|
|
||||||
with open(filename, 'rb') as f:
|
|
||||||
graph = cls.unserialize(pickle.load(f), graph_mtime)
|
|
||||||
|
|
||||||
if do_cache:
|
|
||||||
cls.graph_cached_mtime = graph_mtime
|
|
||||||
cls.graph_cached = graph
|
|
||||||
|
|
||||||
graph.print_stats()
|
|
||||||
return graph
|
|
||||||
|
|
||||||
# Drawing
|
|
||||||
def draw_pngs(self, points=True, lines=True):
|
|
||||||
for level in self.levels.values():
|
|
||||||
level.draw_png(points, lines)
|
|
||||||
|
|
||||||
# Router
|
|
||||||
def build_routers(self, allowed_ctypes, allow_nonpublic, avoid, include):
|
|
||||||
routers = {}
|
|
||||||
|
|
||||||
empty_distances = np.empty(shape=(len(self.level_transfer_points),) * 2, dtype=np.float16)
|
|
||||||
empty_distances[:] = np.inf
|
|
||||||
|
|
||||||
sparse_distances = empty_distances.copy()
|
|
||||||
|
|
||||||
level_transfers = np.zeros(shape=(len(self.level_transfer_points),) * 2, dtype=np.int16)
|
|
||||||
level_transfers[:] = -1
|
|
||||||
|
|
||||||
for i, level in enumerate(self.levels.values()):
|
|
||||||
routers.update(level.build_routers(allowed_ctypes, allow_nonpublic, avoid, include))
|
|
||||||
router = routers[level]
|
|
||||||
|
|
||||||
level_distances = empty_distances.copy()
|
|
||||||
in_level_i = np.array(tuple(level.room_transfer_points.index(point)
|
|
||||||
for point in level.level_transfer_points), dtype=int)
|
|
||||||
in_graph_i = np.array(tuple(self.level_transfer_points.index(point)
|
|
||||||
for point in level.level_transfer_points), dtype=int)
|
|
||||||
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] = level_distances[better]
|
|
||||||
level_transfers[better] = i
|
|
||||||
|
|
||||||
g_sparse = csgraph_from_dense(sparse_distances, null_value=np.inf)
|
|
||||||
shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True)
|
|
||||||
|
|
||||||
routers[self] = GraphRouter(shortest_paths, predecessors, level_transfers)
|
|
||||||
return routers
|
|
||||||
|
|
||||||
def get_location_points(self, location: Location, mode):
|
|
||||||
if isinstance(location, PointLocation):
|
|
||||||
points = self.levels[location.section.name].connected_points(np.array((location.x, location.y)), mode)
|
|
||||||
if not points:
|
|
||||||
return (), None, None
|
|
||||||
points, distances, ctypes = zip(*((point, distance, ctype) for point, (distance, ctype) in points.items()))
|
|
||||||
distances = {points[i]: distance for i, distance in enumerate(distances)}
|
|
||||||
points = np.array(points, dtype=np.int)
|
|
||||||
return points, distances, ctypes
|
|
||||||
try:
|
|
||||||
if isinstance(location, AreaLocation):
|
|
||||||
points = self.levels[location.level.name].arealocation_points[location.name]
|
|
||||||
return points, None, None
|
|
||||||
elif isinstance(location, LocationGroup):
|
|
||||||
points = tuple(np.hstack(tuple(self.get_location_points(area, mode)[0]
|
|
||||||
for area in location.arealocations.all())).astype(np.int))
|
|
||||||
return points, None, None
|
|
||||||
except KeyError:
|
|
||||||
raise NotYetRoutable
|
|
||||||
|
|
||||||
def _get_points_by_i(self, points):
|
|
||||||
return tuple(self.points[i] for i in points)
|
|
||||||
|
|
||||||
def _allowed_points_index(self, points, allowed_points_i):
|
|
||||||
return np.array(tuple(i for i, point in enumerate(points) if point in allowed_points_i))
|
|
||||||
|
|
||||||
def get_route(self, origin: Location, destination: Location,
|
|
||||||
allowed_ctypes, allow_nonpublic, avoid, include, visible_nonpublic_areas=None):
|
|
||||||
orig_points_i, orig_distances, orig_ctypes = self.get_location_points(origin, 'orig')
|
|
||||||
dest_points_i, dest_distances, dest_ctypes = self.get_location_points(destination, 'dest')
|
|
||||||
|
|
||||||
if not len(orig_points_i) or not len(dest_points_i):
|
|
||||||
raise NoRouteFound()
|
|
||||||
|
|
||||||
if orig_distances is None and dest_distances is None:
|
|
||||||
if set(dest_points_i) & set(orig_points_i):
|
|
||||||
orig_points_i = tuple(set(orig_points_i) - set(dest_points_i))
|
|
||||||
if set(dest_points_i) & set(orig_points_i):
|
|
||||||
dest_points_i = tuple(set(dest_points_i) - set(orig_points_i))
|
|
||||||
|
|
||||||
if not len(orig_points_i) or not len(dest_points_i):
|
|
||||||
raise AlreadyThere()
|
|
||||||
|
|
||||||
# if set(orig_points_i) & set(dest_points_i):
|
|
||||||
# raise AlreadyThere()
|
|
||||||
|
|
||||||
add_orig_point = origin if isinstance(origin, PointLocation) else None
|
|
||||||
add_dest_point = destination if isinstance(destination, PointLocation) else None
|
|
||||||
|
|
||||||
orig_points = self._get_points_by_i(orig_points_i)
|
|
||||||
dest_points = self._get_points_by_i(dest_points_i)
|
|
||||||
common_points = tuple(set(orig_points) & set(dest_points))
|
|
||||||
|
|
||||||
best_route = NoRoute
|
|
||||||
|
|
||||||
# get routers
|
|
||||||
routers = self.build_routers(allowed_ctypes, allow_nonpublic, avoid, include)
|
|
||||||
|
|
||||||
# route within room
|
|
||||||
orig_rooms = set(point.room for point in orig_points)
|
|
||||||
dest_rooms = set(point.room for point in dest_points)
|
|
||||||
common_rooms = orig_rooms & dest_rooms
|
|
||||||
|
|
||||||
# rooms are directly connectable
|
|
||||||
if add_orig_point and add_dest_point and common_rooms:
|
|
||||||
room = tuple(common_rooms)[0]
|
|
||||||
ctype = room.check_connection((add_orig_point.x, add_orig_point.y), (add_dest_point.x, add_dest_point.y))
|
|
||||||
if ctype is not None:
|
|
||||||
from_point = GraphPoint(add_orig_point.x, add_orig_point.y, room)
|
|
||||||
to_point = GraphPoint(add_dest_point.x, add_dest_point.y, room)
|
|
||||||
return Route((GraphConnection(from_point, to_point, ctype=ctype), ))
|
|
||||||
|
|
||||||
if common_points:
|
|
||||||
# same location
|
|
||||||
if not add_orig_point and not add_dest_point:
|
|
||||||
raise AlreadyThere()
|
|
||||||
|
|
||||||
# points are connectable with only one via
|
|
||||||
best_point = None
|
|
||||||
best_distance = np.inf
|
|
||||||
for point in common_points:
|
|
||||||
distance = 0
|
|
||||||
if add_orig_point:
|
|
||||||
distance += abs(np.linalg.norm(add_orig_point.xy - point.xy))
|
|
||||||
if add_dest_point:
|
|
||||||
distance += abs(np.linalg.norm(add_dest_point.xy - point.xy))
|
|
||||||
if distance < best_distance:
|
|
||||||
best_distance = distance
|
|
||||||
best_point = point
|
|
||||||
|
|
||||||
connections = []
|
|
||||||
if add_orig_point:
|
|
||||||
from_point = GraphPoint(add_orig_point.x, add_orig_point.y, best_point.room)
|
|
||||||
ctype = orig_ctypes[tuple(orig_points_i).index(best_point.i)]
|
|
||||||
connections.append(GraphConnection(from_point, best_point, ctype=ctype))
|
|
||||||
|
|
||||||
if add_dest_point:
|
|
||||||
to_point = GraphPoint(add_dest_point.x, add_dest_point.y, best_point.room)
|
|
||||||
ctype = dest_ctypes[tuple(dest_points_i).index(best_point.i)]
|
|
||||||
connections.append(GraphConnection(best_point, to_point, ctype=ctype))
|
|
||||||
|
|
||||||
return Route(connections)
|
|
||||||
|
|
||||||
# get origin points for each room (points as point index within room)
|
|
||||||
orig_room_points = {room: self._allowed_points_index(room.points, orig_points_i) for room in orig_rooms}
|
|
||||||
dest_room_points = {room: self._allowed_points_index(room.points, dest_points_i) for room in dest_rooms}
|
|
||||||
|
|
||||||
# add distances to room routers
|
|
||||||
if orig_distances is not None:
|
|
||||||
for room in orig_rooms:
|
|
||||||
distances = np.array(tuple(orig_distances[room.points[i]] for i in orig_room_points[room]))
|
|
||||||
routers[room].shortest_paths[orig_room_points[room], :] += distances[:, None]
|
|
||||||
|
|
||||||
if dest_distances is not None:
|
|
||||||
for room in dest_rooms:
|
|
||||||
distances = np.array(tuple(dest_distances[room.points[i]] for i in dest_room_points[room]))
|
|
||||||
routers[room].shortest_paths[:, dest_room_points[room]] += distances
|
|
||||||
|
|
||||||
# if the points have common rooms, search for routes within those rooms
|
|
||||||
if common_rooms:
|
|
||||||
for room in common_rooms:
|
|
||||||
shortest_paths = routers[room].shortest_paths[orig_room_points[room][:, None],
|
|
||||||
dest_room_points[room]]
|
|
||||||
distance = shortest_paths.min()
|
|
||||||
|
|
||||||
# Is this route better than the previous ones?
|
|
||||||
if distance >= best_route.distance:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
from_point, to_point = np.argwhere(shortest_paths == distance)[0]
|
|
||||||
from_point = orig_room_points[room][from_point]
|
|
||||||
to_point = dest_room_points[room][to_point]
|
|
||||||
best_route = RoomRouteSegment(room, routers, from_point, to_point).as_route()
|
|
||||||
|
|
||||||
# get reachable room transfer points and their distance
|
|
||||||
# as a dictionary: global transfer point index => RoomRouteSegment
|
|
||||||
orig_room_transfers = self._room_transfers(orig_rooms, orig_room_points, routers, mode='orig')
|
|
||||||
dest_room_transfers = self._room_transfers(dest_rooms, dest_room_points, routers, mode='dest')
|
|
||||||
|
|
||||||
# route within level
|
|
||||||
orig_levels = set(room.level for room in orig_rooms)
|
|
||||||
dest_levels = set(room.level for room in dest_rooms)
|
|
||||||
common_levels = orig_levels & dest_levels
|
|
||||||
|
|
||||||
# get reachable roomtransfer points for each level (points as room transfer point index within level)
|
|
||||||
orig_room_transfer_points = {level: self._allowed_points_index(level.room_transfer_points, orig_room_transfers)
|
|
||||||
for level in orig_levels}
|
|
||||||
dest_room_transfer_points = {level: self._allowed_points_index(level.room_transfer_points, dest_room_transfers)
|
|
||||||
for level in dest_levels}
|
|
||||||
|
|
||||||
# if the points have common levels, search for routes within those levels
|
|
||||||
if common_levels:
|
|
||||||
for level in common_levels:
|
|
||||||
o_points = orig_room_transfer_points[level]
|
|
||||||
d_points = dest_room_transfer_points[level]
|
|
||||||
|
|
||||||
if not len(o_points) or not len(d_points):
|
|
||||||
continue
|
|
||||||
|
|
||||||
shortest_paths = routers[level].shortest_paths[o_points[:, None], d_points]
|
|
||||||
|
|
||||||
# add distances to the the room transfer points to the rows and columns
|
|
||||||
shortest_paths += np.array(tuple(orig_room_transfers[level.room_transfer_points[in_level_i]].distance
|
|
||||||
for in_level_i in o_points))[:, None]
|
|
||||||
shortest_paths += np.array(tuple(dest_room_transfers[level.room_transfer_points[in_level_i]].distance
|
|
||||||
for in_level_i in d_points))
|
|
||||||
distance = shortest_paths.min()
|
|
||||||
|
|
||||||
# Is this route better than the previous ones?
|
|
||||||
if distance >= best_route.distance:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
from_point, to_point = np.argwhere(shortest_paths == distance)[0]
|
|
||||||
from_point = o_points[from_point]
|
|
||||||
to_point = d_points[to_point]
|
|
||||||
best_route = SegmentRoute((orig_room_transfers[level.room_transfer_points[from_point]],
|
|
||||||
LevelRouteSegment(level, routers, from_point, to_point),
|
|
||||||
dest_room_transfers[level.room_transfer_points[to_point]]),
|
|
||||||
distance=distance)
|
|
||||||
|
|
||||||
# get reachable level transfer points and their distance
|
|
||||||
# as a dictionary: global transfer point index => Route
|
|
||||||
orig_level_transfers = self._level_transfers(orig_levels, orig_room_transfers, routers, mode='orig')
|
|
||||||
dest_level_transfers = self._level_transfers(dest_levels, dest_room_transfers, routers, mode='dest')
|
|
||||||
|
|
||||||
# get reachable leveltransfer points (points as level transfer point index within graph)
|
|
||||||
orig_level_transfer_points = self._allowed_points_index(self.level_transfer_points, orig_level_transfers)
|
|
||||||
dest_level_transfer_points = self._allowed_points_index(self.level_transfer_points, dest_level_transfers)
|
|
||||||
|
|
||||||
# search a route within the whole graph
|
|
||||||
o_points = orig_level_transfer_points
|
|
||||||
d_points = dest_level_transfer_points
|
|
||||||
if len(o_points) and len(d_points):
|
|
||||||
shortest_paths = routers[self].shortest_paths[o_points[:, None], d_points]
|
|
||||||
|
|
||||||
# add distances to the the room transfer points to the rows and columns
|
|
||||||
shortest_paths += np.array(tuple(orig_level_transfers[self.level_transfer_points[in_graph_i]].distance
|
|
||||||
for in_graph_i in o_points))[:, None]
|
|
||||||
shortest_paths += np.array(tuple(dest_level_transfers[self.level_transfer_points[in_graph_i]].distance
|
|
||||||
for in_graph_i in d_points))
|
|
||||||
distance = shortest_paths.min()
|
|
||||||
|
|
||||||
# Is this route better than the previous ones?
|
|
||||||
if distance < best_route.distance:
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
from_point, to_point = np.argwhere(shortest_paths == distance)[0]
|
|
||||||
from_point = o_points[from_point]
|
|
||||||
to_point = d_points[to_point]
|
|
||||||
best_route = SegmentRoute((orig_level_transfers[self.level_transfer_points[from_point]],
|
|
||||||
GraphRouteSegment(self, routers, from_point, to_point),
|
|
||||||
dest_level_transfers[self.level_transfer_points[to_point]]),
|
|
||||||
distance=distance)
|
|
||||||
|
|
||||||
if best_route is NoRoute:
|
|
||||||
raise NoRouteFound()
|
|
||||||
|
|
||||||
orig_ctype = orig_ctypes[tuple(orig_points_i).index(best_route.from_point)] if add_orig_point else None
|
|
||||||
dest_ctype = dest_ctypes[tuple(dest_points_i).index(best_route.to_point)] if add_dest_point else None
|
|
||||||
best_route = SegmentRouteWrapper(best_route, orig_point=add_orig_point, dest_point=add_dest_point,
|
|
||||||
orig_ctype=orig_ctype, dest_ctype=dest_ctype)
|
|
||||||
best_route = best_route.split()
|
|
||||||
return best_route
|
|
||||||
|
|
||||||
def _room_transfers(self, rooms, room_points, routers, mode):
|
|
||||||
if mode not in ('orig', 'dest'):
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
room_transfers = {}
|
|
||||||
for room in rooms:
|
|
||||||
room_transfer_points = np.array(tuple(room.points.index(point)
|
|
||||||
for point in room.room_transfer_points), dtype=int)
|
|
||||||
|
|
||||||
points = room_points[room]
|
|
||||||
if mode == 'orig':
|
|
||||||
shortest_paths = routers[room].shortest_paths[points[:, None], room_transfer_points]
|
|
||||||
else:
|
|
||||||
shortest_paths = routers[room].shortest_paths[room_transfer_points[:, None], points]
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
for from_i, to_i in np.argwhere(shortest_paths != np.inf):
|
|
||||||
distance = shortest_paths[from_i, to_i]
|
|
||||||
from_i = points[from_i] if mode == 'orig' else room_transfer_points[from_i]
|
|
||||||
to_i = room_transfer_points[to_i] if mode == 'orig' else points[to_i]
|
|
||||||
|
|
||||||
transfer_i = room.points[to_i if mode == 'orig' else from_i]
|
|
||||||
if transfer_i not in room_transfers or room_transfers[transfer_i].distance > distance:
|
|
||||||
room_transfers[transfer_i] = RoomRouteSegment(room, routers, from_i, to_i)
|
|
||||||
|
|
||||||
return room_transfers
|
|
||||||
|
|
||||||
def _level_transfers(self, levels, all_room_transfers, routers, mode):
|
|
||||||
if mode not in ('orig', 'dest'):
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
level_transfers = {}
|
|
||||||
for level in levels:
|
|
||||||
level_transfer_points = np.array(tuple(level.room_transfer_points.index(point)
|
|
||||||
for point in level.level_transfer_points), dtype=int)
|
|
||||||
|
|
||||||
points, distances = zip(*(tuple((level.room_transfer_points.index(point), segment.distance)
|
|
||||||
for point, segment in all_room_transfers.items()
|
|
||||||
if point in level.room_transfer_points)))
|
|
||||||
points = np.array(points)
|
|
||||||
distances = np.array(distances)
|
|
||||||
|
|
||||||
if mode == 'orig':
|
|
||||||
shortest_paths = routers[level].shortest_paths[points[:, None], level_transfer_points]
|
|
||||||
shortest_paths += distances[:, None]
|
|
||||||
else:
|
|
||||||
shortest_paths = routers[level].shortest_paths[level_transfer_points[:, None], points]
|
|
||||||
shortest_paths += distances
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
for from_i, to_i in np.argwhere(shortest_paths != np.inf):
|
|
||||||
distance = shortest_paths[from_i, to_i]
|
|
||||||
from_i = points[from_i] if mode == 'orig' else level_transfer_points[from_i]
|
|
||||||
to_i = level_transfer_points[to_i] if mode == 'orig' else points[to_i]
|
|
||||||
|
|
||||||
transfer_i = level.room_transfer_points[to_i if mode == 'orig' else from_i]
|
|
||||||
room_transfer_i = level.room_transfer_points[from_i if mode == 'orig' else to_i]
|
|
||||||
if transfer_i not in level_transfers or level_transfers[transfer_i].distance > distance:
|
|
||||||
segments = [LevelRouteSegment(level, routers, from_i, to_i)]
|
|
||||||
if mode == 'orig':
|
|
||||||
segments.insert(0, all_room_transfers[room_transfer_i])
|
|
||||||
else:
|
|
||||||
segments.append(all_room_transfers[room_transfer_i])
|
|
||||||
level_transfers[transfer_i] = SegmentRoute(segments, distance)
|
|
||||||
|
|
||||||
return level_transfers
|
|
||||||
|
|
||||||
def get_nearest_point(self, level, x, y):
|
|
||||||
return self.levels[level.name].nearest_point(np.array((x, y)), 'orig')
|
|
||||||
|
|
||||||
|
|
||||||
GraphRouter = namedtuple('GraphRouter', ('shortest_paths', 'predecessors', 'level_transfers', ))
|
|
|
@ -1,430 +0,0 @@
|
||||||
import os
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.cache import cache
|
|
||||||
from matplotlib.path import Path
|
|
||||||
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 CAP_STYLE, JOIN_STYLE, LineString
|
|
||||||
|
|
||||||
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
|
|
||||||
from c3nav.mapdata.utils.misc import get_public_private_area
|
|
||||||
from c3nav.mapdata.utils.mpl import shapely_to_mpl
|
|
||||||
from c3nav.routing.point import GraphPoint
|
|
||||||
from c3nav.routing.room import GraphRoom
|
|
||||||
from c3nav.routing.utils.base import get_nearest_point
|
|
||||||
from c3nav.routing.utils.coords import coord_angle
|
|
||||||
from c3nav.routing.utils.draw import _ellipse_bbox, _line_coords
|
|
||||||
|
|
||||||
|
|
||||||
class GraphLevel():
|
|
||||||
def __init__(self, graph, level):
|
|
||||||
self.graph = graph
|
|
||||||
self.level = level
|
|
||||||
self.rooms = []
|
|
||||||
|
|
||||||
self.points = []
|
|
||||||
self.room_transfer_points = None
|
|
||||||
self.level_transfer_points = None
|
|
||||||
self.arealocation_points = None
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
return (
|
|
||||||
[room.serialize() for room in self.rooms],
|
|
||||||
self.points,
|
|
||||||
self.room_transfer_points,
|
|
||||||
self.level_transfer_points,
|
|
||||||
self.arealocation_points,
|
|
||||||
)
|
|
||||||
|
|
||||||
def unserialize(self, data):
|
|
||||||
rooms, self.points, self.room_transfer_points, self.level_transfer_points, self.arealocation_points = data
|
|
||||||
self.rooms = tuple(GraphRoom.unserialize(self, room) for room in rooms)
|
|
||||||
|
|
||||||
# Building the Graph
|
|
||||||
def build(self):
|
|
||||||
print()
|
|
||||||
print('Level %s:' % self.level.name)
|
|
||||||
|
|
||||||
self._built_points = []
|
|
||||||
self._built_room_transfer_points = []
|
|
||||||
|
|
||||||
self.collect_stairs()
|
|
||||||
self.collect_escalators()
|
|
||||||
|
|
||||||
self.collect_rooms()
|
|
||||||
print('%d rooms' % len(self.rooms))
|
|
||||||
|
|
||||||
for room in self.rooms:
|
|
||||||
room.build_areas()
|
|
||||||
room.build_points()
|
|
||||||
|
|
||||||
self.create_doors()
|
|
||||||
self.create_oneways()
|
|
||||||
self.create_levelconnectors()
|
|
||||||
self.create_elevatorlevels()
|
|
||||||
|
|
||||||
self.collect_arealocations()
|
|
||||||
self.collect_stuffedareas()
|
|
||||||
|
|
||||||
self._built_points = sum((room._built_points for room in self.rooms), [])
|
|
||||||
self._built_points.extend(self._built_room_transfer_points)
|
|
||||||
|
|
||||||
for room in self.rooms:
|
|
||||||
room.build_connections()
|
|
||||||
|
|
||||||
print('%d excludables' % len(self._built_excludables))
|
|
||||||
print('%d points' % len(self._built_points))
|
|
||||||
print('%d room transfer points' % len(self._built_room_transfer_points))
|
|
||||||
print('%d area locations' % len(self._built_arealocations))
|
|
||||||
|
|
||||||
def connection_count(self):
|
|
||||||
return sum(room.connection_count() for room in self.rooms)
|
|
||||||
|
|
||||||
def collect_stairs(self):
|
|
||||||
self.mpl_stairs = ()
|
|
||||||
for stair_line in assert_multilinestring(self.level.geometries.stairs):
|
|
||||||
coords = tuple(stair_line.coords)
|
|
||||||
self.mpl_stairs += tuple((Path(part), coord_angle(*part)) for part in zip(coords[:-1], coords[1:]))
|
|
||||||
|
|
||||||
def collect_escalators(self):
|
|
||||||
self.mpl_escalatorslopes = ()
|
|
||||||
for escalatorslope_line in assert_multilinestring(self.level.geometries.escalatorslopes):
|
|
||||||
coords = tuple(escalatorslope_line.coords)
|
|
||||||
self.mpl_escalatorslopes += tuple((Path(part), coord_angle(*part))
|
|
||||||
for part in zip(coords[:-1], coords[1:]))
|
|
||||||
|
|
||||||
self._built_escalators = []
|
|
||||||
for escalator in self.level.escalators.all():
|
|
||||||
mpl_escalator = shapely_to_mpl(escalator.geometry)
|
|
||||||
for slope_line, angle in self.mpl_escalatorslopes:
|
|
||||||
if mpl_escalator.intersects_path(slope_line, filled=True):
|
|
||||||
self._built_escalators.append(EscalatorData(mpl_escalator, escalator.direction, slope_line, angle))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print('Escalator %s has no slope line!' % escalator.name)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def collect_oneways(self):
|
|
||||||
self._built_oneways = ()
|
|
||||||
for oneway_line in assert_multilinestring(self.level.geometries.oneways):
|
|
||||||
coords = tuple(oneway_line.coords)
|
|
||||||
self._built_oneways += tuple((Path(part), coord_angle(*part))
|
|
||||||
for part in zip(coords[:-1], coords[1:]))
|
|
||||||
|
|
||||||
def collect_rooms(self):
|
|
||||||
accessibles = self.level.geometries.accessible_without_oneways
|
|
||||||
accessibles = assert_multipolygon(accessibles)
|
|
||||||
for geometry in accessibles:
|
|
||||||
room = GraphRoom(self)
|
|
||||||
if room.prepare_build(geometry):
|
|
||||||
self.rooms.append(room)
|
|
||||||
|
|
||||||
def collect_arealocations(self):
|
|
||||||
self._built_arealocations = {}
|
|
||||||
self._built_excludables = {}
|
|
||||||
for excludable in self.level.arealocations.all():
|
|
||||||
self._built_arealocations[excludable.name] = excludable.geometry
|
|
||||||
if excludable.routing_inclusion != 'default' or not excludable.public:
|
|
||||||
self._built_excludables[excludable.name] = excludable.geometry
|
|
||||||
|
|
||||||
public_area, private_area = get_public_private_area(self.level)
|
|
||||||
|
|
||||||
self._built_arealocations[':public'] = public_area
|
|
||||||
self._built_excludables[':public'] = public_area
|
|
||||||
|
|
||||||
self._built_arealocations[':nonpublic'] = private_area
|
|
||||||
self._built_excludables[':nonpublic'] = private_area
|
|
||||||
|
|
||||||
# add points inside arealocations to be able to route to its borders
|
|
||||||
for excludable in self._built_arealocations.values():
|
|
||||||
smaller = excludable.buffer(-0.05, join_style=JOIN_STYLE.mitre)
|
|
||||||
for room in self.rooms:
|
|
||||||
room.add_points_on_rings(assert_multipolygon(smaller))
|
|
||||||
|
|
||||||
# add points outside excludables so if excluded you can walk around them
|
|
||||||
for excludable in self._built_excludables.values():
|
|
||||||
for polygon in assert_multipolygon(excludable.buffer(0.28, join_style=JOIN_STYLE.mitre)):
|
|
||||||
for room in self.rooms:
|
|
||||||
room._add_ring(polygon.exterior, want_left=True)
|
|
||||||
|
|
||||||
for interior in polygon.interiors:
|
|
||||||
room._add_ring(interior, want_left=False)
|
|
||||||
|
|
||||||
def collect_stuffedareas(self):
|
|
||||||
self._built_stuffedareas = self.level.geometries.stuffedareas
|
|
||||||
for polygon in assert_multipolygon(self._built_stuffedareas.buffer(0.05, join_style=JOIN_STYLE.mitre)):
|
|
||||||
for room in self.rooms:
|
|
||||||
room._add_ring(polygon.exterior, want_left=True)
|
|
||||||
|
|
||||||
for interior in polygon.interiors:
|
|
||||||
room._add_ring(interior, want_left=False)
|
|
||||||
|
|
||||||
def create_doors(self):
|
|
||||||
doors = self.level.geometries.doors
|
|
||||||
doors = assert_multipolygon(doors)
|
|
||||||
for door in doors:
|
|
||||||
polygon = door.buffer(0.01, join_style=JOIN_STYLE.mitre)
|
|
||||||
center = door.centroid
|
|
||||||
|
|
||||||
num_points = 0
|
|
||||||
connected_rooms = set()
|
|
||||||
points = []
|
|
||||||
for room in self.rooms:
|
|
||||||
if not polygon.intersects(room._built_geometry):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
|
|
||||||
connected_rooms.add(room)
|
|
||||||
nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid)
|
|
||||||
point, = room.add_point(nearest_point.coords[0])
|
|
||||||
points.append(point)
|
|
||||||
|
|
||||||
if len(points) < 2:
|
|
||||||
print('door with <2 points (%d) detected at (%.2f, %.2f)' % (num_points, center.x, center.y))
|
|
||||||
continue
|
|
||||||
|
|
||||||
center_point = GraphPoint(center.x, center.y, None)
|
|
||||||
self._built_room_transfer_points.append(center_point)
|
|
||||||
for room in connected_rooms:
|
|
||||||
room._built_points.append(center_point)
|
|
||||||
|
|
||||||
for point in points:
|
|
||||||
center_point.connect_to(point)
|
|
||||||
point.connect_to(center_point)
|
|
||||||
|
|
||||||
def create_oneways(self):
|
|
||||||
oneways = self.level.geometries.oneways
|
|
||||||
oneways = assert_multilinestring(oneways)
|
|
||||||
|
|
||||||
segments = ()
|
|
||||||
for oneway in oneways:
|
|
||||||
coords = tuple(oneway.coords)
|
|
||||||
segments += tuple((Path(part), coord_angle(*part))
|
|
||||||
for part in zip(coords[:-1], coords[1:]))
|
|
||||||
|
|
||||||
for oneway, oneway_angle in segments:
|
|
||||||
line_string = LineString(tuple(oneway.vertices))
|
|
||||||
polygon = line_string.buffer(0.10, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
|
||||||
center = polygon.centroid
|
|
||||||
|
|
||||||
num_points = 0
|
|
||||||
connected_rooms = set()
|
|
||||||
points = []
|
|
||||||
for room in self.rooms:
|
|
||||||
if not polygon.intersects(room._built_geometry):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
|
|
||||||
connected_rooms.add(room)
|
|
||||||
nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid)
|
|
||||||
point, = room.add_point(nearest_point.coords[0])
|
|
||||||
points.append(point)
|
|
||||||
|
|
||||||
if len(points) < 2:
|
|
||||||
print('oneway with <2 points (%d) detected at (%.2f, %.2f)' % (num_points, center.x, center.y))
|
|
||||||
continue
|
|
||||||
|
|
||||||
center_point = GraphPoint(center.x, center.y, None)
|
|
||||||
self._built_room_transfer_points.append(center_point)
|
|
||||||
for room in connected_rooms:
|
|
||||||
room._built_points.append(center_point)
|
|
||||||
|
|
||||||
for point in points:
|
|
||||||
angle = coord_angle(point.xy, center_point.xy)
|
|
||||||
angle_diff = ((oneway_angle - angle + 180) % 360) - 180
|
|
||||||
direction_up = (angle_diff > 0)
|
|
||||||
if direction_up:
|
|
||||||
point.connect_to(center_point)
|
|
||||||
else:
|
|
||||||
center_point.connect_to(point)
|
|
||||||
|
|
||||||
def create_levelconnectors(self):
|
|
||||||
for levelconnector in self.level.levelconnectors.all():
|
|
||||||
polygon = levelconnector.geometry
|
|
||||||
|
|
||||||
for room in self.rooms:
|
|
||||||
if not polygon.intersects(room._built_geometry):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
|
|
||||||
point = subpolygon.centroid
|
|
||||||
if not point.within(room.clear_geometry):
|
|
||||||
point = get_nearest_point(room.clear_geometry, point)
|
|
||||||
point, = room.add_point(point.coords[0])
|
|
||||||
self.graph.add_levelconnector_point(levelconnector, point)
|
|
||||||
|
|
||||||
def create_elevatorlevels(self):
|
|
||||||
for elevatorlevel in self.level.elevatorlevels.all():
|
|
||||||
center = elevatorlevel.geometry.centroid
|
|
||||||
mpl_elevatorlevel = shapely_to_mpl(elevatorlevel.geometry)
|
|
||||||
for room in self.rooms:
|
|
||||||
if not room.mpl_clear.contains_point(center.coords[0]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
room._built_is_elevatorlevel = True
|
|
||||||
|
|
||||||
points = [point for point in room._built_points if mpl_elevatorlevel.contains_point(point.xy)]
|
|
||||||
if not points:
|
|
||||||
print('elevatorlevel %s has 0 points!' % (elevatorlevel.name))
|
|
||||||
break
|
|
||||||
elif len(points) > 1:
|
|
||||||
print('elevatorlevel %s has > 2 points!' % (elevatorlevel.name))
|
|
||||||
break
|
|
||||||
|
|
||||||
point = points[0]
|
|
||||||
self.graph.add_elevatorlevel_point(elevatorlevel, point)
|
|
||||||
break
|
|
||||||
|
|
||||||
def finish_build(self):
|
|
||||||
self.rooms = tuple(self.rooms)
|
|
||||||
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()
|
|
||||||
|
|
||||||
for room in self.rooms:
|
|
||||||
room.finish_build()
|
|
||||||
|
|
||||||
def collect_arealocation_points(self):
|
|
||||||
self.arealocation_points = {}
|
|
||||||
|
|
||||||
for room in self.rooms:
|
|
||||||
room.excludables = []
|
|
||||||
|
|
||||||
for name, arealocation in self._built_arealocations.items():
|
|
||||||
mpl_area = shapely_to_mpl(arealocation)
|
|
||||||
|
|
||||||
rooms = [room for room in self.rooms
|
|
||||||
if any(room.mpl_clear.intersects_path(exterior, filled=True) for exterior in mpl_area.exteriors)]
|
|
||||||
possible_points = tuple(point for point in sum((room._built_points for room in rooms), []) if point.room)
|
|
||||||
points = tuple(point for point in possible_points if mpl_area.contains_point(point.xy))
|
|
||||||
self.arealocation_points[name] = tuple(point.i for point in points)
|
|
||||||
|
|
||||||
if name in self._built_excludables:
|
|
||||||
for room in set(point.room for point in points):
|
|
||||||
room.excludables.append(name)
|
|
||||||
|
|
||||||
# Drawing
|
|
||||||
ctype_colors = {
|
|
||||||
'': (50, 200, 0),
|
|
||||||
'stairs_up': (255, 50, 50),
|
|
||||||
'stairs_down': (255, 50, 50),
|
|
||||||
'escalator_up': (255, 150, 0),
|
|
||||||
'escalator_down': (200, 100, 0),
|
|
||||||
'elevator_up': (200, 0, 200),
|
|
||||||
'elevator_down': (200, 0, 200),
|
|
||||||
}
|
|
||||||
|
|
||||||
def draw_png(self, points=True, lines=True):
|
|
||||||
filename = os.path.join(settings.RENDER_ROOT, 'base-level-%s.png' % self.level.name)
|
|
||||||
graph_filename = os.path.join(settings.RENDER_ROOT, 'graph-level-%s.png' % self.level.name)
|
|
||||||
|
|
||||||
im = Image.open(filename)
|
|
||||||
height = im.size[1]
|
|
||||||
draw = ImageDraw.Draw(im)
|
|
||||||
|
|
||||||
if lines:
|
|
||||||
for room in self.rooms:
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
for ctype, from_i, to_i in np.argwhere(room.distances != np.inf):
|
|
||||||
draw.line(_line_coords(self.graph.points[room.points[from_i]],
|
|
||||||
self.graph.points[room.points[to_i]], height),
|
|
||||||
fill=self.ctype_colors[room.ctypes[ctype]])
|
|
||||||
|
|
||||||
if points:
|
|
||||||
for point_i in self.points:
|
|
||||||
point = self.graph.points[point_i]
|
|
||||||
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (200, 0, 0))
|
|
||||||
|
|
||||||
for point_i in self.room_transfer_points:
|
|
||||||
point = self.graph.points[point_i]
|
|
||||||
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (0, 0, 255))
|
|
||||||
|
|
||||||
for point_i in self.level_transfer_points:
|
|
||||||
point = self.graph.points[point_i]
|
|
||||||
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (0, 180, 0))
|
|
||||||
|
|
||||||
if lines:
|
|
||||||
for room in self.rooms:
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
for ctype, from_i, to_i in np.argwhere(room.distances != np.inf):
|
|
||||||
if room.points[from_i] in room.room_transfer_points:
|
|
||||||
draw.line(_line_coords(self.graph.points[room.points[from_i]],
|
|
||||||
self.graph.points[room.points[to_i]], height), fill=(0, 255, 255))
|
|
||||||
|
|
||||||
im.save(graph_filename)
|
|
||||||
|
|
||||||
# Routing
|
|
||||||
def build_routers(self, allowed_ctypes, allow_nonpublic, avoid, include):
|
|
||||||
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(allowed_ctypes, allow_nonpublic, avoid, include)
|
|
||||||
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), dtype=int)
|
|
||||||
in_level_i = np.array(tuple(self.room_transfer_points.index(point)
|
|
||||||
for point in room.room_transfer_points), dtype=int)
|
|
||||||
|
|
||||||
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] = room_distances[better]
|
|
||||||
room_transfers[better] = i
|
|
||||||
|
|
||||||
g_sparse = csgraph_from_dense(sparse_distances, null_value=np.inf)
|
|
||||||
shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True)
|
|
||||||
|
|
||||||
routers[self] = LevelRouter(shortest_paths, predecessors, room_transfers)
|
|
||||||
return routers
|
|
||||||
|
|
||||||
def nearest_point(self, point, mode):
|
|
||||||
cache_key = ('c3nav__routing__nearest_point__%s__%s__%.2f_%.2f__%s' %
|
|
||||||
(self.graph.mtime, self.level.name, point[0], point[1], mode))
|
|
||||||
nearest_point = cache.get(cache_key, None)
|
|
||||||
if nearest_point is None:
|
|
||||||
nearest_point = self._nearest_point(point, mode)
|
|
||||||
cache.set(cache_key, nearest_point, 60)
|
|
||||||
if nearest_point is None:
|
|
||||||
return None
|
|
||||||
return self.graph.points[nearest_point]
|
|
||||||
|
|
||||||
def _nearest_point(self, point, mode):
|
|
||||||
points = self.connected_points(point, mode)
|
|
||||||
if not points:
|
|
||||||
return None
|
|
||||||
|
|
||||||
nearest_point = min(points.items(), key=lambda x: x[1][0])
|
|
||||||
return nearest_point[0]
|
|
||||||
|
|
||||||
def connected_points(self, point, mode):
|
|
||||||
cache_key = ('c3nav__routing__connected_points__%s__%s__%.2f_%.2f__%s' %
|
|
||||||
(self.graph.mtime, self.level.name, point[0], point[1], mode))
|
|
||||||
points = cache.get(cache_key, None)
|
|
||||||
if points is None or True:
|
|
||||||
points = self._connected_points(point, mode)
|
|
||||||
cache.set(cache_key, points, 60)
|
|
||||||
return points
|
|
||||||
|
|
||||||
def _connected_points(self, point, mode):
|
|
||||||
for room in self.rooms:
|
|
||||||
if room.contains_point(point):
|
|
||||||
return room.connected_points(point, mode)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
LevelRouter = namedtuple('LevelRouter', ('shortest_paths', 'predecessors', 'room_transfers', ))
|
|
||||||
EscalatorData = namedtuple('EscalatorData', ('mpl_geom', 'direction_up', 'slope', 'angle'))
|
|
|
@ -1,24 +0,0 @@
|
||||||
import time
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from c3nav.routing.graph import Graph
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'build the routing graph'
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
start = time.time()
|
|
||||||
graph = Graph()
|
|
||||||
graph.build()
|
|
||||||
print()
|
|
||||||
print('Built in %.4fs' % (time.time() - start))
|
|
||||||
|
|
||||||
start = time.time()
|
|
||||||
graph.save()
|
|
||||||
print('Saved in %.4fs' % (time.time()-start))
|
|
||||||
|
|
||||||
start = time.time()
|
|
||||||
Graph.load()
|
|
||||||
print('Loaded in %.4fs' % (time.time() - start))
|
|
|
@ -1,18 +0,0 @@
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from c3nav.routing.graph import Graph
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'draw the routing graph'
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument('--no-points', action='store_const', dest='points', const=False, default=True,
|
|
||||||
help='dont draw points on the graph image')
|
|
||||||
|
|
||||||
parser.add_argument('--no-lines', action='store_const', dest='lines', const=False, default=True,
|
|
||||||
help='dont draw lines on the graph image')
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
graph = Graph.load()
|
|
||||||
graph.draw_pngs(points=options['points'], lines=options['lines'])
|
|
|
@ -1,22 +0,0 @@
|
||||||
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_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))
|
|
|
@ -1,46 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.functional import cached_property
|
|
||||||
|
|
||||||
from c3nav.routing.connection import GraphConnection
|
|
||||||
|
|
||||||
|
|
||||||
class GraphPoint():
|
|
||||||
def __init__(self, x, y, room):
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.room = room
|
|
||||||
self.xy = np.array((x, y))
|
|
||||||
self.i = None
|
|
||||||
|
|
||||||
self.connections = {}
|
|
||||||
self.connections_in = {}
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def level(self):
|
|
||||||
return self.room and self.room.level
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
return (
|
|
||||||
self.x,
|
|
||||||
self.y,
|
|
||||||
None if self.room is None else self.room.i,
|
|
||||||
)
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def ellipse_bbox(self):
|
|
||||||
x = self.x * settings.RENDER_SCALE
|
|
||||||
y = self.y * settings.RENDER_SCALE
|
|
||||||
return ((x-5, y-5), (x+5, y+5))
|
|
||||||
|
|
||||||
def connect_to(self, other_point, ctype='', distance=None):
|
|
||||||
connection = GraphConnection(self, other_point, ctype=ctype, distance=distance)
|
|
||||||
self.connections[other_point] = connection
|
|
||||||
other_point.connections_in[self] = connection
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def arealocations(self):
|
|
||||||
return tuple(name for name, points_i in self.level.arealocation_points.items() if self.i in points_i)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<GraphPoint x=%f y=%f room=%s>' % (self.x, self.y, (id(self.room) if self.room else None))
|
|
|
@ -1,332 +0,0 @@
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from matplotlib.path import Path
|
|
||||||
from scipy.sparse.csgraph._shortest_path import shortest_path
|
|
||||||
from scipy.sparse.csgraph._tools import csgraph_from_dense
|
|
||||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString
|
|
||||||
from shapely.ops import cascaded_union
|
|
||||||
|
|
||||||
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
|
|
||||||
from c3nav.mapdata.utils.mpl import shapely_to_mpl
|
|
||||||
from c3nav.routing.area import GraphArea
|
|
||||||
from c3nav.routing.connection import GraphConnection
|
|
||||||
from c3nav.routing.point import GraphPoint
|
|
||||||
from c3nav.routing.utils.coords import get_coords_angles
|
|
||||||
|
|
||||||
|
|
||||||
class GraphRoom():
|
|
||||||
def __init__(self, level):
|
|
||||||
self.level = level
|
|
||||||
self.graph = level.graph
|
|
||||||
|
|
||||||
self.mpl_clear = None
|
|
||||||
|
|
||||||
self.i = None
|
|
||||||
self.areas = []
|
|
||||||
self.points = None
|
|
||||||
self.room_transfer_points = None
|
|
||||||
self.distances = np.zeros((1, ))
|
|
||||||
self.ctypes = None
|
|
||||||
self.excludables = None
|
|
||||||
self.stuffedareas = None
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
return (
|
|
||||||
self.mpl_clear,
|
|
||||||
[area.serialize() for area in self.areas],
|
|
||||||
self.points,
|
|
||||||
self.room_transfer_points,
|
|
||||||
self.distances,
|
|
||||||
self.ctypes,
|
|
||||||
self.excludables,
|
|
||||||
self.excludable_points,
|
|
||||||
self.stuffedareas,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def unserialize(cls, level, data):
|
|
||||||
room = cls(level)
|
|
||||||
(room.mpl_clear, areas, room.points, room.room_transfer_points,
|
|
||||||
room.distances, room.ctypes, room.excludables, room.excludable_points, room.stuffedareas) = data
|
|
||||||
room.areas = tuple(GraphArea(room, *area) for area in areas)
|
|
||||||
return room
|
|
||||||
|
|
||||||
# Building the Graph
|
|
||||||
def prepare_build(self, geometry):
|
|
||||||
self._built_geometry = geometry
|
|
||||||
self.clear_geometry = self._built_geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre)
|
|
||||||
|
|
||||||
if self.clear_geometry.is_empty:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._built_points = []
|
|
||||||
self._built_is_elevatorlevel = False
|
|
||||||
|
|
||||||
self.mpl_clear = shapely_to_mpl(self.clear_geometry.buffer(0.01, join_style=JOIN_STYLE.mitre))
|
|
||||||
self.mpl_stairs = tuple((stair, angle) for stair, angle in self.level.mpl_stairs
|
|
||||||
if self.mpl_clear.intersects_path(stair, filled=True))
|
|
||||||
self._built_escalators = tuple(escalator for escalator in self.level._built_escalators
|
|
||||||
if self.mpl_clear.intersects_path(escalator.mpl_geom.exterior, filled=True))
|
|
||||||
|
|
||||||
self.isolated_areas = []
|
|
||||||
return True
|
|
||||||
|
|
||||||
def build_areas(self):
|
|
||||||
stairs_areas = self.level.level.geometries.stairs
|
|
||||||
stairs_areas = stairs_areas.buffer(0.3, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
|
||||||
stairs_areas = stairs_areas.intersection(self._built_geometry)
|
|
||||||
self._built_isolated_areas = tuple(assert_multipolygon(stairs_areas))
|
|
||||||
|
|
||||||
escalators_areas = self.level.level.geometries.escalators
|
|
||||||
escalators_areas = escalators_areas.intersection(self._built_geometry)
|
|
||||||
self._built_isolated_areas += tuple(assert_multipolygon(escalators_areas))
|
|
||||||
|
|
||||||
escalators_and_stairs = cascaded_union((stairs_areas, escalators_areas))
|
|
||||||
|
|
||||||
isolated_areas = tuple(assert_multipolygon(stairs_areas.intersection(self.clear_geometry)))
|
|
||||||
isolated_areas += tuple(assert_multipolygon(escalators_areas.intersection(self.clear_geometry)))
|
|
||||||
isolated_areas += tuple(assert_multipolygon(self.clear_geometry.difference(escalators_and_stairs)))
|
|
||||||
|
|
||||||
for isolated_area in isolated_areas:
|
|
||||||
mpl_clear = shapely_to_mpl(isolated_area.buffer(0.01, join_style=JOIN_STYLE.mitre))
|
|
||||||
mpl_stairs = tuple((stair, angle) for stair, angle in self.mpl_stairs
|
|
||||||
if mpl_clear.intersects_path(stair, filled=True))
|
|
||||||
escalators = tuple(escalator for escalator in self._built_escalators
|
|
||||||
if escalator.mpl_geom.intersects_path(mpl_clear.exterior, filled=True))
|
|
||||||
area = GraphArea(self, mpl_clear, mpl_stairs, escalators)
|
|
||||||
area.prepare_build()
|
|
||||||
self.areas.append(area)
|
|
||||||
|
|
||||||
def build_points(self):
|
|
||||||
narrowed_geometry = self._built_geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre)
|
|
||||||
geometry = narrowed_geometry.buffer(0.31, join_style=JOIN_STYLE.mitre).intersection(self.clear_geometry)
|
|
||||||
|
|
||||||
if geometry.is_empty:
|
|
||||||
return
|
|
||||||
|
|
||||||
# points with 60cm distance to borders
|
|
||||||
polygons = assert_multipolygon(geometry)
|
|
||||||
for polygon in polygons:
|
|
||||||
self._add_ring(polygon.exterior, want_left=False)
|
|
||||||
|
|
||||||
for interior in polygon.interiors:
|
|
||||||
self._add_ring(interior, want_left=True)
|
|
||||||
|
|
||||||
# now fill in missing doorways or similar
|
|
||||||
accessible_clear_geometry = geometry.buffer(0.31, join_style=JOIN_STYLE.mitre)
|
|
||||||
missing_geometry = self.clear_geometry.difference(accessible_clear_geometry)
|
|
||||||
polygons = assert_multipolygon(missing_geometry)
|
|
||||||
for polygon in polygons:
|
|
||||||
overlaps = polygon.buffer(0.02).intersection(accessible_clear_geometry)
|
|
||||||
if overlaps.is_empty:
|
|
||||||
continue
|
|
||||||
|
|
||||||
points = []
|
|
||||||
|
|
||||||
# overlaps to non-missing areas
|
|
||||||
overlaps = assert_multipolygon(overlaps)
|
|
||||||
for overlap in overlaps:
|
|
||||||
points += self.add_point(overlap.centroid.coords[0])
|
|
||||||
|
|
||||||
points += self._add_ring(polygon.exterior, want_left=False)
|
|
||||||
|
|
||||||
for interior in polygon.interiors:
|
|
||||||
points += self._add_ring(interior, want_left=True)
|
|
||||||
|
|
||||||
# points around steps
|
|
||||||
self.add_points_on_rings(self._built_isolated_areas)
|
|
||||||
|
|
||||||
def _add_ring(self, geom, want_left):
|
|
||||||
"""
|
|
||||||
add the points of a ring, but only those that have a specific direction change.
|
|
||||||
additionally removes unneeded points if the neighbors can be connected in self.clear_geometry
|
|
||||||
:param geom: LinearRing
|
|
||||||
:param want_left: True if the direction has to be left, False if it has to be right
|
|
||||||
"""
|
|
||||||
coords = []
|
|
||||||
skipped = False
|
|
||||||
can_delete_last = False
|
|
||||||
for coord, is_left in get_coords_angles(geom):
|
|
||||||
if is_left != want_left:
|
|
||||||
skipped = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not skipped and can_delete_last and len(coords) >= 2:
|
|
||||||
if LineString((coords[-2], coord)).within(self.clear_geometry):
|
|
||||||
coords[-1] = coord
|
|
||||||
continue
|
|
||||||
|
|
||||||
coords.append(coord)
|
|
||||||
can_delete_last = not skipped
|
|
||||||
skipped = False
|
|
||||||
|
|
||||||
if not skipped and can_delete_last and len(coords) >= 3:
|
|
||||||
if LineString((coords[-2], coords[0])).within(self.clear_geometry):
|
|
||||||
coords.pop()
|
|
||||||
|
|
||||||
points = []
|
|
||||||
for coord in coords:
|
|
||||||
points += self.add_point(coord)
|
|
||||||
|
|
||||||
return points
|
|
||||||
|
|
||||||
def add_points_on_rings(self, areas):
|
|
||||||
for polygon in areas:
|
|
||||||
for ring in (polygon.exterior,) + tuple(polygon.interiors):
|
|
||||||
for linestring in assert_multilinestring(ring.intersection(self.clear_geometry)):
|
|
||||||
coords = tuple(linestring.coords)
|
|
||||||
if len(coords) == 2:
|
|
||||||
path = Path(coords)
|
|
||||||
length = abs(np.linalg.norm(path.vertices[0] - path.vertices[1]))
|
|
||||||
for coord in tuple(path.interpolated(int(length / 1.0 + 1)).vertices):
|
|
||||||
self.add_point(coord)
|
|
||||||
continue
|
|
||||||
|
|
||||||
start = 0
|
|
||||||
for segment in zip(coords[:-1], coords[1:]):
|
|
||||||
path = Path(segment)
|
|
||||||
length = abs(np.linalg.norm(path.vertices[0] - path.vertices[1]))
|
|
||||||
if length < 1.0:
|
|
||||||
coords = (path.vertices[1 if start == 0 else 0],)
|
|
||||||
else:
|
|
||||||
coords = tuple(path.interpolated(int(length / 1.0 + 0.5)).vertices)[start:]
|
|
||||||
for coord in coords:
|
|
||||||
self.add_point(coord)
|
|
||||||
start = 1
|
|
||||||
|
|
||||||
def add_point(self, coord):
|
|
||||||
if not self.mpl_clear.contains_point(coord):
|
|
||||||
return []
|
|
||||||
point = GraphPoint(coord[0], coord[1], self)
|
|
||||||
self._built_points.append(point)
|
|
||||||
for area in self.areas:
|
|
||||||
area.add_point(point)
|
|
||||||
return [point]
|
|
||||||
|
|
||||||
def build_connections(self):
|
|
||||||
if self._built_is_elevatorlevel:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stuffedareas = shapely_to_mpl(self.level._built_stuffedareas.intersection(self._built_geometry))
|
|
||||||
|
|
||||||
for area in self.areas:
|
|
||||||
area.build_connections()
|
|
||||||
|
|
||||||
def connection_count(self):
|
|
||||||
return np.count_nonzero(self.distances >= 0)
|
|
||||||
|
|
||||||
def finish_build(self):
|
|
||||||
self.areas = tuple(self.areas)
|
|
||||||
self.points = tuple(point.i for point in self._built_points)
|
|
||||||
|
|
||||||
set_points = set(self.points)
|
|
||||||
if len(self.points) != len(set_points):
|
|
||||||
print('ERROR: POINTS DOUBLE-ADDED (ROOM)', len(self.points), len(set_points))
|
|
||||||
|
|
||||||
self.room_transfer_points = tuple(i for i in self.points if i in self.level.room_transfer_points)
|
|
||||||
self.excludables = tuple(self.excludables)
|
|
||||||
|
|
||||||
excludable_points = list()
|
|
||||||
for excludable in self.excludables:
|
|
||||||
points = self.level.arealocation_points[excludable]
|
|
||||||
excludable_points.append(np.array(tuple((i in points) for i in self.points)))
|
|
||||||
self.excludable_points = np.array(excludable_points)
|
|
||||||
|
|
||||||
mapping = {point.i: i for i, point in enumerate(self._built_points)}
|
|
||||||
|
|
||||||
empty = np.empty(shape=(len(self._built_points), len(self._built_points)), dtype=np.float16)
|
|
||||||
empty[:] = np.inf
|
|
||||||
|
|
||||||
ctypes = []
|
|
||||||
distances = {}
|
|
||||||
for from_point in self._built_points:
|
|
||||||
for to_point, connection in from_point.connections.items():
|
|
||||||
if to_point.i in mapping:
|
|
||||||
if connection.ctype not in distances:
|
|
||||||
ctypes.append(connection.ctype)
|
|
||||||
distances[connection.ctype] = empty.copy()
|
|
||||||
distances[connection.ctype][mapping[from_point.i], mapping[to_point.i]] = connection.distance
|
|
||||||
|
|
||||||
self.ctypes = tuple(ctypes)
|
|
||||||
self.distances = np.array(tuple(distances[ctype] for ctype in ctypes))
|
|
||||||
|
|
||||||
for area in self.areas:
|
|
||||||
area.finish_build()
|
|
||||||
|
|
||||||
# Routing
|
|
||||||
router_cache = {}
|
|
||||||
|
|
||||||
def build_router(self, allowed_ctypes, allow_nonpublic, avoid, include):
|
|
||||||
ctypes = tuple(i for i, ctype in enumerate(self.ctypes) if ctype in allowed_ctypes)
|
|
||||||
avoid = tuple(i for i, excludable in enumerate(self.excludables) if excludable in avoid)
|
|
||||||
include = tuple(i for i, excludable in enumerate(self.excludables) if excludable in include)
|
|
||||||
cache_key = ('c3nav__graph__roomrouter__%s__%s__%s__%d__%s__%s' %
|
|
||||||
(self.graph.mtime, self.i, ','.join(str(i) for i in ctypes),
|
|
||||||
allow_nonpublic, ','.join(str(i) for i in avoid), ','.join(str(i) for i in include)))
|
|
||||||
|
|
||||||
roomrouter = self.router_cache.get(cache_key)
|
|
||||||
if not roomrouter:
|
|
||||||
roomrouter = self._build_router(ctypes, allow_nonpublic, avoid, include)
|
|
||||||
self.router_cache[cache_key] = roomrouter
|
|
||||||
return roomrouter
|
|
||||||
|
|
||||||
def _build_router(self, ctypes, allow_nonpublic, avoid, include):
|
|
||||||
ctype_factors = np.ones((len(self.ctypes), 1, 1))*1000
|
|
||||||
ctype_factors[ctypes, :, :] = 1
|
|
||||||
|
|
||||||
if not self.distances.size:
|
|
||||||
return RoomRouter(np.ones((0, 0), dtype=int), np.ones((0, 0), dtype=int))
|
|
||||||
|
|
||||||
distances = np.amin(self.distances*ctype_factors, axis=0).astype(np.float32)
|
|
||||||
factors = np.ones_like(distances, dtype=np.float16)
|
|
||||||
|
|
||||||
if ':nonpublic' in self.excludables and ':nonpublic' not in include:
|
|
||||||
points, = self.excludable_points[self.excludables.index(':nonpublic')].nonzero()
|
|
||||||
factors[points[:, None], :] = 1000 if allow_nonpublic else np.inf
|
|
||||||
factors[:, points] = 1000 if allow_nonpublic else np.inf
|
|
||||||
|
|
||||||
if avoid:
|
|
||||||
points, = self.excludable_points[avoid, :].any(axis=0).nonzero()
|
|
||||||
factors[points[:, None], :] = np.maximum(factors[points[:, None], :], 1000)
|
|
||||||
factors[:, points] = np.maximum(factors[:, points], 1000)
|
|
||||||
|
|
||||||
if include:
|
|
||||||
points, = self.excludable_points[include, :].any(axis=0).nonzero()
|
|
||||||
factors[points[:, None], :] = 1
|
|
||||||
factors[:, points] = 1
|
|
||||||
|
|
||||||
g_sparse = csgraph_from_dense(distances*factors, null_value=np.inf)
|
|
||||||
shortest_paths, predecessors = shortest_path(g_sparse, return_predecessors=True)
|
|
||||||
return RoomRouter(shortest_paths, predecessors)
|
|
||||||
|
|
||||||
def get_connection(self, from_i, to_i):
|
|
||||||
stack = self.distances[:, from_i, to_i]
|
|
||||||
min_i = stack.argmin()
|
|
||||||
distance = stack[min_i]
|
|
||||||
ctype = self.ctypes[min_i]
|
|
||||||
return GraphConnection(self.graph.points[self.points[from_i]], self.graph.points[self.points[to_i]],
|
|
||||||
distance=distance, ctype=ctype)
|
|
||||||
|
|
||||||
def contains_point(self, point):
|
|
||||||
return self.mpl_clear.contains_point(point)
|
|
||||||
|
|
||||||
def connected_points(self, point, mode):
|
|
||||||
connections = {}
|
|
||||||
for area in self.areas:
|
|
||||||
if area.contains_point(point):
|
|
||||||
connections.update(area.connected_points(point, mode))
|
|
||||||
return connections
|
|
||||||
|
|
||||||
def check_connection(self, from_point, to_point):
|
|
||||||
from_point = np.array(from_point)
|
|
||||||
to_point = np.array(to_point)
|
|
||||||
for area in self.areas:
|
|
||||||
if area.contains_point(from_point) and area.contains_point(to_point):
|
|
||||||
there, back, distance = area.check_connection(from_point, to_point)
|
|
||||||
if there is not None:
|
|
||||||
return there
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
RoomRouter = namedtuple('RoomRouter', ('shortest_paths', 'predecessors', ))
|
|
218
src/c3nav/routing/router.py
Normal file
218
src/c3nav/routing/router.py
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
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:
|
||||||
|
def __init__(self):
|
||||||
|
self.nodes = None
|
||||||
|
self.node_coords = None
|
||||||
|
self.node_lookup = None
|
||||||
|
|
||||||
|
@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):
|
||||||
|
graph = 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))
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
|
@ -1,172 +0,0 @@
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
from django.utils.functional import cached_property
|
|
||||||
|
|
||||||
from c3nav.routing.connection import GraphConnection
|
|
||||||
from c3nav.routing.point import GraphPoint
|
|
||||||
from c3nav.routing.route import Route
|
|
||||||
|
|
||||||
|
|
||||||
class RouteSegment(ABC):
|
|
||||||
def __init__(self, routers, router, from_point, to_point):
|
|
||||||
"""
|
|
||||||
:param router: a Router (RoomRouter, GraphRouter, …)
|
|
||||||
:param from_point: in-router index of first point
|
|
||||||
:param to_point: in-router index of last point
|
|
||||||
"""
|
|
||||||
self.routers = routers
|
|
||||||
self.router = router
|
|
||||||
self.from_point = int(from_point)
|
|
||||||
self.to_point = int(to_point)
|
|
||||||
|
|
||||||
def as_route(self):
|
|
||||||
return SegmentRoute([self])
|
|
||||||
|
|
||||||
def _get_points(self):
|
|
||||||
points = [self.to_point]
|
|
||||||
first = self.from_point
|
|
||||||
current = self.to_point
|
|
||||||
while current != first:
|
|
||||||
current = self.router.predecessors[first, current]
|
|
||||||
points.append(current)
|
|
||||||
return tuple(reversed(points))
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_connections(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def distance(self):
|
|
||||||
return self.router.shortest_paths[self.from_point, self.to_point]
|
|
||||||
|
|
||||||
|
|
||||||
class RoomRouteSegment(RouteSegment):
|
|
||||||
def __init__(self, room, routers, from_point, to_point):
|
|
||||||
"""
|
|
||||||
Route segment within a Room
|
|
||||||
:param room: GraphRoom
|
|
||||||
"""
|
|
||||||
super().__init__(routers, routers[room], from_point, to_point)
|
|
||||||
self.room = room
|
|
||||||
self.global_from_point = room.points[from_point]
|
|
||||||
self.global_to_point = room.points[to_point]
|
|
||||||
|
|
||||||
def get_connections(self):
|
|
||||||
points = self._get_points()
|
|
||||||
return tuple(self.room.get_connection(from_point, to_point)
|
|
||||||
for from_point, to_point in zip(points[:-1], points[1:]))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<RoomRouteSegment in %r from points %d to %d with distance %f>' %
|
|
||||||
(self.room, self.from_point, self.to_point, self.distance))
|
|
||||||
|
|
||||||
|
|
||||||
class LevelRouteSegment(RouteSegment):
|
|
||||||
def __init__(self, level, routers, from_point, to_point):
|
|
||||||
"""
|
|
||||||
Route segment within a Level (from room transfer point to room transfer point)
|
|
||||||
:param level: GraphLevel
|
|
||||||
"""
|
|
||||||
super().__init__(routers, routers[level], from_point, to_point)
|
|
||||||
self.level = level
|
|
||||||
self.global_from_point = level.room_transfer_points[from_point]
|
|
||||||
self.global_to_point = level.room_transfer_points[to_point]
|
|
||||||
|
|
||||||
def split(self):
|
|
||||||
segments = []
|
|
||||||
points = self._get_points()
|
|
||||||
for from_point, to_point in zip(points[:-1], points[1:]):
|
|
||||||
room = self.level.rooms[self.router.room_transfers[from_point, to_point]]
|
|
||||||
global_from_point = self.level.room_transfer_points[from_point]
|
|
||||||
global_to_point = self.level.room_transfer_points[to_point]
|
|
||||||
segments.append(RoomRouteSegment(room, self.routers,
|
|
||||||
from_point=room.points.index(global_from_point),
|
|
||||||
to_point=room.points.index(global_to_point)))
|
|
||||||
return tuple(segments)
|
|
||||||
|
|
||||||
def get_connections(self):
|
|
||||||
return sum((segment.get_connections() for segment in self.split()), ())
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<LevelRouteSegment in %r from points %d to %d with distance %f>' %
|
|
||||||
(self.level, self.from_point, self.to_point, self.distance))
|
|
||||||
|
|
||||||
|
|
||||||
class GraphRouteSegment(RouteSegment):
|
|
||||||
def __init__(self, graph, routers, from_point, to_point):
|
|
||||||
"""
|
|
||||||
Route segment within a Graph (from level transfer point to level transfer point)
|
|
||||||
:param graph: Graph
|
|
||||||
"""
|
|
||||||
super().__init__(routers, routers[graph], from_point, to_point)
|
|
||||||
self.graph = graph
|
|
||||||
self.global_from_point = graph.level_transfer_points[from_point]
|
|
||||||
self.global_to_point = graph.level_transfer_points[to_point]
|
|
||||||
|
|
||||||
def split(self):
|
|
||||||
segments = []
|
|
||||||
points = self._get_points()
|
|
||||||
for from_point, to_point in zip(points[:-1], points[1:]):
|
|
||||||
level = tuple(self.graph.levels.values())[self.router.level_transfers[from_point, to_point]]
|
|
||||||
global_from_point = self.graph.level_transfer_points[from_point]
|
|
||||||
global_to_point = self.graph.level_transfer_points[to_point]
|
|
||||||
segments.append(LevelRouteSegment(level, self.routers,
|
|
||||||
from_point=level.room_transfer_points.index(global_from_point),
|
|
||||||
to_point=level.room_transfer_points.index(global_to_point)))
|
|
||||||
return tuple(segments)
|
|
||||||
|
|
||||||
def get_connections(self):
|
|
||||||
return sum((segment.get_connections() for segment in self.split()), ())
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<GraphRouteSegment in %r from points %d to %d with distance %f>' %
|
|
||||||
(self.graph, self.from_point, self.to_point, self.distance))
|
|
||||||
|
|
||||||
|
|
||||||
class SegmentRoute:
|
|
||||||
def __init__(self, segments, distance=None):
|
|
||||||
self.segments = sum(((item.segments if isinstance(item, SegmentRoute) else (item,))
|
|
||||||
for item in segments if item.from_point != item.to_point), ())
|
|
||||||
self.distance = sum(segment.distance for segment in self.segments)
|
|
||||||
self.from_point = segments[0].global_from_point
|
|
||||||
self.to_point = segments[-1].global_to_point
|
|
||||||
self.global_from_point = self.from_point
|
|
||||||
self.global_to_point = self.to_point
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<SegmentedRoute (\n %s\n) distance=%f>' %
|
|
||||||
('\n '.join(repr(segment) for segment in self.segments), self.distance))
|
|
||||||
|
|
||||||
def rawsplit(self):
|
|
||||||
return sum((segment.get_connections() for segment in self.segments), ())
|
|
||||||
|
|
||||||
def split(self):
|
|
||||||
return Route(self.rawsplit())
|
|
||||||
|
|
||||||
|
|
||||||
class SegmentRouteWrapper:
|
|
||||||
def __init__(self, segmentroute: SegmentRoute, orig_point, dest_point, orig_ctype, dest_ctype):
|
|
||||||
self.segmentroute = segmentroute
|
|
||||||
self.orig_point = orig_point
|
|
||||||
self.dest_point = dest_point
|
|
||||||
self.orig_ctype = orig_ctype
|
|
||||||
self.dest_ctype = dest_ctype
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<SegmentedRouteWrapper %s, add_orig_point=%s, add_dest_point=%s>' %
|
|
||||||
(repr(self.segmentroute), repr(self.orig_point), repr(self.dest_point)))
|
|
||||||
|
|
||||||
def split(self):
|
|
||||||
connections = self.segmentroute.rawsplit()
|
|
||||||
|
|
||||||
if self.orig_point:
|
|
||||||
first_point = connections[0].from_point
|
|
||||||
orig_point = GraphPoint(self.orig_point.x, self.orig_point.y, first_point.room)
|
|
||||||
connections = (GraphConnection(orig_point, first_point, ctype=self.orig_ctype),) + connections
|
|
||||||
|
|
||||||
if self.dest_point:
|
|
||||||
last_point = connections[-1].to_point
|
|
||||||
dest_point = GraphPoint(self.dest_point.x, self.dest_point.y, last_point.room)
|
|
||||||
connections = connections + (GraphConnection(last_point, dest_point, ctype=self.dest_ctype), )
|
|
||||||
|
|
||||||
return Route(connections)
|
|
Loading…
Add table
Add a link
Reference in a new issue