mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-12-01 15:23:47 +00:00
* enhance make_airlines_db tool * enhance make_icao24_db tool * update airlinescode (.txt, .db), aircraftdatabase/icao24 (.csv, .db)
274 lines
9.9 KiB
Python
Executable File
274 lines
9.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (C) 2021 ArjanOnwezen
|
|
# Copyright (C) 2025 Tommaso Ventafridda
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2, or (at your option)
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; see the file COPYING. If not, write to
|
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
|
# Boston, MA 02110-1301, USA.
|
|
#
|
|
# -------------------------------------------------------------------------------------
|
|
# Create airline.db, used for ADS-B receiver application, using
|
|
# https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt
|
|
# as a source.
|
|
# -------------------------------------------------------------------------------------
|
|
import csv
|
|
import os
|
|
import shutil
|
|
import unicodedata
|
|
import urllib.request
|
|
from dataclasses import dataclass
|
|
from typing import Dict, Set, Tuple
|
|
|
|
|
|
@dataclass
|
|
class AirlineRecord:
|
|
"""Represents an airline record with all relevant fields"""
|
|
|
|
icao_code: str
|
|
airline: str
|
|
country: str
|
|
|
|
def __hash__(self):
|
|
return hash(self.icao_code)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, AirlineRecord):
|
|
return False
|
|
return self.icao_code == other.icao_code
|
|
|
|
def data_equals(self, other):
|
|
"""Check if all data fields are equal (excluding ICAO code which is the key)"""
|
|
if not isinstance(other, AirlineRecord):
|
|
return False
|
|
return self.airline == other.airline and self.country == other.country
|
|
|
|
|
|
def backup_previous_file() -> bool:
|
|
"""Backup the existing airlinecodes.txt file if it exists. Returns True if backup was made."""
|
|
if os.path.exists("airlinecodes.txt"):
|
|
shutil.copy2("airlinecodes.txt", "airlinecodes_previous.txt")
|
|
print("Backed up previous airline codes file")
|
|
return True
|
|
return False
|
|
|
|
|
|
def download_airline_codes() -> None:
|
|
"""Download the airline codes file from GitHub"""
|
|
url = "https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt"
|
|
print(f"Downloading airline codes database from {url}...")
|
|
try:
|
|
urllib.request.urlretrieve(url, "airlinecodes.txt")
|
|
print("Download completed successfully.")
|
|
except Exception as e:
|
|
print(f"Error downloading airline codes file: {e}")
|
|
raise
|
|
|
|
|
|
def parse_airline_file(filename: str) -> Dict[str, AirlineRecord]:
|
|
"""Parse airline codes file and return a dictionary of records"""
|
|
records = {}
|
|
|
|
if not os.path.exists(filename):
|
|
return records
|
|
|
|
try:
|
|
with open(filename, "rt", encoding="utf-8") as csv_file:
|
|
sorted_lines = sorted(csv_file.readlines()[1:])
|
|
for row in csv.reader(
|
|
sorted_lines,
|
|
quotechar='"',
|
|
delimiter=",",
|
|
quoting=csv.QUOTE_ALL,
|
|
skipinitialspace=True,
|
|
):
|
|
if len(row) >= 4: # Ensure we have enough columns
|
|
icao_code = row[0]
|
|
# Normalize some unicode characters
|
|
airline = (
|
|
unicodedata.normalize("NFKD", row[1][:32])
|
|
.encode("ascii", "ignore")
|
|
.decode("ascii")
|
|
)
|
|
country = (
|
|
unicodedata.normalize("NFKD", row[3][:32])
|
|
.encode("ascii", "ignore")
|
|
.decode("ascii")
|
|
)
|
|
|
|
if len(icao_code) == 3:
|
|
records[icao_code] = AirlineRecord(
|
|
icao_code=icao_code, airline=airline, country=country
|
|
)
|
|
except Exception as e:
|
|
print(f"Warning: Could not parse airline file {filename}: {e}")
|
|
return {}
|
|
|
|
return records
|
|
|
|
|
|
def compare_records(
|
|
old_records: Dict[str, AirlineRecord], new_records: Dict[str, AirlineRecord]
|
|
) -> Tuple[Set[str], Set[str], Set[str]]:
|
|
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
|
|
old_keys = set(old_records.keys())
|
|
new_keys = set(new_records.keys())
|
|
|
|
new_icao_codes = new_keys - old_keys
|
|
deleted_icao_codes = old_keys - new_keys
|
|
|
|
# Check for changes in existing records
|
|
changed_icao_codes = set()
|
|
for icao in old_keys & new_keys:
|
|
if not old_records[icao].data_equals(new_records[icao]):
|
|
changed_icao_codes.add(icao)
|
|
|
|
return new_icao_codes, deleted_icao_codes, changed_icao_codes
|
|
|
|
|
|
def write_database(records: Dict[str, AirlineRecord]) -> int:
|
|
"""Write records to database file using original format"""
|
|
icao_codes = bytearray()
|
|
airlines_countries = bytearray()
|
|
row_count = 0
|
|
|
|
# Sort by ICAO code for consistency
|
|
sorted_records = sorted(records.values(), key=lambda r: r.icao_code)
|
|
|
|
for record in sorted_records:
|
|
# Normalize and encode data (same as original)
|
|
airline = unicodedata.normalize("NFKD", record.airline[:32]).encode(
|
|
"ascii", "ignore"
|
|
)
|
|
country = unicodedata.normalize("NFKD", record.country[:32]).encode(
|
|
"ascii", "ignore"
|
|
)
|
|
|
|
# Add ICAO code with null terminator (original format)
|
|
icao_codes = icao_codes + bytearray(record.icao_code + "\0", encoding="ascii")
|
|
|
|
# Add padded data fields (original format)
|
|
airline_padding = bytearray("\0" * (32 - len(airline)), encoding="ascii")
|
|
country_padding = bytearray("\0" * (32 - len(country)), encoding="ascii")
|
|
|
|
airlines_countries = airlines_countries + bytearray(
|
|
airline + airline_padding + country + country_padding
|
|
)
|
|
row_count += 1
|
|
|
|
with open("airlines.db", "wb") as database:
|
|
database.write(icao_codes + airlines_countries)
|
|
|
|
return row_count
|
|
|
|
|
|
def create_database() -> None:
|
|
"""Create the airline database from the downloaded file"""
|
|
# Backup existing file before downloading new one
|
|
has_previous = backup_previous_file()
|
|
|
|
# Download new file
|
|
download_airline_codes()
|
|
|
|
# Parse both files for comparison
|
|
old_records = {}
|
|
if has_previous:
|
|
print("Parsing previous airline codes file...")
|
|
old_records = parse_airline_file("airlinecodes_previous.txt")
|
|
print(f"Found {len(old_records)} records in previous file")
|
|
|
|
print("Parsing new airline codes file...")
|
|
new_records = parse_airline_file("airlinecodes.txt")
|
|
print(f"Found {len(new_records)} records in new file")
|
|
|
|
# Compare records if we have a previous version
|
|
if old_records:
|
|
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
|
|
old_records, new_records
|
|
)
|
|
|
|
# Print change statistics
|
|
print("\n" + "=" * 50)
|
|
print("AIRLINE CODES CHANGE SUMMARY")
|
|
print("=" * 50)
|
|
print(f"New records: {len(new_icao_codes):>8}")
|
|
print(f"Deleted records: {len(deleted_icao_codes):>8}")
|
|
print(f"Changed records: {len(changed_icao_codes):>8}")
|
|
print(f"Total records: {len(new_records):>8}")
|
|
print("=" * 50)
|
|
|
|
# Show examples of changes (limited to avoid spam)
|
|
if new_icao_codes and len(new_icao_codes) <= 15:
|
|
print(f"\nNew ICAO codes: {', '.join(sorted(new_icao_codes))}")
|
|
elif new_icao_codes:
|
|
sample_new = sorted(list(new_icao_codes))[:10]
|
|
print(
|
|
f"\nSample new ICAO codes: {', '.join(sample_new)} (and {len(new_icao_codes)-10} more)"
|
|
)
|
|
|
|
if deleted_icao_codes and len(deleted_icao_codes) <= 15:
|
|
print(f"Deleted ICAO codes: {', '.join(sorted(deleted_icao_codes))}")
|
|
elif deleted_icao_codes:
|
|
sample_deleted = sorted(list(deleted_icao_codes))[:10]
|
|
print(
|
|
f"Sample deleted ICAO codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-10} more)"
|
|
)
|
|
|
|
if changed_icao_codes and len(changed_icao_codes) <= 15:
|
|
print(f"Changed ICAO codes: {', '.join(sorted(changed_icao_codes))}")
|
|
elif changed_icao_codes:
|
|
sample_changed = sorted(list(changed_icao_codes))[:10]
|
|
print(
|
|
f"Sample changed ICAO codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-10} more)"
|
|
)
|
|
|
|
# Show some specific examples of changes
|
|
if changed_icao_codes:
|
|
print(f"\nExample changes:")
|
|
for icao in sorted(list(changed_icao_codes))[:3]:
|
|
old_rec = old_records[icao]
|
|
new_rec = new_records[icao]
|
|
if old_rec.airline != new_rec.airline:
|
|
print(
|
|
f" {icao}: Airline '{old_rec.airline}' → '{new_rec.airline}'"
|
|
)
|
|
if old_rec.country != new_rec.country:
|
|
print(
|
|
f" {icao}: Country '{old_rec.country}' → '{new_rec.country}'"
|
|
)
|
|
else:
|
|
print(
|
|
"\nNo previous airline codes file found - this appears to be the first run"
|
|
)
|
|
|
|
# Create database from new records
|
|
print("\nCreating airline database...")
|
|
row_count = write_database(new_records)
|
|
print("Total of", row_count, "ICAO codes stored in database")
|
|
|
|
|
|
def cleanup_temp() -> None:
|
|
"""Cleanup temporary files created during the process"""
|
|
if os.path.exists("airlinecodes_previous.txt"):
|
|
os.remove("airlinecodes_previous.txt")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
create_database()
|
|
cleanup_temp()
|
|
except Exception as e:
|
|
print(f"Error creating airline database: {e}")
|
|
else:
|
|
print("Airline database created successfully.")
|