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
on reload.
A plain static server without these proxies (e.g. `python3 -m http.server
58881`) runs the UI, but terrain, basemap and road data will not load.
The proxies are a convenience, not a requirement: every data source is also
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
@@ -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
`<script src>` or a CSS `<link>`).
Runtime *data* is still fetched at load (tiles, Overpass, weather): serve the
file from the Docker image for the full experience. Opened via `file://` or a
proxy-less server it renders sky, clouds and UI with an "OFFLINE"-labelled
ground grid instead of basemap tiles.
Runtime *data* is fetched at load, but from public sources only: ESRI basemap
tiles, the AWS elevation tiles and the Overpass mirrors are all CORS-enabled,
and Open-Meteo needs no key. So the built file runs on its own - from any
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
+5 -6
View File
@@ -15,12 +15,11 @@ What gets inlined:
(window.__inlineThemeCss, so
theme switching works offline)
The result needs no sibling files. Runtime DATA is still fetched on load:
basemap and elevation tiles, Overpass road/settlement cells and the Open-Meteo
weather API. Serve the file from the same nginx as the development site (its
CORS proxies make the tiles reachable) or open it with a warm IndexedDB cache
for fully offline rendering. Opened via file:// it still runs sky, clouds,
terrain shading and the whole UI, just without the textured ground.
The result needs no sibling files and no server of ours: basemap tiles, the
elevation tiles and the Overpass mirrors are all CORS-enabled public sources,
so the file runs from any static host or straight from disk (file://). The
bundled nginx is optional - it adds the same-origin proxies and a 30-day
Overpass cache, which is the gentlest way to hit those upstreams.
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:
// 'streets' - ESRI World_Street_Map (place names), /esri/streets/
// 'satellite' - ESRI World_Imagery (no labels), /esri/imagery/
// Both are z11 {z}/{y}/{x}.jpg via the nginx CORS proxy. The IndexedDB
// id carries a style prefix so both styles can coexist in the cache.
// Both are z11 {z}/{y}/{x}.jpg (y before x). ESRI answers cross-origin
// 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 &&
window.CONFIG.ground.style) || '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/' : '';
// Debug tile overlays (border + N/S + x/y labels) - off unless
// 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) {
// ESRI World_Street_Map format: {z}/{y}/{x}.jpg via local
// proxy. (y before x, jpg not png)
// ESRI World_Street_Map format: {z}/{y}/{x}.jpg (y before x, jpg not png)
return fetch(GROUND_TILE_URL + GROUND_TILE_Z + '/' + tileY + '/' + tileX + '.jpg')
.then(function (r) {
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_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_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 elevCtx = 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.
var OP_PUBLIC = [
'https://overpass-api.de/api/interpreter',
'https://overpass.kumi.systems/api/interpreter',
'https://maps.mail.ru/osm/tools/overpass/api/interpreter'
];
var OP_PROXY = ['/overpass/api/interpreter', '/overpass/mail/interpreter'];