correctly implement avoid/exclude

This commit is contained in:
Laura Klünder 2016-12-21 13:31:56 +01:00
parent d5a52bf9e3
commit 49757f7c06
8 changed files with 114 additions and 46 deletions

View file

@ -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

View file

@ -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'),
),
]

View file

@ -88,9 +88,9 @@ class LocationModelMixin(Location):
LOCATION_ROUTING_INCLUSION = ( LOCATION_ROUTING_INCLUSION = (
('default', _('Default, include if map package is unlocked')), ('default', _('Default, include if map package is unlocked')),
('allow_exclude', _('Included, but allow excluding')), ('allow_avoid', _('Included, but allow excluding')),
('allow_include', _('Excluded, but allow including')), ('allow_include', _('Avoided, but allow including')),
('needs_permission', _('Excluded, needs permission to include')), ('needs_permission', _('Avoided, needs permission to include')),
) )

View file

@ -1,5 +1,3 @@
from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
@ -52,17 +50,6 @@ def get_public_private_area(level):
return public_area, private_area 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): class LockedMapFeatures(BasePermission):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if isinstance(obj, Source): if isinstance(obj, Source):

View file

@ -211,7 +211,7 @@ class Graph:
level.draw_png(points, lines) level.draw_png(points, lines)
# Router # Router
def build_routers(self, allowed_ctypes, public, nonpublic, avoid, include): def build_routers(self, allowed_ctypes, allow_nonpublic, avoid, include):
routers = {} routers = {}
empty_distances = np.empty(shape=(len(self.level_transfer_points),) * 2, dtype=np.float16) empty_distances = np.empty(shape=(len(self.level_transfer_points),) * 2, dtype=np.float16)
@ -223,7 +223,7 @@ class Graph:
level_transfers[:] = -1 level_transfers[:] = -1
for i, level in enumerate(self.levels.values()): 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] router = routers[level]
level_distances = empty_distances.copy() level_distances = empty_distances.copy()
@ -268,7 +268,7 @@ class Graph:
def _allowed_points_index(self, points, allowed_points_i): 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)) 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') 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') dest_points_i, dest_distances, dest_ctypes = self.get_location_points(destination, 'dest')
@ -285,7 +285,7 @@ class Graph:
best_route = NoRoute best_route = NoRoute
# get routers # 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 # route within room
orig_rooms = set(point.room for point in orig_points) orig_rooms = set(point.room for point in orig_points)
@ -428,7 +428,6 @@ class Graph:
distance = shortest_paths.min() distance = shortest_paths.min()
# Is this route better than the previous ones? # Is this route better than the previous ones?
print('vialevels', distance, best_route.distance)
if distance < best_route.distance: if distance < best_route.distance:
# noinspection PyTypeChecker # noinspection PyTypeChecker
from_point, to_point = np.argwhere(shortest_paths == distance)[0] from_point, to_point = np.argwhere(shortest_paths == distance)[0]

View file

@ -353,7 +353,7 @@ class GraphLevel():
im.save(graph_filename) im.save(graph_filename)
# Routing # Routing
def build_routers(self, allowed_ctypes, public, nonpublic, avoid, include): def build_routers(self, allowed_ctypes, allow_nonpublic, avoid, include):
routers = {} routers = {}
empty_distances = np.empty(shape=(len(self.room_transfer_points),) * 2, dtype=np.float16) empty_distances = np.empty(shape=(len(self.room_transfer_points),) * 2, dtype=np.float16)
@ -365,7 +365,7 @@ class GraphLevel():
room_transfers[:] = -1 room_transfers[:] = -1
for i, room in enumerate(self.rooms): 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 routers[room] = router
room_distances = empty_distances.copy() room_distances = empty_distances.copy()

View file

@ -248,41 +248,37 @@ class GraphRoom():
# Routing # Routing
router_cache = {} 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) 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) 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) 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), (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) roomrouter = self.router_cache.get(cache_key)
if not roomrouter: 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 self.router_cache[cache_key] = roomrouter
return 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 = np.ones((len(self.ctypes), 1, 1))*1000
ctype_factors[ctypes, :, :] = 1 ctype_factors[ctypes, :, :] = 1
distances = np.amin(self.distances*ctype_factors, axis=0).astype(np.float32) distances = np.amin(self.distances*ctype_factors, axis=0).astype(np.float32)
factors = np.ones_like(distances, dtype=np.float16) factors = np.ones_like(distances, dtype=np.float16)
if ':public' in self.excludables and not public: if ':nonpublic' in self.excludables and ':nonpublic' not in include:
points, = self.excludable_points[self.excludables.index(':public')].nonzero()
factors[points[:, None], points] = 1000
if ':nonpublic' in self.excludables and not nonpublic:
points, = self.excludable_points[self.excludables.index(':nonpublic')].nonzero() 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: if avoid:
points, = self.excludable_points[avoid].any(axis=0).nonzero() points, = self.excludable_points[avoid, :].any(axis=0).nonzero()
factors[points[:, None], points] = 1000 factors[points[:, None], points] = np.maximum(factors[points[:, None], points], 1000)
if include: 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 factors[points[:, None], points] = 1
g_sparse = csgraph_from_dense(distances*factors, null_value=np.inf) g_sparse = csgraph_from_dense(distances*factors, null_value=np.inf)

View file

@ -5,9 +5,9 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils import timezone 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 import Level
from c3nav.mapdata.models.locations import get_location, search_location 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.render.compose import composer
from c3nav.mapdata.utils.cache import get_levels_cached from c3nav.mapdata.utils.cache import get_levels_cached
from c3nav.mapdata.utils.misc import get_dimensions 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') escalators = reverse_ctypes(allowed_ctypes, 'escalator')
elevators = reverse_ctypes(allowed_ctypes, 'elevator') elevators = reverse_ctypes(allowed_ctypes, 'elevator')
excludables, includables = get_excludables_includables() includables, avoidables = get_includables_avoidables(request)
include = set(include) & set(includables) allow_nonpublic, include, avoid = parse_include_avoid(request, include, avoid)
avoid = set(avoid) & set(excludables)
if request.method == 'POST': if request.method == 'POST':
save_settings = request.POST.get('save_settings', '') == '1' save_settings = request.POST.get('save_settings', '') == '1'
@ -169,7 +168,7 @@ def main(request, location=None, origin=None, destination=None):
'stairs': stairs, 'stairs': stairs,
'escalators': escalators, 'escalators': escalators,
'elevators': elevators, 'elevators': elevators,
'excludables': excludables.items(), 'excludables': avoidables.items(),
'includables': includables.items(), 'includables': includables.items(),
'include': include, 'include': include,
'avoid': avoid, 'avoid': avoid,
@ -178,13 +177,10 @@ def main(request, location=None, origin=None, destination=None):
# routing # routing
if request.method == 'POST' and origin and destination: if request.method == 'POST' and origin and destination:
public = ':public' not in avoid
nonpublic = ':nonpublic' in include
graph = Graph.load() graph = Graph.load()
try: 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')) avoid=avoid-set(':public'), include=include-set(':nonpublic'))
except NoRouteFound: except NoRouteFound:
ctx.update({'error': 'noroutefound'}) ctx.update({'error': 'noroutefound'})