PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); $pdo->exec(" CREATE TABLE IF NOT EXISTS `spacecraft_tracks` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `mission_key` varchar(64) NOT NULL, `mission_name` varchar(128) NOT NULL, `date_tdb` datetime NOT NULL, `jd_tdb` decimal(16,9) NOT NULL, `x_km` double NOT NULL, `y_km` double NOT NULL, `z_km` double NOT NULL, `vx_km_s` double NOT NULL, `vy_km_s` double NOT NULL, `vz_km_s` double NOT NULL, `distance_km` double NOT NULL, `distance_au` double NOT NULL, `created_at` datetime NOT NULL DEFAULT current_timestamp(), `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `uq_spacecraft_tracks_mission_date` (`mission_key`,`date_tdb`), KEY `idx_spacecraft_tracks_mission_key` (`mission_key`), KEY `idx_spacecraft_tracks_date_tdb` (`date_tdb`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Sonnenzentrierte Horizons-Vektoren fuer Raumsonden' "); $missions = [ [ 'key' => 'voyager1', 'name' => 'Voyager 1', 'command' => "'-31'", 'start_time' => '1977-09-05 14:00:00', 'dense_stop_time' => '1989-12-05 14:00:00', ], [ 'key' => 'voyager2', 'name' => 'Voyager 2', 'command' => "'-32'", 'start_time' => '1977-08-20 16:29:00', 'dense_stop_time' => '1993-03-20 16:29:00', ], [ 'key' => 'new_horizons', 'name' => 'New Horizons', 'command' => "'-98'", 'start_time' => '2006-01-19 19:52:00', 'dense_stop_time' => '2017-10-19 19:52:00', ], [ 'key' => 'pioneer10', 'name' => 'Pioneer 10', 'command' => "'-23'", 'start_time' => '1972-03-03 02:56:00', 'dense_stop_time' => '1986-11-03 02:56:00', ], ]; function fetchHorizonsResult(array $extraParams): string { $params = array_merge([ 'format' => 'json', 'OBJ_DATA' => "'NO'", 'MAKE_EPHEM' => "'YES'", 'EPHEM_TYPE' => "'VECTORS'", 'CENTER' => "'500@10'", 'REF_SYSTEM' => "'ICRF'", 'REF_PLANE' => "'ECLIPTIC'", 'VEC_TABLE' => "'2'", 'VEC_CORR' => "'NONE'", 'OUT_UNITS' => "'KM-S'", 'TIME_TYPE' => "'TDB'", 'CSV_FORMAT' => "'YES'", ], $extraParams); $query = http_build_query($params, '', '&'); $query = str_replace( ['%27', '%40', '%3A', '+'], ["'", '@', ':', '%20'], $query ); $url = 'https://ssd.jpl.nasa.gov/api/horizons.api?' . $query; $json = false; if (function_exists('curl_init')) { $curlHandle = curl_init($url); if ($curlHandle !== false) { curl_setopt_array($curlHandle, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CONNECTTIMEOUT => 30, CURLOPT_TIMEOUT => 180, CURLOPT_USERAGENT => 'skyview-spacecraft-import/1.0', ]); $curlResponse = curl_exec($curlHandle); $curlError = curl_error($curlHandle); $httpCode = (int) curl_getinfo($curlHandle, CURLINFO_RESPONSE_CODE); curl_close($curlHandle); if ($curlResponse !== false && $httpCode >= 200 && $httpCode < 300) { $json = $curlResponse; } elseif ($curlError !== '') { $json = false; } } } if ($json === false) { $context = stream_context_create([ 'http' => [ 'method' => 'GET', 'timeout' => 180, 'header' => "User-Agent: skyview-spacecraft-import/1.0\r\n", ], ]); $json = @file_get_contents($url, false, $context); } if ($json === false) { $escapedUrl = str_replace('"', '\"', $url); $command = 'curl.exe --silent --show-error --location --globoff "' . $escapedUrl . '"'; $shellResponse = shell_exec($command); if (is_string($shellResponse) && $shellResponse !== '') { $json = $shellResponse; } } if ($json === false) { throw new RuntimeException('Horizons API konnte nicht geladen werden: ' . $url); } $payload = json_decode($json, true); if (!is_array($payload)) { throw new RuntimeException('Horizons API lieferte kein gueltiges JSON.'); } $result = (string) ($payload['result'] ?? ''); if (isset($payload['error']) && $payload['error'] !== '') { throw new RuntimeException($result !== '' ? $result : (string) $payload['error']); } return $result; } function parseHorizonsVectorRows(string $result): array { $start = strpos($result, '$$SOE'); $end = strpos($result, '$$EOE'); if ($start === false || $end === false || $end <= $start) { throw new RuntimeException('Horizons-Antwort enthaelt keinen $$SOE/$$EOE-Block.'); } $block = trim(str_replace("\r", '', substr($result, $start + 5, $end - ($start + 5)))); if ($block === '') { return []; } $rows = []; foreach (explode("\n", $block) as $rawLine) { $line = trim($rawLine); if ($line === '') { continue; } $parts = array_map('trim', explode(',', $line)); if (end($parts) === '') { array_pop($parts); } if (count($parts) !== 8) { continue; } $jdTdb = (float) $parts[0]; $dateTdbRaw = (string) $parts[1]; $dateTdbNormalized = preg_replace('/^A\.D\.\s+/i', '', $dateTdbRaw) ?? $dateTdbRaw; $date = DateTimeImmutable::createFromFormat('Y-M-d H:i:s.u', $dateTdbNormalized, new DateTimeZone('UTC')); if (!$date instanceof DateTimeImmutable) { continue; } $xKm = (float) $parts[2]; $yKm = (float) $parts[3]; $zKm = (float) $parts[4]; $vxKmS = (float) $parts[5]; $vyKmS = (float) $parts[6]; $vzKmS = (float) $parts[7]; $distanceKm = sqrt(($xKm ** 2) + ($yKm ** 2) + ($zKm ** 2)); $rows[$date->format('Y-m-d H:i:s')] = [ 'date_tdb' => $date->format('Y-m-d H:i:s'), 'jd_tdb' => $jdTdb, 'x_km' => $xKm, 'y_km' => $yKm, 'z_km' => $zKm, 'vx_km_s' => $vxKmS, 'vy_km_s' => $vyKmS, 'vz_km_s' => $vzKmS, 'distance_km' => $distanceKm, 'distance_au' => $distanceKm / AU_IN_KM, ]; } ksort($rows); return array_values($rows); } $insertStatement = $pdo->prepare(" INSERT INTO `spacecraft_tracks` ( `mission_key`, `mission_name`, `date_tdb`, `jd_tdb`, `x_km`, `y_km`, `z_km`, `vx_km_s`, `vy_km_s`, `vz_km_s`, `distance_km`, `distance_au` ) VALUES ( :mission_key, :mission_name, :date_tdb, :jd_tdb, :x_km, :y_km, :z_km, :vx_km_s, :vy_km_s, :vz_km_s, :distance_km, :distance_au ) ON DUPLICATE KEY UPDATE `mission_name` = VALUES(`mission_name`), `jd_tdb` = VALUES(`jd_tdb`), `x_km` = VALUES(`x_km`), `y_km` = VALUES(`y_km`), `z_km` = VALUES(`z_km`), `vx_km_s` = VALUES(`vx_km_s`), `vy_km_s` = VALUES(`vy_km_s`), `vz_km_s` = VALUES(`vz_km_s`), `distance_km` = VALUES(`distance_km`), `distance_au` = VALUES(`distance_au`) "); $deleteStatement = $pdo->prepare('DELETE FROM `spacecraft_tracks` WHERE `mission_key` = :mission_key'); foreach ($missions as $mission) { $denseRows = parseHorizonsVectorRows(fetchHorizonsResult([ 'COMMAND' => $mission['command'], 'START_TIME' => "'" . $mission['start_time'] . "'", 'STOP_TIME' => "'" . $mission['dense_stop_time'] . "'", 'STEP_SIZE' => "'1 d'", ])); $monthlyRows = parseHorizonsVectorRows(fetchHorizonsResult([ 'COMMAND' => $mission['command'], 'START_TIME' => "'" . $mission['dense_stop_time'] . "'", 'STOP_TIME' => "'" . IMPORT_END_TIME . "'", 'STEP_SIZE' => "'1 mo'", ])); $finalRows = parseHorizonsVectorRows(fetchHorizonsResult([ 'COMMAND' => $mission['command'], 'TLIST' => "'" . IMPORT_END_TIME . "'", ])); $rowsByDate = []; foreach (array_merge($denseRows, $monthlyRows, $finalRows) as $row) { $rowsByDate[$row['date_tdb']] = $row; } ksort($rowsByDate); $rows = array_values($rowsByDate); $pdo->beginTransaction(); try { $deleteStatement->execute([ ':mission_key' => $mission['key'], ]); foreach ($rows as $row) { $insertStatement->execute([ ':mission_key' => $mission['key'], ':mission_name' => $mission['name'], ':date_tdb' => $row['date_tdb'], ':jd_tdb' => $row['jd_tdb'], ':x_km' => $row['x_km'], ':y_km' => $row['y_km'], ':z_km' => $row['z_km'], ':vx_km_s' => $row['vx_km_s'], ':vy_km_s' => $row['vy_km_s'], ':vz_km_s' => $row['vz_km_s'], ':distance_km' => $row['distance_km'], ':distance_au' => $row['distance_au'], ]); } $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } echo $mission['name'] . ': ' . count($rows) . " Zeilen importiert.\n"; } echo "Import abgeschlossen.\n";