Geocron ist drin
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* plugin-moon.js – Mond-Marker mit Popup (Alter & Phase)
|
||||
* =========================================================
|
||||
* Abhängigkeit: earth-core.js
|
||||
* Erstellt: #moonCanvas (z-index 6)
|
||||
*/
|
||||
|
||||
(function () {
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// KONFIGURATION
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
const CFG = {
|
||||
RADIUS_PX: 16,
|
||||
ICON: "../images/moons/moon4geocron.png",
|
||||
UPDATE_MS: 60_000,
|
||||
OBS_LAT: 50.3667, // Sternwarte Sonneberg
|
||||
OBS_LON: 11.1833,
|
||||
};
|
||||
|
||||
const { toX, toY, createCanvas, syncCanvas, roundRectPath, toDeg } = EarthCore;
|
||||
|
||||
const moonCanvas = createCanvas("moonCanvas", 6);
|
||||
const ctx = moonCanvas.getContext("2d");
|
||||
|
||||
const moonImg = new Image();
|
||||
moonImg.src = CFG.ICON;
|
||||
|
||||
let _moon = { lat: 0, lon: 0 };
|
||||
let _phase = 0; // 0–360°
|
||||
let _popup = false;
|
||||
let _rise = null; // Date | null
|
||||
let _set = null; // Date | null
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MONDBERECHNUNG (Astronomy Engine)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function moonPosition(date) {
|
||||
try {
|
||||
const vec = Astronomy.GeoMoon(date);
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// AUFGANG / UNTERGANG (Astronomy Engine)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
let _obs = null;
|
||||
|
||||
function findMoonRiseSet(date) {
|
||||
try {
|
||||
if (!_obs) _obs = new Astronomy.Observer(CFG.OBS_LAT, CFG.OBS_LON, 0);
|
||||
const d0 = new Date(date); d0.setUTCHours(0, 0, 0, 0);
|
||||
const r = Astronomy.SearchRiseSet(Astronomy.Body.Moon, _obs, +1, d0, 1);
|
||||
const s = Astronomy.SearchRiseSet(Astronomy.Body.Moon, _obs, -1, d0, 1);
|
||||
return { rise: r ? r.date : null, set: s ? s.date : null };
|
||||
} catch (e) {
|
||||
return { rise: null, set: null };
|
||||
}
|
||||
}
|
||||
|
||||
function fmtTime(d) {
|
||||
if (!d) return "–";
|
||||
return d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
|
||||
/** Mondalter in Tagen (0–29.53) */
|
||||
function moonAge(phaseDeg) {
|
||||
return phaseDeg / 360 * 29.530589;
|
||||
}
|
||||
|
||||
/** Deutsche Phasenbezeichnung */
|
||||
function phaseName(phaseDeg) {
|
||||
if (phaseDeg < 2 || phaseDeg >= 358) return "Neumond";
|
||||
if (phaseDeg < 88) return "Zunehmende Sichel";
|
||||
if (phaseDeg < 92) return "Erstes Viertel";
|
||||
if (phaseDeg < 178) return "Zunehmender Mond";
|
||||
if (phaseDeg < 182) return "Vollmond";
|
||||
if (phaseDeg < 268) return "Abnehmender Mond";
|
||||
if (phaseDeg < 272) return "Letztes Viertel";
|
||||
return "Abnehmende Sichel";
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// ZEICHNEN
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function drawMoon(cx, cy) {
|
||||
const r = CFG.RADIUS_PX;
|
||||
if (moonImg.complete && moonImg.naturalWidth > 0) {
|
||||
ctx.drawImage(moonImg, cx - r, cy - r, r * 2, r * 2);
|
||||
} else {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.fillStyle = "rgba(210, 210, 210, 0.95)";
|
||||
ctx.shadowColor = "rgba(200, 200, 200, 0.6)";
|
||||
ctx.shadowBlur = 8;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function drawPopup(cx, cy, w, h) {
|
||||
const age = moonAge(_phase);
|
||||
const illum = (1 - Math.cos(_phase * Math.PI / 180)) / 2 * 100;
|
||||
const lines = [
|
||||
phaseName(_phase),
|
||||
"Mondalter: " + age.toFixed(1) + " Tage",
|
||||
"Beleuchtung: " + illum.toFixed(0) + " %",
|
||||
"Aufgang: " + fmtTime(_rise),
|
||||
"Untergang: " + fmtTime(_set),
|
||||
];
|
||||
|
||||
ctx.save();
|
||||
ctx.font = "13px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif";
|
||||
ctx.textBaseline = "top";
|
||||
const lh = 18, padX = 10, padY = 8;
|
||||
const bw = Math.max(...lines.map(l => ctx.measureText(l).width)) + padX * 2;
|
||||
const bh = lines.length * lh + padY * 2;
|
||||
|
||||
let bx = cx + CFG.RADIUS_PX + 8;
|
||||
let by = cy - bh / 2;
|
||||
if (bx + bw > w - 5) bx = cx - bw - CFG.RADIUS_PX - 8;
|
||||
if (by < 5) by = 5;
|
||||
if (by + bh > h - 5) by = h - bh - 5;
|
||||
|
||||
roundRectPath(ctx, bx, by, bw, bh, 7);
|
||||
ctx.fillStyle = "rgba(10, 15, 30, 0.92)";
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "rgba(180, 200, 255, 0.6)";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
ctx.fillStyle = i === 0
|
||||
? "rgba(180, 210, 255, 0.95)"
|
||||
: "rgba(220, 230, 255, 0.9)";
|
||||
ctx.font = i === 0
|
||||
? "bold 13px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif"
|
||||
: "13px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif";
|
||||
ctx.fillText(line, bx + padX, by + padY + i * lh);
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// RENDER
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function render({ w, h }) {
|
||||
if (!w || !h) return;
|
||||
syncCanvas(moonCanvas, w, h);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
const mx = toX(_moon.lon, w);
|
||||
const my = toY(_moon.lat, h);
|
||||
const edge = CFG.RADIUS_PX + 4;
|
||||
|
||||
drawMoon(mx, my);
|
||||
if (mx - edge < 0) drawMoon(mx + w, my);
|
||||
if (mx + edge > w) drawMoon(mx - w, my);
|
||||
|
||||
if (_popup) drawPopup(mx, my, w, h);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// KLICK-ERKENNUNG (via window, Canvas hat pointerEvents:none)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function isHit(mx, my) {
|
||||
const { w, h } = EarthCore.size;
|
||||
const cx = toX(_moon.lon, w);
|
||||
const cy = toY(_moon.lat, h);
|
||||
const r = CFG.RADIUS_PX + 6;
|
||||
// Auch Wrap-Kopie prüfen
|
||||
return Math.hypot(mx - cx, my - cy) <= r
|
||||
|| Math.hypot(mx - (cx + w), my - cy) <= r
|
||||
|| Math.hypot(mx - (cx - w), my - cy) <= r;
|
||||
}
|
||||
|
||||
window.addEventListener("click", e => {
|
||||
const rect = moonCanvas.getBoundingClientRect();
|
||||
if (!rect.width) return;
|
||||
const mx = (e.clientX - rect.left) * (moonCanvas.width / rect.width);
|
||||
const my = (e.clientY - rect.top) * (moonCanvas.height / rect.height);
|
||||
if (isHit(mx, my)) {
|
||||
_popup = !_popup;
|
||||
render(EarthCore.size);
|
||||
} else if (_popup) {
|
||||
_popup = false;
|
||||
render(EarthCore.size);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("mousemove", e => {
|
||||
const rect = moonCanvas.getBoundingClientRect();
|
||||
if (!rect.width) return;
|
||||
const mx = (e.clientX - rect.left) * (moonCanvas.width / rect.width);
|
||||
const my = (e.clientY - rect.top) * (moonCanvas.height / rect.height);
|
||||
if (isHit(mx, my)) document.body.style.cursor = "pointer";
|
||||
});
|
||||
|
||||
// ── Updates ───────────────────────────────────────────────────
|
||||
function update() {
|
||||
const now = new Date();
|
||||
_moon = moonPosition(now);
|
||||
_phase = Astronomy.MoonPhase(now);
|
||||
const rs = findMoonRiseSet(now);
|
||||
_rise = rs.rise;
|
||||
_set = rs.set;
|
||||
render(EarthCore.size);
|
||||
}
|
||||
|
||||
window.addEventListener("earth:slow", () => update());
|
||||
window.addEventListener("earth:resize", () => render(EarthCore.size));
|
||||
moonImg.onload = () => render(EarthCore.size);
|
||||
|
||||
update();
|
||||
setInterval(update, CFG.UPDATE_MS);
|
||||
|
||||
}());
|
||||
Reference in New Issue
Block a user