Geocron ist drin
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* 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; },
|
||||
};
|
||||
|
||||
}());
|
||||
Reference in New Issue
Block a user