format('W'); } } $years = range($currentYear - 2, $currentYear + 5); $selectedWeek = max(1, min($selectedWeek, getIsoWeekCountForYear($selectedWeekYear))); $weekNumbers = range(1, getIsoWeekCountForYear($selectedWeekYear)); $months = [ 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', ]; $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 = []; $goldenGatePlanetEvents = []; $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; $monthNarrativeText = ''; $observationTipInsertSql = ''; $observationTipCalendarIcs = ''; $chartConfig = null; $chartConfigJson = 'null'; $starDataJson = '[]'; $lineStarDataJson = '[]'; $constellationLinesJson = '[]'; $messierDataJson = '[]'; $planetDataJson = '[]'; $moonDataJson = 'null'; $monthChartCometsJson = '[]'; $moonPhaseEventsJson = '[]'; $starDataErrorJson = 'null'; $observationTipCalendarIcsJson = '""'; $observationTipCalendarFilenameJson = json_encode( sprintf('astrotools-monatsereignisse-%04d-%02d.ics', $selectedYear, $selectedMonth), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); 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' )); // Variante wird jährlich rotiert: Jahr mod 3 → 0=V1, 1=V2, 2=V3 $skyIntroByMonthV1 = [ 1 => "Das bekannteste Wintersternbild prägt im Januar den Südhimmel: 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 => "Wer an einem klaren Februarabend nach draußen tritt, blickt in einen besonders eindrucksvollen Sternenhimmel. Die Wintersternbilder rund um den Orion prägen nun das Bild 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 => "Noch halten die Wintersternbilder den Abendhimmel im März fest in ihrem Griff: Orion, Großer Hund, Kleiner Hund, Zwillinge, Fuhrmann und Stier sind vertraut. 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 => "Der Frühjahrsabendhimmel zeigt sich im April ganz im Zeichen der Frühlingssternbilder. 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 => "Hoch über dem Südhorizont thront am Maiabend das Sternbild Jungfrau. 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 => "Unverkennbar kündigt sich der Sommerhimmel im Juni 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.\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. 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.", 7 => "Für Beobachter weiter südlich im Mittelmeerraum steigen Skorpion und Schütze im Juli imposant hoch – von Deutschland aus dagegen bleiben beide Sternbilder stets tief über dem Horizont. Deutlich günstiger zeigt sich der Sommerhimmel in unseren Breiten bei einem Blick nach oben und in Richtung Osten.\n\nOberhalb von Skorpion und Schütze liegen die eher lichtschwachen Sternbilder Schlangenträger und Schlange; noch darüber befindet sich Herkules. 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 steht inzwischen in nordwestlicher Richtung und sinkt im Lauf der Nacht immer tiefer zum Horizont.", 8 => "Wenn es nach 22 Uhr langsam dunkel wird, taucht der Augusthimmel in seine ganze sommerliche Pracht. Nach und nach treten 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. 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.\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. 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.", 9 => "Schon gegen 21.30 Uhr sind die meisten Sternbilder im September wieder gut zu erkennen – die hellen Sommernächte sind vorbei, und die Dunkelheit kehrt merklich früher zurück. 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. Wega, Altair im Adler und Deneb im Schwan 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 => "Längere Nächte als Tage – das ist das Markenzeichen des Oktobers, seit die Sonne Ende September den Himmelsäquator nach Süden überschritten hat. Am frühen Abend erinnert der Sternenhimmel noch an den Sommer: Das Sommerdreieck aus Wega, Deneb und Altair ist gut zu erkennen.\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. 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.", 11 => "Das Herbstviereck dominiert den Novemberhimmel – dieses markante Quadrat, das meist auch Pegasus-Quadrat genannt wird, ist kaum zu übersehen. 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. Im Osten kündigt sich dagegen schon der Winterhimmel an. 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.", 12 => "Schon früh am Abend enthüllt der Dezemberhimmel die ganze Pracht des Winters. Die Sommersternbilder Schwan, Leier und Adler verabschieden sich; Orion, Stier, Zwillinge und Fuhrmann sind schon gut aufgegangen. 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 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 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.", ]; $skyIntroByMonthV2 = [ 1 => "Kein Sternbild des Winters ist eindrucksvoller als der Orion, der im Januar hoch im Süden steht. Seine sieben Hauptsterne leuchten auffallend hell; mit etwas Vorstellungskraft erkennt man in ihrer Anordnung die Gestalt des Himmelsjägers. Unterhalb der drei markanten Gürtelsterne lässt sich bereits mit einem Fernglas der blass schimmernde Orionnebel entdecken. Rigel markiert dabei die rechte untere Ecke dieser prächtigen Sternfigur.\n\nGleichzeitig ist Rigel Teil des Wintersechsecks – nicht ein eigenes Sternbild, sondern eine auffällige Formation aus sechs hellen Sternen verschiedener Sternbilder. Im Uhrzeigersinn schließen sich an: Sirius im Großen Hund, Prokyon im Kleinen Hund, Castor in den Zwillingen, Capella im Fuhrmann und Aldebaran im Stier. Die Wintermilchstraße durchquert diesen Bereich, verläuft durch Großen Hund, Einhorn, Zwillinge, Stier, Fuhrmann, Perseus und Kassiopeia und wirkt dabei schwächer als im Sommer – unser Blick ist jetzt eher zum Rand der Galaxis gerichtet.", 2 => "Sternenreicher als im Februar zeigt sich der Winterhimmel kaum. Die markanten Sternbilder rund um den Orion beherrschen um 22 Uhr, auf diese Zeit bezieht sich die Sternkarte, die Szene, nachdem sie den Meridian bereits überschritten haben. Unterhalb des Orion glitzert Sirius im Großen Hund, der hellste Stern des gesamten Nachthimmels. Rechts oben steht der Stier mit Aldebaran, noch höher der Fuhrmann mit der gelblich leuchtenden Capella, links daneben die Zwillinge mit Castor und Pollux. Prokyon, Hauptstern des Kleinen Hundes, leuchtet unterhalb der Zwillinge.\n\nDer Osthimmel verrät unterdessen schon die nächste Jahreszeit: Der Löwe mit Regulus ist vollständig aufgegangen, und Alphard in der Wasserschlange leuchtet als auffälliger Einzelstern darunter. Die Jungfrau lässt sich erst nach Mitternacht vollständig über dem Horizont blicken.", 3 => "Noch stehen die Wintersternbilder am Märzhimmel bereit – Orion, Großer Hund, Kleiner Hund, Zwillinge, Fuhrmann und Stier beleben den Abend. Doch im Lauf der Nacht sinken sie nach Westen ab, während im Osten und Süden die Frühlingssternbilder aufsteigen. Um Mitternacht bestimmen vor allem die Sternbilder um den Löwen das Bild.\n\nDas Tierkreissternbild Krebs liegt zwischen den Zwillingen und dem Löwen. Unscheinbar und schwer zu erkennen, verbirgt es doch einen Schatz: den offenen Sternhaufen Praesepe – die „Krippe\" –, der bei klarem, dunklem Himmel als milchiger Lichtfleck sichtbar wird. Der Große Bär thront hoch über unseren Köpfen; seine sieben hellsten Sterne formen den vertrauten Großen Wagen. Spica, der hellste Stern der Jungfrau, geht im Südosten auf. Im Nordosten leuchtet der orange Arktur im Bärenhüter. Tief im Süden streckt die Wasserschlange ihren vorderen Teil empor – oft ist nur Alphard zu sehen, dessen Name treffend „der Einsame\" bedeutet. Knapp über dem Horizont steht auch der kompakte Rabe.", 4 => "Drei helle Sterne ziehen am Aprilabend besonders den Blick auf sich: Regulus im Löwen, Spica in der Jungfrau und der orange Arktur im Bärenhüter. Gemeinsam formen sie das sogenannte Frühlingsdreieck. Der Löwe selbst steht genau im Süden; wer mag, erkennt in seiner Sternfigur eine liegende Raubkatze.\n\nDer Große Wagen steht hoch über uns, beinahe senkrecht aufgerichtet – er ist das auffälligste Teilstück des Großen Bären. Zwischen ihm und dem Löwen tummeln sich eher unauffällige Sternbilder: Kleiner Löwe, Jagdhunde und Haar der Berenike. Ähnlich sternarm zeigt sich der Himmelsstreifen zwischen dem Löwen und dem Horizont, wo das ausgedehnte, aber lichtschwache Sternbild Wasserschlange entlangzieht. Von den Sternbildern dieses Himmelsabschnitts sind Becher und Rabe noch am ehesten zu erkennen.", 5 => "Das Frühlingsdreieck steht am Maiabendhimmel in voller Pracht: Arktur im Bärenhüter glüht orange hoch am Himmel, Spica in der Jungfrau leuchtet tiefer im Süden, und Regulus im Löwen hält im Südwesten die Stellung. Über dem Frühlingsdreieck liegt das eher unauffällige Sternbild Haar der Berenike.\n\nAm Südosthorizont zeigt sich schon ein Vorbote des Sommers: der Skorpion, noch sehr tief, kündigt sich an. Im Westen verschwinden langsam die letzten Wintergestirne unter dem Horizont, während im Osten Wega – der helle Hauptstern der Leier – schon erstrahlt. Zwischen Wega und Arktur liegen das lichtschwächere Herkules und der anmutige Halbkreis der Nördlichen Krone.", 6 => "Tief im Südosten glüht Antares, der rötliche Hauptstern des Skorpions, in den Juninächten dicht über dem Horizont. Höher steigt er von unseren Breiten aus nicht. Weit oben thront der Große Wagen als auffälligster Teil des Großen Bären; sein geschwungener Deichselbogen weist zuverlässig zu Arktur und weiter zu Spica. Weiter westlich sinkt der Löwe allmählich Richtung Horizont.\n\nLinks und oberhalb von Arktur wölbt sich die Nördliche Krone – ein zarter Halbkreis, leicht zu finden. Ihr hellster Stern Gemma, der „Edelstein\", trägt seinen Namen zu Recht. Unterhalb davon breiten sich Schlangenträger und die zweigeteilte Schlange aus – weniger auffällig, aber bei dunklem Himmel gut verfolgbar. Den eigentlichen Blickfang des Junihimmels bildet das Sommerdreieck im Osten: Wega (Leier), Deneb (Schwan) und Altair (Adler) leuchten auf und werden uns durch die kommenden Monate begleiten.", 7 => "Tief über dem Südhorizont zeigen sich im Juli Skorpion und Schütze – für Beobachter im Mittelmeerraum ein großartiger Anblick, von Deutschland aus jedoch stets nur knapp über dem Horizont. Oberhalb der beiden südlichen Sternbilder folgen Schlangenträger, Schlange und Herkules, alle eher unscheinbar, aber typisch für den Sommerhimmel.\n\nWirklich ins Auge fallen dagegen Arktur, Wega, Deneb und Altair – die vier Leuchttürme der warmen Jahreszeit. Wega, Deneb und Altair bilden das Sommerdreieck. Die Milchstraße zieht als Band über den Himmel, bleibt aber bei den noch hellen Julisommernächten oft kaum sichtbar – ihre Zeit kommt im August. Der Große Bär ist mittlerweile in den Nordwesten gerückt.", 8 => "Kaum eine andere Jahreszeit zeigt den Sternenhimmel so eindrucksvoll wie der August. Nach 22 Uhr wird es langsam dunkel, und Stern für Stern taucht auf. Um 23 Uhr dominiert das Sommerdreieck den Zenit: Wega in der Leier thront fast senkrecht über uns, links davon Deneb im Schwan, deutlich tiefer Altair im Adler – markant und leicht zu finden.\n\nDie kürzer werdenden Nächte schenken der Milchstraße ihre Bühne zurück: Als schimmerndes Band durchzieht sie das Sommerdreieck, am eindrucksvollsten weit draußen in der Dunkelheit. Im Osten weisen Pegasus und Andromeda auf den nahenden Herbst. Den Höhepunkt setzt der Meteorstrom der Perseiden: Um den 12. August regnen Staubteilchen des Kometen Swift-Tuttle als Sternschnuppen durch die Atmosphäre.", 9 => "Deutlich früher als noch vor wenigen Wochen wird es im September dunkel. Gegen 21.30 Uhr sind die meisten Sternbilder bereits gut erkennbar. Wega strahlt als erster auffälliger Stern über uns; wer genauer hinschaut, findet auch die kleine Raute der Leier. Zusammen mit Altair im Adler und Deneb im Schwan bildet Wega das große Sommerdreieck, das noch den Herbsthimmel überbrückt.\n\nInnerhalb und seitlich des Sommerdreiecks verstecken sich die kompakten Sternbilder Pfeil und Delfin – nicht besonders hell, aber durch ihre einprägsame Form überraschend einfach aufzufinden. Die Milchstraße erstrahlt jetzt wieder in vollem Glanz als fahles Band quer über den Himmel. Im Westen weichen die Frühsommersternbilder zurück, im Osten treten die ersten Herbstgestalten hervor – ein Himmel im Übergang.", 10 => "Mit dem Oktober kehren die langen Nächte zurück. Seit die Sonne Ende September den Himmelsäquator nach Süden überschritt, überwiegt die Dunkelheit. Am frühen Abend ist noch das Sommerdreieck mit Wega, Deneb und Altair präsent; im weiteren Verlauf der Nacht übernehmen die Herbststernbilder das Ruder.\n\nGegen 23 Uhr Sommerzeit prangt das Herbstviereck hoch am Himmel. Drei seiner Ecksterne gehören zu Pegasus, die vierte – links oben – bereits zur Andromeda-Kette. Darunter erstrecken sich die lichtschwachen Tierkreissternbilder Steinbock, Wassermann und Fische – nur an dunklen Orten richtig zu erleben. Diphda im Walfisch ist einer der wenigen helleren Sterne in dieser Himmelsregion. Tief im Süden blinkt Fomalhaut im Südlichen Fisch.", 11 => "Herbststimmung liegt über dem Novemberhimmel. Zentrales Schaustück ist das Pegasus-Quadrat – drei Sterne aus Pegasus und einer aus der Andromeda-Kette. Im Sternbild Andromeda lohnt ein gezielter Blick: Die Andromedagalaxie, mit bloßem Auge das fernste sichtbare Objekt überhaupt, erscheint an dunklen Standorten als schwacher, nebliger Lichtfleck.\n\nIm Südwesten und Süden breiten sich die lichtschwachen Sternbilder Wassermann, Walfisch und Fische aus. Im Walfisch liegt Mira, ein Stern mit stark schwankender Helligkeit – mal leicht zu sehen, mal kaum vorhanden. Der Osten hingegen zeigt schon die Vorzeichen des Winters: Die Kassiopeia formt ihr charakteristisches W, Perseus steigt mit dem veränderlichen Algol empor, der Stier zeigt Plejaden und Aldebaran. Am Osthorizont erscheint schließlich Orion – der unumstrittene König des Winterhimmels.", 12 => "Früh senkt sich im Dezember die Dunkelheit über den Himmel, und mit ihr erstrahlen die Wintergestalten. Schwan, Leier und Adler rücken in den Abendstunden schon in den Hintergrund; Orion, Stier, Zwillinge und Fuhrmann haben längst ihren Platz eingenommen. Sirius erscheint früh über dem Südosthorizont. In der Neujahrsnacht erreicht er gegen Mitternacht seinen Tageshöchstpunkt und steht dann von Deutschland aus fast genau im Süden.\n\nIm Südwesten verbleiben Reste des Herbsthimmels: Wassermann, Fische und Walfisch unterhalb des Herbstvierecks, lichtschwach und weitläufig. Zwischen Walfisch und Orion windet sich der Eridanus – seine Sterne sind unauffällig, doch sein geschwungener Verlauf lädt bei klarer Nacht zu einer kleinen Himmelsreise ein.", ]; $skyIntroByMonthV3 = [ 1 => "Sieben helle Sterne zeichnen den Orion an den Januarnächten in den Südhimmel – das bekannteste Wintersternbild ist unverkennbar. Die Anordnung der Hauptsterne lässt mit etwas Fantasie die Gestalt des mythischen Jägers erahnen. Unterhalb der drei Gürtelsterne schimmert bereits im Fernglas der Orionnebel. Rigel markiert die rechte untere Ecke dieser imposanten Figur.\n\nRigel gehört zugleich zum Wintersechseck, einer auffälligen Formation aus sechs Sternen verschiedener Sternbilder. Im Uhrzeigersinn reihen sich an: Sirius (Großer Hund), Prokyon (Kleiner Hund), Castor (Zwillinge), Capella (Fuhrmann) und Aldebaran (Stier). Die Wintermilchstraße durchzieht denselben Himmelsbereich – von Großem Hund über Zwillinge, Stier, Fuhrmann und Perseus bis zur Kassiopeia. Schwächer als im Sommer, weist unser Blick doch zum Rand der Galaxis.", 2 => "Sternreich und leuchtend präsentiert sich der Himmel an einem klaren Februarabend. Um 22 Uhr, auf diese Zeit bezieht sich die Sternkarte, haben die Wintersternbilder um den Orion den Meridian bereits passiert. Unter dem Orion glänzt Sirius, der hellste Stern des Nachthimmels. Oben rechts steht der Stier mit Aldebaran, darüber der Fuhrmann mit der gelblich leuchtenden Capella, links davon die Zwillinge mit Castor und Pollux. Prokyon im Kleinen Hund leuchtet unterhalb der Zwillinge.\n\nDer Frühlingsvorgeschmack lauert im Osten: Der Löwe mit Regulus ist vollständig aufgegangen; darunter strahlt Alphard als auffälliger Einzelstern in der Wasserschlange. Die Jungfrau wartet noch bis nach Mitternacht.", 3 => "Der Märzhimmel steht zwischen den Jahreszeiten. Die vertrauten Wintersternbilder – Orion, Großer Hund, Kleiner Hund, Zwillinge, Fuhrmann, Stier – beleben den frühen Abend. Doch Stunde für Stunde wandern sie westwärts, während die Frühlingssternbilder im Osten und Süden nachrücken. Um Mitternacht gehört die Bühne den Sternbildern um den Löwen.\n\nZwischen Zwillingen und Löwen liegt der unscheinbare Krebs. Wer ihn unter dunklem Himmel aufsucht, wird mit einem Anblick belohnt: dem offenen Sternhaufen Praesepe, der „Krippe\", als milchiger Lichtfleck. Der Große Wagen steht im Zenit, hoch wie selten. Spica geht im Südosten auf; der orange Arktur leuchtet im Nordosten. Tief im Süden streckt die Wasserschlange ihren Vorderteil empor – oft nur durch den vereinsamten Alphard verraten, der seinen Namen „der Einsame\" zurecht trägt. Auch der kompakte Rabe kauert knapp über dem Horizont.", 4 => "Ein Dreieck aus hellen Sternen zieht im April den Blick unweigerlich an: Regulus im Löwen, Spica in der Jungfrau und Arktur im Bärenhüter bilden das Frühlingsdreieck. Der Löwe selbst thront genau im Süden; wer mag, erkennt in seiner Sternfigur eine liegende Raubkatze.\n\nDer Große Wagen steht hoch über uns, beinahe senkrecht aufgerichtet. Zwischen ihm und dem Löwen sind die Sternbilder Kleiner Löwe, Jagdhunde und Haar der Berenike wenig auffällig. Ebenso sternarm zeigt sich der Himmelsstreifen zwischen dem Löwen und dem Horizont, wo die Wasserschlange langgezogen dahinfließt. Markanter sind noch Becher und Rabe, deren Sternfiguren trotz mäßiger Helligkeit gut erkennbar sind.", 5 => "Arktur glüht orange am Maiabendhimmel – kaum ein Stern im Frühjahr ist auffälliger. Der Hauptstern des Bärenhüters leuchtet hoch im Süden, tiefer schimmert Spica in der Jungfrau, und Regulus im Löwen hält im Südwesten. Diese drei Sterne – Arktur, Spica und Regulus – spannen das Frühlingsdreieck. Über ihnen, etwas blasser, liegt das Sternbild Haar der Berenike.\n\nAm Südosthorizont zeigt sich der Skorpion mit seinem Hauptstern Antares – noch sehr tief, ein erster Sommergruß. Im Westen sinken die letzten Wintergestirne langsam hinab; im Osten steigt Wega in der Leier bereits auf. Zwischen Wega und Arktur liegt das lichtschwächere Herkules, flankiert vom eleganten Halbkreis der Nördlichen Krone.", 6 => "Kaum über dem Südosthorizont hält sich im Juni Antares, der rötlich leuchtende Hauptstern des Skorpions. In unseren Breiten bleibt er ein Horizontgast. Oben dominiert der Große Wagen als auffälligster Teil des Großen Bären; seine gebogene Deichsel weist zuverlässig zu Arktur und weiter zu Spica. Im Südwesten neigt sich der Löwe dem Untergang.\n\nLinks und über Arktur öffnet sich der zarte Halbkreis der Nördlichen Krone – Gemma, der „Edelstein\", ist ihr hellster Stern. Darunter breiten sich Schlangenträger und die zweigeteilte Schlange aus, lichtschwach, bei dunklem Himmel aber gut zu verfolgen. Den Hauptanziehungspunkt des Juniabends bildet das Sommerdreieck im Osten: Wega (Leier), Deneb (Schwan) und Altair (Adler) leuchten auf und werden den Himmel noch Monate lang prägen.", 7 => "Skorpion und Schütze zeigen sich im Juli von ihrer schönsten Seite – allerdings nur, wer weit genug südlich beobachtet. Von Deutschland aus bleiben beide Sternbilder flach über dem Horizont. Weiter oben am Himmel reihen sich Schlangenträger, Schlange und Herkules, alle eher unscheinbar, aber typisch für den Sommerhimmel.\n\nDeutlich auffälliger sind Arktur, Wega, Deneb und Altair – die vier Leuchttürme der warmen Jahreszeit. Wega, Deneb und Altair bilden das Sommerdreieck. Die Milchstraße zieht als Band über den Himmel, bleibt aber bei den noch hellen Julisommernächten oft kaum sichtbar – ihre Zeit kommt im August. Der Große Bär ist mittlerweile in den Nordwesten gewandert.", 8 => "Kein Monat empfängt die Sterngucker besser als der August. Nach 22 Uhr wird es langsam dunkel, und Stern für Stern taucht auf. Um 23 Uhr thront das Sommerdreieck über uns: Wega nahezu senkrecht im Zenit, links Deneb im Schwan, tiefer Altair im Adler – markant und leicht zu finden.\n\nDie kürzer werdenden Nächte schenken der Milchstraße ihre Bühne zurück: Als schimmerndes Band durchzieht sie das Sommerdreieck, am eindrucksvollsten weit draußen in der Dunkelheit. Im Osten weisen Pegasus und Andromeda auf den nahenden Herbst. Den Höhepunkt setzt der Meteorstrom der Perseiden: Um den 12. August regnen Staubteilchen des Kometen Swift-Tuttle als Sternschnuppen durch die Atmosphäre.", 9 => "Früher Dunkel, früher Sterne: Der September schenkt uns wieder richtige Nächte. Gegen 21.30 Uhr füllt sich der Himmel – Wega zeigt sich als erste Leuchtbake hoch über uns. Genauer betrachtet, entfaltet sich um sie herum die kleine Raute der Leier. Altair im Adler und Deneb im Schwan ergänzen das Sommerdreieck, das auch jetzt noch den Herbsthimmel überbrückt.\n\nPfeil und Delfin, zwei kompakte kleine Sternbilder, verstecken sich im und neben dem Sommerdreieck – nicht hell, aber durch ihre einprägsame Form überraschend gut zu finden. Oben wölbt sich die Milchstraße wieder sichtbar als fahles, schimmerndes Band. Im Westen verabschieden sich die Frühsommersternbilder, im Osten erscheinen die ersten Herbstgestalten.", 10 => "Der Herbst hat den Himmel übernommen – zumindest dem Kalender nach. Am frühen Oktoberabend erinnert noch das Sommerdreieck (Wega, Deneb, Altair) an den Sommer; im Laufe der Nacht rücken die Herbststernbilder in den Vordergrund.\n\nGegen 23 Uhr Sommerzeit prangt das Herbstviereck hoch am Himmel: drei Sterne aus Pegasus, ein vierter aus der Andromeda-Kette. Darunter erstrecken sich die lichtschwachen Tierkreissternbilder Steinbock, Wassermann und Fische – nur an dunklen Orten richtig zu erleben. Diphda im Walfisch sticht als einer der wenigen helleren Sterne in dieser Himmelsregion heraus. Tief im Süden blinkt Fomalhaut im Südlichen Fisch.", 11 => "Pegasus-Quadrat und Andromeda stehen im November im Mittelpunkt – drei Ecken aus Pegasus, eine aus der Andromeda-Kette. Im Sternbild Andromeda lohnt ein gezielter Blick: Die Andromedagalaxie, mit bloßem Auge das fernste sichtbare Objekt überhaupt, erscheint an dunklem Himmel als schwacher, nebliger Fleck.\n\nIm Südwesten und Süden breiten sich die lichtschwachen Sternbilder Wassermann, Walfisch und Fische aus. Im Walfisch liegt Mira, ein Stern mit stark schwankender Helligkeit – mal leicht zu sehen, mal kaum vorhanden. Der Osten hingegen zeigt schon die Vorzeichen des Winters: Die Kassiopeia formt ihr charakteristisches W, Perseus steigt mit dem veränderlichen Algol empor, der Stier zeigt Plejaden und Aldebaran. Am Osthorizont erscheint schließlich Orion – der unumstrittene König des Winterhimmels.", 12 => "Lange Nächte und ein glanzvoller Winterhimmel – das ist der Dezember. Die Sommersternbilder ziehen sich in den frühen Abendstunden zurück; Orion, Stier, Zwillinge und Fuhrmann haben längst ihren Platz eingenommen. Sirius erscheint früh über dem Südosthorizont. In der Neujahrsnacht erreicht er gegen Mitternacht seinen Tageshöchstpunkt und steht dann von Deutschland aus fast genau im Süden.\n\nIm Südwesten verbleiben Reste des Herbsthimmels: Wassermann, Fische und Walfisch unterhalb des Herbstvierecks, lichtschwach und weitläufig. Zwischen Walfisch und Orion windet sich der Eridanus – seine Sterne sind unauffällig, doch sein geschwungener Verlauf lädt bei klarer Nacht zu einer kleinen Himmelsreise ein.", ]; $skyIntroByMonth = match ($year % 3) { 1 => $skyIntroByMonthV2, 2 => $skyIntroByMonthV3, default => $skyIntroByMonthV1, }; foreach ($skyIntroByMonth as $introMonthNumber => $introText) { $skyIntroByMonth[$introMonthNumber] = [$introText]; } $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', 'moon_planet_approach', 'moon_deep_sky', 'moon_deep_sky_approach', 'moon_occultation', 'moon_apsis'], true), 10 ); if ($moonEvents !== []) { // Chronologisch sortieren nach local_iso $sortedMoonEvents = $moonEvents; usort($sortedMoonEvents, static fn (array $a, array $b): int => strcmp( (string) ($a['local_iso'] ?? $a['date'] ?? ''), (string) ($b['local_iso'] ?? $b['date'] ?? '') )); // Natürlichsprachliche Liste: "Vollmond ist am 01.05., Apogäum am 05.05. und Neumond am 16.05." $moonParts = []; foreach ($sortedMoonEvents as $me) { $label = trim((string) ($me['event'] ?? '')); $date = trim((string) ($me['date'] ?? '')); if ($label === '' || $date === '') { continue; } // "Mond bedeckt X" → "der Mond bedeckt X" für natürlicheren Satzbau if (str_starts_with($label, 'Mond bedeckt ')) { $label = 'der ' . $label; } $moonParts[] = ['label' => $label, 'date' => $date]; } if ($moonParts !== []) { $moonSentenceParts = []; foreach ($moonParts as $idx => $part) { if ($idx === 0) { $moonSentenceParts[] = $part['label'] . ' ist am ' . $part['date']; } else { $moonSentenceParts[] = $part['label'] . ' am ' . $part['date']; } } $lastPart = array_pop($moonSentenceParts); $moonSentence = $moonSentenceParts !== [] ? implode(', ', $moonSentenceParts) . ' und ' . $lastPart : $lastPart; $paragraphs[] = $moonSentence . '.'; } } $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', 'golden_gate_of_ecliptic'], 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'); if ($shouldRunForecast) { $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'] : []; } $goldenGateOutput = []; $goldenGateExitCode = 0; $goldenGateArgs = [ $pythonScriptPath, 'golden_gate_of_ecliptic_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $selectedYear, (string) $selectedMonth, $chartTimezone, ]; $goldenGateCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $goldenGateArgs)) . ' 2>&1'; exec($goldenGateCommand, $goldenGateOutput, $goldenGateExitCode); $goldenGateRaw = trim(implode("\n", $goldenGateOutput)); $goldenGateDecoded = json_decode($goldenGateRaw, true); if (is_array($goldenGateDecoded) && ($goldenGateDecoded['ok'] ?? false)) { $goldenGatePlanetEvents = is_array($goldenGateDecoded['events'] ?? null) ? $goldenGateDecoded['events'] : []; } $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 ($goldenGatePlanetEvents as $goldenGateEvent) { if (!is_array($goldenGateEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($goldenGateEvent['label'] ?? 'Planet im Goldenen Tor der Ekliptik'), 'date' => (string) ($goldenGateEvent['local_date'] ?? ''), 'time' => (string) ($goldenGateEvent['local_time'] ?? ''), 'local_iso' => (string) ($goldenGateEvent['local_iso'] ?? ''), 'type' => 'golden_gate_of_ecliptic', ]; } 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

Bestehende Monatsansicht mit Berechnung per Button.

Woche

Nur vorbereitete GUI. Die Wochenberechnung ist noch nicht angeschlossen.

Monat Im Ueberblick

Zenitprojektion

,

Südhimmel

,

Mondphasen

Mondphasen des Monats in einer Zeile

Ereignisse

Chronologische Monatsliste für

Monatsvorhersage