consider access restrictions during routing
This commit is contained in:
parent
c3570bb9ff
commit
dba04b4179
1 changed files with 86 additions and 22 deletions
|
@ -8,16 +8,18 @@ from itertools import chain
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from scipy.sparse.csgraph._shortest_path import shortest_path
|
from scipy.sparse.csgraph._shortest_path import shortest_path
|
||||||
from shapely import prepared
|
from shapely import prepared
|
||||||
from shapely.geometry import LineString, Point
|
from shapely.geometry import LineString, Point
|
||||||
from shapely.ops import unary_union
|
from shapely.ops import unary_union
|
||||||
|
|
||||||
from c3nav.mapdata.models import AltitudeArea, Area, GraphEdge, Level, LocationGroup, Space, WayType
|
from c3nav.mapdata.models import AltitudeArea, Area, GraphEdge, Level, LocationGroup, MapUpdate, Space, WayType
|
||||||
from c3nav.mapdata.models.geometry.space import POI
|
from c3nav.mapdata.models.geometry.space import POI
|
||||||
from c3nav.mapdata.utils.geometry import assert_multipolygon, get_rings, good_representative_point
|
from c3nav.mapdata.utils.geometry import assert_multipolygon, get_rings, good_representative_point
|
||||||
from c3nav.mapdata.utils.locations import CustomLocation
|
from c3nav.mapdata.utils.locations import CustomLocation
|
||||||
|
from c3nav.routing.exceptions import NotYetRoutable
|
||||||
from c3nav.routing.route import Route
|
from c3nav.routing.route import Route
|
||||||
|
|
||||||
|
|
||||||
|
@ -230,63 +232,102 @@ class Router:
|
||||||
cls.cached = cls.load_nocache()
|
cls.cached = cls.load_nocache()
|
||||||
return cls.cached
|
return cls.cached
|
||||||
|
|
||||||
def get_locations(self, location, permissions=frozenset()):
|
def get_locations(self, location, restrictions):
|
||||||
locations = ()
|
locations = ()
|
||||||
if isinstance(location, Level):
|
if isinstance(location, Level):
|
||||||
locations = (self.levels.get(location.pk), )
|
if location.access_restriction_id not in restrictions:
|
||||||
|
if location.pk not in self.levels:
|
||||||
|
raise NotYetRoutable
|
||||||
|
locations = (self.levels[location.pk], )
|
||||||
elif isinstance(location, Space):
|
elif isinstance(location, Space):
|
||||||
locations = (self.spaces.get(location.pk), )
|
if location.pk not in restrictions.spaces:
|
||||||
|
if location.pk not in self.spaces:
|
||||||
|
raise NotYetRoutable
|
||||||
|
locations = (self.spaces[location.pk], )
|
||||||
elif isinstance(location, Area):
|
elif isinstance(location, Area):
|
||||||
locations = (self.areas.get(location.pk), )
|
if location.space_id not in restrictions.spaces and location.access_restriction_id not in restrictions:
|
||||||
|
if location.pk not in self.areas:
|
||||||
|
raise NotYetRoutable
|
||||||
|
locations = (self.areas[location.pk], )
|
||||||
elif isinstance(location, POI):
|
elif isinstance(location, POI):
|
||||||
locations = (self.pois.get(location.pk), )
|
if location.space_id not in restrictions.spaces and location.access_restriction_id not in restrictions:
|
||||||
|
if location.pk not in self.pois:
|
||||||
|
raise NotYetRoutable
|
||||||
|
locations = (self.pois[location.pk], )
|
||||||
elif isinstance(location, LocationGroup):
|
elif isinstance(location, LocationGroup):
|
||||||
group = self.groups.get(location.pk)
|
if location.pk not in self.groups:
|
||||||
|
raise NotYetRoutable
|
||||||
|
group = self.groups[location.pk]
|
||||||
locations = tuple(chain(
|
locations = tuple(chain(
|
||||||
(self.levels[pk] for pk in group.get('levels', ())),
|
(level for level in (self.levels[pk] for pk in group.get('levels', ()))
|
||||||
(self.spaces[pk] for pk in group.get('spaces', ())),
|
if level.access_restriction_id not in restrictions),
|
||||||
(self.areas[pk] for pk in group.get('areas', ()))
|
(space for space in (self.spaces[pk] for pk in group.get('spaces', ()))
|
||||||
|
if space.pk not in restrictions.spaces),
|
||||||
|
(area for area in (self.areas[pk] for pk in group.get('areas', ()))
|
||||||
|
if area.space_id not in restrictions.spaces and area.access_restriction_id not in restrictions),
|
||||||
|
(poi for poi in (self.pois[pk] for pk in group.get('pois', ()))
|
||||||
|
if poi.space_id not in restrictions.spaces and poi.access_restriction_id not in restrictions),
|
||||||
))
|
))
|
||||||
elif isinstance(location, CustomLocation):
|
elif isinstance(location, CustomLocation):
|
||||||
point = Point(location.x, location.y)
|
point = Point(location.x, location.y)
|
||||||
location = RouterPoint(location)
|
location = RouterPoint(location)
|
||||||
space = self.space_for_point(location.level.pk, point)
|
space = self.space_for_point(location.level.pk, point, restrictions)
|
||||||
altitudearea = space.altitudearea_for_point(point)
|
altitudearea = space.altitudearea_for_point(point)
|
||||||
location_nodes = altitudearea.nodes_for_point(point, all_nodes=self.nodes)
|
location_nodes = altitudearea.nodes_for_point(point, all_nodes=self.nodes)
|
||||||
location.nodes = set(i for i in location_nodes.keys())
|
location.nodes = set(i for i in location_nodes.keys())
|
||||||
location.nodes_addition = location_nodes
|
location.nodes_addition = location_nodes
|
||||||
locations = tuple((location, ))
|
locations = tuple((location, ))
|
||||||
return RouterLocation(tuple(location for location in locations
|
return RouterLocation(locations)
|
||||||
if location is not None and (location.access_restriction_id is None or
|
|
||||||
location.access_restriction_id in permissions)))
|
|
||||||
|
|
||||||
def space_for_point(self, level, point):
|
def space_for_point(self, level, point, restrictions=None):
|
||||||
# todo: only spaces that the user can see
|
|
||||||
point = Point(point.x, point.y)
|
point = Point(point.x, point.y)
|
||||||
level = self.levels[level]
|
level = self.levels[level]
|
||||||
|
excluded_spaces = restrictions.spaces if restrictions else ()
|
||||||
for space in level.spaces:
|
for space in level.spaces:
|
||||||
|
if space in excluded_spaces:
|
||||||
|
continue
|
||||||
if self.spaces[space].geometry_prep.contains(point):
|
if self.spaces[space].geometry_prep.contains(point):
|
||||||
return self.spaces[space]
|
return self.spaces[space]
|
||||||
return self.spaces[min(level.spaces, key=lambda space: self.spaces[space].geometry.distance(point))]
|
return self.spaces[min(level.spaces, key=lambda space: self.spaces[space].geometry.distance(point))]
|
||||||
|
|
||||||
def describe_custom_location(self, location):
|
def describe_custom_location(self, location):
|
||||||
|
# todo: location.request
|
||||||
return CustomLocationDescription(
|
return CustomLocationDescription(
|
||||||
space=self.space_for_point(location.level.pk, location)
|
space=self.space_for_point(location.level.pk, location)
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
def shortest_path(self, restrictions):
|
||||||
def shortest_path(self):
|
cache_key = 'router:shortest_path:%s:%s' % (MapUpdate.current_processed_cache_key(),
|
||||||
return shortest_path(self.graph, directed=True, return_predecessors=True)
|
restrictions.cache_key)
|
||||||
|
result = cache.get(cache_key)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
graph = self.graph.copy()
|
||||||
|
graph[tuple(restrictions.spaces), :] = np.inf
|
||||||
|
graph[:, tuple(restrictions.spaces)] = np.inf
|
||||||
|
graph[restrictions.edges.transpose().tolist()] = np.inf
|
||||||
|
|
||||||
|
result = shortest_path(graph, directed=True, return_predecessors=True)
|
||||||
|
cache.set(cache_key, result, 600)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_restrictions(self, permissions):
|
||||||
|
return RouterRestrictionSet({
|
||||||
|
pk: restriction for pk, restriction in self.restrictions.items() if pk not in permissions
|
||||||
|
})
|
||||||
|
|
||||||
def get_route(self, origin, destination, permissions=frozenset()):
|
def get_route(self, origin, destination, permissions=frozenset()):
|
||||||
|
restrictions = self.get_restrictions(permissions)
|
||||||
|
|
||||||
# get possible origins and destinations
|
# get possible origins and destinations
|
||||||
origins = self.get_locations(origin, permissions=permissions)
|
origins = self.get_locations(origin, restrictions)
|
||||||
destinations = self.get_locations(destination, permissions=permissions)
|
destinations = self.get_locations(destination, restrictions)
|
||||||
|
|
||||||
# todo: throw error if route is impossible
|
# todo: throw error if route is impossible
|
||||||
|
|
||||||
# calculate shortest path matrix
|
# calculate shortest path matrix
|
||||||
distances, predecessors = self.shortest_path
|
distances, predecessors = self.shortest_path(restrictions)
|
||||||
|
|
||||||
# find shortest path for our origins and destinations
|
# find shortest path for our origins and destinations
|
||||||
origin_nodes = np.array(tuple(origins.nodes))
|
origin_nodes = np.array(tuple(origins.nodes))
|
||||||
|
@ -480,3 +521,26 @@ class RouterRestriction:
|
||||||
def __init__(self, spaces=None):
|
def __init__(self, spaces=None):
|
||||||
self.spaces = spaces if spaces else set()
|
self.spaces = spaces if spaces else set()
|
||||||
self.edges = deque()
|
self.edges = deque()
|
||||||
|
|
||||||
|
|
||||||
|
class RouterRestrictionSet:
|
||||||
|
def __init__(self, restrictions):
|
||||||
|
self.restrictions = restrictions
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def spaces(self):
|
||||||
|
return reduce(operator.or_, (restriction.spaces for restriction in self.restrictions.values()), frozenset())
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def edges(self):
|
||||||
|
if not self.restrictions:
|
||||||
|
return np.array((), dtype=np.uint32).reshape((-1, 2))
|
||||||
|
return np.vstack(tuple(restriction.edges for restriction in self.restrictions.values()))
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def cache_key(self):
|
||||||
|
return '%s_%s' % ('-'.join(str(i) for i in self.spaces),
|
||||||
|
'-'.join(str(i) for i in self.edges.flatten().tolist()))
|
||||||
|
|
||||||
|
def __contains__(self, pk):
|
||||||
|
return pk in self.restrictions
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue