327 lines
9.8 KiB
PHP
327 lines
9.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
ini_set('display_errors', '1');
|
|
error_reporting(E_ALL);
|
|
|
|
const AU_IN_KM = 149597870.7;
|
|
const IMPORT_END_TIME = '2046-04-12 00:00:00';
|
|
|
|
$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,
|
|
]
|
|
);
|
|
|
|
$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',
|
|
],
|
|
];
|
|
|
|
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";
|