Files
skyview.astronomiemuseum.de/public/geocron/plugin-sun.js
T
2026-04-02 15:40:06 +02:00

217 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* plugin-sun.js – Sonnen-Marker mit Popup (Aufgang & Untergang)
* ================================================================
* Abhängigkeit: earth-core.js, astronomy.browser.min.js
*
* Erstellt: #sunCanvas (z-index 6)
*/
(function () {
// ═══════════════════════════════════════════════════════════════
// KONFIGURATION
// ═══════════════════════════════════════════════════════════════
const CFG = {
CORE_RADIUS_PX: 16,
RAY_INNER_PX: 19,
RAY_OUTER_PX: 29,
RAY_COUNT: 11,
OBS_LAT: 50.3667, // Sternwarte Sonneberg
OBS_LON: 11.1833,
UPDATE_MS: 60_000,
};
const { toX, toY, createCanvas, syncCanvas, roundRectPath, toDeg } = EarthCore;
const sunCanvas = createCanvas("sunCanvas", 6);
const ctx = sunCanvas.getContext("2d");
let _sun = { lat: 0, lon: 0 };
let _rise = null;
let _set = null;
let _popup = false;
let _obs = null;
// ═══════════════════════════════════════════════════════════════
// SONNENBERECHNUNG (Astronomy Engine)
// ═══════════════════════════════════════════════════════════════
function sunPosition(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 };
}
}
// ═══════════════════════════════════════════════════════════════
// AUFGANG / UNTERGANG (Astronomy Engine)
// ═══════════════════════════════════════════════════════════════
function findSunRiseSet(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.Sun, _obs, +1, d0, 1);
const s = Astronomy.SearchRiseSet(Astronomy.Body.Sun, _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" });
}
// ═══════════════════════════════════════════════════════════════
// ZEICHNEN
// ═══════════════════════════════════════════════════════════════
function drawSunAt(cx, cy) {
ctx.save();
ctx.strokeStyle = "rgba(255, 200, 0, 0.9)";
ctx.lineWidth = 1.5;
ctx.shadowColor = "rgba(255, 200, 0, 0.7)";
ctx.shadowBlur = 6;
for (let i = 0; i < CFG.RAY_COUNT; i++) {
const a = (i / CFG.RAY_COUNT) * 2 * Math.PI;
ctx.beginPath();
ctx.moveTo(cx + Math.cos(a) * CFG.RAY_INNER_PX, cy + Math.sin(a) * CFG.RAY_INNER_PX);
ctx.lineTo(cx + Math.cos(a) * CFG.RAY_OUTER_PX, cy + Math.sin(a) * CFG.RAY_OUTER_PX);
ctx.stroke();
}
ctx.beginPath();
ctx.arc(cx, cy, CFG.CORE_RADIUS_PX, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255, 230, 0, 1)";
ctx.shadowColor = "rgba(255, 200, 0, 0.9)";
ctx.shadowBlur = 12;
ctx.fill();
ctx.strokeStyle = "rgba(255, 140, 0, 1)";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.restore();
}
function drawPopup(cx, cy, w, h) {
const lines = [
"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.RAY_OUTER_PX + 8;
let by = cy - bh / 2;
if (bx + bw > w - 5) bx = cx - bw - CFG.RAY_OUTER_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(30, 20, 5, 0.92)";
ctx.fill();
ctx.strokeStyle = "rgba(255, 200, 80, 0.6)";
ctx.lineWidth = 1;
ctx.stroke();
lines.forEach((line, i) => {
ctx.fillStyle = i === 0
? "rgba(255, 220, 100, 0.95)"
: "rgba(255, 240, 200, 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(sunCanvas, w, h);
ctx.clearRect(0, 0, w, h);
const sx = toX(_sun.lon, w);
const sy = toY(_sun.lat, h);
const edge = CFG.RAY_OUTER_PX + 2;
drawSunAt(sx, sy);
if (sx - edge < 0) drawSunAt(sx + w, sy);
if (sx + edge > w) drawSunAt(sx - w, sy);
if (_popup) drawPopup(sx, sy, w, h);
}
// ═══════════════════════════════════════════════════════════════
// KLICK-ERKENNUNG
// ═══════════════════════════════════════════════════════════════
function isHit(mx, my) {
const { w, h } = EarthCore.size;
const cx = toX(_sun.lon, w);
const cy = toY(_sun.lat, h);
const r = CFG.RAY_OUTER_PX + 4;
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 = sunCanvas.getBoundingClientRect();
if (!rect.width) return;
const mx = (e.clientX - rect.left) * (sunCanvas.width / rect.width);
const my = (e.clientY - rect.top) * (sunCanvas.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 = sunCanvas.getBoundingClientRect();
if (!rect.width) return;
const mx = (e.clientX - rect.left) * (sunCanvas.width / rect.width);
const my = (e.clientY - rect.top) * (sunCanvas.height / rect.height);
if (isHit(mx, my)) document.body.style.cursor = "pointer";
});
// ═══════════════════════════════════════════════════════════════
// UPDATE
// ═══════════════════════════════════════════════════════════════
function update() {
const now = new Date();
_sun = sunPosition(now);
const rs = findSunRiseSet(now);
_rise = rs.rise;
_set = rs.set;
render(EarthCore.size);
}
window.addEventListener("earth:slow", () => update());
window.addEventListener("earth:resize", () => render(EarthCore.size));
update();
setInterval(update, CFG.UPDATE_MS);
}());