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.utils.functional import cached_property
|
||||
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 c3nav.mapdata.fields import GeometryField
|
||||
|
@ -68,6 +68,9 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
|||
def get_shadow_geojson(self):
|
||||
return None
|
||||
|
||||
def contains(self, x, y):
|
||||
return self.geometry.contains(Point(x, y))
|
||||
|
||||
|
||||
class GeometryMapItemWithLevel(GeometryMapItem):
|
||||
"""
|
||||
|
|
|
@ -172,8 +172,14 @@ class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel):
|
|||
def subtitle(self):
|
||||
return self.get_subtitle()
|
||||
|
||||
def get_subtitle(self):
|
||||
items = [self.get_location_type_display()]
|
||||
@property
|
||||
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()]
|
||||
return ', '.join(items)
|
||||
|
||||
|
@ -284,13 +290,37 @@ class PointLocation(Location):
|
|||
def location_id(self):
|
||||
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
|
||||
def title(self) -> str:
|
||||
return 'Custom location'
|
||||
return self.description[0]
|
||||
|
||||
@property
|
||||
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):
|
||||
result = super().to_location_json()
|
||||
|
|
|
@ -31,7 +31,7 @@ class GraphArea():
|
|||
def build_connections(self):
|
||||
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:
|
||||
point1.connect_to(point2, ctype=there)
|
||||
|
@ -40,14 +40,14 @@ class GraphArea():
|
|||
point2.connect_to(point1, ctype=back)
|
||||
|
||||
def check_connection(self, point1, point2):
|
||||
path = Path(np.vstack((point1.xy, point2.xy)))
|
||||
path = Path(np.vstack((point1, point2)))
|
||||
|
||||
# lies within room
|
||||
if self.mpl_clear.intersects_path(path):
|
||||
return None, None
|
||||
|
||||
# stair checker
|
||||
angle = coord_angle(point1.xy, point2.xy)
|
||||
angle = coord_angle(point1, point2)
|
||||
stair_direction_up = None
|
||||
for stair_path, stair_angle in self.mpl_stairs:
|
||||
if not path.intersects_path(stair_path):
|
||||
|
@ -65,7 +65,7 @@ class GraphArea():
|
|||
return None, None
|
||||
|
||||
# escalator checker
|
||||
angle = coord_angle(point1.xy, point2.xy)
|
||||
angle = coord_angle(point1, point2)
|
||||
escalator_direction_up = None
|
||||
escalator_swap_direction = False
|
||||
for escalator in self.escalators:
|
||||
|
@ -102,3 +102,18 @@ class GraphArea():
|
|||
|
||||
def finish_build(self):
|
||||
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)
|
||||
return routers
|
||||
|
||||
def get_location_points(self, location: Location):
|
||||
def get_location_points(self, location: Location, mode):
|
||||
if isinstance(location, PointLocation):
|
||||
return self.levels[level.name].points_from()
|
||||
return 'bla'
|
||||
points = self.levels[location.level.name].connected_points(np.array((location.x, location.y)), mode)
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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))
|
||||
|
||||
def get_route(self, origin: Location, destination: Location, allowed_ctypes, public, nonpublic, avoid, include):
|
||||
orig_points_i = set(self.get_location_points(origin))
|
||||
dest_points_i = set(self.get_location_points(destination))
|
||||
orig_points_i, orig_distances = self.get_location_points(origin, 'orig')
|
||||
dest_points_i, dest_distances = self.get_location_points(destination, 'dest')
|
||||
|
||||
orig_points = self._get_points_by_i(orig_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}
|
||||
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 common_rooms:
|
||||
for room in common_rooms:
|
||||
|
@ -439,5 +453,8 @@ class Graph:
|
|||
|
||||
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', ))
|
||||
|
|
|
@ -3,6 +3,7 @@ from collections import namedtuple
|
|||
|
||||
import numpy as np
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from matplotlib.path import Path
|
||||
from PIL import Image, ImageDraw
|
||||
from scipy.sparse.csgraph._shortest_path import shortest_path
|
||||
|
@ -383,6 +384,38 @@ class GraphLevel():
|
|||
routers[self] = LevelRouter(shortest_paths, predecessors, room_transfers)
|
||||
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', ))
|
||||
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]],
|
||||
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', ))
|
||||
|
|
|
@ -54,7 +54,7 @@ class Route:
|
|||
@staticmethod
|
||||
def describe_point(point):
|
||||
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)
|
||||
|
||||
if not locations:
|
||||
|
|
|
@ -84,7 +84,7 @@ c3nav = {
|
|||
},
|
||||
_locationselect_click_image: function(e) {
|
||||
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');
|
||||
location_group.removeClass('map').addClass('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')
|
||||
y = request.POST.get('y')
|
||||
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':
|
||||
return redirect('site.route', origin=coords, destination=destination.location_id)
|
||||
elif active_field == 'destination':
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue