physical lighting, volumetric clouds, terrain shadows, pixel-art render scale
- atmosphere: physical terrain lighting (air mass, Rayleigh beam colour, skylight, moon, starlight floor), aerial perspective for terrain and cloud deck, configurable haze/albedo/ambient gains - clouds: march the deck in height instead of ray distance, IGN dither (no per-frame flicker), per-step height jitter, 4-octave fbm renormalised, softer density threshold, distance softening as a procedural mip, grazing path clamp, cloud shadows on the ground (cloudShadowDensity/ Floor/Ambient) and cloud haze factor - terrain: self-shadows from the elevation field (penumbra + shadow floor), soft mosaic rim (edgeFadeTiles), east/west axis corrected (+X = west) - lights: per-object twilight switching for cars, city dots and ground blocks - flights: contrail width 620 m, fade to zero by contrailFadeKm, aircraft size decoupled from trail width, count halved - render: CONFIG.renderScale with integer upscale + image-rendering: pixelated - data: nginx overpass proxy + cache, per-mirror cooldown ladder, ring-based cell ordering (disc fills evenly), settlements fetched before roads, interim chain persistence, two worker lanes (car/city) with progress logs - removed: NASA VIIRS night-lights layer (fetch, canvas, texture, uniforms)
This commit is contained in:
1 parent
cd460ec100
commit
6eb25bdd03
6 files changed
+2579
-795
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
COPY overpass-cache.conf /etc/nginx/conf.d/00-overpass-cache.conf
|
||||||
COPY default.conf /etc/nginx/conf.d/default.conf
|
COPY default.conf /etc/nginx/conf.d/default.conf
|
||||||
COPY ash.css /usr/share/nginx/html/ash.css
|
COPY ash.css /usr/share/nginx/html/ash.css
|
||||||
COPY gruvbox.css /usr/share/nginx/html/gruvbox.css
|
COPY gruvbox.css /usr/share/nginx/html/gruvbox.css
|
||||||
|
|||||||
+114
-21
@@ -191,21 +191,27 @@
|
|||||||
bottom: 2vh;
|
bottom: 2vh;
|
||||||
color: var(--bone);
|
color: var(--bone);
|
||||||
background: rgba(28, 27, 31, 0.7);
|
background: rgba(28, 27, 31, 0.7);
|
||||||
padding: 1vh 1vw;
|
padding: 1vh 1em;
|
||||||
border-radius: 0.6vh;
|
border-radius: 0.6vh;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
font-size: clamp(10px, 1.2vh, 14px);
|
/* Everything below is derived from vh (font-size) or em (which
|
||||||
|
follows font-size), so the whole panel is one scale factor:
|
||||||
|
grow the viewport and it grows, shrink it and it shrinks.
|
||||||
|
No px floors - those were what made the content overflow the
|
||||||
|
box in both directions on short screens. */
|
||||||
|
font-size: 1.5vh;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
width: clamp(260px, min(33vh, 28vw), 40vw);
|
width: min(32vh, 44vw);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: portrait) and (max-width: 700px) {
|
@media (orientation: portrait) and (max-width: 700px) {
|
||||||
#info-panel {
|
#info-panel {
|
||||||
width: 90vw;
|
width: min(88vw, 46vh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#analog-clock {
|
#analog-clock {
|
||||||
@@ -218,7 +224,7 @@
|
|||||||
#digital-clock {
|
#digital-clock {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 0.12em;
|
||||||
margin-bottom: 0.6vh;
|
margin-bottom: 0.6vh;
|
||||||
padding-bottom: 0.5vh;
|
padding-bottom: 0.5vh;
|
||||||
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
|
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
|
||||||
@@ -227,7 +233,7 @@
|
|||||||
.info-row {
|
.info-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.6vw;
|
gap: 0.5em;
|
||||||
padding: 0.05vh 0;
|
padding: 0.05vh 0;
|
||||||
}
|
}
|
||||||
.info-icon {
|
.info-icon {
|
||||||
@@ -246,7 +252,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.6vw;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
.info-label {
|
.info-label {
|
||||||
color: var(--icon);
|
color: var(--icon);
|
||||||
@@ -258,7 +264,7 @@
|
|||||||
.info-sub {
|
.info-sub {
|
||||||
color: var(--stone);
|
color: var(--stone);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
margin-left: 4px;
|
margin-left: 0.3em;
|
||||||
}
|
}
|
||||||
.weather-divider {
|
.weather-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
@@ -268,7 +274,7 @@
|
|||||||
.weather-current {
|
.weather-current {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.8vw;
|
gap: 0.7em;
|
||||||
padding: 0.8vh 0 1vh 0;
|
padding: 0.8vh 0 1vh 0;
|
||||||
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
|
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
|
||||||
margin-bottom: 1vh;
|
margin-bottom: 1vh;
|
||||||
@@ -285,7 +291,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 1vw;
|
gap: 0.8em;
|
||||||
}
|
}
|
||||||
.weather-location {
|
.weather-location {
|
||||||
font-size: 0.45em;
|
font-size: 0.45em;
|
||||||
@@ -329,7 +335,7 @@
|
|||||||
.weather-day-slots {
|
.weather-day-slots {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.3vw;
|
gap: 0.3em;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin-top: 0.3vh;
|
margin-top: 0.3vh;
|
||||||
}
|
}
|
||||||
@@ -583,8 +589,34 @@ var _maxFps = (window.CONFIG && typeof window.CONFIG.maxFps === 'number')
|
|||||||
? window.CONFIG.maxFps : 30;
|
? window.CONFIG.maxFps : 30;
|
||||||
var _frameGap = _maxFps > 0 ? 1000 / _maxFps : 0;
|
var _frameGap = _maxFps > 0 ? 1000 / _maxFps : 0;
|
||||||
var _lastDraw = 0;
|
var _lastDraw = 0;
|
||||||
|
|
||||||
|
// ---- Info panel auto-fit -------------------------------------------------
|
||||||
|
// The panel box itself is viewport-sized (top/bottom 2vh) and every size
|
||||||
|
// inside it is em/vh based, so a single font size scales the whole content.
|
||||||
|
// Binary search that font size until the content fills the box exactly:
|
||||||
|
// no scrollbar at any viewport size, and the proportions never change.
|
||||||
|
function fitInfoPanel() {
|
||||||
|
var p = document.getElementById('info-panel');
|
||||||
|
if (!p) return;
|
||||||
|
var avail = p.clientHeight;
|
||||||
|
if (!avail) return;
|
||||||
|
var lo = 0.4, hi = 3.0;
|
||||||
|
for (var i = 0; i < 9; i++) {
|
||||||
|
var mid = (lo + hi) / 2;
|
||||||
|
p.style.fontSize = mid + 'vh';
|
||||||
|
if (p.scrollHeight <= avail + 1) lo = mid; else hi = mid;
|
||||||
|
}
|
||||||
|
p.style.fontSize = lo.toFixed(3) + 'vh';
|
||||||
|
}
|
||||||
|
var _fitTimer = null;
|
||||||
|
function scheduleFitInfoPanel() {
|
||||||
|
if (_fitTimer) clearTimeout(_fitTimer);
|
||||||
|
_fitTimer = setTimeout(fitInfoPanel, 120);
|
||||||
|
}
|
||||||
|
|
||||||
sceneRender();
|
sceneRender();
|
||||||
window.addEventListener('resize', sceneRender);
|
window.addEventListener('resize', sceneRender);
|
||||||
|
window.addEventListener('resize', scheduleFitInfoPanel);
|
||||||
(function sceneLoop() {
|
(function sceneLoop() {
|
||||||
window._sceneRaf = requestAnimationFrame(sceneLoop);
|
window._sceneRaf = requestAnimationFrame(sceneLoop);
|
||||||
var t = performance.now();
|
var t = performance.now();
|
||||||
@@ -1316,6 +1348,13 @@ window.sunPalettes = [
|
|||||||
|
|
||||||
function updateClockAndEvents() {
|
function updateClockAndEvents() {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
// Simulated wall clock: with CONFIG.day.simulation the scene runs
|
||||||
|
// a whole day in cycleSeconds, so the clock hands, the digital
|
||||||
|
// readout and every sun/moon table follow the accelerated date
|
||||||
|
// instead of the real one - the scene and the clock stay in step.
|
||||||
|
// simNow() is the same accelerated date the weather/calendar
|
||||||
|
// layer uses, so nothing drifts apart.
|
||||||
|
if (DAY_SIM) now = simNow();
|
||||||
|
|
||||||
// Calculate real day progress based on sun position
|
// Calculate real day progress based on sun position
|
||||||
// 0% = midnight, 50% = noon, 100% = next midnight
|
// 0% = midnight, 50% = noon, 100% = next midnight
|
||||||
@@ -1805,6 +1844,7 @@ window.weatherThemes = {
|
|||||||
|
|
||||||
function updateDebug(t, palette, sceneColors, themeName, realDayProgress) {
|
function updateDebug(t, palette, sceneColors, themeName, realDayProgress) {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
if (DAY_SIM) now = simNow();
|
||||||
var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds();
|
var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds();
|
||||||
document.getElementById('dbg-time').textContent =
|
document.getElementById('dbg-time').textContent =
|
||||||
(hh < 10 ? '0' : '') + hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss;
|
(hh < 10 ? '0' : '') + hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss;
|
||||||
@@ -1948,6 +1988,12 @@ window.weatherThemes = {
|
|||||||
// bearing: 6:00 (t=0.25) = east (pi/2), 12:00 = south (pi),
|
// bearing: 6:00 (t=0.25) = east (pi/2), 12:00 = south (pi),
|
||||||
// 18:00 = west (3pi/2) -> bearing = 2*PI*t
|
// 18:00 = west (3pi/2) -> bearing = 2*PI*t
|
||||||
var _sunBearing = t * Math.PI * 2;
|
var _sunBearing = t * Math.PI * 2;
|
||||||
|
// World frame (verified from the star frame and the terrain
|
||||||
|
// mesh): +X points WEST, +Y up, +Z north. The star frame maps
|
||||||
|
// RA=0 to +X and rotates it to due west after 6 h of sidereal
|
||||||
|
// time, and the mesh samples the mosaic with u = 0.5 - x/size,
|
||||||
|
// so east is -X. The east component therefore has to land on
|
||||||
|
// -cos(alt)*sin(bearing).
|
||||||
window.__currentSunDir = [
|
window.__currentSunDir = [
|
||||||
-Math.cos(_alt) * Math.sin(_sunBearing),
|
-Math.cos(_alt) * Math.sin(_sunBearing),
|
||||||
Math.sin(_alt),
|
Math.sin(_alt),
|
||||||
@@ -1957,9 +2003,10 @@ window.weatherThemes = {
|
|||||||
var _sunPos = SunCalc.getPosition(new Date(), SITE_LAT, SITE_LON);
|
var _sunPos = SunCalc.getPosition(new Date(), SITE_LAT, SITE_LON);
|
||||||
window.__currentSunAlt = _sunPos.altitude;
|
window.__currentSunAlt = _sunPos.altitude;
|
||||||
window.__flightDow = new Date().getDay();
|
window.__flightDow = new Date().getDay();
|
||||||
// Unit vector towards the sun in world space (X east, Z north,
|
// Unit vector towards the sun in world space. SunCalc azimuth
|
||||||
// Y up). SunCalc azimuth is measured from south, clockwise ->
|
// is measured from the south, positive towards the west, so
|
||||||
// bearing from north = azimuth + PI.
|
// the compass bearing (0 = north, clockwise) is azimuth + PI.
|
||||||
|
// World frame: +X = west, +Z = north, +Y = up -> east is -X.
|
||||||
var _sunBearingR = _sunPos.azimuth + Math.PI;
|
var _sunBearingR = _sunPos.azimuth + Math.PI;
|
||||||
window.__currentSunDir = [
|
window.__currentSunDir = [
|
||||||
-Math.cos(_sunPos.altitude) * Math.sin(_sunBearingR),
|
-Math.cos(_sunPos.altitude) * Math.sin(_sunBearingR),
|
||||||
@@ -1967,6 +2014,26 @@ window.weatherThemes = {
|
|||||||
Math.cos(_sunPos.altitude) * Math.cos(_sunBearingR)
|
Math.cos(_sunPos.altitude) * Math.cos(_sunBearingR)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
// ---- Local sidereal time -------------------------------------
|
||||||
|
// Same formula SunCalc uses internally (Meeus): GMST in degrees
|
||||||
|
// plus the observer's east longitude. This is what the star
|
||||||
|
// frame has to follow - a solar day fraction is 3 min 56 s per
|
||||||
|
// day off and has no absolute phase at all.
|
||||||
|
var _sidD = (Date.now() - Date.UTC(2000, 0, 1, 12, 0, 0)) / 86400000;
|
||||||
|
var _sidReal = (280.16 + 360.9856235 * _sidD + SITE_LON) * Math.PI / 180;
|
||||||
|
if (DAY_SIM) {
|
||||||
|
if (typeof window.__sidStart !== 'number') {
|
||||||
|
window.__sidStart = _sidReal;
|
||||||
|
window.__sidStartMs = Date.now();
|
||||||
|
}
|
||||||
|
// simulated days elapsed * sidereal rate (1.0027379 solar days
|
||||||
|
// per sidereal day)
|
||||||
|
var _sidDays = (Date.now() - window.__sidStartMs) / CYCLE;
|
||||||
|
window.__currentSiderealRad = window.__sidStart + _sidDays * 2 * Math.PI * 1.0027379;
|
||||||
|
} else {
|
||||||
|
window.__currentSiderealRad = _sidReal;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Moon direction ------------------------------------------
|
// ---- Moon direction ------------------------------------------
|
||||||
// Start elongation = real lunar phase (SunCalc: 0 new .. 0.5
|
// Start elongation = real lunar phase (SunCalc: 0 new .. 0.5
|
||||||
// full); in day simulation the elongation advances 360/29.53
|
// full); in day simulation the elongation advances 360/29.53
|
||||||
@@ -1985,11 +2052,30 @@ window.weatherThemes = {
|
|||||||
var _cx = _kp[1]*_sd[2] - _kp[2]*_sd[1];
|
var _cx = _kp[1]*_sd[2] - _kp[2]*_sd[1];
|
||||||
var _cy = _kp[2]*_sd[0] - _kp[0]*_sd[2];
|
var _cy = _kp[2]*_sd[0] - _kp[0]*_sd[2];
|
||||||
var _cz = _kp[0]*_sd[1] - _kp[1]*_sd[0];
|
var _cz = _kp[0]*_sd[1] - _kp[1]*_sd[0];
|
||||||
window.__currentMoonDir = [
|
if (DAY_SIM) {
|
||||||
_sd[0]*_ecs + _cx*_esn + _kp[0]*_kdv*(1-_ecs),
|
window.__currentMoonDir = [
|
||||||
_sd[1]*_ecs + _cy*_esn + _kp[1]*_kdv*(1-_ecs),
|
_sd[0]*_ecs + _cx*_esn + _kp[0]*_kdv*(1-_ecs),
|
||||||
_sd[2]*_ecs + _cz*_esn + _kp[2]*_kdv*(1-_ecs)
|
_sd[1]*_ecs + _cy*_esn + _kp[1]*_kdv*(1-_ecs),
|
||||||
];
|
_sd[2]*_ecs + _cz*_esn + _kp[2]*_kdv*(1-_ecs)
|
||||||
|
];
|
||||||
|
// illuminated fraction straight from the elongation used above
|
||||||
|
// (0 = new, 1 = full) so other layers can light themselves by it
|
||||||
|
window.__currentMoonFrac = (1 - Math.cos(_elong)) * 0.5;
|
||||||
|
} else {
|
||||||
|
// Real mode: the geometric construction above keeps the moon on
|
||||||
|
// the SUN's declination circle. The real moon leaves it by up to
|
||||||
|
// 28.5 deg (obliquity 23.4 + lunar inclination 5.1), which puts
|
||||||
|
// it in the wrong part of the sky. SunCalc solves the actual
|
||||||
|
// lunar ephemeris, so use its altitude/azimuth directly.
|
||||||
|
var _moonPos = SunCalc.getMoonPosition(new Date(), SITE_LAT, SITE_LON);
|
||||||
|
var _moonBearingR = _moonPos.azimuth + Math.PI;
|
||||||
|
window.__currentMoonDir = [
|
||||||
|
-Math.cos(_moonPos.altitude) * Math.sin(_moonBearingR),
|
||||||
|
Math.sin(_moonPos.altitude),
|
||||||
|
Math.cos(_moonPos.altitude) * Math.cos(_moonBearingR)
|
||||||
|
];
|
||||||
|
window.__currentMoonFrac = SunCalc.getMoonIllumination(new Date()).fraction;
|
||||||
|
}
|
||||||
var __skyHex = '#' + ('00000' + c.sky.toString(16)).slice(-6);
|
var __skyHex = '#' + ('00000' + c.sky.toString(16)).slice(-6);
|
||||||
if (__skyHex !== window.__lastSkyHex) {
|
if (__skyHex !== window.__lastSkyHex) {
|
||||||
window.__lastSkyHex = __skyHex;
|
window.__lastSkyHex = __skyHex;
|
||||||
@@ -2104,6 +2190,7 @@ window.weatherThemes = {
|
|||||||
if (window.BackgroundCache) {
|
if (window.BackgroundCache) {
|
||||||
window.BackgroundCache.put('meta/geolabel', { value: name });
|
window.BackgroundCache.put('meta/geolabel', { value: name });
|
||||||
}
|
}
|
||||||
|
scheduleFitInfoPanel();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function() { /* ignore errors */ });
|
.catch(function() { /* ignore errors */ });
|
||||||
@@ -2214,7 +2301,10 @@ window.weatherThemes = {
|
|||||||
window.BackgroundCache.put('meta/weather', { json: data });
|
window.BackgroundCache.put('meta/weather', { json: data });
|
||||||
}
|
}
|
||||||
currentWeatherCode = data.current.weather_code;
|
currentWeatherCode = data.current.weather_code;
|
||||||
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
// while the weather sim runs it owns the theme - a live
|
||||||
|
// poll must not override the configured test weather
|
||||||
|
if (!__weatherSimActive)
|
||||||
|
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
||||||
updateWeatherDisplay();
|
updateWeatherDisplay();
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
@@ -2227,7 +2317,8 @@ window.weatherThemes = {
|
|||||||
weatherData = rec.json;
|
weatherData = rec.json;
|
||||||
window.__weatherStale = true;
|
window.__weatherStale = true;
|
||||||
currentWeatherCode = rec.json.current.weather_code;
|
currentWeatherCode = rec.json.current.weather_code;
|
||||||
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
if (!__weatherSimActive)
|
||||||
|
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
||||||
updateWeatherDisplay();
|
updateWeatherDisplay();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2356,11 +2447,13 @@ window.weatherThemes = {
|
|||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
document.getElementById('weather-forecast').innerHTML = forecastHtml;
|
document.getElementById('weather-forecast').innerHTML = forecastHtml;
|
||||||
|
fitInfoPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update weather every 15 minutes
|
// Update weather every 15 minutes
|
||||||
applyI18n();
|
applyI18n();
|
||||||
populateDebugSelects();
|
populateDebugSelects();
|
||||||
|
fitInfoPanel();
|
||||||
// Apply debug flag from CONFIG
|
// Apply debug flag from CONFIG
|
||||||
var debugEl = document.getElementById('debug-panel');
|
var debugEl = document.getElementById('debug-panel');
|
||||||
if (debugEl && !(window.CONFIG && window.CONFIG.debug)) {
|
if (debugEl && !(window.CONFIG && window.CONFIG.debug)) {
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ window.CONFIG = {
|
|||||||
// Internal 3D resolution scale (0.3..1, default 1). Lower values
|
// Internal 3D resolution scale (0.3..1, default 1). Lower values
|
||||||
// render the clouds/map shader at reduced resolution and upscale
|
// render the clouds/map shader at reduced resolution and upscale
|
||||||
// via CSS - use it if fps is low (GPU-bound) despite a quiet CPU.
|
// via CSS - use it if fps is low (GPU-bound) despite a quiet CPU.
|
||||||
renderScale: 1,
|
// 1 = native. 0.5 = a quarter of the pixels (half width, half height),
|
||||||
|
// upscaled 2x as hard blocks; the DOM UI on top stays crisp.
|
||||||
|
renderScale: 0.5,
|
||||||
// WebGL scene frame cap (default 30; 0 = free-running/vsync). All
|
// WebGL scene frame cap (default 30; 0 = free-running/vsync). All
|
||||||
// scene motion is time-based, so the cap only changes smoothness.
|
// scene motion is time-based, so the cap only changes smoothness.
|
||||||
maxFps: 30,
|
maxFps: 30,
|
||||||
@@ -95,7 +97,10 @@ window.CONFIG = {
|
|||||||
lat: 48.5667,
|
lat: 48.5667,
|
||||||
lon: 13.4319
|
lon: 13.4319
|
||||||
},
|
},
|
||||||
weatherSim: true,
|
// weatherSim: true = Szenario-Wetter statt Open-Meteo (A/B-Durchlauf
|
||||||
|
// aller Wetterstufen alle weatherSimInterval ms). false = echtes
|
||||||
|
// Wetter von Open-Meteo.
|
||||||
|
weatherSim: false,
|
||||||
weatherSimInterval: 30000,
|
weatherSimInterval: 30000,
|
||||||
// All downloads (basemap tiles, elevation grid, weather JSON, geo
|
// All downloads (basemap tiles, elevation grid, weather JSON, geo
|
||||||
// label) stay in IndexedDB permanently and are never re-fetched once
|
// label) stay in IndexedDB permanently and are never re-fetched once
|
||||||
@@ -104,13 +109,13 @@ window.CONFIG = {
|
|||||||
expireDays: null,
|
expireDays: null,
|
||||||
// true = draw tile borders + N/S + x/y coords into the ground
|
// true = draw tile borders + N/S + x/y coords into the ground
|
||||||
// texture (debug orientation). Off = clean map.
|
// texture (debug orientation). Off = clean map.
|
||||||
debug: false
|
debug: true
|
||||||
},
|
},
|
||||||
// Ground imagery: 'satellite' = ESRI World_Imagery (aerial, no
|
// Ground imagery: 'satellite' = ESRI World_Imagery (aerial, no
|
||||||
// labels), 'streets' = ESRI World_Street_Map (with place names).
|
// labels), 'streets' = ESRI World_Street_Map (with place names).
|
||||||
// Both cached permanently (separate cache prefix per style).
|
// Both cached permanently (separate cache prefix per style).
|
||||||
ground: {
|
ground: {
|
||||||
style: 'satellite'
|
style: 'streets'
|
||||||
},
|
},
|
||||||
// Figure-eight course around location (lat/lon); the crossing point
|
// Figure-eight course around location (lat/lon); the crossing point
|
||||||
// sits exactly on the coordinate. null/removed = straight flight.
|
// sits exactly on the coordinate. null/removed = straight flight.
|
||||||
@@ -145,7 +150,7 @@ window.CONFIG = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
altitudeKm: 14, // cruise altitude
|
altitudeKm: 14, // cruise altitude
|
||||||
climbKm: 100, // climb/descent run to/from cruise altitude
|
climbKm: 100, // climb/descent run to/from cruise altitude
|
||||||
count: 84, // max simultaneous aircraft
|
count: 21, // max simultaneous aircraft
|
||||||
speedKmh: 850, // mean ground speed
|
speedKmh: 850, // mean ground speed
|
||||||
speedVar: 0.18, // +/- deviation (0..0.5)
|
speedVar: 0.18, // +/- deviation (0..0.5)
|
||||||
minLegKm: 60, // shortest leg (no taxi hops)
|
minLegKm: 60, // shortest leg (no taxi hops)
|
||||||
@@ -154,20 +159,28 @@ window.CONFIG = {
|
|||||||
wHub: 4, // destination weights: hub / airfield / strip
|
wHub: 4, // destination weights: hub / airfield / strip
|
||||||
wField: 2,
|
wField: 2,
|
||||||
wStrip: 1,
|
wStrip: 1,
|
||||||
contrailChance: 0.6, // share of aircraft with a persistent trail
|
contrailChance: 0.85, // share of aircraft with a persistent trail
|
||||||
contrailStartKm: 6, // trail formation altitude
|
contrailStartKm: 6, // trail formation altitude
|
||||||
contrailLifeS: 300, // trail visibility duration
|
contrailLifeS: 300, // trail visibility duration
|
||||||
contrailWidthM: 310 // full trail width
|
contrailWidthM: 620, // full trail width
|
||||||
|
// trails fade out completely by this distance (start is 35% of it)
|
||||||
|
contrailFadeKm: 100,
|
||||||
|
// Night strobes. Size shrinks with distance but never below
|
||||||
|
// lightPx, and never grows past lightMaxPx for a jet right above.
|
||||||
|
// The white strobe is a double flash: two equal flashes
|
||||||
|
// blinkGapS apart, then blinkPeriodS - blinkGapS - blinkFlashS
|
||||||
|
// of darkness (1.4 - 0.16 - 0.08 = 1.16 s pause).
|
||||||
|
lightPx: 7,
|
||||||
|
lightMaxPx: 16,
|
||||||
|
lightBoost: 9,
|
||||||
|
blinkPeriodS: 1.4,
|
||||||
|
blinkGapS: 0.16,
|
||||||
|
blinkFlashS: 0.08
|
||||||
},
|
},
|
||||||
// 3D ground from real elevation data (Copernicus DEM, terrarium
|
// 3D ground from real elevation data (Copernicus DEM, terrarium
|
||||||
// tiles). enabled: false = flat plane. exaggeration: vertical
|
// tiles). enabled: false = flat plane. exaggeration: vertical
|
||||||
// elevation scale (1 = real, nearly invisible; default 8).
|
// elevation scale (1 = real, nearly invisible; default 8).
|
||||||
// wireframe: true = overlay mesh edges as orange lines (debug).
|
// wireframe: true = overlay mesh edges as orange lines (debug).
|
||||||
// nightLights: NASA VIIRS city-lights layer, cross-faded into the
|
|
||||||
// day map over ~1 h after sunset (dark ground, glowing cities).
|
|
||||||
// One-time fetch (~49 tiles), cached permanently. false = no light
|
|
||||||
// overlay; at night the normal texture stays much brighter (only
|
|
||||||
// gently dimmed).
|
|
||||||
// carLights: moving lights on the real road network (OSM/Overpass
|
// carLights: moving lights on the real road network (OSM/Overpass
|
||||||
// curves, additive over the terrain at night). JSON once, cached.
|
// curves, additive over the terrain at night). JSON once, cached.
|
||||||
// cityLights: settlement lights - OSM place nodes (city/town/
|
// cityLights: settlement lights - OSM place nodes (city/town/
|
||||||
@@ -176,13 +189,127 @@ window.CONFIG = {
|
|||||||
// brighter dots. One Overpass query, cached permanently.
|
// brighter dots. One Overpass query, cached permanently.
|
||||||
// Data (c) OpenStreetMap contributors, ODbL.
|
// Data (c) OpenStreetMap contributors, ODbL.
|
||||||
terrain: {
|
terrain: {
|
||||||
|
// soft rim at the mosaic border, in tiles (1 tile ~ 13 km)
|
||||||
|
edgeFadeTiles: 4,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
exaggeration: 8,
|
exaggeration: 8,
|
||||||
wireframe: false,
|
wireframe: false,
|
||||||
nightLights: false,
|
// How far the day map is dimmed at full night: 0 = pitch black,
|
||||||
|
// 1 = unchanged. Only affects night; the day map is untouched.
|
||||||
|
// (The NASA VIIRS city-lights layer was removed; city lights come
|
||||||
|
// from OSM place nodes via cityLights.)
|
||||||
|
nightDim: 0.22,
|
||||||
carLights: true,
|
carLights: true,
|
||||||
cityLights: true
|
cityLights: true
|
||||||
},
|
},
|
||||||
|
// Car lights on the real road network (on/off stays in terrain.carLights).
|
||||||
|
// Two layers now: a thin road line (bandPx / bandBrightness) and one
|
||||||
|
// round point sprite per vehicle (minPx..maxPx), same idea as the city
|
||||||
|
// dots' gl_PointSize floor. laneM is the real distance between the two
|
||||||
|
// traffic directions, so they split close to the camera and merge into
|
||||||
|
// one line far away instead of drifting apart with the camera distance.
|
||||||
|
// dashM: mean vehicle spacing (secondary roads use dashM * 2.8).
|
||||||
|
// speed: multiplier on the real driving speed (motorway ~120 km/h,
|
||||||
|
// secondary ~70 km/h at dashM spacing).
|
||||||
|
carLights: {
|
||||||
|
roadWidthM: 24, // real carriageway width of the road line
|
||||||
|
bandPx: 1.2, // pixel floor for the road line
|
||||||
|
bandBrightness: 0.10, // road line glow (points are the main layer)
|
||||||
|
minPx: 3.6, // guaranteed vehicle light diameter
|
||||||
|
maxPx: 10, // ceiling for a light right above the camera
|
||||||
|
boost: 5, // extra light size close to the camera
|
||||||
|
laneM: 8, // real distance between the two directions
|
||||||
|
lanePx: 2.6, // screen floor for that distance, so the two
|
||||||
|
// directions never collapse into one stream
|
||||||
|
// Traffic lives on a disc around the configured location: roads
|
||||||
|
// outside rangeKm carry no vehicles. fadeKm / fadeSpanKm are the
|
||||||
|
// distance fade belonging to that disc (start, and how far past it
|
||||||
|
// until nothing is left) - keep them at or beyond rangeKm so the
|
||||||
|
// region never ends in a hard cut.
|
||||||
|
rangeKm: 200,
|
||||||
|
fadeKm: 150,
|
||||||
|
fadeSpanKm: 60,
|
||||||
|
// 'auto' = side of the road taken from location (lat/lon): left in
|
||||||
|
// the UK, Japan, Australia, India, Southern Africa, ... and right
|
||||||
|
// everywhere else. Set 'left' or 'right' to force it.
|
||||||
|
trafficSide: 'auto',
|
||||||
|
dashM: 620, // mean vehicle spacing on motorways
|
||||||
|
// Traffic spread around the motorway network (gaussian): peak on the
|
||||||
|
// motorway itself, thinning with distance to the nearest one.
|
||||||
|
// spreadKm = sigma, spreadFloor = share that far side roads keep.
|
||||||
|
spreadKm: 45,
|
||||||
|
spreadFloor: 0.30,
|
||||||
|
speed: 1.0, // flow speed multiplier
|
||||||
|
density: 1.0, // vehicle density multiplier
|
||||||
|
brightness: 1.3 // vehicle light intensity
|
||||||
|
},
|
||||||
|
// Headlights and house lights do not ignite at once: every single
|
||||||
|
// light gets its own sun-altitude threshold between offDeg (nothing
|
||||||
|
// lit) and onDeg (everything lit), drawn per vehicle / per city block.
|
||||||
|
// offDeg +5 deg is roughly 30 min before sunset here, onDeg -6 deg is
|
||||||
|
// civil twilight. In the morning the same thresholds switch the lights
|
||||||
|
// off again as the sun climbs.
|
||||||
|
lights: {
|
||||||
|
offDeg: 5,
|
||||||
|
onDeg: -6,
|
||||||
|
softDeg: 1.2
|
||||||
|
},
|
||||||
|
// Physical lighting: direct beam from a Kasten-Young air mass with
|
||||||
|
// Rayleigh + ozone + aerosol extinction, diffuse skylight and moonlight
|
||||||
|
// from the same model. terrainLighting: 'palette' switches back to the
|
||||||
|
// old hand-painted hillshade. turbidity scales the aerosol load,
|
||||||
|
// moonGain the moonlight strength, nightFloor the darkest skylight
|
||||||
|
// (airglow + starlight), albedoTint is the theme's share on surfaces.
|
||||||
|
atmosphere: {
|
||||||
|
terrainLighting: 'physical',
|
||||||
|
turbidity: 1.0,
|
||||||
|
// moonlight strength; scales the direct term AND the diffuse sky
|
||||||
|
// lift, both with the illuminated fraction (new moon -> none)
|
||||||
|
moonGain: 0.60,
|
||||||
|
// stylised starlight + airglow floor on a dark night (real
|
||||||
|
// integrated starlight is ~1e-6 of daylight and would be invisible)
|
||||||
|
starGain: 0.18,
|
||||||
|
// extra share on the ground. The product is the starlit floor and
|
||||||
|
// must stay below the skylight at sunset, otherwise the night would
|
||||||
|
// be brighter than the moment of sunset. Moonlight (moonGain) is
|
||||||
|
// what lifts a night above that floor.
|
||||||
|
starGainGround: 0.4,
|
||||||
|
nightFloor: 0.10,
|
||||||
|
// cloud albedo in physical mode: theme hue kept, brightness comes
|
||||||
|
// from the light model (the night palette alone would double-darken)
|
||||||
|
cloudBrightness: 1.0,
|
||||||
|
// moving cloud patches darken the ground beneath them (the terrain
|
||||||
|
// samples the same cloud field as the sky pass)
|
||||||
|
cloudShadows: true,
|
||||||
|
cloudShadowDensity: 1.8, // optical depth through the deck
|
||||||
|
cloudShadowFloor: 0.06, // darkest shade under a thick cloud
|
||||||
|
cloudShadowAmbient: 0.65, // share of the skylight a cloud also blocks
|
||||||
|
// ridges occlude the sun for the ground behind them (march over the
|
||||||
|
// same height field the mesh was built from)
|
||||||
|
terrainShadows: true,
|
||||||
|
// brightness a fully shadowed slope keeps (0 = pitch black, 1 = off)
|
||||||
|
terrainShadowFloor: 0.42,
|
||||||
|
// how much of the ground haze applies to the cloud deck (clouds sit
|
||||||
|
// above most of the air that causes it)
|
||||||
|
cloudHaze: 0.22,
|
||||||
|
albedoGain: 1.0,
|
||||||
|
// share of the daylight column that the distance fog / horizon takes,
|
||||||
|
// and the diffuse skylight share on surfaces
|
||||||
|
skyGain: 0.45,
|
||||||
|
// Aerial perspective in physical mode: the terrain is attenuated by
|
||||||
|
// exp(-tau) and the scattered light of the same air column is added.
|
||||||
|
// airGain scales the Rayleigh airlight (blue-biased), hazeResidual
|
||||||
|
// is how much of the surface survives at the full fog distance
|
||||||
|
// (0.3 = distant ground keeps 30 % of its own colour).
|
||||||
|
airGain: 0.55,
|
||||||
|
hazeResidual: 0.30
|
||||||
|
},
|
||||||
|
// Star colour: every catalogue star gets its real blackbody tint, mixed
|
||||||
|
// towards white by this factor. 0 = pure white points, 1 = full colour.
|
||||||
|
stars: {
|
||||||
|
tint: 0.10, // 90 % white + 10 % of the star's real colour
|
||||||
|
brightness: 1.6 // overall glow of the catalogue points
|
||||||
|
},
|
||||||
// Day simulation: true = a full 24 h cycle runs in cycleSeconds
|
// Day simulation: true = a full 24 h cycle runs in cycleSeconds
|
||||||
// (sun rises east, culminates south, sets west).
|
// (sun rises east, culminates south, sets west).
|
||||||
// false = real sun/moon positions via SunCalc.
|
// false = real sun/moon positions via SunCalc.
|
||||||
@@ -190,16 +317,23 @@ window.CONFIG = {
|
|||||||
// that time of day (moving objects, clouds, cars and the weather
|
// that time of day (moving objects, clouds, cars and the weather
|
||||||
// sim keep running unaffected); null/omitted = the clock advances.
|
// sim keep running unaffected); null/omitted = the clock advances.
|
||||||
day: {
|
day: {
|
||||||
|
// simulation: true runs a full 24 h cycle in cycleSeconds.
|
||||||
|
// 60 s = one simulated day per minute of wall time (one simulated
|
||||||
|
// hour every 2.5 s). The analog clock and the sun/moon tables follow
|
||||||
|
// the simulated date.
|
||||||
simulation: true,
|
simulation: true,
|
||||||
cycleSeconds: 20,
|
cycleSeconds: 60,
|
||||||
fixedTime: '20:30'
|
fixedTime: null
|
||||||
},
|
},
|
||||||
// Cloud deck in world units (1 unit ~ 12.9 km at zoom 11; flight
|
// Cloud deck in world units (1 unit ~ 12.9 km at zoom 11; flight
|
||||||
// band 15-35 km = y ~ 1.16-2.70). Keep the top below the lowest
|
// band 15-35 km = y ~ 1.16-2.70). Keep the top below the lowest
|
||||||
// flight altitude.
|
// flight altitude.
|
||||||
clouds: {
|
clouds: {
|
||||||
top: 0.9,
|
top: 0.9,
|
||||||
bottom: -0.3
|
bottom: -0.3,
|
||||||
|
// per weather level (0 = clear, 1 = overcast); overrides the built-in
|
||||||
|
// sunny 0.20 / cloudy 0.70 / stormy 0.80 / night 0.45
|
||||||
|
density: { stormy: 0.80 }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,17 @@ server {
|
|||||||
index background.html;
|
index background.html;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
add_header Cache-Control "no-store, must-revalidate" always;
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Page, config and scripts must never come out of a stale browser
|
||||||
|
# cache: without this a changed CONFIG (new location, layer toggles)
|
||||||
|
# keeps rendering the previous setup until a hard reload.
|
||||||
|
location ~* \.(js|html)$ {
|
||||||
|
add_header Cache-Control "no-store, must-revalidate" always;
|
||||||
|
}
|
||||||
|
|
||||||
# The background page runs as an iframe on other origins; a stray
|
# The background page runs as an iframe on other origins; a stray
|
||||||
# favicon request must not log 404s.
|
# favicon request must not log 404s.
|
||||||
location = /favicon.ico {
|
location = /favicon.ico {
|
||||||
@@ -55,6 +63,83 @@ server {
|
|||||||
add_header Access-Control-Allow-Origin "*" always;
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# CORS proxy for Overpass (road, place and airport queries). The public
|
||||||
|
# mirrors answer cross-origin POSTs without Access-Control-Allow-Origin
|
||||||
|
# (or refuse outright), so the app talks to same-origin paths here and
|
||||||
|
# nginx forwards with a browser-like request. Overpass timeouts are long:
|
||||||
|
# a busy instance needs more than the default 60 s.
|
||||||
|
# URL format: /overpass/<mirror>/interpreter (mirror: api | coffee | mail)
|
||||||
|
location ^~ /overpass/api/ {
|
||||||
|
# Host in a variable so the resolver directive applies: with a literal
|
||||||
|
# host nginx resolves at config time and tried AAAA records, which the
|
||||||
|
# container cannot reach (101 Network unreachable) and which then
|
||||||
|
# disabled the upstream for a while.
|
||||||
|
resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s;
|
||||||
|
set $overpass_api_host "overpass-api.de";
|
||||||
|
proxy_pass https://$overpass_api_host/api/;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_set_header Host overpass-api.de;
|
||||||
|
proxy_set_header Origin "https://overpass-api.de";
|
||||||
|
proxy_set_header Referer "https://overpass-api.de/api/interpreter";
|
||||||
|
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||||
|
proxy_connect_timeout 15s; proxy_send_timeout 120s; proxy_read_timeout 120s;
|
||||||
|
# A cell that answered once is kept for 30 days and served even while
|
||||||
|
# the mirror is down (stale), so a flaky instance cannot undo progress
|
||||||
|
# the browser already paid for.
|
||||||
|
proxy_cache overpass;
|
||||||
|
proxy_cache_key "$scheme$host$request_uri$request_body";
|
||||||
|
proxy_cache_methods POST;
|
||||||
|
proxy_cache_valid 200 30d;
|
||||||
|
proxy_cache_lock on;
|
||||||
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||||
|
add_header X-Cache $upstream_cache_status always;
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
}
|
||||||
|
location ^~ /overpass/coffee/ {
|
||||||
|
resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s;
|
||||||
|
set $overpass_coffee_host "overpass.private.coffee";
|
||||||
|
proxy_pass https://$overpass_coffee_host/api/;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_set_header Host overpass.private.coffee;
|
||||||
|
proxy_set_header Origin "https://overpass.private.coffee";
|
||||||
|
proxy_set_header Referer "https://overpass.private.coffee/api/interpreter";
|
||||||
|
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||||
|
proxy_connect_timeout 15s; proxy_send_timeout 120s; proxy_read_timeout 120s;
|
||||||
|
# A cell that answered once is kept for 30 days and served even while
|
||||||
|
# the mirror is down (stale), so a flaky instance cannot undo progress
|
||||||
|
# the browser already paid for.
|
||||||
|
proxy_cache overpass;
|
||||||
|
proxy_cache_key "$scheme$host$request_uri$request_body";
|
||||||
|
proxy_cache_methods POST;
|
||||||
|
proxy_cache_valid 200 30d;
|
||||||
|
proxy_cache_lock on;
|
||||||
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||||
|
add_header X-Cache $upstream_cache_status always;
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
}
|
||||||
|
location ^~ /overpass/mail/ {
|
||||||
|
resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s;
|
||||||
|
set $overpass_mail_host "maps.mail.ru";
|
||||||
|
proxy_pass https://$overpass_mail_host/osm/tools/overpass/api/;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_set_header Host maps.mail.ru;
|
||||||
|
proxy_set_header Origin "https://maps.mail.ru";
|
||||||
|
proxy_set_header Referer "https://maps.mail.ru/osm/tools/overpass/api/interpreter";
|
||||||
|
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||||
|
proxy_connect_timeout 15s; proxy_send_timeout 120s; proxy_read_timeout 120s;
|
||||||
|
# A cell that answered once is kept for 30 days and served even while
|
||||||
|
# the mirror is down (stale), so a flaky instance cannot undo progress
|
||||||
|
# the browser already paid for.
|
||||||
|
proxy_cache overpass;
|
||||||
|
proxy_cache_key "$scheme$host$request_uri$request_body";
|
||||||
|
proxy_cache_methods POST;
|
||||||
|
proxy_cache_valid 200 30d;
|
||||||
|
proxy_cache_lock on;
|
||||||
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||||
|
add_header X-Cache $upstream_cache_status always;
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
}
|
||||||
|
|
||||||
# CORS preflight handler
|
# CORS preflight handler
|
||||||
location ~* \.(png|jpg)$ {
|
location ~* \.(png|jpg)$ {
|
||||||
if ($request_method = 'OPTIONS') {
|
if ($request_method = 'OPTIONS') {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
proxy_cache_path /var/cache/nginx/overpass levels=1:2 keys_zone=overpass:20m max_size=2g inactive=30d use_temp_path=off;
|
||||||
Vendored
+2228
-758
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user