add support for connection types and excluding steps_down or steps_up

This commit is contained in:
Laura Klünder 2016-12-17 22:15:14 +01:00
parent f6129d621c
commit 5183b8fa7d
11 changed files with 125 additions and 76 deletions

View file

@ -282,7 +282,7 @@ editor = {
'levelconnector': '#FFFF00',
'shadow': '#000000',
'stair': '#FF0000',
'areaofinterest': '#0099FF'
'arealocation': '#0099FF'
},
_line_draw_geometry_style: function(style) {
style.stroke = true;
@ -304,10 +304,10 @@ editor = {
var result = {
stroke: false,
fillColor: editor._geometry_colors[mapitem_type],
fillOpacity: (mapitem_type == 'areaofinterest') ? 0.2 : 0.6,
fillOpacity: (mapitem_type == 'arealocation') ? 0.2 : 0.6,
smoothFactor: 0
};
if (mapitem_type == 'areaofinterest') {
if (mapitem_type == 'arealocation') {
result.fillOpacity = 0.02;
result.color = result.fillColor;
result.stroke = true;

View file

@ -37,12 +37,20 @@ class GraphArea():
# stair checker
angle = coord_angle(point1.xy, point2.xy)
valid = True
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
up = angle_diff < 0 # noqa
new_direction_up = (angle_diff > 0)
if direction_up is None:
direction_up = new_direction_up
elif direction_up != new_direction_up:
valid = False
break
if not (40 < abs(angle_diff) < 150):
valid = False
break
@ -50,8 +58,8 @@ class GraphArea():
if not valid:
continue
point1.connect_to(point2)
point2.connect_to(point1)
point1.connect_to(point2, ctype={True: 'steps_up', False: 'steps_down'}.get(direction_up, ''))
point2.connect_to(point1, ctype={True: 'steps_down', False: 'steps_up'}.get(direction_up, ''))
def add_point(self, point):
if not self.mpl_clear.contains_point(point.xy):

View file

@ -2,10 +2,12 @@ import numpy as np
class GraphConnection():
def __init__(self, from_point, to_point, distance=None):
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
def __repr__(self):
return '<GraphConnection %r %r distance=%f>' % (self.from_point, self.to_point, self.distance)
return ('<GraphConnection %r %r distance=%f ctype=%s>' %
(self.from_point, self.to_point, self.distance, self.ctype))

View file

@ -174,7 +174,7 @@ class Graph:
level.draw_png(points, lines)
# Router
def build_routers(self):
def build_routers(self, allowed_ctypes):
routers = {}
empty_distances = np.empty(shape=(len(self.level_transfer_points),) * 2, dtype=np.float16)
@ -186,7 +186,7 @@ class Graph:
level_transfers[:] = -1
for i, level in enumerate(self.levels.values()):
routers.update(level.build_routers())
routers.update(level.build_routers(allowed_ctypes))
router = routers[level]
level_distances = empty_distances.copy()
@ -220,7 +220,7 @@ class Graph:
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):
def get_route(self, origin: Location, destination: Location, allowed_ctypes):
orig_points_i = set(self.get_location_points(origin))
dest_points_i = set(self.get_location_points(destination))
@ -230,7 +230,7 @@ class Graph:
best_route = NoRoute
# get routers
routers = self.build_routers()
routers = self.build_routers(allowed_ctypes)
# route within room
orig_rooms = set(point.room for point in orig_points)
@ -273,6 +273,7 @@ class Graph:
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}
print(dest_room_transfer_points)
# if the points have common rooms, search for routes within thos levels
if common_levels:
@ -305,10 +306,12 @@ class Graph:
# 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(orig_levels, dest_room_transfers, routers, mode='dest')
print(orig_levels, dest_room_transfers, dest_level_transfers)
# get reachable roomtransfer points for each level (points as room transfer point index within level)
# 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)
print(dest_level_transfer_points)
# search a route within the whole graph
if True:

View file

@ -156,6 +156,12 @@ class GraphLevel():
if mpl_arealocation.contains_point(point.xy))
# Drawing
ctype_colors = {
'': (50, 200, 0),
'steps_up': (255, 50, 50),
'steps_down': (255, 50, 50),
}
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)
@ -163,12 +169,17 @@ class GraphLevel():
im = Image.open(filename)
height = im.size[1]
draw = ImageDraw.Draw(im)
if lines:
for room in self.rooms:
# noinspection PyTypeChecker
for from_i, to_i in np.argwhere(room.distances != np.inf):
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=(255, 100, 100))
self.graph.points[room.points[to_i]], height),
fill=self.ctype_colors[room.ctypes[ctype]])
if room.ctypes[ctype] == 'steps_up':
point = self.graph.points[room.points[from_i]]
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (0, 255, 255))
if points:
for point_i in self.points:
@ -186,7 +197,7 @@ class GraphLevel():
if lines:
for room in self.rooms:
# noinspection PyTypeChecker
for from_i, to_i in np.argwhere(room.distances != np.inf):
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))
@ -194,7 +205,7 @@ class GraphLevel():
im.save(graph_filename)
# Routing
def build_routers(self):
def build_routers(self, allowed_ctypes):
routers = {}
empty_distances = np.empty(shape=(len(self.room_transfer_points),) * 2, dtype=np.float16)
@ -206,7 +217,7 @@ class GraphLevel():
room_transfers[:] = -1
for i, room in enumerate(self.rooms):
router = room.build_router()
router = room.build_router(allowed_ctypes)
routers[room] = router
room_distances = empty_distances.copy()

View file

@ -11,6 +11,7 @@ class GraphPoint():
self.y = y
self.room = room
self.xy = np.array((x, y))
self.i = None
self.connections = {}
self.connections_in = {}
@ -32,8 +33,8 @@ class GraphPoint():
y = self.y * settings.RENDER_SCALE
return ((x-5, y-5), (x+5, y+5))
def connect_to(self, other_point):
connection = GraphConnection(self, other_point)
def connect_to(self, other_point, ctype=''):
connection = GraphConnection(self, other_point, ctype=ctype)
self.connections[other_point] = connection
other_point.connections_in[self] = connection

View file

@ -22,10 +22,12 @@ class GraphRoom():
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
def serialize(self):
return (
@ -34,12 +36,13 @@ class GraphRoom():
self.points,
self.room_transfer_points,
self.distances,
self.ctypes,
)
@classmethod
def unserialize(cls, level, data):
room = cls(level)
room.mpl_clear, areas, room.points, room.room_transfer_points, room.distances = data
room.mpl_clear, areas, room.points, room.room_transfer_points, room.distances, room.ctypes = data
room.areas = tuple(GraphArea(room, *area) for area in areas)
return room
@ -183,6 +186,8 @@ class GraphRoom():
return [point]
def build_connections(self):
print('\n\n')
print('room')
for area in self.areas:
area.build_connections()
@ -195,32 +200,49 @@ class GraphRoom():
self.room_transfer_points = tuple(i for i in self.points if i in self.level.room_transfer_points)
mapping = {point.i: i for i, point in enumerate(self._built_points)}
self.distances = np.empty(shape=(len(self._built_points), len(self._built_points)), dtype=np.float16)
self.distances[:] = np.inf
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:
self.distances[mapping[from_point.i], mapping[to_point.i]] = connection.distance
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
def build_router(self):
cache_key = 'c3nav__graph__roomrouter__%s__%s' % (self.graph.mtime, self.i)
def build_router(self, allowed_ctypes):
ctypes = tuple(i for i, ctype in enumerate(self.ctypes) if ctype in allowed_ctypes)
cache_key = ('c3nav__graph__roomrouter__%s__%s__%s' %
(self.graph.mtime, self.i, ','.join(str(i) for i in ctypes)))
roomrouter = cache.get(cache_key)
if not roomrouter:
roomrouter = self._build_router()
roomrouter = self._build_router(ctypes)
cache.set(cache_key, roomrouter, 600)
return roomrouter
def _build_router(self):
g_sparse = csgraph_from_dense(self.distances, null_value=np.inf)
def _build_router(self, ctypes):
g_sparse = csgraph_from_dense(np.amin(self.distances[ctypes, :, :], 0), 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):
return GraphConnection(self.graph.points[self.points[from_i]], self.graph.points[self.points[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)
RoomRouter = namedtuple('RoomRouter', ('shortest_paths', 'predecessors', ))

View file

@ -41,36 +41,36 @@ class RoutePart:
self.level_name = level.level.name
self.connections = connections
width, height = get_dimensions()
svg_width, svg_height = get_dimensions()
points = (connections[0].from_point, ) + tuple(connection.to_point for connection in connections)
for point in points:
point.svg_x = point.x * 6
point.svg_y = (height - point.y) * 6
point.svg_y = (svg_height - point.y) * 6
x, y = zip(*((point.svg_x, point.svg_y) for point in points if point.level == level))
self.distance = sum(connection.distance for connection in connections)
# bounds for rendering
self.min_x = min(x) - 20
self.max_x = max(x) + 20
self.min_y = min(y) - 20
self.max_y = max(y) + 20
self.svg_min_x = min(x) - 20
self.svg_max_x = max(x) + 20
self.svg_min_y = min(y) - 20
self.svg_max_y = max(y) + 20
width = self.max_x - self.min_x
height = self.max_y - self.min_y
svg_width = self.svg_max_x - self.svg_min_x
svg_height = self.svg_max_y - self.svg_min_y
if width < 150:
self.min_x -= (10 - width) / 2
self.max_x += (10 - width) / 2
if svg_width < 150:
self.svg_min_x -= (10 - svg_width) / 2
self.svg_max_x += (10 - svg_width) / 2
if height < 150:
self.min_y -= (10 - height) / 2
self.max_y += (10 - height) / 2
if svg_height < 150:
self.svg_min_y += (10 - svg_height) / 2
self.svg_max_y -= (10 - svg_height) / 2
self.width = self.max_x - self.min_x
self.height = self.max_y - self.min_y
self.svg_width = self.svg_max_x - self.svg_min_x
self.svg_height = self.svg_max_y - self.svg_min_y
def __str__(self):
return repr(self.__dict__)

View file

@ -0,0 +1,28 @@
{% load route_render %}
<h2>Your Route</h2>
<div class="routeparts">
{% for routepart in route.routeparts %}
<svg xmlns="http://www.w3.org/2000/svg" class="map" data-level="{{ routepart.level_name }}"
viewBox="0 0 {{ routepart.svg_width }} {{ routepart.svg_height }}"
style="max-height:{% if routepart.svg_height > 300 %}300{% else %}{{ routepart.svg_height }}{% endif %}px">
<defs>
<marker id="arrow-{{ forloop.counter0 }}" markerWidth="4" markerHeight="4" refX="2.5" refY="2" orient="auto">
<path d="M0,0 L3,2 L0,4 L0,0" style="fill: #FF0000; stroke: 0;"></path>
</marker>
</defs>
<image width="{{ svg_width }}" height="{{ svg_height }}" x="{{ routepart.svg_min_x | negate }}" y="{{ routepart.svg_min_y | negate }}" xlink:href="/map/{{ routepart.level_name }}.png"></image>
<g class="connections">
{% for c in routepart.connections %}
<line x1="{{ c.from_point.svg_x | subtract:routepart.svg_min_x }}"
y1="{{ c.from_point.svg_y | subtract:routepart.svg_min_y }}"
x2="{{ c.to_point.svg_x | subtract:routepart.svg_min_x }}"
y2="{{ c.to_point.svg_y | subtract:routepart.svg_min_y }}"
marker-end="url(#arrow-{{ forloop.parentloop.counter0 }})"></line>
{% endfor %}
</g>
</svg>
{% endfor %}
</div>

View file

@ -2,7 +2,6 @@
{% load static %}
{% load i18n %}
{% load route_render %}
{% block content %}
<form>
@ -15,32 +14,7 @@
</div>
</form>
{% if route %}
<h2>Your Route</h2>
<div class="routeparts">
{% for routepart in route.routeparts %}
<svg xmlns="http://www.w3.org/2000/svg" class="map" data-level="{{ routepart.level_name }}"
viewBox="0 0 {{ routepart.width }} {{ routepart.height }}"
style="max-height:{% if routepart.height > 300 %}300{% else %}{{ routepart.height }}{% endif %}px">
<defs>
<marker id="arrow-{{ forloop.counter0 }}" markerWidth="4" markerHeight="4" refX="2.5" refY="2" orient="auto">
<path d="M0,0 L3,2 L0,4 L0,0" style="fill: #FF0000; stroke: 0;"></path>
</marker>
</defs>
<image width="{{ svg_width }}" height="{{ svg_height }}" x="{{ routepart.min_x | negate }}" y="{{ routepart.min_y | negate }}" xlink:href="/map/{{ routepart.level_name }}.png"></image>
<g class="connections">
{% for c in routepart.connections %}
<line x1="{{ c.from_point.svg_x | subtract:routepart.min_x }}"
y1="{{ c.from_point.svg_y | subtract:routepart.min_y }}"
x2="{{ c.to_point.svg_x | subtract:routepart.min_x }}"
y2="{{ c.to_point.svg_y | subtract:routepart.min_y }}"
marker-end="url(#arrow-{{ forloop.parentloop.counter0 }})"></line>
{% endfor %}
</g>
</svg>
{% endfor %}
</div>
{% endif %}
{% if route %}
{% include 'site/fragment_route.html' %}
{% endif %}
{% endblock %}

View file

@ -42,7 +42,7 @@ def main(request, origin=None, destination=None):
route = None
if origin and destination:
graph = Graph.load()
route = graph.get_route(origin, destination)
route = graph.get_route(origin, destination, ('', 'steps_down', 'steps_up'))
route = route.split()
print(route)