aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/config.py2
-rw-r--r--fietsboek/routes.py6
-rw-r--r--fietsboek/trackmap.py45
-rw-r--r--fietsboek/views/detail.py26
-rw-r--r--fietsboek/views/tileproxy.py9
5 files changed, 76 insertions, 12 deletions
diff --git a/fietsboek/config.py b/fietsboek/config.py
index 637dd17..beea820 100644
--- a/fietsboek/config.py
+++ b/fietsboek/config.py
@@ -283,7 +283,9 @@ class Config(BaseModel):
:return: A list of public :class:`TileLayerConfig`s.
"""
+ # pylint: disable=import-outside-toplevel,cyclic-import
from .views.tileproxy import DEFAULT_TILE_LAYERS, extract_tile_layers
+
return [
source
for source in chain(
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index 9c2af7b..f1d4ee2 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -46,7 +46,11 @@ def includeme(config):
config.add_route(
"image", "/track/{track_id}/images/{image_name}", factory="fietsboek.models.Track.factory"
)
- config.add_route("track-map", "/track/{track_id}/preview", factory="fietsboek.models.Track.factory")
+ config.add_route(
+ "track-map",
+ "/track/{track_id}/preview",
+ factory="fietsboek.models.Track.factory",
+ )
config.add_route("badge", "/badge/{badge_id}", factory="fietsboek.models.Badge.factory")
diff --git a/fietsboek/trackmap.py b/fietsboek/trackmap.py
index b5fc62b..2457876 100644
--- a/fietsboek/trackmap.py
+++ b/fietsboek/trackmap.py
@@ -1,8 +1,7 @@
"""Module to render tracks to static images on OSM tiles."""
+
import io
-import itertools
import math
-from urllib.parse import quote
from gpxpy.gpx import GPX
from PIL import Image, ImageDraw
@@ -15,19 +14,42 @@ TILE_SIZE = 256
def to_web_mercator(lat: float, lon: float, zoom: int) -> tuple[int, int]:
+ """Convert a pari of latitude/longitude coordinates to web mercator form.
+
+ :param lat: Latitude (in degrees).
+ :param lon: Longitude (in degrees).
+ :param zoom: Zoom level.
+ :return: The web mercator x/y coordinates. Both will be between 0 and
+ 2**zoom * 256.
+ """
width = height = TILE_SIZE
la = math.radians(lon)
phi = math.radians(lat)
x = float(2**zoom) / (2 * math.pi) * width * (la + math.pi)
- y = float(2**zoom) / (2 * math.pi) * height * (math.pi - math.log(math.tan((math.pi / 4 + phi / 2))))
+ y = (
+ float(2**zoom)
+ / (2 * math.pi)
+ * height
+ * (math.pi - math.log(math.tan((math.pi / 4 + phi / 2))))
+ )
return (int(math.floor(x)), int(math.floor(y)))
class TrackMapRenderer:
- def __init__(self, track: GPX, requester: TileRequester, size: tuple[int, int], layer: TileLayerConfig):
+ """A renderer that renders GPX tracks onto small map excerpts."""
+
+ # pylint: disable=too-few-public-methods
+
+ def __init__(
+ self,
+ track: GPX,
+ requester: TileRequester,
+ size: tuple[int, int],
+ layer: TileLayerConfig,
+ ):
self.track = track
self.requester = requester
self.size = size
@@ -37,6 +59,10 @@ class TrackMapRenderer:
self.line_width = 5
def render(self) -> Image.Image:
+ """Render the track.
+
+ :return: The image containing the rendered preview.
+ """
zoom, bbox = self._find_zoom()
image = Image.new("RGB", self.size)
start_x, start_y = self._draw_base(image, zoom, bbox)
@@ -85,7 +111,6 @@ class TrackMapRenderer:
return Image.open(io.BytesIO(tile_data))
def _draw_lines(self, image, zoom, start_x, start_y):
- starts = self.track.walk(only_points=True)
coords = (
to_web_mercator(point.latitude, point.longitude, zoom)
for point in self.track.walk(only_points=True)
@@ -97,4 +122,14 @@ class TrackMapRenderer:
def render(track: GPX, layer: TileLayerConfig, requester: TileRequester) -> Image.Image:
+ """Shorthand to construct a :class:`TrackMapRenderer` and render the preview.
+
+ :param track: Parsed track to render.
+ :param layer: The tile layer to take the map tiles from.
+ :param requester: The requester which will be used to request the tiles.
+ :return: The image containing the rendered preview.
+ """
return TrackMapRenderer(track, requester, (300, 300), layer).render()
+
+
+__all__ = ["to_web_mercator", "TrackMapRenderer", "render"]
diff --git a/fietsboek/views/detail.py b/fietsboek/views/detail.py
index c5e18b4..d7f35a8 100644
--- a/fietsboek/views/detail.py
+++ b/fietsboek/views/detail.py
@@ -18,7 +18,6 @@ from pyramid.response import FileResponse, Response
from pyramid.view import view_config
from sqlalchemy import select
-from . import tileproxy
from .tileproxy import ITileRequester
from .. import models, trackmap, util
from ..models.track import Track, TrackWithMetadata
@@ -229,6 +228,14 @@ def add_comment(request):
@view_config(route_name="track-map", http_cache=3600, permission="track.view")
def track_map(request: Request):
+ """Endpoint to provide the track's preview image.
+
+ Will use the cached version if available. Otherwise, will create the
+ preview and cache it.
+
+ :param request: The pyramid request.
+ :return: The HTTP response.
+ """
track = request.context
manager = request.data_manager.open(track.id)
preview_path = manager.preview_path()
@@ -242,11 +249,11 @@ def track_map(request: Request):
loader: ITileRequester = request.registry.getUtility(ITileRequester)
layer = request.config.public_tile_layers()[0]
- gpx = gpxpy.parse(manager.decompress_gpx())
- image = trackmap.render(gpx, layer, loader)
+ parsed_gpx = gpxpy.parse(manager.decompress_gpx())
+ track_image = trackmap.render(parsed_gpx, layer, loader)
imageio = io.BytesIO()
- image.save(imageio, "png")
+ track_image.save(imageio, "png")
tile_data = imageio.getvalue()
with manager.lock():
@@ -257,4 +264,13 @@ def track_map(request: Request):
return response
-__all__ = ["details", "gpx", "invalidate_share", "delete_track", "badge", "image", "add_comment", "track_map"]
+__all__ = [
+ "details",
+ "gpx",
+ "invalidate_share",
+ "delete_track",
+ "badge",
+ "image",
+ "add_comment",
+ "track_map",
+]
diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py
index b52d974..f472d6d 100644
--- a/fietsboek/views/tileproxy.py
+++ b/fietsboek/views/tileproxy.py
@@ -242,6 +242,8 @@ Note that new requests reset the timeout.
class ITileRequester(Interface): # pylint: disable=inherit-non-class
"""An interface to define the tile requester."""
+ # pylint: disable=too-many-arguments
+
def load_url(self, url: str, headers: Optional[dict[str, str]] = None) -> requests.Response:
"""Loads a tile at the given URL.
@@ -277,7 +279,7 @@ class ITileRequester(Interface): # pylint: disable=inherit-non-class
@implementer(ITileRequester)
-class TileRequester: # pylint: disable=too-few-public-methods
+class TileRequester:
"""Implementation of the tile requester using requests sessions.
The benefit of this over doing ``requests.get`` is that we can re-use
@@ -285,6 +287,8 @@ class TileRequester: # pylint: disable=too-few-public-methods
servers by not hammering them with too many connections.
"""
+ # pylint: disable=too-many-arguments
+
def __init__(self, redis):
self.session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
@@ -429,6 +433,9 @@ def sources_for(request: Request) -> list[TileLayerConfig]:
:param request: The Pyramid request.
:return: A list of tile sources.
"""
+ # This code is similar to Config.public_tile_layers. Maybe it's worth
+ # refactoring it when we refactor this module?
+ # pylint: disable=duplicate-code
return [
source
for source in chain(