// 数据来源:FAA Aircraft Registry。美国 FAA 官方注册库,每日更新,MASTER.txt 包含 Mode-S Hex。 // 链接:https://www.faa.gov/licenses_certificates/aircraft_certification/aircraft_registry/releasable_aircraft_download // 下载:https://registry.faa.gov/database/ReleasableAircraft.zip // 官方更新要求:FAA 下载页说明文件每日刷新。本地告警策略:超过 24 小时提示更新。 #include "../server/libarchive.h" #include "Resource_Utils.h" #include #include #include namespace { const std::vector kFaaColumns = { "n_number", "serial_number", "aircraft_mfr_model_code", "engine_mfr_model_code", "year_mfr", "registrant_type", "name", "street", "street2", "city", "state", "zip_code", "region", "county", "country", "last_action_date", "cert_issue_date", "certification", "type_aircraft", "type_engine", "status_code", "mode_s_code", "fractional_ownership", "airworthiness_date", "other_names_1", "other_names_2", "other_names_3", "other_names_4", "other_names_5", "expiration_date", "unique_id", "kit_mfr", "kit_model", "icao24" }; const std::vector kFaaPrimaryKey = {"icao24"}; class Faa_Aircraft_Registry_Resource final : public External_Resources { public: Faa_Aircraft_Registry_Resource() : External_Resources("faa_aircraft_registry", "faa_registry/MASTER.txt", "https://registry.faa.gov/database/ReleasableAircraft.zip", "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。", 86400) {} void create_table(SQLite::Database& db) const override { db.exec(R"sql( CREATE TABLE IF NOT EXISTS faa_aircraft_registry ( n_number TEXT, serial_number TEXT, aircraft_mfr_model_code TEXT, engine_mfr_model_code TEXT, year_mfr TEXT, registrant_type TEXT, name TEXT, street TEXT, street2 TEXT, city TEXT, state TEXT, zip_code TEXT, region TEXT, county TEXT, country TEXT, last_action_date TEXT, cert_issue_date TEXT, certification TEXT, type_aircraft TEXT, type_engine TEXT, status_code TEXT, mode_s_code TEXT, fractional_ownership TEXT, airworthiness_date TEXT, other_names_1 TEXT, other_names_2 TEXT, other_names_3 TEXT, other_names_4 TEXT, other_names_5 TEXT, expiration_date TEXT, unique_id TEXT, kit_mfr TEXT, kit_model TEXT, icao24 TEXT NOT NULL PRIMARY KEY ); )sql"); } protected: bool fetch(const std::filesystem::path& cache_pos, const bool force_download) const override { const auto master_file = source_file(cache_pos); if (!force_download) return false; const auto zip_file = cache_pos / "ReleasableAircraft.zip"; External_Database_Utils::download_to_file(download_url, zip_file); unarchive_file(zip_file, cache_pos / "faa_registry"); return true; } std::size_t import_file(SQLite::Database& db, const std::filesystem::path& source_file) const override { auto master_file = source_file; if (source_file.extension() == ".zip") { const auto output_dir = source_file.parent_path() / "faa_registry"; unarchive_file(source_file, output_dir); master_file = output_dir / "MASTER.txt"; } std::ifstream input(master_file, std::ios::binary); if (!input) throw std::runtime_error("Cannot open FAA MASTER.txt: " + master_file.string()); SQLite::Transaction transaction(db); db.exec("DELETE FROM faa_aircraft_registry"); SQLite::Statement statement( db, External_Database_Utils::make_upsert_sql( name, kFaaColumns, kFaaPrimaryKey)); std::vector fields; External_Database_Utils::read_csv_row(input, fields, ','); // 跳过表头。 std::size_t row_count = 0; while (External_Database_Utils::read_csv_row(input, fields, ',')) { if (fields.size() < kFaaColumns.size()) fields.resize(kFaaColumns.size()); const auto icao24 = External_Database_Utils::normalize_key(fields[33]); if (icao24.empty()) continue; statement.reset(); statement.clearBindings(); for (std::size_t index = 0; index < kFaaColumns.size(); ++index) { auto value = External_Database_Utils::normalize_field(fields[index]); if (index == 33) value = icao24; External_Database_Utils::bind_optional( statement, static_cast(index + 1), value); } statement.exec(); ++row_count; } transaction.commit(); return row_count; } [[nodiscard]] const std::vector& primary_key_columns() const override { return kFaaPrimaryKey; } }; } // 命名空间 std::unique_ptr make_faa_aircraft_registry_resource() { return std::make_unique(); }