'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April', 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', ]; $monthSkyTimes = [ 1 => ['hour' => 22, 'minute' => 0], 2 => ['hour' => 22, 'minute' => 0], 3 => ['hour' => 22, 'minute' => 0], 4 => ['hour' => 23, 'minute' => 0], 5 => ['hour' => 23, 'minute' => 0], 6 => ['hour' => 23, 'minute' => 30], 7 => ['hour' => 23, 'minute' => 30], 8 => ['hour' => 23, 'minute' => 0], 9 => ['hour' => 23, 'minute' => 0], 10 => ['hour' => 23, 'minute' => 0], 11 => ['hour' => 22, 'minute' => 0], 12 => ['hour' => 22, 'minute' => 0], ]; $starData = []; $lineStarData = []; $constellationLines = []; $messierData = []; $planetData = []; $moonData = null; $moonPhaseEvents = []; $goldenHandleEvents = []; $moonPlanetApproaches = []; $moonDeepSkyApproaches = []; $planetBrightStarApproaches = []; $mercuryGoodVisibilityEvents = []; $planetParadeEvents = []; $planetConstellationChangeEvents = []; $planetConjunctions = []; $eclipseEvents = []; $moonApsisEvents = []; $sunApsisEvents = []; $innerPlanetElongationEvents = []; $venusPeakMagnitudeEvents = []; $outerPlanetEvents = []; $outerPlanetStationEvents = []; $jupiterMoonSideEvents = []; $moonOccultations = []; $meteorShowerPeaks = []; $favoriteSatellitePasses = []; $favoriteSatelliteName = null; $favoriteSatellitePayload = []; $favoriteCometPayload = []; $favoriteCometEvents = []; $seasonChanges = []; $timeChanges = []; $monthEventList = []; $starDataError = null; if (!function_exists('buildMonthOccultationStarLabel')) { function buildMonthOccultationStarLabel(array $row): string { $proper = trim((string) ($row['proper'] ?? '')); if ($proper !== '') { return $proper; } $bayer = trim((string) ($row['bayer'] ?? '')); $con = trim((string) ($row['con'] ?? '')); if ($bayer !== '') { return $con !== '' ? $bayer . ' ' . $con : $bayer; } $hip = isset($row['hip']) ? (int) $row['hip'] : 0; return $hip > 0 ? 'HIP ' . $hip : 'Unbenannter Stern'; } } if (!function_exists('normalizeMonthForecastDegree')) { function normalizeMonthForecastDegree(float $value): float { $normalized = fmod($value, 360.0); if ($normalized < 0.0) { $normalized += 360.0; } return $normalized; } } if (!function_exists('monthForecastSolarLongitude')) { function monthForecastSolarLongitude(DateTimeImmutable $dateTime): float { $julianDay = ((float) $dateTime->format('U')) / 86400.0 + 2440587.5; $daysSinceJ2000 = $julianDay - 2451545.0; $meanLongitude = normalizeMonthForecastDegree(280.46 + 0.9856474 * $daysSinceJ2000); $meanAnomalyRad = deg2rad(normalizeMonthForecastDegree(357.528 + 0.9856003 * $daysSinceJ2000)); return normalizeMonthForecastDegree( $meanLongitude + 1.915 * sin($meanAnomalyRad) + 0.020 * sin(2.0 * $meanAnomalyRad) ); } } if (!function_exists('monthForecastForwardDegreeDistance')) { function monthForecastForwardDegreeDistance(float $from, float $to): float { return normalizeMonthForecastDegree($to - $from); } } 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 { if ($declinationDeg === null) { return false; } return abs($latitudeDeg - $declinationDeg) < 90.0; } } if (!function_exists('monthForecastTleChecksum')) { function monthForecastTleChecksum(string $line): int { $sum = 0; for ($i = 0; $i < 68; $i++) { $c = $line[$i] ?? ' '; if (ctype_digit($c)) { $sum += (int) $c; } elseif ($c === '-') { $sum += 1; } } return $sum % 10; } } if (!function_exists('monthForecastTleExp')) { function monthForecastTleExp(float $value): string { if (abs($value) < 1e-99) { return ' 00000-0'; } $sign = $value >= 0 ? '+' : '-'; $abs = abs($value); $exp = (int) floor(log10($abs)) + 1; $mantissa = (int) round($abs / pow(10, $exp) * 1e5); if ($mantissa >= 100000) { $mantissa = 10000; $exp++; } $expSign = $exp >= 0 ? '+' : '-'; return sprintf('%s%05d%s%d', $sign, $mantissa, $expSign, abs($exp)); } } if (!function_exists('monthForecastTleMmDot')) { function monthForecastTleMmDot(float $value): string { $sign = $value >= 0 ? ' ' : '-'; return sprintf('%s.%08d', $sign, (int) round(abs($value) * 1e8)); } } if (!function_exists('monthForecastTleEpoch')) { function monthForecastTleEpoch(string $epochStr): string { $dt = new DateTimeImmutable($epochStr, new DateTimeZone('UTC')); $yy = (int) $dt->format('Y') % 100; $jan1 = new DateTimeImmutable($dt->format('Y') . '-01-01 00:00:00', new DateTimeZone('UTC')); $secs = (float) $dt->format('U') - (float) $jan1->format('U'); $day = $secs / 86400.0 + 1.0; return sprintf('%02d%012.8f', $yy, $day); } } if (!function_exists('monthForecastTleIntlDesig')) { function monthForecastTleIntlDesig(?string $objectId): string { if (!$objectId) { return str_repeat(' ', 8); } if (preg_match('/^(\d{4})-(\d+)([A-Z]*)$/i', trim($objectId), $match)) { $yy = substr($match[1], 2, 2); $launch = str_pad($match[2], 3, '0', STR_PAD_LEFT); $piece = str_pad(strtoupper($match[3]), 3, ' ', STR_PAD_RIGHT); return substr(str_pad($yy . $launch . $piece, 8, ' '), 0, 8); } return str_pad(substr($objectId, 0, 8), 8); } } if (!function_exists('monthForecastBuildTleLine1')) { function monthForecastBuildTleLine1(array $sat, array $tle): string { $norad = (int) ($sat['norad_cat_id'] ?? 0); $class = in_array($sat['classification_type'] ?? '', ['U', 'C', 'S'], true) ? (string) $sat['classification_type'] : 'U'; $line = sprintf( '1 %05d%1s %-8s %14s %10s %8s %8s %1d %4d', $norad, $class, monthForecastTleIntlDesig($sat['object_id'] ?? null), monthForecastTleEpoch((string) ($tle['epoch'] ?? '')), monthForecastTleMmDot((float) ($tle['mean_motion_dot'] ?? 0)), monthForecastTleExp((float) ($tle['mean_motion_ddot'] ?? 0)), monthForecastTleExp((float) ($tle['bstar'] ?? 0)), (int) ($tle['ephemeris_type'] ?? 0), (int) ($tle['element_set_no'] ?? 0) ); $line = str_pad($line, 68); return $line . monthForecastTleChecksum($line); } } if (!function_exists('monthForecastBuildTleLine2')) { function monthForecastBuildTleLine2(array $sat, array $tle): string { $norad = (int) ($sat['norad_cat_id'] ?? 0); $ecc = (float) ($tle['eccentricity'] ?? 0); $eccStr = substr(sprintf('%.7f', $ecc), 2); $line = sprintf( '2 %05d %8.4f %8.4f %7s %8.4f %8.4f %11.8f%5d', $norad, (float) ($tle['inclination'] ?? 0), (float) ($tle['ra_of_asc_node'] ?? 0), $eccStr, (float) ($tle['arg_of_pericenter'] ?? 0), (float) ($tle['mean_anomaly'] ?? 0), (float) ($tle['mean_motion'] ?? 0), (int) ($tle['rev_at_epoch'] ?? 0) ); $line = str_pad($line, 68); return $line . monthForecastTleChecksum($line); } } if (!function_exists('findMonthForecastSolarLongitudeCrossing')) { function findMonthForecastSolarLongitudeCrossing( DateTimeImmutable $rangeStartLocal, DateTimeImmutable $rangeEndLocal, float $targetLongitude, DateTimeZone $displayTimezone ): ?DateTimeImmutable { $utc = new DateTimeZone('UTC'); $rangeStartUtc = $rangeStartLocal->setTimezone($utc); $rangeEndUtc = $rangeEndLocal->setTimezone($utc); $startLongitude = monthForecastSolarLongitude($rangeStartUtc); $endLongitude = monthForecastSolarLongitude($rangeEndUtc); $targetDelta = monthForecastForwardDegreeDistance($startLongitude, $targetLongitude); $rangeDelta = monthForecastForwardDegreeDistance($startLongitude, $endLongitude); if ($targetDelta > $rangeDelta + 0.0001) { return null; } $low = (int) $rangeStartUtc->format('U'); $high = (int) $rangeEndUtc->format('U'); for ($i = 0; $i < 40; $i++) { $mid = (int) floor(($low + $high) / 2); $midLongitude = monthForecastSolarLongitude(new DateTimeImmutable('@' . $mid)); $midDelta = monthForecastForwardDegreeDistance($startLongitude, $midLongitude); if ($midDelta < $targetDelta) { $low = $mid; } else { $high = $mid; } } return (new DateTimeImmutable('@' . $high))->setTimezone($displayTimezone); } } if (!function_exists('buildObservationTipInsertSql')) { function buildObservationTipInsertSql(array $events, DateTimeZone $timezone): string { $rows = []; foreach ($events as $event) { if (!is_array($event)) { continue; } $tip = trim((string) ($event['event'] ?? '')); if ($tip === '') { continue; } $moment = null; $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { $moment = new DateTimeImmutable($localIso); } catch (Throwable $e) { $moment = null; } } if (!$moment instanceof DateTimeImmutable) { $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date !== '' && $time !== '') { $moment = DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!$moment instanceof DateTimeImmutable) { continue; } $rows[] = sprintf( "('%s', '%s')", $moment->setTimezone($timezone)->format('Y-m-d H:i:s'), str_replace("'", "''", $tip) ); } if ($rows === []) { return ''; } return "INSERT INTO `astro_sonobs_observationtips` (`moment`, `tip`)\nVALUES\n " . implode(",\n ", $rows) . ";\n"; } } if (!function_exists('escapeIcalText')) { function escapeIcalText(string $value): string { return str_replace( ["\\", ";", ",", "\r\n", "\n", "\r"], ["\\\\", "\\;", "\\,", "\\n", "\\n", "\\n"], $value ); } } if (!function_exists('foldIcalLine')) { function foldIcalLine(string $line): string { $chunks = preg_split('/(.{1,73})/u', $line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); if ($chunks === false || $chunks === []) { return $line; } $firstChunk = array_shift($chunks); return $firstChunk . ($chunks ? "\r\n " . implode("\r\n ", $chunks) : ''); } } if (!function_exists('buildObservationTipCalendarIcs')) { function buildObservationTipCalendarIcs(array $events, DateTimeZone $timezone): string { $lines = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//AstroTools//Monatsvorhersage//DE', 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-WR-CALNAME:AstroTools Monatsereignisse', ]; $createdUtc = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Ymd\THis\Z'); foreach ($events as $index => $event) { if (!is_array($event)) { continue; } $summary = trim((string) ($event['event'] ?? '')); if ($summary === '') { continue; } $moment = null; $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { $moment = new DateTimeImmutable($localIso); } catch (Throwable $e) { $moment = null; } } if (!$moment instanceof DateTimeImmutable) { $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date !== '' && $time !== '') { $moment = DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!$moment instanceof DateTimeImmutable) { continue; } $startUtc = $moment->setTimezone(new DateTimeZone('UTC')); $endMoment = null; $endLocalIso = trim((string) ($event['end_local_iso'] ?? '')); if ($endLocalIso !== '') { try { $endMoment = new DateTimeImmutable($endLocalIso); } catch (Throwable $e) { $endMoment = null; } } if (!$endMoment instanceof DateTimeImmutable) { $durationMinutes = isset($event['duration_minutes']) ? max(1, (int) $event['duration_minutes']) : 15; $endMoment = $moment->modify('+' . $durationMinutes . ' minutes'); } $endUtc = $endMoment->setTimezone(new DateTimeZone('UTC')); $uidBase = sha1($startUtc->format(DateTimeInterface::ATOM) . '|' . $summary . '|' . (string) $index); $eventLines = [ 'BEGIN:VEVENT', 'UID:' . $uidBase . '@astrotools.local', 'DTSTAMP:' . $createdUtc, 'DTSTART:' . $startUtc->format('Ymd\THis\Z'), 'DTEND:' . $endUtc->format('Ymd\THis\Z'), 'SUMMARY:' . escapeIcalText($summary), 'DESCRIPTION:' . escapeIcalText($summary), 'END:VEVENT', ]; foreach ($eventLines as $eventLine) { $lines[] = foldIcalLine($eventLine); } } $lines[] = 'END:VCALENDAR'; return implode("\r\n", $lines) . "\r\n"; } } if (!function_exists('monthForecastBuildNarrativeText')) { function monthForecastBuildNarrativeText(array $monthEventList, int $monthNumber, string $monthName, int $year): string { $filteredEvents = array_values(array_filter( $monthEventList, static fn (array $item): bool => (string) ($item['type'] ?? '') !== 'favorite_satellite_pass' )); $skyIntroByMonth = [ 1 => "Im Januar fällt in Südrichtung besonders das bekannteste Wintersternbild auf: der Orion. Seine sieben Hauptsterne sind auffallend hell, und in ihrer Anordnung lässt sich mit etwas Vorstellungskraft gut die Gestalt des Himmelsjägers erkennen. Unterhalb der drei Gürtelsterne kann man schon mit einem Fernglas den blass schimmernden Orionnebel entdecken. Der Stern Rigel bildet dabei die rechte untere Ecke des Orion.\n\nRigel ist zugleich Teil einer großen Sternfigur, des Wintersechsecks. Dabei handelt es sich nicht um ein eigenes Sternbild, sondern um eine auffällige Formation aus sechs hellen Sternen verschiedener Sternbilder. Im Uhrzeigersinn folgen auf Rigel zunächst Sirius im Großen Hund, dann Prokyon im Kleinen Hund, Castor in den Zwillingen, Capella im Fuhrmann und schließlich Aldebaran im Stier. Auch die Wintermilchstraße zieht durch diesen Himmelsbereich. Sie verläuft durch die Sternbilder Großer Hund, Einhorn, Zwillinge, Stier, Fuhrmann, Perseus und Kassiopeia, wirkt jedoch deutlich schwächer als im Sommer, da unser Blick nun eher zum Rand unserer Galaxis gerichtet ist.", 2 => "In den Abendstunden des Februar zeigt sich der Sternenhimmel besonders eindrucksvoll. Die Wintersternbilder rund um den Orion prägen nun den Himmel und sind für ihre vielen hellen Sterne bekannt. Um 22 Uhr, auf diese Zeit bezieht sich die Sternkarte, haben sie den Meridian bereits überschritten. Unterhalb des Orion funkelt Sirius im Großen Hund, der hellste Stern des Nachthimmels. Oberhalb des Orion stehen rechts der Stier mit seinem Hauptstern Aldebaran, noch höher der Fuhrmann mit der gelblich leuchtenden Capella und links die Zwillinge mit Castor und Pollux. Unterhalb der Zwillinge fällt zudem Prokyon auf, der Hauptstern des Kleinen Hundes.\n\nAm Osthimmel kündigen sich bereits die Frühlingssternbilder an. Der Löwe mit seinem Hauptstern Regulus ist schon vollständig aufgegangen. Unter ihm leuchtet mit Alphard ein auffälliger Einzelstern in der Wasserschlange. Die Jungfrau folgt erst später in der Nacht und steigt nach Mitternacht vollständig über den Horizont.", 3 => "Im März zeigt der Sternenhimmel den Übergang vom Winter zum Frühling. Am Abend sind noch viele markante Wintersternbilder zu sehen, darunter Orion, Großer Hund, Kleiner Hund, Zwillinge, Fuhrmann und Stier. Im Lauf der Nacht sinken sie jedoch immer weiter nach Westen, während im Osten und Süden die Frühlingssternbilder aufsteigen. Gegen Mitternacht prägen dann vor allem die Sternbilder rund um den Löwen den Himmel.\n\nZwischen den Zwillingen und dem Löwen liegt das Tierkreissternbild Krebs. Es ist eher unscheinbar und deshalb am Himmel nicht leicht zu erkennen. Bei dunklem, klarem Himmel lässt sich in seiner Mitte jedoch ein schwacher, milchiger Lichtfleck entdecken: der offene Sternhaufen Praesepe, auch „Krippe“ genannt. Hoch über unseren Köpfen steht jetzt der Große Bär. Seine sieben hellsten Sterne bilden die bekannte Figur des Großen Wagens. Im Südosten geht Spica auf, der hellste Stern im Sternbild Jungfrau. Weiter nördlich, im Osten bis Nordosten, fällt der orange leuchtende Arktur im Sternbild Bärenhüter auf. Tief über dem Südhorizont erscheint bereits der vordere Teil des ausgedehnten Sternbilds Wasserschlange. Bei weniger klarem Himmel ist davon oft nur der hellste Stern Alphard zu erkennen, dessen Name passend „der Einsame“ bedeutet. Auch das kleine Sternbild Rabe steht nun knapp über dem Horizont. Obwohl seine Sterne nicht besonders hell sind, ist es wegen seiner kompakten Form vergleichsweise gut auszumachen.", 4 => "Im April prägen die Frühlingssternbilder den Sternenhimmel. Genau im Süden steht nun das Sternbild Löwe, dessen Figur mit etwas Vorstellungskraft tatsächlich an eine liegende Raubkatze erinnert. In südöstlicher Richtung fallen zwei helle Sterne besonders auf: Spica, der hellste Stern im Sternbild Jungfrau, und höher am Himmel der orange leuchtende Arktur im Sternbild Bärenhüter. Gemeinsam mit Regulus im Löwen bilden sie das sogenannte Frühlingsdreieck.\n\nDer Große Wagen, ein markanter Teil des Sternbilds Großer Bär, steht jetzt hoch über uns und erscheint nahezu senkrecht aufgerichtet. Zwischen ihm und dem Löwen liegen nur die eher unauffälligen Sternbilder Kleiner Löwe, Jagdhunde und Haar der Berenike. Ähnlich sternarm wirkt auch der Bereich zwischen dem Löwen und dem Horizont: Dort zieht sich das ausgedehnte, aber lichtschwache Sternbild Wasserschlange entlang. Der Sextant ist nur schwer zu erkennen; meist lassen sich in diesem Himmelsabschnitt vor allem Becher und Rabe ausmachen, da ihre Sternfiguren vergleichsweise markant sind.", 5 => "Im Mai steht am frühen Abend das Sternbild Jungfrau über dem Südhorizont. Sein hellster Stern ist Spica. Etwas höher am Himmel leuchtet Arktur, der auffällig orange Hauptstern im Sternbild Bärenhüter. In südwestlicher Richtung ist noch Regulus im Löwen zu sehen. Diese drei hellen Sterne – Spica, Arktur und Regulus – bilden zusammen das sogenannte Frühlingsdreieck. Oberhalb davon liegt das eher unauffällige Sternbild Haar der Berenike.\n\nZum Südosthorizont hin kündigt sich bereits der Übergang vom Frühlings- zum Sommerhimmel an. Dort geht das Sternbild Skorpion auf, zunächst allerdings noch sehr tief über dem Horizont. Wer den Sternenhimmel im Mai am Abend beobachtet, kann im Westen noch einige helle Sterne der Wintersternbilder erkennen; sie sinken jedoch bald unter den Horizont. Im Osten steigen dagegen schon die ersten Sommersternbilder empor. Besonders auffällig ist dort Wega, der helle Hauptstern des Sternbilds Leier. Zwischen Wega und Arktur liegen das lichtschwächere Sternbild Herkules und das markante Halbrund der Nördlichen Krone.", 6 => "Im Juni kündigt sich der Sommerhimmel bereits deutlich an. Tief über dem südöstlichen Horizont fällt der rötlich leuchtende Antares im Sternbild Skorpion auf. Da er in unseren Breiten nie besonders hoch steigt, bleibt er stets ein Objekt in Horizontnähe. Hoch über unseren Köpfen steht dagegen der Große Wagen, der auffälligste Teil des Sternbilds Großer Bär. Seine Deichsel dient zugleich als praktischer Wegweiser am Himmel: Verlängert man ihren geschwungenen Bogen, gelangt man zunächst zu Arktur, dem hellen Hauptstern des Sternbilds Bärenhüter, und weiter zu Spica in der Jungfrau. Noch weiter westlich steht der Löwe bereits tief über dem südwestlichen Horizont und nähert sich langsam seinem Untergang.\n\nLinks oberhalb von Arktur ist ein kleines, aber sehr charakteristisches Sternbild zu erkennen: die Nördliche Krone. Ihre Sterne bilden einen zarten Halbkreis, der sich auch für ungeübte Beobachter gut auffinden lässt. Ihr hellster Stern heißt Gemma, also „Edelstein“ – ein passender Name für dieses auffällige Himmelsbild. Unterhalb davon breiten sich der Schlangenträger und die aus zwei Teilen bestehende Schlange aus. Beide Sternbilder sind jedoch recht lichtschwach und daher vor allem bei klarem, dunklem Himmel gut zu verfolgen. Besonders eindrucksvoll wirkt der Himmel nun in östlicher Richtung. Dort sind bereits die ersten markanten Sommersternbilder aufgegangen. Vor allem das Sommerdreieck zieht die Aufmerksamkeit auf sich. Es wird von drei hellen Sternen gebildet: Wega in der Leier, Deneb im Schwan und Altair im Adler. Diese drei Sterne gehören zu den bekanntesten Orientierungspunkten des Sommerhimmels und begleiten uns in den kommenden Monaten durch die hellen Nächte.", 7 => "Im Juli sind die südlichen Sternbilder Skorpion und Schütze besonders gut zu beobachten. Von Deutschland aus bleiben sie allerdings stets tief über dem Horizont. Deutlich günstiger sind die Bedingungen weiter südlich, etwa im Mittelmeerraum: Dort steigen beide Sternbilder merklich höher und zeigen sich wesentlich eindrucksvoller.\n\nOberhalb von Skorpion und Schütze liegen die eher lichtschwachen Sternbilder Schlangenträger und Schlange; noch darüber befindet sich Herkules. Zwar sind diese Sternbilder weniger auffällig, sie gehören aber zu den charakteristischen Bereichen des Sommerhimmels. Besonders ins Auge fallen in dieser Jahreszeit die hellen Sterne Arktur im Bärenhüter, Wega in der Leier, Deneb im Schwan und Altair im Adler. Auch die Milchstraße zieht sich nun als helles Band quer über den Himmel. Im Juli sind die Nächte in unseren Breiten jedoch noch nicht vollständig dunkel, sodass sie oft nur schwach oder gar nicht zu erkennen ist. Für ihre Beobachtung sind die Bedingungen meist erst im August günstiger. Der Große Bär ist auf der Sternkarte nur noch teilweise zu sehen. Er steht inzwischen in nordwestlicher Richtung und sinkt im Lauf der Nacht immer tiefer zum Horizont.", 8 => "Im August zeigt sich der Sommersternhimmel von seiner schönsten Seite. Wenn es nach 22 Uhr langsam dunkel wird, treten nach und nach die ersten helleren Sterne hervor. Gegen 23 Uhr bietet sich dann ein besonders eindrucksvoller Anblick: Hoch über unseren Köpfen steht Wega im Sternbild Leier, einer der auffälligsten Sterne des Sommerhimmels. Zusammen mit Deneb im Sternbild Schwan und Altair im Adler bildet sie das sogenannte Sommerdreieck, eine der markantesten Sternfiguren dieser Jahreszeit. Deneb steht dabei links von Wega, während Altair deutlich tiefer in Richtung Horizont zu finden ist. Diese drei hellen Sterne sind auch für ungeübte Beobachter leicht auszumachen und eignen sich gut zur Orientierung am Nachthimmel.\n\nMit den dunkler werdenden Nächten wird nun auch die Milchstraße wieder besser sichtbar. Sie zieht sich als zartes, helles Band quer über den Himmel und verläuft mitten durch den Bereich des Sommerdreiecks. Besonders fernab künstlicher Lichtquellen lässt sich ihr schimmernder Verlauf gut erkennen. Während der Blick noch von den Sommersternbildern beherrscht wird, kündigt sich im Osten schon die nächste Jahreszeit an. Dort steigen mit Pegasus und Andromeda bereits die ersten Herbststernbilder über den Horizont. Um den 12. August sind zudem besonders viele Sternschnuppen zu sehen. Dann erreicht der Meteorstrom der Perseiden sein Maximum. Diese Sternschnuppen entstehen, wenn kleine Staubteilchen des Kometen Swift-Tuttle in die Erdatmosphäre eindringen und dort verglühen.", 9 => "Im September wird es nach den hellen Sommernächten wieder deutlich früher dunkel. Schon gegen 21.30 Uhr sind die meisten Sternbilder gut zu erkennen. Hoch über unseren Köpfen fällt zunächst der helle Stern Wega auf. Wer später, wenn es ganz dunkel geworden ist, noch einmal in diese Himmelsregion blickt, kann auch das kleine rautenförmige Sternbild Leier erkennen, dessen hellster Stern Wega ist. Wer diese Sternfigur nicht ausmacht, hat möglicherweise stattdessen Altair im Adler oder Deneb im Schwan im Blick. Diese drei hellen Sterne bilden gemeinsam das große Sommerdreieck.\n\nMitten im Sommerdreieck und etwas links davon liegen die kleinen Sternbilder Pfeil und Delfin. Zwar bestehen sie nicht aus besonders hellen Sternen, doch ihre kompakten und einprägsamen Formen machen sie vergleichsweise leicht auffindbar. Nun wird auch die Milchstraße wieder gut sichtbar: Als fahles, schimmerndes Band zieht sie sich hoch über den Himmel. Sowohl im Westen, wo die Frühsommersternbilder allmählich tiefer sinken, als auch im Osten, wo die Herbststernbilder aufsteigen, zeigt sich der Himmel vergleichsweise arm an auffällig hellen Sternen.", 10 => "Im Oktober sind die Nächte bereits wieder länger als die Tage. Der Grund dafür ist, dass die Sonne Ende September den Himmelsäquator nach Süden überschritten hat und nun auf ihrer scheinbaren Jahresbahn weiter südlich steht. Am frühen Abend erinnert der Sternenhimmel noch an den Sommer. Gut zu erkennen ist das Sommerdreieck, das von Wega in der Leier, Deneb im Schwan und Altair im Adler gebildet wird.\n\nIm Lauf des Abends rücken dann immer mehr die Herbststernbilder in den Mittelpunkt. Gegen 23 Uhr Sommerzeit steht hoch am Himmel das große Herbstviereck. Drei seiner Sterne gehören zum Sternbild Pegasus, während die linke obere Ecke bereits zur Sternkette der Andromeda zählt. Diese markante Figur ist eines der bekanntesten Himmelsbilder des Herbstes. Unterhalb von Pegasus und Andromeda liegen die Tierkreissternbilder Steinbock, Wassermann und Fische. Sie bestehen allerdings überwiegend aus eher schwachen Sternen und sind deshalb nur an einem dunklen Beobachtungsort gut zu erkennen. In diesem Himmelsbereich fällt vor allem Diphda im Sternbild Walfisch auf. Sehr tief über dem Südhorizont kann man außerdem Fomalhaut im Südlichen Fisch sehen, einen der wenigen auffällig hellen Sterne dieser Himmelsgegend.", 11 => "Im November prägen vor allem die Sternbilder des Herbsthimmels das Bild. Besonders auffällig ist das große Herbstviereck, das meist auch Pegasus-Quadrat genannt wird. Drei seiner Sterne gehören zum Sternbild Pegasus, während die linke obere Ecke bereits zur Andromeda zählt. In diesem Sternbild liegt auch die Andromedagalaxie, das fernste Objekt, das unter guten Bedingungen mit bloßem Auge zu erkennen ist. An einem dunklen, klaren Himmel erscheint sie als schwacher, nebliger Lichtfleck.\n\nUnterhalb von Pegasus und Andromeda liegen vor allem eher lichtschwache Sternbilder: im Südwesten der Wassermann, im Süden der Walfisch und darüber die Fische. Im Walfisch befindet sich zudem Mira, einer der bekanntesten veränderlichen Sterne des Himmels. Ihre Helligkeit schwankt so stark, dass sie zeitweise gut zu sehen ist und dann wieder fast verschwindet. Im Osten kündigt sich dagegen schon der Winterhimmel an, dessen Sternbilder deutlich auffälliger sind. Hoch über uns steht die Kassiopeia mit ihrer markanten W-Form. Weiter südöstlich folgen Perseus mit dem bekannten veränderlichen Stern Algol und der Stier. In diesem Sternbild fallen besonders der Sternhaufen der Plejaden und der rötlich leuchtende Aldebaran auf. Über dem östlichen Horizont ist nun auch Orion erschienen, eines der markantesten Sternbilder des Winterhimmels mit seinen auffallend hellen Sternen.", 12 => "Im Dezember wird es schon früh dunkel, und so verabschieden sich die Sommersternbilder Schwan, Leier und Adler am Abend allmählich vom Himmel. Stattdessen zeigt sich nun bereits die ganze Pracht des Winterhimmels. Orion, Stier, Zwillinge und Fuhrmann sind schon gut aufgegangen und prägen den Abendhimmel. Selbst Sirius, der hellste Stern des Nachthimmels, erscheint bereits über dem Südosthorizont. Zum Jahreswechsel gibt es dabei einen besonderen Orientierungspunkt: In der Nacht vom 31. Dezember auf den 1. Januar erreicht Sirius gegen Mitternacht seine höchste Stellung am Himmel und steht dann von Deutschland aus nahezu genau im Süden.\n\nIm Süden und Südwesten halten sich noch einige Sternbilder des Herbsthimmels. Unterhalb des Herbstvierecks – des großen Quadrats aus drei Sternen des Pegasus und einem Stern der Andromeda – liegen die ausgedehnten, aber eher lichtschwachen Sternbilder Wassermann, Fische und Walfisch. Auch der Eridanus, das Sternbild zwischen Walfisch und Orion, besteht überwiegend aus wenig auffälligen Sternen. In einer klaren Nacht lohnt es sich jedoch, seinen gewundenen Verlauf am Himmel einmal genauer zu verfolgen.", ]; foreach ($skyIntroByMonth as $introMonthNumber => $introText) { $monthLabel = $monthName; if ($introMonthNumber !== $monthNumber) { $monthLabel = [ 1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April', 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', ][$introMonthNumber] ?? $monthName; } $skyIntroByMonth[$introMonthNumber] = [ $introText, $monthLabel, $monthLabel, ]; } $paragraphs = []; $monthIntroVariants = $skyIntroByMonth[$monthNumber] ?? [($monthName . ' ' . $year . ' bietet einen abwechslungsreichen Himmel zwischen den jahreszeitlichen Sternbildern.')]; $monthIntroIndex = (($year * 100) + $monthNumber) % count($monthIntroVariants); $paragraphs[] = $monthIntroVariants[$monthIntroIndex]; $findEvents = static function (array $events, callable $filter, int $limit = 3): array { $matches = []; foreach ($events as $event) { if ($filter($event)) { $matches[] = $event; if (count($matches) >= $limit) { break; } } } return $matches; }; $describeEvents = static function (array $events): string { $parts = []; foreach ($events as $event) { $label = trim((string) ($event['event'] ?? '')); $date = trim((string) ($event['date'] ?? '')); if ($label === '' || $date === '') { continue; } $parts[] = $label . ' am ' . $date; } 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'] ?? '')); if ($label === '' || $date === '') { return null; } return match ($label) { 'Fruehlingsanfang' => 'Der astronomische Frühling beginnt am ' . $date . '. Dann überschreitet die Sonne den Himmelsäquator nach Norden; Tag und Nacht sind zu diesem Zeitpunkt annähernd gleich lang.', 'Sommeranfang' => 'Der astronomische Sommer beginnt am ' . $date . '. Dann erreicht die Sonne ihren nördlichsten Punkt auf der scheinbaren Jahresbahn und steht senkrecht über dem Wendekreis des Krebses.', 'Herbstanfang' => 'Der astronomische Herbst beginnt am ' . $date . '. Dann überschreitet die Sonne den Himmelsäquator wieder nach Süden; Tag und Nacht sind dabei annähernd gleich lang.', 'Winteranfang' => 'Der astronomische Winter beginnt am ' . $date . '. Dann erreicht die Sonne ihren südlichsten Punkt auf der scheinbaren Jahresbahn und steht senkrecht über dem Wendekreis des Steinbocks.', default => null, }; }; $seasonEvents = $findEvents( $filteredEvents, static fn (array $event): bool => (string) ($event['type'] ?? '') === 'season_change', 1 ); if ($seasonEvents !== []) { $seasonNarrative = $buildSeasonNarrative($seasonEvents[0]); if ($seasonNarrative !== null) { $paragraphs[] = $seasonNarrative; } } $moonEvents = $findEvents( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['moon_phase', 'golden_handle', 'moon_planet_approach', 'moon_deep_sky_approach', 'moon_occultation', 'moon_apsis'], true), 4 ); if ($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 auffällige 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 auffällig sind ' . $describeEvents($moonEvents) . '.'; } $planetObservationEvents = $findEvents( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['inner_planet_elongation', 'venus_peak_magnitude', 'outer_planet_event', 'planet_conjunction', 'planet_parade', 'mercury_good_visibility'], true), 5 ); if ($planetObservationEvents !== []) { $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 prägen 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( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['outer_planet_station', 'planet_constellation_change'], true), 4 ); if ($planetMotionEvents !== []) { $planetMotionIntro = $pickRandomText([ 'Im Monatsverlauf zeigen sich bei den Planeten auch auffällige Positionswechsel.', 'Auch in ihrer Stellung am Himmel verändern 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 gehören ' . $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 können sich in diesem Monat besonders für', ], '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( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['eclipse', 'lunar_eclipse', 'solar_eclipse'], true), 3 ); if ($eclipseEvents !== []) { $eclipseIntro = $pickRandomText([ 'Besondere Aufmerksamkeit verdienen außerdem die Finsternistermine.', 'Auch die Finsternisse setzen in diesem Monat markante Akzente.', 'Ein weiterer Höhepunkt können in diesem Monat die Finsternistermine sein.', 'Nicht zuletzt lohnt sich in diesem Monat auch ein Blick auf mögliche 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' && ( (int) ($event['zhr_min_estimate'] ?? 0) >= 20 || (int) ($event['zhr_max_estimate'] ?? 0) >= 30 ), 2 ); if ($meteorEvents !== []) { $meteorIntro = $pickRandomText([ 'Freunde von Meteoren sollten zudem die Aktivität der Meteorströme im Blick behalten.', 'Auch die Meteorströme verdienen in diesem Monat Aufmerksamkeit.', 'Wer nach Sternschnuppen Ausschau halten möchte, sollte auch die Meteorströme 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 Meteorströmen lohnt in diesem Monat vor allem ein Blick auf', 'Unter den Meteorströmen verdienen in diesem Monat besonders', 'Auch bei den Meteorströmen fallen in diesem Monat vor allem', 'Wer Meteorströme verfolgen möchte, 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); $highlightIntro = $pickRandomText([ 'Zu den auffälligen Terminen des Monats gehören', '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); } } try { $dbConfig = require __DIR__ . '/../config/database.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=%s', $dbConfig['host'], $dbConfig['dbname'], $dbConfig['charset'] ?? 'utf8mb4' ); $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['pass'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); $starStmt = $pdo->query( "SELECT hip, proper, bayer, con, spect, mag, ci, rarad, decrad FROM star_hipparcos WHERE mag IS NOT NULL AND rarad IS NOT NULL AND decrad IS NOT NULL AND mag <= " . (float) $starDatabaseMagnitudeLimit . " ORDER BY mag ASC" ); while ($row = $starStmt->fetch()) { $starData[] = [ 'hip' => $row['hip'] !== null ? (int) $row['hip'] : null, 'proper' => trim((string) ($row['proper'] ?? '')), 'bayer' => trim((string) ($row['bayer'] ?? '')), 'con' => trim((string) ($row['con'] ?? '')), 'spect' => trim((string) ($row['spect'] ?? '')), 'ra' => (float) $row['rarad'], 'dec' => (float) $row['decrad'], 'mag' => (float) $row['mag'], 'ci' => $row['ci'] !== null ? (float) $row['ci'] : null, ]; } $constellationIndex = []; $ensureConstellation = static function (string $id) use (&$constellationIndex): void { if (!isset($constellationIndex[$id])) { $constellationIndex[$id] = [ 'id' => $id, 'lines' => [], 'label_positions' => [], 'common_name' => [ 'german' => null, 'native' => null, 'byname' => null, ], ]; } }; $nameStmt = $pdo->query( "SELECT constellation_id, german_name, native_name, byname FROM constellation_names ORDER BY constellation_id" ); foreach ($nameStmt as $row) { $constellationId = (string) $row['constellation_id']; $ensureConstellation($constellationId); $constellationIndex[$constellationId]['common_name'] = [ 'german' => $row['german_name'] !== null ? (string) $row['german_name'] : null, 'native' => $row['native_name'] !== null ? (string) $row['native_name'] : null, 'byname' => $row['byname'] !== null ? (string) $row['byname'] : null, ]; } $labelStmt = $pdo->query( "SELECT constellation_id, position_index, ra_hours, dec_degrees FROM constellation_label_positions ORDER BY constellation_id, position_index" ); foreach ($labelStmt as $row) { $constellationId = (string) $row['constellation_id']; $ensureConstellation($constellationId); $constellationIndex[$constellationId]['label_positions'][] = [ (float) $row['ra_hours'], (float) $row['dec_degrees'], ]; } $lineStmt = $pdo->query( "SELECT cl.constellation_id, cl.line_index, clp.point_index, clp.hip_number FROM constellation_lines cl INNER JOIN constellation_line_points clp ON clp.line_id = cl.id ORDER BY cl.constellation_id, cl.line_index, clp.point_index" ); foreach ($lineStmt as $row) { $constellationId = (string) $row['constellation_id']; $lineIndex = (int) $row['line_index']; $ensureConstellation($constellationId); $constellationIndex[$constellationId]['lines'][$lineIndex] ??= []; $constellationIndex[$constellationId]['lines'][$lineIndex][] = (int) $row['hip_number']; } foreach ($constellationIndex as &$constellation) { if ($constellation['lines']) { ksort($constellation['lines']); $constellation['lines'] = array_values($constellation['lines']); } } unset($constellation); $constellationLines = array_values($constellationIndex); $lineStarStmt = $pdo->query( "SELECT DISTINCT sh.hip, sh.proper, sh.bayer, sh.con, sh.spect, sh.mag, sh.ci, sh.rarad, sh.decrad FROM constellation_line_points clp INNER JOIN star_hipparcos sh ON sh.hip = clp.hip_number WHERE sh.rarad IS NOT NULL AND sh.decrad IS NOT NULL" ); while ($row = $lineStarStmt->fetch()) { $lineStarData[] = [ 'hip' => $row['hip'] !== null ? (int) $row['hip'] : null, 'proper' => trim((string) ($row['proper'] ?? '')), 'bayer' => trim((string) ($row['bayer'] ?? '')), 'con' => trim((string) ($row['con'] ?? '')), 'spect' => trim((string) ($row['spect'] ?? '')), 'ra' => (float) $row['rarad'], 'dec' => (float) $row['decrad'], 'mag' => $row['mag'] !== null ? (float) $row['mag'] : 99.0, 'ci' => $row['ci'] !== null ? (float) $row['ci'] : null, ]; } $messierStmt = $pdo->query( "SELECT messier, name, object_type_abrev, ra_deg, dec_deg, v_mag, major_axis FROM dso_objects WHERE messier IS NOT NULL AND messier <> '' AND ra_deg IS NOT NULL AND dec_deg IS NOT NULL AND v_mag IS NOT NULL AND v_mag <= " . (float) $messierDatabaseMagnitudeLimit . " ORDER BY v_mag ASC, CAST(REGEXP_REPLACE(messier, '[^0-9]', '') AS UNSIGNED)" ); while ($row = $messierStmt->fetch()) { if (!preg_match('/(\d+)/', (string) $row['messier'], $match)) { continue; } $messierData[] = [ 'm' => (int) $match[1], 'name' => trim((string) ($row['name'] ?? '')), 'type' => trim((string) ($row['object_type_abrev'] ?? 'As')), 'ra' => ((float) $row['ra_deg']) / 15.0, 'dec' => (float) $row['dec_deg'], 'mag' => (float) $row['v_mag'], 'size' => $row['major_axis'] !== null ? (float) $row['major_axis'] : 8.0, ]; } $meteorStmt = $pdo->query( "SELECT designation, shower_code, radiant_delta_deg, peak_sol, zhr, zhr_variable FROM meteorshowers WHERE peak_sol IS NOT NULL ORDER BY peak_sol ASC, designation ASC" ); while ($row = $meteorStmt->fetch()) { $meteorShowerPeaks[] = [ 'designation' => trim((string) ($row['designation'] ?? '')), 'shower_code' => trim((string) ($row['shower_code'] ?? '')), 'radiant_delta_deg' => $row['radiant_delta_deg'] !== null ? (float) $row['radiant_delta_deg'] : null, 'peak_sol' => (float) $row['peak_sol'], 'zhr' => $row['zhr'] !== null ? (int) $row['zhr'] : null, 'zhr_variable' => trim((string) ($row['zhr_variable'] ?? '')), ]; } 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, t.epoch, t.mean_motion, t.eccentricity, t.inclination, t.ra_of_asc_node, t.arg_of_pericenter, t.mean_anomaly, t.ephemeris_type, t.element_set_no, t.rev_at_epoch, t.bstar, t.mean_motion_dot, t.mean_motion_ddot FROM app_user_satellites us INNER JOIN sat_satellites s ON s.id = us.satellite_id INNER JOIN sat_tle_current t ON t.satellite_id = s.id WHERE us.user_id = :user_id AND us.is_favorite = 1 AND t.epoch IS NOT NULL AND t.mean_motion IS NOT NULL ORDER BY s.object_name ASC LIMIT 1" ); $favoriteSatelliteStmt->execute([ ':user_id' => (int) $_SESSION['user_id'], ]); $favoriteSatelliteRow = $favoriteSatelliteStmt->fetch(); if (is_array($favoriteSatelliteRow)) { $favoriteSatelliteName = trim((string) ($favoriteSatelliteRow['object_name'] ?? '')); $favoriteSatellitePayload = [[ 'id' => (int) ($favoriteSatelliteRow['id'] ?? 0), 'norad_cat_id' => (int) ($favoriteSatelliteRow['norad_cat_id'] ?? 0), 'object_name' => $favoriteSatelliteName, 'is_favorite' => true, 'std_mag' => $favoriteSatelliteRow['std_mag'] !== null ? (float) $favoriteSatelliteRow['std_mag'] : null, 'rcs_size' => $favoriteSatelliteRow['rcs_size'] !== null ? (string) $favoriteSatelliteRow['rcs_size'] : null, 'rcs' => $favoriteSatelliteRow['rcs'] !== null && (float) $favoriteSatelliteRow['rcs'] > 0 ? (float) $favoriteSatelliteRow['rcs'] : null, 'tle_line1' => monthForecastBuildTleLine1($favoriteSatelliteRow, $favoriteSatelliteRow), 'tle_line2' => monthForecastBuildTleLine2($favoriteSatelliteRow, $favoriteSatelliteRow), ]]; } } } catch (Throwable $e) { $starDataError = $e->getMessage(); } require __DIR__ . '/header.php'; $chartLocationName = (string) (($currentLocation['name'] ?? null) ?: 'Sternwarte Sonneberg'); $chartLatitude = isset($currentLocation['latitude']) ? (float) $currentLocation['latitude'] : 50.3547; $chartLongitude = isset($currentLocation['longitude']) ? (float) $currentLocation['longitude'] : 11.1900; $chartElevation = isset($currentLocation['elevation']) && $currentLocation['elevation'] !== null ? (float) $currentLocation['elevation'] : 640.0; $chartTimezone = (string) (($currentLocation['timezone'] ?? null) ?: 'Europe/Berlin'); $monthTime = $monthSkyTimes[$selectedMonth]; $chartLocalTime = new DateTimeImmutable( sprintf('%04d-%02d-15 %02d:%02d:00', $selectedYear, $selectedMonth, $monthTime['hour'], $monthTime['minute']), new DateTimeZone($chartTimezone) ); $chartUtcTime = $chartLocalTime->setTimezone(new DateTimeZone('UTC')); $monthStartLocal = new DateTimeImmutable( sprintf('%04d-%02d-01 00:00:00', $selectedYear, $selectedMonth), new DateTimeZone($chartTimezone) ); $monthEndLocal = $monthStartLocal->modify('first day of next month'); $pythonExecutable = PHP_OS_FAMILY === 'Windows' ? 'python' : '/usr/bin/python'; $pythonScriptPath = __DIR__ . '/py/api.py'; $pythonOutput = []; $pythonExitCode = 0; $planetErrorText = null; $pythonArgs = [ $pythonScriptPath, 'month_sky_context', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, (string) $monthTime['hour'], (string) $monthTime['minute'], $chartTimezone, ]; $pythonCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $pythonArgs)) . ' 2>&1'; exec($pythonCommand, $pythonOutput, $pythonExitCode); $pythonRaw = trim(implode("\n", $pythonOutput)); $pythonDecoded = json_decode($pythonRaw, true); if (is_array($pythonDecoded) && ($pythonDecoded['ok'] ?? false)) { if (is_array($pythonDecoded['bodies']['moon'] ?? null)) { $moon = $pythonDecoded['bodies']['moon']; $moonData = [ 'label' => (string) ($moon['label'] ?? 'Mond'), 'az' => isset($moon['azimuth_deg']) ? (float) $moon['azimuth_deg'] : null, 'alt' => isset($moon['altitude_deg']) ? (float) $moon['altitude_deg'] : null, 'visible' => !empty($moon['visible']), 'phaseFraction' => isset($moon['phase_fraction']) ? (float) $moon['phase_fraction'] : null, 'waxing' => isset($moon['waxing']) ? (bool) $moon['waxing'] : null, ]; } foreach (($pythonDecoded['bodies']['planets'] ?? []) as $planet) { if (!is_array($planet)) { continue; } $planetData[] = [ 'key' => (string) ($planet['key'] ?? ''), 'label' => (string) ($planet['label'] ?? ''), 'az' => isset($planet['azimuth_deg']) ? (float) $planet['azimuth_deg'] : null, 'alt' => isset($planet['altitude_deg']) ? (float) $planet['altitude_deg'] : null, 'mag' => isset($planet['magnitude']) ? (float) $planet['magnitude'] : null, 'visible' => !empty($planet['visible']), ]; } } elseif ($pythonRaw !== '') { $planetErrorText = $pythonRaw; } if (!empty($favoriteSatellitePayload)) { $favoriteSatellitePassOutput = []; $favoriteSatellitePassArgs = [ $pythonScriptPath, 'satellite_passes', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, $chartTimezone, $monthStartLocal->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s\Z'), $monthEndLocal->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s\Z'), base64_encode((string) json_encode($favoriteSatellitePayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)), ]; $favoriteSatellitePassCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $favoriteSatellitePassArgs)) . ' 2>&1'; exec($favoriteSatellitePassCommand, $favoriteSatellitePassOutput); $favoriteSatellitePassRaw = trim(implode("\n", $favoriteSatellitePassOutput)); $favoriteSatellitePassDecoded = json_decode($favoriteSatellitePassRaw, true); if (is_array($favoriteSatellitePassDecoded) && ($favoriteSatellitePassDecoded['ok'] ?? false)) { $favoriteSatelliteResults = is_array($favoriteSatellitePassDecoded['results'] ?? null) ? $favoriteSatellitePassDecoded['results'] : []; $favoriteSatellitePasses = is_array($favoriteSatelliteResults[0]['passes'] ?? null) ? $favoriteSatelliteResults[0]['passes'] : []; } } 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 = [ $pythonScriptPath, 'moon_phases_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $moonPhaseCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonPhaseArgs)) . ' 2>&1'; exec($moonPhaseCommand, $moonPhaseOutput, $moonPhaseExitCode); $moonPhaseRaw = trim(implode("\n", $moonPhaseOutput)); $moonPhaseDecoded = json_decode($moonPhaseRaw, true); if (is_array($moonPhaseDecoded) && ($moonPhaseDecoded['ok'] ?? false)) { $moonPhaseEvents = is_array($moonPhaseDecoded['phases'] ?? null) ? $moonPhaseDecoded['phases'] : []; } $goldenHandleOutput = []; $goldenHandleExitCode = 0; $goldenHandleArgs = [ $pythonScriptPath, 'golden_handle_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $goldenHandleCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $goldenHandleArgs)) . ' 2>&1'; exec($goldenHandleCommand, $goldenHandleOutput, $goldenHandleExitCode); $goldenHandleRaw = trim(implode("\n", $goldenHandleOutput)); $goldenHandleDecoded = json_decode($goldenHandleRaw, true); if (is_array($goldenHandleDecoded) && ($goldenHandleDecoded['ok'] ?? false)) { $goldenHandleEvents = is_array($goldenHandleDecoded['events'] ?? null) ? $goldenHandleDecoded['events'] : []; } $seasonOutput = []; $seasonExitCode = 0; $seasonArgs = [ $pythonScriptPath, 'season_changes_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $seasonCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $seasonArgs)) . ' 2>&1'; exec($seasonCommand, $seasonOutput, $seasonExitCode); $seasonRaw = trim(implode("\n", $seasonOutput)); $seasonDecoded = json_decode($seasonRaw, true); if (is_array($seasonDecoded) && ($seasonDecoded['ok'] ?? false)) { $seasonChanges = is_array($seasonDecoded['changes'] ?? null) ? $seasonDecoded['changes'] : []; } $timeChangeOutput = []; $timeChangeExitCode = 0; $timeChangeArgs = [ $pythonScriptPath, 'time_changes_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $timeChangeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $timeChangeArgs)) . ' 2>&1'; exec($timeChangeCommand, $timeChangeOutput, $timeChangeExitCode); $timeChangeRaw = trim(implode("\n", $timeChangeOutput)); $timeChangeDecoded = json_decode($timeChangeRaw, true); if (is_array($timeChangeDecoded) && ($timeChangeDecoded['ok'] ?? false)) { $timeChanges = is_array($timeChangeDecoded['events'] ?? null) ? $timeChangeDecoded['events'] : []; } $eclipseOutput = []; $eclipseExitCode = 0; $eclipseArgs = [ $pythonScriptPath, 'eclipses_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $eclipseCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $eclipseArgs)) . ' 2>&1'; exec($eclipseCommand, $eclipseOutput, $eclipseExitCode); $eclipseRaw = trim(implode("\n", $eclipseOutput)); $eclipseDecoded = json_decode($eclipseRaw, true); if (is_array($eclipseDecoded) && ($eclipseDecoded['ok'] ?? false)) { $eclipseEvents = is_array($eclipseDecoded['events'] ?? null) ? $eclipseDecoded['events'] : []; } $moonApsisOutput = []; $moonApsisExitCode = 0; $moonApsisArgs = [ $pythonScriptPath, 'moon_apsides_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $moonApsisCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonApsisArgs)) . ' 2>&1'; exec($moonApsisCommand, $moonApsisOutput, $moonApsisExitCode); $moonApsisRaw = trim(implode("\n", $moonApsisOutput)); $moonApsisDecoded = json_decode($moonApsisRaw, true); if (is_array($moonApsisDecoded) && ($moonApsisDecoded['ok'] ?? false)) { $moonApsisEvents = is_array($moonApsisDecoded['events'] ?? null) ? $moonApsisDecoded['events'] : []; } $sunApsisOutput = []; $sunApsisExitCode = 0; $sunApsisArgs = [ $pythonScriptPath, 'sun_apsides_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $sunApsisCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $sunApsisArgs)) . ' 2>&1'; exec($sunApsisCommand, $sunApsisOutput, $sunApsisExitCode); $sunApsisRaw = trim(implode("\n", $sunApsisOutput)); $sunApsisDecoded = json_decode($sunApsisRaw, true); if (is_array($sunApsisDecoded) && ($sunApsisDecoded['ok'] ?? false)) { $sunApsisEvents = is_array($sunApsisDecoded['events'] ?? null) ? $sunApsisDecoded['events'] : []; } $elongationOutput = []; $elongationExitCode = 0; $elongationArgs = [ $pythonScriptPath, 'inner_planet_elongations_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $elongationCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $elongationArgs)) . ' 2>&1'; exec($elongationCommand, $elongationOutput, $elongationExitCode); $elongationRaw = trim(implode("\n", $elongationOutput)); $elongationDecoded = json_decode($elongationRaw, true); if (is_array($elongationDecoded) && ($elongationDecoded['ok'] ?? false)) { $innerPlanetElongationEvents = is_array($elongationDecoded['events'] ?? null) ? $elongationDecoded['events'] : []; } $venusPeakMagnitudeOutput = []; $venusPeakMagnitudeExitCode = 0; $venusPeakMagnitudeArgs = [ $pythonScriptPath, 'venus_peak_magnitude_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $venusPeakMagnitudeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $venusPeakMagnitudeArgs)) . ' 2>&1'; exec($venusPeakMagnitudeCommand, $venusPeakMagnitudeOutput, $venusPeakMagnitudeExitCode); $venusPeakMagnitudeRaw = trim(implode("\n", $venusPeakMagnitudeOutput)); $venusPeakMagnitudeDecoded = json_decode($venusPeakMagnitudeRaw, true); if (is_array($venusPeakMagnitudeDecoded) && ($venusPeakMagnitudeDecoded['ok'] ?? false)) { $venusPeakMagnitudeEvents = is_array($venusPeakMagnitudeDecoded['events'] ?? null) ? $venusPeakMagnitudeDecoded['events'] : []; } $outerPlanetOutput = []; $outerPlanetExitCode = 0; $outerPlanetArgs = [ $pythonScriptPath, 'outer_planet_events_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $outerPlanetCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $outerPlanetArgs)) . ' 2>&1'; exec($outerPlanetCommand, $outerPlanetOutput, $outerPlanetExitCode); $outerPlanetRaw = trim(implode("\n", $outerPlanetOutput)); $outerPlanetDecoded = json_decode($outerPlanetRaw, true); if (is_array($outerPlanetDecoded) && ($outerPlanetDecoded['ok'] ?? false)) { $outerPlanetEvents = is_array($outerPlanetDecoded['events'] ?? null) ? $outerPlanetDecoded['events'] : []; } $outerPlanetStationOutput = []; $outerPlanetStationExitCode = 0; $outerPlanetStationArgs = [ $pythonScriptPath, 'outer_planet_stations_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $outerPlanetStationCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $outerPlanetStationArgs)) . ' 2>&1'; exec($outerPlanetStationCommand, $outerPlanetStationOutput, $outerPlanetStationExitCode); $outerPlanetStationRaw = trim(implode("\n", $outerPlanetStationOutput)); $outerPlanetStationDecoded = json_decode($outerPlanetStationRaw, true); if (is_array($outerPlanetStationDecoded) && ($outerPlanetStationDecoded['ok'] ?? false)) { $outerPlanetStationEvents = is_array($outerPlanetStationDecoded['events'] ?? null) ? $outerPlanetStationDecoded['events'] : []; } $jupiterMoonSideOutput = []; $jupiterMoonSideExitCode = 0; $jupiterMoonSideArgs = [ $pythonScriptPath, 'jupiter_moons_one_side_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $jupiterMoonSideCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $jupiterMoonSideArgs)) . ' 2>&1'; exec($jupiterMoonSideCommand, $jupiterMoonSideOutput, $jupiterMoonSideExitCode); $jupiterMoonSideRaw = trim(implode("\n", $jupiterMoonSideOutput)); $jupiterMoonSideDecoded = json_decode($jupiterMoonSideRaw, true); if (is_array($jupiterMoonSideDecoded) && ($jupiterMoonSideDecoded['ok'] ?? false)) { $jupiterMoonSideEvents = is_array($jupiterMoonSideDecoded['events'] ?? null) ? $jupiterMoonSideDecoded['events'] : []; } $moonPlanetOutput = []; $moonPlanetExitCode = 0; $moonPlanetArgs = [ $pythonScriptPath, 'moon_planet_approaches', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $moonPlanetMaxSeparationDeg, ]; $moonPlanetCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonPlanetArgs)) . ' 2>&1'; exec($moonPlanetCommand, $moonPlanetOutput, $moonPlanetExitCode); $moonPlanetRaw = trim(implode("\n", $moonPlanetOutput)); $moonPlanetDecoded = json_decode($moonPlanetRaw, true); if (is_array($moonPlanetDecoded) && ($moonPlanetDecoded['ok'] ?? false)) { $moonPlanetApproaches = is_array($moonPlanetDecoded['approaches'] ?? null) ? $moonPlanetDecoded['approaches'] : []; } $moonDeepSkyOutput = []; $moonDeepSkyExitCode = 0; $moonDeepSkyArgs = [ $pythonScriptPath, 'moon_deep_sky_approaches_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $moonDeepSkyMaxSeparationDeg, ]; $moonDeepSkyCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonDeepSkyArgs)) . ' 2>&1'; exec($moonDeepSkyCommand, $moonDeepSkyOutput, $moonDeepSkyExitCode); $moonDeepSkyRaw = trim(implode("\n", $moonDeepSkyOutput)); $moonDeepSkyDecoded = json_decode($moonDeepSkyRaw, true); if (is_array($moonDeepSkyDecoded) && ($moonDeepSkyDecoded['ok'] ?? false)) { $moonDeepSkyApproaches = is_array($moonDeepSkyDecoded['approaches'] ?? null) ? $moonDeepSkyDecoded['approaches'] : []; } $planetBrightStarOutput = []; $planetBrightStarExitCode = 0; $planetBrightStarArgs = [ $pythonScriptPath, 'planet_bright_star_approaches_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $planetBrightStarMaxSeparationDeg, ]; $planetBrightStarCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetBrightStarArgs)) . ' 2>&1'; exec($planetBrightStarCommand, $planetBrightStarOutput, $planetBrightStarExitCode); $planetBrightStarRaw = trim(implode("\n", $planetBrightStarOutput)); $planetBrightStarDecoded = json_decode($planetBrightStarRaw, true); if (is_array($planetBrightStarDecoded) && ($planetBrightStarDecoded['ok'] ?? false)) { $planetBrightStarApproaches = is_array($planetBrightStarDecoded['approaches'] ?? null) ? $planetBrightStarDecoded['approaches'] : []; } $planetConjunctionOutput = []; $planetConjunctionExitCode = 0; $planetConjunctionArgs = [ $pythonScriptPath, 'planet_conjunctions_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $planetConjunctionMaxSeparationDeg, ]; $planetConjunctionCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetConjunctionArgs)) . ' 2>&1'; exec($planetConjunctionCommand, $planetConjunctionOutput, $planetConjunctionExitCode); $planetConjunctionRaw = trim(implode("\n", $planetConjunctionOutput)); $planetConjunctionDecoded = json_decode($planetConjunctionRaw, true); if (is_array($planetConjunctionDecoded) && ($planetConjunctionDecoded['ok'] ?? false)) { $planetConjunctions = is_array($planetConjunctionDecoded['conjunctions'] ?? null) ? $planetConjunctionDecoded['conjunctions'] : []; } $mercuryGoodVisibilityOutput = []; $mercuryGoodVisibilityExitCode = 0; $mercuryGoodVisibilityArgs = [ $pythonScriptPath, 'mercury_good_visibility_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $mercuryGoodVisibilitySampleOffsetMinutes, (string) $mercuryGoodVisibilityMinAltitudeDeg, (string) $mercuryGoodVisibilityMaxSunAltitudeDeg, (string) $mercuryGoodVisibilityMinConsecutiveDays, ]; $mercuryGoodVisibilityCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $mercuryGoodVisibilityArgs)) . ' 2>&1'; exec($mercuryGoodVisibilityCommand, $mercuryGoodVisibilityOutput, $mercuryGoodVisibilityExitCode); $mercuryGoodVisibilityRaw = trim(implode("\n", $mercuryGoodVisibilityOutput)); $mercuryGoodVisibilityDecoded = json_decode($mercuryGoodVisibilityRaw, true); if (is_array($mercuryGoodVisibilityDecoded) && ($mercuryGoodVisibilityDecoded['ok'] ?? false)) { $mercuryGoodVisibilityEvents = is_array($mercuryGoodVisibilityDecoded['events'] ?? null) ? $mercuryGoodVisibilityDecoded['events'] : []; } $planetParadeOutput = []; $planetParadeExitCode = 0; $planetParadeArgs = [ $pythonScriptPath, 'planet_parades_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, (string) $planetParadeSampleOffsetMinutes, (string) $planetParadeMinAltitudeDeg, (string) $planetParadeMaxSunAltitudeDeg, (string) $planetParadeMaxAzimuthSpanDeg, (string) $planetParadeMinPlanetCount, (string) $planetParadeMinConsecutiveDays, ]; $planetParadeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetParadeArgs)) . ' 2>&1'; exec($planetParadeCommand, $planetParadeOutput, $planetParadeExitCode); $planetParadeRaw = trim(implode("\n", $planetParadeOutput)); $planetParadeDecoded = json_decode($planetParadeRaw, true); if (is_array($planetParadeDecoded) && ($planetParadeDecoded['ok'] ?? false)) { $planetParadeEvents = is_array($planetParadeDecoded['events'] ?? null) ? $planetParadeDecoded['events'] : []; } $planetConstellationChangeOutput = []; $planetConstellationChangeExitCode = 0; $planetConstellationChangeArgs = [ $pythonScriptPath, 'planet_constellation_changes_for_month', (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $planetConstellationChangeCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetConstellationChangeArgs)) . ' 2>&1'; exec($planetConstellationChangeCommand, $planetConstellationChangeOutput, $planetConstellationChangeExitCode); $planetConstellationChangeRaw = trim(implode("\n", $planetConstellationChangeOutput)); $planetConstellationChangeDecoded = json_decode($planetConstellationChangeRaw, true); if (is_array($planetConstellationChangeDecoded) && ($planetConstellationChangeDecoded['ok'] ?? false)) { $planetConstellationChangeEvents = is_array($planetConstellationChangeDecoded['events'] ?? null) ? $planetConstellationChangeDecoded['events'] : []; } $moonOccultationStars = []; try { if (isset($pdo)) { $stmtOccultStars = $pdo->prepare( "SELECT hip, proper, bayer, con, mag, rarad, decrad, dist FROM star_hipparcos WHERE rarad IS NOT NULL AND decrad IS NOT NULL AND mag IS NOT NULL AND mag <= :max_mag ORDER BY mag ASC, proper ASC, bayer ASC, hip ASC" ); $stmtOccultStars->execute([':max_mag' => $moonOccultationMaxMag]); foreach ($stmtOccultStars->fetchAll() as $row) { $moonOccultationStars[] = [ 'hip' => isset($row['hip']) ? (int) $row['hip'] : 0, 'label' => buildMonthOccultationStarLabel($row), 'constellation' => trim((string) ($row['con'] ?? '')), 'mag' => isset($row['mag']) ? (float) $row['mag'] : 99.0, 'ra' => isset($row['rarad']) ? (float) $row['rarad'] * (12 / M_PI) : 0.0, 'dec' => isset($row['decrad']) ? (float) $row['decrad'] * (180 / M_PI) : 0.0, 'distLy' => isset($row['dist']) && $row['dist'] !== null && (float) $row['dist'] > 0.0 ? (float) $row['dist'] * 3.26156 : 1000.0, ]; } } } catch (Throwable $e) { } if ($moonOccultationStars) { $tempOccultationFile = tempnam(sys_get_temp_dir(), 'month_occult_'); if ($tempOccultationFile !== false) { try { file_put_contents($tempOccultationFile, json_encode($moonOccultationStars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)); $moonOccultOutput = []; $moonOccultExitCode = 0; $moonOccultArgs = [ $pythonScriptPath, 'moon_star_occultations_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, $tempOccultationFile, ]; $moonOccultCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $moonOccultArgs)) . ' 2>&1'; exec($moonOccultCommand, $moonOccultOutput, $moonOccultExitCode); $moonOccultRaw = trim(implode("\n", $moonOccultOutput)); $moonOccultDecoded = json_decode($moonOccultRaw, true); if (is_array($moonOccultDecoded) && ($moonOccultDecoded['ok'] ?? false)) { $moonOccultations = is_array($moonOccultDecoded['results'] ?? null) ? $moonOccultDecoded['results'] : []; } } catch (Throwable $e) { } finally { if (is_file($tempOccultationFile)) { @unlink($tempOccultationFile); } } } } foreach ($moonPhaseEvents as $phaseEvent) { if (!is_array($phaseEvent)) { continue; } $phaseLabel = trim((string) ($phaseEvent['label'] ?? '')); if ($phaseLabel === 'Erstes Viertel' || $phaseLabel === 'Letztes Viertel') { $phaseLabel = 'Mond ' . $phaseLabel; } $monthEventList[] = [ 'event' => $phaseLabel, 'date' => (string) ($phaseEvent['local_date'] ?? ''), 'time' => (string) ($phaseEvent['local_time'] ?? ''), 'local_iso' => (string) ($phaseEvent['local_iso'] ?? ''), 'type' => 'moon_phase', ]; } foreach ($goldenHandleEvents as $goldenHandleEvent) { if (!is_array($goldenHandleEvent)) { continue; } $peakDate = (string) ($goldenHandleEvent['peak_local_date'] ?? ''); $peakTime = (string) ($goldenHandleEvent['peak_local_time'] ?? ''); $goldenHandleLabel = 'Goldener Henkel am Mond'; $monthEventList[] = [ 'event' => $goldenHandleLabel, 'date' => $peakDate !== '' ? $peakDate : (string) ($goldenHandleEvent['local_date'] ?? ''), 'time' => $peakTime !== '' ? $peakTime : (string) ($goldenHandleEvent['local_time'] ?? ''), 'local_iso' => (string) ($goldenHandleEvent['peak_local_iso'] ?? $goldenHandleEvent['local_iso'] ?? ''), 'type' => 'golden_handle', ]; } foreach ($seasonChanges as $seasonChange) { if (!is_array($seasonChange)) { continue; } $monthEventList[] = [ 'event' => (string) ($seasonChange['label'] ?? 'Jahreszeitenwechsel'), 'date' => (string) ($seasonChange['local_date'] ?? ''), 'time' => (string) ($seasonChange['local_time'] ?? ''), 'local_iso' => (string) ($seasonChange['local_iso'] ?? ''), 'type' => 'season_change', ]; } foreach ($timeChanges as $timeChange) { if (!is_array($timeChange)) { continue; } $timeChangeKind = (string) ($timeChange['kind'] ?? ''); $timeChangeLabel = $timeChangeKind === 'dst_start' ? 'Beginn der Sommerzeit (Uhren von 02:00 auf 03:00 Uhr vorstellen)' : ($timeChangeKind === 'dst_end' ? 'Beginn der Normalzeit (Uhren von 03:00 auf 02:00 Uhr zurückstellen)' : 'Zeitumstellung'); $timeChangeTime = $timeChangeKind === 'dst_start' ? '02:00' : ($timeChangeKind === 'dst_end' ? '03:00' : (string) ($timeChange['local_time'] ?? '')); $monthEventList[] = [ 'event' => $timeChangeLabel, 'date' => (string) ($timeChange['local_date'] ?? ''), 'time' => $timeChangeTime, 'local_iso' => (string) ($timeChange['local_iso'] ?? ''), 'type' => 'time_change', ]; } foreach ($eclipseEvents as $eclipseEvent) { if (!is_array($eclipseEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($eclipseEvent['label'] ?? 'Finsternis'), 'date' => (string) ($eclipseEvent['local_date'] ?? ''), 'time' => (string) ($eclipseEvent['local_time'] ?? ''), 'local_iso' => (string) ($eclipseEvent['local_iso'] ?? ''), 'type' => (string) ($eclipseEvent['category'] ?? 'eclipse'), ]; } foreach ($moonApsisEvents as $moonApsisEvent) { if (!is_array($moonApsisEvent)) { continue; } $moonApsisKind = (string) ($moonApsisEvent['kind'] ?? ''); $distanceText = isset($moonApsisEvent['distance_km']) ? number_format((float) $moonApsisEvent['distance_km'], 0, ',', '.') . ' km' : ''; $moonApsisLabel = $moonApsisKind === 'perigee' ? 'Mond in Erdnähe (Perigäum)' : ($moonApsisKind === 'apogee' ? 'Mond in Erdferne (Apogäum)' : (string) ($moonApsisEvent['label'] ?? 'Mondabstand')); $monthEventList[] = [ 'event' => trim($moonApsisLabel . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($moonApsisEvent['local_date'] ?? ''), 'time' => (string) ($moonApsisEvent['local_time'] ?? ''), 'local_iso' => (string) ($moonApsisEvent['local_iso'] ?? ''), 'type' => 'moon_apsis', ]; } foreach ($sunApsisEvents as $sunApsisEvent) { if (!is_array($sunApsisEvent)) { continue; } $sunApsisKind = (string) ($sunApsisEvent['kind'] ?? ''); $distanceText = isset($sunApsisEvent['distance_km']) ? number_format((float) $sunApsisEvent['distance_km'], 0, ',', '.') . ' km' : ''; $sunApsisLabel = $sunApsisKind === 'perihelion' ? 'Erde erreicht sonnennächsten Punkt (Perihel)' : ($sunApsisKind === 'aphelion' ? 'Erde erreicht sonnenfernsten Punkt (Aphel)' : (string) ($sunApsisEvent['label'] ?? 'Sonnenabstand')); $monthEventList[] = [ 'event' => trim($sunApsisLabel . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($sunApsisEvent['local_date'] ?? ''), 'time' => (string) ($sunApsisEvent['local_time'] ?? ''), 'local_iso' => (string) ($sunApsisEvent['local_iso'] ?? ''), 'type' => 'sun_apsis', ]; } foreach ($innerPlanetElongationEvents as $elongationEvent) { if (!is_array($elongationEvent)) { continue; } $elongationPlanet = (string) ($elongationEvent['planet_label'] ?? 'Planet'); $elongationKind = (string) ($elongationEvent['kind'] ?? ''); $elongationText = isset($elongationEvent['elongation_deg']) ? number_format((float) $elongationEvent['elongation_deg'], 2, ',', '') . ' Grad' : ''; $elongationLabel = $elongationKind === 'western' ? $elongationPlanet . ' größte westliche Elongation' : ($elongationKind === 'eastern' ? $elongationPlanet . ' größte östliche Elongation' : (string) ($elongationEvent['label'] ?? 'Elongation')); $monthEventList[] = [ 'event' => trim($elongationLabel . ($elongationText !== '' ? ' (' . $elongationText . ')' : '')), 'date' => (string) ($elongationEvent['local_date'] ?? ''), 'time' => (string) ($elongationEvent['local_time'] ?? ''), 'local_iso' => (string) ($elongationEvent['local_iso'] ?? ''), 'type' => 'planet_elongation', ]; } foreach ($venusPeakMagnitudeEvents as $venusPeakMagnitudeEvent) { if (!is_array($venusPeakMagnitudeEvent)) { continue; } $magnitudeText = isset($venusPeakMagnitudeEvent['mag']) ? number_format((float) $venusPeakMagnitudeEvent['mag'], 1, ',', '') . ' mag' : ''; $monthEventList[] = [ 'event' => trim('Venus in größter Helligkeit' . ($magnitudeText !== '' ? ' (' . $magnitudeText . ')' : '')), 'date' => (string) ($venusPeakMagnitudeEvent['local_date'] ?? ''), 'time' => (string) ($venusPeakMagnitudeEvent['local_time'] ?? ''), 'local_iso' => (string) ($venusPeakMagnitudeEvent['local_iso'] ?? ''), 'type' => 'venus_peak_magnitude', ]; } foreach ($outerPlanetEvents as $outerPlanetEvent) { if (!is_array($outerPlanetEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($outerPlanetEvent['label'] ?? 'Planetenereignis'), 'date' => (string) ($outerPlanetEvent['local_date'] ?? ''), 'time' => (string) ($outerPlanetEvent['local_time'] ?? ''), 'local_iso' => (string) ($outerPlanetEvent['local_iso'] ?? ''), 'type' => 'outer_planet_event', ]; } foreach ($outerPlanetStationEvents as $outerPlanetStationEvent) { if (!is_array($outerPlanetStationEvent)) { continue; } $stationPlanet = (string) ($outerPlanetStationEvent['planet_label'] ?? 'Planet'); $stationKind = (string) ($outerPlanetStationEvent['kind'] ?? ''); $stationLabel = $stationKind === 'retrograde' ? $stationPlanet . ' stationär, dann rückläufig' : ($stationKind === 'prograde' ? $stationPlanet . ' stationär, dann rechtläufig' : (string) ($outerPlanetStationEvent['label'] ?? 'Stationärer Punkt')); $monthEventList[] = [ 'event' => $stationLabel, 'date' => (string) ($outerPlanetStationEvent['local_date'] ?? ''), 'time' => (string) ($outerPlanetStationEvent['local_time'] ?? ''), 'local_iso' => (string) ($outerPlanetStationEvent['local_iso'] ?? ''), 'type' => 'outer_planet_station', ]; } foreach ($jupiterMoonSideEvents as $jupiterMoonSideEvent) { if (!is_array($jupiterMoonSideEvent)) { continue; } $eventLabel = trim((string) ($jupiterMoonSideEvent['label'] ?? 'Alle 4 Jupitermonde auf einer Seite von Jupiter')); $monthEventList[] = [ 'event' => $eventLabel, 'date' => (string) ($jupiterMoonSideEvent['local_date'] ?? ''), 'time' => (string) ($jupiterMoonSideEvent['local_time'] ?? ''), 'local_iso' => (string) ($jupiterMoonSideEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($jupiterMoonSideEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($jupiterMoonSideEvent['duration_minutes']) ? (int) $jupiterMoonSideEvent['duration_minutes'] : null, 'type' => 'jupiter_moons_one_side', ]; } foreach ($moonPlanetApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) $approachEvent['separation_deg'], 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Mond nahe Planet') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'moon_planet', ]; } foreach ($moonDeepSkyApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) ($approachEvent['separation_deg']), 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Mond nahe Sternhaufen') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'moon_deep_sky', ]; } foreach ($planetBrightStarApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) ($approachEvent['separation_deg']), 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Planet nahe hellem Stern') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'planet_bright_star', ]; } foreach ($planetConjunctions as $conjunctionEvent) { if (!is_array($conjunctionEvent)) { continue; } $distanceText = isset($conjunctionEvent['separation_deg']) ? number_format((float) $conjunctionEvent['separation_deg'], 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($conjunctionEvent['label'] ?? 'Planetenkonjunktion') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($conjunctionEvent['local_date'] ?? ''), 'time' => (string) ($conjunctionEvent['local_time'] ?? ''), 'local_iso' => (string) ($conjunctionEvent['local_iso'] ?? ''), 'type' => 'planet_conjunction', ]; } foreach ($mercuryGoodVisibilityEvents as $visibilityEvent) { if (!is_array($visibilityEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($visibilityEvent['label'] ?? 'Gute Merkur-Sichtbarkeit'), 'date' => (string) ($visibilityEvent['local_date'] ?? ''), 'time' => (string) ($visibilityEvent['local_time'] ?? ''), 'local_iso' => (string) ($visibilityEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($visibilityEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($visibilityEvent['duration_minutes']) ? (int) $visibilityEvent['duration_minutes'] : null, 'type' => 'mercury_good_visibility', ]; } foreach ($planetParadeEvents as $paradeEvent) { if (!is_array($paradeEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($paradeEvent['label'] ?? 'Planetenparade'), 'date' => (string) ($paradeEvent['local_date'] ?? ''), 'time' => (string) ($paradeEvent['local_time'] ?? ''), 'local_iso' => (string) ($paradeEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($paradeEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($paradeEvent['duration_minutes']) ? (int) $paradeEvent['duration_minutes'] : null, 'type' => 'planet_parade', ]; } foreach ($planetConstellationChangeEvents as $changeEvent) { if (!is_array($changeEvent)) { continue; } $planetLabel = trim((string) ($changeEvent['planet_label'] ?? 'Planet')); $fromConstellationName = trim((string) ($changeEvent['from_constellation_name'] ?? '')); $toConstellationName = trim((string) ($changeEvent['to_constellation_name'] ?? '')); $changeLabel = (string) ($changeEvent['label'] ?? 'Planet wechselt Sternbild'); if ($fromConstellationName !== '' && $toConstellationName !== '') { $changeLabel = sprintf( '%s wechselt vom Sternbild %s in das Sternbild %s', $planetLabel, $fromConstellationName, $toConstellationName ); } $monthEventList[] = [ 'event' => $changeLabel, 'date' => (string) ($changeEvent['local_date'] ?? ''), 'time' => (string) ($changeEvent['local_time'] ?? ''), 'local_iso' => (string) ($changeEvent['local_iso'] ?? ''), 'type' => 'planet_constellation_change', ]; } 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; } $maximum = is_array($occultationEvent['maximum'] ?? null) ? $occultationEvent['maximum'] : null; $star = is_array($occultationEvent['star'] ?? null) ? $occultationEvent['star'] : null; if ($maximum === null || $star === null) { continue; } $starLabel = (string) ($star['label'] ?? 'Stern'); $starMag = isset($star['mag']) ? number_format((float) $star['mag'], 1, ',', '') : ''; $maximumLocalDate = (string) ($maximum['local_date'] ?? ''); if ($maximumLocalDate !== '') { $parsedMaximumDate = DateTimeImmutable::createFromFormat('Y-m-d', $maximumLocalDate); if ($parsedMaximumDate instanceof DateTimeImmutable) { $maximumLocalDate = $parsedMaximumDate->format('d.m.Y'); } } $monthEventList[] = [ 'event' => 'Mond bedeckt ' . $starLabel . ($starMag !== '' ? ' (' . $starMag . ' mag)' : ''), 'date' => $maximumLocalDate, 'time' => (string) ($maximum['local_time'] ?? ''), 'local_iso' => (string) ($maximum['local_iso'] ?? ''), 'type' => 'moon_occultation', ]; } foreach ($meteorShowerPeaks as $meteorShowerPeak) { if (!monthForecastRadiantIsVisible($chartLatitude, $meteorShowerPeak['radiant_delta_deg'] ?? null)) { continue; } $peakSol = isset($meteorShowerPeak['peak_sol']) ? (float) $meteorShowerPeak['peak_sol'] : null; if ($peakSol === null) { continue; } $peakDateTime = findMonthForecastSolarLongitudeCrossing( $monthStartLocal, $monthEndLocal, $peakSol, new DateTimeZone($chartTimezone) ); if (!$peakDateTime instanceof DateTimeImmutable) { continue; } $designation = trim((string) ($meteorShowerPeak['designation'] ?? '')); $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) { $rateText = 'ZHR ca. ' . $zhr; } elseif ($zhrVariable !== '') { $rateText = 'ZHR ca. ' . $zhrVariable; } $labelParts = []; $labelParts[] = 'Maximum ' . ($designation !== '' ? $designation : 'Meteorstrom'); if ($showerCode !== '') { $labelParts[] = $showerCode; } if ($rateText !== '') { $labelParts[] = $rateText; } $monthEventList[] = [ 'event' => implode(' - ', $labelParts), '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', ]; } foreach ($favoriteSatellitePasses as $satellitePass) { if (!is_array($satellitePass)) { continue; } $peakUtc = trim((string) ($satellitePass['peak_utc'] ?? '')); if ($peakUtc === '') { continue; } $peakAltitude = isset($satellitePass['peak_el']) ? (float) $satellitePass['peak_el'] : null; $peakSunAltitude = isset($satellitePass['peak_sun_altitude_deg']) ? (float) $satellitePass['peak_sun_altitude_deg'] : null; $peakMagnitude = isset($satellitePass['peak_mag']['mag']) ? (float) $satellitePass['peak_mag']['mag'] : null; if ($peakAltitude === null || $peakSunAltitude === null) { continue; } if ($peakAltitude < $favoriteSatelliteBestPassMinAltitudeDeg) { continue; } if ($peakSunAltitude > $favoriteSatelliteBestPassMaxSunAltitudeDeg) { continue; } try { $peakDateTime = (new DateTimeImmutable($peakUtc))->setTimezone(new DateTimeZone($chartTimezone)); } catch (Throwable) { continue; } if ($favoriteSatelliteBestPassOnlyFirstNightHalf && (int) $peakDateTime->format('G') < 12) { continue; } if ($peakMagnitude === null || $peakMagnitude > $favoriteSatelliteBestPassMaxPeakMagnitude) { continue; } $peakAltitudeText = number_format($peakAltitude, 0, ',', ''); $eventLabel = 'Guter Überflug ' . ($favoriteSatelliteName !== null && $favoriteSatelliteName !== '' ? $favoriteSatelliteName : 'Favoriten-Satellit'); $peakMagnitudeText = number_format($peakMagnitude, 1, ',', ''); $eventLabel .= ' (max. ' . $peakAltitudeText . ' Grad, ' . $peakMagnitudeText . ' mag)'; $monthEventList[] = [ 'event' => $eventLabel, 'date' => $peakDateTime->format('d.m.Y'), 'time' => $peakDateTime->format('H:i'), 'local_iso' => $peakDateTime->format(DateTimeInterface::ATOM), 'type' => 'favorite_satellite_pass', ]; } usort( $monthEventList, static fn (array $a, array $b): int => strcmp((string) ($a['local_iso'] ?? ''), (string) ($b['local_iso'] ?? '')) ); $monthNarrativeText = monthForecastBuildNarrativeText($monthEventList, $selectedMonth, $months[$selectedMonth], $selectedYear); $observationTipInsertSql = buildObservationTipInsertSql($monthEventList, new DateTimeZone($chartTimezone)); $observationTipCalendarIcs = buildObservationTipCalendarIcs($monthEventList, new DateTimeZone($chartTimezone)); $chartConfig = [ 'monthName' => $months[$selectedMonth], 'year' => $selectedYear, 'month' => $selectedMonth, 'location' => [ 'name' => $chartLocationName, 'latitude' => $chartLatitude, 'longitude' => $chartLongitude, 'elevation' => $chartElevation, 'timezone' => $chartTimezone, ], 'localTimeLabel' => $chartLocalTime->format('d.m.Y H:i') . ' ' . $chartTimezone, 'utcTimeIso' => $chartUtcTime->format(DateTimeInterface::ATOM), 'monthSkyHour' => $monthTime['hour'], 'monthSkyMinute' => $monthTime['minute'], 'starLimit' => $starDisplayMagnitudeLimit, 'starLabelLimit' => $starLabelMagnitudeLimit, 'messierLimit' => $messierDisplayMagnitudeLimit, ]; $chartConfigJson = json_encode($chartConfig, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $starDataJson = json_encode($starData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $lineStarDataJson = json_encode($lineStarData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $constellationLinesJson = json_encode($constellationLines, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $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); $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatsereignisse-%04d-%02d.ics', $selectedYear, $selectedMonth), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>

Zeitraum wählen

Die Sternkarte wird für den 15. des gewählten Monats mit der festen Monatsuhrzeit erzeugt.

Monat Im Ueberblick

Zenitprojektion

,

Südhimmel

,

Mondphasen des Monats in einer Zeile

Ereignisse

Chronologische Monatsliste für