Texte verbessert

This commit is contained in:
2026-04-20 15:28:42 +02:00
parent 4e8baae1ad
commit 90512a0550
3 changed files with 891 additions and 10 deletions
+546 -8
View File
@@ -34,6 +34,7 @@ $selectedMonth = isset($_GET['month']) ? max(1, min(12, (int) $_GET['month'])) :
// - Sternbedeckungen durch den Mond
// - Meteorstrom-Maxima
// - Gute Überflüge des Favoriten-Satelliten
// - Kometenereignisse für ausgewählte Kometen
// - Zusammengeführte Monatsereignisliste für Übersicht, SQL-Export und iCal
// -------------------------------------------------------------------------
@@ -155,6 +156,27 @@ $planetParadeMinPlanetCount = 4;
// Das filtert kurzlebige Ein-Tages-Treffer heraus.
$planetParadeMinConsecutiveDays = 2;
// -------------------------------------------------------------------------
// Schwellen fuer ausgewaehlte Kometen
// -------------------------------------------------------------------------
// Gute Kometensichtbarkeit wird morgens vor Sonnenaufgang und abends nach
// Sonnenuntergang geprueft.
$favoriteCometVisibilitySampleOffsetMinutes = 60;
// Der Komet sollte ausreichend hoch stehen, damit die Beobachtung praktisch
// sinnvoll bleibt.
$favoriteCometVisibilityMinAltitudeDeg = 10.0;
// Die Sonne muss tief genug unter dem Horizont stehen.
$favoriteCometVisibilityMaxSunAltitudeDeg = -8.0;
// Optional koennen wir eine grobe Helligkeitsschwelle setzen, damit nur
// realistisch interessante Fenster erscheinen.
$favoriteCometVisibilityMaxMagnitude = 10.0;
// Einzelne Zufallstreffer sollen nicht als eigenes Monatsereignis erscheinen.
$favoriteCometVisibilityMinConsecutiveDays = 2;
$years = range($currentYear - 2, $currentYear + 5);
$months = [
1 => 'Januar',
@@ -213,6 +235,8 @@ $meteorShowerPeaks = [];
$favoriteSatellitePasses = [];
$favoriteSatelliteName = null;
$favoriteSatellitePayload = [];
$favoriteCometPayload = [];
$favoriteCometEvents = [];
$seasonChanges = [];
$timeChanges = [];
$monthEventList = [];
@@ -272,6 +296,30 @@ if (!function_exists('monthForecastForwardDegreeDistance')) {
}
}
if (!function_exists('monthForecastParseMeteorZhrRange')) {
function monthForecastParseMeteorZhrRange(?int $zhr, string $zhrVariable): array
{
if ($zhr !== null && $zhr >= 0) {
return ['min' => $zhr, 'max' => $zhr];
}
if ($zhrVariable === '') {
return ['min' => null, 'max' => null];
}
preg_match_all('/\d+/', $zhrVariable, $matches);
$numbers = array_map('intval', $matches[0] ?? []);
if ($numbers === []) {
return ['min' => null, 'max' => null];
}
return [
'min' => min($numbers),
'max' => max($numbers),
];
}
}
if (!function_exists('monthForecastRadiantIsVisible')) {
function monthForecastRadiantIsVisible(float $latitudeDeg, ?float $declinationDeg): bool
{
@@ -629,7 +677,7 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
4 => 'Im April dominiert der Fruehlingshimmel den Abend. Loewe, Jungfrau und Bootes stehen guenstig, waehrend die Wintersternbilder nun rasch in den Westen abwandern und am spaeteren Abend bereits Platz fuer den kommenden Sommerhimmel machen.',
5 => 'Der Mai zeigt den Fruehlingshimmel in voller Pracht. Loewe, Jungfrau, Bootes und die ausgedehnte Wasserschlange praegen die Nacht, waehrend sich im Osten schon die ersten Sternbilder des Sommers bemerkbar machen.',
6 => 'Im Juni werden die Naechte kurz, doch gerade in der spaeteren Dunkelheit treten bereits die Sommersternbilder hervor. Schwan, Leier und Adler gewinnen an Hoehe, waehrend der Fruehlingshimmel langsam nach Westen rueckt.',
7 => 'Der Juli gehoert dem Sommerhimmel. Das Sommerdreieck aus Wega, Deneb und Atair dominiert den Blick nach oben, daneben laden Schuetze, Skorpion und die reichen Sternfelder der Milchstrasse zu ausgedehnten Beobachtungen ein.',
7 => 'Im Juli praegen die Sommersternbilder den Himmel. Das Sommerdreieck aus Wega, Deneb und Atair dominiert den Blick nach oben, daneben laden Schuetze, Skorpion und die reichen Sternfelder der Milchstrasse zu ausgedehnten Beobachtungen ein.',
8 => 'Im August zeigt sich der Sommerhimmel von seiner eindrucksvollsten Seite. Das Sommerdreieck steht hoch, die Milchstrasse zieht sich markant ueber den Himmel, und in der zweiten Nachthaelfte machen sich bereits erste Herbststernbilder bemerkbar.',
9 => 'Der September vermittelt zwischen Sommer- und Herbsthimmel. Das Sommerdreieck bleibt ein Blickfang, doch Pegasus, Andromeda und die herbstlichen Sternbilder steigen nun immer besser am Abendhimmel auf.',
10 => 'Im Oktober uebernimmt der Herbsthimmel die Hauptrolle. Pegasus, Andromeda, Perseus und Dreieck stehen guenstig, waehrend sich gegen Morgen schon die ersten Vorboten des Winterhimmels zeigen.',
@@ -666,6 +714,19 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
return implode(', ', $parts);
};
$pickRandomText = static function (array $options, string $fallback): string {
$options = array_values(array_filter($options, static fn ($option): bool => trim((string) $option) !== ''));
if ($options === []) {
return $fallback;
}
try {
return (string) $options[random_int(0, count($options) - 1)];
} catch (Throwable) {
return (string) $options[array_rand($options)];
}
};
$buildSeasonNarrative = static function (array $event): ?string {
$label = trim((string) ($event['event'] ?? ''));
$date = trim((string) ($event['date'] ?? ''));
@@ -700,7 +761,13 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
4
);
if ($moonEvents !== []) {
$paragraphs[] = 'Auch der Mond liefert im Laufe des Monats einige markante Beobachtungsmomente. Besonders auffaellig sind ' . $describeEvents($moonEvents) . '.';
$moonIntro = $pickRandomText([
'Auch der Mond liefert im Laufe des Monats einige markante Beobachtungsmomente.',
'Der Mond zeigt im Monatsverlauf mehrere gut beobachtbare Phasen und Konstellationen.',
'Am Mond ergeben sich im Laufe des Monats einige auffaellige Termine und Begegnungen.',
'Der Mond bietet im Monatsverlauf mehrere charakteristische Beobachtungssituationen.',
], 'Auch der Mond liefert im Laufe des Monats einige markante Beobachtungsmomente.');
$paragraphs[] = $moonIntro . ' Besonders auffaellig sind ' . $describeEvents($moonEvents) . '.';
}
$planetObservationEvents = $findEvents(
@@ -709,7 +776,19 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
5
);
if ($planetObservationEvents !== []) {
$paragraphs[] = 'Bei den Planeten lohnt sich vor allem der Blick auf ' . $describeEvents($planetObservationEvents) . '. Diese Termine geben dem Monat seinen charakteristischen planetaren Akzent.';
$planetIntro = $pickRandomText([
'Bei den Planeten lohnt sich vor allem der Blick auf',
'Planetarisch besonders interessant sind in diesem Monat',
'Bei den Planeten stechen in diesem Monat vor allem',
'Einen planetaren Schwerpunkt bilden in diesem Monat',
], 'Bei den Planeten lohnt sich vor allem der Blick auf');
$planetOutro = $pickRandomText([
'Diese Termine geben dem Monat seinen charakteristischen planetaren Akzent.',
'Damit bekommt der Monat einen klaren planetaren Schwerpunkt.',
'Solche Termine praegen den planetaren Charakter dieses Monats besonders deutlich.',
'Sie setzen im Monatsverlauf markante planetare Akzente.',
], 'Diese Termine geben dem Monat seinen charakteristischen planetaren Akzent.');
$paragraphs[] = $planetIntro . ' ' . $describeEvents($planetObservationEvents) . '. ' . $planetOutro;
}
$planetMotionEvents = $findEvents(
@@ -718,7 +797,34 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
4
);
if ($planetMotionEvents !== []) {
$paragraphs[] = 'Im Monatsverlauf zeigen sich bei den Planeten auch auffaellige Positionswechsel. Dazu gehoeren ' . $describeEvents($planetMotionEvents) . '.';
$planetMotionIntro = $pickRandomText([
'Im Monatsverlauf zeigen sich bei den Planeten auch auffaellige Positionswechsel.',
'Auch in ihrer Stellung am Himmel veraendern sich die Planeten in diesem Monat sichtbar.',
'Neben den Beobachtungshighlights fallen bei den Planeten auch einige Positionswechsel auf.',
'Auch die Bewegung der Planeten vor dem Sternhintergrund macht sich in diesem Monat bemerkbar.',
], 'Im Monatsverlauf zeigen sich bei den Planeten auch auffaellige Positionswechsel.');
$paragraphs[] = $planetMotionIntro . ' Dazu gehoeren ' . $describeEvents($planetMotionEvents) . '.';
}
$cometEvents = $findEvents(
$filteredEvents,
static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['favorite_comet_good_visibility', 'favorite_comet_perihelion', 'favorite_comet_geocentric_minimum'], true),
4
);
if ($cometEvents !== []) {
$cometIntro = $pickRandomText([
'Bei den Kometen sind in diesem Monat besonders',
'Auch bei den Kometen lohnt sich in diesem Monat der Blick auf',
'Unter den Kometen fallen in diesem Monat vor allem',
'Kometenfreunde koennen sich in diesem Monat besonders fuer',
], 'Bei den Kometen sind in diesem Monat besonders');
$cometOutro = $pickRandomText([
'interessant.',
'beobachtenswert.',
'bemerkenswert.',
'einen genaueren Blick wert.',
], 'interessant.');
$paragraphs[] = $cometIntro . ' ' . $describeEvents($cometEvents) . ' ' . $cometOutro;
}
$eclipseEvents = $findEvents(
@@ -727,21 +833,62 @@ if (!function_exists('monthForecastBuildNarrativeText')) {
3
);
if ($eclipseEvents !== []) {
$paragraphs[] = 'Besondere Aufmerksamkeit verdienen ausserdem die Finsternistermine. In diesem Monat stechen ' . $describeEvents($eclipseEvents) . ' hervor.';
$eclipseIntro = $pickRandomText([
'Besondere Aufmerksamkeit verdienen ausserdem die Finsternistermine.',
'Auch die Finsternisse setzen in diesem Monat markante Akzente.',
'Ein weiterer Hoehepunkt koennen in diesem Monat die Finsternistermine sein.',
'Nicht zuletzt lohnt sich in diesem Monat auch ein Blick auf moegliche Finsternisse.',
], 'Besondere Aufmerksamkeit verdienen ausserdem die Finsternistermine.');
$paragraphs[] = $eclipseIntro . ' In diesem Monat stechen ' . $describeEvents($eclipseEvents) . ' hervor.';
}
$meteorEvents = $findEvents(
$filteredEvents,
static fn (array $event): bool => (string) ($event['type'] ?? '') === 'meteor_shower_peak',
static fn (array $event): bool => (string) ($event['type'] ?? '') === 'meteor_shower_peak'
&& (
(int) ($event['zhr_min_estimate'] ?? 0) >= 20
|| (int) ($event['zhr_max_estimate'] ?? 0) >= 30
),
2
);
if ($meteorEvents !== []) {
$paragraphs[] = 'Freunde von Meteoren sollten zudem die Aktivitaet der Meteorstroeme im Blick behalten. Erwähnenswert sind besonders ' . $describeEvents($meteorEvents) . '.';
$meteorIntro = $pickRandomText([
'Freunde von Meteoren sollten zudem die Aktivitaet der Meteorstroeme im Blick behalten.',
'Auch die Meteorstroeme verdienen in diesem Monat Aufmerksamkeit.',
'Wer nach Sternschnuppen Ausschau halten moechte, sollte auch die Meteorstroeme beachten.',
'Ein Blick auf die Meteorstroeme kann sich in diesem Monat ebenfalls lohnen.',
], 'Freunde von Meteoren sollten zudem die Aktivitaet der Meteorstroeme im Blick behalten.');
$paragraphs[] = $meteorIntro . ' Erwähnenswert sind besonders ' . $describeEvents($meteorEvents) . '.';
} else {
$meteorEvents = $findEvents(
$filteredEvents,
static fn (array $event): bool => (string) ($event['type'] ?? '') === 'meteor_shower_peak'
&& (
(int) ($event['zhr_min_estimate'] ?? 0) >= 10
|| (int) ($event['zhr_max_estimate'] ?? 0) >= 20
),
2
);
if ($meteorEvents !== []) {
$meteorModerateIntro = $pickRandomText([
'Bei den Meteorstroemen lohnt in diesem Monat vor allem ein Blick auf',
'Unter den Meteorstroemen verdienen in diesem Monat besonders',
'Auch bei den Meteorstroemen fallen in diesem Monat vor allem',
'Wer Meteorstroeme verfolgen moechte, sollte in diesem Monat vor allem auf',
], 'Bei den Meteorstroemen lohnt in diesem Monat vor allem ein Blick auf');
$paragraphs[] = $meteorModerateIntro . ' ' . $describeEvents($meteorEvents) . '.';
}
}
if (count($paragraphs) === 1 && $filteredEvents !== []) {
$highlights = array_slice($filteredEvents, 0, 4);
$paragraphs[] = 'Zu den auffaelligen Terminen des Monats gehoeren ' . $describeEvents($highlights) . '.';
$highlightIntro = $pickRandomText([
'Zu den auffaelligen Terminen des Monats gehoeren',
'Unter den markanten Terminen dieses Monats finden sich',
'Im Monatsverlauf fallen vor allem',
'Besonders bemerkenswert im Monatskalender sind',
], 'Zu den auffaelligen Terminen des Monats gehoeren');
$paragraphs[] = $highlightIntro . ' ' . $describeEvents($highlights) . '.';
}
return implode("\n\n", $paragraphs);
@@ -926,6 +1073,40 @@ try {
}
if ($loggedIn && isset($_SESSION['user_id'])) {
$favoriteCometStmt = $pdo->prepare(
"SELECT c.id, c.designation_and_name, c.designation_packed, c.orbit_type,
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
INNER JOIN comets_mpc c ON c.id = uc.comet_id
WHERE uc.user_id = :user_id
ORDER BY c.designation_and_name ASC, c.id ASC"
);
$favoriteCometStmt->execute([
':user_id' => (int) $_SESSION['user_id'],
]);
foreach ($favoriteCometStmt->fetchAll() as $favoriteCometRow) {
$favoriteCometPayload[] = [
'id' => (int) ($favoriteCometRow['id'] ?? 0),
'designation_and_name' => trim((string) ($favoriteCometRow['designation_and_name'] ?? '')),
'designation_packed' => trim((string) ($favoriteCometRow['designation_packed'] ?? '')),
'orbit_type' => trim((string) ($favoriteCometRow['orbit_type'] ?? '')),
'year_of_perihelion' => (int) ($favoriteCometRow['year_of_perihelion'] ?? 0),
'month_of_perihelion' => (int) ($favoriteCometRow['month_of_perihelion'] ?? 0),
'day_of_perihelion' => (float) ($favoriteCometRow['day_of_perihelion'] ?? 0.0),
'perihelion_dist_au' => (float) ($favoriteCometRow['perihelion_dist_au'] ?? 0.0),
'eccentricity' => (float) ($favoriteCometRow['eccentricity'] ?? 0.0),
'arg_perihelion_deg' => (float) ($favoriteCometRow['arg_perihelion_deg'] ?? 0.0),
'ascending_node_deg' => (float) ($favoriteCometRow['ascending_node_deg'] ?? 0.0),
'inclination_deg' => (float) ($favoriteCometRow['inclination_deg'] ?? 0.0),
'absolute_magnitude_h' => $favoriteCometRow['absolute_magnitude_h'] !== null ? (float) $favoriteCometRow['absolute_magnitude_h'] : null,
'slope_parameter_g' => $favoriteCometRow['slope_parameter_g'] !== null ? (float) $favoriteCometRow['slope_parameter_g'] : null,
'is_favorite' => true,
];
}
$favoriteSatelliteStmt = $pdo->prepare(
"SELECT s.id, s.norad_cat_id, s.object_name, s.object_id, s.classification_type,
s.std_mag, s.rcs_size, s.rcs,
@@ -1067,6 +1248,44 @@ if (!empty($favoriteSatellitePayload)) {
}
}
if (!empty($favoriteCometPayload)) {
$tempFavoriteCometFile = tempnam(sys_get_temp_dir(), 'month_comets_');
if ($tempFavoriteCometFile !== false) {
try {
file_put_contents($tempFavoriteCometFile, json_encode($favoriteCometPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
$favoriteCometOutput = [];
$favoriteCometArgs = [
$pythonScriptPath,
'favorite_comet_events_for_month',
(string) $chartLatitude,
(string) $chartLongitude,
(string) $chartElevation,
(string) $selectedYear,
(string) $selectedMonth,
$chartTimezone,
(string) $favoriteCometVisibilitySampleOffsetMinutes,
(string) $favoriteCometVisibilityMinAltitudeDeg,
(string) $favoriteCometVisibilityMaxSunAltitudeDeg,
(string) $favoriteCometVisibilityMaxMagnitude,
(string) $favoriteCometVisibilityMinConsecutiveDays,
'@' . $tempFavoriteCometFile,
];
$favoriteCometCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $favoriteCometArgs)) . ' 2>&1';
exec($favoriteCometCommand, $favoriteCometOutput);
$favoriteCometRaw = trim(implode("\n", $favoriteCometOutput));
$favoriteCometDecoded = json_decode($favoriteCometRaw, true);
if (is_array($favoriteCometDecoded) && ($favoriteCometDecoded['ok'] ?? false)) {
$favoriteCometEvents = is_array($favoriteCometDecoded['events'] ?? null) ? $favoriteCometDecoded['events'] : [];
}
} catch (Throwable $e) {
} finally {
if (is_file($tempFavoriteCometFile)) {
@unlink($tempFavoriteCometFile);
}
}
}
}
$moonPhaseOutput = [];
$moonPhaseExitCode = 0;
$moonPhaseArgs = [
@@ -1851,6 +2070,30 @@ foreach ($planetConstellationChangeEvents as $changeEvent) {
];
}
foreach ($favoriteCometEvents as $favoriteCometEvent) {
if (!is_array($favoriteCometEvent)) {
continue;
}
$favoriteCometKind = trim((string) ($favoriteCometEvent['kind'] ?? ''));
$favoriteCometType = match ($favoriteCometKind) {
'good_visibility' => 'favorite_comet_good_visibility',
'perihelion' => 'favorite_comet_perihelion',
'geocentric_minimum' => 'favorite_comet_geocentric_minimum',
default => 'favorite_comet_event',
};
$monthEventList[] = [
'event' => (string) ($favoriteCometEvent['label'] ?? 'Kometenereignis'),
'date' => (string) ($favoriteCometEvent['local_date'] ?? ''),
'time' => (string) ($favoriteCometEvent['local_time'] ?? ''),
'local_iso' => (string) ($favoriteCometEvent['local_iso'] ?? ''),
'end_local_iso' => (string) ($favoriteCometEvent['end_local_iso'] ?? ''),
'duration_minutes' => isset($favoriteCometEvent['duration_minutes']) ? (int) $favoriteCometEvent['duration_minutes'] : null,
'type' => $favoriteCometType,
];
}
foreach ($moonOccultations as $occultationEvent) {
if (!is_array($occultationEvent)) {
continue;
@@ -1904,6 +2147,9 @@ foreach ($meteorShowerPeaks as $meteorShowerPeak) {
$showerCode = trim((string) ($meteorShowerPeak['shower_code'] ?? ''));
$zhr = $meteorShowerPeak['zhr'] ?? null;
$zhrVariable = trim((string) ($meteorShowerPeak['zhr_variable'] ?? ''));
$zhrRange = monthForecastParseMeteorZhrRange(is_int($zhr) ? $zhr : null, $zhrVariable);
$zhrMinEstimate = $zhrRange['min'];
$zhrMaxEstimate = $zhrRange['max'];
$rateText = '';
if (is_int($zhr) && $zhr >= 0) {
@@ -1926,6 +2172,8 @@ foreach ($meteorShowerPeaks as $meteorShowerPeak) {
'date' => $peakDateTime->format('d.m.Y'),
'time' => $peakDateTime->format('H:i'),
'local_iso' => $peakDateTime->format(DateTimeInterface::ATOM),
'zhr_min_estimate' => $zhrMinEstimate,
'zhr_max_estimate' => $zhrMaxEstimate,
'type' => 'meteor_shower_peak',
];
@@ -2021,6 +2269,26 @@ $constellationLinesJson = json_encode($constellationLines, JSON_UNESCAPED_UNICOD
$messierDataJson = json_encode($messierData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$planetDataJson = json_encode($planetData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$moonDataJson = json_encode($moonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$monthChartComets = array_map(
static function (array $comet): array {
return [
'id' => (int) ($comet['id'] ?? 0),
'name' => trim((string) (($comet['designation_and_name'] ?? '') !== '' ? $comet['designation_and_name'] : ($comet['designation_packed'] ?? 'Komet'))),
'q' => isset($comet['perihelion_dist_au']) ? (float) $comet['perihelion_dist_au'] : null,
'e' => isset($comet['eccentricity']) ? (float) $comet['eccentricity'] : null,
'w' => isset($comet['arg_perihelion_deg']) ? (float) $comet['arg_perihelion_deg'] : null,
'Om' => isset($comet['ascending_node_deg']) ? (float) $comet['ascending_node_deg'] : null,
'inc' => isset($comet['inclination_deg']) ? (float) $comet['inclination_deg'] : null,
'Tp_y' => isset($comet['year_of_perihelion']) ? (int) $comet['year_of_perihelion'] : null,
'Tp_m' => isset($comet['month_of_perihelion']) ? (int) $comet['month_of_perihelion'] : null,
'Tp_d' => isset($comet['day_of_perihelion']) ? (float) $comet['day_of_perihelion'] : null,
'H' => array_key_exists('absolute_magnitude_h', $comet) && $comet['absolute_magnitude_h'] !== null ? (float) $comet['absolute_magnitude_h'] : null,
'G' => array_key_exists('slope_parameter_g', $comet) && $comet['slope_parameter_g'] !== null ? (float) $comet['slope_parameter_g'] : null,
];
},
$favoriteCometPayload
);
$monthChartCometsJson = json_encode($monthChartComets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$moonPhaseEventsJson = json_encode($moonPhaseEvents, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$observationTipCalendarIcsJson = json_encode($observationTipCalendarIcs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
@@ -2396,11 +2664,129 @@ const MONTH_CHART_CONSTELLATIONS = <?= $constellationLinesJson ?: '[]' ?>;
const MONTH_CHART_MESSIER = <?= $messierDataJson ?: '[]' ?>;
const MONTH_CHART_PLANETS = <?= $planetDataJson ?: '[]' ?>;
const MONTH_CHART_MOON = <?= $moonDataJson ?: 'null' ?>;
const MONTH_CHART_COMETS = <?= $monthChartCometsJson ?: '[]' ?>;
const MONTH_PHASE_EVENTS = <?= $moonPhaseEventsJson ?: '[]' ?>;
const MONTH_CHART_ERROR = <?= $starDataErrorJson ?: 'null' ?>;
const OBSERVATION_TIP_ICAL = <?= $observationTipCalendarIcsJson ?: '""' ?>;
const OBSERVATION_TIP_ICAL_FILENAME = <?= $observationTipCalendarFilenameJson ?: '"astrotools-monatsereignisse.ics"' ?>;
const MONTH_CHART_COMET_K = 0.01720209895;
const MONTH_CHART_COMET_OBLIQ = 23.439279444444445 * Math.PI / 180;
function monthChartCometSolveElliptic(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 monthChartCometSolveHyperbolic(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 monthChartCometSolveParabolic(deltaDays, q) {
const s = MONTH_CHART_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 monthChartCometNuR(deltaDays, q, e) {
const TOL = 1e-6;
if (e < 1 - TOL) {
const a = q / (1 - e);
const n = MONTH_CHART_COMET_K / Math.pow(a, 1.5);
const M = (n * deltaDays) % (2 * Math.PI);
const E = monthChartCometSolveElliptic(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 = MONTH_CHART_COMET_K * deltaDays / Math.pow(a, 1.5);
const F = monthChartCometSolveHyperbolic(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 = monthChartCometSolveParabolic(deltaDays, q);
return [2 * Math.atan(W), q * (1 + W * W)];
}
function monthChartCometRaDec(comet, date) {
if (!comet || !Number.isFinite(Number(comet.q)) || !Number.isFinite(Number(comet.e)) ||
!Number.isFinite(Number(comet.w)) || !Number.isFinite(Number(comet.Om)) ||
!Number.isFinite(Number(comet.inc)) || !Number.isFinite(Number(comet.Tp_y)) ||
!Number.isFinite(Number(comet.Tp_m)) || !Number.isFinite(Number(comet.Tp_d))) {
return null;
}
const dayFloor = Math.floor(Number(comet.Tp_d));
const fracMs = (Number(comet.Tp_d) - dayFloor) * 86400000;
const tpMs = Date.UTC(Number(comet.Tp_y), Number(comet.Tp_m) - 1, dayFloor) + fracMs;
const deltaDays = (date.getTime() - tpMs) / 86400000;
const [nu, r] = monthChartCometNuR(deltaDays, Number(comet.q), Number(comet.e));
const w = Number(comet.w) * Math.PI / 180;
const Om = Number(comet.Om) * Math.PI / 180;
const inc = Number(comet.inc) * Math.PI / 180;
const u = w + nu;
const cOs = Math.cos(Om);
const sOs = Math.sin(Om);
const cI = Math.cos(inc);
const sI = Math.sin(inc);
const cU = Math.cos(u);
const 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);
const cosEps = Math.cos(MONTH_CHART_COMET_OBLIQ);
const sinEps = Math.sin(MONTH_CHART_COMET_OBLIQ);
const xEq = xE;
const yEq = yE * cosEps - zE * sinEps;
const zEq = yE * sinEps + zE * cosEps;
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);
const rH = Math.sqrt(xEq * xEq + yEq * yEq + zEq * zEq);
let mag = null;
if (comet.H !== null && comet.G !== null && rH > 0 && dist > 0) {
mag = Number(comet.H) + 5 * Math.log10(dist) + 2.5 * Number(comet.G) * Math.log10(rH);
}
return { ra, dec, mag, rH };
}
(function () {
const toggleButton = document.getElementById('toggleObservationTipSql');
const copyButton = document.getElementById('copyObservationTipSql');
@@ -3423,6 +3809,81 @@ const OBSERVATION_TIP_ICAL_FILENAME = <?= $observationTipCalendarFilenameJson ?:
}
}
function drawComets(cx, cy, radius) {
if (!Array.isArray(MONTH_CHART_COMETS) || MONTH_CHART_COMETS.length === 0) {
return;
}
const sunEqu = Astronomy.Equator(Astronomy.Body.Sun, date, observer, true, true);
const sunHor = Astronomy.Horizon(date, observer, sunEqu.ra, sunEqu.dec, 'normal');
const sunPt = Number.isFinite(Number(sunHor.altitude)) && Number.isFinite(Number(sunHor.azimuth))
? project(Number(sunHor.altitude), Number(sunHor.azimuth), radius, cx, cy)
: null;
for (const comet of MONTH_CHART_COMETS) {
let pos = null;
try {
pos = monthChartCometRaDec(comet, date);
} catch (error) {
pos = null;
}
if (!pos) {
continue;
}
const hor = Astronomy.Horizon(date, observer, pos.ra, pos.dec, 'normal');
if (!hor || Number(hor.altitude) < 0) {
continue;
}
const pt = project(Number(hor.altitude), Number(hor.azimuth), radius, cx, cy);
if (!pt) {
continue;
}
let tailAngle = -Math.PI * 0.65;
if (sunPt) {
tailAngle = Math.atan2(pt.y - sunPt.y, pt.x - sunPt.x);
}
const tailLen = pos.rH > 0 ? Math.max(8, Math.min(28, 18 / pos.rH)) : 12;
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.82)');
grd.addColorStop(0.55, 'rgba(240, 217, 144, 0.34)');
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.2;
ctx.lineCap = 'round';
ctx.stroke();
const comaR = 4.2;
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, comaR * 2.6);
glow.addColorStop(0, 'rgba(255, 245, 200, 0.94)');
glow.addColorStop(0.45, 'rgba(201, 168, 76, 0.54)');
glow.addColorStop(1, 'rgba(201, 168, 76, 0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(pt.x, pt.y, comaR * 2.6, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 250, 220, 0.96)';
ctx.beginPath();
ctx.arc(pt.x, pt.y, comaR * 0.72, 0, Math.PI * 2);
ctx.fill();
const label = String(comet.name || '').trim();
if (label !== '') {
placePlanetLabel(label, pt.x, pt.y, comaR);
}
}
}
function drawMessier(cx, cy, radius) {
for (const obj of MONTH_CHART_MESSIER) {
if (Number(obj.mag) > Number(MONTH_CHART_CONFIG.messierLimit)) {
@@ -3591,6 +4052,7 @@ const OBSERVATION_TIP_ICAL_FILENAME = <?= $observationTipCalendarFilenameJson ?:
drawMessier(cx, cy, radius);
drawMoon(cx, cy, radius);
drawPlanets(cx, cy, radius);
drawComets(cx, cy, radius);
drawStars(cx, cy, radius);
statusEl.textContent = 'Sternkarte fuer ' + MONTH_CHART_CONFIG.localTimeLabel;
@@ -4088,6 +4550,81 @@ const OBSERVATION_TIP_ICAL_FILENAME = <?= $observationTipCalendarFilenameJson ?:
}
}
function drawComets(width, height) {
if (!Array.isArray(MONTH_CHART_COMETS) || MONTH_CHART_COMETS.length === 0) {
return;
}
const sunEqu = Astronomy.Equator(Astronomy.Body.Sun, date, observer, true, true);
const sunHor = Astronomy.Horizon(date, observer, sunEqu.ra, sunEqu.dec, 'normal');
const sunPt = Number.isFinite(Number(sunHor.altitude)) && Number.isFinite(Number(sunHor.azimuth))
? southProject(Number(sunHor.altitude), Number(sunHor.azimuth))
: null;
for (const comet of MONTH_CHART_COMETS) {
let pos = null;
try {
pos = monthChartCometRaDec(comet, date);
} catch (error) {
pos = null;
}
if (!pos) {
continue;
}
const hor = Astronomy.Horizon(date, observer, pos.ra, pos.dec, 'normal');
if (!hor || Number(hor.altitude) < 0) {
continue;
}
const pt = southProject(Number(hor.altitude), Number(hor.azimuth));
if (!pt) {
continue;
}
let tailAngle = -Math.PI * 0.65;
if (sunPt) {
tailAngle = Math.atan2(pt.y - sunPt.y, pt.x - sunPt.x);
}
const tailLen = pos.rH > 0 ? Math.max(8, Math.min(28, 18 / pos.rH)) : 12;
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.82)');
grd.addColorStop(0.55, 'rgba(240, 217, 144, 0.34)');
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.2;
ctx.lineCap = 'round';
ctx.stroke();
const comaR = 4.2;
const glow = ctx.createRadialGradient(pt.x, pt.y, 0, pt.x, pt.y, comaR * 2.6);
glow.addColorStop(0, 'rgba(255, 245, 200, 0.94)');
glow.addColorStop(0.45, 'rgba(201, 168, 76, 0.54)');
glow.addColorStop(1, 'rgba(201, 168, 76, 0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(pt.x, pt.y, comaR * 2.6, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 250, 220, 0.96)';
ctx.beginPath();
ctx.arc(pt.x, pt.y, comaR * 0.72, 0, Math.PI * 2);
ctx.fill();
const label = String(comet.name || '').trim();
if (label !== '') {
placePlanetLabel(label, pt.x, pt.y, comaR);
}
}
}
function drawBackground(width, height) {
const zenitPt = southProject(88, 180) || { x: width / 2, y: height * 0.18 };
const horizonPt = southProject(0, 180) || { x: width / 2, y: height * 0.88 };
@@ -4410,6 +4947,7 @@ const OBSERVATION_TIP_ICAL_FILENAME = <?= $observationTipCalendarFilenameJson ?:
drawMessier(width, height);
drawMoon(width, height);
drawPlanets(width, height);
drawComets(width, height);
drawStars(width, height);
statusEl.textContent = 'Südhimmel für ' + MONTH_CHART_CONFIG.localTimeLabel;
}