From 55aad97b75351b671e7c3ab3a05c43d2decc05c5 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 17 Nov 2022 18:47:38 +0100 Subject: initial support for proxying OSM tiles --- development.ini | 1 + fietsboek/__init__.py | 9 + fietsboek/locale/fietslog.pot | 102 +++++------ fietsboek/routes.py | 3 + fietsboek/static/osm-monkeypatch.js | 352 ++++++++++++++++++++++++++++++++++++ fietsboek/templates/layout.jinja2 | 3 + fietsboek/views/tileproxy.py | 72 ++++++++ poetry.lock | 214 +++++++++++++++++++++- pylint.toml | 2 +- pyproject.toml | 2 + 10 files changed, 705 insertions(+), 55 deletions(-) create mode 100644 fietsboek/static/osm-monkeypatch.js create mode 100644 fietsboek/views/tileproxy.py diff --git a/development.ini b/development.ini index eb33586..3259d3b 100644 --- a/development.ini +++ b/development.ini @@ -18,6 +18,7 @@ pyramid.includes = pyramid_debugtoolbar sqlalchemy.url = sqlite:///%(here)s/fietsboek.sqlite +redis.url = redis://localhost/ fietsboek.data_dir = %(here)s/data retry.attempts = 3 diff --git a/fietsboek/__init__.py b/fietsboek/__init__.py index a727230..6dc435a 100644 --- a/fietsboek/__init__.py +++ b/fietsboek/__init__.py @@ -2,8 +2,10 @@ For more information, see the README or the included documentation. """ +import importlib.metadata from pathlib import Path +import redis from pyramid.config import Configurator from pyramid.session import SignedCookieSessionFactory from pyramid.csrf import CookieCSRFStoragePolicy @@ -16,6 +18,9 @@ from .pages import Pages from . import jinja2 as fiets_jinja2 +__VERSION__ = importlib.metadata.version('fietsboek') + + def locale_negotiator(request): """Negotiates the right locale to use. @@ -57,6 +62,9 @@ def main(global_config, **settings): data_dir = request.registry.settings["fietsboek.data_dir"] return DataManager(Path(data_dir)) + def redis_(request): + return redis.from_url(request.registry.settings["redis.url"]) + settings['enable_account_registration'] = asbool( settings.get('enable_account_registration', 'false')) settings['available_locales'] = aslist( @@ -90,6 +98,7 @@ def main(global_config, **settings): config.set_locale_negotiator(locale_negotiator) config.add_request_method(data_manager, reify=True) config.add_request_method(pages, reify=True) + config.add_request_method(redis_, name="redis", reify=True) jinja2_env = config.get_jinja2_environment() jinja2_env.filters['format_decimal'] = fiets_jinja2.filter_format_decimal diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot index 91390c8..abb2bfc 100644 --- a/fietsboek/locale/fietslog.pot +++ b/fietsboek/locale/fietslog.pot @@ -8,14 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-08-10 13:36+0200\n" +"POT-Creation-Date: 2022-11-15 23:42+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.3\n" +"Generated-By: Babel 2.11.0\n" #: fietsboek/util.py:282 msgid "password_constraint.mismatch" @@ -153,43 +153,43 @@ msgstr "" msgid "page.browse.synthetic_tooltip" msgstr "" -#: fietsboek/templates/browse.jinja2:132 fietsboek/templates/details.jinja2:88 +#: fietsboek/templates/browse.jinja2:132 fietsboek/templates/details.jinja2:90 msgid "page.details.date" msgstr "" -#: fietsboek/templates/browse.jinja2:134 fietsboek/templates/details.jinja2:102 +#: fietsboek/templates/browse.jinja2:134 fietsboek/templates/details.jinja2:104 msgid "page.details.length" msgstr "" -#: fietsboek/templates/browse.jinja2:139 fietsboek/templates/details.jinja2:93 +#: fietsboek/templates/browse.jinja2:139 fietsboek/templates/details.jinja2:95 msgid "page.details.start_time" msgstr "" -#: fietsboek/templates/browse.jinja2:141 fietsboek/templates/details.jinja2:97 +#: fietsboek/templates/browse.jinja2:141 fietsboek/templates/details.jinja2:99 msgid "page.details.end_time" msgstr "" -#: fietsboek/templates/browse.jinja2:146 fietsboek/templates/details.jinja2:106 +#: fietsboek/templates/browse.jinja2:146 fietsboek/templates/details.jinja2:108 msgid "page.details.uphill" msgstr "" -#: fietsboek/templates/browse.jinja2:148 fietsboek/templates/details.jinja2:110 +#: fietsboek/templates/browse.jinja2:148 fietsboek/templates/details.jinja2:112 msgid "page.details.downhill" msgstr "" -#: fietsboek/templates/browse.jinja2:153 fietsboek/templates/details.jinja2:115 +#: fietsboek/templates/browse.jinja2:153 fietsboek/templates/details.jinja2:117 msgid "page.details.moving_time" msgstr "" -#: fietsboek/templates/browse.jinja2:155 fietsboek/templates/details.jinja2:119 +#: fietsboek/templates/browse.jinja2:155 fietsboek/templates/details.jinja2:121 msgid "page.details.stopped_time" msgstr "" -#: fietsboek/templates/browse.jinja2:159 fietsboek/templates/details.jinja2:123 +#: fietsboek/templates/browse.jinja2:159 fietsboek/templates/details.jinja2:125 msgid "page.details.max_speed" msgstr "" -#: fietsboek/templates/browse.jinja2:161 fietsboek/templates/details.jinja2:127 +#: fietsboek/templates/browse.jinja2:161 fietsboek/templates/details.jinja2:129 msgid "page.details.avg_speed" msgstr "" @@ -293,40 +293,40 @@ msgstr "" msgid "page.details.delete.close" msgstr "" -#: fietsboek/templates/details.jinja2:69 +#: fietsboek/templates/details.jinja2:70 msgid "page.details.tags" msgstr "" -#: fietsboek/templates/details.jinja2:78 fietsboek/templates/edit.jinja2:10 +#: fietsboek/templates/details.jinja2:80 fietsboek/templates/edit.jinja2:10 #: fietsboek/templates/finish_upload.jinja2:10 msgid "page.noscript" msgstr "" -#: fietsboek/templates/details.jinja2:83 +#: fietsboek/templates/details.jinja2:85 msgid "page.details.download" msgstr "" -#: fietsboek/templates/details.jinja2:172 +#: fietsboek/templates/details.jinja2:174 msgid "page.details.comments" msgstr "" -#: fietsboek/templates/details.jinja2:176 +#: fietsboek/templates/details.jinja2:178 msgid "page.details.comments.author" msgstr "" -#: fietsboek/templates/details.jinja2:193 +#: fietsboek/templates/details.jinja2:195 msgid "page.details.comments.new.title" msgstr "" -#: fietsboek/templates/details.jinja2:196 +#: fietsboek/templates/details.jinja2:198 msgid "page.details.comments.new.input_title" msgstr "" -#: fietsboek/templates/details.jinja2:197 +#: fietsboek/templates/details.jinja2:199 msgid "page.details.comments.new.input_comment" msgstr "" -#: fietsboek/templates/details.jinja2:200 +#: fietsboek/templates/details.jinja2:202 msgid "page.details.comments.new.submit" msgstr "" @@ -394,40 +394,40 @@ msgstr "" msgid "page.track.form.tags" msgstr "" -#: fietsboek/templates/edit_form.jinja2:43 +#: fietsboek/templates/edit_form.jinja2:50 msgid "page.track.form.add_tag" msgstr "" -#: fietsboek/templates/edit_form.jinja2:48 +#: fietsboek/templates/edit_form.jinja2:55 msgid "page.track.form.tagged_people" msgstr "" -#: fietsboek/templates/edit_form.jinja2:63 +#: fietsboek/templates/edit_form.jinja2:70 msgid "page.track.form.add_friend" msgstr "" -#: fietsboek/templates/edit_form.jinja2:83 +#: fietsboek/templates/edit_form.jinja2:90 msgid "page.track.form.badges" msgstr "" -#: fietsboek/templates/edit_form.jinja2:94 +#: fietsboek/templates/edit_form.jinja2:101 msgid "page.track.form.description" msgstr "" -#: fietsboek/templates/edit_form.jinja2:101 -#: fietsboek/templates/edit_form.jinja2:115 +#: fietsboek/templates/edit_form.jinja2:108 +#: fietsboek/templates/edit_form.jinja2:122 msgid "page.track.form.remove_image" msgstr "" -#: fietsboek/templates/edit_form.jinja2:110 +#: fietsboek/templates/edit_form.jinja2:117 msgid "page.track.form.select_images" msgstr "" -#: fietsboek/templates/edit_form.jinja2:126 +#: fietsboek/templates/edit_form.jinja2:133 msgid "page.track.form.image_description_modal" msgstr "" -#: fietsboek/templates/edit_form.jinja2:133 +#: fietsboek/templates/edit_form.jinja2:140 msgid "page.track.form.image_description_modal.save" msgstr "" @@ -457,43 +457,43 @@ msgstr "" msgid "page.navbar.toggle" msgstr "" -#: fietsboek/templates/layout.jinja2:46 +#: fietsboek/templates/layout.jinja2:51 msgid "page.navbar.home" msgstr "" -#: fietsboek/templates/layout.jinja2:49 +#: fietsboek/templates/layout.jinja2:54 msgid "page.navbar.browse" msgstr "" -#: fietsboek/templates/layout.jinja2:53 +#: fietsboek/templates/layout.jinja2:58 msgid "page.navbar.upload" msgstr "" -#: fietsboek/templates/layout.jinja2:57 +#: fietsboek/templates/layout.jinja2:67 msgid "page.navbar.user" msgstr "" -#: fietsboek/templates/layout.jinja2:61 +#: fietsboek/templates/layout.jinja2:71 msgid "page.navbar.welcome_user" msgstr "" -#: fietsboek/templates/layout.jinja2:64 +#: fietsboek/templates/layout.jinja2:74 msgid "page.navbar.logout" msgstr "" -#: fietsboek/templates/layout.jinja2:67 +#: fietsboek/templates/layout.jinja2:77 msgid "page.navbar.profile" msgstr "" -#: fietsboek/templates/layout.jinja2:71 +#: fietsboek/templates/layout.jinja2:81 msgid "page.navbar.admin" msgstr "" -#: fietsboek/templates/layout.jinja2:77 +#: fietsboek/templates/layout.jinja2:87 msgid "page.navbar.login" msgstr "" -#: fietsboek/templates/layout.jinja2:81 +#: fietsboek/templates/layout.jinja2:91 msgid "page.navbar.create_account" msgstr "" @@ -645,43 +645,43 @@ msgstr "" msgid "flash.badge_deleted" msgstr "" -#: fietsboek/views/default.py:75 +#: fietsboek/views/default.py:96 msgid "flash.invalid_credentials" msgstr "" -#: fietsboek/views/default.py:79 +#: fietsboek/views/default.py:100 msgid "flash.account_not_verified" msgstr "" -#: fietsboek/views/default.py:82 +#: fietsboek/views/default.py:103 msgid "flash.logged_in" msgstr "" -#: fietsboek/views/default.py:96 +#: fietsboek/views/default.py:117 msgid "flash.logged_out" msgstr "" -#: fietsboek/views/default.py:127 +#: fietsboek/views/default.py:148 msgid "flash.reset_invalid_email" msgstr "" -#: fietsboek/views/default.py:132 +#: fietsboek/views/default.py:153 msgid "flash.password_token_generated" msgstr "" -#: fietsboek/views/default.py:137 +#: fietsboek/views/default.py:158 msgid "page.password_reset.email.subject" msgstr "" -#: fietsboek/views/default.py:141 +#: fietsboek/views/default.py:162 msgid "page.password_reset.email.body" msgstr "" -#: fietsboek/views/default.py:168 +#: fietsboek/views/default.py:189 msgid "flash.email_verified" msgstr "" -#: fietsboek/views/default.py:182 +#: fietsboek/views/default.py:203 msgid "flash.password_updated" msgstr "" diff --git a/fietsboek/routes.py b/fietsboek/routes.py index ab1eabf..9286f13 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -54,3 +54,6 @@ def includeme(config): config.add_route('delete-friend', '/me/delete-friend') config.add_route('accept-friend', '/me/accept-friend') config.add_route('json-friends', '/me/friends.json') + + config.add_route('tile-proxy', + '/tile/{provider}/{z:\\d+}/{x:\\d+}/{y:\\d+}') diff --git a/fietsboek/static/osm-monkeypatch.js b/fietsboek/static/osm-monkeypatch.js new file mode 100644 index 0000000..203a4ca --- /dev/null +++ b/fietsboek/static/osm-monkeypatch.js @@ -0,0 +1,352 @@ +/* We want to override JB.Map to add our own maps instead. + * We do this by (ab)using the JS property system to override the setter, so + * that JB.Map won't actually set the new function. This means we don't have to + * source-patch the gmutils.js file. + */ +"use strict"; + +(() => { + let ourMap = function(makemap) { + var dieses = this; + var id = makemap.id; + var mapcanvas = makemap.mapdiv; + dieses.id = id; + dieses.makemap = makemap; + dieses.mapcanvas = mapcanvas; + this.cluster_zoomhistory = []; + + // Map anlegen + + 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["TopPlusOpen"] = L.tileLayer(BASE_URL + 'tile/topplusopen/{z}/{x}/{y}', { + maxZoom: 18, + attribution: mycp+'Kartendaten: © Bundesamt für Kartographie und Geodäsie' + }); + + // 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", + osm: "OSM", + osmde: "OSMDE", + opentopo: "Open Topo", + topplusopen: "TopPlusOpen", + cycle: "Cycle", + landscape: "Landscape", + outdoors: "Outdoors", + keinekarte: "Keine Karte", + pasdecarte: "Pas de carte", + nomap: "No Map", + ning\u00FAnmapa: "Ning\u00FAn Mapa", + nessunamappa: "Nessuna mappa", + opensea: "Open Sea", + hiking: "Hiking", + cycling: "Cycling", + } + + // ['hiking', 'cycling', 'mtb', 'skating', 'slopes', 'riding']; + + var genugplatz = JB.platzgenug(makemap.mapdiv); + + this.map = L.map(mapcanvas, { + // layers: osm, + closePopupOnClick: false, + scrollWheelZoom: genugplatz & makemap.parameters.scrollwheelzoom, + tap: genugplatz, + keyboard: genugplatz, + touchZoom: true, + dragging: true, + } ); + + JB.handle_touch_action(dieses,genugplatz); + + if(makemap.parameters.unit=="si") L.control.scale({imperial:false}).addTo(this.map); // Mit Maßstab km + else L.control.scale({metric:false}).addTo(this.map); // Mit Maßstab ml + + var ctrl_layer = null; + var showmaptypecontroll_save = makemap.parameters.showmaptypecontroll; + JB.onresize(mapcanvas,function(w,h) { + makemap.parameters.showmaptypecontroll = (w>200 && h>190 && showmaptypecontroll_save); + if(makemap.parameters.showmaptypecontroll) { + if(!ctrl_layer) ctrl_layer = L.control.layers(dieses.baseLayers, dieses.overlayLayers).addTo(dieses.map); + } + else { + if(ctrl_layer) { + ctrl_layer.remove(); + ctrl_layer = null; + } + } + },true); + + // Button für Full Screen / normale Größe + var fullscreen = false; + if(makemap.parameters.fullscreenbutton) { + var fsb = document.createElement("button"); + fsb.style.backgroundColor = "transparent"; + fsb.style.border = "none"; + fsb.style.padding = "7px 7px 7px 0"; + fsb.style.cursor = "pointer"; + var fsbim = document.createElement("img"); + fsbim.width = 31; + fsbim.height = 31; + fsbim.src = JB.GPX2GM.Path+"Icons/fullscreen_p.svg"; + fsb.title = fsbim.title = fsbim.alt = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].fullScreen; + fsbim.large = false; + var ele = mapcanvas.parentNode; + fsb.onclick = function() { + this.blur(); + if(fsbim.large) { + document.body.style.overflow = ""; + fsbim.src = JB.GPX2GM.Path+"Icons/fullscreen_p.svg"; + fsb.title = fsbim.title = fsbim.alt = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].fullScreen; + ele.style.left = ele.oleft + "px"; + ele.style.top = ele.otop + "px"; + ele.style.width = ele.owidth + "px"; + ele.style.height = ele.oheight + "px"; + ele.style.margin = ele.omargin; + ele.style.padding = ele.opadding; + window.setTimeout(function() { + JB.removeClass("JBfull",ele); + ele.style.position = ele.sposition; + ele.style.left = ele.sleft; + ele.style.top = ele.stop; + ele.style.width = ele.swidth; + ele.style.height = ele.sheight; + //ele.style.zIndex = ele.szindex; + },1000); + JB.handle_touch_action(dieses,genugplatz); + fullscreen = false; + } + else { + document.body.style.overflow = "hidden"; + fsbim.src = JB.GPX2GM.Path+"Icons/fullscreen_m.svg"; + fsb.title = fsbim.title = fsbim.alt = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].normalSize; + var scrollY = 0; + if(document.documentElement.scrollTop && document.documentElement.scrollTop!=0) scrollY = document.documentElement.scrollTop; + else if(document.body.scrollTop && document.body.scrollTop!=0) scrollY = document.body.scrollTop; + else if(window.scrollY) scrollY = window.scrollY; + else if(window.pageYOffset) scrollY = window.pageYOffset; + var rect = JB.getRect(ele); + ele.oleft = rect.left; + ele.otop = rect.top - scrollY; + ele.owidth = rect.width; + ele.oheight = rect.height; + //ele.szindex = ele.style.zIndex; + ele.sposition = ele.style.position; + ele.omargin = ele.style.margin; + ele.opadding = ele.style.padding; + ele.sleft = ele.style.left; + ele.stop = ele.style.top; + ele.swidth = ele.style.width; + ele.sheight = ele.style.height; + ele.style.position = "fixed"; + ele.style.left = ele.oleft+"px"; + ele.style.top = ele.otop+"px"; + ele.style.width = ele.owidth+"px"; + ele.style.height = ele.oheight+"px"; + //ele.style.zIndex = "1001"; + window.setTimeout(function() { + JB.addClass("JBfull",ele); + ele.style.width = "100%"; + ele.style.height = "100%"; + ele.style.left = "0px"; + ele.style.top = "0px"; + ele.style.margin = "0px"; + ele.style.padding = "0px"; + },100); + dieses.map.scrollWheelZoom.enable(); + JB.handle_touch_action(dieses,true); + makemap.mapdiv.focus(); + fullscreen = true; + } + fsbim.large = !fsbim.large; + }; + fsb.appendChild(fsbim); + fsb.index = 0; + L.Control.Fsbutton = L.Control.extend({ + onAdd: function(map) { + return fsb; + } + }); + var fsbutton = new L.Control.Fsbutton({ position: 'topright' }); + fsbutton.addTo(this.map); + } // fullscreenbutton + + // Button für Traffic-Layer + if(makemap.parameters.trafficbutton) { + console.warn("Traffic-Layer wird unter Leaflet (noch) nicht unterstützt."); + } + + // Button für Anzeige aktuelle Position + if(makemap.parameters.currentlocationbutton) { + var clb = document.createElement("button"); + clb.style.backgroundColor = "white"; + clb.style.border = "none"; + clb.style.width = "28px"; + clb.style.height = "28px"; + clb.style.margin = "10px 10px 0 0"; + clb.style.borderRadius = "2px"; + clb.style.cursor = "pointer"; + clb.title = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].showCurrentLocation; + var clbimg = document.createElement("img"); + clbimg.style.position = "absolute"; + clbimg.style.top = "50%"; + clbimg.style.left = "50%"; + clbimg.style.transform = "translate(-50%, -50%)"; + clbimg.src = JB.GPX2GM.Path+"Icons/whereami.svg"; + var wpid = -1, marker = null, first; + clb.onclick = function() { + this.blur(); + if (navigator.geolocation) { + var geolocpos = function(position) { + var lat = position.coords.latitude; + var lon = position.coords.longitude; + marker.setLatLng([lat,lon]); + if(first) { + dieses.map.setView([lat,lon]); + first = false; + } + } + var geolocerror = function(error) { + var errorCodes = ["Permission Denied","Position unavailible","Timeout"]; + var errorString = (error.code<=3)?errorCodes[error.code-1]:"Error code: "+error.code; + JB.Debug_Info("Geolocation-Dienst fehlgeschlagen!",errorString+". "+error.message,true); + } + first = true; + if(!marker) marker = dieses.Marker({lat:0,lon:0},JB.icons.CL)[0]; + if ( wpid == -1 ) { + clb.title = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].hideCurrentLocation; + wpid = navigator.geolocation.watchPosition(geolocpos,geolocerror,{enableHighAccuracy:true, timeout: 5000, maximumAge: 60000}); + marker.addTo(dieses.map); + JB.Debug_Info("","Geolocation-Dienst wird eingerichtet.",false); + } + else { + clb.title = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].showCurrentLocation; + navigator.geolocation.clearWatch(wpid); + wpid = -1; + marker.remove(); + JB.Debug_Info("","Geolocation-Dienst wird abgeschaltet.",false); + } + } + else JB.Debug_Info("geolocation","Geolocation wird nicht unterstützt!",true); + } // click-Handler + clb.appendChild(clbimg); + L.Control.Clbutton = L.Control.extend({ + onAdd: function(map) { + return clb; + } + }); + var clbutton = new L.Control.Clbutton({ position: 'topright' }); + clbutton.addTo(this.map); + } // currentlocationbutton + + // Scalieren nach MAP-Resize + dieses.zoomstatus = {}; + dieses.zoomstatus.iszoomed = false; + dieses.zoomstatus.zoom_changed = function() { + dieses.zoomstatus.iszoomed = true; + dieses.zoomstatus.level = dieses.map.getZoom(); + dieses.zoomstatus.w = mapcanvas.offsetWidth; + dieses.zoomstatus.h = mapcanvas.offsetHeight; + } + dieses.zoomstatus.move_end = function() { + dieses.zoomstatus.iszoomed = true; + dieses.mapcenter = dieses.map.getCenter(); + } + dieses.map.on("moveend", dieses.zoomstatus.move_end); + JB.onresize(mapcanvas,function(w,h) { + if(w*h==0) return; + dieses.map.invalidateSize(); + dieses.map.setView(dieses.mapcenter); + dieses.map.off("zoomend", dieses.zoomstatus.zoom_changed); + if(dieses.zoomstatus.iszoomed) { + var dz = Math.round(Math.min(Math.log(w/dieses.zoomstatus.w)/Math.LN2,Math.log(h/dieses.zoomstatus.h)/Math.LN2)); + dieses.map.setZoom(dieses.zoomstatus.level+dz); + } + else { + if(dieses.bounds) { + dieses.map.fitBounds(dieses.bounds,{padding:[20,20]}); + dieses.map.setView(dieses.mapcenter); + dieses.zoomstatus.level = dieses.map.getZoom(); + dieses.zoomstatus.w = w; + dieses.zoomstatus.h = h; + } + } + if(!fullscreen) { + genugplatz = JB.platzgenug(makemap.mapdiv); + JB.handle_touch_action(dieses,genugplatz); + } + }); + }; + window.JB = window.JB || {}; + Object.defineProperty(window.JB, "Map", { + get() { return ourMap; }, + set(_) {}, + }); +})(); diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index 7371052..bbd84f8 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -20,6 +20,7 @@ + + diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py new file mode 100644 index 0000000..16fd4c5 --- /dev/null +++ b/fietsboek/views/tileproxy.py @@ -0,0 +1,72 @@ +"""Tile proxying layer. + +While this might slow down the initial load (as we now load everything through +fietsboek), we can cache the OSM tiles per instance, and we can provide better +access control for services like thunderforest.com. + +Additionally, this protects the users' IP, as only fietsboek can see it. +""" +import datetime +import random + +from pyramid.view import view_config +from pyramid.response import Response +from pyramid.httpexceptions import HTTPBadRequest + +import requests + +from .. import __VERSION__ + + +TILE_SOURCES = { + # Main base layers + 'osm': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'osmde': 'https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', + 'opentopo': 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', + 'topplusopen': 'https://sgx.geodatenzentrum.de/wmts_topplus_open/tile/' + '1.0.0/web/default/WEBMERCATOR/{z}/{y}/{x}.png', + 'satellite': 'https://server.arcgisonline.com/ArcGIS/rest/services/' + 'World_Imagery/MapServer/tile/{z}/{y}/{x}', + + # Overlay layers + 'opensea': 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', + 'hiking': 'https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png', + 'cycling': 'https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png', +} + +TTL = datetime.timedelta(days=7) + + +@view_config(route_name='tile-proxy', http_cache=3600) +def tile_proxy(request): + """Requests the given tile from the proxy. + + :param request: The Pyramid request. + :type request: pyramid.request.Request + :return: The HTTP response. + :rtype: pyramid.response.Response + """ + provider = request.matchdict['provider'] + if provider not in TILE_SOURCES: + return HTTPBadRequest("Invalid provider") + + x, y, z = (int(request.matchdict['x']), int(request.matchdict['y']), + int(request.matchdict['z'])) + cache_key = f"tile-{provider}-{x}-{y}-{z}" + content_type = "image/png" + + cached = request.redis.get(cache_key) + if cached is not None: + return Response(cached, content_type=content_type) + + url = TILE_SOURCES[provider].format(x=x, y=y, z=z, s=random.choice("abc")) + headers = { + "user-agent": f"Fietsboek-Tile-Proxy/{__VERSION__}", + } + from_mail = request.registry.settings.get('email.from') + if from_mail: + headers["from"] = from_mail + + resp = requests.get(url, headers=headers) + request.redis.set(cache_key, resp.content, ex=TTL) + return Response(resp.content, content_type=resp.headers.get("Content-type", content_type)) diff --git a/poetry.lock b/poetry.lock index d9fb521..fc38406 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,6 +15,17 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + [[package]] name = "attrs" version = "22.1.0" @@ -71,6 +82,14 @@ webencodings = "*" css = ["tinycss2 (>=1.1.0,<1.2)"] dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "cffi" version = "1.15.1" @@ -82,6 +101,17 @@ python-versions = "*" [package.dependencies] pycparser = "*" +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + [[package]] name = "click" version = "8.1.3" @@ -135,6 +165,20 @@ sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] + [[package]] name = "exceptiongroup" version = "1.0.4" @@ -178,6 +222,14 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" docs = ["Sphinx", "pylons-sphinx-themes", "watchdog"] testing = ["mock", "pytest", "pytest-cov", "watchdog"] +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "importlib-metadata" version = "5.0.0" @@ -276,7 +328,7 @@ name = "packaging" version = "21.3" description = "Core utilities for Python packages" category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -367,7 +419,7 @@ name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" -optional = true +optional = false python-versions = ">=3.6.8" [package.extras] @@ -524,6 +576,25 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "redis" +version = "4.3.4" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} +packaging = ">=20.4" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "repoze-lru" version = "0.7" @@ -536,6 +607,24 @@ python-versions = "*" docs = ["Sphinx"] testing = ["coverage", "nose"] +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "setuptools" version = "65.5.1" @@ -641,6 +730,19 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "venusian" version = "3.0.0" @@ -702,6 +804,14 @@ WebOb = ">=1.2" docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"] tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-cov"] +[[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + [[package]] name = "zipp" version = "3.10.0" @@ -768,13 +878,17 @@ testing = ["WebTest", "pytest", "pytest-cov"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "5452a4dee7949b3ed0f47ddae85defaef989f8ffc06b77cfea1e0f3116e6a40c" +content-hash = "ef5a076f811b957b45dfbac68ef2012ddb94c13aa89e229bf53b3dc0fa6d7cca" [metadata.files] alembic = [ {file = "alembic-1.8.1-py3-none-any.whl", hash = "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4"}, {file = "alembic-1.8.1.tar.gz", hash = "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa"}, ] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, @@ -791,6 +905,10 @@ bleach = [ {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, ] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] cffi = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, @@ -857,6 +975,10 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -945,6 +1067,10 @@ cryptography = [ {file = "cryptography-38.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a"}, {file = "cryptography-38.0.3.tar.gz", hash = "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd"}, ] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] exceptiongroup = [ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, @@ -1018,6 +1144,10 @@ hupper = [ {file = "hupper-1.10.3-py2.py3-none-any.whl", hash = "sha256:f683850d62598c02faf3c7cdaaa727d8cbe3c5a2497a5737a8358386903b2601"}, {file = "hupper-1.10.3.tar.gz", hash = "sha256:cd6f51b72c7587bc9bce8a65ecd025a1e95f1b03284519bfe91284d010316cd9"}, ] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] importlib-metadata = [ {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, @@ -1152,10 +1282,18 @@ pytz = [ {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, ] +redis = [ + {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, + {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, +] repoze-lru = [ {file = "repoze.lru-0.7-py3-none-any.whl", hash = "sha256:f77bf0e1096ea445beadd35f3479c5cff2aa1efe604a133e67150bc8630a62ea"}, {file = "repoze.lru-0.7.tar.gz", hash = "sha256:0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77"}, ] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] setuptools = [ {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, @@ -1227,6 +1365,10 @@ typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] venusian = [ {file = "venusian-3.0.0-py3-none-any.whl", hash = "sha256:06e7385786ad3a15c70740b2af8d30dfb063a946a851dcb4159f9e2a2302578f"}, {file = "venusian-3.0.0.tar.gz", hash = "sha256:f6842b7242b1039c0c28f6feef29016e7e7dd3caaeb476a193acf737db31ee38"}, @@ -1247,6 +1389,72 @@ webtest = [ {file = "WebTest-3.0.0-py3-none-any.whl", hash = "sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead"}, {file = "WebTest-3.0.0.tar.gz", hash = "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"}, ] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] zipp = [ {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, diff --git a/pylint.toml b/pylint.toml index 7495cf7..9e3a63c 100644 --- a/pylint.toml +++ b/pylint.toml @@ -149,7 +149,7 @@ function-naming-style = "snake_case" # function-rgx = # Good variable names which should always be accepted, separated by a comma. -good-names = ["i", "j", "k", "ex", "Run", "_", "id"] +good-names = ["i", "j", "k", "ex", "Run", "_", "id", "x", "y", "z"] # Good variable names regexes, separated by a comma. If names match any regex, # they will always be accepted diff --git a/pyproject.toml b/pyproject.toml index 9fab1a6..3504164 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ SQLAlchemy = "^1.4" alembic = "^1.8" transaction = "^3" "zope.sqlalchemy" = "^1.6" +redis = "^4.3.4" importlib_resources = "^5.10" Babel = "^2.11" @@ -45,6 +46,7 @@ gpxpy = "^1.5" markdown = "^3.4" bleach = "^5" Click = "^8.1" +requests = "^2.28.1" WebTest = {version = "^3", optional = true} pytest = {version = "^7.2", optional = true} -- cgit v1.2.3 From 8155036b1af08dfa6cd5ff9a746f018f79350cef Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 17 Nov 2022 19:07:31 +0100 Subject: add a timeout to the tile proxy This avoids blocking the whole pipeline --- fietsboek/views/tileproxy.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py index 16fd4c5..01dc9a9 100644 --- a/fietsboek/views/tileproxy.py +++ b/fietsboek/views/tileproxy.py @@ -8,16 +8,20 @@ Additionally, this protects the users' IP, as only fietsboek can see it. """ import datetime import random +import logging from pyramid.view import view_config from pyramid.response import Response -from pyramid.httpexceptions import HTTPBadRequest +from pyramid.httpexceptions import HTTPBadRequest, HTTPGatewayTimeout import requests +from requests.exceptions import ReadTimeout from .. import __VERSION__ +LOGGER = logging.getLogger(__name__) + TILE_SOURCES = { # Main base layers 'osm': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', @@ -35,6 +39,7 @@ TILE_SOURCES = { } TTL = datetime.timedelta(days=7) +TIMEOUT = datetime.timedelta(seconds=1.5) @view_config(route_name='tile-proxy', http_cache=3600) @@ -67,6 +72,11 @@ def tile_proxy(request): if from_mail: headers["from"] = from_mail - resp = requests.get(url, headers=headers) - request.redis.set(cache_key, resp.content, ex=TTL) - return Response(resp.content, content_type=resp.headers.get("Content-type", content_type)) + try: + resp = requests.get(url, headers=headers, timeout=TIMEOUT.total_seconds()) + except ReadTimeout: + LOGGER.debug("Proxy timeout when accessing %r", url) + return HTTPGatewayTimeout(f"No response in time from {url}") + else: + request.redis.set(cache_key, resp.content, ex=TTL) + return Response(resp.content, content_type=resp.headers.get("Content-type", content_type)) -- cgit v1.2.3 From b12336b2eabb2787c9089eae438b753856d361f3 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 17 Nov 2022 19:21:48 +0100 Subject: remember timeouts for tile servers 1. This makes Fietsboek faster because we don't have to wait for the timeout on every single request. 2. This is better for the servers, as we don't add more requests when they're already overloaded. --- fietsboek/views/tileproxy.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py index 01dc9a9..5195c91 100644 --- a/fietsboek/views/tileproxy.py +++ b/fietsboek/views/tileproxy.py @@ -39,7 +39,16 @@ TILE_SOURCES = { } TTL = datetime.timedelta(days=7) +"""Time to live of cached tiles.""" + TIMEOUT = datetime.timedelta(seconds=1.5) +"""Timeout when requesting new tiles from a source server.""" + +PUNISHMENT_TTL = datetime.timedelta(minutes=10) +"""Block-out period after too many requests of a server have timed out.""" + +PUNISHMENT_THRESHOLD = 10 +"""Block a provider after that many requests have timed out.""" @view_config(route_name='tile-proxy', http_cache=3600) @@ -57,13 +66,21 @@ def tile_proxy(request): x, y, z = (int(request.matchdict['x']), int(request.matchdict['y']), int(request.matchdict['z'])) - cache_key = f"tile-{provider}-{x}-{y}-{z}" + cache_key = f"tile:{provider}-{x}-{y}-{z}" content_type = "image/png" cached = request.redis.get(cache_key) if cached is not None: return Response(cached, content_type=content_type) + timeout_tracker = f"provider-timeout:{provider}" + previous_timeouts = int(request.redis.get(timeout_tracker) or "0") + if previous_timeouts > PUNISHMENT_THRESHOLD: + # We've gotten too many timeouts from this provider recently, so avoid + # contacting it in the first place. + LOGGER.debug("Aborted attempt to contact %s due to previous timeouts", provider) + return HTTPGatewayTimeout(f"Avoiding request to {provider}") + url = TILE_SOURCES[provider].format(x=x, y=y, z=z, s=random.choice("abc")) headers = { "user-agent": f"Fietsboek-Tile-Proxy/{__VERSION__}", @@ -76,6 +93,8 @@ def tile_proxy(request): resp = requests.get(url, headers=headers, timeout=TIMEOUT.total_seconds()) except ReadTimeout: LOGGER.debug("Proxy timeout when accessing %r", url) + request.redis.incr(timeout_tracker) + request.redis.expire(timeout_tracker, PUNISHMENT_TTL) return HTTPGatewayTimeout(f"No response in time from {url}") else: request.redis.set(cache_key, resp.content, ex=TTL) -- cgit v1.2.3 From c1f8b0e55db852e4cfffd75af34bd6f146b35b77 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 17 Nov 2022 22:45:57 +0100 Subject: doc: add intersphinx links for jinja2/markupsafe --- doc/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index e7f04e0..7769cf1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -36,6 +36,8 @@ intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None), 'sqlalchemy': ('https://docs.sqlalchemy.org/en/14/', None), + 'jinja2': ('https://jinja.palletsprojects.com/en/3.0.x/', None), + 'markupsafe': ('https://markupsafe.palletsprojects.com/en/2.1.x/', None), } # Add any paths that contain templates here, relative to this directory. -- cgit v1.2.3 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 @@