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;
}
Binary file not shown.
+345 -2
View File
@@ -278,6 +278,24 @@ def action_comet_brightnesses(args: list[str]) -> dict:
fail(str(exc), extra={"action": "comet_brightnesses"})
def parse_comet_payload_arg(payload_arg: str, action_name: str) -> list[dict]:
try:
if payload_arg.startswith("@"):
payload_path = payload_arg[1:]
with open(payload_path, "r", encoding="utf-8") as handle:
payload_text = handle.read()
else:
payload_text = base64.urlsafe_b64decode(payload_arg.encode("ascii")).decode("utf-8")
payload = json.loads(payload_text)
except Exception as exc:
fail(f"Payload fuer {action_name} ist ungueltig.", extra={"details": str(exc)})
if not isinstance(payload, list):
fail(f"Payload fuer {action_name} muss eine JSON-Liste sein.")
return [item for item in payload if isinstance(item, dict)]
def search_event(
body: astronomy.Body,
direction: astronomy.Direction,
@@ -824,6 +842,31 @@ def body_horizontal_coords(
return float(hor.altitude), float(hor.azimuth)
def comet_horizontal_coords(
comet: dict,
observer: astronomy.Observer,
dt_utc: datetime,
) -> dict | None:
brightness = comets.calculate_brightness(comet, dt_utc)
ra_hours = brightness.get("ra_hours")
dec_deg = brightness.get("dec_deg")
if ra_hours is None or dec_deg is None:
return None
time_value = dt_to_time(dt_utc)
hor = astronomy.Horizon(time_value, observer, float(ra_hours), float(dec_deg), astronomy.Refraction.Normal)
return {
"altitude_deg": float(hor.altitude),
"azimuth_deg": float(hor.azimuth),
"estimated_magnitude": brightness.get("estimated_magnitude"),
"heliocentric_distance_au": brightness.get("heliocentric_distance_au"),
"geocentric_distance_au": brightness.get("geocentric_distance_au"),
"ra_hours": float(ra_hours),
"dec_deg": float(dec_deg),
}
def body_constellation_info(
body: astronomy.Body,
dt_utc: datetime,
@@ -864,6 +907,301 @@ def serialize_body_position(
return payload
def action_favorite_comet_events_for_month(args: list[str]) -> dict:
if len(args) != 12:
fail(
"Aktion favorite_comet_events_for_month erwartet 12 Argumente: latitude longitude elevation year month timezone sample_offset_minutes min_comet_alt_deg max_sun_alt_deg max_comet_magnitude min_consecutive_days payloadBase64Oder@Datei",
extra={"argv": args},
)
latitude = parse_float(args[0], "Latitude")
longitude = parse_float(args[1], "Longitude")
elevation = parse_float(args[2], "Elevation")
try:
year = int(args[3])
month = int(args[4])
sample_offset_minutes = int(args[6])
min_consecutive_days = int(args[10])
except ValueError as exc:
fail("Jahr, Monat oder Kometenparameter sind ungueltig.", extra={"details": str(exc), "argv": args})
if month < 1 or month > 12:
fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month})
timezone_name = args[5]
min_comet_alt_deg = parse_float(args[7], "Minimale Kometenhoehe")
max_sun_alt_deg = parse_float(args[8], "Maximale Sonnenhoehe")
max_comet_magnitude = parse_float(args[9], "Maximale Kometenhelligkeit")
comet_payload = parse_comet_payload_arg(args[11], "favorite_comet_events_for_month")
try:
tz = ZoneInfo(timezone_name)
except Exception as exc:
fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)})
observer = astronomy.Observer(latitude, longitude, elevation)
local_start = datetime(year, month, 1, 0, 0, 0, tzinfo=tz)
if month == 12:
local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz)
else:
local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz)
utc_start = local_start.astimezone(timezone.utc)
utc_end = local_end.astimezone(timezone.utc)
utc_start_padded = utc_start - timedelta(days=1)
utc_end_padded = utc_end + timedelta(days=1)
def comet_name(comet: dict) -> str:
designation = str(comet.get("designation_and_name") or "").strip()
if designation != "":
return designation
packed = str(comet.get("designation_packed") or "").strip()
if packed != "":
return packed
comet_id = comet.get("id")
return f"Komet {comet_id}" if comet_id is not None else "Komet"
def append_perihelion_event(events: list[dict], comet: dict, name: str) -> None:
perihelion_utc = comets.build_perihelion_datetime(comet)
if perihelion_utc is None:
return
local_dt = perihelion_utc.astimezone(tz)
if not (local_start <= local_dt < local_end):
return
events.append({
"kind": "perihelion",
"comet_id": comets.parse_int(comet.get("id")),
"comet_name": name,
"label": f"Perihel von {name}",
"utc_iso": perihelion_utc.isoformat().replace("+00:00", "Z"),
"local_iso": local_dt.isoformat(),
"local_date": local_dt.strftime("%d.%m.%Y"),
"local_time": local_dt.strftime("%H:%M"),
})
def append_geocentric_minimum_event(events: list[dict], comet: dict, name: str) -> None:
sample_step = timedelta(hours=2)
sample_times: list[datetime] = []
sample_distances: list[float] = []
current_utc = utc_start_padded
while current_utc <= utc_end_padded:
brightness = comets.calculate_brightness(comet, current_utc)
distance_au = brightness.get("geocentric_distance_au")
if isinstance(distance_au, (int, float)):
sample_times.append(current_utc)
sample_distances.append(float(distance_au))
current_utc += sample_step
candidate_index = None
candidate_distance = None
for index in range(1, len(sample_distances) - 1):
current_distance = sample_distances[index]
if current_distance > sample_distances[index - 1] or current_distance > sample_distances[index + 1]:
continue
candidate_utc = sample_times[index]
if not (utc_start <= candidate_utc < utc_end):
continue
if candidate_distance is None or current_distance < candidate_distance:
candidate_index = index
candidate_distance = current_distance
if candidate_index is None:
return
rough_utc = sample_times[candidate_index]
best_utc = rough_utc
best_distance = candidate_distance if candidate_distance is not None else sample_distances[candidate_index]
refine_start = rough_utc - sample_step
refine_end = rough_utc + sample_step
refine_step = timedelta(minutes=5)
current_utc = refine_start
while current_utc <= refine_end:
brightness = comets.calculate_brightness(comet, current_utc)
distance_au = brightness.get("geocentric_distance_au")
if isinstance(distance_au, (int, float)) and float(distance_au) < best_distance:
best_distance = float(distance_au)
best_utc = current_utc
current_utc += refine_step
if not (utc_start <= best_utc < utc_end):
return
local_dt = best_utc.astimezone(tz)
events.append({
"kind": "geocentric_minimum",
"comet_id": comets.parse_int(comet.get("id")),
"comet_name": name,
"label": f"{name} in Erdnähe ({best_distance:.3f} AE)",
"distance_au": best_distance,
"utc_iso": best_utc.isoformat().replace("+00:00", "Z"),
"local_iso": local_dt.isoformat(),
"local_date": local_dt.strftime("%d.%m.%Y"),
"local_time": local_dt.strftime("%H:%M"),
})
def append_visibility_events(events: list[dict], comet: dict, name: str) -> None:
daily_checks: list[dict] = []
current_local = local_start
while current_local < local_end:
day_start_utc = current_local.astimezone(timezone.utc)
next_day_utc = (current_local + timedelta(days=1)).astimezone(timezone.utc)
start_time = dt_to_time(day_start_utc)
sunrise = search_event(astronomy.Body.Sun, astronomy.Direction.Rise, observer, start_time, next_day_utc)
sunset = search_event(astronomy.Body.Sun, astronomy.Direction.Set, observer, start_time, next_day_utc)
def build_period(sample_local: datetime | None) -> dict:
if sample_local is None:
return {
"ok": False,
"sample_local": None,
"comet_alt_deg": None,
"sun_alt_deg": None,
"estimated_magnitude": None,
}
sample_utc = sample_local.astimezone(timezone.utc)
comet_coords = comet_horizontal_coords(comet, observer, sample_utc)
if comet_coords is None:
return {
"ok": False,
"sample_local": sample_local,
"comet_alt_deg": None,
"sun_alt_deg": None,
"estimated_magnitude": None,
}
comet_alt_deg = float(comet_coords["altitude_deg"])
sun_alt_deg = body_altitude_deg(astronomy.Body.Sun, observer, sample_utc)
estimated_magnitude = comet_coords.get("estimated_magnitude")
is_ok = (
comet_alt_deg >= min_comet_alt_deg
and sun_alt_deg <= max_sun_alt_deg
and isinstance(estimated_magnitude, (int, float))
and float(estimated_magnitude) <= max_comet_magnitude
)
return {
"ok": is_ok,
"sample_local": sample_local,
"comet_alt_deg": comet_alt_deg,
"sun_alt_deg": sun_alt_deg,
"estimated_magnitude": float(estimated_magnitude) if isinstance(estimated_magnitude, (int, float)) else None,
}
morning_local = None
if sunrise is not None:
sunrise_local = time_to_datetime(sunrise).astimezone(tz)
morning_local = sunrise_local - timedelta(minutes=sample_offset_minutes)
evening_local = None
if sunset is not None:
sunset_local = time_to_datetime(sunset).astimezone(tz)
evening_local = sunset_local + timedelta(minutes=sample_offset_minutes)
daily_checks.append({
"date": current_local.strftime("%d.%m.%Y"),
"morning": build_period(morning_local),
"evening": build_period(evening_local),
})
current_local += timedelta(days=1)
def build_windows(period_key: str, label_text: str) -> list[dict]:
comet_events = []
start_index = None
for index, day in enumerate(daily_checks):
period = day[period_key]
is_ok = bool(period["ok"])
if is_ok and start_index is None:
start_index = index
is_last = index == len(daily_checks) - 1
if start_index is not None and (not is_ok or is_last):
end_index = index if (is_ok and is_last) else index - 1
duration_days = end_index - start_index + 1
if duration_days >= min_consecutive_days:
window_days = daily_checks[start_index:end_index + 1]
start_day = window_days[0]
end_day = window_days[-1]
start_local = start_day[period_key]["sample_local"]
if start_local is not None:
range_text = (
start_day["date"]
if start_index == end_index
else f'{start_day["date"]} bis {end_day["date"]}'
)
best_magnitude = None
for window_day in window_days:
magnitude = window_day[period_key]["estimated_magnitude"]
if magnitude is None:
continue
best_magnitude = magnitude if best_magnitude is None else min(best_magnitude, magnitude)
label = f"Gute Sichtbarkeit von {name} am {label_text} ({range_text})"
if best_magnitude is not None:
label += f" bis ca. {best_magnitude:.1f} mag"
end_local = end_day[period_key]["sample_local"] or start_local
comet_events.append({
"kind": "good_visibility",
"period": period_key,
"comet_id": comets.parse_int(comet.get("id")),
"comet_name": name,
"label": label,
"best_magnitude": best_magnitude,
"utc_iso": start_local.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"),
"local_iso": start_local.isoformat(),
"local_date": start_local.strftime("%d.%m.%Y"),
"local_time": start_local.strftime("%H:%M"),
"end_local_iso": end_local.isoformat(),
"end_local_date": end_day["date"],
"end_local_time": end_local.strftime("%H:%M"),
"duration_minutes": max(1, int(round((end_local - start_local).total_seconds() / 60.0))),
"duration_days": duration_days,
})
start_index = None
return comet_events
events.extend(build_windows("morning", "Morgen"))
events.extend(build_windows("evening", "Abend"))
events: list[dict] = []
for comet in comet_payload:
name = comet_name(comet)
append_perihelion_event(events, comet, name)
append_geocentric_minimum_event(events, comet, name)
append_visibility_events(events, comet, name)
events.sort(key=lambda item: item["local_iso"])
return {
"ok": True,
"action": "favorite_comet_events_for_month",
"selected": {
"year": year,
"month": month,
"timezone": timezone_name,
"sample_offset_minutes": sample_offset_minutes,
"min_comet_alt_deg": min_comet_alt_deg,
"max_sun_alt_deg": max_sun_alt_deg,
"max_comet_magnitude": max_comet_magnitude,
"min_consecutive_days": min_consecutive_days,
"comet_count": len(comet_payload),
},
"events": events,
}
def action_mercury_good_visibility_for_month(args: list[str]) -> dict:
if len(args) != 10:
fail(
@@ -4554,7 +4892,7 @@ def action_satellite_passes(args: list[str]) -> dict:
def main() -> None:
if len(sys.argv) < 2:
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})
action = sys.argv[1]
args = sys.argv[2:]
@@ -4584,6 +4922,11 @@ def main() -> None:
print(json.dumps(result, ensure_ascii=True))
return
if action == "favorite_comet_events_for_month":
result = action_favorite_comet_events_for_month(args)
print(json.dumps(result, ensure_ascii=True))
return
if action == "moon_star_occultations":
result = action_moon_star_occultations(args)
print(json.dumps(result, ensure_ascii=True))
@@ -4714,7 +5057,7 @@ def main() -> None:
print(json.dumps(result, ensure_ascii=True))
return
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_visibility_chart", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "favorite_comet_events_for_month", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_visibility_chart", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})
if __name__ == "__main__":