diff --git a/src/c3nav/mapdata/inclusion.py b/src/c3nav/mapdata/inclusion.py new file mode 100644 index 00000000..35c430d0 --- /dev/null +++ b/src/c3nav/mapdata/inclusion.py @@ -0,0 +1,65 @@ +from collections import OrderedDict + +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + +from c3nav.mapdata.models import AreaLocation, LocationGroup +from c3nav.mapdata.permissions import can_access_package + + +def get_default_include_avoid(): + include = set() + avoid = set() + + locations = list(AreaLocation.objects.exclude(routing_inclusion='default')) + locations += list(LocationGroup.objects.exclude(routing_inclusion='default')) + + for location in locations: + if location.routing_inclusion != 'allow_avoid': + avoid.add(location.location_id) + + return include, avoid + + +# Todo: add cache +def get_includables_avoidables(request): + includables = [] + avoidables = [] + + locations = list(AreaLocation.objects.exclude(routing_inclusion='default')) + locations += list(LocationGroup.objects.exclude(routing_inclusion='default')) + + if settings.DEBUG: + includables.append((':nonpublic', _('non-public areas'))) + avoidables.append((':public', _('public areas'))) + + for location in locations: + item = (location.location_id, location.title) + + # Todo: allow by access token + if not can_access_package(request, location.package): + continue + + # Todo: allow by access token + if location.routing_inclusion == 'needs_permission' and not settings.DEBUG: + continue + + if location.routing_inclusion == 'allow_avoid': + avoidables.append(item) + else: + includables.append(item) + + return OrderedDict(includables), OrderedDict(avoidables) + + +def parse_include_avoid(request, include_input, avoid_input): + includable, avoidable = get_includables_avoidables(request) + include_input = set(include_input) & set(includable) + avoid_input = set(avoid_input) & set(avoidable) + + default_include, default_avoid = get_default_include_avoid() + + include = set(default_include) | include_input + avoid = set(default_avoid) - include_input | avoid_input + + return ':nonpublic' in includable, include, avoid diff --git a/src/c3nav/mapdata/migrations/0029_auto_20161221_1120.py b/src/c3nav/mapdata/migrations/0029_auto_20161221_1120.py new file mode 100644 index 00000000..df31dc31 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0029_auto_20161221_1120.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-21 11:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0028_auto_20161219_1757'), + ] + + operations = [ + migrations.AlterField( + model_name='arealocation', + name='routing_inclusion', + field=models.CharField(choices=[('default', 'Default, include if map package is unlocked'), ('allow_avoid', 'Included, but allow excluding'), ('allow_include', 'Avoided, but allow including'), ('needs_permission', 'Avoided, needs permission to include')], default='default', max_length=20, verbose_name='Routing Inclusion'), + ), + migrations.AlterField( + model_name='locationgroup', + name='routing_inclusion', + field=models.CharField(choices=[('default', 'Default, include if map package is unlocked'), ('allow_avoid', 'Included, but allow excluding'), ('allow_include', 'Avoided, but allow including'), ('needs_permission', 'Avoided, needs permission to include')], default='default', max_length=20, verbose_name='Routing Inclusion'), + ), + ] diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 6ce7a596..ac4ee9fd 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -88,9 +88,9 @@ class LocationModelMixin(Location): LOCATION_ROUTING_INCLUSION = ( ('default', _('Default, include if map package is unlocked')), - ('allow_exclude', _('Included, but allow excluding')), - ('allow_include', _('Excluded, but allow including')), - ('needs_permission', _('Excluded, needs permission to include')), + ('allow_avoid', _('Included, but allow excluding')), + ('allow_include', _('Avoided, but allow including')), + ('needs_permission', _('Avoided, needs permission to include')), ) diff --git a/src/c3nav/mapdata/permissions.py b/src/c3nav/mapdata/permissions.py index bf3bf9e0..913dd559 100644 --- a/src/c3nav/mapdata/permissions.py +++ b/src/c3nav/mapdata/permissions.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from django.conf import settings from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import PermissionDenied @@ -52,17 +50,6 @@ def get_public_private_area(level): return public_area, private_area -def get_excludables_includables(): - excludables = [] - includables = [] - if settings.DEBUG: - excludables.append((':public', _('public areas'))) - includables.append((':nonpublic', _('non-public areas'))) - else: - pass - return OrderedDict(excludables), OrderedDict(includables) - - class LockedMapFeatures(BasePermission): def has_object_permission(self, request, view, obj): if isinstance(obj, Source): diff --git a/src/c3nav/routing/graph.py b/src/c3nav/routing/graph.py index fe0ab05e..1412f25a 100644 --- a/src/c3nav/routing/graph.py +++ b/src/c3nav/routing/graph.py @@ -211,7 +211,7 @@ class Graph: level.draw_png(points, lines) # Router - def build_routers(self, allowed_ctypes, public, nonpublic, avoid, include): + 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) @@ -223,7 +223,7 @@ class Graph: level_transfers[:] = -1 for i, level in enumerate(self.levels.values()): - routers.update(level.build_routers(allowed_ctypes, public, nonpublic, avoid, include)) + routers.update(level.build_routers(allowed_ctypes, allow_nonpublic, avoid, include)) router = routers[level] level_distances = empty_distances.copy() @@ -268,7 +268,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, allowed_ctypes, public, nonpublic, avoid, include): + def get_route(self, origin: Location, destination: Location, allowed_ctypes, allow_nonpublic, avoid, include): 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') @@ -285,7 +285,7 @@ class Graph: best_route = NoRoute # get routers - routers = self.build_routers(allowed_ctypes, public, nonpublic, avoid, include) + routers = self.build_routers(allowed_ctypes, allow_nonpublic, avoid, include) # route within room orig_rooms = set(point.room for point in orig_points) @@ -428,7 +428,6 @@ class Graph: distance = shortest_paths.min() # Is this route better than the previous ones? - print('vialevels', distance, best_route.distance) if distance < best_route.distance: # noinspection PyTypeChecker from_point, to_point = np.argwhere(shortest_paths == distance)[0] diff --git a/src/c3nav/routing/level.py b/src/c3nav/routing/level.py index 7e8aa102..1183bda6 100644 --- a/src/c3nav/routing/level.py +++ b/src/c3nav/routing/level.py @@ -353,7 +353,7 @@ class GraphLevel(): im.save(graph_filename) # Routing - def build_routers(self, allowed_ctypes, public, nonpublic, avoid, include): + 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) @@ -365,7 +365,7 @@ class GraphLevel(): room_transfers[:] = -1 for i, room in enumerate(self.rooms): - router = room.build_router(allowed_ctypes, public, nonpublic, avoid, include) + router = room.build_router(allowed_ctypes, allow_nonpublic, avoid, include) routers[room] = router room_distances = empty_distances.copy() diff --git a/src/c3nav/routing/room.py b/src/c3nav/routing/room.py index 15b0ee50..2c4ac3b9 100644 --- a/src/c3nav/routing/room.py +++ b/src/c3nav/routing/room.py @@ -248,41 +248,37 @@ class GraphRoom(): # Routing router_cache = {} - def build_router(self, allowed_ctypes, public, nonpublic, avoid, include): + 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,%d__%s__%s' % + cache_key = ('c3nav__graph__roomrouter__%s__%s__%s__%d__%s__%s' % (self.graph.mtime, self.i, ','.join(str(i) for i in ctypes), - public, nonpublic, ','.join(str(i) for i in avoid), ','.join(str(i) for i in include))) + 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, public, nonpublic, avoid, include) + roomrouter = self._build_router(ctypes, allow_nonpublic, avoid, include) self.router_cache[cache_key] = roomrouter return roomrouter - def _build_router(self, ctypes, public, nonpublic, avoid, include): + def _build_router(self, ctypes, allow_nonpublic, avoid, include): ctype_factors = np.ones((len(self.ctypes), 1, 1))*1000 ctype_factors[ctypes, :, :] = 1 distances = np.amin(self.distances*ctype_factors, axis=0).astype(np.float32) factors = np.ones_like(distances, dtype=np.float16) - if ':public' in self.excludables and not public: - points, = self.excludable_points[self.excludables.index(':public')].nonzero() - factors[points[:, None], points] = 1000 - - if ':nonpublic' in self.excludables and not nonpublic: + if ':nonpublic' in self.excludables and ':nonpublic' not in include: points, = self.excludable_points[self.excludables.index(':nonpublic')].nonzero() - factors[points[:, None], points] = np.inf + factors[points[:, None], points] = 1000 if allow_nonpublic else np.inf if avoid: - points, = self.excludable_points[avoid].any(axis=0).nonzero() - factors[points[:, None], points] = 1000 + points, = self.excludable_points[avoid, :].any(axis=0).nonzero() + factors[points[:, None], points] = np.maximum(factors[points[:, None], points], 1000) if include: - points, = self.excludable_points[include].any(axis=0).nonzero() + points, = self.excludable_points[include, :].any(axis=0).nonzero() factors[points[:, None], points] = 1 g_sparse = csgraph_from_dense(distances*factors, null_value=np.inf) diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index 966d9f16..edef7b0d 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -5,9 +5,9 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone +from c3nav.mapdata.inclusion import get_includables_avoidables, parse_include_avoid from c3nav.mapdata.models import Level from c3nav.mapdata.models.locations import get_location, search_location -from c3nav.mapdata.permissions import get_excludables_includables from c3nav.mapdata.render.compose import composer from c3nav.mapdata.utils.cache import get_levels_cached from c3nav.mapdata.utils.misc import get_dimensions @@ -158,9 +158,8 @@ def main(request, location=None, origin=None, destination=None): escalators = reverse_ctypes(allowed_ctypes, 'escalator') elevators = reverse_ctypes(allowed_ctypes, 'elevator') - excludables, includables = get_excludables_includables() - include = set(include) & set(includables) - avoid = set(avoid) & set(excludables) + includables, avoidables = get_includables_avoidables(request) + allow_nonpublic, include, avoid = parse_include_avoid(request, include, avoid) if request.method == 'POST': save_settings = request.POST.get('save_settings', '') == '1' @@ -169,7 +168,7 @@ def main(request, location=None, origin=None, destination=None): 'stairs': stairs, 'escalators': escalators, 'elevators': elevators, - 'excludables': excludables.items(), + 'excludables': avoidables.items(), 'includables': includables.items(), 'include': include, 'avoid': avoid, @@ -178,13 +177,10 @@ def main(request, location=None, origin=None, destination=None): # routing if request.method == 'POST' and origin and destination: - public = ':public' not in avoid - nonpublic = ':nonpublic' in include - graph = Graph.load() try: - route = graph.get_route(origin, destination, allowed_ctypes, public=public, nonpublic=nonpublic, + route = graph.get_route(origin, destination, allowed_ctypes, allow_nonpublic=allow_nonpublic, avoid=avoid-set(':public'), include=include-set(':nonpublic')) except NoRouteFound: ctx.update({'error': 'noroutefound'})