&1'; $output = []; $resultCode = 0; exec($command, $output, $resultCode); $joined = trim(implode("\n", $output)); $decoded = json_decode($joined, true); if (is_array($decoded)) { return [ 'ok' => (bool) ($decoded['ok'] ?? false), 'data' => $decoded, 'raw' => $joined, 'command' => $command, ]; } if ($joined !== '') { $lastError = $joined; } elseif ($resultCode !== 0) { $lastError = 'Fehlercode ' . $resultCode . ' bei ' . $pythonBinary; } } return [ 'ok' => false, 'error' => $lastError, ]; } function runPythonApiWithJsonPayloadFile(string $action, array $payload): array { $tempFile = tempnam(sys_get_temp_dir(), 'skyview_py_'); if ($tempFile === false) { return [ 'ok' => false, 'error' => 'Temporäre Datei für Python-Payload konnte nicht erstellt werden.', ]; } $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($jsonPayload === false) { @unlink($tempFile); return [ 'ok' => false, 'error' => 'Python-Payload konnte nicht als JSON kodiert werden.', ]; } if (file_put_contents($tempFile, $jsonPayload) === false) { @unlink($tempFile); return [ 'ok' => false, 'error' => 'Temporäre Datei für Python-Payload konnte nicht geschrieben werden.', ]; } try { return runPythonApi($action, ['@' . $tempFile]); } finally { @unlink($tempFile); } } function formatDecimalGerman(?float $value, int $decimals = 3): string { if ($value === null || !is_finite($value)) { return '-'; } return number_format($value, $decimals, ',', '.'); } function formatRaHours(?float $hours): string { if ($hours === null || !is_finite($hours)) { return '-'; } $normalized = fmod($hours, 24.0); if ($normalized < 0.0) { $normalized += 24.0; } $totalSeconds = (int) round($normalized * 3600.0); $raHours = intdiv($totalSeconds, 3600) % 24; $raMinutes = intdiv($totalSeconds % 3600, 60); $raSeconds = $totalSeconds % 60; return sprintf('%02dh %02dm %02ds', $raHours, $raMinutes, $raSeconds); } function formatDeclination(?float $degrees): string { if ($degrees === null || !is_finite($degrees)) { return '-'; } $sign = $degrees < 0.0 ? '-' : '+'; $absolute = abs($degrees); $deg = (int) floor($absolute); $minutesFloat = ($absolute - $deg) * 60.0; $minutes = (int) floor($minutesFloat); $seconds = (int) round(($minutesFloat - $minutes) * 60.0); if ($seconds >= 60) { $seconds -= 60; $minutes += 1; } if ($minutes >= 60) { $minutes -= 60; $deg += 1; } return sprintf('%s%02d° %02d\' %02d"', $sign, $deg, $minutes, $seconds); } function buildPerihelionDate(array $row): ?DateTimeImmutable { $year = isset($row['year_of_perihelion']) ? (int) $row['year_of_perihelion'] : 0; $month = isset($row['month_of_perihelion']) ? (int) $row['month_of_perihelion'] : 0; $dayValue = isset($row['day_of_perihelion']) ? (float) $row['day_of_perihelion'] : 0.0; if ($year === 0 || $month < 1 || $month > 12 || $dayValue <= 0) { return null; } $day = (int) floor($dayValue); if ($day < 1 || $day > 31) { return null; } $fractionalDay = $dayValue - $day; $seconds = (int) round($fractionalDay * 86400); try { $date = new DateTimeImmutable(sprintf('%04d-%02d-%02d 00:00:00', $year, $month, $day), new DateTimeZone('UTC')); } catch (Throwable $e) { return null; } return $date->modify(sprintf('+%d seconds', $seconds)); } function classifyOrbitType(string $orbitType): string { return match (strtoupper(trim($orbitType))) { 'P' => 'Periodisch', 'C' => 'Nicht periodisch', 'I' => 'Interstellar', 'D' => 'Verschollen / zerfallen', 'A' => 'Asteroidal', default => trim($orbitType) !== '' ? trim($orbitType) : '-', }; } $config = require __DIR__ . '/../config/database.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=%s', $config['host'], $config['dbname'], $config['charset'] ?? 'utf8mb4' ); $pdo = new PDO( $dsn, $config['user'], $config['pass'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); $userId = isset($_SESSION['user_id']) ? (int) $_SESSION['user_id'] : 0; $today = new DateTimeImmutable('today', new DateTimeZone('UTC')); $windowStart = $today->modify('-180 days'); $windowEnd = $today->modify('+365 days'); $queryYearMin = (int) $windowStart->format('Y') - 1; $queryYearMax = (int) $windowEnd->format('Y') + 1; $stmtComets = $pdo->prepare(" SELECT c.*, CASE WHEN auc.`comet_id` IS NULL THEN 0 ELSE 1 END AS `is_tracked`, CASE WHEN auc.`is_favorite` = 1 THEN 1 ELSE 0 END AS `is_favorite` FROM `comets_mpc` c LEFT JOIN `app_user_comets` auc ON auc.`comet_id` = c.`id` AND auc.`user_id` = :user_id WHERE c.`year_of_perihelion` BETWEEN :year_min AND :year_max OR auc.`comet_id` IS NOT NULL ORDER BY c.`year_of_perihelion` ASC, c.`month_of_perihelion` ASC, c.`day_of_perihelion` ASC, c.`designation_and_name` ASC "); $stmtComets->execute([ ':user_id' => $userId, ':year_min' => $queryYearMin, ':year_max' => $queryYearMax, ]); $cometRows = $stmtComets->fetchAll(); $allComets = []; $trackedComets = []; $interestingComets = []; foreach ($cometRows as $row) { $perihelionDate = buildPerihelionDate($row); $daysFromToday = $perihelionDate ? (int) floor(($perihelionDate->getTimestamp() - $today->getTimestamp()) / 86400) : null; $row['perihelion_date_object'] = $perihelionDate; $row['perihelion_date_display'] = $perihelionDate ? $perihelionDate->format('d.m.Y H:i') . ' UTC' : '-'; $row['days_from_today'] = $daysFromToday; $row['orbit_type_label'] = classifyOrbitType((string) ($row['orbit_type'] ?? '')); $row['absolute_magnitude_display'] = isset($row['absolute_magnitude_h']) && $row['absolute_magnitude_h'] !== null ? formatDecimalGerman((float) $row['absolute_magnitude_h'], 1) : '-'; $row['photometric_index_display'] = isset($row['slope_parameter_g']) && $row['slope_parameter_g'] !== null ? formatDecimalGerman((float) $row['slope_parameter_g'], 1) : '-'; $row['estimated_magnitude'] = null; $row['estimated_magnitude_display'] = '-'; $row['brightness_error'] = null; $allComets[] = $row; if ((int) ($row['is_tracked'] ?? 0) === 1) { $trackedComets[] = $row; } if ($perihelionDate !== null && $perihelionDate >= $windowStart && $perihelionDate <= $windowEnd) { $interestingComets[] = $row; } } usort($trackedComets, static function (array $left, array $right): int { $favoriteCompare = ((int) $right['is_favorite']) <=> ((int) $left['is_favorite']); if ($favoriteCompare !== 0) { return $favoriteCompare; } return ((int) ($left['days_from_today'] ?? PHP_INT_MAX)) <=> ((int) ($right['days_from_today'] ?? PHP_INT_MAX)); }); usort($interestingComets, static function (array $left, array $right): int { $leftDistance = abs((int) ($left['days_from_today'] ?? PHP_INT_MAX)); $rightDistance = abs((int) ($right['days_from_today'] ?? PHP_INT_MAX)); if ($leftDistance !== $rightDistance) { return $leftDistance <=> $rightDistance; } return strcmp((string) $left['designation_and_name'], (string) $right['designation_and_name']); }); $interestingComets = array_slice($interestingComets, 0, 24); $visibleCometIds = []; foreach (array_merge($trackedComets, $interestingComets) as $visibleComet) { $visibleId = isset($visibleComet['id']) ? (int) $visibleComet['id'] : 0; if ($visibleId > 0) { $visibleCometIds[$visibleId] = true; } } $calculationMoment = new DateTimeImmutable('now', new DateTimeZone('UTC')); $brightnessByCometId = []; $brightnessError = null; if ($visibleCometIds !== []) { $pythonPayload = [ 'date' => $calculationMoment->format('Y-m-d\TH:i:s\Z'), 'comets' => array_values(array_map(static function (array $row): array { return [ 'id' => $row['id'] ?? null, 'year_of_perihelion' => $row['year_of_perihelion'] ?? null, 'month_of_perihelion' => $row['month_of_perihelion'] ?? null, 'day_of_perihelion' => $row['day_of_perihelion'] ?? null, 'perihelion_dist_au' => $row['perihelion_dist_au'] ?? null, 'eccentricity' => $row['eccentricity'] ?? null, 'arg_perihelion_deg' => $row['arg_perihelion_deg'] ?? null, 'ascending_node_deg' => $row['ascending_node_deg'] ?? null, 'inclination_deg' => $row['inclination_deg'] ?? null, 'absolute_magnitude_h' => $row['absolute_magnitude_h'] ?? null, 'slope_parameter_g' => $row['slope_parameter_g'] ?? null, ]; }, array_filter($cometRows, static function (array $row) use ($visibleCometIds): bool { $rowId = isset($row['id']) ? (int) $row['id'] : 0; return $rowId > 0 && isset($visibleCometIds[$rowId]); }))), ]; $pythonResult = runPythonApiWithJsonPayloadFile('comet_brightnesses', $pythonPayload); if (($pythonResult['ok'] ?? false) && isset($pythonResult['data']['results']) && is_array($pythonResult['data']['results'])) { foreach ($pythonResult['data']['results'] as $result) { $resultId = isset($result['id']) ? (int) $result['id'] : 0; if ($resultId > 0) { $brightnessByCometId[$resultId] = $result; } } } else { $brightnessError = (string) ($pythonResult['error'] ?? 'Python-Berechnung nicht verfuegbar.'); } } $applyBrightness = static function (array $row) use ($brightnessByCometId): array { $brightnessData = isset($row['id']) ? ($brightnessByCometId[(int) $row['id']] ?? null) : null; $estimatedMagnitude = isset($brightnessData['estimated_magnitude']) && is_numeric($brightnessData['estimated_magnitude']) ? (float) $brightnessData['estimated_magnitude'] : null; $raHours = isset($brightnessData['ra_hours']) && is_numeric($brightnessData['ra_hours']) ? (float) $brightnessData['ra_hours'] : null; $decDeg = isset($brightnessData['dec_deg']) && is_numeric($brightnessData['dec_deg']) ? (float) $brightnessData['dec_deg'] : null; $heliocentricDistance = isset($brightnessData['heliocentric_distance_au']) && is_numeric($brightnessData['heliocentric_distance_au']) ? (float) $brightnessData['heliocentric_distance_au'] : null; $geocentricDistance = isset($brightnessData['geocentric_distance_au']) && is_numeric($brightnessData['geocentric_distance_au']) ? (float) $brightnessData['geocentric_distance_au'] : null; $row['estimated_magnitude'] = $estimatedMagnitude; $row['estimated_magnitude_display'] = $estimatedMagnitude !== null ? formatDecimalGerman($estimatedMagnitude, 1) : '-'; $row['ra_hours'] = $raHours; $row['ra_display'] = formatRaHours($raHours); $row['dec_deg'] = $decDeg; $row['dec_display'] = formatDeclination($decDeg); $row['heliocentric_distance_au'] = $heliocentricDistance; $row['heliocentric_distance_display'] = formatDecimalGerman($heliocentricDistance, 3); $row['geocentric_distance_au'] = $geocentricDistance; $row['geocentric_distance_display'] = formatDecimalGerman($geocentricDistance, 3); $row['brightness_error'] = is_array($brightnessData) && !empty($brightnessData['error']) ? (string) $brightnessData['error'] : null; return $row; }; $trackedComets = array_map($applyBrightness, $trackedComets); $interestingComets = array_map($applyBrightness, $interestingComets); $orbitTypeCounts = []; foreach ($allComets as $comet) { $orbitKey = (string) ($comet['orbit_type'] ?? '?'); $orbitTypeCounts[$orbitKey] = ($orbitTypeCounts[$orbitKey] ?? 0) + 1; } arsort($orbitTypeCounts); ?>
Diese erste Version liest die vorhandenen Kometen direkt aus comets_mpc und hebt Objekte hervor,
deren Perihel zwischen = h($windowStart->format('d.m.Y')) ?> und
= h($windowEnd->format('d.m.Y')) ?> liegt.
Nutzer-Favoriten aus app_user_comets werden zusaetzlich separat gezeigt.
Die Spalte m grob nutzt jetzt die uebliche MPC-Kometenformel fuer heute:
H + 5 log(Delta) + 2,5 * n * log(r). Sie bleibt trotzdem nur eine Orientierung.
Position und Helligkeit werden fuer = h($calculationMoment->format('d.m.Y H:i')) ?> UTC berechnet.
Die Python-Berechnung der Helligkeit war nicht verfuegbar: = h($brightnessError) ?>
Du hast aktuell noch keine Kometen in deinen Favoriten. In Favoriten kannst du welche markieren.
| Komet | Typ | RA | Dec | Perihel | Abstand | m grob |
|---|---|---|---|---|---|---|
|
= h((string) $comet['designation_and_name']) ?>
|
= h((string) $comet['orbit_type_label']) ?> | = h((string) $comet['ra_display']) ?> | = h((string) $comet['dec_display']) ?> | = h((string) $comet['perihelion_date_display']) ?> | = h($daysLabel) ?> | = h((string) $comet['estimated_magnitude_display']) ?> |
Sortiert nach Naehe zum heutigen Datum bezogen auf den Periheldurchgang.
Im aktuellen Zeitfenster wurden keine Kometen gefunden.
| Komet | Typ | RA | Dec | Perihel | Abstand | m grob |
|---|---|---|---|---|---|---|
|
= h((string) $comet['designation_and_name']) ?>
|
= h((string) $comet['orbit_type_label']) ?> | = h((string) $comet['ra_display']) ?> | = h((string) $comet['dec_display']) ?> | = h((string) $comet['perihelion_date_display']) ?> | = h($daysLabel) ?> | = h((string) $comet['estimated_magnitude_display']) ?> |