social/sharing card with opengraph and twitter, including preview image (no routing preview for now)
This commit is contained in:
parent
6c0ed4fd1d
commit
86180c8fb0
6 changed files with 159 additions and 5 deletions
|
@ -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('<int:level>/<sint:zoom>/<sint:x>/<sint:y>.png', tile, name='mapdata.tile'),
|
||||
path('preview/l/<loc:slug>.png', preview_location, name='mapdata.preview.location'),
|
||||
# path('preview/r/<loc:slug>/<loc:slug2>.png', preview_route, name='mapdata.preview.route'),
|
||||
path('<int:level>/<sint:zoom>/<sint:x>/<sint:y>/<a_perms:access_permissions>.png', tile, name='mapdata.tile'),
|
||||
path('history/<int:level>/<h_mode:mode>.<h_fileext:filetype>', map_history, name='mapdata.map_history'),
|
||||
path('cache/package.<archive_fileext:filetype>', get_cache_package, name='mapdata.cache_package'),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
<link href="{% static 'material-symbols/material-symbols.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'site/css/c3nav.scss' %}" rel="stylesheet" type="text/x-scss">
|
||||
{% endcompress %}
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body data-user-data="{{ user_data_json }}">
|
||||
{% if not embed and not request.mobileclient %}
|
||||
|
|
|
@ -3,6 +3,34 @@
|
|||
{% load compress %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% if meta.title %}{{ meta.title }}{% else %}{{ block.super }}{% endif %}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{% if meta %}
|
||||
<meta property="twitter:card" content="summary_large_image"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="c3nav"/>
|
||||
{% endif %}
|
||||
{% if meta.title %}
|
||||
<meta name="title" content="{{ meta.title }}"/>
|
||||
<meta property="og:title" content="{{ meta.title }}"/>
|
||||
<meta property="twitter:title" content="{{ meta.title }}"/>
|
||||
{% endif %}
|
||||
{% if meta.description %}
|
||||
<meta name="description" content="{{ meta.description }}"/>
|
||||
<meta property="og:description" content="{{ meta.description }}"/>
|
||||
<meta property="twitter:description" content="{{ meta.description }}"/>
|
||||
{% endif %}
|
||||
{% if meta.preview_img_url %}
|
||||
<meta property="og:image" content="{{ meta.preview_img_url }}"/>
|
||||
<meta property="twitter:image" content="{{ meta.preview_img_url }}"/>
|
||||
{% endif %}
|
||||
{% if meta.canonical_url %}
|
||||
<meta property="og:url" content="{{ meta.canonical_url }}"/>
|
||||
<meta property="twitter:url" content="{{ meta.canonical_url }}"/>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="map" data-state="{{ state }}"{% if embed %} data-embed{% endif %} data-last-site-update="{{ last_site_update }}"{% if ssids %} data-ssids="{{ ssids }}"{% endif %} data-primary-color="{{ primary_color }}"{% if random_location_groups %} data-random-location-groups="{{ random_location_groups }}"{% endif %}>
|
||||
{% if not request.mobileclient %}
|
||||
|
|
|
@ -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 = '<at_pos:pos>'
|
|||
|
||||
def index_paths(pre, suf):
|
||||
return [
|
||||
path(f'{pre}l/<loc:slug>/{suf}', map_index, {'mode': 'l'}, name='site.index', ),
|
||||
path(f'{pre}l/<loc:slug>/{suf}', map_index, {'mode': 'l', 'details': False, 'nearby': False}, name='site.index', ),
|
||||
path(f'{pre}l/<loc:slug>/details/{suf}', map_index, {'mode': 'l', 'details': True}, name='site.index'),
|
||||
path(f'{pre}l/<loc:slug>/nearby/{suf}', map_index, {'mode': 'l', 'nearby': True}, name='site.index'),
|
||||
path(f'{pre}o/<loc:slug>/{suf}', map_index, {'mode': 'o'}, name='site.index'),
|
||||
path(f'{pre}d/<loc:slug>/{suf}', map_index, {'mode': 'd'}, name='site.index'),
|
||||
path(f'{pre}r/{suf}', map_index, {'mode': 'r'}, name='site.index'),
|
||||
path(f'{pre}r/<loc:slug>/<loc:slug2>/{suf}', map_index, {'mode': 'r'}, name='site.index'),
|
||||
path(f'{pre}r/<loc:slug>/<loc:slug2>/{suf}', map_index, {'mode': 'r', 'details': False, 'options': False}, name='site.index'),
|
||||
path(f'{pre}r/<loc:slug>/<loc:slug2>/details/{suf}', map_index, {'mode': 'r', 'details': True},
|
||||
name='site.index'),
|
||||
path(f'{pre}r/<loc:slug>/<loc:slug2>/options/{suf}', map_index, {'mode': 'r', 'options': True},
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue