swift
flightplanaircraftinfo.cpp
1 // SPDX-FileCopyrightText: Copyright (C) swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "flightplanaircraftinfo.h"
5 
6 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::aviation, CFlightPlanAircraftInfo)
7 
8 namespace swift::misc::aviation
9 {
10  CFlightPlanAircraftInfo::CFlightPlanAircraftInfo(const CAircraftIcaoCode &aircraftIcao,
11  const CComNavEquipment &comNavEquipment,
12  const CSsrEquipment &ssrEquipment,
13  const CWakeTurbulenceCategory &wtc)
14  : m_aircraftIcao(aircraftIcao), m_comNavEquipment(comNavEquipment), m_ssrEquipment(ssrEquipment), m_wtc(wtc)
15  {}
16 
17  CFlightPlanAircraftInfo::CFlightPlanAircraftInfo(QString equipmentCodeAndAircraft)
18  {
19  equipmentCodeAndAircraft = equipmentCodeAndAircraft.trimmed().toUpper().replace(" ", "");
20  const int numberSlash = equipmentCodeAndAircraft.count("/");
21  const int numberHypen = equipmentCodeAndAircraft.count("-");
22  if (numberHypen == 1 && numberSlash == 2) { parseIcaoEquipmentCode(equipmentCodeAndAircraft); }
23  else if (numberSlash >= 1 && numberSlash <= 2 && numberHypen == 0)
24  {
25  parseFaaEquipmentCode(equipmentCodeAndAircraft);
26  }
27  else { parseUnknownEquipmentCode(equipmentCodeAndAircraft); }
28  }
29 
31  {
32  // Avoid returning empty wake turbulence categories and send it to the server.
33  // This can in particular happen when translating from FAA codes to ICAO codes.
34  // This sets a default wake turbulence category of MEDIUM if the category is unknown otherwise.
35  QChar wtc;
36  if (!m_wtc.isUnknown()) { wtc = m_wtc.toQString().at(0); }
38 
39  return m_aircraftIcao.getDesignator() % "/" % wtc % "-" % m_comNavEquipment.toQString() % "/" %
40  m_ssrEquipment.toQString();
41  }
42 
44  {
45  // TCAS prefix (T/) is not used
46  QString s;
47  if (m_wtc.isCategory(CWakeTurbulenceCategory::HEAVY)) { s = "H/"; }
48  else if (m_wtc.isCategory(CWakeTurbulenceCategory::SUPER)) { s = "J/"; }
49  s += m_aircraftIcao.getDesignator() % "/" % equipmentToFaaCode(m_comNavEquipment, m_ssrEquipment);
50  return s;
51  }
52 
54 
55  CComNavEquipment CFlightPlanAircraftInfo::getComNavEquipment() const { return m_comNavEquipment; }
56 
57  CSsrEquipment CFlightPlanAircraftInfo::getSsrEquipment() const { return m_ssrEquipment; }
58 
60 
61  QString CFlightPlanAircraftInfo::convertToQString(bool) const { return asIcaoString(); }
62 
63  void CFlightPlanAircraftInfo::parseIcaoEquipmentCode(const QString &equipment)
64  {
65  // Example: B789/H-SDE1E2E3FGHIJ2J3J4J5M1RWXY/LB1D1
66  QStringList firstSplit = equipment.split("/");
67  Q_ASSERT_X(firstSplit.size() == 3, Q_FUNC_INFO, "Cannot split string as required for the ICAO format");
68  if (!CAircraftIcaoCode::isValidDesignator(firstSplit[0]) || firstSplit[1].isEmpty() || firstSplit[2].isEmpty())
69  {
70  return; // Invalid equipment code, leave everything default initialized
71  }
72 
73  m_aircraftIcao = CAircraftIcaoCode(firstSplit[0]);
74 
75  try
76  {
77  m_ssrEquipment = CSsrEquipment(firstSplit[2]);
78  }
79  catch (const std::invalid_argument &)
80  {
81  m_ssrEquipment = CSsrEquipment();
82  }
83 
84  QStringList secondSplit = firstSplit[1].split("-");
85  if (secondSplit.size() != 2)
86  {
87  return; // Invalid code, leave everything else default initialized
88  }
89 
90  if (!secondSplit[0].isEmpty())
91  {
92  try
93  {
94  // if the wake turbulence category incorrectly contains more than one letter
95  // just take the first letter
96  m_wtc = CWakeTurbulenceCategory(secondSplit[0].at(0));
97  }
98  catch (std::invalid_argument &)
99  {
100  m_wtc = CWakeTurbulenceCategory();
101  }
102  }
103  else { m_wtc = CWakeTurbulenceCategory(); }
104 
105  try
106  {
107  m_comNavEquipment = CComNavEquipment(secondSplit[1]);
108  }
109  catch (std::runtime_error &)
110  {
111  m_comNavEquipment = CComNavEquipment();
112  }
113  }
114 
115  void CFlightPlanAircraftInfo::parseFaaEquipmentCode(const QString &equipment)
116  {
117  // Example: H/A346/L
118  QStringList split = equipment.split('/');
119  Q_ASSERT_X(split.size() == 2 || split.size() == 3, Q_FUNC_INFO,
120  "Cannot split string as required for the FAA format");
121  bool missingEquipmentCode = false;
122 
124  {
125  m_aircraftIcao = CAircraftIcaoCode(split.at(split.size() - 2));
126  }
127  else if (CAircraftIcaoCode::isValidDesignator(split.at(split.size() - 1)))
128  {
129  // the equipment code is missing (like J/A388)
130  m_aircraftIcao = CAircraftIcaoCode(split.at(split.size() - 1));
131  missingEquipmentCode = true;
132  }
133  else { m_aircraftIcao = CAircraftIcaoCode(); }
134 
135  // Check prefix (wake turbulence category)
136  if (split.length() == 3)
137  {
138  const QString &prefix = split.at(0);
139  if (prefix == "H" || prefix == "J") { m_wtc = CWakeTurbulenceCategory(prefix.at(0)); }
140  }
141  else if (split.length() == 2 && split.at(0).size() == 1)
142  {
143  // the equipment code is missing (like J/A388)
144  m_wtc = CWakeTurbulenceCategory(split.at(0).at(0));
145  missingEquipmentCode = true;
146  }
147  else { m_wtc = CWakeTurbulenceCategory(); }
148 
149  // Equipment Code
150  if (missingEquipmentCode || split.at(split.size() - 1).isEmpty())
151  {
152  return; // No (empty) equipment code
153  }
154 
155  // Always taking the first character. If the code contains more than one character, this is likely an error, but
156  // we will try it anyway
157  QChar equipmentCode = split.at(split.size() - 1).at(0);
158  auto [comNavEquipment, ssrEquipment] = faaCodeToEquipment(equipmentCode);
159  m_comNavEquipment = comNavEquipment;
160  m_ssrEquipment = ssrEquipment;
161  }
162 
163  void CFlightPlanAircraftInfo::parseUnknownEquipmentCode(const QString &equipment)
164  {
165  // likely one part only
166  QStringList split = equipment.split("/");
167  if (split[0].length() > 1 && CAircraftIcaoCode::isValidDesignator(split[0]))
168  {
169  // only ICAO
170  m_aircraftIcao = split[0];
171  }
172  else
173  {
174  // something invalid. Keep default initialized
175  }
176  }
177 
178  std::tuple<CComNavEquipment, CSsrEquipment> CFlightPlanAircraftInfo::faaCodeToEquipment(QChar equipmentCode)
179  {
180  CComNavEquipment equip;
181  CSsrEquipment ssr;
182 
183  // COM/NAV equipment
184  if (equipmentCode == 'H' || equipmentCode == 'O' || equipmentCode == 'W')
185  {
186  equip = CComNavEquipment({ CComNavEquipment::Rvsm }, {});
187  }
188  else if (equipmentCode == 'Z')
189  {
190  // PBN might not be the correct translation for "RNAV" but we use it to differentiate the codes
191  equip = CComNavEquipment({ CComNavEquipment::Rvsm | CComNavEquipment::Pbn }, {});
192  }
193  else if (equipmentCode == 'L')
194  {
195  equip = CComNavEquipment({ CComNavEquipment::Rvsm | CComNavEquipment::Gnss }, {});
196  }
197  else if (equipmentCode == 'X' || equipmentCode == 'T' || equipmentCode == 'U')
198  {
199  equip = CComNavEquipment({}, {});
200  }
201  else if (equipmentCode == 'D' || equipmentCode == 'B' || equipmentCode == 'A')
202  {
203  equip = CComNavEquipment({ CComNavEquipment::Dme }, {});
204  }
205  else if (equipmentCode == 'M' || equipmentCode == 'N' || equipmentCode == 'P')
206  {
207  equip = CComNavEquipment({ CComNavEquipment::Tacan }, {});
208  }
209  else if (equipmentCode == 'Y' || equipmentCode == 'C' || equipmentCode == 'I')
210  {
211  // PBN might not be the correct translation for "RNAV" but we use it to differentiate the codes
212  equip = CComNavEquipment({ CComNavEquipment::Pbn }, {});
213  }
214  else if (equipmentCode == 'V' || equipmentCode == 'S' || equipmentCode == 'G')
215  {
216  equip = CComNavEquipment({ CComNavEquipment::Gnss }, {});
217  }
218 
219  // SSR equipment
220  if (equipmentCode == 'W' || equipmentCode == 'Z' || equipmentCode == 'L' || equipmentCode == 'U' ||
221  equipmentCode == 'A' || equipmentCode == 'P' || equipmentCode == 'I' || equipmentCode == 'G')
222  {
223  ssr = CSsrEquipment::SSrEquipment { CSsrEquipment::ModeAC };
224  }
225  else if (equipmentCode == 'H' || equipmentCode == 'O' || equipmentCode == 'X' || equipmentCode == 'D' ||
226  equipmentCode == 'M' || equipmentCode == 'Y' || equipmentCode == 'V')
227  {
228  // "O" corresponds to a failed Mode C transponder.
229  // The ICAO format does not contain a separate code for a failed Mode C transponder
230  ssr = CSsrEquipment::SSrEquipment { CSsrEquipment::None };
231  }
232  else if (equipmentCode == 'T' || equipmentCode == 'B' || equipmentCode == 'N' || equipmentCode == 'C' ||
233  equipmentCode == 'S')
234  {
235  // The ICAO format does not contain a separate code for a general NONE Mode C transponder. We use Mode A
236  // instead.
237  ssr = CSsrEquipment::SSrEquipment { CSsrEquipment::ModeA };
238  }
239 
240  return { equip, ssr };
241  }
242 
244  {
245  if (equip.hasEquipment(CComNavEquipment::Rvsm))
246  {
247  if (ssr.hasEquipment(CSsrEquipment::None))
248  {
249  // This could also be 'O' as we cannot differentiate between a failed transponder and a failed Mode C
250  // transponder
251  return 'H';
252  }
253 
254  // In the following, do not check the transponder capability, as the FAA codes only work with Mode C and not
255  // Mode S transponders
256  if (equip.hasEquipment(CComNavEquipment::Gnss)) { return 'L'; }
257  if (equip.hasEquipment(CComNavEquipment::Pbn))
258  {
259  // PBN is used for RNAV when converting from FAA string to CFlightPlanAircraftInfo
260  return 'Z';
261  }
262  else { return 'W'; }
263  }
264  else
265  {
266  if (equip.hasEquipment(CComNavEquipment::Gnss))
267  {
268  if (ssr.hasEquipment(CSsrEquipment::None)) { return 'V'; }
269  if (ssr.hasEquipment(CSsrEquipment::ModeAC)) { return 'G'; }
270  else { return 'S'; }
271  }
272  if (equip.hasEquipment(CComNavEquipment::Tacan))
273  {
274  if (ssr.hasEquipment(CSsrEquipment::None)) { return 'M'; }
275  if (ssr.hasEquipment(CSsrEquipment::ModeAC)) { return 'P'; }
276  else { return 'N'; }
277  }
278  if (equip.hasEquipment(CComNavEquipment::Pbn))
279  {
280  // PBN is used for RNAV when converting from FAA string to CFlightPlanAircraftInfo
281  if (ssr.hasEquipment(CSsrEquipment::None)) { return 'Y'; }
282  if (ssr.hasEquipment(CSsrEquipment::ModeAC)) { return 'I'; }
283  else { return 'C'; }
284  }
285  if (equip.hasEquipment(CComNavEquipment::Dme))
286  {
287  if (ssr.hasEquipment(CSsrEquipment::None)) { return 'D'; }
288  if (ssr.hasEquipment(CSsrEquipment::ModeAC)) { return 'A'; }
289  else { return 'B'; }
290  }
291 
292  // No DME
293  if (ssr.hasEquipment(CSsrEquipment::None)) { return 'X'; }
294  if (ssr.hasEquipment(CSsrEquipment::ModeAC)) { return 'U'; }
295  else { return 'T'; }
296  }
297  }
298 
299 } // namespace swift::misc::aviation
Value object for ICAO classification.
const QString & getDesignator() const
Get ICAO designator, e.g. "B737".
static bool isValidDesignator(const QString &designator)
Valid designator?
bool hasEquipment(ComNavEquipmentOption equip) const
Does this object contains equip?
static QChar equipmentToFaaCode(const CComNavEquipment &equip, const CSsrEquipment &ssr)
Transform ICAO-based COM/NAV and SSR equipment to a single character FAA equipment code.
CWakeTurbulenceCategory getWtc() const
Get Wake Turbulence Category.
QString asIcaoString() const
Full string in ICAO format: "AIRCRAFT_ICAO/WTC-EQUIPMENT/SSR".
CSsrEquipment getSsrEquipment() const
Get SSR equipment.
static std::tuple< CComNavEquipment, CSsrEquipment > faaCodeToEquipment(QChar equipmentCode)
Transform single character FAA equipment code to ICAO-based COM/NAV and SSR equipment.
QString convertToQString(bool i18n=false) const
Cast as QString.
QString asFaaString() const
Full string in FAA format: "H/J (if heavy/super)/AIRCRAFT_ICAO/EQUIPMENT-CODE".
CAircraftIcaoCode getAircraftIcao() const
Get Aircraft ICAO.
CComNavEquipment getComNavEquipment() const
Get COM/NAV equipment.
ICAO flightplan field 10b.
Definition: ssrequipment.h:16
bool hasEquipment(SsrEquipmentOption equip) const
Does this object contains equip?
Definition: ssrequipment.h:61
bool isUnknown() const
Is the wake turbulence category unknown?
bool isCategory(WakeTurbulenceCategory category) const
Is the wake turbulence category of this object the same as category?
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
std::vector< std::string > split(const std::string &str, size_t maxSplitCount=0, const std::string &delimiter=" ")
Split string by delimiter and maxSplitCount times.
Definition: qtfreeutils.h:55
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67