Sonne / Mond
@@ -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 = ;
const EMBEDDED_MESSIER_DATA = ;
const EMBEDDED_MESSIER_ERROR = ;
const MESSIER = Array.isArray(EMBEDDED_MESSIER_DATA) ? EMBEDDED_MESSIER_DATA : [];
+const FAV_COMETS = ;
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 =
+ '☄ ' + comet.name + '' +
+ 'Mag: ' + magStr + '
' +
+ 'Alt: ' + hor.altitude.toFixed(2) + '°
' +
+ 'Az: ' + hor.azimuth.toFixed(2) + '°
' +
+ 'RA: ' + (pos.ra ?? 0).toFixed(4) + ' h
' +
+ 'Dec: ' + (pos.dec ?? 0).toFixed(3) + '°';
+ tip.className = 'visible'; positionTooltip(e); return;
+ }
+ }
+ }
+
// Messier-Objekte
if (showMessier) {
for (const obj of MESSIER) {