From 4500fe3214ee51cdb0ac8c457f4697880638f77a Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Thu, 8 Oct 2026 09:17:15 +0200 Subject: [PATCH] Switch to ESRI World_Street_Map via local CORS proxy Carto Voyager was returning 'API Key required' content for the user (likely rate-limited or IP-blocked by Carto). Switched to ESRI World_Street_Map which has CORS-friendly tiles with plenty of place names and street labels. URL format is different from Carto: {z}/{y}/{x}.jpg (y before x, jpg not png). Updated the build to match. The local /esri/ proxy in default.conf handles the upstream CORS-incompatibility by adding permissive CORS headers and a proper User-Agent. --- default.conf | 22 ++++++++++++++++++---- vendor/clouds3d.js | 7 ++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/default.conf b/default.conf index 0129b77..ad17d94 100644 --- a/default.conf +++ b/default.conf @@ -8,10 +8,8 @@ server { try_files $uri $uri/ =404; } - # CORS proxy for map tiles. Forwards to the upstream tile server - # but injects permissive CORS headers so the browser can load - # tiles via cross-origin img.src requests. The 'resolver' is - # required by the proxy module to resolve the upstream hostname. + # CORS proxy for Carto basemaps (place names, OSM-derived). + # URL format: /carto/voyager/{z}/{x}/{y}.png location ^~ /carto/ { resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s; proxy_pass https://a.basemaps.cartocdn.com/; @@ -23,6 +21,22 @@ server { add_header Access-Control-Max-Age 86400 always; } + # CORS proxy for ESRI World_Street_Map. URL format: + # /esri/wsm/{z}/{y}/{x}.jpg -> ESRI /ArcGIS/.../World_Street_Map/.../tile/{z}/{y}/{x} + location ^~ /esri/ { + resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s; + rewrite ^/esri/streets/(.*)$ /ArcGIS/rest/services/World_Street_Map/MapServer/tile/$1 break; + rewrite ^/esri/topo/(.*)$ /ArcGIS/rest/services/World_Topo_Map/MapServer/tile/$1 break; + rewrite ^/esri/imagery/(.*)$ /ArcGIS/rest/services/World_Imagery/MapServer/tile/$1 break; + proxy_pass https://server.arcgisonline.com; + proxy_ssl_server_name on; + proxy_set_header Host server.arcgisonline.com; + proxy_set_header User-Agent "Mozilla/5.0 (compatible; tile-proxy)"; + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always; + add_header Access-Control-Max-Age 86400 always; + } + # CORS preflight handler location ~* \.(png|jpg)$ { if ($request_method = 'OPTIONS') { diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index 654ea5a..b63a18f 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -284,7 +284,7 @@ function init(canvas) { // mosaic centre always corresponds to UV (0.5, 0.5). // Tile source: Carto basemaps Voyager style (with place names). // Free, CORS-enabled, OSM-derived. URL format {z}/{x}/{y}.png. -var GROUND_TILE_URL = '/carto/voyager/'; +var GROUND_TILE_URL = '/esri/streets/'; var GROUND_TILE_Z = 11; var GROUND_TILE_SIZE = 256; // Fetch direction: which way the tile corridor is oriented. @@ -318,8 +318,9 @@ function fetchTileImage(tileX, tileY) { img.crossOrigin = 'anonymous'; img.onload = function() { resolve(img); }; img.onerror = function() { reject(new Error('tile fetch failed')); }; - // Carto/OSM-style format: {z}/{x}/{y}.png via local proxy - img.src = GROUND_TILE_URL + GROUND_TILE_Z + '/' + tileX + '/' + tileY + '.png'; + // ESRI World_Street_Map format: {z}/{y}/{x}.jpg via local + // proxy. (y before x, jpg not png) + img.src = GROUND_TILE_URL + GROUND_TILE_Z + '/' + tileY + '/' + tileX + '.jpg'; }); }