standalone without our server: basemap and DEM load from the CORS-enabled upstreams, same-origin proxy only when a probe proves it exists; drop dead kumi.systems mirror

This commit is contained in:
Richard Zink committed 2026-10-11 16:02:10 +02:00
1 parent 3395ac8f8e
commit 499c00d8b1
3 files changed
+62 -19

No files matched your search

+14 -6
View File
@@ -68,8 +68,12 @@ The bundled nginx config (`default.conf`) is more than a file server:
- `Cache-Control: no-store` for HTML and JS, so a changed `CONFIG` takes effect - `Cache-Control: no-store` for HTML and JS, so a changed `CONFIG` takes effect
on reload. on reload.
A plain static server without these proxies (e.g. `python3 -m http.server The proxies are a convenience, not a requirement: every data source is also
58881`) runs the UI, but terrain, basemap and road data will not load. reachable directly from the browser (ESRI, the AWS elevation bucket and the
Overpass mirrors all answer cross-origin requests), and the page probes for
`/terrarium/` and `/esri/...` at start-up and switches to the proxy only when
this same-origin proxy actually exists. Running the files from any static
server therefore works as well - just without the nginx-side Overpass cache.
## Single-file build ## Single-file build
@@ -86,10 +90,14 @@ python3 build.py --check # build to a temp file and verify, write not
`--check` fails if anything is left that would need a sibling file (an external `--check` fails if anything is left that would need a sibling file (an external
`<script src>` or a CSS `<link>`). `<script src>` or a CSS `<link>`).
Runtime *data* is still fetched at load (tiles, Overpass, weather): serve the Runtime *data* is fetched at load, but from public sources only: ESRI basemap
file from the Docker image for the full experience. Opened via `file://` or a tiles, the AWS elevation tiles and the Overpass mirrors are all CORS-enabled,
proxy-less server it renders sky, clouds and UI with an "OFFLINE"-labelled and Open-Meteo needs no key. So the built file runs on its own - from any
ground grid instead of basemap tiles. static server, and even opened directly via `file://`. Serving it from the
Docker image additionally gives the nginx proxies and the 30-day Overpass
cache, which is the gentlest option for the upstreams. If a source is
unreachable, the scene degrades instead of failing: an "OFFLINE"-labelled
ground grid in place of basemap tiles.
## Configuration ## Configuration
+5 -6
View File
@@ -15,12 +15,11 @@ What gets inlined:
(window.__inlineThemeCss, so (window.__inlineThemeCss, so
theme switching works offline) theme switching works offline)
The result needs no sibling files. Runtime DATA is still fetched on load: The result needs no sibling files and no server of ours: basemap tiles, the
basemap and elevation tiles, Overpass road/settlement cells and the Open-Meteo elevation tiles and the Overpass mirrors are all CORS-enabled public sources,
weather API. Serve the file from the same nginx as the development site (its so the file runs from any static host or straight from disk (file://). The
CORS proxies make the tiles reachable) or open it with a warm IndexedDB cache bundled nginx is optional - it adds the same-origin proxies and a 30-day
for fully offline rendering. Opened via file:// it still runs sky, clouds, Overpass cache, which is the gentlest way to hit those upstreams.
terrain shading and the whole UI, just without the textured ground.
The build fails loudly if any external script or stylesheet reference survives. The build fails loudly if any external script or stylesheet reference survives.
""" """
+43 -7
View File
@@ -826,12 +826,20 @@ function init(canvas) {
// Tile source per CONFIG.ground.style: // Tile source per CONFIG.ground.style:
// 'streets' - ESRI World_Street_Map (place names), /esri/streets/ // 'streets' - ESRI World_Street_Map (place names), /esri/streets/
// 'satellite' - ESRI World_Imagery (no labels), /esri/imagery/ // 'satellite' - ESRI World_Imagery (no labels), /esri/imagery/
// Both are z11 {z}/{y}/{x}.jpg via the nginx CORS proxy. The IndexedDB // Both are z11 {z}/{y}/{x}.jpg (y before x). ESRI answers cross-origin
// id carries a style prefix so both styles can coexist in the cache. // GETs with Access-Control-Allow-Origin: *, so the standalone build needs
// no server at all; the same-origin nginx paths are used instead when this
// page has them (proxy cache, friendlier to the upstream). The IndexedDB id
// carries a style prefix so both styles can coexist in the cache.
var GROUND_STYLE_CFG = (window.CONFIG && window.CONFIG.ground && var GROUND_STYLE_CFG = (window.CONFIG && window.CONFIG.ground &&
window.CONFIG.ground.style) || 'streets'; window.CONFIG.ground.style) || 'streets';
var GROUND_STYLE = GROUND_STYLE_CFG === 'satellite' ? 'satellite' : 'streets'; var GROUND_STYLE = GROUND_STYLE_CFG === 'satellite' ? 'satellite' : 'streets';
var GROUND_TILE_URL = GROUND_STYLE === 'satellite' ? '/esri/imagery/' : '/esri/streets/'; var GROUND_TILE_DIRECT = (GROUND_STYLE === 'satellite')
? 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/'
: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/';
var GROUND_TILE_PROXY = (GROUND_STYLE === 'satellite') ? '/esri/imagery/' : '/esri/streets/';
// direct by default: works from any origin, including a plain file:// open
var GROUND_TILE_URL = GROUND_TILE_DIRECT;
var GROUND_TILE_ID_PREFIX = GROUND_STYLE === 'satellite' ? 'sat/' : ''; var GROUND_TILE_ID_PREFIX = GROUND_STYLE === 'satellite' ? 'sat/' : '';
// Debug tile overlays (border + N/S + x/y labels) - off unless // Debug tile overlays (border + N/S + x/y labels) - off unless
// CONFIG.tiles.debug is true. // CONFIG.tiles.debug is true.
@@ -1172,9 +1180,31 @@ window.BackgroundCache = {
} }
}; };
// ---- tile source selection ---------------------------------------------
// Start from the public upstreams so the page works anywhere, then switch a
// source over to the same-origin proxy if a probe shows it is really served
// by our nginx (an image content-type is required, so a SPA fallback that
// answers 200 + HTML for every path cannot hijack the switch).
var _tileProxyChecked = false;
function tileProbeProxy() {
if (_tileProxyChecked) return;
_tileProxyChecked = true;
if (location.protocol === 'file:') return;
function probe(path, apply) {
fetch(path, { method: 'HEAD' }).then(function (r) {
var type = (r.headers.get('Content-Type') || '').toLowerCase();
if (r.ok && type.indexOf('image/') === 0) {
apply();
console.log('[clouds3d] tiles: using dev proxy ' + path);
}
}, function () {});
}
probe(ELEV_TILE_PROXY + '11/0/0.png', function () { ELEV_TILE_URL = ELEV_TILE_PROXY; });
probe(GROUND_TILE_PROXY + GROUND_TILE_Z + '/0/0.jpg',
function () { GROUND_TILE_URL = GROUND_TILE_PROXY; });
}
function fetchTileBlob(tileX, tileY) { function fetchTileBlob(tileX, tileY) {
// ESRI World_Street_Map format: {z}/{y}/{x}.jpg via local // ESRI World_Street_Map format: {z}/{y}/{x}.jpg (y before x, jpg not png)
// proxy. (y before x, jpg not png)
return fetch(GROUND_TILE_URL + GROUND_TILE_Z + '/' + tileY + '/' + tileX + '.jpg') return fetch(GROUND_TILE_URL + GROUND_TILE_Z + '/' + tileY + '/' + tileX + '.jpg')
.then(function (r) { .then(function (r) {
if (!r.ok) throw new Error('tile http ' + r.status); if (!r.ok) throw new Error('tile http ' + r.status);
@@ -1471,7 +1501,14 @@ function recenterGroundMosaic(centerTileX, centerTileY, dLng, dPerp) {
var ELEV_Z = 10; var ELEV_Z = 10;
var ELEV_TILES_PER = 1 << (11 - ELEV_Z); // z11 tiles covered per DEM tile var ELEV_TILES_PER = 1 << (11 - ELEV_Z); // z11 tiles covered per DEM tile
var ELEV_UNIT_PX = 256 / ELEV_TILES_PER; // canvas px per z11 tile var ELEV_UNIT_PX = 256 / ELEV_TILES_PER; // canvas px per z11 tile
var ELEV_TILE_URL = '/terrarium/'; // elevation-tiles-prod (AWS) answers cross-origin GETs with CORS, so the
// DEM also loads without our server; /terrarium/ is preferred when present.
var ELEV_TILE_DIRECT = 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/';
var ELEV_TILE_PROXY = '/terrarium/';
var ELEV_TILE_URL = ELEV_TILE_DIRECT;
// (the probe needs both URL pairs assigned, so it runs here, not at the
// ground-source block)
tileProbeProxy();
var elevCanvas = null; var elevCanvas = null;
var elevCtx = null; var elevCtx = null;
var elevTexture = null; var elevTexture = null;
@@ -2098,7 +2135,6 @@ var CAR_QUERIES = carCells('geo/mw11', CAR_MOTORWAY_RX, 46.8, 9.4, 50.1, 13.4, 1
// the proxy is actually there (see opProbeProxy), otherwise they are dropped. // the proxy is actually there (see opProbeProxy), otherwise they are dropped.
var OP_PUBLIC = [ var OP_PUBLIC = [
'https://overpass-api.de/api/interpreter', 'https://overpass-api.de/api/interpreter',
'https://overpass.kumi.systems/api/interpreter',
'https://maps.mail.ru/osm/tools/overpass/api/interpreter' 'https://maps.mail.ru/osm/tools/overpass/api/interpreter'
]; ];
var OP_PROXY = ['/overpass/api/interpreter', '/overpass/mail/interpreter']; var OP_PROXY = ['/overpass/api/interpreter', '/overpass/mail/interpreter'];