/** * earth-core.js – Kern-Framework für die Erd-Visualisierung * ============================================================= * Stellt Hilfsfunktionen, astronomische Berechnungen und ein * Event-System bereit, auf das alle Plugins lauschen. * * Gefeuerte Events (auf window): * earth:resize → { detail: { w, h } } * earth:slow → { detail: { sun, w, h, now } } (~1×/min) * earth:fast → { detail: { sun, w, h, now } } (~30 fps) * * Globales Objekt: window.EarthCore */ window.EarthCore = (function () { // ═══════════════════════════════════════════════════════════════ // KONFIGURATION // ═══════════════════════════════════════════════════════════════ const CFG = { // ── Bildpfade ────────────────────────────────────────────── IMG_DAY_DIR: "../images/planets/earth/day/", IMG_EXT: ".jpg", // ── Aktualisierungsintervalle ────────────────────────────── UPDATE_FAST_MS: 1000, // ~30 fps UPDATE_SLOW_MS: 60_000, // 1× pro Minute }; // ═══════════════════════════════════════════════════════════════ // HILFSFUNKTIONEN (öffentlich über EarthCore) // ═══════════════════════════════════════════════════════════════ const toRad = d => d * Math.PI / 180; const toDeg = r => r * 180 / Math.PI; const toX = (lon, w) => ((lon + 180) / 360) * w; const toY = (lat, h) => ((90 - lat) / 180) * h; /** Maximales 2:1-Rechteck im Earth-Container */ function calcSize() { const wrapper = document.getElementById("earth-wrapper"); const W = wrapper?.clientWidth || window.innerWidth; const H = wrapper?.clientHeight || window.innerHeight; return W / H >= 2 ? { w: H * 2, h: H } : { w: W, h: W / 2 }; } /** * Erstellt einen Plugin-Canvas und hängt ihn in #earth-wrapper. * @param {string} id – canvas.id * @param {number} zIndex – CSS z-index * @param {boolean} [noPointer] – pointerEvents: none (Standard: true) */ function createCanvas(id, zIndex, noPointer = true) { const viewport = document.getElementById("earth-viewport"); if (!viewport) throw new Error("earth-core: #earth-viewport nicht gefunden"); const c = document.createElement("canvas"); c.id = id; Object.assign(c.style, { position: "absolute", top: "0", left: "0", display: "block", zIndex: String(zIndex), pointerEvents: noPointer ? "none" : "auto", }); viewport.appendChild(c); return c; } /** * Synchronisiert Größe eines Canvas auf aktuelle Erdgröße. * Gibt true zurück wenn Größe gesetzt werden konnte. */ function syncCanvas(canvas, w, h) { if (!w || !h) return false; const rw = Math.round(w), rh = Math.round(h); canvas.style.width = rw + "px"; canvas.style.height = rh + "px"; canvas.width = rw; canvas.height = rh; return true; } /** * Zeichnet eine abgerundete Rechteck-Path (kein fill/stroke). * Universell für alle Plugins nutzbar. */ function roundRectPath(ctx, x, y, w, h, r) { const rr = Math.min(r, w / 2, h / 2); ctx.beginPath(); ctx.moveTo(x + rr, y); ctx.arcTo(x + w, y, x + w, y + h, rr); ctx.arcTo(x + w, y + h, x, y + h, rr); ctx.arcTo(x, y + h, x, y, rr); ctx.arcTo(x, y, x + w, y, rr); ctx.closePath(); } // ═══════════════════════════════════════════════════════════════ // ASTRONOMISCHE BERECHNUNGEN // ═══════════════════════════════════════════════════════════════ /** Subsolar-Punkt: wo steht die Sonne senkrecht? (Astronomy Engine) */ function subsolarPoint(date) { try { const vec = Astronomy.GeoVector(Astronomy.Body.Sun, date, true); const dist = Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z); const dec = toDeg(Math.asin(vec.z / dist)); const ra = toDeg(Math.atan2(vec.y, vec.x)); const gmst = Astronomy.SiderealTime(date) * 15; // Stunden → Grad const lon = ((ra - gmst + 180) % 360 + 360) % 360 - 180; return { lat: dec, lon: lon }; } catch (e) { return { lat: 0, lon: 0 }; } } // ═══════════════════════════════════════════════════════════════ // ZUSTAND & EVENT-LOOP // ═══════════════════════════════════════════════════════════════ let _sun = { lat: 0, lon: 0 }; let _size = { w: 0, h: 0 }; let _resizeRaf = 0; let _resizeObserver = null; function _buildDetail(now) { return { sun: _sun, w: _size.w, h: _size.h, now }; } function _fireSlow() { const now = new Date(); _sun = subsolarPoint(now); window.dispatchEvent(new CustomEvent("earth:slow", { detail: _buildDetail(now) })); } function _fireFast() { window.dispatchEvent(new CustomEvent("earth:fast", { detail: _buildDetail(new Date()) })); } function _onResize() { _size = calcSize(); const viewport = document.getElementById("earth-viewport"); if (viewport) { viewport.style.width = Math.round(_size.w) + "px"; viewport.style.height = Math.round(_size.h) + "px"; } window.dispatchEvent(new CustomEvent("earth:resize", { detail: { w: _size.w, h: _size.h } })); _fireSlow(); _fireFast(); } function _queueResize() { if (_resizeRaf) cancelAnimationFrame(_resizeRaf); _resizeRaf = requestAnimationFrame(() => { _resizeRaf = 0; _onResize(); }); } window.addEventListener("resize", _queueResize); // ── Start ────────────────────────────────────────────────────── function _start() { // Tagbild laden const img = document.getElementById("earthDay"); if (img) { const month = new Date().getMonth() + 1; img.src = `${CFG.IMG_DAY_DIR}${month}${CFG.IMG_EXT}`; } _size = calcSize(); const viewport = document.getElementById("earth-viewport"); if (viewport) { viewport.style.width = Math.round(_size.w) + "px"; viewport.style.height = Math.round(_size.h) + "px"; } const wrapper = document.getElementById("earth-wrapper"); if (wrapper && typeof ResizeObserver !== "undefined") { _resizeObserver = new ResizeObserver(() => _queueResize()); _resizeObserver.observe(wrapper); } _fireSlow(); _fireFast(); setInterval(_fireFast, CFG.UPDATE_FAST_MS); setInterval(_fireSlow, CFG.UPDATE_SLOW_MS); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", _start); } else { _start(); } // ═══════════════════════════════════════════════════════════════ // ÖFFENTLICHE API // ═══════════════════════════════════════════════════════════════ return { // Koordinaten-Helfer toRad, toDeg, toX, toY, // Canvas-Helfer createCanvas, syncCanvas, roundRectPath, // Astronomie subsolarPoint, // Größe calcSize, // Live-Getter get sun() { return _sun; }, get size() { return _size; }, }; }());