From 5607aa64cac1f67169e742d397c2ac8d9c9057a8 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 17 Nov 2022 22:46:42 +0100 Subject: enable custom map layers This change shuffles around quite a bit because we no longer hardcode our map layers in osm-monkeypatch.js, but instead can pass them through from the Python code. This allows us to dynamically define extra layers, for example to disable layers the admin doesn't want, or to add extra layers that are not yet available. This change for example allows to embed thunderforest maps by adding the following to the config: fietsboek.tile_layers.tf_cycling = TF Cycling fietsboek.tile_layers.tf_cycling.url = https://thunderforest.com/... fietsboek.tile_layers.tf_cycling.access = restricted The next step is to make the Thunderforest maps a bit easier to access (by providing special support for those), but for now, this seems like a good first step and the necessary groundwork. --- fietsboek/__init__.py | 8 +- fietsboek/jinja2.py | 29 +++++ fietsboek/static/osm-monkeypatch.js | 74 +++---------- fietsboek/templates/layout.jinja2 | 1 + fietsboek/views/tileproxy.py | 207 +++++++++++++++++++++++++++++++++--- 5 files changed, 244 insertions(+), 75 deletions(-) diff --git a/fietsboek/__init__.py b/fietsboek/__init__.py index 6dc435a..21674f5 100644 --- a/fietsboek/__init__.py +++ b/fietsboek/__init__.py @@ -51,7 +51,8 @@ def locale_negotiator(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - # pylint: disable=unused-argument + # pylint: disable=unused-argument, import-outside-toplevel, cyclic-import + from .views import tileproxy if settings.get('session_key', '') == '': raise ValueError("Please set a session signing key (session_key) in your settings!") @@ -71,6 +72,10 @@ def main(global_config, **settings): settings.get('available_locales', 'en')) settings['fietsboek.pages'] = aslist( settings.get('fietsboek.pages', '')) + settings['fietsboek.default_tile_layers'] = aslist( + settings.get('fietsboek.default_tile_layers', + 'osm satellite osmde opentopo topplusopen opensea cycling hiking')) + settings['fietsboek.tile_layers'] = tileproxy.extract_tile_layers(settings) # Load the pages page_manager = Pages() @@ -104,5 +109,6 @@ def main(global_config, **settings): jinja2_env.filters['format_decimal'] = fiets_jinja2.filter_format_decimal jinja2_env.filters['format_datetime'] = fiets_jinja2.filter_format_datetime jinja2_env.filters['local_datetime'] = fiets_jinja2.filter_local_datetime + jinja2_env.globals['embed_tile_layers'] = fiets_jinja2.global_embed_tile_layers return config.make_wsgi_app() diff --git a/fietsboek/jinja2.py b/fietsboek/jinja2.py index 6043791..ff60660 100644 --- a/fietsboek/jinja2.py +++ b/fietsboek/jinja2.py @@ -1,5 +1,6 @@ """Custom filters for Jinja2.""" import datetime +import json import jinja2 from markupsafe import Markup @@ -77,3 +78,31 @@ def filter_local_datetime(ctx, value): return Markup( f'{fallback}' ) + + +def global_embed_tile_layers(request): + """Renders the available tile servers for the current user, as a JSON object. + + The returned value is wrapped as a :class:`~markupsafe.Markup` so that it + won't get escaped by jinja. + + :param request: The Pyramid request. + :type request: pyramid.request.Request + :return: The available tile servers. + :rtype: markupsafe.Markup + """ + # pylint: disable=import-outside-toplevel,cyclic-import + from .views import tileproxy + tile_sources = tileproxy.sources_for(request) + return Markup(json.dumps([ + { + "name": source.name, + "url": request.route_url("tile-proxy", provider=source.key, x="{x}", y="{y}", z="{z}") + .replace("%7Bx%7D", "{x}") + .replace("%7By%7D", "{y}") + .replace("%7Bz%7D", "{z}"), + "attribution": source.attribution, + "type": source.layer_type.value, + } + for source in tile_sources + ])) diff --git a/fietsboek/static/osm-monkeypatch.js b/fietsboek/static/osm-monkeypatch.js index 203a4ca..a8310a2 100644 --- a/fietsboek/static/osm-monkeypatch.js +++ b/fietsboek/static/osm-monkeypatch.js @@ -19,77 +19,31 @@ const mycp = 'GPXViewer | '; - this.baseLayers = {}; - - this.baseLayers["OSM"] = L.tileLayer(BASE_URL + 'tile/osm/{z}/{x}/{y}', { - maxZoom: 19, - attribution: mycp+'Map data © OpenStreetMap and contributors CC-BY-SA' - }); - - this.baseLayers["Satellit"] = L.tileLayer(BASE_URL + 'tile/satellite/{z}/{x}/{y}', { - maxZoom: 21, - attribution: mycp+'Map data © Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' - }); - - this.baseLayers["OSMDE"] = L.tileLayer(BASE_URL + 'tile/osmde/{z}/{x}/{y}', { - maxZoom: 19, - attribution: mycp+'Map data © OpenStreetMap and contributors CC-BY-SA' - }); - - this.baseLayers["Open Topo"] = L.tileLayer(BASE_URL + 'tile/opentopo/{z}/{x}/{y}', { - maxZoom: 17, - attribution: mycp+'Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | Kartendarstellung: © OpenTopoMap (CC-BY-SA)' - }); + this.baseLayers = {}; + this.overlayLayers = {}; - this.baseLayers["TopPlusOpen"] = L.tileLayer(BASE_URL + 'tile/topplusopen/{z}/{x}/{y}', { - maxZoom: 18, - attribution: mycp+'Kartendaten: © Bundesamt für Kartographie und Geodäsie' - }); + for (let layer of TILE_LAYERS) { + if (layer.type === "base") { + this.baseLayers[layer.name] = L.tileLayer(layer.url, { + maxZoom: layer.zoom, + attribution: layer.attribution, + }); + } else if (layer.type === "overlay") { + this.overlayLayers[layer.name] = L.tileLayer(layer.url, { + attribution: layer.attribution, + }); + } + } // https://tileserver.4umaps.com/${z}/${x}/${y}.png // zoomlevel 16 // https://www.4umaps.com/ - if(JB.GPX2GM.OSM_Cycle_Api_Key && JB.GPX2GM.OSM_Cycle_Api_Key.length>0) { - this.baseLayers["Cycle"] = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey='+JB.GPX2GM.OSM_Cycle_Api_Key, { - maxZoom: 22, - attribution: mycp+'Map data © OpenCycleMap and contributors CC-BY-SA' - }); - } - - if(JB.GPX2GM.OSM_Landscape_Api_Key && JB.GPX2GM.OSM_Landscape_Api_Key.length>0) { - this.baseLayers["Landscape"] = L.tileLayer('https://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png?apikey='+JB.GPX2GM.OSM_Landscape_Api_Key, { - maxZoom: 22, - attribution: mycp+'Map data © OpenLandscapeMap and contributors CC-BY-SA' - }); - } - - if(JB.GPX2GM.OSM_Outdoors_Api_Key && JB.GPX2GM.OSM_Outdoors_Api_Key.length>0) { - this.baseLayers["Outdoors"] = L.tileLayer('https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey='+JB.GPX2GM.OSM_Outdoors_Api_Key, { - maxZoom: 22, - attribution: mycp+'Map data © OpenOutdoorsMap and contributors CC-BY-SA' - }); - } - this.baseLayers[JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].noMap]= L.tileLayer(JB.GPX2GM.Path+"Icons/Grau256x256.png", { maxZoom: 22, attribution: mycp }); - this.overlayLayers = {}; - - this.overlayLayers["Open Sea"] = L.tileLayer(BASE_URL + 'tile/opensea/{z}/{x}/{y}', { - attribution: 'Kartendaten: © OpenSeaMap contributors' - }); - - this.overlayLayers["Hiking"] = L.tileLayer(BASE_URL + 'tile/hiking/{z}/{x}/{y}', { - attribution: '© Sarah Hoffmann (CC-BY-SA)', - }); - - this.overlayLayers["Cycling"] = L.tileLayer(BASE_URL + 'tile/cycling/{z}/{x}/{y}', { - attribution: '© Sarah Hoffmann (CC-BY-SA)', - }); - this.layerNameTranslate = { satellit: "Satellit", satellite: "Satellit", diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index bbd84f8..bf30143 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -20,6 +20,7 @@