PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); $loggedIn = true; $pageTitle = 'Satellitenhimmel - AstroTools'; $bodyClass = 'satpass-page'; $userId = (int)$_SESSION['user_id']; function h(?string $value): string { return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); } // ── Satelliten aus DB laden ────────────────────────────────────────────────── function tleChecksum(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; } function tleExp(float $val): string { if (abs($val) < 1e-99) { return ' 00000-0'; } $sign = $val >= 0 ? '+' : '-'; $abs = abs($val); $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)); } function tleMmDot(float $val): string { $sign = $val >= 0 ? ' ' : '-'; return sprintf('%s.%08d', $sign, (int)round(abs($val) * 1e8)); } function tleEpoch(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); } function tleIntlDesig(?string $objectId): string { if (!$objectId) { return str_repeat(' ', 8); } if (preg_match('/^(\d{4})-(\d+)([A-Z]*)$/i', trim($objectId), $m)) { $yy = substr($m[1], 2, 2); $launch = str_pad($m[2], 3, '0', STR_PAD_LEFT); $piece = str_pad(strtoupper($m[3]), 3, ' ', STR_PAD_RIGHT); return substr(str_pad($yy . $launch . $piece, 8, ' '), 0, 8); } return str_pad(substr($objectId, 0, 8), 8); } function buildLine1(array $sat, array $tle): string { $norad = (int)$sat['norad_cat_id']; $class = in_array($sat['classification_type'] ?? '', ['U', 'C', 'S'], true) ? $sat['classification_type'] : 'U'; $line = sprintf( '1 %05d%1s %-8s %14s %10s %8s %8s %1d %4d', $norad, $class, tleIntlDesig($sat['object_id'] ?? null), tleEpoch((string)$tle['epoch']), tleMmDot((float)($tle['mean_motion_dot'] ?? 0)), tleExp((float)($tle['mean_motion_ddot'] ?? 0)), tleExp((float)($tle['bstar'] ?? 0)), (int)($tle['ephemeris_type'] ?? 0), (int)($tle['element_set_no'] ?? 0) ); $line = str_pad($line, 68); return $line . tleChecksum($line); } function buildLine2(array $sat, array $tle): string { $norad = (int)$sat['norad_cat_id']; $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 . tleChecksum($line); } // Standard-Standort vorab laden (bevor header.php eingebunden wird) $defaultLocation = null; $stmtDefLoc = $pdo->prepare( 'SELECT id, name, latitude, longitude, elevation, timezone FROM app_user_locations WHERE user_id = ? AND is_default = 1 LIMIT 1' ); $stmtDefLoc->execute([$userId]); $defaultLocation = $stmtDefLoc->fetch() ?: null; $stmtTracked = $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`, aus.`is_favorite`, 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` aus INNER JOIN `sat_satellites` s ON s.`id` = aus.`satellite_id` INNER JOIN `sat_tle_current` t ON t.`satellite_id` = s.`id` WHERE aus.`user_id` = :user_id AND t.`epoch` IS NOT NULL AND t.`mean_motion` IS NOT NULL ORDER BY aus.`is_favorite` DESC, s.`object_name` ASC "); $stmtTracked->execute([':user_id' => $userId]); $trackedSatellites = $stmtTracked->fetchAll(); $satellitePayload = []; foreach ($trackedSatellites as $row) { $satellitePayload[] = [ 'id' => (int)$row['id'], 'norad_cat_id' => (int)$row['norad_cat_id'], 'object_name' => (string)$row['object_name'], 'is_favorite' => (int)$row['is_favorite'] === 1, 'std_mag' => $row['std_mag'] !== null ? (float)$row['std_mag'] : null, 'rcs_size' => $row['rcs_size'] !== null ? (string)$row['rcs_size'] : null, 'rcs' => $row['rcs'] !== null && (float)$row['rcs'] > 0 ? (float)$row['rcs'] : null, 'tle_line1' => buildLine1($row, $row), 'tle_line2' => buildLine2($row, $row), ]; } // ── Sterne & Sternbilder ───────────────────────────────────────────────────── $stmtStars = $pdo->query(" SELECT `proper`, `bf`, `ra`, `dec`, `mag` FROM `star_hipparcos` WHERE `mag` <= 5.0 AND `ra` IS NOT NULL AND `dec` IS NOT NULL ORDER BY `mag` ASC "); $brightStars = $stmtStars->fetchAll(); $starPayload = array_map(fn($r) => [ 'name' => (string)($r['proper'] ?: ($r['bf'] ?: '')), 'ra' => (float)$r['ra'] * 15.0, 'dec' => (float)$r['dec'], 'mag' => (float)$r['mag'], ], $brightStars); $stmtLines = $pdo->query(" SELECT cl.`id` AS line_id, clp.`point_index`, sh.`ra` * 15.0 AS ra_deg, sh.`dec` AS dec_deg FROM `constellation_lines` cl INNER JOIN `constellation_line_points` clp ON clp.`line_id` = cl.`id` INNER JOIN `star_hipparcos` sh ON sh.`hip` = clp.`hip_number` WHERE sh.`ra` IS NOT NULL AND sh.`dec` IS NOT NULL ORDER BY cl.`id`, clp.`point_index` "); $lineRows = $stmtLines->fetchAll(); $constellationLines = []; foreach ($lineRows as $row) { $lid = (int)$row['line_id']; $constellationLines[$lid][] = ['ra' => (float)$row['ra_deg'], 'dec' => (float)$row['dec_deg']]; } $constellationPayload = array_values($constellationLines); $stmtConstellationLabels = $pdo->query(" SELECT clp.`constellation_id`, clp.`position_index`, clp.`ra_hours` * 15.0 AS ra_deg, clp.`dec_degrees` AS dec_deg, cn.`german_name`, cn.`native_name`, cn.`byname` FROM `constellation_label_positions` clp LEFT JOIN `constellation_names` cn ON cn.`constellation_id` = clp.`constellation_id` ORDER BY clp.`constellation_id`, clp.`position_index` "); $constellationLabelRows = $stmtConstellationLabels->fetchAll(); $constellationLabelPayload = array_map(static function (array $row): array { $label = trim((string)($row['german_name'] ?: $row['native_name'] ?: $row['byname'] ?: $row['constellation_id'])); return [ 'id' => (string)$row['constellation_id'], 'label' => $label, 'ra' => (float)$row['ra_deg'], 'dec' => (float)$row['dec_deg'], 'position_index' => (int)$row['position_index'], ]; }, $constellationLabelRows); // ── Python aufrufen ────────────────────────────────────────────────────────── $pythonResult = null; $pythonError = null; if (!empty($satellitePayload) && !empty($defaultLocation['latitude'])) { $loc = $defaultLocation; $lat = (float)$loc['latitude']; $lon = (float)$loc['longitude']; $elev = (float)($loc['elevation'] ?? 0); $tz = (string)($loc['timezone'] ?? 'Europe/Berlin'); // 7-Tage-Fenster: lokaler Tagesbeginn heute → +7 Tage $tzObj = new DateTimeZone($tz); $nowLocal = new DateTimeImmutable('now', $tzObj); $dayStart = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $nowLocal->format('Y-m-d') . ' 00:00:00', $tzObj); $dayEnd = $dayStart->modify('+7 days 23:59:59'); $windowStart = $dayStart->setTimezone(new DateTimeZone('UTC'))->format(DateTime::ATOM); $windowEnd = $dayEnd->setTimezone(new DateTimeZone('UTC'))->format(DateTime::ATOM); $scriptPath = realpath(__DIR__ . '/py/api.py'); $satellitesJson = json_encode($satellitePayload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $args = [ '/usr/bin/python', escapeshellarg($scriptPath), escapeshellarg('satellite_passes'), escapeshellarg((string)$lat), escapeshellarg((string)$lon), escapeshellarg((string)$elev), escapeshellarg($tz), escapeshellarg($windowStart), escapeshellarg($windowEnd), escapeshellarg($satellitesJson), ]; $command = implode(' ', $args) . ' 2>&1'; $output = []; $code = 0; exec($command, $output, $code); $joined = implode('', $output); $decoded = json_decode($joined, true); if (is_array($decoded) && ($decoded['ok'] ?? false)) { $pythonResult = $decoded; } else { $pythonError = $joined ?: 'Leere Antwort von Python.'; } } ?> (string)($currentLocation['name'] ?? ''), 'latitude' => isset($currentLocation['latitude']) ? (float)$currentLocation['latitude'] : null, 'longitude' => isset($currentLocation['longitude']) ? (float)$currentLocation['longitude'] : null, 'elevation' => isset($currentLocation['elevation']) && $currentLocation['elevation'] !== null ? (float)$currentLocation['elevation'] : 0.0, 'timezone' => (string)($currentLocation['timezone'] ?? 'Europe/Berlin'), ]; } ?>
Berechnet fuer den heutigen Tag den naechsten Ueberflug deiner ausgewaehlten Satelliten und zeichnet deren Bahn auf einer Zenitkarte fuer deinen Standard-Standort.
Bitte waehle in den Einstellungen einen Standard-Standort aus.
Es sind noch keine ausgewaehlten Satelliten mit aktuellem TLE vorhanden. Bitte fuege in Favoriten Satelliten hinzu.