Fav Komet im Sternenhimmel angezeigt

This commit is contained in:
2026-04-13 08:59:10 +02:00
parent bbb9b4946c
commit 5f981d55a6
2 changed files with 376 additions and 0 deletions
+122
View File
@@ -117,6 +117,25 @@ if ($hasFullAdminAccess && $pdoHeader !== null) {
$pendingApprovals = (int)$stmtPendingUsers->fetchColumn();
}
// Favoriten-Kometen des eingeloggten Users für den Sternenhimmel laden
$favCometsForCanvas = [];
if ($loggedIn && isset($_SESSION['user_id']) && $pdoHeader !== null) {
try {
$stmtFavComets = $pdoHeader->prepare(
'SELECT c.designation_and_name
FROM app_user_comets uc
JOIN comets_mpc c ON c.id = uc.comet_id
WHERE uc.user_id = ? AND uc.is_favorite = 1
ORDER BY c.designation_and_name
LIMIT 8'
);
$stmtFavComets->execute([(int)$_SESSION['user_id']]);
$favCometsForCanvas = $stmtFavComets->fetchAll(PDO::FETCH_COLUMN);
} catch (Throwable $e) {
$favCometsForCanvas = [];
}
}
$menuGroups = [
[
'title' => 'Himmel & Planeten',
@@ -281,6 +300,109 @@ if ($loggedIn && !$hasFullAdminAccess && $pdoHeader !== null) {
})();
</script>
<?php if (!empty($favCometsForCanvas)): ?>
<script>
(function () {
const COMET_NAMES = <?= json_encode(array_values($favCometsForCanvas), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
if (!COMET_NAMES.length) return;
const canvas = document.getElementById('starfield');
// Warten bis das Sternfeld-Canvas bereit ist
function initComets() {
if (!canvas.width) { setTimeout(initComets, 100); return; }
const ctx = canvas.getContext('2d');
// Jeden Kometen mit Startposition und Geschwindigkeit initialisieren
const comets = COMET_NAMES.map(function (name, i) {
return makeComet(name, i, COMET_NAMES.length, canvas.width, canvas.height);
});
function makeComet(name, index, total, w, h) {
// Kometen gleichmäßig über die Höhe verteilt, aber mit leichtem Versatz
const seedY = (index + 0.5) / total;
return {
name: name,
x: Math.random() * w,
y: seedY * h * 0.85 + h * 0.05,
// Langsame Bewegung leicht schräg nach rechts-unten
vx: 0.12 + Math.random() * 0.10,
vy: (Math.random() - 0.5) * 0.04,
// Schweif­länge und Coma-Größe
tailLen: 60 + Math.random() * 50,
comaR: 2.5 + Math.random() * 1.5,
// Leichtes Funkeln
phase: Math.random() * Math.PI * 2,
speed: 0.0004 + Math.random() * 0.0003,
};
}
let t = 0;
function drawComets() {
t++;
const w = canvas.width;
const h = canvas.height;
for (const c of comets) {
c.x += c.vx;
c.y += c.vy;
// Wenn rechts raus → links neu eintreten, y leicht variieren
if (c.x - c.tailLen > w) {
c.x = -c.tailLen;
c.y = Math.random() * h * 0.85 + h * 0.05;
}
const alpha = 0.55 + 0.18 * Math.sin(c.phase + t * c.speed * Math.PI * 2);
// --- Schweif (Gradient nach links) ---
const grd = ctx.createLinearGradient(c.x, c.y, c.x - c.tailLen, c.y);
grd.addColorStop(0, `rgba(201,168,76,${(alpha * 0.7).toFixed(3)})`);
grd.addColorStop(0.4, `rgba(240,217,144,${(alpha * 0.25).toFixed(3)})`);
grd.addColorStop(1, 'rgba(201,168,76,0)');
ctx.beginPath();
ctx.moveTo(c.x, c.y);
ctx.lineTo(c.x - c.tailLen, c.y - c.tailLen * 0.06);
ctx.strokeStyle = grd;
ctx.lineWidth = c.comaR * 0.9;
ctx.lineCap = 'round';
ctx.stroke();
// --- Coma (Leuchtkern) ---
const glow = ctx.createRadialGradient(c.x, c.y, 0, c.x, c.y, c.comaR * 3);
glow.addColorStop(0, `rgba(255,245,200,${alpha.toFixed(3)})`);
glow.addColorStop(0.4, `rgba(201,168,76,${(alpha * 0.6).toFixed(3)})`);
glow.addColorStop(1, 'rgba(201,168,76,0)');
ctx.beginPath();
ctx.arc(c.x, c.y, c.comaR * 3, 0, Math.PI * 2);
ctx.fillStyle = glow;
ctx.fill();
// --- Namens-Label ---
ctx.font = '11px sans-serif';
ctx.fillStyle = `rgba(240,217,144,${(alpha * 0.85).toFixed(3)})`;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(c.name, c.x + c.comaR * 3 + 4, c.y - 8);
}
}
// In den bestehenden Animation-Loop einhängen
const origRAF = window.requestAnimationFrame;
(function loop() {
drawComets();
origRAF(loop);
})();
}
// Kurze Verzögerung damit der Stern-Canvas zuerst initialisiert wird
setTimeout(initComets, 200);
})();
</script>
<?php endif; ?>
<header>
<div class="container">
<div class="header-inner">
+254
View File
@@ -186,6 +186,39 @@ try {
];
}
}
// Favoritenkometen des Users laden – Bahnelemente für JS-seitige Berechnung
$favCometData = [];
if ($loggedIn && isset($_SESSION['user_id'])) {
$favCometStmt = $planetariumPdo->prepare(
"SELECT c.designation_and_name,
c.year_of_perihelion, c.month_of_perihelion, c.day_of_perihelion,
c.perihelion_dist_au, c.eccentricity,
c.arg_perihelion_deg, c.ascending_node_deg, c.inclination_deg,
c.absolute_magnitude_h, c.slope_parameter_g
FROM app_user_comets uc
JOIN comets_mpc c ON c.id = uc.comet_id
WHERE uc.user_id = ? AND uc.is_favorite = 1
ORDER BY c.designation_and_name
LIMIT 8"
);
$favCometStmt->execute([(int)$_SESSION['user_id']]);
foreach ($favCometStmt->fetchAll() as $r) {
$favCometData[] = [
'name' => (string)$r['designation_and_name'],
'q' => (float)$r['perihelion_dist_au'],
'e' => (float)$r['eccentricity'],
'w' => (float)$r['arg_perihelion_deg'],
'Om' => (float)$r['ascending_node_deg'],
'inc' => (float)$r['inclination_deg'],
'Tp_y' => (int)$r['year_of_perihelion'],
'Tp_m' => (int)$r['month_of_perihelion'],
'Tp_d' => (float)$r['day_of_perihelion'],
'H' => $r['absolute_magnitude_h'] !== null ? (float)$r['absolute_magnitude_h'] : null,
'G' => $r['slope_parameter_g'] !== null ? (float)$r['slope_parameter_g'] : null,
];
}
}
} catch (Throwable $e) {
if ($starsApiRequested) {
header('Content-Type: application/json; charset=utf-8');
@@ -201,6 +234,11 @@ try {
$messierDataError = $e->getMessage();
}
$favCometDataJson = json_encode(
$favCometData ?? [],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$constellationDataJson = json_encode(
$constellationData,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
@@ -368,6 +406,16 @@ if (!$availableLocations) {
</div>
</div>
<?php if (!empty($favCometData)): ?>
<div class="sp-row">
<span class="sp-label">Fav.-Kometen</span>
<div class="sp-toggles">
<label class="sp-toggle"><input type="checkbox" id="cfgCometsShow" checked><span class="sp-pill"></span><span class="sp-text">Objekte</span></label>
<label class="sp-toggle"><input type="checkbox" id="cfgCometsLabels" checked><span class="sp-pill"></span><span class="sp-text">Namen</span></label>
</div>
</div>
<?php endif; ?>
<div class="sp-row">
<span class="sp-label">Sonne / Mond</span>
<div class="sp-toggles">
@@ -550,6 +598,7 @@ let showSunMoon = true;
let showEclip = true;
let showMessier = true;
let showConst = true;
let showComets = true;
// Label-Sichtbarkeit (separat von Layer-Sichtbarkeit)
let labelEq = true;
@@ -559,6 +608,7 @@ let labelStars = true;
let labelPlanets = true;
let labelSunMoon = true;
let labelMessier = true;
let labelComets = true;
let showStars = true;
// ═══════════════════════════════════════════════════════════════════════════════
@@ -569,6 +619,7 @@ const EMBEDDED_STAR_ERROR = <?php echo $starDataErrorJson ?: 'null'; ?>;
const EMBEDDED_MESSIER_DATA = <?php echo $messierDataJson ?: '[]'; ?>;
const EMBEDDED_MESSIER_ERROR = <?php echo $messierDataErrorJson ?: 'null'; ?>;
const MESSIER = Array.isArray(EMBEDDED_MESSIER_DATA) ? EMBEDDED_MESSIER_DATA : [];
const FAV_COMETS = <?php echo $favCometDataJson ?: '[]'; ?>;
let constellationData = EMBEDDED_CONSTELLATION_DATA;
let hipMap = new Map();
@@ -1507,6 +1558,184 @@ function drawMessier(date) {
}
// ── Kometenposition: Bahnmechanik (Port aus comets.py) ───────────────────────
const COMET_K = 0.01720209895; // Gauß'sche Gravitationskonstante
const COMET_OBLIQ = 23.439279444444445 * Math.PI / 180; // J2000-Schiefe
function cometSolveElliptic(M, e) {
let E = e < 0.8 ? M : (M >= 0 ? Math.PI : -Math.PI);
for (let i = 0; i < 30; i++) {
const d = (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
E -= d;
if (Math.abs(d) < 1e-12) break;
}
return E;
}
function cometSolveHyperbolic(M, e) {
let F = M === 0 ? 0 : Math.asinh(M / e);
for (let i = 0; i < 40; i++) {
const d = (e * Math.sinh(F) - F - M) / (e * Math.cosh(F) - 1);
F -= d;
if (Math.abs(d) < 1e-12) break;
}
return F;
}
function cometSolveParabolic(deltaDays, q) {
const s = COMET_K * deltaDays / Math.sqrt(2 * q * q * q);
let W = s;
for (let i = 0; i < 40; i++) {
const d = (W + W*W*W/3 - s) / (1 + W*W);
W -= d;
if (Math.abs(d) < 1e-12) break;
}
return W;
}
function cometNuR(deltaDays, q, e) {
const TOL = 1e-6;
if (e < 1 - TOL) {
const a = q / (1 - e);
const n = COMET_K / Math.pow(a, 1.5);
const M = ((n * deltaDays) % (2 * Math.PI));
const E = cometSolveElliptic(M, e);
const r = a * (1 - e * Math.cos(E));
const nu = 2 * Math.atan2(Math.sqrt(1+e)*Math.sin(E/2), Math.sqrt(1-e)*Math.cos(E/2));
return [nu, r];
}
if (e > 1 + TOL) {
const a = q / (e - 1);
const M = COMET_K * deltaDays / Math.pow(a, 1.5);
const F = cometSolveHyperbolic(M, e);
const r = a * (e * Math.cosh(F) - 1);
const nu = 2 * Math.atan2(Math.sqrt(e+1)*Math.sinh(F/2), Math.sqrt(e-1)*Math.cosh(F/2));
return [nu, r];
}
const W = cometSolveParabolic(deltaDays, q);
return [2 * Math.atan(W), q * (1 + W*W)];
}
function cometRaDec(comet, date) {
// Periheldatum in ms
const dayFloor = Math.floor(comet.Tp_d);
const fracMs = (comet.Tp_d - dayFloor) * 86400000;
const tpMs = Date.UTC(comet.Tp_y, comet.Tp_m - 1, dayFloor) + fracMs;
const deltaDays = (date.getTime() - tpMs) / 86400000;
const [nu, r] = cometNuR(deltaDays, comet.q, comet.e);
const w = comet.w * Math.PI / 180;
const Om = comet.Om * Math.PI / 180;
const inc = comet.inc * Math.PI / 180;
const u = w + nu;
const cOs = Math.cos(Om), sOs = Math.sin(Om);
const cI = Math.cos(inc), sI = Math.sin(inc);
const cU = Math.cos(u), sU = Math.sin(u);
const xE = r * (cOs*cU - sOs*sU*cI);
const yE = r * (sOs*cU + cOs*sU*cI);
const zE = r * (sU * sI);
// Ekliptik → Äquatorial (J2000)
const cosEps = Math.cos(COMET_OBLIQ), sinEps = Math.sin(COMET_OBLIQ);
const xEq = xE;
const yEq = yE*cosEps - zE*sinEps;
const zEq = yE*sinEps + zE*cosEps;
// Erdvektor (Astronomy Engine) subtrahieren → geozentrisch
const astTime = Astronomy.MakeTime(date);
const earth = Astronomy.HelioVector(Astronomy.Body.Earth, astTime);
const gx = xEq - earth.x;
const gy = yEq - earth.y;
const gz = zEq - earth.z;
const dist = Math.sqrt(gx*gx + gy*gy + gz*gz);
if (dist === 0) return null;
let ra = Math.atan2(gy, gx) * (180 / Math.PI) / 15;
if (ra < 0) ra += 24;
const dec = Math.asin(gz / dist) * (180 / Math.PI);
// Helligkeit schätzen (HG-Modell wie in comets.py)
let mag = null;
if (comet.H !== null && comet.G !== null) {
const rH = Math.sqrt(xEq*xEq + yEq*yEq + zEq*zEq);
if (rH > 0 && dist > 0) {
mag = comet.H + 5*Math.log10(dist) + 2.5*comet.G*Math.log10(rH);
}
}
return { ra, dec, mag };
}
function drawComets(date) {
if (!FAV_COMETS || FAV_COMETS.length === 0) return;
for (const comet of FAV_COMETS) {
let pos;
try { pos = cometRaDec(comet, date); } catch(e) { comet._xy = null; continue; }
if (!pos) { comet._xy = null; continue; }
const hor = eqHorizon(pos.ra, pos.dec, date);
if (hor.altitude < 0) { comet._xy = null; continue; }
const pt = project(hor.altitude, hor.azimuth);
if (!pt) { comet._xy = null; continue; }
comet._xy = pt;
comet._pos = pos;
// Schweifrichtung: vom Sonnenzentrum weg (Näherung über Sonnenazimut)
let tailAngle = -Math.PI * 0.65;
if (sunCache && Number.isFinite(sunCache.az)) {
const sunPt = project(sunCache.alt, sunCache.az);
if (sunPt) {
tailAngle = Math.atan2(pt.y - sunPt.y, pt.x - sunPt.x);
}
}
const tailLen = 22;
const tx = pt.x + Math.cos(tailAngle) * tailLen;
const ty = pt.y + Math.sin(tailAngle) * tailLen;
const grd = ctx.createLinearGradient(pt.x, pt.y, tx, ty);
grd.addColorStop(0, 'rgba(201,168,76,0.80)');
grd.addColorStop(0.5, 'rgba(240,217,144,0.35)');
grd.addColorStop(1, 'rgba(201,168,76,0)');
ctx.beginPath();
ctx.moveTo(pt.x, pt.y);
ctx.lineTo(tx, ty);
ctx.strokeStyle = grd;
ctx.lineWidth = 2.5;
ctx.lineCap = 'round';
ctx.stroke();
const comaR = 4;
const glow = createSafeRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, comaR * 2.5);
if (glow) {
glow.addColorStop(0, 'rgba(255,245,200,0.95)');
glow.addColorStop(0.4, 'rgba(201,168,76,0.60)');
glow.addColorStop(1, 'rgba(201,168,76,0)');
ctx.beginPath();
ctx.arc(pt.x, pt.y, comaR * 2.5, 0, Math.PI * 2);
ctx.fillStyle = glow;
ctx.fill();
}
ctx.beginPath();
ctx.arc(pt.x, pt.y, comaR * 0.7, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,250,220,0.95)';
ctx.fill();
if (labelComets) {
const magStr = pos.mag !== null && isFinite(pos.mag) ? ' (' + pos.mag.toFixed(1) + 'mag)' : '';
ctx.font = '10px Rajdhani,sans-serif';
ctx.fillStyle = 'rgba(240,217,144,0.90)';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(comet.name + magStr, pt.x + comaR * 2.5 + 4, pt.y - 6);
}
}
}
function render() {
const date = getTime();
ctx.clearRect(0, 0, W, H);
@@ -1596,6 +1825,9 @@ function render() {
}
}
// ── Favoriten-Kometen ────────────────────────────────────────────────────
if (showComets) drawComets(date);
ctx.restore(); // Ende Clip
// Horizontring
@@ -1858,6 +2090,8 @@ document.querySelectorAll('input[name="gridMode"]').forEach(radio => {
spBind('cfgEqLabels', () => labelEq, v => { labelEq = v; labelAltaz = v; });
spBind('cfgConstShow', () => showConst, v => showConst = v);
spBind('cfgPlanetsShow', () => showPlanets, v => showPlanets = v);
spBind('cfgCometsShow', () => showComets, v => showComets = v);
spBind('cfgCometsLabels', () => labelComets, v => labelComets = v);
spBind('cfgEclipShow', () => showEclip, v => showEclip = v);
spBind('cfgMessierShow', () => showMessier, v => showMessier = v);
spBind('cfgSunMoonShow', () => showSunMoon, v => showSunMoon = v);
@@ -2147,6 +2381,26 @@ canvas.addEventListener('click', function(e) {
tip.className = 'visible'; positionTooltip(e); return;
}
// Favoriten-Kometen
if (showComets) {
for (const comet of FAV_COMETS) {
if (!comet._xy) continue;
if (Math.hypot(mx - comet._xy.x, my - comet._xy.y) < 14) {
const pos = comet._pos || {};
const hor = eqHorizon(pos.ra ?? 0, pos.dec ?? 0, getTime());
const magStr = pos.mag !== null && pos.mag !== undefined && isFinite(pos.mag) ? pos.mag.toFixed(1) : '–';
tip.innerHTML =
'<span class="tt-name tt-planet" style="color:#f0d990">☄ ' + comet.name + '</span>' +
'Mag: ' + magStr + '<br>' +
'Alt: ' + hor.altitude.toFixed(2) + '°<br>' +
'Az: ' + hor.azimuth.toFixed(2) + '°<br>' +
'RA: ' + (pos.ra ?? 0).toFixed(4) + ' h<br>' +
'Dec: ' + (pos.dec ?? 0).toFixed(3) + '°';
tip.className = 'visible'; positionTooltip(e); return;
}
}
}
// Messier-Objekte
if (showMessier) {
for (const obj of MESSIER) {