add support for connection types and excluding steps_down or steps_up
This commit is contained in:
parent
f6129d621c
commit
5183b8fa7d
11 changed files with 125 additions and 76 deletions
|
@ -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;
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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', ))
|
||||
|
|
|
@ -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__)
|
||||
|
|
28
src/c3nav/site/templates/site/fragment_route.html
Normal file
28
src/c3nav/site/templates/site/fragment_route.html
Normal 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>
|
|
@ -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 %}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue