= '0' && $value <= '9') { return (int) $value; } $upper = strtoupper($value); if ($upper >= 'A' && $upper <= 'V') { return ord($upper) - ord('A') + 10; } return null; } function decodeMpcPackedDate(?string $packed): ?string { if ($packed === null || strlen($packed) !== 5) { return null; } $centuryMap = [ 'I' => 1800, 'J' => 1900, 'K' => 2000, ]; $centuryCode = strtoupper($packed[0]); if (!isset($centuryMap[$centuryCode])) { return null; } $yearPart = substr($packed, 1, 2); if (!ctype_digit($yearPart)) { return null; } $month = decodeMpcPackedDateChar($packed[3]); $day = decodeMpcPackedDateChar($packed[4]); if ($month === null || $day === null || $month < 1 || $month > 12 || $day < 1 || $day > 31) { return null; } return sprintf('%04d-%02d-%02d', $centuryMap[$centuryCode] + (int) $yearPart, $month, $day); } function formatMinorplanetDisplayName(array $minorplanet): string { $designation = trim((string) ($minorplanet['designation'] ?? $minorplanet['designation_text'] ?? '')); $designation = preg_replace('/^\(\d+\)\s*/', '', $designation) ?? $designation; $number = $minorplanet['number'] ?? $minorplanet['mp_number'] ?? null; if ($number !== null && $number !== '') { return '(' . (int) $number . ') ' . $designation; } return $designation !== '' ? $designation : (string) ($minorplanet['packedDesignation'] ?? $minorplanet['desig_packed'] ?? '--'); } function formatCometDisplayName(array $comet): string { $designation = trim((string) ($comet['designation_and_name'] ?? $comet['designation'] ?? '')); return $designation !== '' ? $designation : (string) ($comet['designation_packed'] ?? $comet['packedDesignation'] ?? '--'); } function buildCometPerihelionIso(array $comet): ?string { $year = isset($comet['year_of_perihelion']) ? (int) $comet['year_of_perihelion'] : 0; $month = isset($comet['month_of_perihelion']) ? (int) $comet['month_of_perihelion'] : 0; $dayValue = isset($comet['day_of_perihelion']) ? (float) $comet['day_of_perihelion'] : 0.0; if ($year <= 0 || $month < 1 || $month > 12 || $dayValue <= 0.0) { return null; } $day = (int) floor($dayValue); if ($day < 1 || $day > 31) { return null; } $fractionalDay = $dayValue - $day; $seconds = (int) round($fractionalDay * 86400.0); 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))->format('c'); } function loadSpacecraftTrackFromDatabase(PDO $pdo, string $missionKey): array { $stmt = $pdo->prepare(" SELECT `date_tdb`, `x_km`, `y_km`, `z_km`, `distance_au` FROM `spacecraft_tracks` WHERE `mission_key` = :mission_key ORDER BY `date_tdb` ASC "); $stmt->execute([ ':mission_key' => $missionKey, ]); $rows = []; foreach ($stmt->fetchAll() as $row) { $timestamp = strtotime((string) ($row['date_tdb'] ?? '')); $xKm = isset($row['x_km']) ? (float) $row['x_km'] : null; $yKm = isset($row['y_km']) ? (float) $row['y_km'] : null; $zKm = isset($row['z_km']) ? (float) $row['z_km'] : null; $distanceAu = isset($row['distance_au']) ? (float) $row['distance_au'] : null; if ($timestamp === false || !is_finite((float) $xKm) || !is_finite((float) $yKm) || !is_finite((float) $zKm) || !is_finite((float) $distanceAu)) { continue; } $rows[] = [ 'timestampMs' => $timestamp * 1000, 'dateTdb' => (string) $row['date_tdb'], 'xAu' => $xKm / 149597870.7, 'yAu' => $yKm / 149597870.7, 'zAu' => $zKm / 149597870.7, 'distanceAu' => $distanceAu, ]; } return $rows; } try { $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, ] ); } catch (Throwable $e) { $pdo = null; } if ($loggedIn && $pdo instanceof PDO) { try { $stmtTrackedMinorplanets = $pdo->prepare(" SELECT mp.`id`, mp.`mp_number`, mp.`designation_text`, mp.`desig_packed`, mp.`epoch_packed`, mp.`mean_anomaly_deg`, mp.`arg_perihelion_deg`, mp.`ascending_node_deg`, mp.`inclination_deg`, mp.`eccentricity`, mp.`mean_motion_deg_per_day`, mp.`semimajor_axis_au`, mp.`absolute_magnitude_h`, mp.`slope_g`, aum.`is_favorite` FROM `app_user_minorplanets` aum INNER JOIN `minorplanets_mpc` mp ON mp.`id` = aum.`minorplanet_id` WHERE aum.`user_id` = :user_id ORDER BY aum.`is_favorite` DESC, mp.`mp_number` IS NULL ASC, mp.`mp_number` ASC, mp.`designation_text` ASC "); $stmtTrackedMinorplanets->execute([ ':user_id' => (int) $_SESSION['user_id'], ]); foreach ($stmtTrackedMinorplanets->fetchAll() as $minorplanet) { $trackedMinorplanetsForJs[] = [ 'id' => (int) $minorplanet['id'], 'number' => $minorplanet['mp_number'] !== null ? (int) $minorplanet['mp_number'] : null, 'designation' => (string) $minorplanet['designation_text'], 'packedDesignation' => (string) $minorplanet['desig_packed'], 'epochPacked' => (string) $minorplanet['epoch_packed'], 'epochDateIso' => decodeMpcPackedDate($minorplanet['epoch_packed']), 'meanAnomalyDeg' => (float) $minorplanet['mean_anomaly_deg'], 'argPerihelionDeg' => (float) $minorplanet['arg_perihelion_deg'], 'ascendingNodeDeg' => (float) $minorplanet['ascending_node_deg'], 'inclinationDeg' => (float) $minorplanet['inclination_deg'], 'eccentricity' => (float) $minorplanet['eccentricity'], 'meanMotionDegPerDay' => (float) $minorplanet['mean_motion_deg_per_day'], 'semimajorAxisAu' => (float) $minorplanet['semimajor_axis_au'], 'absoluteMagnitudeH' => $minorplanet['absolute_magnitude_h'] !== null ? (float) $minorplanet['absolute_magnitude_h'] : null, 'slopeG' => $minorplanet['slope_g'] !== null ? (float) $minorplanet['slope_g'] : null, 'isFavorite' => (bool) $minorplanet['is_favorite'], ]; } $stmtTrackedComets = $pdo->prepare(" SELECT c.`id`, c.`designation_packed`, c.`designation_and_name`, 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`, auc.`is_favorite` FROM `app_user_comets` auc INNER JOIN `comets_mpc` c ON c.`id` = auc.`comet_id` WHERE auc.`user_id` = :user_id ORDER BY auc.`is_favorite` DESC, c.`year_of_perihelion` ASC, c.`month_of_perihelion` ASC, c.`day_of_perihelion` ASC, c.`designation_and_name` ASC "); $stmtTrackedComets->execute([ ':user_id' => (int) $_SESSION['user_id'], ]); foreach ($stmtTrackedComets->fetchAll() as $comet) { $trackedCometsForJs[] = [ 'id' => (int) $comet['id'], 'designation' => (string) $comet['designation_and_name'], 'packedDesignation' => (string) $comet['designation_packed'], 'orbitType' => (string) $comet['orbit_type'], 'perihelionIso' => buildCometPerihelionIso($comet), 'perihelionDistanceAu' => (float) $comet['perihelion_dist_au'], 'eccentricity' => (float) $comet['eccentricity'], 'argPerihelionDeg' => (float) $comet['arg_perihelion_deg'], 'ascendingNodeDeg' => (float) $comet['ascending_node_deg'], 'inclinationDeg' => (float) $comet['inclination_deg'], 'absoluteMagnitudeH' => $comet['absolute_magnitude_h'] !== null ? (float) $comet['absolute_magnitude_h'] : null, 'photometricIndex' => $comet['slope_parameter_g'] !== null ? (float) $comet['slope_parameter_g'] : null, 'isFavorite' => (bool) $comet['is_favorite'], ]; } } catch (Throwable $e) { $trackedMinorplanetsForJs = []; $trackedCometsForJs = []; } } if ($pdo instanceof PDO) { try { $voyager1TrackForJs = loadSpacecraftTrackFromDatabase($pdo, 'voyager1'); $voyager2TrackForJs = loadSpacecraftTrackFromDatabase($pdo, 'voyager2'); $newHorizonsTrackForJs = loadSpacecraftTrackFromDatabase($pdo, 'new_horizons'); $pioneer10TrackForJs = loadSpacecraftTrackFromDatabase($pdo, 'pioneer10'); } catch (Throwable $e) { $voyager1TrackForJs = []; $voyager2TrackForJs = []; $newHorizonsTrackForJs = []; $pioneer10TrackForJs = []; } } if ($voyager1TrackForJs !== []) { $spacecraftOptionsForJs[] = ['key' => 'voyager1', 'label' => 'Voyager 1']; } if ($voyager2TrackForJs !== []) { $spacecraftOptionsForJs[] = ['key' => 'voyager2', 'label' => 'Voyager 2']; } if ($newHorizonsTrackForJs !== []) { $spacecraftOptionsForJs[] = ['key' => 'new_horizons', 'label' => 'New Horizons']; } if ($pioneer10TrackForJs !== []) { $spacecraftOptionsForJs[] = ['key' => 'pioneer10', 'label' => 'Pioneer 10']; } $pageTitle = 'Sonnensystem - AstroTools'; $bodyClass = 'solarsystem-page'; ?>