format('Y-m-d'); } } return null; } function isSatelliteCraterRow(array $assoc, ?string $csvPath = null): bool { $nameType = normalizeImportValue($assoc['NAME_TYPE'] ?? null); if ($nameType !== null && strcasecmp($nameType, 'Satellite feature') === 0) { return true; } if ($csvPath !== null) { $filename = basename($csvPath); if (stripos($filename, 'SATELLITE') !== false) { return true; } } return false; } function deriveParentCraterName(string $name, bool $isSatelliteCrater): ?string { if (!$isSatelliteCrater) { return null; } if (preg_match('/^(.*?)\s+([A-Z])$/', $name, $matches) === 1) { $parentName = trim($matches[1]); return $parentName !== '' ? $parentName : null; } return null; } function translateCatalogValue(string $field, ?string $value): ?string { $value = normalizeImportValue($value); if ($value === null) { return null; } static $maps = [ 'iau_type' => [ 'Crater' => 'Krater', ], 'type' => [ 'Crater' => 'Krater', ], 'subtype' => [ 'Not determined' => 'Nicht bestimmt', ], 'process' => [ 'Meteoritic impact' => 'Meteoriteneinschlag', ], 'period' => [ 'Formation period not found' => 'Entstehungszeit nicht bekannt', 'Pre-Nectarian (From -4.55 billions years to -3.92 billions years)' => 'Prä-Nektarisch (von -4,55 bis -3,92 Milliarden Jahre)', 'Nectarian (From -3.92 billions years to -3.85 billions years)' => 'Nektarisch (von -3,92 bis -3,85 Milliarden Jahre)', 'Imbrian (From -3.85 billions years to -3.2 billions years) ?' => 'Imbrisch (von -3,85 bis -3,2 Milliarden Jahre)?', ], 'geology' => [ 'No details' => 'Keine Details', ], 'country' => [ 'Germany' => 'Deutschland', 'Norway' => 'Norwegen', 'U. S. A.' => 'USA', 'Not appliable' => 'Nicht anwendbar', ], 'nationality' => [ 'German' => 'Deutsch', 'Norwegian' => 'Norwegisch', 'American' => 'Amerikanisch', 'Not appliable' => 'Nicht anwendbar', ], 'face' => [ 'Nearside' => 'Vorderseite', 'Farside' => 'Rückseite', 'Librations zone' => 'Librationszone', ], 'quadrant' => [ 'North-East' => 'Nordost', 'South-East' => 'Südost', 'North-West' => 'Nordwest', 'South-West' => 'Südwest', ], 'interest_c' => [ 'Low interest or difficult formation' => 'Geringes Interesse oder schwer beobachtbar', 'Pretty interesting formation' => 'Ziemlich interessante Formation', 'Not observable' => 'Nicht beobachtbar', ], 'moonday_s' => [ 'Not observable' => 'Nicht beobachtbar', '3 days after New Moon' => '3 Tage nach Neumond', '2 days after New Moon' => '2 Tage nach Neumond', ], 'moonday_m' => [ 'Not observable' => 'Nicht beobachtbar', '2 days after Full Moon' => '2 Tage nach Vollmond', '1 day after Full Moon' => '1 Tag nach Vollmond', ], 'diam_inst' => [ 'Not observable' => 'Nicht beobachtbar', 'Naked eye' => 'Mit bloßem Auge', ], 'th_instru' => [ 'Not observable' => 'Nicht beobachtbar', '50 mm refractor' => '50-mm-Refraktor', '10x binoculars' => '10x-Fernglas', ], 'pr_instru' => [ 'Not observable' => 'Nicht beobachtbar', '100 mm refractor' => '100-mm-Refraktor', '10x binoculars' => '10x-Fernglas', ], ]; return $maps[$field][$value] ?? $value; } function createMoonCraterTableSql(): string { return "CREATE TABLE moon_craters ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, lun VARCHAR(64) DEFAULT NULL, is_satellite_crater TINYINT(1) NOT NULL DEFAULT 0, parent_crater_name VARCHAR(255) DEFAULT NULL, name_type VARCHAR(255) DEFAULT NULL, iau_type VARCHAR(100) DEFAULT NULL, iau_type_de VARCHAR(100) DEFAULT NULL, type VARCHAR(100) DEFAULT NULL, type_de VARCHAR(100) DEFAULT NULL, subtype VARCHAR(150) DEFAULT NULL, subtype_de VARCHAR(150) DEFAULT NULL, process VARCHAR(150) DEFAULT NULL, process_de VARCHAR(150) DEFAULT NULL, period VARCHAR(255) DEFAULT NULL, period_de VARCHAR(255) DEFAULT NULL, period_source TEXT DEFAULT NULL, geology VARCHAR(255) DEFAULT NULL, geology_de VARCHAR(255) DEFAULT NULL, name_detail VARCHAR(255) DEFAULT NULL, name_origin VARCHAR(255) DEFAULT NULL, iau_approval_date DATE DEFAULT NULL, langrenus VARCHAR(255) DEFAULT NULL, hevelius VARCHAR(255) DEFAULT NULL, riccioli VARCHAR(255) DEFAULT NULL, work VARCHAR(255) DEFAULT NULL, country VARCHAR(255) DEFAULT NULL, country_de VARCHAR(255) DEFAULT NULL, nationality VARCHAR(255) DEFAULT NULL, nationality_de VARCHAR(255) DEFAULT NULL, century_n VARCHAR(50) DEFAULT NULL, century_c VARCHAR(50) DEFAULT NULL, birth_place VARCHAR(255) DEFAULT NULL, birth_date VARCHAR(100) DEFAULT NULL, death_place VARCHAR(255) DEFAULT NULL, death_date VARCHAR(100) DEFAULT NULL, facts TEXT DEFAULT NULL, longi_n DECIMAL(10,6) DEFAULT NULL, longi_n_360 DECIMAL(10,6) DEFAULT NULL, longi_c VARCHAR(255) DEFAULT NULL, lati_n DECIMAL(10,6) DEFAULT NULL, lati_c VARCHAR(255) DEFAULT NULL, face VARCHAR(100) DEFAULT NULL, face_de VARCHAR(100) DEFAULT NULL, quadrant VARCHAR(100) DEFAULT NULL, quadrant_de VARCHAR(100) DEFAULT NULL, area VARCHAR(255) DEFAULT NULL, length_km DECIMAL(10,3) DEFAULT NULL, wide_km DECIMAL(10,3) DEFAULT NULL, length_arcsec DECIMAL(10,3) DEFAULT NULL, height_m VARCHAR(255) DEFAULT NULL, rapport VARCHAR(255) DEFAULT NULL, general_1 TEXT DEFAULT NULL, general_2 TEXT DEFAULT NULL, slopes TEXT DEFAULT NULL, walls TEXT DEFAULT NULL, floor TEXT DEFAULT NULL, interest_n INT DEFAULT NULL, interest_c VARCHAR(255) DEFAULT NULL, interest_c_de VARCHAR(255) DEFAULT NULL, lunation VARCHAR(100) DEFAULT NULL, moonday_s VARCHAR(255) DEFAULT NULL, moonday_s_de VARCHAR(255) DEFAULT NULL, moonday_m VARCHAR(255) DEFAULT NULL, moonday_m_de VARCHAR(255) DEFAULT NULL, diam_inst VARCHAR(255) DEFAULT NULL, diam_inst_de VARCHAR(255) DEFAULT NULL, th_instru VARCHAR(255) DEFAULT NULL, th_instru_de VARCHAR(255) DEFAULT NULL, pr_instru VARCHAR(255) DEFAULT NULL, pr_instru_de VARCHAR(255) DEFAULT NULL, source_row_json JSON DEFAULT NULL, imported_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uniq_lun (lun), KEY idx_is_satellite_crater (is_satellite_crater), KEY idx_parent_crater_name (parent_crater_name), KEY idx_name (name), KEY idx_type (type), KEY idx_face (face), KEY idx_quadrant (quadrant), KEY idx_longi_n_360 (longi_n_360), KEY idx_lati_n (lati_n) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; } function ensureMoonCraterTable(PDO $pdo): void { $tableExists = (bool)$pdo->query("SHOW TABLES LIKE 'moon_craters'")->fetchColumn(); if (!$tableExists) { $pdo->exec(createMoonCraterTableSql()); return; } $columns = []; $stmt = $pdo->query("SHOW COLUMNS FROM moon_craters"); foreach ($stmt->fetchAll() as $row) { $columns[] = (string)($row['Field'] ?? ''); } $alterStatements = []; if (!in_array('is_satellite_crater', $columns, true)) { $alterStatements[] = "ADD COLUMN is_satellite_crater TINYINT(1) NOT NULL DEFAULT 0 AFTER lun"; } if (!in_array('parent_crater_name', $columns, true)) { $alterStatements[] = "ADD COLUMN parent_crater_name VARCHAR(255) DEFAULT NULL AFTER is_satellite_crater"; } if ($alterStatements !== []) { $pdo->exec("ALTER TABLE moon_craters\n " . implode(",\n ", $alterStatements)); } $indexNames = []; $indexStmt = $pdo->query("SHOW INDEX FROM moon_craters"); foreach ($indexStmt->fetchAll() as $row) { $indexNames[] = (string)($row['Key_name'] ?? ''); } if (!in_array('idx_is_satellite_crater', $indexNames, true)) { $pdo->exec("ALTER TABLE moon_craters ADD KEY idx_is_satellite_crater (is_satellite_crater)"); } if (!in_array('idx_parent_crater_name', $indexNames, true)) { $pdo->exec("ALTER TABLE moon_craters ADD KEY idx_parent_crater_name (parent_crater_name)"); } } $report = null; $defaultCsv = 'C:\Users\ich\Downloads\01_IAU_NAMED_EN.csv'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $csvPath = trim((string)($_POST['csv_path'] ?? $defaultCsv)); $truncateFirst = isset($_POST['truncate_first']) && (string)$_POST['truncate_first'] === '1'; $uploadedLabel = null; try { if ( isset($_FILES['csv_upload']) && is_array($_FILES['csv_upload']) && (int)($_FILES['csv_upload']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE ) { $uploadError = (int)($_FILES['csv_upload']['error'] ?? UPLOAD_ERR_OK); if ($uploadError !== UPLOAD_ERR_OK) { throw new RuntimeException('Datei-Upload fehlgeschlagen. Fehlercode: ' . $uploadError); } $tmpName = (string)($_FILES['csv_upload']['tmp_name'] ?? ''); if ($tmpName === '' || !is_uploaded_file($tmpName)) { throw new RuntimeException('Die hochgeladene Datei ist ungueltig.'); } $csvPath = $tmpName; $uploadedLabel = (string)($_FILES['csv_upload']['name'] ?? 'hochgeladene Datei'); } if (!is_file($csvPath)) { throw new RuntimeException('CSV-Datei nicht gefunden: ' . $csvPath); } $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, ] ); ensureMoonCraterTable($pdo); if ($truncateFirst) { $pdo->exec('TRUNCATE TABLE moon_craters'); } $handle = fopen($csvPath, 'rb'); if ($handle === false) { throw new RuntimeException('CSV-Datei konnte nicht geöffnet werden.'); } $headers = fgetcsv($handle, 0, ';', '"', '\\'); if (!is_array($headers)) { throw new RuntimeException('CSV-Kopfzeile konnte nicht gelesen werden.'); } $headers = array_map(static fn($value) => strtoupper((string)$value), $headers); $insertSql = "INSERT INTO moon_craters ( name, lun, is_satellite_crater, parent_crater_name, name_type, iau_type, iau_type_de, type, type_de, subtype, subtype_de, process, process_de, period, period_de, period_source, geology, geology_de, name_detail, name_origin, iau_approval_date, langrenus, hevelius, riccioli, work, country, country_de, nationality, nationality_de, century_n, century_c, birth_place, birth_date, death_place, death_date, facts, longi_n, longi_n_360, longi_c, lati_n, lati_c, face, face_de, quadrant, quadrant_de, area, length_km, wide_km, length_arcsec, height_m, rapport, general_1, general_2, slopes, walls, floor, interest_n, interest_c, interest_c_de, lunation, moonday_s, moonday_s_de, moonday_m, moonday_m_de, diam_inst, diam_inst_de, th_instru, th_instru_de, pr_instru, pr_instru_de, source_row_json ) VALUES ( :name, :lun, :is_satellite_crater, :parent_crater_name, :name_type, :iau_type, :iau_type_de, :type, :type_de, :subtype, :subtype_de, :process, :process_de, :period, :period_de, :period_source, :geology, :geology_de, :name_detail, :name_origin, :iau_approval_date, :langrenus, :hevelius, :riccioli, :work, :country, :country_de, :nationality, :nationality_de, :century_n, :century_c, :birth_place, :birth_date, :death_place, :death_date, :facts, :longi_n, :longi_n_360, :longi_c, :lati_n, :lati_c, :face, :face_de, :quadrant, :quadrant_de, :area, :length_km, :wide_km, :length_arcsec, :height_m, :rapport, :general_1, :general_2, :slopes, :walls, :floor, :interest_n, :interest_c, :interest_c_de, :lunation, :moonday_s, :moonday_s_de, :moonday_m, :moonday_m_de, :diam_inst, :diam_inst_de, :th_instru, :th_instru_de, :pr_instru, :pr_instru_de, :source_row_json ) ON DUPLICATE KEY UPDATE name = VALUES(name), is_satellite_crater = VALUES(is_satellite_crater), parent_crater_name = VALUES(parent_crater_name), name_type = VALUES(name_type), iau_type = VALUES(iau_type), iau_type_de = VALUES(iau_type_de), type = VALUES(type), type_de = VALUES(type_de), subtype = VALUES(subtype), subtype_de = VALUES(subtype_de), process = VALUES(process), process_de = VALUES(process_de), period = VALUES(period), period_de = VALUES(period_de), period_source = VALUES(period_source), geology = VALUES(geology), geology_de = VALUES(geology_de), name_detail = VALUES(name_detail), name_origin = VALUES(name_origin), iau_approval_date = VALUES(iau_approval_date), langrenus = VALUES(langrenus), hevelius = VALUES(hevelius), riccioli = VALUES(riccioli), work = VALUES(work), country = VALUES(country), country_de = VALUES(country_de), nationality = VALUES(nationality), nationality_de = VALUES(nationality_de), century_n = VALUES(century_n), century_c = VALUES(century_c), birth_place = VALUES(birth_place), birth_date = VALUES(birth_date), death_place = VALUES(death_place), death_date = VALUES(death_date), facts = VALUES(facts), longi_n = VALUES(longi_n), longi_n_360 = VALUES(longi_n_360), longi_c = VALUES(longi_c), lati_n = VALUES(lati_n), lati_c = VALUES(lati_c), face = VALUES(face), face_de = VALUES(face_de), quadrant = VALUES(quadrant), quadrant_de = VALUES(quadrant_de), area = VALUES(area), length_km = VALUES(length_km), wide_km = VALUES(wide_km), length_arcsec = VALUES(length_arcsec), height_m = VALUES(height_m), rapport = VALUES(rapport), general_1 = VALUES(general_1), general_2 = VALUES(general_2), slopes = VALUES(slopes), walls = VALUES(walls), floor = VALUES(floor), interest_n = VALUES(interest_n), interest_c = VALUES(interest_c), interest_c_de = VALUES(interest_c_de), lunation = VALUES(lunation), moonday_s = VALUES(moonday_s), moonday_s_de = VALUES(moonday_s_de), moonday_m = VALUES(moonday_m), moonday_m_de = VALUES(moonday_m_de), diam_inst = VALUES(diam_inst), diam_inst_de = VALUES(diam_inst_de), th_instru = VALUES(th_instru), th_instru_de = VALUES(th_instru_de), pr_instru = VALUES(pr_instru), pr_instru_de = VALUES(pr_instru_de), source_row_json = VALUES(source_row_json)"; $stmt = $pdo->prepare($insertSql); $imported = 0; $skipped = 0; while (($row = fgetcsv($handle, 0, ';', '"', '\\')) !== false) { if ($row === [null] || $row === false) { continue; } $assoc = []; foreach ($headers as $index => $header) { $assoc[$header] = $row[$index] ?? null; } $name = normalizeImportValue($assoc['NAME'] ?? null); if ($name === null) { $skipped++; continue; } $isSatelliteCrater = isSatelliteCraterRow($assoc, $uploadedLabel ?? $csvPath); $payload = [ 'name' => $name, 'lun' => normalizeImportValue($assoc['LUN'] ?? null), 'is_satellite_crater' => $isSatelliteCrater ? 1 : 0, 'parent_crater_name' => deriveParentCraterName($name, $isSatelliteCrater), 'name_type' => normalizeImportValue($assoc['NAME_TYPE'] ?? null), 'iau_type' => normalizeImportValue($assoc['IAU_TYPE'] ?? null), 'iau_type_de' => translateCatalogValue('iau_type', $assoc['IAU_TYPE'] ?? null), 'type' => normalizeImportValue($assoc['TYPE'] ?? null), 'type_de' => translateCatalogValue('type', $assoc['TYPE'] ?? null), 'subtype' => normalizeImportValue($assoc['SUBTYPE'] ?? null), 'subtype_de' => translateCatalogValue('subtype', $assoc['SUBTYPE'] ?? null), 'process' => normalizeImportValue($assoc['PROCESS'] ?? null), 'process_de' => translateCatalogValue('process', $assoc['PROCESS'] ?? null), 'period' => normalizeImportValue($assoc['PERIOD'] ?? null), 'period_de' => translateCatalogValue('period', $assoc['PERIOD'] ?? null), 'period_source' => normalizeImportValue($assoc['PERIOD_SOURCE'] ?? null), 'geology' => normalizeImportValue($assoc['GEOLOGY'] ?? null), 'geology_de' => translateCatalogValue('geology', $assoc['GEOLOGY'] ?? null), 'name_detail' => normalizeImportValue($assoc['NAME_DETAIL'] ?? null), 'name_origin' => normalizeImportValue($assoc['NAME_ORIGIN'] ?? null), 'iau_approval_date' => parseNullableDate($assoc['IAU_APPROVAL'] ?? null), 'langrenus' => normalizeImportValue($assoc['LANGRENUS'] ?? null), 'hevelius' => normalizeImportValue($assoc['HEVELIUS'] ?? null), 'riccioli' => normalizeImportValue($assoc['RICCIOLI'] ?? null), 'work' => normalizeImportValue($assoc['WORK'] ?? null), 'country' => normalizeImportValue($assoc['COUNTRY'] ?? null), 'country_de' => translateCatalogValue('country', $assoc['COUNTRY'] ?? null), 'nationality' => normalizeImportValue($assoc['NATIONLITY'] ?? null), 'nationality_de' => translateCatalogValue('nationality', $assoc['NATIONLITY'] ?? null), 'century_n' => normalizeImportValue($assoc['CENTURY_N'] ?? null), 'century_c' => normalizeImportValue($assoc['CENTURY_C'] ?? null), 'birth_place' => normalizeImportValue($assoc['BIRTH_PLACE'] ?? null), 'birth_date' => normalizeImportValue($assoc['BIRTH_DATE'] ?? null), 'death_place' => normalizeImportValue($assoc['DEATH_PLACE'] ?? null), 'death_date' => normalizeImportValue($assoc['DEATH_DATE'] ?? null), 'facts' => normalizeImportValue($assoc['FACTS'] ?? null), 'longi_n' => parseNullableFloat($assoc['LONGI_N'] ?? null), 'longi_n_360' => parseNullableFloat($assoc['LONGI_N_360'] ?? null), 'longi_c' => normalizeImportValue($assoc['LONGI_C'] ?? null), 'lati_n' => parseNullableFloat($assoc['LATI_N'] ?? null), 'lati_c' => normalizeImportValue($assoc['LATI_C'] ?? null), 'face' => normalizeImportValue($assoc['FACE'] ?? null), 'face_de' => translateCatalogValue('face', $assoc['FACE'] ?? null), 'quadrant' => normalizeImportValue($assoc['QUADRANT'] ?? null), 'quadrant_de' => translateCatalogValue('quadrant', $assoc['QUADRANT'] ?? null), 'area' => normalizeImportValue($assoc['AREA'] ?? null), 'length_km' => parseNullableFloat($assoc['LENGTH_KM'] ?? null), 'wide_km' => parseNullableFloat($assoc['WIDE_KM'] ?? null), 'length_arcsec' => parseNullableFloat($assoc['LENGTH_ARCSEC'] ?? null), 'height_m' => normalizeImportValue($assoc['HEIGHT_M'] ?? null), 'rapport' => normalizeImportValue($assoc['RAPPORT'] ?? null), 'general_1' => normalizeImportValue($assoc['GENERAL_1'] ?? null), 'general_2' => normalizeImportValue($assoc['GENERAL_2'] ?? null), 'slopes' => normalizeImportValue($assoc['SLOPES'] ?? null), 'walls' => normalizeImportValue($assoc['WALLS'] ?? null), 'floor' => normalizeImportValue($assoc['FLOOR'] ?? null), 'interest_n' => parseNullableInt($assoc['INTEREST_N'] ?? null), 'interest_c' => normalizeImportValue($assoc['INTEREST_C'] ?? null), 'interest_c_de' => translateCatalogValue('interest_c', $assoc['INTEREST_C'] ?? null), 'lunation' => normalizeImportValue($assoc['LUNATION'] ?? null), 'moonday_s' => normalizeImportValue($assoc['MOONDAY_S'] ?? null), 'moonday_s_de' => translateCatalogValue('moonday_s', $assoc['MOONDAY_S'] ?? null), 'moonday_m' => normalizeImportValue($assoc['MOONDAY_M'] ?? null), 'moonday_m_de' => translateCatalogValue('moonday_m', $assoc['MOONDAY_M'] ?? null), 'diam_inst' => normalizeImportValue($assoc['DIAM_INST'] ?? null), 'diam_inst_de' => translateCatalogValue('diam_inst', $assoc['DIAM_INST'] ?? null), 'th_instru' => normalizeImportValue($assoc['TH_INSTRU'] ?? null), 'th_instru_de' => translateCatalogValue('th_instru', $assoc['TH_INSTRU'] ?? null), 'pr_instru' => normalizeImportValue($assoc['PR_INSTRU'] ?? null), 'pr_instru_de' => translateCatalogValue('pr_instru', $assoc['PR_INSTRU'] ?? null), 'source_row_json' => json_encode($assoc, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]; $stmt->execute($payload); $imported++; } fclose($handle); $report = [ 'ok' => true, 'csv_path' => $csvPath, 'csv_label' => $uploadedLabel ?? $csvPath, 'imported' => $imported, 'skipped' => $skipped, 'truncate_first' => $truncateFirst, ]; } catch (Throwable $exception) { $report = [ 'ok' => false, 'error' => $exception->getMessage(), ]; } } require __DIR__ . '/../../public/header.php'; ?>
Importiert die IAU-Kraterdaten aus der CSV in die Tabelle moon_craters. Die Tabelle wird bei Bedarf automatisch angelegt.