From b669e75af13c68cb245dbc5daf91cb1eea701f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Mon, 19 Dec 2016 15:11:11 +0100 Subject: [PATCH] route descriptions --- src/c3nav/locale/de/LC_MESSAGES/django.po | 81 ++++++++- src/c3nav/routing/connection.py | 7 + src/c3nav/routing/graph.py | 22 ++- src/c3nav/routing/point.py | 4 +- src/c3nav/routing/route.py | 171 ++++++++++++++++-- src/c3nav/site/static/site/css/c3nav.css | 75 +++++++- .../site/static/site/img/icons/steps-down.svg | 90 --------- .../site/static/site/img/icons/steps-up.svg | 1 - .../site/templates/site/fragment_route.html | 43 ++++- src/c3nav/site/views.py | 5 +- 10 files changed, 359 insertions(+), 140 deletions(-) delete mode 100644 src/c3nav/site/static/site/img/icons/steps-down.svg delete mode 100644 src/c3nav/site/static/site/img/icons/steps-up.svg diff --git a/src/c3nav/locale/de/LC_MESSAGES/django.po b/src/c3nav/locale/de/LC_MESSAGES/django.po index 419bd67c..b7d1fa79 100644 --- a/src/c3nav/locale/de/LC_MESSAGES/django.po +++ b/src/c3nav/locale/de/LC_MESSAGES/django.po @@ -7,15 +7,15 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-12-19 00:34+0000\n" -"PO-Revision-Date: 2016-12-19 01:53+0100\n" +"POT-Creation-Date: 2016-12-19 14:10+0000\n" +"PO-Revision-Date: 2016-12-19 15:11+0100\n" +"Last-Translator: Laura Klünder \n" +"Language-Team: \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Last-Translator: Laura Klünder \n" -"Language-Team: \n" "X-Generator: Poedit 1.8.11\n" #: c3nav/editor/forms.py:94 @@ -288,11 +288,72 @@ msgstr "Vorlagen" msgid "This Source belongs to a package you don't have access to." msgstr "Diese Vorlage gehört zu einem Paket, auf das du kein Zugriff hast." -#: c3nav/settings.py:198 +#: c3nav/routing/route.py:86 +msgid "Go up the stairs." +msgstr "Geh die Treppe nach oben." + +#: c3nav/routing/route.py:87 +msgid "Go down the stairs." +msgstr "Geh die Treppe nach unten." + +#: c3nav/routing/route.py:88 +msgid "Take the escalator upwards." +msgstr "Fahr die Rolltreppe hoch." + +#: c3nav/routing/route.py:89 +msgid "Take the escalator downwards." +msgstr "Fahr die Rolltreppe runter." + +#: c3nav/routing/route.py:90 +msgid "Take the elevator upwards." +msgstr "Fahr mit dem Aufzug nach oben." + +#: c3nav/routing/route.py:91 +msgid "Take the elevator downwards." +msgstr "Fahr mit den Aufzug nach unten." + +#: c3nav/routing/route.py:116 +msgid "Go through the door on the left." +msgstr "Geh links durch die Tür." + +#: c3nav/routing/route.py:117 +msgid "Go through the door on the right." +msgstr "Geh rechts durch die Tür." + +#: c3nav/routing/route.py:118 +msgid "Go through the door." +msgstr "Geh durch die Tür." + +#: c3nav/routing/route.py:147 +#, python-format +msgid "Turn light to the left and continue for %(d).1f meters." +msgstr "Gehe schräg nach links %(d).1f m weiter." + +#: c3nav/routing/route.py:148 +#, python-format +msgid "Turn light to the right and continue for %(d).1f meters." +msgstr "Gehe schräg nach rechts %(d).1f m weiter." + +#: c3nav/routing/route.py:149 +#, python-format +msgid "Turn left and continue for %(d).1f meters." +msgstr "Gehe nach links %(d).1f m weiter." + +#: c3nav/routing/route.py:150 +#, python-format +msgid "Turn right and continue for %(d).1f meters." +msgstr "Gehe nach rechts %(d).1f m weiter." + +#: c3nav/routing/route.py:151 +#, python-format +msgid "Continue for %(d).1f meters." +msgstr "Gehe %(d).1f m weiter geradeaus." + +#: c3nav/settings.py:199 msgid "English" msgstr "Englisch" -#: c3nav/settings.py:199 +#: c3nav/settings.py:200 msgid "German" msgstr "Deutsch" @@ -300,6 +361,14 @@ msgstr "Deutsch" msgid "Search any Location…" msgstr "Tipp einen beliebigen Ort ein…" +#: c3nav/site/templates/site/fragment_route.html:5 +msgid "Your Route" +msgstr "Deine Route" + +#: c3nav/site/templates/site/fragment_route.html:50 +msgid "You have reached your destination." +msgstr "Du hast dein Ziel erreich." + #: c3nav/site/templates/site/main.html:10 msgid "Origin" msgstr "Start" diff --git a/src/c3nav/routing/connection.py b/src/c3nav/routing/connection.py index fe8c20a7..e4158bd2 100644 --- a/src/c3nav/routing/connection.py +++ b/src/c3nav/routing/connection.py @@ -1,4 +1,7 @@ import numpy as np +from django.utils.functional import cached_property + +from c3nav.routing.utils.coords import coord_angle class GraphConnection(): @@ -8,6 +11,10 @@ class GraphConnection(): 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 ('' % (self.from_point, self.to_point, self.distance, self.ctype)) diff --git a/src/c3nav/routing/graph.py b/src/c3nav/routing/graph.py index bb0e75ac..0ece45aa 100644 --- a/src/c3nav/routing/graph.py +++ b/src/c3nav/routing/graph.py @@ -123,22 +123,24 @@ class Graph: point1 = self._built_elevatorlevel_points[level1.name] point2 = self._built_elevatorlevel_points[level2.name] - center_point = GraphPoint((point1.x+point2.x)/2, (point1.y+point2.y)/2, None) - self.points.append(center_point) - self._built_level_transfer_points.append(center_point) + 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_point) - room.level._built_room_transfer_points.append(center_point) - room.level._built_points.append(center_point) + 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 - point1.connect_to(center_point, ctype=('elevator_up' if direction_up else 'elevator_down')) - center_point.connect_to(point2, ctype=('elevator_up' if direction_up else 'elevator_down')) + dist = abs(level2.altitude-level1.altitude) - point2.connect_to(center_point, ctype=('elevator_down' if direction_up else 'elevator_up')) - center_point.connect_to(point1, ctype=('elevator_down' if direction_up else 'elevator_up')) + 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): diff --git a/src/c3nav/routing/point.py b/src/c3nav/routing/point.py index b0d160e3..d31fb1c3 100644 --- a/src/c3nav/routing/point.py +++ b/src/c3nav/routing/point.py @@ -33,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, ctype=''): - connection = GraphConnection(self, other_point, ctype=ctype) + 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 diff --git a/src/c3nav/routing/route.py b/src/c3nav/routing/route.py index 16732c5c..4e2ce5d8 100644 --- a/src/c3nav/routing/route.py +++ b/src/c3nav/routing/route.py @@ -1,5 +1,7 @@ +import copy + import numpy as np -from django.utils.functional import cached_property +from django.utils.translation import ugettext_lazy as _ from c3nav.mapdata.utils.misc import get_dimensions @@ -11,31 +13,32 @@ class Route: self.from_point = connections[0].from_point self.to_point = connections[-1].to_point + self.routeparts = None + def __repr__(self): return ('' % ('\n '.join(repr(connection) for connection in self.connections), self.distance)) - @cached_property - def routeparts(self): + def create_routeparts(self): routeparts = [] connections = [] add_connections = [] level = self.connections[0].from_point.level for connection in self.connections: - connections.append(connection) + connections.append(RouteLine(connection)) point = connection.to_point if point.level and point.level != level: if routeparts: - routeparts[-1].connections.extend(connections[:1]) + routeparts[-1].lines.extend(connections[:1]) routeparts.append(RoutePart(level, add_connections+connections)) level = point.level - add_connections = connections[-3:] + add_connections = [copy.copy(line) for line in connections[-3:]] connections = [] - if connections: + if connections or add_connections: if routeparts: - routeparts[-1].connections.extend(connections[:1]) + routeparts[-1].lines.extend(connections[:1]) routeparts.append(RoutePart(level, add_connections+connections)) routeparts = [routepart for routepart in routeparts if not routepart.level.intermediate] @@ -43,26 +46,147 @@ class Route: for routepart in routeparts: routepart.render_svg_coordinates() - return tuple(routeparts) + self.describe(routeparts) + + self.routeparts = routeparts + + def describe(self, routeparts): + for i, routepart in enumerate(routeparts): + 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 + + if from_room is None: + line.ignore = True + line.arrow = True + + distance = line.distance + + if line.ctype_main in ('stairs', 'escalator', 'elevator'): + 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 + + 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 + elif last.turning.endswith('left') and line.turning.endswith('left'): + last.ignore = True + last.arrow = False + line.turning = 'left' + 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 + + 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 class RoutePart: - def __init__(self, graphlevel, connections): + def __init__(self, graphlevel, lines): self.graphlevel = graphlevel self.level = graphlevel.level - self.connections = connections + self.lines = lines def render_svg_coordinates(self): svg_width, svg_height = get_dimensions() - points = (self.connections[0].from_point, ) + tuple(connection.to_point for connection in self.connections) + points = (self.lines[0].from_point,) + tuple(connection.to_point for connection in self.lines) for point in points: point.svg_x = point.x * 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 == self.graphlevel)) - self.distance = sum(connection.distance for connection in self.connections) + self.distance = sum(connection.distance for connection in self.lines) # bounds for rendering self.svg_min_x = min(x) - 20 @@ -89,10 +213,23 @@ class RoutePart: class RouteLine: - def __init__(self, from_point, to_point, distance): - self.from_point = from_point - self.to_point = to_point - self.distance = distance + 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 + self.description = None class NoRoute: diff --git a/src/c3nav/site/static/site/css/c3nav.css b/src/c3nav/site/static/site/css/c3nav.css index 5b507f37..c3680005 100644 --- a/src/c3nav/site/static/site/css/c3nav.css +++ b/src/c3nav/site/static/site/css/c3nav.css @@ -31,7 +31,7 @@ body { height:48px; } .locationselect .icons .reset { - background-image:url('/static/site/img/icons/cross.svg'); + background-image:url('../img/icons/cross.svg'); } @@ -95,6 +95,7 @@ svg { width:100%; height:auto; vertical-align:bottom; + margin-bottom:10px; } line { stroke:#FF0000; @@ -109,3 +110,75 @@ circle.pos { stroke-width:10px; stroke:rgba(51, 153, 255, 0.2); } + +.routepart { + margin-bottom:20px; +} + +.desc { + line-height:35px; + position:relative; + min-height:35px; + margin-bottom:8px; +} +.desc .icon { + display:inline-block; + vertical-align:middle; + width:35px; + height:35px; + text-align:center; + + background-image:url('../img/icons/arrow.svg'); + background-repeat: no-repeat; + background-size: contain; + background-position:center; +} +.desc .icon.light_right { + transform: rotate(45deg); +} +.desc .icon.light_left { + transform: rotate(-45deg); +} +.desc .icon.right { + transform: rotate(90deg); +} +.desc .icon.left { + transform: rotate(-90deg); +} +.desc .icon.location { + background-image:url('../img/icons/location.svg'); +} +.desc .icon.stairs_up { + background-image:url('../img/icons/stairs-up.svg'); +} +.desc .icon.stairs_down { + background-image:url('../img/icons/stairs-down.svg'); +} +.desc .icon.escalator_up { + background-image:url('../img/icons/escalator-up.svg'); +} +.desc .icon.escalator_down { + background-image:url('../img/icons/escalator-down.svg'); +} +.desc .icon.elevator_up { + background-image:url('../img/icons/elevator-up.svg'); +} +.desc .icon.elevator_down { + background-image:url('../img/icons/elevator-down.svg'); +} +.desc .icon.destination { + background-image:url('../img/icons/destination.svg'); +} + +.desc .icon img { + max-width:35px; +} +.desc p { + white-space:normal; + display:inline-block; + padding-left:8px; + margin:0; + box-sizing:border-box; + width:auto; + vertical-align:middle; +} diff --git a/src/c3nav/site/static/site/img/icons/steps-down.svg b/src/c3nav/site/static/site/img/icons/steps-down.svg deleted file mode 100644 index 5deddb9a..00000000 --- a/src/c3nav/site/static/site/img/icons/steps-down.svg +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/src/c3nav/site/static/site/img/icons/steps-up.svg b/src/c3nav/site/static/site/img/icons/steps-up.svg deleted file mode 100644 index 2776c280..00000000 --- a/src/c3nav/site/static/site/img/icons/steps-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/c3nav/site/templates/site/fragment_route.html b/src/c3nav/site/templates/site/fragment_route.html index eba67efc..9c944f0f 100644 --- a/src/c3nav/site/templates/site/fragment_route.html +++ b/src/c3nav/site/templates/site/fragment_route.html @@ -1,11 +1,12 @@ +{% load static %} +{% load i18n %} {% load route_render %} -

Your Route

+

{% trans 'Your Route' %}

- {% for routepart in route.routeparts %} -
-
+
+
@@ -20,16 +21,38 @@ xlink:href="/map/{{ routepart.level.name }}.png"> - {% for c in routepart.connections %} - + {% for line in routepart.lines %} + {% endfor %}
+
+
+
+

{{ routepart.line | safe }}

+
+ {% for line in routepart.lines %} + {% if not line.ignore %} +
+
+

{{ line.description }}

+
+ {% endif %} + {% endfor %} + {% if forloop.last %} +
+
+

{% trans 'You have reached your destination.' %} + {% if destination_title %}
{{ destination_title }}{% endif %}

+
+ {% endif %} +
+
{% endfor %}
diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index 8b3a56b7..7a57a4b3 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -36,7 +36,7 @@ def main(request, origin=None, destination=None): raise Http404 route = None - if request.method == 'POST' and origin and destination: + if request.method in ('GET', 'POST') and origin and destination: graph = Graph.load() allowed_ctypes = ('', ) @@ -45,9 +45,8 @@ def main(request, origin=None, destination=None): allowed_ctypes += get_ctypes('elevator', request.POST.get('elevators')) route = graph.get_route(origin, destination, allowed_ctypes) - print(route) route = route.split() - print(route) + route.create_routeparts() if False: filename = os.path.join(settings.RENDER_ROOT, 'base-level-0.png')