diff --git a/src/c3nav/mapdata/urls.py b/src/c3nav/mapdata/urls.py index b9fea0ad..078d9240 100644 --- a/src/c3nav/mapdata/urls.py +++ b/src/c3nav/mapdata/urls.py @@ -2,8 +2,10 @@ from django.urls import path, register_converter from c3nav.mapdata.converters import (AccessPermissionsConverter, ArchiveFileExtConverter, HistoryFileExtConverter, HistoryModeConverter, SignedIntConverter) -from c3nav.mapdata.views import get_cache_package, map_history, tile +from c3nav.mapdata.views import get_cache_package, map_history, tile, preview_location, preview_route +from c3nav.site.converters import LocationConverter +register_converter(LocationConverter, 'loc') register_converter(SignedIntConverter, 'sint') register_converter(AccessPermissionsConverter, 'a_perms') register_converter(HistoryModeConverter, 'h_mode') @@ -12,6 +14,8 @@ register_converter(ArchiveFileExtConverter, 'archive_fileext') urlpatterns = [ path('///.png', tile, name='mapdata.tile'), + path('preview/l/.png', preview_location, name='mapdata.preview.location'), + # path('preview/r//.png', preview_route, name='mapdata.preview.route'), path('////.png', tile, name='mapdata.tile'), path('history//.', map_history, name='mapdata.map_history'), path('cache/package.', get_cache_package, name='mapdata.cache_package'), diff --git a/src/c3nav/mapdata/views.py b/src/c3nav/mapdata/views.py index 45c7dc02..11328946 100644 --- a/src/c3nav/mapdata/views.py +++ b/src/c3nav/mapdata/views.py @@ -11,17 +11,28 @@ from django.http import Http404, HttpResponse, HttpResponseNotModified, Streamin from django.shortcuts import get_object_or_404 from django.utils.http import content_disposition_header from django.views.decorators.http import etag +from shapely import Point, Polygon from c3nav.mapdata.middleware import no_language from c3nav.mapdata.models import Level, MapUpdate from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.render.engines import ImageRenderEngine +from c3nav.mapdata.render.engines.base import FillAttribs, StrokeAttribs from c3nav.mapdata.render.renderer import MapRenderer from c3nav.mapdata.utils.cache import CachePackage, MapHistory from c3nav.mapdata.utils.tiles import (build_access_cache_key, build_base_cache_key, build_tile_access_cookie, build_tile_etag, get_tile_bounds, parse_tile_access_cookie) +PREVIEW_HIGHLIGHT_FILL_COLOR = settings.PRIMARY_COLOR +PREVIEW_HIGHLIGHT_FILL_OPACITY = 0.1 +PREVIEW_HIGHLIGHT_STROKE_COLOR = PREVIEW_HIGHLIGHT_FILL_COLOR +PREVIEW_HIGHLIGHT_STROKE_WIDTH = 0.5 +PREVIEW_IMG_WIDTH = 1200 +PREVIEW_IMG_HEIGHT = 628 +PREVIEW_MIN_Y = 100 + + def set_tile_access_cookie(request, response): access_permissions = AccessPermission.get_for_request(request) if access_permissions: @@ -48,6 +59,78 @@ def enforce_tile_secret_auth(request): raise PermissionDenied +@no_language() +def preview_location(request, slug): + from c3nav.site.views import check_location + from c3nav.mapdata.utils.locations import CustomLocation + from c3nav.mapdata.models.geometry.base import GeometryMixin + + location = check_location(slug, request) + highlight = True + if location is None: + raise Http404 + if isinstance(location, CustomLocation): + geometry = Point(location.x, location.y) + level = location.level.pk + elif isinstance(location, GeometryMixin): + geometry = location.geometry + level = location.level_id + elif isinstance(location, Level): + [minx, miny, maxx, maxy] = location.bounds + geometry = Polygon([(minx, miny), (minx, maxy), (maxx, maxy), (maxx, miny), (minx, miny)]) + level = location.pk + highlight = False + else: + raise NotImplementedError(f'location type {type(location)} is not supported yet') + cache_package = CachePackage.open_cached() + + if isinstance(geometry, Point): + geometry = geometry.buffer(1) + + bounds = geometry.bounds + + if not cache_package.bounds_valid(bounds[0], bounds[1], bounds[2], bounds[3]): + raise Http404 + + bounds_width = bounds[2] - bounds[0] + bounds_height = bounds[3] - bounds[1] + + height = PREVIEW_MIN_Y + if height < bounds_height: + height = bounds_height + 10 + width = height * PREVIEW_IMG_WIDTH / PREVIEW_IMG_HEIGHT + if width < bounds_width: + width = bounds_width + 10 + height = width * PREVIEW_IMG_HEIGHT / PREVIEW_IMG_WIDTH + + dx = width - bounds_width + dy = height - bounds_height + minx = bounds[0] - dx/2 + maxx = bounds[2] + dx/2 + miny = bounds[1] - dy/2 + maxy = bounds[3] + dy/2 + + img_scale = PREVIEW_IMG_HEIGHT/height + + level_data = cache_package.levels.get(level) + if level_data is None: + raise Http404 + renderer = MapRenderer(level, minx, miny, maxx, maxy, scale=img_scale, access_permissions=set()) + image = renderer.render(ImageRenderEngine) + if highlight: + image.add_geometry(geometry, fill=FillAttribs(PREVIEW_HIGHLIGHT_FILL_COLOR, PREVIEW_HIGHLIGHT_FILL_OPACITY), + stroke=StrokeAttribs(PREVIEW_HIGHLIGHT_STROKE_COLOR, PREVIEW_HIGHLIGHT_STROKE_WIDTH), + category='highlight') + data = image.render() + response = HttpResponse(data, 'image/png') + return response + + +@no_language() +def preview_route(request, slug, slug2): + raise NotImplementedError() + + @no_language() def tile(request, level, zoom, x, y, access_permissions: Optional[set] = None): if access_permissions is not None: diff --git a/src/c3nav/site/templates/site/base.html b/src/c3nav/site/templates/site/base.html index eb8514c1..69522618 100644 --- a/src/c3nav/site/templates/site/base.html +++ b/src/c3nav/site/templates/site/base.html @@ -27,6 +27,8 @@ {% endcompress %} + {% block head %} + {% endblock %} {% if not embed and not request.mobileclient %} diff --git a/src/c3nav/site/templates/site/map.html b/src/c3nav/site/templates/site/map.html index 27292f21..f7dada73 100644 --- a/src/c3nav/site/templates/site/map.html +++ b/src/c3nav/site/templates/site/map.html @@ -3,6 +3,34 @@ {% load compress %} {% load i18n %} +{% block title %}{% if meta.title %}{{ meta.title }}{% else %}{{ block.super }}{% endif %}{% endblock %} + +{% block head %} + {% if meta %} + + + + {% endif %} + {% if meta.title %} + + + + {% endif %} + {% if meta.description %} + + + + {% endif %} + {% if meta.preview_img_url %} + + + {% endif %} + {% if meta.canonical_url %} + + + {% endif %} +{% endblock %} + {% block content %}
{% if not request.mobileclient %} diff --git a/src/c3nav/site/urls.py b/src/c3nav/site/urls.py index cf499962..2352327a 100644 --- a/src/c3nav/site/urls.py +++ b/src/c3nav/site/urls.py @@ -8,7 +8,6 @@ from c3nav.site.views import (about_view, access_redeem_view, account_manage, ac logout_view, map_index, position_create, position_detail, position_list, position_set, qr_code, register_view, report_create, report_detail, report_list) -register_converter(LocationConverter, 'loc') register_converter(CoordinatesConverter, 'coords') register_converter(AtPositionConverter, 'at_pos') register_converter(IsEmbedConverter, 'is_embed') @@ -19,13 +18,13 @@ pos = '' def index_paths(pre, suf): return [ - path(f'{pre}l//{suf}', map_index, {'mode': 'l'}, name='site.index', ), + path(f'{pre}l//{suf}', map_index, {'mode': 'l', 'details': False, 'nearby': False}, name='site.index', ), path(f'{pre}l//details/{suf}', map_index, {'mode': 'l', 'details': True}, name='site.index'), path(f'{pre}l//nearby/{suf}', map_index, {'mode': 'l', 'nearby': True}, name='site.index'), path(f'{pre}o//{suf}', map_index, {'mode': 'o'}, name='site.index'), path(f'{pre}d//{suf}', map_index, {'mode': 'd'}, name='site.index'), path(f'{pre}r/{suf}', map_index, {'mode': 'r'}, name='site.index'), - path(f'{pre}r///{suf}', map_index, {'mode': 'r'}, name='site.index'), + path(f'{pre}r///{suf}', map_index, {'mode': 'r', 'details': False, 'options': False}, name='site.index'), path(f'{pre}r///details/{suf}', map_index, {'mode': 'r', 'details': True}, name='site.index'), path(f'{pre}r///options/{suf}', map_index, {'mode': 'r', 'options': True}, diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index 3231a033..c16495e3 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -61,7 +61,6 @@ def check_location(location: Optional[str], request) -> Optional[SpecificLocatio def map_index(request, mode=None, slug=None, slug2=None, details=None, options=None, nearby=None, pos=None, embed=None): - # check for access token access_token = request.GET.get('access') if access_token: @@ -133,6 +132,44 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N if not initial_bounds: initial_bounds = tuple(chain(*Source.max_bounds())) + if origin is not None and destination is not None: + metadata = { + 'title': _('Route from %s to %s') % (origin.title, destination.title), + # TODO: enable when route image generation is implemented + # 'preview_img_url': request.build_absolute_uri(reverse('mapdata.preview.route', kwargs={ + # 'slug': slug, + # 'slug2': slug2, + # })), + 'canonical_url': request.build_absolute_uri(reverse('site.index', kwargs={ + 'mode': 'r', + 'slug': slug, + 'slug2': slug2, + 'details': False, + 'options': False, + })), + } + elif destination is not None or origin is not None: + metadata = { + 'title': destination.title, + 'description': destination.subtitle, + 'preview_img_url': request.build_absolute_uri(reverse('mapdata.preview.location', kwargs={'slug': slug})), + 'canonical_url': request.build_absolute_uri(reverse('site.index', kwargs={ + 'mode': 'l', + 'slug': slug, + 'nearby': False, + 'details': False, + })), + } + elif mode is None: + metadata = { + 'title': 'c3nav', + # 'description': '', + # 'preview_img_url': '', + 'canonical_url': request.build_absolute_uri('/'), + } + else: + metadata = None + ctx = { 'bounds': json.dumps(Source.max_bounds(), separators=(',', ':')), 'levels': json.dumps(tuple((level.pk, level.short_label) for level in levels.values()), separators=(',', ':')), @@ -149,6 +186,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N 'editor': can_access_editor(request), 'embed': bool(embed), 'imprint': settings.IMPRINT_LINK, + 'meta': metadata, } if grid.enabled: