diff --git a/src/c3nav/mapdata/utils/locations.py b/src/c3nav/mapdata/utils/locations.py index 7eb3ad24..453b3b0c 100644 --- a/src/c3nav/mapdata/utils/locations.py +++ b/src/c3nav/mapdata/utils/locations.py @@ -283,6 +283,7 @@ class CustomLocation: ('grid_square', self.grid_square), ('near_area', self.near_area.pk if self.near_area else None), ('near_poi', self.near_poi.pk if self.near_poi else None), + ('nearby', tuple(location.pk for location in self.nearby)), ('altitude', None if self.altitude is None else round(self.altitude, 2)) )) if not grid.enabled: @@ -378,6 +379,10 @@ class CustomLocation: def near_poi(self): return self.description.near_poi + @cached_property + def nearby(self): + return self.description.nearby + @cached_property def grid_square(self): return grid.get_square_for_point(self.x, self.y) or '' diff --git a/src/c3nav/routing/router.py b/src/c3nav/routing/router.py index 6e1fca55..689b52dd 100644 --- a/src/c3nav/routing/router.py +++ b/src/c3nav/routing/router.py @@ -363,15 +363,25 @@ class Router: restrictions = self.get_restrictions(location.permissions) space = self.space_for_point(level=location.level.pk, point=location, restrictions=restrictions) if not space: - return CustomLocationDescription(space=space, altitude=None, areas=(), near_area=None, near_poi=None) + return CustomLocationDescription(space=space, altitude=None, areas=(), near_area=None, near_poi=None, + nearby=()) try: altitude = space.altitudearea_for_point(location).get_altitude(location) except LocationUnreachable: altitude = None - areas, near_area = space.areas_for_point(areas=self.areas, point=location, restrictions=restrictions) - near_poi = space.poi_for_point(pois=self.pois, point=location, restrictions=restrictions) + areas, near_area, nearby_areas = space.areas_for_point( + areas=self.areas, point=location, restrictions=restrictions + ) + near_poi, nearby_pois = space.poi_for_point( + pois=self.pois, point=location, restrictions=restrictions + ) + nearby = tuple(sorted( + tuple(l for l in nearby_areas+nearby_pois if l[0].can_search), + key=operator.itemgetter(1) + ))[:20] + nearby = tuple(location for location, distance in nearby) return CustomLocationDescription(space=space, altitude=altitude, - areas=areas, near_area=near_area, near_poi=near_poi) + areas=areas, near_area=near_area, near_poi=near_poi, nearby=nearby) def shortest_path(self, restrictions, options): options_key = options.serialize_string() @@ -482,7 +492,7 @@ class Router: CustomLocationDescription = namedtuple('CustomLocationDescription', ('space', 'altitude', - 'areas', 'near_area', 'near_poi')) + 'areas', 'near_area', 'near_poi', 'nearby')) class BaseRouterProxy: @@ -535,26 +545,30 @@ class RouterSpace(BaseRouterProxy): areas = {pk: area for pk, area in areas.items() if pk in self.areas and area.can_describe and area.access_restriction_id not in restrictions} + nearby = ((area, area.geometry.distance(point)) for area in areas.values()) + nearby = tuple((area, distance) for area, distance in nearby if distance < 20) + contained = tuple(area for area in areas.values() if area.geometry_prep.contains(point)) if contained: - return tuple(sorted(contained, key=lambda area: area.geometry.area)), None + return tuple(sorted(contained, key=lambda area: area.geometry.area)), None, nearby - near = ((area, area.geometry.distance(point)) for area in areas.values()) - near = tuple((area, distance) for area, distance in near if distance < 5) + near = tuple((area, distance) for area, distance in nearby if distance < 5) if not near: - return (), None - return (), min(near, key=operator.itemgetter(1))[0] + return (), None, nearby + return (), min(near, key=operator.itemgetter(1))[0], nearby def poi_for_point(self, pois, point, restrictions): point = Point(point.x, point.y) pois = {pk: poi for pk, poi in pois.items() if pk in self.pois and poi.can_describe and poi.access_restriction_id not in restrictions} - near = ((poi, poi.geometry.distance(point)) for poi in pois.values()) - near = tuple((poi, distance) for poi, distance in near if distance < 5) + nearby = ((poi, poi.geometry.distance(point)) for poi in pois.values()) + nearby = tuple((poi, distance) for poi, distance in nearby if distance < 20) + + near = tuple((poi, distance) for poi, distance in nearby if distance < 5) if not near: - return None - return min(near, key=operator.itemgetter(1))[0] + return None, nearby + return min(near, key=operator.itemgetter(1))[0], nearby class RouterArea(BaseRouterProxy): diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index 9be097de..9422e23b 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -213,13 +213,18 @@ c3nav = { }, state: {}, - update_state: function(routing, replace, details, options) { + update_state: function(routing, replace, details, options, nearby) { if (typeof routing !== "boolean") routing = c3nav.state.routing; if (details) { options = false; + nearby = false; } else if (options) { details = false; + nearby = false; + } else if (nearby) { + details = false; + options = false; } var destination = $('#destination-input').data('location'), @@ -230,7 +235,8 @@ c3nav = { destination: destination, sidebar: true, details: !!details, - options: !!options + options: !!options, + nearby: !!nearby, }; c3nav._push_state(new_state, replace); @@ -669,6 +675,9 @@ c3nav = { if (state.details && (url.startsWith('/l/') || url.startsWith('/r/'))) { url += 'details/' } + if (state.nearby && url.startsWith('/l/')) { + url += 'nearby/' + } if (state.options && url.startsWith('/r/')) { url += 'options/' } @@ -1212,6 +1221,7 @@ c3nav = { L.Icon.Default.imagePath = '/static/leaflet/images/'; c3nav._add_icon('origin'); c3nav._add_icon('destination'); + c3nav._add_icon('nearby'); // setup scale control L.control.scale({imperial: false}).addTo(c3nav.map); @@ -1281,7 +1291,7 @@ c3nav = { if (nearby) { var $destination = $('#destination-input'); c3nav._locationinput_set($destination, data); - c3nav.update_state(false); + c3nav.update_state(false, false, false, false, true); } else { newpopup = L.popup(c3nav._add_map_padding({ className: 'location-popup', @@ -1333,6 +1343,24 @@ c3nav = { c3nav._visible_map_locations = []; if (origin) c3nav._merge_bounds(bounds, c3nav._add_location_to_map(origin, single ? new L.Icon.Default() : c3nav.originIcon)); if (destination) c3nav._merge_bounds(bounds, c3nav._add_location_to_map(destination, single ? new L.Icon.Default() : c3nav.destinationIcon)); + var done = []; + if (c3nav.state.nearby && destination && 'areas' in destination) { + if (destination.space) { + c3nav._merge_bounds(bounds, c3nav._add_location_to_map(c3nav.locations_by_id[destination.space], c3nav.nearbyIcon, true)); + } + if (destination.near_area) { + done.push(destination.near_area); + c3nav._merge_bounds(bounds, c3nav._add_location_to_map(c3nav.locations_by_id[destination.near_area], c3nav.nearbyIcon, true)); + } + for (var area of destination.areas) { + done.push(area); + c3nav._merge_bounds(bounds, c3nav._add_location_to_map(c3nav.locations_by_id[area], c3nav.nearbyIcon, true)); + } + for (var location of destination.nearby) { + if (location in done) continue; + c3nav._merge_bounds(bounds, c3nav._add_location_to_map(c3nav.locations_by_id[location], c3nav.nearbyIcon, true)); + } + } c3nav._locationLayerBounds = bounds; }, fly_to_bounds: function(replace_state, nofly) { diff --git a/src/c3nav/site/urls.py b/src/c3nav/site/urls.py index 1a367969..5f102c9a 100644 --- a/src/c3nav/site/urls.py +++ b/src/c3nav/site/urls.py @@ -8,12 +8,13 @@ slug = r'(?P[a-z0-9-_.:]+)' coordinates = r'(?P[a-z0-9-_:]+:-?\d+(\.\d+)?:-?\d+(\.\d+)?)' slug2 = r'(?P[a-z0-9-_.:]+)' details = r'(?P
details/)?' +nearby = r'(?Pnearby/)?' options = r'(?Poptions/)?' pos = r'(@(?P[a-z0-9-_:]+),(?P-?\d+(\.\d+)?),(?P-?\d+(\.\d+)?),(?P-?\d+(\.\d+)?))?' embed = r'(?Pembed/)?' urlpatterns = [ - url(r'^%s(?P[l])/%s/%s%s$' % (embed, slug, details, pos), map_index, name='site.index'), + url(r'^%s(?P[l])/%s/(%s|%s)%s$' % (embed, slug, details, nearby, pos), map_index, name='site.index'), url(r'^%s(?P[od])/%s/%s$' % (embed, slug, pos), map_index, name='site.index'), url(r'^%sr/%s/%s/(%s|%s)%s$' % (embed, slug, slug2, details, options, pos), map_index, name='site.index'), url(r'^%s(?Pr)/%s$' % (embed, pos), map_index, name='site.index'), diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index 085f13fb..99c3d3c4 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -57,7 +57,7 @@ def check_location(location: Optional[str], request) -> Optional[SpecificLocatio return location -def map_index(request, mode=None, slug=None, slug2=None, details=None, options=None, +def map_index(request, mode=None, slug=None, slug2=None, details=None, options=None, nearby=None, level=None, x=None, y=None, zoom=None, embed=None): # check for access token @@ -108,6 +108,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N 'sidebar': routing or destination is not None, 'details': True if details else False, 'options': True if options else False, + 'nearby': True if nearby else False, } levels = levels_by_short_label_for_request(request) diff --git a/src/c3nav/static/img/marker-icon-nearby-2x.png b/src/c3nav/static/img/marker-icon-nearby-2x.png new file mode 100644 index 00000000..791429aa Binary files /dev/null and b/src/c3nav/static/img/marker-icon-nearby-2x.png differ diff --git a/src/c3nav/static/img/marker-icon-nearby.png b/src/c3nav/static/img/marker-icon-nearby.png new file mode 100644 index 00000000..50676d60 Binary files /dev/null and b/src/c3nav/static/img/marker-icon-nearby.png differ