route from point to point and describe points

This commit is contained in:
Laura Klünder 2016-12-21 00:24:56 +01:00
parent 8a9406275b
commit bbfca9f318
9 changed files with 127 additions and 19 deletions

View file

@ -3,7 +3,7 @@ from collections import OrderedDict
from django.db import models from django.db import models
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from shapely.geometry import CAP_STYLE, JOIN_STYLE from shapely.geometry import CAP_STYLE, JOIN_STYLE, Point
from shapely.geometry.geo import mapping, shape from shapely.geometry.geo import mapping, shape
from c3nav.mapdata.fields import GeometryField from c3nav.mapdata.fields import GeometryField
@ -68,6 +68,9 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
def get_shadow_geojson(self): def get_shadow_geojson(self):
return None return None
def contains(self, x, y):
return self.geometry.contains(Point(x, y))
class GeometryMapItemWithLevel(GeometryMapItem): class GeometryMapItemWithLevel(GeometryMapItem):
""" """

View file

@ -172,8 +172,14 @@ class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel):
def subtitle(self): def subtitle(self):
return self.get_subtitle() return self.get_subtitle()
def get_subtitle(self): @property
items = [self.get_location_type_display()] def subtitle_without_type(self):
return self.get_subtitle(with_type=False)
def get_subtitle(self, with_type=True):
items = []
if with_type:
items += [self.get_location_type_display()]
items += [area.title for area in self.get_in_areas()] items += [area.title for area in self.get_in_areas()]
return ', '.join(items) return ', '.join(items)
@ -284,13 +290,37 @@ class PointLocation(Location):
def location_id(self): def location_id(self):
return 'c:%s:%d:%d' % (self.level.name, self.x*100, self.y*100) return 'c:%s:%d:%d' % (self.level.name, self.x*100, self.y*100)
@cached_property
def description(self):
from c3nav.routing.graph import Graph
graph = Graph.load()
point = graph.get_nearest_point(self.level, self.x, self.y)
if point is None:
return _('Unreachable Coordinates'), ''
locations = sorted(AreaLocation.objects.filter(name__in=point.arealocations, can_describe=True),
key=AreaLocation.get_sort_key, reverse=True)
if not locations:
return _('Coordinates'), ''
location = locations[0]
if location.contains(self.x, self.y):
return (_('Coordinates in %(location)s') % {'location': location.title}), location.subtitle_without_type
else:
return (_('Coordinates near %(location)s') % {'location': location.title}), location.subtitle_without_type
@property @property
def title(self) -> str: def title(self) -> str:
return 'Custom location' return self.description[0]
@property @property
def subtitle(self) -> str: def subtitle(self) -> str:
return 'Coordinates' add_subtitle = self.description[1]
subtitle = '%s:%d:%d' % (self.level.name, self.x*100, self.y*100)
if add_subtitle:
subtitle += ' - '+add_subtitle
return subtitle
def to_json(self): def to_json(self):
result = super().to_location_json() result = super().to_location_json()

View file

@ -31,7 +31,7 @@ class GraphArea():
def build_connections(self): def build_connections(self):
for point1, point2 in combinations(self._built_points, 2): for point1, point2 in combinations(self._built_points, 2):
there, back = self.check_connection(point1, point2) there, back = self.check_connection(point1.xy, point2.xy)
if there is not None: if there is not None:
point1.connect_to(point2, ctype=there) point1.connect_to(point2, ctype=there)
@ -40,14 +40,14 @@ class GraphArea():
point2.connect_to(point1, ctype=back) point2.connect_to(point1, ctype=back)
def check_connection(self, point1, point2): def check_connection(self, point1, point2):
path = Path(np.vstack((point1.xy, point2.xy))) path = Path(np.vstack((point1, point2)))
# lies within room # lies within room
if self.mpl_clear.intersects_path(path): if self.mpl_clear.intersects_path(path):
return None, None return None, None
# stair checker # stair checker
angle = coord_angle(point1.xy, point2.xy) angle = coord_angle(point1, point2)
stair_direction_up = None stair_direction_up = None
for stair_path, stair_angle in self.mpl_stairs: for stair_path, stair_angle in self.mpl_stairs:
if not path.intersects_path(stair_path): if not path.intersects_path(stair_path):
@ -65,7 +65,7 @@ class GraphArea():
return None, None return None, None
# escalator checker # escalator checker
angle = coord_angle(point1.xy, point2.xy) angle = coord_angle(point1, point2)
escalator_direction_up = None escalator_direction_up = None
escalator_swap_direction = False escalator_swap_direction = False
for escalator in self.escalators: for escalator in self.escalators:
@ -102,3 +102,18 @@ class GraphArea():
def finish_build(self): def finish_build(self):
self.points = np.array(tuple(point.i for point in self._built_points)) self.points = np.array(tuple(point.i for point in self._built_points))
def contains_point(self, point):
return self.mpl_clear.contains_point(point)
def connected_points(self, point, mode):
connections = {}
for point_i in self.points:
other_point = self.graph.points[point_i]
there, back = self.check_connection(point, other_point.xy)
ctype = there if mode == 'orig' else back
if ctype is not None:
distance = np.linalg.norm(point - other_point.xy)
connections[point_i] = (distance, ctype)
return connections

View file

@ -240,14 +240,19 @@ class Graph:
routers[self] = GraphRouter(shortest_paths, predecessors, level_transfers) routers[self] = GraphRouter(shortest_paths, predecessors, level_transfers)
return routers return routers
def get_location_points(self, location: Location): def get_location_points(self, location: Location, mode):
if isinstance(location, PointLocation): if isinstance(location, PointLocation):
return self.levels[level.name].points_from() points = self.levels[location.level.name].connected_points(np.array((location.x, location.y)), mode)
return 'bla' points, distances = zip(*((point_i, distance) for point_i, (distance, ctype) in points.items()))
points = np.array(points)
distances = np.array(distances)
return points, distances
elif isinstance(location, AreaLocation): elif isinstance(location, AreaLocation):
return self.levels[location.level.name].arealocation_points[location.name] points = self.levels[location.level.name].arealocation_points[location.name]
return points, None
elif isinstance(location, LocationGroup): elif isinstance(location, LocationGroup):
return np.hstack(tuple(self.get_location_points(area) for area in location.locationareas)) points = set(np.hstack(tuple(self.get_location_points(area) for area in location.locationareas)))
return points, None
def _get_points_by_i(self, points): def _get_points_by_i(self, points):
return tuple(self.points[i] for i in points) return tuple(self.points[i] for i in points)
@ -256,8 +261,8 @@ class Graph:
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, public, nonpublic, avoid, include):
orig_points_i = set(self.get_location_points(origin)) orig_points_i, orig_distances = self.get_location_points(origin, 'orig')
dest_points_i = set(self.get_location_points(destination)) dest_points_i, dest_distances = self.get_location_points(destination, 'dest')
orig_points = self._get_points_by_i(orig_points_i) orig_points = self._get_points_by_i(orig_points_i)
dest_points = self._get_points_by_i(dest_points_i) dest_points = self._get_points_by_i(dest_points_i)
@ -276,6 +281,15 @@ class Graph:
orig_room_points = {room: self._allowed_points_index(room.points, orig_points_i) for room in orig_rooms} orig_room_points = {room: self._allowed_points_index(room.points, orig_points_i) for room in orig_rooms}
dest_room_points = {room: self._allowed_points_index(room.points, dest_points_i) for room in dest_rooms} dest_room_points = {room: self._allowed_points_index(room.points, dest_points_i) for room in dest_rooms}
# add distances to room routers
if orig_distances is not None:
for room in orig_rooms:
routers[room].shortest_paths[orig_room_points[room], :] += orig_distances[:, None]
if dest_distances is not None:
for room in dest_rooms:
routers[room].shortest_paths[:, dest_room_points[room]] += dest_distances
# if the points have common rooms, search for routes within those rooms # if the points have common rooms, search for routes within those rooms
if common_rooms: if common_rooms:
for room in common_rooms: for room in common_rooms:
@ -439,5 +453,8 @@ class Graph:
return level_transfers return level_transfers
def get_nearest_point(self, level, x, y):
return self.levels[level.name].nearest_point(np.array((x, y)), 'orig')
GraphRouter = namedtuple('GraphRouter', ('shortest_paths', 'predecessors', 'level_transfers', )) GraphRouter = namedtuple('GraphRouter', ('shortest_paths', 'predecessors', 'level_transfers', ))

View file

@ -3,6 +3,7 @@ from collections import namedtuple
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 matplotlib.path import Path from matplotlib.path import Path
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._shortest_path import shortest_path
@ -383,6 +384,38 @@ class GraphLevel():
routers[self] = LevelRouter(shortest_paths, predecessors, room_transfers) routers[self] = LevelRouter(shortest_paths, predecessors, room_transfers)
return routers return routers
def nearest_point(self, point, mode):
cache_key = ('c3nav__routing__nearest_point__%s__%s__%.2f_%.2f__%s' %
(self.graph.mtime, self.level.name, point[0], point[1], mode))
points = cache.get(cache_key, None)
if points is None:
points = self._nearest_point(point, mode)
cache.set(cache_key, points, 60)
return points
def _nearest_point(self, point, mode):
points = self.connected_points(point, mode)
if not points:
return None
nearest_point = min(points.items(), key=lambda x: x[1][0])
return self.graph.points[nearest_point[0]]
def connected_points(self, point, mode):
cache_key = ('c3nav__routing__connected_points__%s__%s__%.2f_%.2f__%s' %
(self.graph.mtime, self.level.name, point[0], point[1], mode))
points = cache.get(cache_key, None)
if points is None:
points = self._connected_points(point, mode)
cache.set(cache_key, points, 60)
return points
def _connected_points(self, point, mode):
for room in self.rooms:
if room.contains_point(point):
return room.connected_points(point, mode)
return {}
LevelRouter = namedtuple('LevelRouter', ('shortest_paths', 'predecessors', 'room_transfers', )) LevelRouter = namedtuple('LevelRouter', ('shortest_paths', 'predecessors', 'room_transfers', ))
EscalatorData = namedtuple('EscalatorData', ('mpl_geom', 'direction_up', 'slope', 'angle')) EscalatorData = namedtuple('EscalatorData', ('mpl_geom', 'direction_up', 'slope', 'angle'))

View file

@ -294,5 +294,15 @@ class GraphRoom():
return GraphConnection(self.graph.points[self.points[from_i]], self.graph.points[self.points[to_i]], return GraphConnection(self.graph.points[self.points[from_i]], self.graph.points[self.points[to_i]],
distance=distance, ctype=ctype) distance=distance, ctype=ctype)
def contains_point(self, point):
return self.mpl_clear.contains_point(point)
def connected_points(self, point, mode):
connections = {}
for area in self.areas:
if area.contains_point(point):
connections.update(area.connected_points(point, mode))
return connections
RoomRouter = namedtuple('RoomRouter', ('shortest_paths', 'predecessors', )) RoomRouter = namedtuple('RoomRouter', ('shortest_paths', 'predecessors', ))

View file

@ -54,7 +54,7 @@ class Route:
@staticmethod @staticmethod
def describe_point(point): def describe_point(point):
locations = sorted(AreaLocation.objects.filter(location_type__in=('room', 'level', 'area'), locations = sorted(AreaLocation.objects.filter(location_type__in=('room', 'level', 'area'),
name__in=point.arealocations), name__in=point.arealocations, can_describe=True),
key=AreaLocation.get_sort_key, reverse=True) key=AreaLocation.get_sort_key, reverse=True)
if not locations: if not locations:

View file

@ -84,7 +84,7 @@ c3nav = {
}, },
_locationselect_click_image: function(e) { _locationselect_click_image: function(e) {
var level = $(e.delegateTarget).attr('data-level'); var level = $(e.delegateTarget).attr('data-level');
var coords = 'c:'+level+':'+parseInt(e.offsetX/6*100)+':'+parseInt(e.offsetY/6*100); var coords = 'c:'+level+':'+parseInt(e.offsetX/6*100)+':'+parseInt((c3nav.svg_height-e.offsetY)/6*100);
var location_group = $(this).closest('.location-group'); var location_group = $(this).closest('.location-group');
location_group.removeClass('map').addClass('selected'); location_group.removeClass('map').addClass('selected');
var selected = location_group.find('.locationselect-selected'); var selected = location_group.find('.locationselect-selected');

View file

@ -87,7 +87,7 @@ def main(request, location=None, origin=None, destination=None):
x = request.POST.get('x') x = request.POST.get('x')
y = request.POST.get('y') y = request.POST.get('y')
if x.isnumeric() and y.isnumeric(): if x.isnumeric() and y.isnumeric():
coords = 'c:%s:%d:%d' % (map_level, int(int(x)/6*100), int(int(y)/6*100)) coords = 'c:%s:%d:%d' % (map_level, int(int(x)/6*100), height-int(int(y)/6*100))
if active_field == 'origin': if active_field == 'origin':
return redirect('site.route', origin=coords, destination=destination.location_id) return redirect('site.route', origin=coords, destination=destination.location_id)
elif active_field == 'destination': elif active_field == 'destination':