From 27b35a384d21d3cbe3c910e12b5f256737849fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Wed, 25 Dec 2019 00:40:19 +0100 Subject: [PATCH] show nearby locations --- src/c3nav/mapdata/utils/locations.py | 5 +++ src/c3nav/routing/router.py | 42 ++++++++++++------ src/c3nav/site/static/site/js/c3nav.js | 34 ++++++++++++-- src/c3nav/site/urls.py | 3 +- src/c3nav/site/views.py | 3 +- .../static/img/marker-icon-nearby-2x.png | Bin 0 -> 7523 bytes src/c3nav/static/img/marker-icon-nearby.png | Bin 0 -> 5340 bytes 7 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 src/c3nav/static/img/marker-icon-nearby-2x.png create mode 100644 src/c3nav/static/img/marker-icon-nearby.png 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 0000000000000000000000000000000000000000..791429aaecf302613fd09c35e1297ba4f86ab5a2 GIT binary patch literal 7523 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..50676d60c14fb6464a44e300c1eec4c1458b98ad GIT binary patch literal 5340 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