diff --git a/src/blackmisc/CMakeLists.txt b/src/blackmisc/CMakeLists.txt index 30978289b..3194d25c3 100644 --- a/src/blackmisc/CMakeLists.txt +++ b/src/blackmisc/CMakeLists.txt @@ -71,10 +71,14 @@ add_library(misc SHARED aviation/callsignobjectlist.h aviation/callsignset.cpp aviation/callsignset.h + aviation/comnavequipment.cpp + aviation/comnavequipment.h aviation/comsystem.cpp aviation/comsystem.h aviation/flightplan.cpp aviation/flightplan.h + aviation/flightplanaircraftinfo.cpp + aviation/flightplanaircraftinfo.h aviation/flightplanlist.cpp aviation/flightplanlist.h aviation/heading.cpp @@ -95,6 +99,8 @@ add_library(misc SHARED aviation/selcal.h aviation/simbriefdata.cpp aviation/simbriefdata.h + aviation/ssrequipment.cpp + aviation/ssrequipment.h aviation/track.cpp aviation/track.h aviation/transponder.cpp diff --git a/src/blackmisc/aviation/comnavequipment.cpp b/src/blackmisc/aviation/comnavequipment.cpp new file mode 100644 index 000000000..e468480cd --- /dev/null +++ b/src/blackmisc/aviation/comnavequipment.cpp @@ -0,0 +1,212 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +#include "comnavequipment.h" + +BLACK_DEFINE_VALUEOBJECT_MIXINS(BlackMisc::Aviation, CComNavEquipment) + +namespace BlackMisc::Aviation +{ + CComNavEquipment::CComNavEquipment(ComNavEquipment comNavEquipment, CpdlcSatcomEquipment cpdlcSatcomEquipment) : m_equipment(comNavEquipment), m_cpdlcSatcomEquipment(cpdlcSatcomEquipment) + { + if (m_equipment == ComNavEquipment()) + { + m_equipment = NoEquip; + } + } + + CComNavEquipment::CComNavEquipment(QString equipment) + { + if (equipment.isEmpty()) + { + return; + } + + m_equipment = {}; // Clear default flag + + auto append_equipment_flag_if_exist = [&equipment, this](ComNavEquipmentOption flag) { + QString str = flagToString(flag); + if (equipment.contains(str)) + { + equipment = equipment.remove(str); + m_equipment |= flag; + } + }; + + auto append_satcom_flag_if_exist = [&equipment, this](CpdlcSatcomEquipmentOption flag) { + QString str = flagToString(flag); + if (equipment.contains(str)) + { + equipment = equipment.remove(str); + m_cpdlcSatcomEquipment |= flag; + } + }; + + append_equipment_flag_if_exist(Standard); + append_equipment_flag_if_exist(Gbas); + append_equipment_flag_if_exist(Lpv); + append_equipment_flag_if_exist(LoranC); + append_equipment_flag_if_exist(Dme); + append_equipment_flag_if_exist(FmcAcars); + append_equipment_flag_if_exist(DFisAcars); + append_equipment_flag_if_exist(PdcAcars); + append_equipment_flag_if_exist(Adf); + append_equipment_flag_if_exist(Gnss); + append_equipment_flag_if_exist(HfRtf); + append_equipment_flag_if_exist(InertiaNavigation); + append_equipment_flag_if_exist(Mls); + append_equipment_flag_if_exist(Ils); + append_equipment_flag_if_exist(NoEquip); + append_equipment_flag_if_exist(Vor); + append_equipment_flag_if_exist(Pbn); + append_equipment_flag_if_exist(Tacan); + append_equipment_flag_if_exist(UhfRtf); + append_equipment_flag_if_exist(VhfRtf); + append_equipment_flag_if_exist(Rvsm); + append_equipment_flag_if_exist(Mnps); + append_equipment_flag_if_exist(Vhf833); + append_equipment_flag_if_exist(Other); + append_satcom_flag_if_exist(CpdlcAtn); + append_satcom_flag_if_exist(CpdlcFansHfdl); + append_satcom_flag_if_exist(CpdlcFansVdlA); + append_satcom_flag_if_exist(CpdlcFansVdl2); + append_satcom_flag_if_exist(CpdlcFansSatcomInmarsat); + append_satcom_flag_if_exist(CpdlcFansSatcomMtsat); + append_satcom_flag_if_exist(CpdlcFansSatcomIridium); + append_satcom_flag_if_exist(AtcSatvoiceInmarsat); + append_satcom_flag_if_exist(AtcSatvoiceMtsat); + append_satcom_flag_if_exist(AtcSatvoiceIridium); + append_satcom_flag_if_exist(CpdlcRcp400); + append_satcom_flag_if_exist(CpdlcRcp240); + append_satcom_flag_if_exist(SatvoiceRcp400); + + if (!equipment.isEmpty() && m_equipment == ComNavEquipment()) + { + // Default if nothing correct is provided + m_equipment = NoEquip; + m_cpdlcSatcomEquipment = {}; + } + } + + QStringList CComNavEquipment::enabledOptions() const + { + QStringList list; + + auto append_equipment_flag_if_exist = [&list, this](ComNavEquipmentOption flag) { + if (m_equipment.testFlag(flag)) list << flagToString(flag); + }; + + auto append_satcom_flag_if_exist = [&list, this](CpdlcSatcomEquipmentOption flag) { + if (m_cpdlcSatcomEquipment.testFlag(flag)) list << flagToString(flag); + }; + + append_equipment_flag_if_exist(Standard); + append_equipment_flag_if_exist(Gbas); + append_equipment_flag_if_exist(Lpv); + append_equipment_flag_if_exist(LoranC); + append_equipment_flag_if_exist(Dme); + append_equipment_flag_if_exist(FmcAcars); + append_equipment_flag_if_exist(DFisAcars); + append_equipment_flag_if_exist(PdcAcars); + append_equipment_flag_if_exist(Adf); + append_equipment_flag_if_exist(Gnss); + append_equipment_flag_if_exist(HfRtf); + append_equipment_flag_if_exist(InertiaNavigation); + append_satcom_flag_if_exist(CpdlcAtn); + append_satcom_flag_if_exist(CpdlcFansHfdl); + append_satcom_flag_if_exist(CpdlcFansVdlA); + append_satcom_flag_if_exist(CpdlcFansVdl2); + append_satcom_flag_if_exist(CpdlcFansSatcomInmarsat); + append_satcom_flag_if_exist(CpdlcFansSatcomMtsat); + append_satcom_flag_if_exist(CpdlcFansSatcomIridium); + append_equipment_flag_if_exist(Mls); + append_equipment_flag_if_exist(Ils); + append_satcom_flag_if_exist(AtcSatvoiceInmarsat); + append_satcom_flag_if_exist(AtcSatvoiceMtsat); + append_satcom_flag_if_exist(AtcSatvoiceIridium); + append_equipment_flag_if_exist(NoEquip); + append_equipment_flag_if_exist(Vor); + append_satcom_flag_if_exist(CpdlcRcp400); + append_satcom_flag_if_exist(CpdlcRcp240); + append_satcom_flag_if_exist(SatvoiceRcp400); + append_equipment_flag_if_exist(Pbn); + append_equipment_flag_if_exist(Tacan); + append_equipment_flag_if_exist(UhfRtf); + append_equipment_flag_if_exist(VhfRtf); + append_equipment_flag_if_exist(Rvsm); + append_equipment_flag_if_exist(Mnps); + append_equipment_flag_if_exist(Vhf833); + append_equipment_flag_if_exist(Other); + + return list; + } + + QString CComNavEquipment::convertToQString(bool) const + { + const QString equipmentString = enabledOptions().join(""); + Q_ASSERT_X(!equipmentString.isEmpty(), Q_FUNC_INFO, "Equipment string should not be empty"); + return equipmentString; + } + + QString CComNavEquipment::flagToString(CpdlcSatcomEquipmentOption flag) + { + switch (flag) + { + case CpdlcAtn: return QStringLiteral("J1"); + case CpdlcFansHfdl: return QStringLiteral("J2"); + case CpdlcFansVdlA: return QStringLiteral("J3"); + case CpdlcFansVdl2: return QStringLiteral("J4"); + case CpdlcFansSatcomInmarsat: return QStringLiteral("J5"); + case CpdlcFansSatcomMtsat: return QStringLiteral("J6"); + case CpdlcFansSatcomIridium: return QStringLiteral("J7"); + case AtcSatvoiceInmarsat: return QStringLiteral("M1"); + case AtcSatvoiceMtsat: return QStringLiteral("M2"); + case AtcSatvoiceIridium: return QStringLiteral("M3"); + case CpdlcRcp400: return QStringLiteral("P1"); + case CpdlcRcp240: return QStringLiteral("P2"); + case SatvoiceRcp400: return QStringLiteral("P3"); + default: return {}; + } + } + + QString CComNavEquipment::flagToString(ComNavEquipmentOption flag) + { + switch (flag) + { + case Standard: return QStringLiteral("S"); + case Gbas: return QStringLiteral("A"); + case Lpv: return QStringLiteral("B"); + case LoranC: return QStringLiteral("C"); + case Dme: return QStringLiteral("D"); + case FmcAcars: return QStringLiteral("E1"); + case DFisAcars: return QStringLiteral("E2"); + case PdcAcars: return QStringLiteral("E3"); + case Adf: return QStringLiteral("F"); + case Gnss: return QStringLiteral("G"); + case HfRtf: return QStringLiteral("H"); + case InertiaNavigation: return QStringLiteral("I"); + case Mls: return QStringLiteral("K"); + case Ils: return QStringLiteral("L"); + case NoEquip: return QStringLiteral("N"); + case Vor: return QStringLiteral("O"); + case Pbn: return QStringLiteral("R"); + case Tacan: return QStringLiteral("T"); + case UhfRtf: return QStringLiteral("U"); + case VhfRtf: return QStringLiteral("V"); + case Rvsm: return QStringLiteral("W"); + case Mnps: return QStringLiteral("X"); + case Vhf833: return QStringLiteral("Y"); + case Other: return QStringLiteral("Z"); + default: return {}; + } + } + + QStringList CComNavEquipment::allEquipmentLetters() + { + // In order as they appear in the final string + static const QStringList r({ "S", "A", "B", "C", "D", "E1", "E2", "E3", "F", "G", "H", "I", "J1", "J2", "J3", "J4", "J5", "J6", "J7", "K", "L", "M1", "M2", "M3", + "N", "O", "P1", "P2", "P3", "R", "T", "U", "V", "W", "X", "Y", "Z" }); + return r; + } + +} diff --git a/src/blackmisc/aviation/comnavequipment.h b/src/blackmisc/aviation/comnavequipment.h new file mode 100644 index 000000000..a1b3924b6 --- /dev/null +++ b/src/blackmisc/aviation/comnavequipment.h @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +#ifndef BLACKMISC_AVIATION_COMNAVEQUIPMENT_H +#define BLACKMISC_AVIATION_COMNAVEQUIPMENT_H + +#include "blackmisc/blackmiscexport.h" +#include "blackmisc/valueobject.h" + +BLACK_DECLARE_VALUEOBJECT_MIXINS(BlackMisc::Aviation, CComNavEquipment) + +namespace BlackMisc::Aviation +{ + //! ICAO flightplan field 10a + class BLACKMISC_EXPORT CComNavEquipment : public BlackMisc::CValueObject + { + public: + // QFlag uses int as an underlying type. Hence, the equipment is split up into two enums. + // Once Qt adds a QFlag64, these can be united. + // See https://bugreports.qt.io/browse/QTBUG-53178 + + //! CPLDC and SATCOM equipment options + enum CpdlcSatcomEquipmentOption : int + { + CpdlcAtn = (1 << 0), + CpdlcFansHfdl = (1 << 1), + CpdlcFansVdlA = (1 << 2), + CpdlcFansVdl2 = (1 << 3), + CpdlcFansSatcomInmarsat = (1 << 4), + CpdlcFansSatcomMtsat = (1 << 5), + CpdlcFansSatcomIridium = (1 << 6), + AtcSatvoiceInmarsat = (1 << 7), + AtcSatvoiceMtsat = (1 << 8), + AtcSatvoiceIridium = (1 << 9), + CpdlcRcp400 = (1 << 10), + CpdlcRcp240 = (1 << 11), + SatvoiceRcp400 = (1 << 12), + }; + + //! COM/NAV equipment options + enum ComNavEquipmentOption : int + { + Standard = (1 << 0), + Gbas = (1 << 1), + Lpv = (1 << 2), + LoranC = (1 << 3), + Dme = (1 << 4), + FmcAcars = (1 << 5), + DFisAcars = (1 << 6), + PdcAcars = (1 << 7), + Adf = (1 << 8), + Gnss = (1 << 9), + HfRtf = (1 << 10), + InertiaNavigation = (1 << 11), + Mls = (1 << 12), + Ils = (1 << 13), + NoEquip = (1 << 14), + Vor = (1 << 15), + Pbn = (1 << 16), + Tacan = (1 << 17), + UhfRtf = (1 << 18), + VhfRtf = (1 << 19), + Rvsm = (1 << 20), + Mnps = (1 << 21), + Vhf833 = (1 << 22), + Other = (1 << 23) + }; + + Q_DECLARE_FLAGS(ComNavEquipment, ComNavEquipmentOption); + Q_DECLARE_FLAGS(CpdlcSatcomEquipment, CpdlcSatcomEquipmentOption); + + //! Create default equipment with Standard COM/NAV + CComNavEquipment() = default; + + //! Create object with given COM/NAV, CPDLC and SATCOM equipment + CComNavEquipment(ComNavEquipment comNavEquipment, CpdlcSatcomEquipment cpdlcSatcomEquipment); + + //! Create object from an ICAO equipment string (for example "SDE2E3FGHIJ1RWXY") + explicit CComNavEquipment(QString equipment); + + //! Get all possible equipment code letters + static QStringList allEquipmentLetters(); + + //! @{ + //! Does this object contains \p equip? + bool hasEquipment(ComNavEquipmentOption equip) const { return m_equipment.testFlag(equip); } + bool hasEquipment(CpdlcSatcomEquipmentOption equip) const { return m_cpdlcSatcomEquipment.testFlag(equip); } + //! @} + + //! Get all enabled equipment codes of this object as a list + QStringList enabledOptions() const; + + //! Get the equipment string of this object (for example "SDE2E3FGHIJ1RWXY") + QString convertToQString(bool i18n = false) const; + + private: + //! @{ + //! Get the string for the specific equipment + static QString flagToString(CpdlcSatcomEquipmentOption flag); + static QString flagToString(ComNavEquipmentOption flag); + //! @} + + ComNavEquipment m_equipment = Standard; + CpdlcSatcomEquipment m_cpdlcSatcomEquipment; + + BLACK_METACLASS( + CComNavEquipment, + BLACK_METAMEMBER(equipment), + BLACK_METAMEMBER(cpdlcSatcomEquipment) + ); + }; +} + +Q_DECLARE_METATYPE(BlackMisc::Aviation::CComNavEquipment) + +#endif // BLACKMISC_AVIATION_COMNAVEQUIPMENT_H diff --git a/src/blackmisc/aviation/flightplanaircraftinfo.cpp b/src/blackmisc/aviation/flightplanaircraftinfo.cpp new file mode 100644 index 000000000..29d78cf26 --- /dev/null +++ b/src/blackmisc/aviation/flightplanaircraftinfo.cpp @@ -0,0 +1,388 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +#include "flightplanaircraftinfo.h" + +BLACK_DEFINE_VALUEOBJECT_MIXINS(BlackMisc::Aviation, CFlightPlanAircraftInfo) + +namespace BlackMisc::Aviation +{ + CFlightPlanAircraftInfo::CFlightPlanAircraftInfo(const CAircraftIcaoCode &aircraftIcao, const CComNavEquipment &comNavEquipment, + const CSsrEquipment &ssrEquipment, const CWakeTurbulenceCategory &wtc) : m_aircraftIcao(aircraftIcao), + m_comNavEquipment(comNavEquipment), + m_ssrEquipment(ssrEquipment), + m_wtc(wtc) {} + + CFlightPlanAircraftInfo::CFlightPlanAircraftInfo(QString equipmentCodeAndAircraft) + { + equipmentCodeAndAircraft = equipmentCodeAndAircraft.trimmed().toUpper().replace(" ", ""); + const int numberSlash = equipmentCodeAndAircraft.count("/"); + const int numberHypen = equipmentCodeAndAircraft.count("-"); + if (numberHypen == 1 && numberSlash == 2) + { + parseIcaoEquipmentCode(equipmentCodeAndAircraft); + } + else if (numberSlash >= 1 && numberSlash <= 2 && numberHypen == 0) + { + parseFaaEquipmentCode(equipmentCodeAndAircraft); + } + else + { + parseUnknownEquipmentCode(equipmentCodeAndAircraft); + } + } + + QString CFlightPlanAircraftInfo::asIcaoString() const + { + // Avoid returning empty wake turbulence categories and send it to the server. + // This can in particular happen when translating from FAA codes to ICAO codes. + // This sets a default wake turbulence category of MEDIUM if the category is unknown otherwise. + QChar wtc; + if (!m_wtc.isUnknown()) + { + wtc = m_wtc.toQString().at(0); + } + else + { + wtc = CWakeTurbulenceCategory(CWakeTurbulenceCategory::MEDIUM).toQString().at(0); + } + + return m_aircraftIcao.getDesignator() % "/" % wtc % "-" % m_comNavEquipment.toQString() % "/" % m_ssrEquipment.toQString(); + } + + QString CFlightPlanAircraftInfo::asFaaString() const + { + // TCAS prefix (T/) is not used + QString s; + if (m_wtc.isCategory(CWakeTurbulenceCategory::HEAVY)) + { + s = "H/"; + } + else if (m_wtc.isCategory(CWakeTurbulenceCategory::SUPER)) + { + s = "J/"; + } + s += m_aircraftIcao.getDesignator() % "/" % equipmentToFaaCode(m_comNavEquipment, m_ssrEquipment); + return s; + } + + CAircraftIcaoCode CFlightPlanAircraftInfo::getAircraftIcao() const + { + return m_aircraftIcao; + } + + CComNavEquipment CFlightPlanAircraftInfo::getComNavEquipment() const + { + return m_comNavEquipment; + } + + CSsrEquipment CFlightPlanAircraftInfo::getSsrEquipment() const + { + return m_ssrEquipment; + } + + CWakeTurbulenceCategory CFlightPlanAircraftInfo::getWtc() const + { + return m_wtc; + } + + QString CFlightPlanAircraftInfo::convertToQString(bool) const + { + return asIcaoString(); + } + + void CFlightPlanAircraftInfo::parseIcaoEquipmentCode(const QString &equipment) + { + // Example: B789/H-SDE1E2E3FGHIJ2J3J4J5M1RWXY/LB1D1 + QStringList firstSplit = equipment.split("/"); + Q_ASSERT_X(firstSplit.size() == 3, Q_FUNC_INFO, "Cannot split string as required for the ICAO format"); + if (!CAircraftIcaoCode::isValidDesignator(firstSplit[0]) || firstSplit[1].isEmpty() || firstSplit[2].isEmpty()) + { + return; // Invalid equipment code, leave everything default initialized + } + + m_aircraftIcao = CAircraftIcaoCode(firstSplit[0]); + + try + { + m_ssrEquipment = CSsrEquipment(firstSplit[2]); + } + catch (const std::invalid_argument &) + { + m_ssrEquipment = CSsrEquipment(); + } + + QStringList secondSplit = firstSplit[1].split("-"); + if (secondSplit.size() != 2) + { + return; // Invalid code, leave everything else default initialized + } + + if (!secondSplit[0].isEmpty()) + { + try + { + // if the wake turbulence category incorrectly contains more than one letter + // just take the first letter + m_wtc = CWakeTurbulenceCategory(secondSplit[0].at(0)); + } + catch (std::invalid_argument &) + { + m_wtc = CWakeTurbulenceCategory(); + } + } + else + { + m_wtc = CWakeTurbulenceCategory(); + } + + try + { + m_comNavEquipment = CComNavEquipment(secondSplit[1]); + } + catch (std::runtime_error &) + { + m_comNavEquipment = CComNavEquipment(); + } + } + + void CFlightPlanAircraftInfo::parseFaaEquipmentCode(const QString &equipment) + { + // Example: H/A346/L + QStringList split = equipment.split('/'); + Q_ASSERT_X(split.size() == 2 || split.size() == 3, Q_FUNC_INFO, "Cannot split string as required for the FAA format"); + bool missingEquipmentCode = false; + + if (CAircraftIcaoCode::isValidDesignator(split.at(split.size() - 2))) + { + m_aircraftIcao = CAircraftIcaoCode(split.at(split.size() - 2)); + } + else if (CAircraftIcaoCode::isValidDesignator(split.at(split.size() - 1))) + { + // the equipment code is missing (like J/A388) + m_aircraftIcao = CAircraftIcaoCode(split.at(split.size() - 1)); + missingEquipmentCode = true; + } + else + { + m_aircraftIcao = CAircraftIcaoCode(); + } + + // Check prefix (wake turbulence category) + if (split.length() == 3) + { + const QString &prefix = split.at(0); + if (prefix == "H" || prefix == "J") + { + m_wtc = CWakeTurbulenceCategory(prefix.at(0)); + } + } + else if (split.length() == 2 && split.at(0).size() == 1) + { + // the equipment code is missing (like J/A388) + m_wtc = CWakeTurbulenceCategory(split.at(0).at(0)); + missingEquipmentCode = true; + } + else + { + m_wtc = CWakeTurbulenceCategory(); + } + + // Equipment Code + if (missingEquipmentCode || split.at(split.size() - 1).isEmpty()) + { + return; // No (empty) equipment code + } + + // Always taking the first character. If the code contains more than one character, this is likely an error, but we will try it anyway + QChar equipmentCode = split.at(split.size() - 1).at(0); + auto [comNavEquipment, ssrEquipment] = faaCodeToEquipment(equipmentCode); + m_comNavEquipment = comNavEquipment; + m_ssrEquipment = ssrEquipment; + } + + void CFlightPlanAircraftInfo::parseUnknownEquipmentCode(const QString &equipment) + { + // likely one part only + QStringList split = equipment.split("/"); + if (split[0].length() > 1 && CAircraftIcaoCode::isValidDesignator(split[0])) + { + // only ICAO + m_aircraftIcao = split[0]; + } + else + { + // something invalid. Keep default initialized + } + } + + std::tuple CFlightPlanAircraftInfo::faaCodeToEquipment(QChar equipmentCode) + { + CComNavEquipment equip; + CSsrEquipment ssr; + + // COM/NAV equipment + if (equipmentCode == 'H' || equipmentCode == 'O' || equipmentCode == 'W') + { + equip = CComNavEquipment({ CComNavEquipment::Rvsm }, {}); + } + else if (equipmentCode == 'Z') + { + // PBN might not be the correct translation for "RNAV" but we use it to differentiate the codes + equip = CComNavEquipment({ CComNavEquipment::Rvsm | CComNavEquipment::Pbn }, {}); + } + else if (equipmentCode == 'L') + { + equip = CComNavEquipment({ CComNavEquipment::Rvsm | CComNavEquipment::Gnss }, {}); + } + else if (equipmentCode == 'X' || equipmentCode == 'T' || equipmentCode == 'U') + { + equip = CComNavEquipment({}, {}); + } + else if (equipmentCode == 'D' || equipmentCode == 'B' || equipmentCode == 'A') + { + equip = CComNavEquipment({ CComNavEquipment::Dme }, {}); + } + else if (equipmentCode == 'M' || equipmentCode == 'N' || equipmentCode == 'P') + { + equip = CComNavEquipment({ CComNavEquipment::Tacan }, {}); + } + else if (equipmentCode == 'Y' || equipmentCode == 'C' || equipmentCode == 'I') + { + // PBN might not be the correct translation for "RNAV" but we use it to differentiate the codes + equip = CComNavEquipment({ CComNavEquipment::Pbn }, {}); + } + else if (equipmentCode == 'V' || equipmentCode == 'S' || equipmentCode == 'G') + { + equip = CComNavEquipment({ CComNavEquipment::Gnss }, {}); + } + + // SSR equipment + if (equipmentCode == 'W' || equipmentCode == 'Z' || equipmentCode == 'L' || equipmentCode == 'U' || + equipmentCode == 'A' || equipmentCode == 'P' || equipmentCode == 'I' || equipmentCode == 'G') + { + ssr = CSsrEquipment::SSrEquipment { CSsrEquipment::ModeAC }; + } + else if (equipmentCode == 'H' || equipmentCode == 'O' || equipmentCode == 'X' || equipmentCode == 'D' || + equipmentCode == 'M' || equipmentCode == 'Y' || equipmentCode == 'V') + { + // "O" corresponds to a failed Mode C transponder. + // The ICAO format does not contain a separate code for a failed Mode C transponder + ssr = CSsrEquipment::SSrEquipment { CSsrEquipment::None }; + } + else if (equipmentCode == 'T' || equipmentCode == 'B' || equipmentCode == 'N' || equipmentCode == 'C' || + equipmentCode == 'S') + { + // The ICAO format does not contain a separate code for a general NONE Mode C transponder. We use Mode A instead. + ssr = CSsrEquipment::SSrEquipment { CSsrEquipment::ModeA }; + } + + return { equip, ssr }; + } + + QChar CFlightPlanAircraftInfo::equipmentToFaaCode(const CComNavEquipment &equip, const CSsrEquipment &ssr) + { + if (equip.hasEquipment(CComNavEquipment::Rvsm)) + { + if (ssr.hasEquipment(CSsrEquipment::None)) + { + // This could also be 'O' as we cannot differentiate between a failed transponder and a failed Mode C transponder + return 'H'; + } + + // In the following, do not check the transponder capability, as the FAA codes only work with Mode C and not Mode S transponders + if (equip.hasEquipment(CComNavEquipment::Gnss)) + { + return 'L'; + } + if (equip.hasEquipment(CComNavEquipment::Pbn)) + { + // PBN is used for RNAV when converting from FAA string to CFlightPlanAircraftInfo + return 'Z'; + } + else + { + return 'W'; + } + } + else + { + if (equip.hasEquipment(CComNavEquipment::Gnss)) + { + if (ssr.hasEquipment(CSsrEquipment::None)) + { + return 'V'; + } + if (ssr.hasEquipment(CSsrEquipment::ModeAC)) + { + return 'G'; + } + else + { + return 'S'; + } + } + if (equip.hasEquipment(CComNavEquipment::Tacan)) + { + if (ssr.hasEquipment(CSsrEquipment::None)) + { + return 'M'; + } + if (ssr.hasEquipment(CSsrEquipment::ModeAC)) + { + return 'P'; + } + else + { + return 'N'; + } + } + if (equip.hasEquipment(CComNavEquipment::Pbn)) + { + // PBN is used for RNAV when converting from FAA string to CFlightPlanAircraftInfo + if (ssr.hasEquipment(CSsrEquipment::None)) + { + return 'Y'; + } + if (ssr.hasEquipment(CSsrEquipment::ModeAC)) + { + return 'I'; + } + else + { + return 'C'; + } + } + if (equip.hasEquipment(CComNavEquipment::Dme)) + { + if (ssr.hasEquipment(CSsrEquipment::None)) + { + return 'D'; + } + if (ssr.hasEquipment(CSsrEquipment::ModeAC)) + { + return 'A'; + } + else + { + return 'B'; + } + } + + // No DME + if (ssr.hasEquipment(CSsrEquipment::None)) + { + return 'X'; + } + if (ssr.hasEquipment(CSsrEquipment::ModeAC)) + { + return 'U'; + } + else + { + return 'T'; + } + } + } + +} diff --git a/src/blackmisc/aviation/flightplanaircraftinfo.h b/src/blackmisc/aviation/flightplanaircraftinfo.h new file mode 100644 index 000000000..5f06758a8 --- /dev/null +++ b/src/blackmisc/aviation/flightplanaircraftinfo.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +#ifndef BLACKMISC_AVIATION_FLIGHTPLAN_AIRCRAFT_INFO_H +#define BLACKMISC_AVIATION_FLIGHTPLAN_AIRCRAFT_INFO_H + +#include "blackmisc/blackmiscexport.h" +#include "blackmisc/valueobject.h" +#include "blackmisc/aviation/aircrafticaocode.h" +#include "blackmisc/aviation/comnavequipment.h" +#include "blackmisc/aviation/ssrequipment.h" +#include "blackmisc/aviation/waketurbulencecategory.h" + +#include + +BLACK_DECLARE_VALUEOBJECT_MIXINS(BlackMisc::Aviation, CFlightPlanAircraftInfo) + +namespace BlackMisc::Aviation +{ + //! Flightplan-related information about an aircraft (aircraft ICAO, equipment and WTC) + class BLACKMISC_EXPORT CFlightPlanAircraftInfo : public CValueObject + { + public: + CFlightPlanAircraftInfo() = default; + + //! Create info from given aircraft ICAO, equipment and wake turbulence category + CFlightPlanAircraftInfo(const CAircraftIcaoCode &aircraftIcao, const CComNavEquipment &comNavEquipment, + const CSsrEquipment &ssrEquipment, const CWakeTurbulenceCategory &wtc); + + //! Create aircraft info from a string that contains the 4 parts of an ICAO equipment code (AIRCRAFT_ICAO/WTC-EQUIPMENT/SSR). + //! Passing FAA equipment codes like "H/B772/F" is supported as well + explicit CFlightPlanAircraftInfo(QString equipmentCodeAndAircraft); + + //! Full string in ICAO format: "AIRCRAFT_ICAO/WTC-EQUIPMENT/SSR" + QString asIcaoString() const; + + //! Full string in FAA format: "H/J (if heavy/super)/AIRCRAFT_ICAO/EQUIPMENT-CODE" + QString asFaaString() const; + + //! Get Aircraft ICAO + CAircraftIcaoCode getAircraftIcao() const; + + //! Get COM/NAV equipment + CComNavEquipment getComNavEquipment() const; + + //! Get SSR equipment + CSsrEquipment getSsrEquipment() const; + + //! Get Wake Turbulence Category + CWakeTurbulenceCategory getWtc() const; + + //! \copydoc BlackMisc::Mixin::String::toQString + QString convertToQString(bool i18n = false) const; + + //! Transform single character FAA equipment code to ICAO-based COM/NAV and SSR equipment + static std::tuple faaCodeToEquipment(QChar equipmentCode); + + //! Transform ICAO-based COM/NAV and SSR equipment to a single character FAA equipment code + static QChar equipmentToFaaCode(const CComNavEquipment &equip, const CSsrEquipment &ssr); + + private: + CAircraftIcaoCode m_aircraftIcao; //!< Aircraft ICAO code + CComNavEquipment m_comNavEquipment; //!< COM & NAV equipment (flight plan field 10a) + CSsrEquipment m_ssrEquipment; //!< secondary surveillance radar equipment (flight plan field 10b) + CWakeTurbulenceCategory m_wtc; //!< wake turbulence category. This information is also part of m_aircraftIcao, but is not always filled. + + void parseIcaoEquipmentCode(const QString &equipment); //!< Initialize members from ICAO format equipment codes + void parseFaaEquipmentCode(const QString &equipment); //!< Initialize members from FAA format equipment codes + void parseUnknownEquipmentCode(const QString &equipment); //!< Initialize members from unknown format equipment strings (best guesses) + + BLACK_METACLASS( + CFlightPlanAircraftInfo, + BLACK_METAMEMBER(aircraftIcao), + BLACK_METAMEMBER(comNavEquipment), + BLACK_METAMEMBER(ssrEquipment), + BLACK_METAMEMBER(wtc) + ); + }; + +} + +Q_DECLARE_METATYPE(BlackMisc::Aviation::CFlightPlanAircraftInfo) + +#endif // BLACKMISC_AVIATION_FLIGHTPLAN_AIRCRAFT_INFO_H diff --git a/src/blackmisc/aviation/registermetadataaviation.cpp b/src/blackmisc/aviation/registermetadataaviation.cpp index 56c704412..4be49089d 100644 --- a/src/blackmisc/aviation/registermetadataaviation.cpp +++ b/src/blackmisc/aviation/registermetadataaviation.cpp @@ -70,8 +70,11 @@ namespace BlackMisc CCallsign::registerMetadata(); CCallsignSet::registerMetadata(); CComSystem::registerMetadata(); + CSsrEquipment::registerMetadata(); + CComNavEquipment::registerMetadata(); CWakeTurbulenceCategory::registerMetadata(); CFlightPlan::registerMetadata(); + CFlightPlanAircraftInfo::registerMetadata(); CFlightPlanList::registerMetadata(); CSimBriefData::registerMetadata(); CFlightPlanRemarks::registerMetadata(); diff --git a/src/blackmisc/aviation/ssrequipment.cpp b/src/blackmisc/aviation/ssrequipment.cpp new file mode 100644 index 000000000..30139b082 --- /dev/null +++ b/src/blackmisc/aviation/ssrequipment.cpp @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +#include "ssrequipment.h" + +BLACK_DEFINE_VALUEOBJECT_MIXINS(BlackMisc::Aviation, CSsrEquipment) + +namespace BlackMisc::Aviation +{ + CSsrEquipment::CSsrEquipment(SSrEquipment equipment) : m_equipment(equipment) + { + if (m_equipment == SSrEquipment()) + { + m_equipment = None; + } + } + + CSsrEquipment::CSsrEquipment(QString equipment) + { + + if (equipment.isEmpty()) + { + return; + } + + m_equipment = {}; // Clear default flag + + auto append_equipment_flag_if_exist = [&equipment, this](SsrEquipmentOption flag) { + QString str = flagToString(flag); + if (equipment.contains(str)) + { + equipment = equipment.remove(str); + m_equipment |= flag; + } + }; + + append_equipment_flag_if_exist(None); + append_equipment_flag_if_exist(ModeA); + append_equipment_flag_if_exist(ModeAC); + append_equipment_flag_if_exist(ModeSTypeE); + append_equipment_flag_if_exist(ModeSTypeH); + append_equipment_flag_if_exist(ModeSTypeI); + append_equipment_flag_if_exist(ModeSTypeL); + append_equipment_flag_if_exist(ModeSTypeX); + append_equipment_flag_if_exist(ModeSTypeP); + append_equipment_flag_if_exist(ModeSTypeS); + append_equipment_flag_if_exist(AdsBB1); + append_equipment_flag_if_exist(AdsBB2); + append_equipment_flag_if_exist(AdsBU1); + append_equipment_flag_if_exist(AdsBU2); + append_equipment_flag_if_exist(AdsBV1); + append_equipment_flag_if_exist(AdsBV2); + append_equipment_flag_if_exist(AdsCD1); + append_equipment_flag_if_exist(AdsCG1); + + if (!equipment.isEmpty() && m_equipment == SSrEquipment()) + { + // Default if nothing correct is provided + m_equipment = None; + } + } + + QStringList CSsrEquipment::enabledOptions() const + { + QStringList list; + + // Append flag (as string) to list if flag exists in current equipment + auto append_flag_if_exist = [&list, this](SsrEquipmentOption flag) { + if (m_equipment.testFlag(flag)) list << flagToString(flag); + }; + + append_flag_if_exist(None); + append_flag_if_exist(ModeA); + append_flag_if_exist(ModeAC); + append_flag_if_exist(ModeSTypeE); + append_flag_if_exist(ModeSTypeH); + append_flag_if_exist(ModeSTypeI); + append_flag_if_exist(ModeSTypeL); + append_flag_if_exist(ModeSTypeX); + append_flag_if_exist(ModeSTypeP); + append_flag_if_exist(ModeSTypeS); + append_flag_if_exist(AdsBB1); + append_flag_if_exist(AdsBB2); + append_flag_if_exist(AdsBU1); + append_flag_if_exist(AdsBU2); + append_flag_if_exist(AdsBV1); + append_flag_if_exist(AdsBV2); + append_flag_if_exist(AdsCD1); + append_flag_if_exist(AdsCG1); + + return list; + } + + QString CSsrEquipment::convertToQString(bool) const + { + const QString equipmentString = enabledOptions().join(""); + Q_ASSERT_X(!equipmentString.isEmpty(), Q_FUNC_INFO, "Equipment string should not be empty"); + return equipmentString; + } + + QString CSsrEquipment::flagToString(CSsrEquipment::SSrEquipment flag) + { + if (flag == None) + { + static const QString q("N"); + return q; + } + if (flag == ModeA) + { + static const QString q("A"); + return q; + } + if (flag == ModeAC) + { + static const QString q("C"); + return q; + } + if (flag == ModeSTypeE) + { + static const QString q("E"); + return q; + } + if (flag == ModeSTypeH) + { + static const QString q("H"); + return q; + } + if (flag == ModeSTypeI) + { + static const QString q("I"); + return q; + } + if (flag == ModeSTypeL) + { + static const QString q("L"); + return q; + } + if (flag == ModeSTypeX) + { + static const QString q("X"); + return q; + } + if (flag == ModeSTypeP) + { + static const QString q("P"); + return q; + } + if (flag == ModeSTypeS) + { + static const QString q("S"); + return q; + } + if (flag == AdsBB1) + { + static const QString q("B1"); + return q; + } + if (flag == AdsBB2) + { + static const QString q("B2"); + return q; + } + if (flag == AdsBU1) + { + static const QString q("U1"); + return q; + } + if (flag == AdsBU2) + { + static const QString q("U2"); + return q; + } + if (flag == AdsBV1) + { + static const QString q("V1"); + return q; + } + if (flag == AdsBV2) + { + static const QString q("V2"); + return q; + } + if (flag == AdsCD1) + { + static const QString q("D1"); + return q; + } + if (flag == AdsCG1) + { + static const QString q("G1"); + return q; + } + return {}; + } + + QStringList CSsrEquipment::allEquipmentLetters() + { + // In order as they appear in the final string + static const QStringList r({ "N", "A", "C", "E", "H", "I", "L", "X", "P", "S", "B1", "B2", "U1", "U2", "V1", "V2", "D1", "G1" }); + return r; + } + +} diff --git a/src/blackmisc/aviation/ssrequipment.h b/src/blackmisc/aviation/ssrequipment.h new file mode 100644 index 000000000..608aac12c --- /dev/null +++ b/src/blackmisc/aviation/ssrequipment.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +#ifndef BLACKMISC_AVIATION_SSREQUIPMENT_H +#define BLACKMISC_AVIATION_SSREQUIPMENT_H + +#include "blackmisc/blackmiscexport.h" +#include "blackmisc/valueobject.h" + +BLACK_DECLARE_VALUEOBJECT_MIXINS(BlackMisc::Aviation, CSsrEquipment) + +namespace BlackMisc::Aviation +{ + //! ICAO flightplan field 10b + class BLACKMISC_EXPORT CSsrEquipment : public BlackMisc::CValueObject + { + public: + enum SsrEquipmentOption : int + { + None = (1 << 0), + ModeA = (1 << 1), + ModeAC = (1 << 2), + ModeSTypeE = (1 << 3), + ModeSTypeH = (1 << 4), + ModeSTypeI = (1 << 5), + ModeSTypeL = (1 << 6), + ModeSTypeX = (1 << 7), + ModeSTypeP = (1 << 8), + ModeSTypeS = (1 << 9), + AdsBB1 = (1 << 10), + AdsBB2 = (1 << 11), + AdsBU1 = (1 << 12), + AdsBU2 = (1 << 13), + AdsBV1 = (1 << 14), + AdsBV2 = (1 << 15), + AdsCD1 = (1 << 16), + AdsCG1 = (1 << 17) + }; + + Q_DECLARE_FLAGS(SSrEquipment, SsrEquipmentOption); + + //! Create default SSR equipment with "None" equipment enabled + CSsrEquipment() = default; + + //! Create object with given equipment + CSsrEquipment(SSrEquipment equipment); + + //! Create object from an ICAO SSR equipment string (for example "LB1") + explicit CSsrEquipment(QString equipment); + + //! Get all possible SSR equipment code letters + static QStringList allEquipmentLetters(); + + //! Get all enabled SSR equipment codes of this object as a list + QStringList enabledOptions() const; + + //! Get the SSR equipment string of this object (for example "LB1") + QString convertToQString(bool i18n = false) const; + + //! Does this object contains \p equip? + bool hasEquipment(SsrEquipmentOption equip) const { return m_equipment.testFlag(equip); } + + private: + //! Get the string for the specific SSR equipment + static QString flagToString(SSrEquipment flag); + + SSrEquipment m_equipment = None; + + BLACK_METACLASS( + CSsrEquipment, + BLACK_METAMEMBER(equipment) + ); + }; + +} + +Q_DECLARE_METATYPE(BlackMisc::Aviation::CSsrEquipment) + +#endif // BLACKMISC_AVIATION_SSREQUIPMENT_H diff --git a/tests/blackmisc/CMakeLists.txt b/tests/blackmisc/CMakeLists.txt index d596ea938..34bf4b1eb 100644 --- a/tests/blackmisc/CMakeLists.txt +++ b/tests/blackmisc/CMakeLists.txt @@ -30,6 +30,12 @@ add_swift_test( LINK_LIBRARIES misc tests_test Qt::Core ) +add_swift_test( + NAME misc_aviation_flightplanaircraftinfo + SOURCES aviation/testflightplan/testflightplanaircraftinfo.cpp + LINK_LIBRARIES misc tests_test Qt::Core +) + ############## ## Geo ## ############## diff --git a/tests/blackmisc/aviation/testflightplan/testflightplanaircraftinfo.cpp b/tests/blackmisc/aviation/testflightplan/testflightplanaircraftinfo.cpp new file mode 100644 index 000000000..4ea7dfe75 --- /dev/null +++ b/tests/blackmisc/aviation/testflightplan/testflightplanaircraftinfo.cpp @@ -0,0 +1,229 @@ +// SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 + +//! \cond PRIVATE_TESTS +//! \file +//! \ingroup testblackmisc + +#include "blackmisc/aviation/flightplanaircraftinfo.h" +#include "test.h" +#include + +using namespace BlackMisc::Aviation; + +namespace BlackMiscTest +{ + //! Flightplan unit tests + class CTestFlightPlanAircraftInfo : public QObject + { + Q_OBJECT + + private slots: + //! Convert ICAO equipment code to CFlightPlanAircraftInfo and vice versa + void icaoConvert(); + + //! Convert FAA equipment code to CFlightPlanAircraftInfo and vice versa + void faaConvert(); + + //! Convert non FAA/ICAO equipment code to CFlightPlanAircraftInfo and vice versa + void nonValidConvert(); + + private: + struct TestEntry + { + QString m_icaoEquipment; + QString m_faaEquipment; + QString m_aircraftIcao; + CWakeTurbulenceCategory m_wtc; + CComNavEquipment m_equipment; + CSsrEquipment m_ssrEquipment; + }; + + QList m_testEntries { + { "B737/M-SDE2E3FGHIRWXY/LB1", + "B737/L", + "B737", + CWakeTurbulenceCategory::MEDIUM, + CComNavEquipment(CComNavEquipment::ComNavEquipment(CComNavEquipment::Standard | CComNavEquipment::Dme | CComNavEquipment::DFisAcars | CComNavEquipment::PdcAcars | CComNavEquipment::Adf | CComNavEquipment::Gnss | CComNavEquipment::HfRtf | CComNavEquipment::InertiaNavigation | CComNavEquipment::Pbn | CComNavEquipment::Rvsm | CComNavEquipment::Mnps | CComNavEquipment::Vhf833), + CComNavEquipment::CpdlcSatcomEquipment()), + CSsrEquipment::SSrEquipment(CSsrEquipment::ModeSTypeL | CSsrEquipment::AdsBB1) }, + { "B748/H-SDE3FGHIM1M2RWXY/LB1", + "H/B748/L", + "B748", + CWakeTurbulenceCategory::HEAVY, + CComNavEquipment(CComNavEquipment::ComNavEquipment(CComNavEquipment::Standard | CComNavEquipment::Dme | CComNavEquipment::PdcAcars | CComNavEquipment::Adf | CComNavEquipment::Gnss | CComNavEquipment::HfRtf | CComNavEquipment::InertiaNavigation | CComNavEquipment::Pbn | CComNavEquipment::Rvsm | CComNavEquipment::Mnps | CComNavEquipment::Vhf833), + CComNavEquipment::CpdlcSatcomEquipment(CComNavEquipment::AtcSatvoiceInmarsat | CComNavEquipment::AtcSatvoiceMtsat)), + CSsrEquipment::SSrEquipment(CSsrEquipment::ModeSTypeL | CSsrEquipment::AdsBB1) }, + { "C172/L-DGV/C", + "C172/G", + "C172", + CWakeTurbulenceCategory::LIGHT, + CComNavEquipment(CComNavEquipment::ComNavEquipment(CComNavEquipment::VhfRtf | CComNavEquipment::Gnss | CComNavEquipment::Dme), + CComNavEquipment::CpdlcSatcomEquipment()), + CSsrEquipment::SSrEquipment(CSsrEquipment::ModeAC) }, + { "A388/J-SADE2E3FGHIJ3J4J5M1RWXY/LB1D1", + "J/A388/L", + "A388", + CWakeTurbulenceCategory::SUPER, + CComNavEquipment(CComNavEquipment::ComNavEquipment(CComNavEquipment::Standard | CComNavEquipment::Gbas | CComNavEquipment::Dme | CComNavEquipment::DFisAcars | CComNavEquipment::PdcAcars | CComNavEquipment::Adf | CComNavEquipment::Gnss | CComNavEquipment::HfRtf | CComNavEquipment::InertiaNavigation | CComNavEquipment::Pbn | CComNavEquipment::Rvsm | CComNavEquipment::Mnps | CComNavEquipment::Vhf833), + CComNavEquipment::CpdlcSatcomEquipment(CComNavEquipment::CpdlcFansVdlA | CComNavEquipment::CpdlcFansVdl2 | CComNavEquipment::CpdlcFansSatcomInmarsat | CComNavEquipment::AtcSatvoiceInmarsat)), + CSsrEquipment::SSrEquipment(CSsrEquipment::ModeSTypeL | CSsrEquipment::AdsBB1 | CSsrEquipment::AdsCD1) } + }; + }; + + void CTestFlightPlanAircraftInfo::icaoConvert() + { + for (const TestEntry &entry : m_testEntries) + { + CFlightPlanAircraftInfo info(entry.m_icaoEquipment); + + QVERIFY2(info.getAircraftIcao().hasDesignator(), "Should have designator"); + QVERIFY2(info.getAircraftIcao().getDesignator() == entry.m_aircraftIcao, "Should have same aircraft ICAO code"); + QCOMPARE(info.getWtc(), entry.m_wtc); + QCOMPARE(info.getComNavEquipment(), entry.m_equipment); + QCOMPARE(info.getSsrEquipment(), entry.m_ssrEquipment); + QCOMPARE(info.asIcaoString(), entry.m_icaoEquipment); + } + } + + void CTestFlightPlanAircraftInfo::faaConvert() + { + for (const TestEntry &entry : m_testEntries) + { + CFlightPlanAircraftInfo info(entry.m_faaEquipment); + + QVERIFY2(info.getAircraftIcao().hasDesignator(), "Should have designator"); + QCOMPARE(info.getAircraftIcao().getDesignator(), entry.m_aircraftIcao); + + // FAA code only contains information about heavy and super wake turbulence category + if (entry.m_wtc.isCategory(CWakeTurbulenceCategory::HEAVY) || entry.m_wtc.isCategory(CWakeTurbulenceCategory::SUPER)) + { + QCOMPARE(info.getWtc(), entry.m_wtc); + } + else + { + QVERIFY2(info.getWtc().isUnknown(), "Should have UNKNOWN wake turbulence category"); + } + + // Cannot compare COM/NAV equipment and SSR equipment as the FAA code does not contain that much detail + + QCOMPARE(info.asFaaString(), entry.m_faaEquipment); + } + } + + void CTestFlightPlanAircraftInfo::nonValidConvert() + { + // Missing WTC and equipment code + CFlightPlanAircraftInfo info("A388"); + QCOMPARE(info.asFaaString(), "A388/X"); + QCOMPARE(info.asIcaoString(), "A388/M-S/N"); + + // FAA format: Missing equipment code + info = CFlightPlanAircraftInfo("J/A388"); + QCOMPARE(info.asFaaString(), "J/A388/X"); + QCOMPARE(info.asIcaoString(), "A388/J-S/N"); + + // FAA format: Wrong aircraft ICAO (but correct length according to CAircraftIcaoCode::isValidDesignator) + info = CFlightPlanAircraftInfo("H/A1/W"); + QCOMPARE(info.asFaaString(), "H/A1/W"); + QCOMPARE(info.asIcaoString(), "A1/H-W/C"); + + // FAA format: Missing aircraft ICAO with WTC + info = CFlightPlanAircraftInfo("H//W"); + QCOMPARE(info.asFaaString(), "H//W"); + QCOMPARE(info.asIcaoString(), "/H-W/C"); + + // FAA format: Equipment code only + info = CFlightPlanAircraftInfo("/W"); + QCOMPARE(info.asFaaString(), "/W"); + QCOMPARE(info.asIcaoString(), "/M-W/C"); + + // Wrong aircraft ICAO (too short) without WTC and equipment + info = CFlightPlanAircraftInfo("X"); + QCOMPARE(info.asFaaString(), "/X"); + QCOMPARE(info.asIcaoString(), "/M-S/N"); + + // Wrong aircraft ICAO (too long) without WTC and equipment + info = CFlightPlanAircraftInfo("ABCDEFGHIJKL"); + QCOMPARE(info.asFaaString(), "/X"); + QCOMPARE(info.asIcaoString(), "/M-S/N"); + + // Empty + info = CFlightPlanAircraftInfo(""); + QCOMPARE(info.asFaaString(), "/X"); + QCOMPARE(info.asIcaoString(), "/M-S/N"); + + // FAA format: Lower case (all) + info = CFlightPlanAircraftInfo("h/b744/L"); + QCOMPARE(info.asFaaString(), "H/B744/L"); + QCOMPARE(info.asIcaoString(), "B744/H-GW/C"); + + // FAA format: Lower case without WTC + info = CFlightPlanAircraftInfo("b738/w"); + QCOMPARE(info.asFaaString(), "B738/W"); + QCOMPARE(info.asIcaoString(), "B738/M-W/C"); + + // Lower case without WTC and equipment + info = CFlightPlanAircraftInfo("dh8d"); + QCOMPARE(info.asFaaString(), "DH8D/X"); + QCOMPARE(info.asIcaoString(), "DH8D/M-S/N"); + + // FAA format: Invalid WTC + info = CFlightPlanAircraftInfo("Q/A346"); + QCOMPARE(info.asFaaString(), "A346/X"); + QCOMPARE(info.asIcaoString(), "A346/M-S/N"); + + // FAA format: Leading whitespace + info = CFlightPlanAircraftInfo(" H/B748/L"); + QCOMPARE(info.asFaaString(), "H/B748/L"); + QCOMPARE(info.asIcaoString(), "B748/H-GW/C"); + + // FAA format: Trailing whitespace + info = CFlightPlanAircraftInfo("H/B748/L "); + QCOMPARE(info.asFaaString(), "H/B748/L"); + QCOMPARE(info.asIcaoString(), "B748/H-GW/C"); + + // FAA format: Whitespaces in between + info = CFlightPlanAircraftInfo("H / B7 48 /L"); + QCOMPARE(info.asFaaString(), "H/B748/L"); + QCOMPARE(info.asIcaoString(), "B748/H-GW/C"); + + // ICAO format: Invalid WTC + info = CFlightPlanAircraftInfo("A339/?-S/N"); + QCOMPARE(info.asFaaString(), "A339/X"); + QCOMPARE(info.asIcaoString(), "A339/M-S/N"); + + // ICAO format: Missing SSR equipment code + info = CFlightPlanAircraftInfo("A339/H-S"); + QCOMPARE(info.asFaaString(), "A339/X"); + QCOMPARE(info.asIcaoString(), "A339/M-S/N"); + + // ICAO format: Lowercase + info = CFlightPlanAircraftInfo("b737/m-sde2e3fghirwxy/lb1"); + QCOMPARE(info.asFaaString(), "B737/L"); + QCOMPARE(info.asIcaoString(), "B737/M-SDE2E3FGHIRWXY/LB1"); + + // ICAO format: Leading whitespace + info = CFlightPlanAircraftInfo(" B737/M-SDE2E3FGHIRWXY/LB1"); + QCOMPARE(info.asFaaString(), "B737/L"); + QCOMPARE(info.asIcaoString(), "B737/M-SDE2E3FGHIRWXY/LB1"); + + // ICAO format: Trailing whitespace + info = CFlightPlanAircraftInfo("B737/M-SDE2E3FGHIRWXY/LB1 "); + QCOMPARE(info.asFaaString(), "B737/L"); + QCOMPARE(info.asIcaoString(), "B737/M-SDE2E3FGHIRWXY/LB1"); + + // ICAO format: Whitespaces in between + info = CFlightPlanAircraftInfo("B737/ M - SDE2E3FGH IRWXY / LB1"); + QCOMPARE(info.asFaaString(), "B737/L"); + QCOMPARE(info.asIcaoString(), "B737/M-SDE2E3FGHIRWXY/LB1"); + } + +} // ns + +//! main +BLACKTEST_APPLESS_MAIN(BlackMiscTest::CTestFlightPlanAircraftInfo); + +#include "testflightplanaircraftinfo.moc" + +//! \endcond