first commit
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
<?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);
|
||||
}
|
||||
Reference in New Issue
Block a user