Files
skyview.astronomiemuseum.de/scripts/import/import_mpc_comets.php
T
2026-03-31 09:44:27 +02:00

292 lines
8.3 KiB
PHP

<?php
declare(strict_types=1);
/**
* Minor Planet Center Comet Import
* Quelle:
* https://www.minorplanetcenter.net/Extended_Files/allcometels.json.gz
*
* Zieltabelle:
* - comets_mpc
*/
ini_set('display_errors', '1');
error_reporting(E_ALL);
date_default_timezone_set('UTC');
$config = require __DIR__ . '/../../config/database.php';
$sourceUrl = 'https://www.minorplanetcenter.net/Extended_Files/allcometels.json.gz';
$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,
]
);
function fetchRemoteJson(string $url): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 20,
CURLOPT_TIMEOUT => 300,
CURLOPT_USERAGENT => 'AstroMINT MPC Comet Importer/1.0',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Accept-Encoding: gzip',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new RuntimeException('Download fehlgeschlagen: ' . curl_error($ch));
}
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
if ($httpCode !== 200) {
throw new RuntimeException('HTTP-Fehler beim Abruf: ' . $httpCode);
}
$isGzip = str_ends_with(strtolower($url), '.gz')
|| str_contains(strtolower($contentType), 'gzip')
|| str_starts_with($response, "\x1f\x8b");
if ($isGzip) {
$decoded = gzdecode($response);
if ($decoded === false) {
throw new RuntimeException('GZip-Datei konnte nicht entpackt werden');
}
$response = $decoded;
}
$data = json_decode($response, true);
if (!is_array($data)) {
throw new RuntimeException('Ungültige JSON-Antwort');
}
return $data;
}
function toNullableString(mixed $value): ?string
{
if ($value === null) {
return null;
}
$value = trim((string) $value);
return $value === '' ? null : $value;
}
function toNullableInt(mixed $value): ?int
{
if ($value === null || $value === '') {
return null;
}
if (!is_numeric($value)) {
return null;
}
return (int) $value;
}
function toNullableFloat(mixed $value): ?float
{
if ($value === null || $value === '') {
return null;
}
if (!is_numeric($value)) {
return null;
}
return (float) $value;
}
function requireIntField(array $row, string $fieldName): int
{
$value = toNullableInt($row[$fieldName] ?? null);
if ($value === null) {
throw new RuntimeException("Pflichtfeld fehlt oder ist ungültig: {$fieldName}");
}
return $value;
}
function requireFloatField(array $row, string $fieldName): float
{
$value = toNullableFloat($row[$fieldName] ?? null);
if ($value === null) {
throw new RuntimeException("Pflichtfeld fehlt oder ist ungültig: {$fieldName}");
}
return $value;
}
function requireStringField(array $row, string $fieldName): string
{
$value = toNullableString($row[$fieldName] ?? null);
if ($value === null) {
throw new RuntimeException("Pflichtfeld fehlt oder ist ungültig: {$fieldName}");
}
return $value;
}
$recordsReceived = 0;
$recordsInserted = 0;
$recordsUpdated = 0;
$recordsSkipped = 0;
try {
$rows = fetchRemoteJson($sourceUrl);
if ($rows === [] || array_is_list($rows) === false) {
throw new RuntimeException('Es wurde keine gültige Kometenliste empfangen');
}
$stmtExists = $pdo->prepare("
SELECT id
FROM comets_mpc
WHERE designation_and_name = :designation_and_name
LIMIT 1
");
$stmtUpsert = $pdo->prepare("
INSERT INTO comets_mpc (
orbit_type,
provisional_packed_desig,
year_of_perihelion,
month_of_perihelion,
day_of_perihelion,
perihelion_dist,
eccentricity,
arg_perihelion_deg,
ascending_node_deg,
inclination_deg,
absolute_magnitude_h,
slope_parameter_g,
designation_and_name,
reference_text,
raw_json,
source_url,
imported_at,
created_at,
updated_at
) VALUES (
:orbit_type,
:provisional_packed_desig,
:year_of_perihelion,
:month_of_perihelion,
:day_of_perihelion,
:perihelion_dist,
:eccentricity,
:arg_perihelion_deg,
:ascending_node_deg,
:inclination_deg,
:absolute_magnitude_h,
:slope_parameter_g,
:designation_and_name,
:reference_text,
:raw_json,
:source_url,
NOW(),
NOW(),
NOW()
)
ON DUPLICATE KEY UPDATE
orbit_type = VALUES(orbit_type),
provisional_packed_desig = VALUES(provisional_packed_desig),
year_of_perihelion = VALUES(year_of_perihelion),
month_of_perihelion = VALUES(month_of_perihelion),
day_of_perihelion = VALUES(day_of_perihelion),
perihelion_dist = VALUES(perihelion_dist),
eccentricity = VALUES(eccentricity),
arg_perihelion_deg = VALUES(arg_perihelion_deg),
ascending_node_deg = VALUES(ascending_node_deg),
inclination_deg = VALUES(inclination_deg),
absolute_magnitude_h = VALUES(absolute_magnitude_h),
slope_parameter_g = VALUES(slope_parameter_g),
reference_text = VALUES(reference_text),
raw_json = VALUES(raw_json),
source_url = VALUES(source_url),
imported_at = NOW(),
updated_at = NOW()
");
$pdo->beginTransaction();
foreach ($rows as $row) {
try {
$designationAndName = requireStringField($row, 'Designation_and_name');
$recordsReceived++;
$stmtExists->execute([
':designation_and_name' => $designationAndName,
]);
$exists = (bool) $stmtExists->fetchColumn();
$stmtUpsert->execute([
':orbit_type' => substr(requireStringField($row, 'Orbit_type'), 0, 1),
':provisional_packed_desig' => toNullableString($row['Provisional_packed_desig'] ?? null),
':year_of_perihelion' => requireIntField($row, 'Year_of_perihelion'),
':month_of_perihelion' => requireIntField($row, 'Month_of_perihelion'),
':day_of_perihelion' => requireFloatField($row, 'Day_of_perihelion'),
':perihelion_dist' => requireFloatField($row, 'Perihelion_dist'),
':eccentricity' => requireFloatField($row, 'e'),
':arg_perihelion_deg' => requireFloatField($row, 'Peri'),
':ascending_node_deg' => requireFloatField($row, 'Node'),
':inclination_deg' => requireFloatField($row, 'i'),
':absolute_magnitude_h' => toNullableFloat($row['H'] ?? null),
':slope_parameter_g' => toNullableFloat($row['G'] ?? null),
':designation_and_name' => $designationAndName,
':reference_text' => toNullableString($row['Ref'] ?? null),
':raw_json' => json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
':source_url' => $sourceUrl,
]);
if ($exists) {
$recordsUpdated++;
} else {
$recordsInserted++;
}
} catch (Throwable $rowError) {
$recordsSkipped++;
echo "Übersprungen: " . $rowError->getMessage() . "\n";
}
}
$pdo->commit();
echo "Import erfolgreich\n";
echo "Empfangen: {$recordsReceived}\n";
echo "Neu: {$recordsInserted}\n";
echo "Aktualisiert: {$recordsUpdated}\n";
echo "Übersprungen: {$recordsSkipped}\n";
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo "Fehler: " . $e->getMessage() . "\n";
exit(1);
}