route from point to point and describe points
This commit is contained in:
parent
8a9406275b
commit
bbfca9f318
9 changed files with 127 additions and 19 deletions
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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', ))
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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', ))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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':
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue