first commit
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
'use strict';
|
||||
|
||||
// Astronomy Engine wird per importScripts geladen
|
||||
importScripts('astronomy.browser.min.js');
|
||||
|
||||
const STAR_BODIES = ['Star1', 'Star2', 'Star3', 'Star4', 'Star5', 'Star6', 'Star7', 'Star8'];
|
||||
const COARSE_STEP_MS = 2 * 60 * 1000;
|
||||
const CANDIDATE_MARGIN_DEG = 0.32;
|
||||
const MOON_RADIUS_KM = 1737.4;
|
||||
|
||||
let OBSERVER = null;
|
||||
|
||||
function moonAngularRadiusDeg(distanceAu) {
|
||||
if (!Number.isFinite(distanceAu) || distanceAu <= 0) return 0;
|
||||
const ratio = MOON_RADIUS_KM / (distanceAu * Astronomy.KM_PER_AU);
|
||||
return Math.asin(Math.max(-1, Math.min(1, ratio))) * Astronomy.RAD2DEG;
|
||||
}
|
||||
|
||||
function evaluateGeometry(bodyName, timeInput) {
|
||||
const time = timeInput instanceof Date
|
||||
? timeInput
|
||||
: (timeInput && timeInput.date instanceof Date ? timeInput.date : new Date(timeInput));
|
||||
const moonEq = Astronomy.Equator('Moon', time, OBSERVER, true, true);
|
||||
const starEq = Astronomy.Equator(bodyName, time, OBSERVER, true, true);
|
||||
const sunEq = Astronomy.Equator('Sun', time, OBSERVER, true, true);
|
||||
const moonHor = Astronomy.Horizon(time, OBSERVER, moonEq.ra, moonEq.dec, 'normal');
|
||||
const starHor = Astronomy.Horizon(time, OBSERVER, starEq.ra, starEq.dec, 'normal');
|
||||
const sunHor = Astronomy.Horizon(time, OBSERVER, sunEq.ra, sunEq.dec, 'normal');
|
||||
const sepDeg = Astronomy.AngleBetween(moonEq.vec, starEq.vec);
|
||||
const radiusDeg = moonAngularRadiusDeg(Number(moonEq.dist || 0));
|
||||
return {
|
||||
timeMs: time.getTime(),
|
||||
sepDeg,
|
||||
radiusDeg,
|
||||
marginDeg: sepDeg - radiusDeg,
|
||||
moonAltDeg: Number(moonHor.altitude || 0),
|
||||
starAltDeg: Number(starHor.altitude || 0),
|
||||
sunAltDeg: Number(sunHor.altitude || 0),
|
||||
};
|
||||
}
|
||||
|
||||
function makeMarginEvaluator(bodyName) {
|
||||
return function (astroTime) {
|
||||
return evaluateGeometry(bodyName, astroTime).marginDeg;
|
||||
};
|
||||
}
|
||||
|
||||
function findRoot(bodyName, leftMs, rightMs) {
|
||||
const evaluator = makeMarginEvaluator(bodyName);
|
||||
try {
|
||||
const root = Astronomy.Search(
|
||||
evaluator,
|
||||
new Astronomy.AstroTime(new Date(leftMs)),
|
||||
new Astronomy.AstroTime(new Date(rightMs)),
|
||||
{ dt_tolerance_seconds: 0.05, iter_limit: 60 }
|
||||
);
|
||||
if (root && root.date instanceof Date) return root.date.getTime();
|
||||
} catch (e) {
|
||||
// fallback: bisection
|
||||
}
|
||||
let left = leftMs;
|
||||
let right = rightMs;
|
||||
let leftVal = evaluateGeometry(bodyName, left).marginDeg;
|
||||
for (let i = 0; i < 48; i += 1) {
|
||||
const mid = (left + right) / 2;
|
||||
const midVal = evaluateGeometry(bodyName, mid).marginDeg;
|
||||
if (Math.sign(leftVal) === Math.sign(midVal)) { left = mid; leftVal = midVal; }
|
||||
else { right = mid; }
|
||||
}
|
||||
return Math.round((left + right) / 2);
|
||||
}
|
||||
|
||||
function findMinimum(bodyName, startMs, endMs) {
|
||||
let left = startMs;
|
||||
let right = endMs;
|
||||
for (let i = 0; i < 60; i += 1) {
|
||||
const span = (right - left) / 3;
|
||||
const m1 = left + span;
|
||||
const m2 = right - span;
|
||||
if (evaluateGeometry(bodyName, m1).marginDeg <= evaluateGeometry(bodyName, m2).marginDeg) {
|
||||
right = m2;
|
||||
} else {
|
||||
left = m1;
|
||||
}
|
||||
}
|
||||
return Math.round((left + right) / 2);
|
||||
}
|
||||
|
||||
function classifyVisibility(sunAltDeg) {
|
||||
if (sunAltDeg > -5) return { key: 'day', label: 'Tag' };
|
||||
if (sunAltDeg >= -10) return { key: 'twilight', label: 'Daemmerung' };
|
||||
return { key: 'night', label: 'Nacht' };
|
||||
}
|
||||
|
||||
function chunkStars(stars, size) {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < stars.length; i += size) chunks.push(stars.slice(i, i + size));
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function defineChunk(chunk) {
|
||||
STAR_BODIES.forEach((body, index) => {
|
||||
const star = chunk[index];
|
||||
if (star) {
|
||||
Astronomy.DefineStar(body, Number(star.ra), Number(star.dec), Number(star.distLy || 1000));
|
||||
} else {
|
||||
Astronomy.DefineStar(body, 0, 0, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function evaluateStar(bodyName, star, dayStartMs, dayEndMs) {
|
||||
let minMarginDeg = Infinity;
|
||||
let minTimeMs = dayStartMs;
|
||||
let everNegative = false;
|
||||
let nearHit = false;
|
||||
let previousMargin = null;
|
||||
let previousTimeMs = null;
|
||||
let startMarginDeg = null;
|
||||
let endMarginDeg = null;
|
||||
let ingressBracket = null;
|
||||
let egressBracket = null;
|
||||
|
||||
for (let timeMs = dayStartMs; timeMs <= dayEndMs; timeMs += COARSE_STEP_MS) {
|
||||
const geometry = evaluateGeometry(bodyName, timeMs);
|
||||
if (geometry.marginDeg < minMarginDeg) { minMarginDeg = geometry.marginDeg; minTimeMs = timeMs; }
|
||||
if (geometry.marginDeg <= 0) everNegative = true;
|
||||
if (geometry.marginDeg <= CANDIDATE_MARGIN_DEG) nearHit = true;
|
||||
if (startMarginDeg === null) startMarginDeg = geometry.marginDeg;
|
||||
endMarginDeg = geometry.marginDeg;
|
||||
if (previousMargin !== null && previousTimeMs !== null && Math.sign(previousMargin) !== Math.sign(geometry.marginDeg)) {
|
||||
nearHit = true;
|
||||
if (previousMargin > 0 && geometry.marginDeg <= 0 && ingressBracket === null) ingressBracket = [previousTimeMs, timeMs];
|
||||
if (previousMargin <= 0 && geometry.marginDeg > 0) egressBracket = [previousTimeMs, timeMs];
|
||||
}
|
||||
previousMargin = geometry.marginDeg;
|
||||
previousTimeMs = timeMs;
|
||||
}
|
||||
|
||||
if (!everNegative && !nearHit) return null;
|
||||
if (!everNegative || minMarginDeg > 0) return null;
|
||||
|
||||
const ingressMs = ingressBracket ? findRoot(bodyName, ingressBracket[0], ingressBracket[1]) : null;
|
||||
const egressMs = egressBracket ? findRoot(bodyName, egressBracket[0], egressBracket[1]) : null;
|
||||
|
||||
let minSearchStart = Math.max(dayStartMs, minTimeMs - COARSE_STEP_MS);
|
||||
let minSearchEnd = Math.min(dayEndMs, minTimeMs + COARSE_STEP_MS);
|
||||
if (ingressMs !== null && egressMs !== null) { minSearchStart = ingressMs; minSearchEnd = egressMs; }
|
||||
|
||||
const maxMs = findMinimum(bodyName, minSearchStart, minSearchEnd);
|
||||
const maxGeom = evaluateGeometry(bodyName, maxMs);
|
||||
|
||||
if (maxGeom.marginDeg > 0 || maxGeom.moonAltDeg <= 0 || maxGeom.starAltDeg <= 0) return null;
|
||||
|
||||
return {
|
||||
star,
|
||||
ingressMs,
|
||||
maxMs,
|
||||
egressMs,
|
||||
partialStart: ingressMs === null && startMarginDeg !== null && startMarginDeg <= 0,
|
||||
partialEnd: egressMs === null && endMarginDeg !== null && endMarginDeg <= 0,
|
||||
durationMs: ingressMs !== null && egressMs !== null ? egressMs - ingressMs : null,
|
||||
limbDistanceArcmin: Math.abs(maxGeom.marginDeg) * 60,
|
||||
moonAltDeg: maxGeom.moonAltDeg,
|
||||
starAltDeg: maxGeom.starAltDeg,
|
||||
sunAltDeg: maxGeom.sunAltDeg,
|
||||
visibility: classifyVisibility(maxGeom.sunAltDeg),
|
||||
};
|
||||
}
|
||||
|
||||
// Nachricht vom Hauptthread: { location, stars, dayStartMs, dayEndMs, dayString, dayIndex, totalDays }
|
||||
self.onmessage = function (e) {
|
||||
const { location, stars, dayStartMs, dayEndMs, dayString, dayIndex, totalDays } = e.data;
|
||||
|
||||
OBSERVER = new Astronomy.Observer(
|
||||
Number(location.latitude),
|
||||
Number(location.longitude),
|
||||
Number(location.elevation || 0)
|
||||
);
|
||||
|
||||
const chunks = chunkStars(stars, STAR_BODIES.length);
|
||||
const results = [];
|
||||
|
||||
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
|
||||
const chunk = chunks[chunkIndex];
|
||||
defineChunk(chunk);
|
||||
chunk.forEach((star, index) => {
|
||||
const event = evaluateStar(STAR_BODIES[index], star, dayStartMs, dayEndMs);
|
||||
if (event) {
|
||||
event._dateString = dayString;
|
||||
results.push(event);
|
||||
}
|
||||
});
|
||||
|
||||
// Fortschritt melden: chunksDone / totalChunks = Anteil dieses Tages fertig
|
||||
self.postMessage({ type: 'progress', dayIndex, chunksDone: chunkIndex + 1, totalChunks: chunks.length });
|
||||
}
|
||||
|
||||
self.postMessage({ type: 'done', dayIndex, dayString, results });
|
||||
};
|
||||
Reference in New Issue
Block a user