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