swift
flightplan.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QDateTime>
7 #include <QDomDocument>
8 #include <QFile>
9 #include <QRegularExpression>
10 #include <QStringBuilder>
11 #include <QTimeZone>
12 
14 #include "misc/aviation/altitude.h"
15 #include "misc/fileutils.h"
16 #include "misc/iconlist.h"
17 #include "misc/json.h"
18 #include "misc/pq/speed.h"
19 #include "misc/pq/time.h"
20 #include "misc/propertyindexvariantmap.h" // needed for mixin::Index::apply
21 #include "misc/stringutils.h"
22 
23 using namespace swift::misc::network;
24 using namespace swift::misc::physical_quantities;
25 
26 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::aviation, CFlightPlanRemarks)
27 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::aviation, CFlightPlan)
28 
29 namespace swift::misc::aviation
30 {
31  CFlightPlanRemarks::CFlightPlanRemarks(const QString &remarks, bool parse) : m_remarks(cleanRemarks(remarks))
32  {
33  if (parse) { this->parseFlightPlanRemarks(); }
34  }
35 
36  CFlightPlanRemarks::CFlightPlanRemarks(const QString &remarks, CVoiceCapabilities voiceCapabilities, bool parse)
37  : m_remarks(cleanRemarks(remarks)), m_voiceCapabilities(voiceCapabilities)
38  {
39  if (parse) { this->parseFlightPlanRemarks(); }
40  }
41 
43  {
44  if (m_selcalCode == selcal || selcal.length() != 4) { return false; }
45  const QString r =
46  CFlightPlanRemarks::replaceRemark(m_remarks, QStringLiteral("SEL/"), QStringLiteral("SEL/%1").arg(selcal));
47  m_remarks = r;
48  return true;
49  }
50 
52  {
53  m_voiceCapabilities = capabilities;
54  const QString r =
55  CFlightPlanRemarks::replaceVoiceCapabilities(m_voiceCapabilities.toFlightPlanRemarks(), m_remarks);
56  m_remarks = r;
57  }
58 
60  {
61  if (!m_isParsed) { return false; }
62  return this->hasParsedAirlineRemarks() || m_selcalCode.isValid() || !m_voiceCapabilities.isUnknown();
63  }
64 
66  {
67  if (!m_isParsed) { return false; }
68  return !m_radioTelephony.isEmpty() || !m_flightOperator.isEmpty() || m_airlineIcao.hasValidDesignator();
69  }
70 
72  {
73  const QString s = (m_registration.isEmpty() ? QString() : u"reg.: " % m_registration.toQString(i18n)) %
74  (!this->hasValidAirlineIcao() ? QString() : u" airline: " % m_airlineIcao.getDesignator()) %
75  (m_radioTelephony.isEmpty() ? QString() : u" radio tel.:" % m_radioTelephony) %
76  (m_flightOperator.isEmpty() ? QString() : u" operator: " % m_flightOperator) %
77  (!m_selcalCode.isValid() ? QString() : u" SELCAL: " % m_selcalCode.getCode()) % u" voice: " %
78  m_voiceCapabilities.toQString(i18n);
79  return s.simplified().trimmed();
80  }
81 
83  {
84  const CVoiceCapabilities vc = CVoiceCapabilities::fromText(text);
85  return vc.toFlightPlanRemarks();
86  }
87 
88  QString CFlightPlanRemarks::replaceVoiceCapabilities(const QString &newCapRemarks, const QString &oldRemarks)
89  {
90  if (newCapRemarks.isEmpty()) { return oldRemarks; }
91  if (oldRemarks.isEmpty()) { return newCapRemarks; }
92 
93  QString r(oldRemarks);
94  if (r.contains("/V/", Qt::CaseInsensitive))
95  {
96  r.replace("/V/", newCapRemarks, Qt::CaseInsensitive);
97  return r;
98  }
99  if (r.contains("/R/", Qt::CaseInsensitive))
100  {
101  r.replace("/R/", newCapRemarks, Qt::CaseInsensitive);
102  return r;
103  }
104  if (r.contains("/T/", Qt::CaseInsensitive))
105  {
106  r.replace("/T/", newCapRemarks, Qt::CaseInsensitive);
107  return r;
108  }
109  return newCapRemarks % u' ' % r;
110  }
111 
113  {
114  QString r = remarksIn;
115  r.replace(':', ' ');
117  return r;
118  }
119 
121  {
122  // examples: VFPS = VATSIM Flightplan Prefile System
123  // 1) RT/KESTREL OPR/MYTRAVEL REG/G-DAJC SEL/FP-ES PER/C NAV/RNP10
124  // 2) OPR/UAL CALLSIGN/UNITED
125  // 3) /v/FPL-VIR9-IS-A346/DEP/S-EGLL/ARR/KJFK/REG/G-VGAS/TCAS RVR/200 OPR/VIRGIN AIRLINES
126 
127  if (!force && m_isParsed) { return; }
128  m_isParsed = true;
129  if (m_remarks.isEmpty()) { return; }
130  const QString remarks = m_remarks.toUpper();
131  const QString callsign = CCallsign::unifyCallsign(getRemark(remarks, "REG/")); // registration is a callsign
132  if (CCallsign::isValidAircraftCallsign(callsign)) { m_registration = CCallsign(callsign, CCallsign::Aircraft); }
133  m_voiceCapabilities = m_voiceCapabilities.isUnknown() ? CVoiceCapabilities(m_remarks) : m_voiceCapabilities;
134  m_flightOperator =
135  getRemark(remarks, "OPR/"); // operator, e.g. British airways, sometimes people use ICAO code here
136  m_selcalCode = CSelcal(getRemark(remarks, "SEL/"));
137  m_radioTelephony = getRemark(remarks, "CALLSIGN/"); // used similar to radio telephony
138  if (m_radioTelephony.isEmpty()) { m_radioTelephony = getRemark(remarks, "RT/"); }
139  if (!m_flightOperator.isEmpty() && CAirlineIcaoCode::isValidAirlineDesignator(m_flightOperator))
140  {
141  // if people use ICAO code as flight operator swap with airline ICAO
142  m_airlineIcao = CAirlineIcaoCode(m_flightOperator);
143  m_flightOperator.clear();
144  }
145  }
146 
147  QString CFlightPlanRemarks::getRemark(const QString &remarks, const QString &marker)
148  {
149  const int maxIndex = remarks.size() - 1;
150  int f = remarks.indexOf(marker);
151  if (f < 0) { return {}; }
152  f += marker.length();
153  if (maxIndex <= f) { return {}; }
154 
155  // the remarks are poorly formatted:
156  // 1) sometimes the values are enclosed in "/", like "/REG/D-AMBZ/"
157  // 2) sometimes the values are containing space, like "/OPR/DELTA AIRLINES"
158  // 3) in many cases the end delimiter is a new marker or the EOL (otherwise 1 applies)
159 
160  thread_local const QRegularExpression nextMarker("\\s+\\w*/|$");
161  int to1 = remarks.indexOf(nextMarker, f + 1); // for case 2,3
162  if (to1 < 0) { to1 = maxIndex + 1; }
163  int to2 = remarks.indexOf('/', f + 1); // for case 1
164  if (to2 < 0) { to2 = maxIndex + 1; } // no more end markers, ends after last character
165  const int to = qMin(to1, to2);
166  const QString cut = remarks.mid(f, to - f).simplified();
167  return cut;
168  }
169 
170  QString CFlightPlanRemarks::replaceRemark(const QString &remarks, const QString &marker, const QString &newRemark)
171  {
172  QString r(remarks);
173  const int maxIndex = remarks.size() - 1;
174  int f = remarks.indexOf(marker);
175  if (f >= 0)
176  {
177  f += marker.length();
178  if (maxIndex <= f) { return remarks; }
179  thread_local const QRegularExpression nextMarker("\\s+\\w*/|$");
180  int to1 = remarks.indexOf(nextMarker, f + 1); // for case 2,3
181  if (to1 < 0) { to1 = maxIndex + 1; }
182  int to2 = remarks.indexOf('/', f + 1); // for case 1
183  if (to2 < 0) { to2 = maxIndex + 1; } // no more end markers, ends after last character
184  const int to = qMin(to1, to2);
185  r.remove(f, to - f);
186  }
187  return r.isEmpty() ? newRemark : r % u" " % newRemark;
188  }
189 
191  {
192  static const QStringList cats { CLogCategories::flightPlan() };
193  return cats;
194  }
195 
196  CFlightPlan::CFlightPlan(const CCallsign &callsign, const CFlightPlanAircraftInfo &aircraftInfo,
197  const CAirportIcaoCode &originAirportIcao, const CAirportIcaoCode &destinationAirportIcao,
198  const CAirportIcaoCode &alternateAirportIcao, const QDateTime &takeoffTimePlanned,
199  const QDateTime &takeoffTimeActual, const physical_quantities::CTime &enrouteTime,
200  const physical_quantities::CTime &fuelTime, const CAltitude &cruiseAltitude,
201  const physical_quantities::CSpeed &cruiseTrueAirspeed,
202  CFlightPlan::FlightRules flightRules, const QString &route, const QString &remarks)
203  : m_callsign(callsign), m_aircraftInfo(aircraftInfo), m_originAirportIcao(originAirportIcao),
204  m_destinationAirportIcao(destinationAirportIcao), m_alternateAirportIcao(alternateAirportIcao),
205  m_takeoffTimePlanned(takeoffTimePlanned), m_takeoffTimeActual(takeoffTimeActual), m_enrouteTime(enrouteTime),
206  m_fuelTime(fuelTime), m_cruiseAltitude(cruiseAltitude), m_cruiseTrueAirspeed(cruiseTrueAirspeed),
207  m_flightRules(flightRules), m_route(route.trimmed().left(MaxRouteLength).toUpper()),
208  m_remarks(remarks.trimmed().left(MaxRemarksLength).toUpper())
209  {
210  m_callsign.setTypeHint(CCallsign::Aircraft);
213  }
214 
215  void CFlightPlan::setCallsign(const CCallsign &callsign)
216  {
217  m_callsign = callsign;
218  m_callsign.setTypeHint(CCallsign::Aircraft);
219  }
220 
221  void CFlightPlan::setAircraftInfo(const CFlightPlanAircraftInfo &aircraftInfo) { m_aircraftInfo = aircraftInfo; }
222 
223  void CFlightPlan::setTakeoffTimePlanned(const QDateTime &takeoffTimePlanned)
224  {
225  m_takeoffTimePlanned = takeoffTimePlanned.toUTC();
226  }
227 
228  void CFlightPlan::setTakeoffTimeActual(const QDateTime &takeoffTimeActual)
229  {
230  m_takeoffTimeActual = takeoffTimeActual.toUTC();
231  }
232 
233  void CFlightPlan::setFlightRule(const QString &flightRule)
234  {
236  this->setFlightRule(r);
237  }
238 
239  void CFlightPlan::setRoute(const QString &route)
240  {
241  QString r = route;
242  r.replace(':', ' ');
243  m_route = asciiOnlyString(r).left(MaxRouteLength).toUpper();
244  }
245 
246  void CFlightPlan::setRemarks(const QString &remarks) { m_remarks = CFlightPlanRemarks(remarks, true); }
247 
248  void CFlightPlan::setVoiceCapabilities(const QString &capabilities)
249  {
250  const CVoiceCapabilities vc = CVoiceCapabilities::fromText(capabilities);
251  m_remarks.setVoiceCapabilities(vc);
252  }
253 
254  QString CFlightPlan::getTakeoffTimePlannedHourMin() const { return m_takeoffTimePlanned.toString("hh:mm"); }
255 
256  QString CFlightPlan::getTakeoffTimeActualHourMin() const { return m_takeoffTimeActual.toString("hh:mm"); }
257 
259  {
260  if (index.isMyself()) { return QVariant::fromValue(*this); }
262 
263  const auto i = index.frontCasted<ColumnIndex>();
264  switch (i)
265  {
266  case IndexAlternateAirportIcao: return m_alternateAirportIcao.propertyByIndex(index.copyFrontRemoved());
267  case IndexDestinationAirportIcao: return m_destinationAirportIcao.propertyByIndex(index.copyFrontRemoved());
268  case IndexOriginAirportIcao: return m_originAirportIcao.propertyByIndex(index.copyFrontRemoved());
269  case IndexCallsign: return m_callsign.propertyByIndex(index.copyFrontRemoved());
270  case IndexRemarks: return QVariant::fromValue(m_remarks);
271  default: return CValueObject::propertyByIndex(index);
272  }
273  }
274 
276  {
277  if (index.isMyself())
278  {
279  (*this) = variant.value<CFlightPlan>();
280  return;
281  }
283  {
284  ITimestampBased::setPropertyByIndex(index, variant);
285  return;
286  }
287 
288  const auto i = index.frontCasted<ColumnIndex>();
289  switch (i)
290  {
291  case IndexAlternateAirportIcao:
292  m_alternateAirportIcao.setPropertyByIndex(index.copyFrontRemoved(), variant);
293  break;
294  case IndexDestinationAirportIcao:
295  m_destinationAirportIcao.setPropertyByIndex(index.copyFrontRemoved(), variant);
296  break;
297  case IndexOriginAirportIcao: m_originAirportIcao.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
298  case IndexCallsign: m_callsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
299  case IndexRemarks: this->setRemarks(variant.toString()); break;
300  default: CValueObject::setPropertyByIndex(index, variant); break;
301  }
302  }
303 
304  QString CFlightPlan::convertToQString(bool i18n) const { return this->buildString(i18n, " "); }
305 
306  QString CFlightPlan::asHTML(bool i18n) const { return this->buildString(i18n, "<br>"); }
307 
308  QString CFlightPlan::buildString(bool i18n, const QString &separator) const
309  {
310  const QString s =
311  m_callsign.toQString(i18n) % u" aircraft: " % m_aircraftInfo.asIcaoString() % separator % u"origin: " %
312  m_originAirportIcao.toQString(i18n) % u" destination: " % m_destinationAirportIcao.toQString(i18n) %
313  u" alternate: " % m_alternateAirportIcao.toQString(i18n) % separator % u"takeoff planed: " %
314  m_takeoffTimePlanned.toString("ddhhmm") % u" actual: " % m_takeoffTimeActual.toString("ddhhmm") %
315  separator % u"enroute time: " % m_enrouteTime.toQString(i18n) % u" fuel time:" %
316  m_fuelTime.toQString(i18n) % separator % u"altitude: " % m_cruiseAltitude.toQString(i18n) % u" speed: " %
317  m_cruiseTrueAirspeed.toQString(i18n) % separator % u"route: " % m_route % separator % u"remarks: " %
318  this->getRemarks();
319  return s;
320  }
321 
323  {
324  if (vPilotData.isEmpty()) { return {}; }
325  QDomDocument doc;
326  doc.setContent(vPilotData);
327  const QDomElement fpDom = doc.firstChildElement();
328  const QString type = fpDom.attribute("FlightType");
329 
330  CFlightPlan fp;
332 
333  const int airspeedKts = fpDom.attribute("CruiseSpeed").toInt();
334  const CSpeed airspeed(airspeedKts, CSpeedUnit::kts());
335  fp.setCruiseTrueAirspeed(airspeed);
336 
337  fp.setOriginAirportIcao(fpDom.attribute("DepartureAirport"));
338  fp.setDestinationAirportIcao(fpDom.attribute("DestinationAirport"));
339  fp.setAlternateAirportIcao(fpDom.attribute("AlternateAirport"));
340  fp.setRemarks(fpDom.attribute("Remarks"));
341  fp.setRoute(fpDom.attribute("Route"));
342 
343  QString voice = fpDom.attribute("VoiceType");
344  if (voice.toLower() == "full")
345  {
346  // Default string in LNM exporter
347  voice = "VOICE";
348  }
349  fp.setVoiceCapabilities(voice);
350 
351  // Ignoring equipment prefix, suffix and IsHeavy flag
352 
353  const int fuelMins = fpDom.attribute("FuelMinutes").toInt() + 60 * fpDom.attribute("FuelHours").toInt();
354  const CTime fuelTime(fuelMins, CTimeUnit::min());
355 
356  const int enrouteMins =
357  fpDom.attribute("EnrouteMinutes").toInt() + 60 * fpDom.attribute("EnrouteHours").toInt();
358  const CTime enrouteTime(enrouteMins, CTimeUnit::min());
359 
360  const QString altStr = fpDom.attribute("CruiseAltitude");
361  CAltitude alt(altStr.length() < 4 ? "FL" + altStr : altStr + "ft");
362  alt.toFlightLevel();
363 
364  fp.setCruiseAltitude(alt);
365  fp.setFuelTime(fuelTime);
366  fp.setEnrouteTime(enrouteTime);
367 
368  const QString departureTime = fpDom.attribute("DepartureTime");
369  if (departureTime.length() == 4)
370  {
371  CTime depTime;
372  if (depTime.parseFromString_hhmm(departureTime)) { fp.setTakeoffTimePlanned(depTime.toQDateTime()); }
373  }
374 
375  return fp;
376  }
377 
379  {
380  if (sbData.isEmpty()) { return {}; }
381  CFlightPlan fp;
382  const QMap<QString, QString> values = parseIniValues(sbData);
383  const QString altStr = values.value("Altitude");
384  const CAltitude alt(altStr.length() < 4 ? "FL" + altStr : altStr + "ft");
385 
386  const QString type = values.value("Type"); // IFR/VFR
387  fp.setFlightRule(type == "0" ? IFR : VFR);
388 
389  const int fuelMins = values.value("FuelMinutes").toInt() + 60 * values.value("FuelHours").toInt();
390  const CTime fuelTime(fuelMins, CTimeUnit::min());
391 
392  const int enrouteMins = values.value("FlightMinutes").toInt() + 60 * values.value("FlightHours").toInt();
393  const CTime enrouteTime(enrouteMins, CTimeUnit::min());
394 
395  fp.setOriginAirportIcao(values.value("Departure"));
396  fp.setDestinationAirportIcao(values.value("Arrival"));
397  fp.setAlternateAirportIcao(values.value("Alternate"));
398  fp.setRemarks(values.value("Remarks"));
399  fp.setRoute(values.value("Route"));
400 
401  fp.setCruiseAltitude(alt);
402  fp.setFuelTime(fuelTime);
403  fp.setEnrouteTime(enrouteTime);
404 
406 
407  int airspeedKts = values.value("Airspeed").toInt();
408  const CSpeed airspeed(airspeedKts, CSpeedUnit::kts());
409  fp.setCruiseTrueAirspeed(airspeed);
410 
411  // Ignoring Heavy flag
412 
413  return fp;
414  }
415 
417  {
418  if (simBrief.isEmpty()) { return {}; }
419  CFlightPlan fp;
420  QDomDocument doc;
421  doc.setContent(simBrief);
422  const QDomNodeList originList = doc.elementsByTagName("origin");
423  if (!originList.isEmpty())
424  {
425  const QDomNode origin = originList.at(0);
426  const QString icao = origin.firstChildElement("icao_code").text();
427  fp.setOriginAirportIcao(icao);
428  }
429  const QDomNodeList destList = doc.elementsByTagName("destination");
430  if (!destList.isEmpty())
431  {
432  const QDomNode dest = destList.at(0);
433  const QString icao = dest.firstChildElement("icao_code").text();
434  fp.setDestinationAirportIcao(icao);
435  }
436  const QDomNodeList altList = doc.elementsByTagName("alternate");
437  if (!altList.isEmpty())
438  {
439  const QDomNode alternate = altList.at(0);
440  const QString icao = alternate.firstChildElement("icao_code").text();
441  fp.setAlternateAirportIcao(icao);
442  }
443  const QDomNodeList generalList = doc.elementsByTagName("general");
444  if (!generalList.isEmpty())
445  {
446  bool ok {};
447  const QDomNode general = generalList.at(0);
448  QString route = general.firstChildElement("route").text();
449  fp.setRoute(route.remove("DCT").simplified().trimmed());
450  const QString airline = general.firstChildElement("icao_airline").text();
451  const QString flightNumber = general.firstChildElement("flight_number").text();
452  fp.setCallsign(CCallsign(airline + flightNumber, CCallsign::Aircraft));
453  const QString cruiseAlt = general.firstChildElement("initial_altitude").text();
454  const int cruiseAltFt = cruiseAlt.toInt(&ok);
455  if (ok)
456  {
457  CAltitude ca(cruiseAltFt, CAltitude::MeanSeaLevel, CLengthUnit::ft());
458  if (cruiseAlt.endsWith("00") && cruiseAltFt > 5000) { ca.toFlightLevel(); }
459  fp.setCruiseAltitude(ca);
460  if (cruiseAltFt >= 10000) { fp.setFlightRule(CFlightPlan::IFR); } // good guess
461  else { fp.setFlightRule(CFlightPlan::VFR); }
462  }
463  else { fp.setCruiseAltitudeString(cruiseAlt); }
464  const QString tas = general.firstChildElement("cruise_tas").text();
465  const int tasKts = tas.toInt(&ok);
466  if (ok) { fp.setCruiseTrueAirspeed(CSpeed(tasKts, CSpeedUnit::kts())); }
467  }
468 
469  const QDomNodeList timeList = doc.elementsByTagName("times");
470  if (!timeList.isEmpty())
471  {
472  bool ok {};
473  const QDomNode times = timeList.at(0);
474  const QString enroute = times.firstChildElement("est_time_enroute").text();
475  const int enrouteSecs = enroute.toInt(&ok);
476  if (ok) { fp.setEnrouteTime(CTime(enrouteSecs, CTimeUnit::s())); }
477  const QString endurance = times.firstChildElement("endurance").text();
478  const int enduranceSecs = endurance.toInt(&ok);
479  if (ok) { fp.setFuelTime(CTime(enduranceSecs, CTimeUnit::s())); }
480  const QString depTime = times.firstChildElement("sched_out").text();
481  const int depTimeUnixTs = depTime.toInt(&ok);
482  if (ok)
483  {
484  QDateTime depTs = QDateTime::fromSecsSinceEpoch(depTimeUnixTs, QTimeZone::utc());
485  depTs.setTimeZone(QTimeZone::utc());
486  fp.setTakeoffTimePlanned(depTs);
487  }
488  }
489 
490  const QDomNodeList aircraftList = doc.elementsByTagName("aircraft");
491  if (!aircraftList.isEmpty())
492  {
493  const QDomNode aircraft = aircraftList.at(0);
494  const QString equipment = aircraft.firstChildElement("equip").text();
495  // H-SDE2E3GHIJ1J3J4J5LM1ORWXY/LB1D1
496  const int b = equipment.indexOf('-');
497  const int e = equipment.indexOf('/');
498 
499  if (e > b && e >= 0 && b >= 0 && equipment.size() > e)
500  {
501  // Do not read aircraft ICAO code as this is read/overwritten from the simulator
502 
503  const QChar wtcChar = equipment.mid(0, 1).at(0);
504  const CWakeTurbulenceCategory wtc(wtcChar);
505 
506  const QString comNavEquipmentString = equipment.mid(b + 1, e - b - 1);
507  const CComNavEquipment comNavEquipment(comNavEquipmentString);
508 
509  const QString ssrEquipmentString = equipment.mid(e + 1);
510  const CSsrEquipment ssrEquipment(ssrEquipmentString);
511 
512  const CFlightPlanAircraftInfo info(fp.getAircraftInfo().getAircraftIcao(), comNavEquipment,
513  ssrEquipment, wtc);
514  fp.setAircraftInfo(info);
515  }
516 
518  bool remarksChanged = false;
519  const QString selcal = aircraft.firstChildElement("selcal").text();
520  if (selcal.length() == 4)
521  {
522  const bool c = r.setSelcalCode(selcal);
523  remarksChanged = c || remarksChanged;
524  }
525 
526  if (remarksChanged) { fp.setFlightPlanRemarks(r); }
527  }
528 
529  return fp;
530  }
531 
533  {
534  if (data.isEmpty()) { return {}; }
535  if (fileSuffix.contains("xml", Qt::CaseInsensitive))
536  {
537  if (data.contains("<OFP>", Qt::CaseInsensitive) && data.contains("<general>", Qt::CaseInsensitive))
538  {
539  return CFlightPlan::fromSimBriefFormat(data);
540  }
541  }
542 
543  if (data.contains("[SBFlightPlan]", Qt::CaseInsensitive)) { return CFlightPlan::fromSB4Format(data); }
544  if (data.contains("<FlightPlan", Qt::CaseInsensitive) && data.contains("<?xml", Qt::CaseInsensitive))
545  {
546  return CFlightPlan::fromVPilotFormat(data);
547  }
548  return CFlightPlan::fromJson(data);
549  }
550 
552  {
553  try
554  {
555  QFileInfo fi(fileName);
556  if (fileName.isEmpty())
557  {
558  if (msgs)
559  {
560  msgs->push_back(
561  CStatusMessage(static_cast<CFlightPlan *>(nullptr)).validationError(u"No file name"));
562  }
563  return {};
564  }
565  else
566  {
567  if (!fi.exists())
568  {
569  if (msgs)
570  {
571  msgs->push_back(CStatusMessage(static_cast<CFlightPlan *>(nullptr))
572  .validationError(u"File '%1' does not exist")
573  << fileName);
574  }
575  return {};
576  }
577  }
578 
579  const QString data = CFileUtils::readFileToString(fileName);
580  if (data.isEmpty())
581  {
582  if (msgs)
583  {
584  msgs->push_back(CStatusMessage(static_cast<CFlightPlan *>(nullptr))
585  .validationError(u"File '%1' does not contain data")
586  << fileName);
587  }
588  return {};
589  }
590 
591  if (fileName.endsWith(".sfp", Qt::CaseInsensitive)) { return CFlightPlan::fromSB4Format(data); }
592  if (fileName.endsWith(".vfp", Qt::CaseInsensitive)) { return CFlightPlan::fromVPilotFormat(data); }
593  if (fileName.endsWith(".json", Qt::CaseInsensitive))
594  {
595  do {
596  CStatusMessage m;
597  if (!json::looksLikeSwiftJson(data))
598  {
599  m = CStatusMessage(static_cast<CFlightPlan *>(nullptr), CStatusMessage::SeverityWarning,
600  u"Reading '%1' yields no data", true)
601  << fileName;
602  if (msgs) { msgs->push_back(m); }
603  break;
604  }
605 
606  try
607  {
608  const QJsonObject jsonObject = json::jsonObjectFromString(data);
609  if (json::looksLikeSwiftTypeValuePairJson(jsonObject))
610  {
611  // CVariant format
612  CVariant variant;
613  variant.convertFromJson(jsonObject);
614  if (variant.canConvert<CFlightPlan>())
615  {
616  const auto fp = variant.value<CFlightPlan>();
617  return fp;
618  }
619  else
620  {
621  m = CStatusMessage(static_cast<CFlightPlan *>(nullptr), CStatusMessage::SeverityWarning,
622  u"Wrong format for flight plan in '%1'")
623  << fileName;
624  if (msgs) { msgs->push_back(m); }
625  }
626  }
627  else
628  {
629  const CFlightPlan fp = CFlightPlan::fromJson(jsonObject);
630  return fp;
631  }
632  }
633  catch (const CJsonException &ex)
634  {
635  m = CStatusMessage::fromJsonException(ex, static_cast<CFlightPlan *>(nullptr),
636  "Parse error in " + fileName);
637  if (msgs) { msgs->push_back(m); }
638  break;
639  }
640  }
641  while (false);
642  }
643 
644  return CFlightPlan::fromMultipleFormats(data, fi.suffix());
645  }
646  catch (const CJsonException &ex)
647  {
648  if (msgs)
649  {
651  ex, static_cast<CFlightPlan *>(nullptr),
652  QStringLiteral("Parsing flight plan from '%1' failed.").arg(fileName)));
653  }
654  }
655  return {};
656  }
657 
659  {
660  static const QString v("VFR");
661  static const QString i("IFR");
662  static const QString s("SVFR");
663  static const QString d("DVFR");
664  static const QString unknown("???");
665 
666  switch (rules)
667  {
668  case VFR: return v;
669  case IFR: return i;
670  case SVFR: return s;
671  case DVFR: return d;
672  case UNKNOWN:
673  default: break;
674  }
675  return unknown;
676  }
677 
679  {
680  if (flightRules.length() < 3) { return UNKNOWN; }
681  const QString fr(flightRules.toUpper().trimmed());
682  if (fr.startsWith("DVFR")) { return DVFR; }
683  if (fr.startsWith("SVFR")) { return SVFR; }
684  if (fr.startsWith("VFR")) { return VFR; }
685  if (fr.startsWith("IFR")) { return IFR; }
686  return UNKNOWN;
687  }
688 
690  {
691  static const QStringList r({ "IFR", "VFR", "SVFR", "DVFR" });
692  return r;
693  }
694 
696  {
697  return rule == CFlightPlan::VFR || rule == CFlightPlan::DVFR || rule == CFlightPlan::SVFR;
698  }
699 
701  {
703  return CFlightPlan::isVFRRules(r);
704  }
705 
707 
709  {
711  return CFlightPlan::isIFRRules(r);
712  }
713 
714  CIcons::IconIndex CFlightPlan::toIcon() const { return CIcons::StandardIconAppFlightPlan16; }
715 
716 } // namespace swift::misc::aviation
static QString readFileToString(const QString &fileNameAndPath)
Read file into string.
Definition: fileutils.cpp:68
IconIndex
Index for each icon, allows to send them via DBus, efficiently store them, etc.
Definition: icons.h:32
Thrown when a convertFromJson method encounters an unrecoverable error in JSON data.
Definition: jsonexception.h:24
static const QString & flightPlan()
Flight plan.
Definition: logcategories.h:80
Non-owning reference to a CPropertyIndex with a subset of its features.
Q_REQUIRED_RESULT CPropertyIndexRef copyFrontRemoved() const
Copy with first element removed.
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
bool isMyself() const
Myself index, used with nesting.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Streamable status message, e.g.
static CStatusMessage fromJsonException(const CJsonException &ex, const CLogCategoryList &categories, const QString &prefix)
Object from JSON exception message.
constexpr static auto SeverityWarning
Status severities.
Status messages, e.g. from Core -> GUI.
Wrapper around QVariant which provides transparent access to CValueObject methods of the contained ob...
Definition: variant.h:66
T value() const
Return the value converted to the type T.
Definition: variant.h:166
void convertFromJson(const QJsonObject &json)
Assign from JSON object.
bool canConvert(int typeId) const
True if this variant can be converted to the type with the given metatype ID.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
static bool canHandleIndex(CPropertyIndexRef index)
Can given index be handled.
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Value object for ICAO classification.
static bool isValidAirlineDesignator(const QString &airline)
Valid designator?
const QString & getDesignator() const
Get airline, e.g. "DLH".
bool hasValidDesignator() const
Airline designator available?
Value object encapsulating information of airport ICAO data.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
bool toFlightLevel()
MSL to flightlevel.
Definition: altitude.cpp:105
Value object encapsulating information of a callsign.
Definition: callsign.h:30
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: callsign.cpp:310
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: callsign.cpp:324
static QString unifyCallsign(const QString &callsign, TypeHint hint=NoHint)
Unify the callsign by removing illegal characters.
Definition: callsign.cpp:67
bool isEmpty() const
Is empty?
Definition: callsign.h:63
static bool isValidAircraftCallsign(const QString &callsign)
Valid callsign?
Definition: callsign.cpp:369
void setTypeHint(TypeHint hint)
Type hint.
Definition: callsign.h:114
Flightplan-related information about an aircraft (aircraft ICAO, equipment and WTC)
QString asIcaoString() const
Full string in ICAO format: "AIRCRAFT_ICAO/WTC-EQUIPMENT/SSR".
CAircraftIcaoCode getAircraftIcao() const
Get Aircraft ICAO.
Value object for a flight plan.
Definition: flightplan.h:148
QString asHTML(bool i18n=false) const
As HTML.
Definition: flightplan.cpp:306
static bool isVFRRules(FlightRules rule)
Is rule a VFR rule?
Definition: flightplan.cpp:695
void setAircraftInfo(const CFlightPlanAircraftInfo &aircraftInfo)
Set information about the aircraft used in this flightplan.
Definition: flightplan.cpp:221
CIcons::IconIndex toIcon() const
As icon, not implemented by all classes.
Definition: flightplan.cpp:714
void setFlightPlanRemarks(const CFlightPlanRemarks &remarks)
Set FP remarks.
Definition: flightplan.h:381
CFlightPlan()=default
Default constructor.
void setFlightRule(FlightRules flightRule)
Set flight rules (VFR or IFR)
Definition: flightplan.h:285
static FlightRules stringToFlightRules(const QString &flightRules)
String to flight rules.
Definition: flightplan.cpp:678
QString getTakeoffTimePlannedHourMin() const
Get planned takeoff time (planned)
Definition: flightplan.cpp:254
const CFlightPlanRemarks & getFlightPlanRemarks() const
Get the parsable remarks.
Definition: flightplan.h:378
ColumnIndex
Properties by index.
Definition: flightplan.h:165
void setVoiceCapabilities(const QString &capabilities)
Set voice capabilities.
Definition: flightplan.cpp:248
void setOriginAirportIcao(const QString &originAirportIcao)
Set origin airport ICAO code.
Definition: flightplan.h:199
void setTakeoffTimePlanned(const QDateTime &takeoffTimePlanned)
Set planned takeoff time.
Definition: flightplan.cpp:223
void setCruiseTrueAirspeed(const physical_quantities::CSpeed &cruiseTrueAirspeed)
Set planned cruise TAS.
Definition: flightplan.h:279
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: flightplan.cpp:258
void setAlternateAirportIcao(const QString &alternateAirportIcao)
Set alternate destination airport ICAO code.
Definition: flightplan.h:220
void setFuelTime(const physical_quantities::CTime &fuelTime)
Set amount of fuel load in time.
Definition: flightplan.h:259
static const QStringList & flightRules()
All rules as string.
Definition: flightplan.cpp:689
static CFlightPlan fromSimBriefFormat(const QString &simBrief)
From SimBrief format (XML)
Definition: flightplan.cpp:416
FlightRules
Flight rules (VFR or IFR)
Definition: flightplan.h:155
@ IFR
Instrument flight rules.
Definition: flightplan.h:157
@ VFR
Visual flight rules.
Definition: flightplan.h:156
@ SVFR
Special VFR (reserved for ATC use),.
Definition: flightplan.h:158
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: flightplan.cpp:275
static CFlightPlan fromMultipleFormats(const QString &data, const QString &fileSuffix)
From multiple formats.
Definition: flightplan.cpp:532
static const QString & flightRulesToString(FlightRules rules)
Rules to string.
Definition: flightplan.cpp:658
void setTakeoffTimeActual(const QDateTime &takeoffTimeActual)
Set actual takeoff time (reserved for ATC use)
Definition: flightplan.cpp:228
const QString & getRemarks() const
Get remarks string.
Definition: flightplan.h:375
void setCruiseAltitudeString(const QString &altitudeString)
Cruising altitude already as string.
Definition: flightplan.h:276
QString getTakeoffTimeActualHourMin() const
Get actual takeoff time (actual)
Definition: flightplan.cpp:256
void setRemarks(const QString &remarks)
Set remarks string (max 100 characters)
Definition: flightplan.cpp:246
static const QStringList & getLogCategories()
The log. catgeories.
Definition: flightplan.cpp:190
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition: flightplan.cpp:304
void setEnrouteTime(const physical_quantities::CTime &enrouteTime)
Set planned enroute flight time.
Definition: flightplan.h:252
static CFlightPlan fromSB4Format(const QString &sbData)
From SB4 data.
Definition: flightplan.cpp:378
void setRoute(const QString &route)
Set route string.
Definition: flightplan.cpp:239
static CFlightPlan fromVPilotFormat(const QString &vPilotData)
From vPilot data.
Definition: flightplan.cpp:322
static bool isIFRRules(FlightRules rule)
Is rule a IFR rule?
Definition: flightplan.cpp:706
static constexpr int MaxRouteLength
Max.route length.
Definition: flightplan.h:177
void setDestinationAirportIcao(const QString &destinationAirportIcao)
Set destination airport ICAO code.
Definition: flightplan.h:208
void setCruiseAltitude(const CAltitude &cruiseAltitude)
Set planned cruise altitude.
Definition: flightplan.h:269
CFlightPlanAircraftInfo getAircraftInfo() const
Get ICAO aircraft NAV/COM equipment.
Definition: flightplan.h:384
static CFlightPlan loadFromMultipleFormats(const QString &fileName, CStatusMessageList *msgs=nullptr)
Load from multiple formats.
Definition: flightplan.cpp:551
void setCallsign(const CCallsign &callsign)
Callsign (of aircraft)
Definition: flightplan.cpp:215
Flight plan remarks, parsed values.
Definition: flightplan.h:46
bool hasValidAirlineIcao() const
Valid airline ICAO?
Definition: flightplan.h:98
bool setSelcalCode(const QString &selcal)
SELCAL code.
Definition: flightplan.cpp:42
static QString textToVoiceCapabilitiesRemarks(const QString &text)
Turn text into voice capabilities for remarks.
Definition: flightplan.cpp:82
bool hasAnyParsedRemarks() const
Any remarks available?
Definition: flightplan.cpp:59
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition: flightplan.cpp:71
static QString cleanRemarks(const QString &remarksIn)
Clean up remarks string.
Definition: flightplan.cpp:112
bool hasParsedAirlineRemarks() const
Airline remarks.
Definition: flightplan.cpp:65
static QString replaceVoiceCapabilities(const QString &newCapRemarks, const QString &oldRemarks)
Replace the voice capabilities remarks part.
Definition: flightplan.cpp:88
void setVoiceCapabilities(const network::CVoiceCapabilities &capabilities)
Set voice capabilities.
Definition: flightplan.cpp:51
void parseFlightPlanRemarks(bool force=false)
Parse remarks from a flight plan.
Definition: flightplan.cpp:120
Value object for SELCAL.
Definition: selcal.h:31
bool isValid() const
Is valid?
Definition: selcal.h:43
const QString & getCode() const
Get SELCAL code.
Definition: selcal.h:46
ICAO flightplan field 10b.
Definition: ssrequipment.h:16
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: mixinindex.h:158
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: mixinindex.h:165
static DerivedObj fromJson(const QJsonObject &json)
Get object from QJsonObject.
Definition: mixinjson.h:190
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:74
Value object encapsulating information for voice capabilities.
bool isUnknown() const
Is capability known.
const QString & toFlightPlanRemarks() const
To flight plan remarks.
PQ & switchUnit(const MU &newUnit)
Change unit, and convert value to maintain the same quantity.
QDateTime toQDateTime() const
To Qt date time.
Definition: time.cpp:139
bool parseFromString_hhmm(const QString &hhmm)
From string like 2211 (hhmm)
Definition: time.cpp:66
static CTimeUnit hrmin()
Hours, minutes.
Definition: units.h:1007
QJsonObject jsonObjectFromString(const QString &json, bool acceptCacheFormat)
JSON Object from string.
Definition: json.cpp:413
SWIFT_MISC_EXPORT QMap< QString, QString > parseIniValues(const QString &data)
Obtain ini file like values, e.g. foo=bar.
SWIFT_MISC_EXPORT QString simplifyAccents(const QString &candidate)
Remove accents / diacritic marks from a string.
QString asciiOnlyString(const QString &string)
String only with ASCII values.
Definition: stringutils.h:203
QDateTime currentDateTimeUtc()
QDateTime fromSecsSinceEpoch(qint64 secs)
void setTimeZone(const QTimeZone &toZone, QDateTime::TransitionResolution resolve)
QString toString(QStringView format) const const
QDateTime toUTC() const const
QDomNodeList elementsByTagName(const QString &tagname) const const
QDomDocument::ParseResult setContent(QAnyStringView text, QDomDocument::ParseOptions options)
QString attribute(const QString &name, const QString &defValue) const const
QString text() const const
QDomElement firstChildElement(const QString &tagName, const QString &namespaceURI) const const
QDomNode at(int index) const const
bool isEmpty() const const
bool exists(const QString &path)
QString suffix() const const
qsizetype length() const const
T value(const Key &key, const T &defaultValue) const const
const QChar at(qsizetype position) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) &&
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) &&
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString simplified() const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString toLower() const const
QString toUpper() const const
QString trimmed() const const
CaseInsensitive
QTimeZone utc()
QVariant fromValue(T &&value)
QString toString() const const
T value() const &const
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67