'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], ]; if (!function_exists('getIsoWeeksInYear')) { function getIsoWeeksInYear(int $year): int { return (int) (new DateTimeImmutable(sprintf('%04d-12-28', $year)))->format('W'); } } if (!function_exists('parseForecastEventDateTime')) { function parseForecastEventDateTime(array $event, DateTimeZone $timezone): ?DateTimeImmutable { $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { return new DateTimeImmutable($localIso); } catch (Throwable $e) { } } $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date === '' || $time === '') { return null; } return DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!function_exists('filterForecastEventsToWeek')) { function filterForecastEventsToWeek(array $events, DateTimeImmutable $weekStartLocal, DateTimeImmutable $weekEndLocal): array { $timezone = $weekStartLocal->getTimezone(); $filtered = []; foreach ($events as $event) { if (!is_array($event)) { continue; } $moment = parseForecastEventDateTime($event, $timezone); if (!$moment instanceof DateTimeImmutable) { continue; } if ($moment < $weekStartLocal || $moment >= $weekEndLocal) { continue; } $event['local_iso'] = $moment->format(DateTimeInterface::ATOM); $event['date'] = $moment->format('d.m.Y'); $event['time'] = $moment->format('H:i'); $filtered[] = $event; } usort( $filtered, static fn (array $a, array $b): int => strcmp((string) ($a['local_iso'] ?? ''), (string) ($b['local_iso'] ?? '')) ); return $filtered; } } if (!function_exists('buildWeekForecastNarrativeText')) { function buildWeekForecastNarrativeText(array $weekEventList, string $weekLabel, DateTimeImmutable $weekStartLocal, DateTimeImmutable $weekEndLocal): string { $filteredEvents = array_values(array_filter( $weekEventList, static fn (array $item): bool => (string) ($item['type'] ?? '') !== 'favorite_satellite_pass' )); $dateLabel = $weekStartLocal->format('d.m.') . ' bis ' . $weekEndLocal->modify('-1 day')->format('d.m.Y'); if ($filteredEvents === []) { return $weekLabel . ' umfasst den Zeitraum ' . $dateLabel . '. Fuer diese Woche wurden in der aktuellen Vorschau keine markanten astronomischen Ereignisse gefunden.'; } $highlights = []; foreach (array_slice($filteredEvents, 0, 4) as $event) { $label = trim((string) ($event['event'] ?? '')); $date = trim((string) ($event['date'] ?? '')); if ($label === '' || $date === '') { continue; } $highlights[] = $label . ' am ' . $date; } $intro = $weekLabel . ' umfasst den Zeitraum ' . $dateLabel . '.'; if ($highlights === []) { return $intro . ' Es liegen Ereignisse innerhalb der Woche vor, sie konnten aber nicht kompakt zusammengefasst werden.'; } return $intro . ' Besonders interessant sind ' . implode(', ', $highlights) . '.'; } } $availableWeeks = getIsoWeeksInYear($selectedYear); $selectedWeek = max(1, min($availableWeeks, $selectedWeek)); $weeks = range(1, $availableWeeks); $starData = []; $lineStarData = []; $constellationLines = []; $messierData = []; $planetData = []; $moonData = null; $moonPhaseEvents = []; $goldenHandleEvents = []; $moonPlanetApproaches = []; $moonDeepSkyApproaches = []; $planetBrightStarApproaches = []; $mercuryGoodVisibilityEvents = []; $planetParadeEvents = []; $planetConstellationChangeEvents = []; $planetConjunctions = []; $eclipseEvents = []; $moonApsisEvents = []; $sunApsisEvents = []; $innerPlanetElongationEvents = []; $venusPeakMagnitudeEvents = []; $outerPlanetEvents = []; $outerPlanetStationEvents = []; $jupiterMoonSideEvents = []; $moonOccultations = []; $meteorShowerPeaks = []; $favoriteSatellitePasses = []; $favoriteSatelliteName = null; $favoriteSatellitePayload = []; $seasonChanges = []; $timeChanges = []; $monthEventList = []; $starDataError = null; if (!function_exists('buildMonthOccultationStarLabel')) { function buildMonthOccultationStarLabel(array $row): string { $proper = trim((string) ($row['proper'] ?? '')); if ($proper !== '') { return $proper; } $bayer = trim((string) ($row['bayer'] ?? '')); $con = trim((string) ($row['con'] ?? '')); if ($bayer !== '') { return $con !== '' ? $bayer . ' ' . $con : $bayer; } $hip = isset($row['hip']) ? (int) $row['hip'] : 0; return $hip > 0 ? 'HIP ' . $hip : 'Unbenannter Stern'; } } if (!function_exists('normalizeMonthForecastDegree')) { function normalizeMonthForecastDegree(float $value): float { $normalized = fmod($value, 360.0); if ($normalized < 0.0) { $normalized += 360.0; } return $normalized; } } if (!function_exists('monthForecastSolarLongitude')) { function monthForecastSolarLongitude(DateTimeImmutable $dateTime): float { $julianDay = ((float) $dateTime->format('U')) / 86400.0 + 2440587.5; $daysSinceJ2000 = $julianDay - 2451545.0; $meanLongitude = normalizeMonthForecastDegree(280.46 + 0.9856474 * $daysSinceJ2000); $meanAnomalyRad = deg2rad(normalizeMonthForecastDegree(357.528 + 0.9856003 * $daysSinceJ2000)); return normalizeMonthForecastDegree( $meanLongitude + 1.915 * sin($meanAnomalyRad) + 0.020 * sin(2.0 * $meanAnomalyRad) ); } } if (!function_exists('monthForecastForwardDegreeDistance')) { function monthForecastForwardDegreeDistance(float $from, float $to): float { return normalizeMonthForecastDegree($to - $from); } } if (!function_exists('monthForecastRadiantIsVisible')) { function monthForecastRadiantIsVisible(float $latitudeDeg, ?float $declinationDeg): bool { if ($declinationDeg === null) { return false; } return abs($latitudeDeg - $declinationDeg) < 90.0; } } if (!function_exists('monthForecastTleChecksum')) { function monthForecastTleChecksum(string $line): int { $sum = 0; for ($i = 0; $i < 68; $i++) { $c = $line[$i] ?? ' '; if (ctype_digit($c)) { $sum += (int) $c; } elseif ($c === '-') { $sum += 1; } } return $sum % 10; } } if (!function_exists('monthForecastTleExp')) { function monthForecastTleExp(float $value): string { if (abs($value) < 1e-99) { return ' 00000-0'; } $sign = $value >= 0 ? '+' : '-'; $abs = abs($value); $exp = (int) floor(log10($abs)) + 1; $mantissa = (int) round($abs / pow(10, $exp) * 1e5); if ($mantissa >= 100000) { $mantissa = 10000; $exp++; } $expSign = $exp >= 0 ? '+' : '-'; return sprintf('%s%05d%s%d', $sign, $mantissa, $expSign, abs($exp)); } } if (!function_exists('monthForecastTleMmDot')) { function monthForecastTleMmDot(float $value): string { $sign = $value >= 0 ? ' ' : '-'; return sprintf('%s.%08d', $sign, (int) round(abs($value) * 1e8)); } } if (!function_exists('monthForecastTleEpoch')) { function monthForecastTleEpoch(string $epochStr): string { $dt = new DateTimeImmutable($epochStr, new DateTimeZone('UTC')); $yy = (int) $dt->format('Y') % 100; $jan1 = new DateTimeImmutable($dt->format('Y') . '-01-01 00:00:00', new DateTimeZone('UTC')); $secs = (float) $dt->format('U') - (float) $jan1->format('U'); $day = $secs / 86400.0 + 1.0; return sprintf('%02d%012.8f', $yy, $day); } } if (!function_exists('monthForecastTleIntlDesig')) { function monthForecastTleIntlDesig(?string $objectId): string { if (!$objectId) { return str_repeat(' ', 8); } if (preg_match('/^(\d{4})-(\d+)([A-Z]*)$/i', trim($objectId), $match)) { $yy = substr($match[1], 2, 2); $launch = str_pad($match[2], 3, '0', STR_PAD_LEFT); $piece = str_pad(strtoupper($match[3]), 3, ' ', STR_PAD_RIGHT); return substr(str_pad($yy . $launch . $piece, 8, ' '), 0, 8); } return str_pad(substr($objectId, 0, 8), 8); } } if (!function_exists('monthForecastBuildTleLine1')) { function monthForecastBuildTleLine1(array $sat, array $tle): string { $norad = (int) ($sat['norad_cat_id'] ?? 0); $class = in_array($sat['classification_type'] ?? '', ['U', 'C', 'S'], true) ? (string) $sat['classification_type'] : 'U'; $line = sprintf( '1 %05d%1s %-8s %14s %10s %8s %8s %1d %4d', $norad, $class, monthForecastTleIntlDesig($sat['object_id'] ?? null), monthForecastTleEpoch((string) ($tle['epoch'] ?? '')), monthForecastTleMmDot((float) ($tle['mean_motion_dot'] ?? 0)), monthForecastTleExp((float) ($tle['mean_motion_ddot'] ?? 0)), monthForecastTleExp((float) ($tle['bstar'] ?? 0)), (int) ($tle['ephemeris_type'] ?? 0), (int) ($tle['element_set_no'] ?? 0) ); $line = str_pad($line, 68); return $line . monthForecastTleChecksum($line); } } if (!function_exists('monthForecastBuildTleLine2')) { function monthForecastBuildTleLine2(array $sat, array $tle): string { $norad = (int) ($sat['norad_cat_id'] ?? 0); $ecc = (float) ($tle['eccentricity'] ?? 0); $eccStr = substr(sprintf('%.7f', $ecc), 2); $line = sprintf( '2 %05d %8.4f %8.4f %7s %8.4f %8.4f %11.8f%5d', $norad, (float) ($tle['inclination'] ?? 0), (float) ($tle['ra_of_asc_node'] ?? 0), $eccStr, (float) ($tle['arg_of_pericenter'] ?? 0), (float) ($tle['mean_anomaly'] ?? 0), (float) ($tle['mean_motion'] ?? 0), (int) ($tle['rev_at_epoch'] ?? 0) ); $line = str_pad($line, 68); return $line . monthForecastTleChecksum($line); } } if (!function_exists('findMonthForecastSolarLongitudeCrossing')) { function findMonthForecastSolarLongitudeCrossing( DateTimeImmutable $rangeStartLocal, DateTimeImmutable $rangeEndLocal, float $targetLongitude, DateTimeZone $displayTimezone ): ?DateTimeImmutable { $utc = new DateTimeZone('UTC'); $rangeStartUtc = $rangeStartLocal->setTimezone($utc); $rangeEndUtc = $rangeEndLocal->setTimezone($utc); $startLongitude = monthForecastSolarLongitude($rangeStartUtc); $endLongitude = monthForecastSolarLongitude($rangeEndUtc); $targetDelta = monthForecastForwardDegreeDistance($startLongitude, $targetLongitude); $rangeDelta = monthForecastForwardDegreeDistance($startLongitude, $endLongitude); if ($targetDelta > $rangeDelta + 0.0001) { return null; } $low = (int) $rangeStartUtc->format('U'); $high = (int) $rangeEndUtc->format('U'); for ($i = 0; $i < 40; $i++) { $mid = (int) floor(($low + $high) / 2); $midLongitude = monthForecastSolarLongitude(new DateTimeImmutable('@' . $mid)); $midDelta = monthForecastForwardDegreeDistance($startLongitude, $midLongitude); if ($midDelta < $targetDelta) { $low = $mid; } else { $high = $mid; } } return (new DateTimeImmutable('@' . $high))->setTimezone($displayTimezone); } } if (!function_exists('buildObservationTipInsertSql')) { function buildObservationTipInsertSql(array $events, DateTimeZone $timezone): string { $rows = []; foreach ($events as $event) { if (!is_array($event)) { continue; } $tip = trim((string) ($event['event'] ?? '')); if ($tip === '') { continue; } $moment = null; $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { $moment = new DateTimeImmutable($localIso); } catch (Throwable $e) { $moment = null; } } if (!$moment instanceof DateTimeImmutable) { $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date !== '' && $time !== '') { $moment = DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!$moment instanceof DateTimeImmutable) { continue; } $rows[] = sprintf( "('%s', '%s')", $moment->setTimezone($timezone)->format('Y-m-d H:i:s'), str_replace("'", "''", $tip) ); } if ($rows === []) { return ''; } return "INSERT INTO `astro_sonobs_observationtips` (`moment`, `tip`)\nVALUES\n " . implode(",\n ", $rows) . ";\n"; } } if (!function_exists('escapeIcalText')) { function escapeIcalText(string $value): string { return str_replace( ["\\", ";", ",", "\r\n", "\n", "\r"], ["\\\\", "\\;", "\\,", "\\n", "\\n", "\\n"], $value ); } } if (!function_exists('foldIcalLine')) { function foldIcalLine(string $line): string { $chunks = preg_split('/(.{1,73})/u', $line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); if ($chunks === false || $chunks === []) { return $line; } $firstChunk = array_shift($chunks); return $firstChunk . ($chunks ? "\r\n " . implode("\r\n ", $chunks) : ''); } } if (!function_exists('buildObservationTipCalendarIcs')) { function buildObservationTipCalendarIcs(array $events, DateTimeZone $timezone): string { $lines = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//AstroTools//Monatsvorhersage//DE', 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-WR-CALNAME:AstroTools Monatsereignisse', ]; $createdUtc = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Ymd\THis\Z'); foreach ($events as $index => $event) { if (!is_array($event)) { continue; } $summary = trim((string) ($event['event'] ?? '')); if ($summary === '') { continue; } $moment = null; $localIso = trim((string) ($event['local_iso'] ?? '')); if ($localIso !== '') { try { $moment = new DateTimeImmutable($localIso); } catch (Throwable $e) { $moment = null; } } if (!$moment instanceof DateTimeImmutable) { $date = trim((string) ($event['date'] ?? '')); $time = trim((string) ($event['time'] ?? '')); if ($date !== '' && $time !== '') { $moment = DateTimeImmutable::createFromFormat('d.m.Y H:i', $date . ' ' . $time, $timezone) ?: null; } } if (!$moment instanceof DateTimeImmutable) { continue; } $startUtc = $moment->setTimezone(new DateTimeZone('UTC')); $endMoment = null; $endLocalIso = trim((string) ($event['end_local_iso'] ?? '')); if ($endLocalIso !== '') { try { $endMoment = new DateTimeImmutable($endLocalIso); } catch (Throwable $e) { $endMoment = null; } } if (!$endMoment instanceof DateTimeImmutable) { $durationMinutes = isset($event['duration_minutes']) ? max(1, (int) $event['duration_minutes']) : 15; $endMoment = $moment->modify('+' . $durationMinutes . ' minutes'); } $endUtc = $endMoment->setTimezone(new DateTimeZone('UTC')); $uidBase = sha1($startUtc->format(DateTimeInterface::ATOM) . '|' . $summary . '|' . (string) $index); $eventLines = [ 'BEGIN:VEVENT', 'UID:' . $uidBase . '@astrotools.local', 'DTSTAMP:' . $createdUtc, 'DTSTART:' . $startUtc->format('Ymd\THis\Z'), 'DTEND:' . $endUtc->format('Ymd\THis\Z'), 'SUMMARY:' . escapeIcalText($summary), 'DESCRIPTION:' . escapeIcalText($summary), 'END:VEVENT', ]; foreach ($eventLines as $eventLine) { $lines[] = foldIcalLine($eventLine); } } $lines[] = 'END:VCALENDAR'; return implode("\r\n", $lines) . "\r\n"; } } if (!function_exists('monthForecastBuildNarrativeText')) { function monthForecastBuildNarrativeText(array $monthEventList, int $monthNumber, string $monthName, int $year): string { $filteredEvents = array_values(array_filter( $monthEventList, static fn (array $item): bool => (string) ($item['type'] ?? '') !== 'favorite_satellite_pass' )); $skyIntroByMonth = [ 1 => 'Der Sternenhimmel im Januar wird noch von den klassischen Wintersternbildern beherrscht. Orion, Stier, Fuhrmann, Zwillinge und der Grosse Hund praegen den Abendhimmel, waehrend im Osten bereits erste Fruehlingsboten hoeher steigen.', 2 => 'Der Februar zeigt den Winterhimmel in seiner vielleicht schoensten Form. Orion, Stier, Zwillinge, Fuhrmann und Grosser Hund stehen abends eindrucksvoll ueber dem Horizont, spaeter in der Nacht ruesten sich bereits Loewe und andere Fruehlingssternbilder fuer ihren Auftritt.', 3 => 'Der Sternenhimmel im Maerz bildet den Uebergang von den Winter- zu den Fruehlingssternbildern. Abends stehen Orion, Zwillinge, Fuhrmann und Stier noch markant am Himmel, waehrend gegen Mitternacht Loewe, Jungfrau und Bootes immer mehr Aufmerksamkeit auf sich ziehen.', 4 => 'Im April dominiert der Fruehlingshimmel den Abend. Loewe, Jungfrau und Bootes stehen guenstig, waehrend die Wintersternbilder nun rasch in den Westen abwandern und am spaeteren Abend bereits Platz fuer den kommenden Sommerhimmel machen.', 5 => 'Der Mai zeigt den Fruehlingshimmel in voller Pracht. Loewe, Jungfrau, Bootes und die ausgedehnte Wasserschlange praegen die Nacht, waehrend sich im Osten schon die ersten Sternbilder des Sommers bemerkbar machen.', 6 => 'Im Juni werden die Naechte kurz, doch gerade in der spaeteren Dunkelheit treten bereits die Sommersternbilder hervor. Schwan, Leier und Adler gewinnen an Hoehe, waehrend der Fruehlingshimmel langsam nach Westen rueckt.', 7 => 'Der Juli gehoert dem Sommerhimmel. Das Sommerdreieck aus Wega, Deneb und Atair dominiert den Blick nach oben, daneben laden Schuetze, Skorpion und die reichen Sternfelder der Milchstrasse zu ausgedehnten Beobachtungen ein.', 8 => 'Im August zeigt sich der Sommerhimmel von seiner eindrucksvollsten Seite. Das Sommerdreieck steht hoch, die Milchstrasse zieht sich markant ueber den Himmel, und in der zweiten Nachthaelfte machen sich bereits erste Herbststernbilder bemerkbar.', 9 => 'Der September vermittelt zwischen Sommer- und Herbsthimmel. Das Sommerdreieck bleibt ein Blickfang, doch Pegasus, Andromeda und die herbstlichen Sternbilder steigen nun immer besser am Abendhimmel auf.', 10 => 'Im Oktober uebernimmt der Herbsthimmel die Hauptrolle. Pegasus, Andromeda, Perseus und Dreieck stehen guenstig, waehrend sich gegen Morgen schon die ersten Vorboten des Winterhimmels zeigen.', 11 => 'Der November bringt lange dunkle Naechte und einen Himmel zwischen Herbst und Winter. Pegasus und Andromeda stehen noch gut, waehrend Stier, Fuhrmann und Orion im Laufe der Nacht immer eindrucksvoller werden.', 12 => 'Im Dezember kehrt der Winterhimmel eindrucksvoll zurueck. Orion, Stier, Zwillinge, Fuhrmann und der Grosse Hund dominieren den Abend und machen den Monat zu einer besonders attraktiven Zeit fuer Sternbeobachtungen.', ]; foreach ($skyIntroByMonth as $introMonthNumber => $introText) { $monthLabel = [ 1 => 'Januar', 2 => 'Februar', 3 => 'Maerz', 4 => 'April', 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', ][$introMonthNumber] ?? $monthName; $skyIntroByMonth[$introMonthNumber] = [ $introText, $monthLabel, $monthLabel, ]; } $paragraphs = []; $monthIntroVariants = $skyIntroByMonth[$monthNumber] ?? [($monthName . ' ' . $year . ' bietet einen abwechslungsreichen Himmel zwischen den jahreszeitlichen Sternbildern.')]; $monthIntroIndex = (($year * 100) + $monthNumber) % count($monthIntroVariants); $paragraphs[] = $monthIntroVariants[$monthIntroIndex]; $findEvents = static function (array $events, callable $filter, int $limit = 3): array { $matches = []; foreach ($events as $event) { if ($filter($event)) { $matches[] = $event; if (count($matches) >= $limit) { break; } } } return $matches; }; $describeEvents = static function (array $events): string { $parts = []; foreach ($events as $event) { $label = trim((string) ($event['event'] ?? '')); $date = trim((string) ($event['date'] ?? '')); if ($label === '' || $date === '') { continue; } $parts[] = $label . ' am ' . $date; } return implode(', ', $parts); }; $moonEvents = $findEvents( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['moon_phase', 'golden_handle', 'moon_planet_approach', 'moon_deep_sky_approach', 'moon_occultation', 'moon_apsis'], true), 4 ); if ($moonEvents !== []) { $paragraphs[] = 'Auch der Mond liefert im Laufe des Monats einige markante Beobachtungsmomente. Besonders auffaellig sind ' . $describeEvents($moonEvents) . '.'; } $planetEvents = $findEvents( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['inner_planet_elongation', 'venus_peak_magnitude', 'outer_planet_event', 'outer_planet_station', 'planet_conjunction', 'planet_constellation_change', 'planet_parade', 'mercury_good_visibility'], true), 5 ); if ($planetEvents !== []) { $paragraphs[] = 'Bei den Planeten lohnt sich vor allem der Blick auf ' . $describeEvents($planetEvents) . '. Diese Termine geben dem Monat seinen charakteristischen planetaren Akzent.'; } $eclipseEvents = $findEvents( $filteredEvents, static fn (array $event): bool => in_array((string) ($event['type'] ?? ''), ['eclipse', 'lunar_eclipse', 'solar_eclipse'], true), 3 ); if ($eclipseEvents !== []) { $paragraphs[] = 'Besondere Aufmerksamkeit verdienen ausserdem die Finsternistermine. In diesem Monat stechen ' . $describeEvents($eclipseEvents) . ' hervor.'; } $meteorEvents = $findEvents( $filteredEvents, static fn (array $event): bool => (string) ($event['type'] ?? '') === 'meteor_shower_peak', 2 ); if ($meteorEvents !== []) { $paragraphs[] = 'Freunde von Meteoren sollten zudem die Aktivitaet der Meteorstroeme im Blick behalten. Erwähnenswert sind besonders ' . $describeEvents($meteorEvents) . '.'; } if (count($paragraphs) === 1 && $filteredEvents !== []) { $highlights = array_slice($filteredEvents, 0, 4); $paragraphs[] = 'Zu den auffaelligen Terminen des Monats gehoeren ' . $describeEvents($highlights) . '.'; } $paragraphs[] = 'Die chronologische Ereignisliste darunter dient als kompakte Terminuebersicht; automatische ISS- und Favoriten-Satellitenueberfluege bleiben in diesem Beschreibungstext bewusst unberuecksichtigt.'; 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'])) { $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'); $weekStartLocal = (new DateTimeImmutable('now', new DateTimeZone($chartTimezone)))->setISODate($selectedYear, $selectedWeek)->setTime(0, 0, 0); $weekEndLocal = $weekStartLocal->modify('+7 days'); $weekMidLocal = $weekStartLocal->modify('+3 days'); $chartMonth = (int) $weekMidLocal->format('n'); $chartYear = (int) $weekMidLocal->format('Y'); $weekLabel = sprintf('KW %02d / %04d', $selectedWeek, $selectedYear); $monthTime = $monthSkyTimes[$chartMonth]; $chartLocalTime = new DateTimeImmutable( sprintf( '%04d-%02d-%02d %02d:%02d:00', $chartYear, $chartMonth, (int) $weekMidLocal->format('d'), $monthTime['hour'], $monthTime['minute'] ), new DateTimeZone($chartTimezone) ); $chartUtcTime = $chartLocalTime->setTimezone(new DateTimeZone('UTC')); $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) $chartYear, (string) $chartMonth, (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, $weekStartLocal->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s\Z'), $weekEndLocal->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'] : []; } } $moonPhaseOutput = []; $moonPhaseExitCode = 0; $moonPhaseArgs = [ $pythonScriptPath, 'moon_phases_for_month', (string) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $chartTimezone, (string) $planetBrightStarMaxSeparationDeg, ]; $planetBrightStarCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $planetBrightStarArgs)) . ' 2>&1'; exec($planetBrightStarCommand, $planetBrightStarOutput, $planetBrightStarExitCode); $planetBrightStarRaw = trim(implode("\n", $planetBrightStarOutput)); $planetBrightStarDecoded = json_decode($planetBrightStarRaw, true); if (is_array($planetBrightStarDecoded) && ($planetBrightStarDecoded['ok'] ?? false)) { $planetBrightStarApproaches = is_array($planetBrightStarDecoded['approaches'] ?? null) ? $planetBrightStarDecoded['approaches'] : []; } $planetConjunctionOutput = []; $planetConjunctionExitCode = 0; $planetConjunctionArgs = [ $pythonScriptPath, 'planet_conjunctions_for_month', (string) $chartLatitude, (string) $chartLongitude, (string) $chartElevation, (string) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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) $chartYear, (string) $chartMonth, $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; } $monthEventList[] = [ 'event' => (string) ($phaseEvent['label'] ?? ''), '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 zurueckstellen)' : '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; } $durationLabel = trim((string) ($jupiterMoonSideEvent['duration_label'] ?? '')); $eventLabel = trim((string) ($jupiterMoonSideEvent['label'] ?? 'Alle 4 Jupitermonde auf einer Seite von Jupiter')); if ($durationLabel !== '') { $eventLabel .= ' (' . $durationLabel . ')'; } $monthEventList[] = [ 'event' => $eventLabel, 'date' => (string) ($jupiterMoonSideEvent['local_date'] ?? ''), 'time' => (string) ($jupiterMoonSideEvent['local_time'] ?? ''), 'local_iso' => (string) ($jupiterMoonSideEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($jupiterMoonSideEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($jupiterMoonSideEvent['duration_minutes']) ? (int) $jupiterMoonSideEvent['duration_minutes'] : null, 'type' => 'jupiter_moons_one_side', ]; } foreach ($moonPlanetApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) $approachEvent['separation_deg'], 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Mond nahe Planet') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'moon_planet', ]; } foreach ($moonDeepSkyApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) ($approachEvent['separation_deg']), 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Mond nahe Sternhaufen') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'moon_deep_sky', ]; } foreach ($planetBrightStarApproaches as $approachEvent) { if (!is_array($approachEvent)) { continue; } $distanceText = isset($approachEvent['separation_deg']) ? number_format((float) ($approachEvent['separation_deg']), 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($approachEvent['label'] ?? 'Planet nahe hellem Stern') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($approachEvent['local_date'] ?? ''), 'time' => (string) ($approachEvent['local_time'] ?? ''), 'local_iso' => (string) ($approachEvent['local_iso'] ?? ''), 'type' => 'planet_bright_star', ]; } foreach ($planetConjunctions as $conjunctionEvent) { if (!is_array($conjunctionEvent)) { continue; } $distanceText = isset($conjunctionEvent['separation_deg']) ? number_format((float) $conjunctionEvent['separation_deg'], 2, ',', '') . ' Grad' : ''; $monthEventList[] = [ 'event' => trim((string) ($conjunctionEvent['label'] ?? 'Planetenkonjunktion') . ($distanceText !== '' ? ' (' . $distanceText . ')' : '')), 'date' => (string) ($conjunctionEvent['local_date'] ?? ''), 'time' => (string) ($conjunctionEvent['local_time'] ?? ''), 'local_iso' => (string) ($conjunctionEvent['local_iso'] ?? ''), 'type' => 'planet_conjunction', ]; } foreach ($mercuryGoodVisibilityEvents as $visibilityEvent) { if (!is_array($visibilityEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($visibilityEvent['label'] ?? 'Gute Merkur-Sichtbarkeit'), 'date' => (string) ($visibilityEvent['local_date'] ?? ''), 'time' => (string) ($visibilityEvent['local_time'] ?? ''), 'local_iso' => (string) ($visibilityEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($visibilityEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($visibilityEvent['duration_minutes']) ? (int) $visibilityEvent['duration_minutes'] : null, 'type' => 'mercury_good_visibility', ]; } foreach ($planetParadeEvents as $paradeEvent) { if (!is_array($paradeEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($paradeEvent['label'] ?? 'Planetenparade'), 'date' => (string) ($paradeEvent['local_date'] ?? ''), 'time' => (string) ($paradeEvent['local_time'] ?? ''), 'local_iso' => (string) ($paradeEvent['local_iso'] ?? ''), 'end_local_iso' => (string) ($paradeEvent['end_local_iso'] ?? ''), 'duration_minutes' => isset($paradeEvent['duration_minutes']) ? (int) $paradeEvent['duration_minutes'] : null, 'type' => 'planet_parade', ]; } foreach ($planetConstellationChangeEvents as $changeEvent) { if (!is_array($changeEvent)) { continue; } $monthEventList[] = [ 'event' => (string) ($changeEvent['label'] ?? 'Planet wechselt Sternbild'), 'date' => (string) ($changeEvent['local_date'] ?? ''), 'time' => (string) ($changeEvent['local_time'] ?? ''), 'local_iso' => (string) ($changeEvent['local_iso'] ?? ''), 'type' => 'planet_constellation_change', ]; } 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( $weekStartLocal, $weekEndLocal, $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'] ?? '')); $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), '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 Ueberflug ' . ($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'] ?? '')) ); $monthEventList = filterForecastEventsToWeek($monthEventList, $weekStartLocal, $weekEndLocal); $moonPhaseEvents = filterForecastEventsToWeek($moonPhaseEvents, $weekStartLocal, $weekEndLocal); $monthNarrativeText = buildWeekForecastNarrativeText($monthEventList, $weekLabel, $weekStartLocal, $weekEndLocal); $observationTipInsertSql = buildObservationTipInsertSql($monthEventList, new DateTimeZone($chartTimezone)); $observationTipCalendarIcs = buildObservationTipCalendarIcs($monthEventList, new DateTimeZone($chartTimezone)); $chartConfig = [ 'monthName' => $months[$chartMonth], 'year' => $chartYear, 'month' => $chartMonth, 'weekLabel' => $weekLabel, 'weekNumber' => $selectedWeek, 'weekStartIso' => $weekStartLocal->format('Y-m-d'), 'weekEndExclusiveIso' => $weekEndLocal->format('Y-m-d'), '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); $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-wochenereignisse-%04d-w%02d.ics', $selectedYear, $selectedWeek), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>

Zeitraum wählen

Die Sternkarte wird fuer die Mitte der gewaehlten ISO-Woche mit der saisonal passenden Uhrzeit erzeugt.

Woche Im Ueberblick

format('d.m.') . ' bis ' . $weekEndLocal->modify('-1 day')->format('d.m.Y'), ENT_QUOTES, 'UTF-8') ?>

Zenitprojektion

,

Südhimmel

,

Mondphasen der gewaehlten Woche in einer Zeile

Ereignisse

Chronologische Wochenliste fuer