correctly implement avoid/exclude
This commit is contained in:
parent
d5a52bf9e3
commit
49757f7c06
8 changed files with 114 additions and 46 deletions
65
src/c3nav/mapdata/inclusion.py
Normal file
65
src/c3nav/mapdata/inclusion.py
Normal 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
|
25
src/c3nav/mapdata/migrations/0029_auto_20161221_1120.py
Normal file
25
src/c3nav/mapdata/migrations/0029_auto_20161221_1120.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue