2016-12-19 15:11:11 +01:00
|
|
|
import copy
|
|
|
|
|
2016-12-17 13:24:42 +01:00
|
|
|
import numpy as np
|
2016-12-19 15:11:11 +01:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-19 17:53:10 +01:00
|
|
|
from c3nav.mapdata.models import AreaLocation
|
2016-12-17 19:25:27 +01:00
|
|
|
from c3nav.mapdata.utils.misc import get_dimensions
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-17 19:25:27 +01:00
|
|
|
class Route:
|
|
|
|
def __init__(self, connections, distance=None):
|
|
|
|
self.connections = tuple(connections)
|
|
|
|
self.distance = sum(connection.distance for connection in self.connections)
|
|
|
|
self.from_point = connections[0].from_point
|
|
|
|
self.to_point = connections[-1].to_point
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-21 03:21:31 +01:00
|
|
|
self.ctypes_exception = None
|
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
self.routeparts = None
|
|
|
|
|
2016-12-17 13:24:42 +01:00
|
|
|
def __repr__(self):
|
2016-12-17 19:25:27 +01:00
|
|
|
return ('<Route (\n %s\n) distance=%f>' %
|
|
|
|
('\n '.join(repr(connection) for connection in self.connections), self.distance))
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-21 01:17:40 +01:00
|
|
|
def split(self):
|
|
|
|
return self
|
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
def create_routeparts(self):
|
2016-12-17 19:25:27 +01:00
|
|
|
routeparts = []
|
|
|
|
connections = []
|
2016-12-19 12:24:07 +01:00
|
|
|
add_connections = []
|
2016-12-17 19:25:27 +01:00
|
|
|
level = self.connections[0].from_point.level
|
|
|
|
|
|
|
|
for connection in self.connections:
|
2016-12-19 15:11:11 +01:00
|
|
|
connections.append(RouteLine(connection))
|
2016-12-17 19:25:27 +01:00
|
|
|
point = connection.to_point
|
|
|
|
if point.level and point.level != level:
|
2016-12-19 12:24:07 +01:00
|
|
|
if routeparts:
|
2016-12-19 15:11:11 +01:00
|
|
|
routeparts[-1].lines.extend(connections[:1])
|
2016-12-19 12:24:07 +01:00
|
|
|
routeparts.append(RoutePart(level, add_connections+connections))
|
2016-12-17 19:25:27 +01:00
|
|
|
level = point.level
|
2016-12-19 15:11:11 +01:00
|
|
|
add_connections = [copy.copy(line) for line in connections[-3:]]
|
2016-12-19 12:24:07 +01:00
|
|
|
connections = []
|
2016-12-17 19:25:27 +01:00
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
if connections or add_connections:
|
2016-12-19 12:24:07 +01:00
|
|
|
if routeparts:
|
2016-12-19 15:11:11 +01:00
|
|
|
routeparts[-1].lines.extend(connections[:1])
|
2016-12-19 12:24:07 +01:00
|
|
|
routeparts.append(RoutePart(level, add_connections+connections))
|
|
|
|
|
|
|
|
routeparts = [routepart for routepart in routeparts if not routepart.level.intermediate]
|
|
|
|
|
|
|
|
for routepart in routeparts:
|
|
|
|
routepart.render_svg_coordinates()
|
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
self.routeparts = routeparts
|
|
|
|
|
2016-12-19 17:53:10 +01:00
|
|
|
@staticmethod
|
|
|
|
def describe_point(point):
|
|
|
|
locations = sorted(AreaLocation.objects.filter(location_type__in=('room', 'level', 'area'),
|
2016-12-21 00:24:56 +01:00
|
|
|
name__in=point.arealocations, can_describe=True),
|
2016-12-19 18:13:14 +01:00
|
|
|
key=AreaLocation.get_sort_key, reverse=True)
|
2016-12-19 17:53:10 +01:00
|
|
|
|
|
|
|
if not locations:
|
|
|
|
return _('Unknown Location'), _('Unknown Location')
|
|
|
|
elif locations[0].location_type == 'level':
|
|
|
|
return _('Unknown Location'), locations[0].title
|
|
|
|
else:
|
|
|
|
return locations[0].title, locations[0].subtitle
|
|
|
|
|
2016-12-24 01:41:32 +01:00
|
|
|
def describe(self, allowed_ctypes):
|
|
|
|
self.create_routeparts()
|
|
|
|
|
|
|
|
for i, routepart in enumerate(self.routeparts):
|
2016-12-19 15:11:11 +01:00
|
|
|
for j, line in enumerate(routepart.lines):
|
|
|
|
from_room = line.from_point.room
|
|
|
|
to_room = line.to_point.room
|
|
|
|
|
|
|
|
if i and not j:
|
|
|
|
line.ignore = True
|
|
|
|
|
|
|
|
line.turning = ''
|
|
|
|
|
|
|
|
if j:
|
|
|
|
line.angle_change = (line.angle - routepart.lines[j - 1].angle + 180) % 360 - 180
|
|
|
|
|
|
|
|
if 20 < line.angle_change <= 75:
|
|
|
|
line.turning = 'light_right'
|
|
|
|
elif -75 <= line.angle_change < -20:
|
|
|
|
line.turning = 'light_left'
|
|
|
|
elif 75 < line.angle_change:
|
|
|
|
line.turning = 'right'
|
|
|
|
elif line.angle_change < -75:
|
|
|
|
line.turning = 'left'
|
|
|
|
|
|
|
|
line.icon = line.ctype or line.turning
|
|
|
|
|
2016-12-19 17:53:10 +01:00
|
|
|
distance = line.distance
|
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
if from_room is None:
|
|
|
|
line.arrow = True
|
2016-12-19 17:53:10 +01:00
|
|
|
if j+1 < len(routepart.lines) and routepart.lines[j+1].ctype_main == 'elevator':
|
|
|
|
line.ignore = True
|
|
|
|
elif j > 0 and (routepart.lines[j-1].ignore or routepart.lines[j-1].ctype_main == 'elevator'):
|
|
|
|
line.ignore = True
|
|
|
|
else:
|
|
|
|
line.icon = 'location'
|
|
|
|
line.title, line.description = self.describe_point(line.to_point)
|
2016-12-19 15:11:11 +01:00
|
|
|
|
2016-12-19 17:53:10 +01:00
|
|
|
elif line.ctype_main in ('stairs', 'escalator', 'elevator'):
|
2016-12-19 15:11:11 +01:00
|
|
|
line.description = {
|
|
|
|
'stairs_up': _('Go up the stairs.'),
|
|
|
|
'stairs_down': _('Go down the stairs.'),
|
|
|
|
'escalator_up': _('Take the escalator upwards.'),
|
|
|
|
'escalator_down': _('Take the escalator downwards.'),
|
|
|
|
'elevator_up': _('Take the elevator upwards.'),
|
|
|
|
'elevator_down': _('Take the elevator downwards.')
|
|
|
|
}.get(line.ctype)
|
|
|
|
|
|
|
|
if line.ctype_main == 'elevator':
|
|
|
|
if from_room is None or (to_room is None and from_room.level.level != routepart.level):
|
|
|
|
line.ignore = True
|
|
|
|
line.arrow = False
|
|
|
|
|
|
|
|
elif to_room is None:
|
|
|
|
if from_room is not None and from_room.level.level.intermediate:
|
|
|
|
line.ignore = True
|
|
|
|
|
|
|
|
if j > 0:
|
|
|
|
if routepart.lines[j-1].ctype_main == 'elevator':
|
|
|
|
line.arrow = False
|
2016-12-19 17:53:10 +01:00
|
|
|
line.ignore = True
|
2016-12-19 15:11:11 +01:00
|
|
|
|
|
|
|
if j+1 < len(routepart.lines):
|
|
|
|
if routepart.lines[j+1].to_point.room.level.level.intermediate:
|
|
|
|
line.ignore = True
|
|
|
|
|
|
|
|
if j+2 < len(routepart.lines):
|
|
|
|
if routepart.lines[j+2].ctype_main == 'elevator':
|
|
|
|
line.ignore = True
|
|
|
|
|
|
|
|
line.description = {
|
|
|
|
'left': _('Go through the door on the left.'),
|
|
|
|
'right': _('Go through the door on the right.'),
|
|
|
|
}.get(line.turning.split('_')[-1], _('Go through the door.'))
|
|
|
|
|
|
|
|
line.arrow = False
|
|
|
|
|
|
|
|
else:
|
|
|
|
if j > 0:
|
|
|
|
last = routepart.lines[j-1]
|
|
|
|
if last.can_merge_to_next:
|
|
|
|
if last.turning == '' and (line.turning == '' or last.desc_distance < 1):
|
|
|
|
last.ignore = True
|
|
|
|
last.arrow = False
|
|
|
|
distance += last.desc_distance
|
|
|
|
elif last.turning.endswith('right') and line.turning.endswith('right'):
|
|
|
|
last.ignore = True
|
|
|
|
last.arrow = False
|
|
|
|
line.turning = 'right'
|
|
|
|
distance += last.desc_distance
|
|
|
|
elif last.turning.endswith('left') and line.turning.endswith('left'):
|
|
|
|
last.ignore = True
|
|
|
|
last.arrow = False
|
|
|
|
line.turning = 'left'
|
|
|
|
distance += last.desc_distance
|
2016-12-19 16:53:58 +01:00
|
|
|
elif line.turning == '':
|
2016-12-19 15:11:11 +01:00
|
|
|
last.ignore = True
|
|
|
|
last.arrow = False
|
2016-12-19 16:53:58 +01:00
|
|
|
line.turning = last.turning
|
2016-12-19 15:11:11 +01:00
|
|
|
distance += last.desc_distance
|
|
|
|
|
|
|
|
line.description = {
|
|
|
|
'light_left': _('Turn light to the left and continue for %(d).1f meters.') % {'d': distance},
|
|
|
|
'light_right': _('Turn light to the right and continue for %(d).1f meters.') % {'d': distance},
|
|
|
|
'left': _('Turn left and continue for %(d).1f meters.') % {'d': distance},
|
|
|
|
'right': _('Turn right and continue for %(d).1f meters.') % {'d': distance}
|
|
|
|
}.get(line.turning, _('Continue for %(d).1f meters.') % {'d': distance})
|
|
|
|
|
|
|
|
if distance < 0.2:
|
|
|
|
line.ignore = True
|
|
|
|
line.can_merge_to_next = True
|
|
|
|
|
|
|
|
line.desc_distance = distance
|
|
|
|
|
2016-12-19 17:53:10 +01:00
|
|
|
# line.ignore = False
|
2016-12-19 15:11:11 +01:00
|
|
|
if line.ignore:
|
|
|
|
line.icon = None
|
|
|
|
line.description = None
|
|
|
|
line.desc_distance = None
|
|
|
|
line.can_merge_to_next = False
|
|
|
|
|
|
|
|
if line.arrow is None:
|
|
|
|
line.arrow = not line.ignore
|
|
|
|
|
|
|
|
last_lines = [line for line in routepart.lines if line.ctype_main != 'elevator']
|
|
|
|
if len(last_lines) > 1:
|
|
|
|
last_lines[-1].arrow = True
|
2016-12-17 19:25:27 +01:00
|
|
|
|
2016-12-19 17:53:10 +01:00
|
|
|
unignored_lines = [i for i, line in enumerate(routepart.lines) if line.description]
|
|
|
|
if unignored_lines:
|
|
|
|
first_unignored_i = unignored_lines[0]
|
|
|
|
if first_unignored_i > 0:
|
|
|
|
first_unignored = routepart.lines[first_unignored_i]
|
|
|
|
point = first_unignored.from_point if first_unignored.from_point.room else first_unignored.to_point
|
|
|
|
|
|
|
|
line = routepart.lines[first_unignored_i-1]
|
|
|
|
line.ignore = False
|
|
|
|
line.icon = 'location'
|
|
|
|
line.title, line.description = self.describe_point(point)
|
|
|
|
|
2016-12-24 01:41:32 +01:00
|
|
|
last_line = self.routeparts[-1].lines[-1]
|
2016-12-19 17:53:10 +01:00
|
|
|
if last_line.icon == 'location':
|
|
|
|
last_line.ignore = True
|
|
|
|
|
2016-12-24 01:41:32 +01:00
|
|
|
# check allowed ctypes
|
2016-12-21 03:21:31 +01:00
|
|
|
allowed_ctypes = set(allowed_ctypes)
|
|
|
|
self.ctypes_exception = False
|
|
|
|
for connection in self.connections:
|
|
|
|
if connection.ctype not in allowed_ctypes:
|
|
|
|
self.ctypes_exception = True
|
2016-12-24 01:41:32 +01:00
|
|
|
break
|
2016-12-21 03:21:31 +01:00
|
|
|
|
2016-12-17 19:25:27 +01:00
|
|
|
|
|
|
|
class RoutePart:
|
2016-12-19 15:11:11 +01:00
|
|
|
def __init__(self, graphlevel, lines):
|
2016-12-19 12:24:07 +01:00
|
|
|
self.graphlevel = graphlevel
|
|
|
|
self.level = graphlevel.level
|
2016-12-19 15:11:11 +01:00
|
|
|
self.lines = lines
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-19 12:24:07 +01:00
|
|
|
def render_svg_coordinates(self):
|
2016-12-17 22:15:14 +01:00
|
|
|
svg_width, svg_height = get_dimensions()
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
points = (self.lines[0].from_point,) + tuple(connection.to_point for connection in self.lines)
|
2016-12-17 19:25:27 +01:00
|
|
|
for point in points:
|
|
|
|
point.svg_x = point.x * 6
|
2016-12-17 22:15:14 +01:00
|
|
|
point.svg_y = (svg_height - point.y) * 6
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-19 12:24:07 +01:00
|
|
|
x, y = zip(*((point.svg_x, point.svg_y) for point in points if point.level == self.graphlevel))
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-19 15:11:11 +01:00
|
|
|
self.distance = sum(connection.distance for connection in self.lines)
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-17 19:25:27 +01:00
|
|
|
# bounds for rendering
|
2016-12-17 22:15:14 +01:00
|
|
|
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
|
2016-12-17 13:24:42 +01:00
|
|
|
|
2016-12-17 22:15:14 +01:00
|
|
|
svg_width = self.svg_max_x - self.svg_min_x
|
|
|
|
svg_height = self.svg_max_y - self.svg_min_y
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-17 22:15:14 +01:00
|
|
|
if svg_width < 150:
|
|
|
|
self.svg_min_x -= (10 - svg_width) / 2
|
|
|
|
self.svg_max_x += (10 - svg_width) / 2
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-17 22:15:14 +01:00
|
|
|
if svg_height < 150:
|
|
|
|
self.svg_min_y += (10 - svg_height) / 2
|
|
|
|
self.svg_max_y -= (10 - svg_height) / 2
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-17 22:15:14 +01:00
|
|
|
self.svg_width = self.svg_max_x - self.svg_min_x
|
|
|
|
self.svg_height = self.svg_max_y - self.svg_min_y
|
2016-12-17 19:25:27 +01:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return repr(self.__dict__)
|
|
|
|
|
|
|
|
|
|
|
|
class RouteLine:
|
2016-12-19 15:11:11 +01:00
|
|
|
def __init__(self, connection):
|
|
|
|
self.from_point = connection.from_point
|
|
|
|
self.to_point = connection.to_point
|
|
|
|
self.distance = connection.distance
|
|
|
|
self.ctype = connection.ctype
|
|
|
|
self.angle = connection.angle
|
|
|
|
|
|
|
|
self.ctype_main = self.ctype.split('_')[0]
|
|
|
|
self.ctype_direction = self.ctype.split('_')[-1]
|
|
|
|
|
|
|
|
self.ignore = False
|
|
|
|
self.arrow = None
|
|
|
|
self.angle_change = None
|
|
|
|
self.can_merge_to_next = False
|
|
|
|
|
|
|
|
self.icon = None
|
2016-12-19 17:53:10 +01:00
|
|
|
self.title = None
|
2016-12-19 15:11:11 +01:00
|
|
|
self.description = None
|
2016-12-17 14:46:15 +01:00
|
|
|
|
2016-12-17 13:24:42 +01:00
|
|
|
|
|
|
|
class NoRoute:
|
|
|
|
distance = np.inf
|