292 lines
8.3 KiB
PHP
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);
|
|
}
|