preview images for routes
This commit is contained in:
parent
7ab1881185
commit
d025ffa194
3 changed files with 152 additions and 45 deletions
|
@ -15,7 +15,7 @@ register_converter(ArchiveFileExtConverter, 'archive_fileext')
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('<int:level>/<sint:zoom>/<sint:x>/<sint:y>.png', tile, name='mapdata.tile'),
|
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/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('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('<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('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'),
|
path('cache/package.<archive_fileext:filetype>', get_cache_package, name='mapdata.cache_package'),
|
||||||
|
|
|
@ -12,7 +12,7 @@ from django.shortcuts import get_object_or_404
|
||||||
from django.utils.http import content_disposition_header
|
from django.utils.http import content_disposition_header
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.decorators.http import etag
|
from django.views.decorators.http import etag
|
||||||
from shapely import Point, box
|
from shapely import Point, box, LineString, unary_union
|
||||||
|
|
||||||
from c3nav.mapdata.middleware import no_language
|
from c3nav.mapdata.middleware import no_language
|
||||||
from c3nav.mapdata.models import Level, MapUpdate
|
from c3nav.mapdata.models import Level, MapUpdate
|
||||||
|
@ -24,7 +24,6 @@ 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,
|
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)
|
build_tile_etag, get_tile_bounds, parse_tile_access_cookie)
|
||||||
|
|
||||||
|
|
||||||
PREVIEW_HIGHLIGHT_FILL_COLOR = settings.PRIMARY_COLOR
|
PREVIEW_HIGHLIGHT_FILL_COLOR = settings.PRIMARY_COLOR
|
||||||
PREVIEW_HIGHLIGHT_FILL_OPACITY = 0.1
|
PREVIEW_HIGHLIGHT_FILL_OPACITY = 0.1
|
||||||
PREVIEW_HIGHLIGHT_STROKE_COLOR = PREVIEW_HIGHLIGHT_FILL_COLOR
|
PREVIEW_HIGHLIGHT_STROKE_COLOR = PREVIEW_HIGHLIGHT_FILL_COLOR
|
||||||
|
@ -60,35 +59,7 @@ def enforce_tile_secret_auth(request):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
|
||||||
@no_language()
|
def bounds_for_preview(geometry, cache_package):
|
||||||
@cache_page(60 * 5)
|
|
||||||
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 = box(minx, miny, maxx, maxy)
|
|
||||||
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
|
bounds = geometry.bounds
|
||||||
|
|
||||||
if not cache_package.bounds_valid(bounds[0], bounds[1], bounds[2], bounds[3]):
|
if not cache_package.bounds_valid(bounds[0], bounds[1], bounds[2], bounds[3]):
|
||||||
|
@ -107,12 +78,46 @@ def preview_location(request, slug):
|
||||||
|
|
||||||
dx = width - bounds_width
|
dx = width - bounds_width
|
||||||
dy = height - bounds_height
|
dy = height - bounds_height
|
||||||
minx = int(bounds[0] - dx/2)
|
minx = int(bounds[0] - dx / 2)
|
||||||
maxx = int(bounds[2] + dx/2)
|
maxx = int(bounds[2] + dx / 2)
|
||||||
miny = int(bounds[1] - dy/2)
|
miny = int(bounds[1] - dy / 2)
|
||||||
maxy = int(bounds[3] + dy/2)
|
maxy = int(bounds[3] + dy / 2)
|
||||||
|
img_scale = PREVIEW_IMG_HEIGHT / height
|
||||||
|
|
||||||
img_scale = PREVIEW_IMG_HEIGHT/height
|
return minx, miny, maxx, maxy, img_scale
|
||||||
|
|
||||||
|
|
||||||
|
@no_language()
|
||||||
|
@cache_page(60 * 5)
|
||||||
|
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, None)
|
||||||
|
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 = box(minx, miny, maxx, maxy)
|
||||||
|
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)
|
||||||
|
|
||||||
|
minx, miny, maxx, maxy, img_scale = bounds_for_preview(geometry, cache_package)
|
||||||
|
|
||||||
level_data = cache_package.levels.get(level)
|
level_data = cache_package.levels.get(level)
|
||||||
if level_data is None:
|
if level_data is None:
|
||||||
|
@ -125,13 +130,116 @@ def preview_location(request, slug):
|
||||||
category='highlight')
|
category='highlight')
|
||||||
data = image.render()
|
data = image.render()
|
||||||
response = HttpResponse(data, 'image/png')
|
response = HttpResponse(data, 'image/png')
|
||||||
# TODO use cache key like in tile render function
|
# TODO use cache key like in tile render function, same for the routing option
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@no_language()
|
@no_language()
|
||||||
|
@cache_page(60 * 5)
|
||||||
def preview_route(request, slug, slug2):
|
def preview_route(request, slug, slug2):
|
||||||
raise NotImplementedError()
|
from c3nav.routing.router import Router
|
||||||
|
from c3nav.routing.models import RouteOptions
|
||||||
|
from c3nav.routing.exceptions import NotYetRoutable
|
||||||
|
from c3nav.routing.exceptions import LocationUnreachable
|
||||||
|
from c3nav.routing.exceptions import NoRouteFound
|
||||||
|
from c3nav.site.views import check_location
|
||||||
|
from c3nav.mapdata.utils.locations import CustomLocation
|
||||||
|
from c3nav.mapdata.utils.geometry import unwrap_geom
|
||||||
|
origin = check_location(slug, None)
|
||||||
|
destination = check_location(slug2, None)
|
||||||
|
try:
|
||||||
|
route = Router.load().get_route(origin=origin,
|
||||||
|
destination=destination,
|
||||||
|
permissions=set(),
|
||||||
|
options=RouteOptions())
|
||||||
|
except NotYetRoutable:
|
||||||
|
raise Http404()
|
||||||
|
except LocationUnreachable:
|
||||||
|
raise Http404()
|
||||||
|
except NoRouteFound:
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
|
route_items = [route.router.nodes[x] for x in route.path_nodes]
|
||||||
|
route_points = [(item.point.x, item.point.y, route.router.spaces[item.space].level_id) for item in route_items]
|
||||||
|
|
||||||
|
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||||
|
if isinstance(origin, CustomLocation):
|
||||||
|
origin_level = origin.level.pk
|
||||||
|
origin_geometry = Point(origin.x, origin.y)
|
||||||
|
elif isinstance(origin, GeometryMixin):
|
||||||
|
origin_level = origin.level_id
|
||||||
|
origin_geometry = origin.geometry
|
||||||
|
elif isinstance(origin, Level):
|
||||||
|
raise Http404 # preview for routes from a level don't really make sense
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f'location type {type(origin)} is not implemented')
|
||||||
|
|
||||||
|
if isinstance(origin_geometry, Point):
|
||||||
|
origin_geometry = origin_geometry.buffer(1)
|
||||||
|
|
||||||
|
if isinstance(destination, CustomLocation):
|
||||||
|
destination_level = destination.level.pk
|
||||||
|
destination_geometry = Point(destination.x, destination.y)
|
||||||
|
elif isinstance(destination, GeometryMixin):
|
||||||
|
destination_level = destination.level_id
|
||||||
|
destination_geometry = destination.geometry
|
||||||
|
elif isinstance(destination, Level):
|
||||||
|
destination_level = destination.pk
|
||||||
|
destination_geometry = None
|
||||||
|
else:
|
||||||
|
destination_level = origin_level # just so that it's set to something
|
||||||
|
destination_geometry = None
|
||||||
|
if destination_level != origin_level:
|
||||||
|
destination_geometry = None
|
||||||
|
|
||||||
|
if isinstance(destination_geometry, Point):
|
||||||
|
origin_geometry = destination_geometry.buffer(1)
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
line = None
|
||||||
|
for x, y, level in route_points:
|
||||||
|
if line is None and level == origin_level:
|
||||||
|
line = [(x, y)]
|
||||||
|
elif line is not None and level == origin_level:
|
||||||
|
line.append((x, y))
|
||||||
|
elif line is not None and level != origin_level:
|
||||||
|
if len(line) > 1:
|
||||||
|
lines.append(line)
|
||||||
|
line = None
|
||||||
|
if line is not None and len(line) > 1:
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
route_geometries = [LineString(line) for line in lines]
|
||||||
|
|
||||||
|
all_geoms = [*route_geometries, unwrap_geom(origin_geometry), unwrap_geom(destination_geometry)]
|
||||||
|
combined_geometry = unary_union([x for x in all_geoms if x is not None])
|
||||||
|
|
||||||
|
cache_package = CachePackage.open_cached()
|
||||||
|
|
||||||
|
minx, miny, maxx, maxy, img_scale = bounds_for_preview(combined_geometry, cache_package)
|
||||||
|
|
||||||
|
level_data = cache_package.levels.get(origin_level)
|
||||||
|
if level_data is None:
|
||||||
|
raise Http404
|
||||||
|
renderer = MapRenderer(origin_level, minx, miny, maxx, maxy, scale=img_scale, access_permissions=set())
|
||||||
|
image = renderer.render(ImageRenderEngine)
|
||||||
|
|
||||||
|
if origin_geometry is not None:
|
||||||
|
image.add_geometry(origin_geometry, stroke=StrokeAttribs(PREVIEW_HIGHLIGHT_STROKE_COLOR,
|
||||||
|
PREVIEW_HIGHLIGHT_STROKE_WIDTH),
|
||||||
|
category='highlight')
|
||||||
|
if destination_geometry is not None:
|
||||||
|
image.add_geometry(destination_geometry, stroke=StrokeAttribs(PREVIEW_HIGHLIGHT_STROKE_COLOR,
|
||||||
|
PREVIEW_HIGHLIGHT_STROKE_WIDTH),
|
||||||
|
category='highlight')
|
||||||
|
|
||||||
|
for geom in route_geometries:
|
||||||
|
image.add_geometry(geom, stroke=StrokeAttribs(PREVIEW_HIGHLIGHT_STROKE_COLOR, PREVIEW_HIGHLIGHT_STROKE_WIDTH),
|
||||||
|
category='route')
|
||||||
|
data = image.render()
|
||||||
|
response = HttpResponse(data, 'image/png')
|
||||||
|
# TODO use cache key like in tile render function
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@no_language()
|
@no_language()
|
||||||
|
@ -191,7 +299,7 @@ def tile(request, level, zoom, x, y, access_permissions: Optional[set] = None):
|
||||||
if settings.CACHE_TILES:
|
if settings.CACHE_TILES:
|
||||||
tile_directory = settings.TILES_ROOT / str(level) / str(zoom) / str(x) / str(y)
|
tile_directory = settings.TILES_ROOT / str(level) / str(zoom) / str(x) / str(y)
|
||||||
last_update_file = tile_directory / 'last_update'
|
last_update_file = tile_directory / 'last_update'
|
||||||
tile_file = tile_directory / (access_cache_key+'.png')
|
tile_file = tile_directory / (access_cache_key + '.png')
|
||||||
|
|
||||||
# get tile cache last update
|
# get tile cache last update
|
||||||
tile_cache_update_cache_key = 'mapdata:tile-cache-update:%d-%d-%d-%d' % (level, zoom, x, y)
|
tile_cache_update_cache_key = 'mapdata:tile-cache-update:%d-%d-%d-%d' % (level, zoom, x, y)
|
||||||
|
|
|
@ -135,11 +135,10 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
|
||||||
if origin is not None and destination is not None:
|
if origin is not None and destination is not None:
|
||||||
metadata = {
|
metadata = {
|
||||||
'title': _('Route from %s to %s') % (origin.title, destination.title),
|
'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={
|
||||||
# 'preview_img_url': request.build_absolute_uri(reverse('mapdata.preview.route', kwargs={
|
'slug': slug,
|
||||||
# 'slug': slug,
|
'slug2': slug2,
|
||||||
# 'slug2': slug2,
|
})),
|
||||||
# })),
|
|
||||||
'canonical_url': request.build_absolute_uri(reverse('site.index', kwargs={
|
'canonical_url': request.build_absolute_uri(reverse('site.index', kwargs={
|
||||||
'mode': 'r',
|
'mode': 'r',
|
||||||
'slug': slug,
|
'slug': slug,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue