swift
aircrafticaocode.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QChar>
7 #include <QMultiMap>
8 #include <QRegularExpression>
9 #include <QStringBuilder>
10 #include <Qt>
11 #include <QtGlobal>
12 
13 #include "misc/comparefunctions.h"
15 #include "misc/logcategories.h"
16 #include "misc/pq/speed.h"
17 #include "misc/propertyindexref.h"
18 #include "misc/statusmessage.h"
19 #include "misc/stringutils.h"
20 
21 using namespace swift::misc;
22 using namespace swift::misc::db;
23 using namespace swift::misc::physical_quantities;
24 
25 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::aviation, CAircraftIcaoCode)
26 
27 namespace swift::misc::aviation
28 {
29  CAircraftIcaoCode::CAircraftIcaoCode(const QString &icao, const QString &combinedType)
30  : m_designator(icao.trimmed().toUpper()), m_combinedType(combinedType.trimmed().toUpper())
31  {}
32 
33  CAircraftIcaoCode::CAircraftIcaoCode(const QString &icao, const QString &combinedType, const QString &manufacturer,
34  const QString &model, CWakeTurbulenceCategory wtc, bool realworld, bool legacy,
35  bool military, int rank)
36  : m_designator(icao.trimmed().toUpper()), m_combinedType(combinedType.trimmed().toUpper()),
37  m_manufacturer(manufacturer.trimmed()), m_modelDescription(model.trimmed()), m_wtc(wtc),
38  m_realWorld(realworld), m_legacy(legacy), m_military(military), m_rank(rank)
39  {
40  if (m_rank < 0 || m_rank >= 10) { m_rank = 10; }
41  }
42 
43  CAircraftIcaoCode::CAircraftIcaoCode(const QString &icao, const QString &iata, const QString &combinedType,
44  const QString &manufacturer, const QString &model, CWakeTurbulenceCategory wtc,
45  bool realworld, bool legacy, bool military, int rank)
46  : m_designator(icao.trimmed().toUpper()), m_iataCode(iata.trimmed().toUpper()),
47  m_combinedType(combinedType.trimmed().toUpper()), m_manufacturer(manufacturer.trimmed()),
48  m_modelDescription(model.trimmed()), m_wtc(wtc), m_realWorld(realworld), m_legacy(legacy),
49  m_military(military), m_rank(rank)
50  {
51  if (m_rank < 0 || m_rank >= 10) { m_rank = 10; }
52  }
53 
54  CAircraftIcaoCode::CAircraftIcaoCode(const QString &icao, const QString &iata, const QString &family,
55  const QString &combinedType, const QString &manufacturer, const QString &model,
56  const QString &modelIata, const QString &modelSwift,
57  CWakeTurbulenceCategory wtc, bool realworld, bool legacy, bool military,
58  int rank)
59  : m_designator(icao.trimmed().toUpper()), m_iataCode(iata.trimmed().toUpper()),
60  m_family(family.trimmed().toUpper()), m_combinedType(combinedType.trimmed().toUpper()),
61  m_manufacturer(manufacturer.trimmed()), m_modelDescription(model.trimmed()),
62  m_modelIataDescription(modelIata.trimmed()), m_modelSwiftDescription(modelSwift.trimmed()), m_wtc(wtc),
63  m_realWorld(realworld), m_legacy(legacy), m_military(military), m_rank(rank)
64  {
65  if (m_rank < 0 || m_rank >= 10) { m_rank = 10; }
66  }
67 
69  {
70  return (this->isLoadedFromDb()) ? this->getDesignator() % u' ' % this->getDbKeyAsStringInParentheses() :
71  this->getDesignator();
72  }
73 
74  QString CAircraftIcaoCode::convertToQString(bool i18n) const
75  {
76  Q_UNUSED(i18n);
77  return (this->hasCategory()) ?
78  QStringLiteral("%1 %2 %3 cat: %4")
79  .arg(this->getDesignatorDbKey(), this->getCombinedType(), this->getWtc().toQString(),
80  this->getCategory().getDbKeyAsString())
81  .trimmed() :
82  QStringLiteral("%1 %2 %3")
83  .arg(this->getDesignatorDbKey(), this->getCombinedType(), this->getWtc().toQString())
84  .trimmed();
85  }
86 
88  {
89  if (!this->hasValidDesignator() && otherIcaoCode.hasValidDesignator())
90  {
91  this->setDesignator(otherIcaoCode.getDesignator());
92  }
93  if (!this->hasValidWtc() && otherIcaoCode.hasValidWtc()) { this->setWtc(otherIcaoCode.getWtc()); }
94  if (!this->hasValidCombinedType() && otherIcaoCode.hasValidCombinedType())
95  {
96  this->setCombinedType(otherIcaoCode.getCombinedType());
97  }
98  if (m_manufacturer.isEmpty()) { this->setManufacturer(otherIcaoCode.getManufacturer()); }
99  if (m_modelDescription.isEmpty()) { this->setModelDescription(otherIcaoCode.getModelDescription()); }
100  if (m_modelIataDescription.isEmpty())
101  {
102  this->setModelIataDescription(otherIcaoCode.getModelIataDescription());
103  }
104  if (m_modelSwiftDescription.isEmpty())
105  {
106  this->setModelSwiftDescription(otherIcaoCode.getModelSwiftDescription());
107  }
108  if (m_family.isEmpty()) { this->setFamily(otherIcaoCode.getFamily()); }
109  if (!this->hasValidDbKey())
110  {
111  // need to observe if it makes sense to copy the key but not copying the whole object
112  this->setDbKey(otherIcaoCode.getDbKey());
113  this->setUtcTimestamp(otherIcaoCode.getUtcTimestamp());
114  }
115  }
116 
118  {
119  static const CLogCategoryList cats({ CLogCategory("swift.misc.aircrafticao"), CLogCategories::validation() });
120  CStatusMessageList msg;
121  if (this->isLoadedFromDb())
122  {
123  // actually we would expect all DB data to be valid, however right now
124  // we only check special cases
125  if (this->getDesignator() == this->getUnassignedDesignator()) { return msg; } // DB ZZZZ
126  }
127 
128  if (!hasKnownDesignator())
129  {
130  msg.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, u"Aircraft ICAO: unknown designator"));
131  }
132  if (!hasValidCombinedType())
133  {
134  msg.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, u"Aircraft ICAO: invalid combined type"));
135  }
136  if (!hasValidWtc())
137  {
138  msg.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, u"Aircraft ICAO: wrong WTC"));
139  }
140  if (!hasManufacturer())
141  {
142  msg.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, u"Aircraft ICAO: missing manufacturer"));
143  }
144  if (!hasModelDescription())
145  {
146  msg.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, u"Aircraft ICAO: no description"));
147  }
148  return msg;
149  }
150 
152 
154  {
155  if (this->isDbEqual(otherCode))
156  {
157  addLogDetailsToList(log, *this, QString("Equal DB code: 100"));
158  return 100;
159  }
160 
161  int score = 0;
162  if (this->hasValidDesignator() && this->getDesignator() == otherCode.getDesignator())
163  {
164  // 0..65
165  score += 50; // same designator
166  if (log) { addLogDetailsToList(log, *this, QStringLiteral("Same designator: %1").arg(score)); }
167 
168  int scoreOld = score;
169  if (this->getRank() == 0) { score += 15; }
170  else if (this->getRank() == 1) { score += 12; }
171  else if (this->getRank() < 10) { score += (10 - this->getRank()); }
172  if (score > scoreOld) { addLogDetailsToList(log, *this, QStringLiteral("Added rank: %1").arg(score)); }
173  }
174  else
175  {
176  if (this->hasFamily() && this->getFamily() == otherCode.getFamily())
177  {
178  score += 40;
179  addLogDetailsToList(log, *this, QStringLiteral("Added family: %1").arg(score));
180  }
181  else if (this->hasValidCombinedType() && otherCode.getCombinedType() == this->getCombinedType())
182  {
183  score += 30;
184  addLogDetailsToList(log, *this, QStringLiteral("Added combined code: %1").arg(score));
185  }
186  else if (this->hasValidCombinedType())
187  {
188  // totally 15
189 
190  // engine count
191  const int eMy = this->getEnginesCount();
192  const int eOther = otherCode.getEnginesCount();
193 
194  if (eMy == eOther && eMy >= 0) { score += 4; }
195  else if (eMy > 0 && eOther > 0)
196  {
197  const int eDiff = qAbs(eMy - eOther);
198  if (eDiff == 1) { score += 2; }
199  else if (eDiff == 2) { score += 1; }
200  }
201 
202  // engine type
203  const QString tMy = this->getEngineType();
204  const QString tOther = this->getEngineType();
205 
206  if (tMy == tOther) { score += 4; }
207  else if (!tMy.isEmpty() && !tOther.isEmpty())
208  {
209  if (isEPTEngineType(tMy[0]) && isEPTEngineType(tOther[0])) { score += 2; }
210  }
211 
212  // aircraft type
213  if (this->getAircraftType() == otherCode.getAircraftType()) { score += 7; }
214  else if (this->isVtol() && otherCode.isVtol()) { score += 4; }
215  addLogDetailsToList(log, *this, QStringLiteral("Added combined code parts: %1").arg(score));
216  }
217  }
218 
219  // 0..65 so far
220  if (this->hasManufacturer() && otherCode.hasManufacturer())
221  {
222  if (this->matchesManufacturer(otherCode.getManufacturer()))
223  {
224  score += 10;
226  log, *this,
227  QStringLiteral("Matches manufacturer '%1': %2").arg(this->getManufacturer()).arg(score));
228  }
229  else if (this->getManufacturer().contains(otherCode.getManufacturer(), Qt::CaseInsensitive))
230  {
232  log, *this,
233  QStringLiteral("Contains manufacturer '%1': %2").arg(this->getManufacturer()).arg(score));
234  score += 5;
235  }
236  }
237 
238  // 0..75 so far
239  if (this->hasCategory() && otherCode.hasCategory() && this->getCategory() == otherCode.getCategory())
240  {
241  score += 8;
243  log, *this,
244  QStringLiteral("Matches military flag '%1': %2").arg(boolToYesNo(this->isMilitary())).arg(score));
245  }
246  else if (this->isMilitary() == otherCode.isMilitary())
247  {
248  score += 8;
250  log, *this,
251  QStringLiteral("Matches military flag '%1': %2").arg(boolToYesNo(this->isMilitary())).arg(score));
252  }
253  // 0..85
254  return score;
255  }
256 
257  void CAircraftIcaoCode::guessModelParameters(CLength &guessedCGOut, CSpeed &guessedVRotateOut) const
258  {
259  // we do not override values
260  if (!guessedCGOut.isNull() && !guessedVRotateOut.isNull()) { return; }
261 
262  // init to defaults
263  CLength guessedCG = CLength(1.5, CLengthUnit::m());
264  CSpeed guessedVRotate = CSpeed(70, CSpeedUnit::km_h());
265 
266  const int engines = this->getEnginesCount();
267  const QChar engineType = this->getEngineTypeChar().toUpper();
268  do {
269  if (engines == 1)
270  {
271  if (engineType == 'T')
272  {
273  guessedCG = CLength(2.0, CLengthUnit::m());
274  break;
275  }
276  }
277  else if (engines == 2)
278  {
279  guessedCG = CLength(2.0, CLengthUnit::m());
280  guessedVRotate = CSpeed(100, CSpeedUnit::kts());
281  if (engineType == 'T')
282  {
283  guessedCG = CLength(2.0, CLengthUnit::m());
284  break;
285  }
286  if (engineType == 'J')
287  {
288  // a B737 has VR 105-160kts
289  guessedVRotate = CSpeed(120, CSpeedUnit::kts());
290  guessedCG = CLength(2.5, CLengthUnit::m());
291  break;
292  }
293  }
294  else if (engines > 2)
295  {
296  guessedCG = CLength(4.0, CLengthUnit::m());
297  guessedVRotate = CSpeed(70, CSpeedUnit::kts());
298  if (engineType == 'J')
299  {
300  // A typical B747 has VR around 160kts
301  guessedCG = CLength(6.0, CLengthUnit::m());
302  guessedVRotate = CSpeed(140, CSpeedUnit::kts());
303  break;
304  }
305  }
306 
307  if (engineType == 'J')
308  {
309  // MIL Jets a bit faster
310  if (this->isMilitary()) { guessedVRotate *= 1.20; }
311  else if (this->matchesDesignator("CONC")) { guessedVRotate = CSpeed(199, CSpeedUnit::kts()); }
312  }
313 
314  // VTOL
315  if (this->isVtol()) { guessedVRotate = CSpeed(0, CSpeedUnit::kts()); }
316  }
317  while (false);
318 
319  if (guessedCGOut.isNull()) { guessedCGOut = guessedCG; }
320  if (guessedVRotateOut.isNull()) { guessedVRotateOut = guessedVRotate; }
321  }
322 
324  {
325  return m_designator.isEmpty() && m_manufacturer.isEmpty() && m_modelDescription.isEmpty();
326  }
327 
329  {
330  static const CAircraftIcaoCode null;
331  return null;
332  }
333 
334  bool CAircraftIcaoCode::hasDesignator() const { return !m_designator.isEmpty(); }
335 
336  bool CAircraftIcaoCode::hasValidDesignator() const { return isValidDesignator(m_designator); }
337 
339  {
340  return (this->hasValidDesignator() && this->getDesignator() != getUnassignedDesignator());
341  }
342 
344  {
345  if (!this->hasDesignator()) { return false; }
346  return getSpecialDesignators().contains(this->getDesignator());
347  }
348 
350  {
351  return hasDesignator() && hasIataCode() && m_iataCode == m_designator;
352  }
353 
355  {
356  return hasFamily() && hasDesignator() && m_designator == m_family;
357  }
358 
360 
362  {
363  if (m_combinedType.length() != 3) return {};
364  return m_combinedType.right(1);
365  }
366 
368  {
369  const QString et = this->getEngineType();
370  if (et.length() == 1) { return et[0]; }
371  return QChar();
372  }
373 
375  {
376  if (m_combinedType.length() < 2) { return -1; }
377  const QString c(m_combinedType.mid(1, 1));
378  if (c == "-") { return -1; }
379  bool ok;
380  int ec = c.toInt(&ok);
381  if (ok && ec >= 0 && ec < 10) { return ec; }
382  return -1;
383  }
384 
386  {
387  if (m_combinedType.length() < 2) { return {}; }
388  return m_combinedType.mid(1, 1);
389  }
390 
392  {
393  if (m_combinedType.length() < 1) { return {}; }
394  QString c(m_combinedType.at(0));
395  if (c == "-") { return {}; }
396  return c;
397  }
398 
400  {
401  if (m_combinedType.length() < 1) { return {}; }
402  QChar c(m_combinedType.at(0));
403  if (c == '-') { return {}; }
404  return c;
405  }
406 
408  {
409  // Shortcut for most cases
410  if (!this->hasModelIataDescription() && !this->hasModelSwiftDescription())
411  {
412  return this->getModelDescription();
413  }
414 
415  QStringList combined({ this->getModelDescription() });
416  if (this->hasModelIataDescription()) { combined.append(this->getModelIataDescription()); }
417  if (this->hasModelSwiftDescription()) { combined.append(this->getModelSwiftDescription()); }
418  combined.removeDuplicates();
419  return combined.join(", ");
420  }
421 
422  bool CAircraftIcaoCode::matchesAnyDescription(const QString &candidate) const
423  {
424  if (this->hasModelDescription())
425  {
426  if (this->getModelDescription().contains(candidate, Qt::CaseInsensitive)) { return true; }
427  }
428  if (this->hasModelIataDescription())
429  {
430  if (this->getModelIataDescription().contains(candidate, Qt::CaseInsensitive)) { return true; }
431  }
432  if (this->hasModelSwiftDescription())
433  {
434  if (this->getModelSwiftDescription().contains(candidate, Qt::CaseInsensitive)) { return true; }
435  }
436  return false;
437  }
438 
439  bool CAircraftIcaoCode::matchesCombinedType(const QString &combinedType) const
440  {
441  const QString cc(combinedType.toUpper().trimmed().replace(' ', '*').replace('-', '*'));
442  if (combinedType.length() != 3) { return false; }
443  if (cc == this->getCombinedType()) { return true; }
444 
445  const bool wildcard = cc.contains('*');
446  if (!wildcard) { return false; }
447  const QChar at = cc.at(0);
448  const QChar c = cc.at(1);
449  const QChar et = cc.at(2);
450  if (at != '*')
451  {
452  const QString cat = getAircraftType();
453  if (cat.isEmpty() || cat.at(0) != at) { return false; }
454  }
455  if (c != '*')
456  {
457  if (getEnginesCount() != c.digitValue()) { return false; }
458  }
459  if (et == '*') { return true; }
460  const QString cet = this->getEngineType();
461  return cet.length() == 1 && cet.at(0) == et;
462  }
463 
465  const QString &manufacturer) const
466  {
467  return this->matchesCombinedType(combinedType) && this->matchesManufacturer(manufacturer);
468  }
469 
471  {
472  return (this->hasDesignator() ? this->getDesignator() : QStringLiteral("????")) %
473  (this->hasManufacturer() ? (u' ' % this->getManufacturer()) : QString());
474  }
475 
476  bool CAircraftIcaoCode::hasManufacturer() const { return !m_manufacturer.isEmpty(); }
477 
478  bool CAircraftIcaoCode::matchesManufacturer(const QString &manufacturer) const
479  {
480  if (manufacturer.isEmpty()) { return false; }
481  return (manufacturer.length() == m_manufacturer.length() &&
482  m_manufacturer.startsWith(manufacturer, Qt::CaseInsensitive));
483  }
484 
486  {
487  // special designators
488  if (m_designator.length() == 4)
489  {
490  if (m_designator == "BALL" || m_designator == "SHIP" || m_designator == "GYRO" || m_designator == "UHEL")
491  {
492  return true;
493  }
494  }
495 
496  if (!m_combinedType.isEmpty())
497  {
498  if (
499  // Ref T654, G is GLIDER at the moment, G as gyrocopyter will be used as in future
500  // m_combinedType.startsWith('G') || // gyrocopter
501  m_combinedType.startsWith('H') || // helicopter
502  m_combinedType.startsWith('T') // tilt wing
503  )
504  {
505  return true;
506  }
507  }
508  return false;
509  }
510 
512  {
513  return m_modelIataDescription.startsWith("duplicate", Qt::CaseInsensitive) ||
514  m_modelSwiftDescription.startsWith("do not", Qt::CaseInsensitive);
515  }
516 
517  void CAircraftIcaoCode::setCodeFlags(bool military, bool legacy, bool realWorld)
518  {
519  m_military = military;
520  m_legacy = legacy;
521  m_realWorld = realWorld;
522  }
523 
524  void CAircraftIcaoCode::setMilitary(bool military) { m_military = military; }
525 
526  void CAircraftIcaoCode::setRealWorld(bool realWorld) { m_realWorld = realWorld; }
527 
528  void CAircraftIcaoCode::setLegacy(bool legacy) { m_legacy = legacy; }
529 
530  QString CAircraftIcaoCode::getRankString() const { return QString::number(getRank()); }
531 
532  void CAircraftIcaoCode::setRank(int rank) { m_rank = (rank < 0 || rank >= 10) ? 10 : rank; }
533 
535  {
536  return (this->hasDesignator() ? this->getDesignator() : QStringLiteral("????")) %
537  (this->hasManufacturer() ? (u' ' % this->getManufacturer()) : QString()) %
538  (this->hasModelDescription() ? (u' ' % this->getModelDescription()) : QString()) %
539  (this->getDbKeyAsStringInParentheses(" "));
540  }
541 
543  {
544  if (!this->hasCategory()) { return this->getCombinedIcaoStringWithKey(); }
545  return (this->hasDesignator() ? this->getDesignator() : QStringLiteral("????")) %
546  (this->hasManufacturer() ? (u' ' % this->getManufacturer()) : QString()) %
547  (u" [" % this->getCategory().getNameDbKey() % ']') %
548  (this->hasModelDescription() ? (u' ' % this->getModelDescription()) : QString()) %
549  (this->getDbKeyAsStringInParentheses(" "));
550  }
551 
553  {
554  if (!this->hasIataCode()) { return {}; }
555  return this->getIataCode() % u" [IATA" % (this->hasDesignator() ? (u' ' % this->getDesignator()) : QString()) %
556  (this->hasManufacturer() ? (u' ' % this->getManufacturer()) : QString()) %
557  (this->hasModelDescription() ? (u' ' % this->getModelDescription()) : QString()) %
558  (this->getDbKeyAsStringInParentheses(" "));
559  }
560 
562  {
563  if (!this->hasFamily()) { return {}; }
564  return this->getFamily() % u" [family" % (this->hasDesignator() ? (u' ' % this->getDesignator()) : QString()) %
565  (this->hasManufacturer() ? (u' ' % this->getManufacturer()) : QString()) %
566  (this->hasModelDescription() ? (u' ' % this->getModelDescription()) : QString()) %
567  (this->getDbKeyAsStringInParentheses(" "));
568  }
569 
570  bool CAircraftIcaoCode::hasCategory() const { return !m_category.isNull(); }
571 
573  {
575  }
576 
577  bool CAircraftIcaoCode::matchesDesignator(const QString &designator, int fuzzyMatch, int *result) const
578  {
579  Q_ASSERT_X(fuzzyMatch >= -1 && fuzzyMatch <= 100, Q_FUNC_INFO, "fuzzyMatch range 0..100 or -1");
580  if (designator.isEmpty()) { return false; }
581  const QString d = designator.trimmed().toUpper();
582  if (fuzzyMatch >= 0)
583  {
584  const int r = fuzzyShortStringComparision(this->getDesignator(), d) >= fuzzyMatch;
585  if (result) { *result = r; }
586  return (r >= fuzzyMatch);
587  }
588  else
589  {
590  const bool e = this->getDesignator() == d;
591  if (result) { *result = e ? 100 : 0; }
592  return e;
593  }
594  }
595 
596  bool CAircraftIcaoCode::matchesIataCode(const QString &iata, int fuzzyMatch, int *result) const
597  {
598  Q_ASSERT_X(fuzzyMatch >= -1 && fuzzyMatch <= 100, Q_FUNC_INFO, "fuzzyMatch range 0..100 or -1");
599  if (iata.isEmpty()) { return false; }
600  const QString i = iata.trimmed().toUpper();
601  if (fuzzyMatch >= 0)
602  {
603  const int r = fuzzyShortStringComparision(this->getIataCode(), i) >= fuzzyMatch;
604  if (result) { *result = r; }
605  return (r >= fuzzyMatch);
606  }
607  else
608  {
609  const bool e = this->getIataCode() == i;
610  if (result) { *result = e ? 100 : 0; }
611  return e;
612  }
613  }
614 
615  bool CAircraftIcaoCode::matchesFamily(const QString &family, int fuzzyMatch, int *result) const
616  {
617  Q_ASSERT_X(fuzzyMatch >= -1 && fuzzyMatch <= 100, Q_FUNC_INFO, "fuzzyMatch range 0..100 or -1");
618  if (family.isEmpty()) { return false; }
619  const QString f = family.trimmed().toUpper();
620  if (fuzzyMatch >= 0)
621  {
622  const int r = fuzzyShortStringComparision(this->getFamily(), f) >= fuzzyMatch;
623  if (result) { *result = r; }
624  return r >= fuzzyMatch;
625  }
626  else
627  {
628  const bool e = this->getFamily() == f;
629  if (result) { *result = e ? 100 : 0; }
630  return e;
631  }
632  }
633 
634  bool CAircraftIcaoCode::matchesDesignatorOrIata(const QString &icaoOrIata) const
635  {
636  if (icaoOrIata.isEmpty()) { return false; }
637  return matchesDesignator(icaoOrIata) || matchesIataCode(icaoOrIata);
638  }
639 
640  bool CAircraftIcaoCode::matchesDesignatorIataOrFamily(const QString &icaoIataOrFamily) const
641  {
642  if (icaoIataOrFamily.isEmpty()) { return false; }
643  return matchesDesignator(icaoIataOrFamily) || matchesIataCode(icaoIataOrFamily) ||
644  matchesFamily(icaoIataOrFamily);
645  }
646 
648  {
649  if (index.isMyself()) { return QVariant::fromValue(*this); }
650  if (IDatastoreObjectWithIntegerKey::canHandleIndex(index))
651  {
652  return IDatastoreObjectWithIntegerKey::propertyByIndex(index);
653  }
654  const ColumnIndex i = index.frontCasted<ColumnIndex>();
655  switch (i)
656  {
657  case IndexAircraftDesignator: return QVariant::fromValue(m_designator);
658  case IndexCategory: return m_category.propertyByIndex(index.copyFrontRemoved());
659  case IndexIataCode: return QVariant::fromValue(m_iataCode);
660  case IndexFamily: return QVariant::fromValue(m_family);
661  case IndexCombinedAircraftType: return QVariant::fromValue(m_combinedType);
662  case IndexModelDescription: return QVariant::fromValue(m_modelDescription);
663  case IndexModelIataDescription: return QVariant::fromValue(m_modelIataDescription);
664  case IndexModelSwiftDescription: return QVariant::fromValue(m_modelSwiftDescription);
665  case IndexCombinedDescription: return QVariant::fromValue(this->getCombinedModelDescription());
666  case IndexManufacturer: return QVariant::fromValue(m_manufacturer);
667  case IndexWtc: return QVariant::fromValue(m_wtc);
668  case IndexIsVtol: return QVariant::fromValue(this->isVtol());
669  case IndexIsLegacy: return QVariant::fromValue(m_legacy);
670  case IndexIsMilitary: return QVariant::fromValue(m_military);
671  case IndexIsRealworld: return QVariant::fromValue(m_realWorld);
672  case IndexRank: return QVariant::fromValue(m_rank);
673  case IndexDesignatorManufacturer: return QVariant::fromValue(this->getDesignatorManufacturer());
674  default: return CValueObject::propertyByIndex(index);
675  }
676  }
677 
678  void CAircraftIcaoCode::setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
679  {
680  if (index.isMyself())
681  {
682  (*this) = variant.value<CAircraftIcaoCode>();
683  return;
684  }
685  if (IDatastoreObjectWithIntegerKey::canHandleIndex(index))
686  {
687  IDatastoreObjectWithIntegerKey::setPropertyByIndex(index, variant);
688  return;
689  }
690  const ColumnIndex i = index.frontCasted<ColumnIndex>();
691  switch (i)
692  {
693  case IndexAircraftDesignator: this->setDesignator(variant.value<QString>()); break;
694  case IndexCategory: m_category.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
695  case IndexIataCode: this->setIataCode(variant.value<QString>()); break;
696  case IndexFamily: this->setFamily(variant.value<QString>()); break;
697  case IndexCombinedAircraftType: this->setCombinedType(variant.value<QString>()); break;
698  case IndexModelDescription: this->setModelDescription(variant.value<QString>()); break;
699  case IndexModelIataDescription: this->setModelIataDescription(variant.value<QString>()); break;
700  case IndexModelSwiftDescription: this->setModelSwiftDescription(variant.value<QString>()); break;
701  case IndexManufacturer: this->setManufacturer(variant.value<QString>()); break;
702  case IndexWtc: this->setWtc(variant.value<CWakeTurbulenceCategory>()); break;
703  case IndexIsLegacy: m_legacy = variant.toBool(); break;
704  case IndexIsMilitary: m_military = variant.toBool(); break;
705  case IndexRank: m_rank = variant.toInt(); break;
706  default: CValueObject::setPropertyByIndex(index, variant); break;
707  }
708  }
709 
711  {
712  if (index.isMyself()) { return m_designator.compare(compareValue.getDesignator(), Qt::CaseInsensitive); }
713  if (IDatastoreObjectWithIntegerKey::canHandleIndex(index))
714  {
715  return IDatastoreObjectWithIntegerKey::comparePropertyByIndex(index, compareValue);
716  }
717  const ColumnIndex i = index.frontCasted<ColumnIndex>();
718  switch (i)
719  {
720  case IndexAircraftDesignator: return m_designator.compare(compareValue.getDesignator(), Qt::CaseInsensitive);
721  case IndexCategory:
722  return m_category.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getCategory());
723  case IndexIataCode: return m_iataCode.compare(compareValue.getIataCode(), Qt::CaseInsensitive);
724  case IndexFamily: return m_family.compare(compareValue.getFamily(), Qt::CaseInsensitive);
725  case IndexCombinedAircraftType:
726  return m_combinedType.compare(compareValue.getCombinedType(), Qt::CaseInsensitive);
727  case IndexModelDescription:
728  return m_modelDescription.compare(compareValue.getModelDescription(), Qt::CaseInsensitive);
729  case IndexModelIataDescription:
730  return m_modelIataDescription.compare(compareValue.getModelIataDescription(), Qt::CaseInsensitive);
731  case IndexModelSwiftDescription:
732  return m_modelSwiftDescription.compare(compareValue.getModelSwiftDescription(), Qt::CaseInsensitive);
733  case IndexCombinedDescription:
734  {
735  // compare without generating new strings
736  int c = m_modelDescription.compare(compareValue.getModelDescription(), Qt::CaseInsensitive);
737  if (c == 0)
738  {
739  c = m_modelIataDescription.compare(compareValue.getModelIataDescription(), Qt::CaseInsensitive);
740  if (c == 0)
741  {
742  c = m_modelSwiftDescription.compare(compareValue.getModelSwiftDescription(), Qt::CaseInsensitive);
743  }
744  }
745  return c;
746  }
747  case IndexManufacturer: return m_manufacturer.compare(compareValue.getManufacturer(), Qt::CaseInsensitive);
748  case IndexWtc: return m_wtc == compareValue.getWtc();
749  case IndexIsLegacy: return Compare::compare(m_legacy, compareValue.isLegacyAircraft());
750  case IndexIsMilitary: return Compare::compare(m_military, compareValue.isMilitary());
751  case IndexIsVtol: return Compare::compare(isVtol(), compareValue.isVtol());
752  case IndexIsRealworld: return Compare::compare(m_realWorld, compareValue.isRealWorld());
753  case IndexRank: return Compare::compare(m_rank, compareValue.getRank());
755  return getDesignatorManufacturer().compare(compareValue.getDesignatorManufacturer(), Qt::CaseInsensitive);
756  default: return CValueObject::comparePropertyByIndex(index, *this);
757  }
758  Q_ASSERT_X(false, Q_FUNC_INFO, "No comparison");
759  return 0;
760  }
761 
762  bool CAircraftIcaoCode::isValidDesignator(const QString &designator)
763  {
764  if (designator.length() < DesignatorMinLength || designator.length() > DesignatorMaxLength) { return false; }
765  if (!designator[0].isUpper()) { return false; }
766  return !containsChar(designator, [](QChar c) { return !c.isUpper() && !c.isDigit(); });
767  }
768 
769  bool CAircraftIcaoCode::isValidCombinedType(const QString &combinedType)
770  {
771  if (combinedType.length() != 3) { return false; }
772 
773  // Amphibian, Glider, Helicopter, Seaplane, Landplane, Tilt wing
774  static const QString validDescriptions = "AGHSLT";
775  // Electric, Jet, Piston, Turpoprop, and - for no engine
776  static const QString validEngines = "EJPT-";
777 
778  if (!validDescriptions.contains(combinedType[0])) { return false; }
779  if (!combinedType[1].isDigit()) { return false; }
780  if (!validEngines.contains(combinedType[2])) { return false; }
781  return true;
782  }
783 
785  {
786  static const QString z("ZZZZ");
787  return z;
788  }
789 
791  {
792  static const QString g("GLID");
793  return g;
794  }
795 
797  {
798  static const CAircraftIcaoCode z(getUnassignedDesignator());
799  return z;
800  }
801 
803  {
804  static const QStringList s({ "ZZZZ", "SHIP", "BALL", getGliderDesignator(), "ULAC", "GYRO", "UHEL" });
805  return s;
806  }
807 
808  QString CAircraftIcaoCode::normalizeDesignator(const QString &candidate)
809  {
810  QString n(candidate.trimmed().toUpper());
811  n = n.left(indexOfChar(n, [](QChar c) { return c.isSpace(); }));
812  return removeChars(n, [](QChar c) { return !c.isLetterOrNumber(); });
813  }
814 
815  QStringList CAircraftIcaoCode::alternativeCombinedCodes(const QString &combinedCode)
816  {
817  // manually add some replacements for frequently used types
818  static const QMultiMap<QString, QString> knownCodes {
819  { "L1P", "L2P" }, { "L1P", "S1P" }, { "L2J", "L3J" }, { "L2J", "L4J" }, { "L3J", "L4J" }
820  };
821 
822  if (isValidCombinedType(combinedCode)) { return QStringList(); }
823  if (knownCodes.contains(combinedCode)) { return knownCodes.values(combinedCode); }
824 
825  // turn E to P engine
826  if (combinedCode.endsWith("E")) { return QStringList({ QStringView { combinedCode }.left(2) % u'P' }); }
827 
828  // turn T to H plane (tilt wing to helicopter
829  if (combinedCode.startsWith("T")) { return QStringList({ u'H' % QStringView { combinedCode }.right(2) }); }
830 
831  // based on engine count
832  QStringList codes;
833  int engineCount = combinedCode[1].digitValue();
834  if (engineCount > 1)
835  {
836  for (int c = 2; c < 5; c++)
837  {
838  if (c == engineCount) { continue; }
839  const QString code(combinedCode.at(0) + QString::number(c) + combinedCode.at(2));
840  codes.push_back(code);
841  }
842  }
843  return codes;
844  }
845 
846  bool CAircraftIcaoCode::isEPTEngineType(const QChar engineType)
847  {
848  const QChar e = engineType.toUpper();
849  return e == 'P' || e == 'E' || e == 'T';
850  }
851 
852  CStatusMessage CAircraftIcaoCode::logMessage(const CAircraftIcaoCode &icaoCode, const QString &message,
853  const QStringList &extraCategories, CStatusMessage::StatusSeverity s)
854  {
855  static const CLogCategoryList cats({ CLogCategories::aviation() });
856  const CStatusMessage m(cats.with(CLogCategoryList::fromQStringList(extraCategories)), s,
857  icaoCode.hasDesignator() ? icaoCode.getDesignatorDbKey() + ": " + message.trimmed() :
858  message.trimmed());
859  return m;
860  }
861 
863  const QString &message, const QStringList &extraCategories,
865  {
866  if (!log) { return; }
867  if (message.isEmpty()) { return; }
868  log->push_back(logMessage(icao, message, extraCategories, s));
869  }
870 
871  CAircraftIcaoCode CAircraftIcaoCode::fromDatabaseJson(const QJsonObject &json, const QString &prefix)
872  {
873  if (!existsKey(json, prefix))
874  {
875  // when using relationship, this can be null
876  return CAircraftIcaoCode();
877  }
878 
879  const int engineCount(json.value(prefix % u"enginecount").toInt(-1));
880  const int categoryId(json.value(prefix % u"idcategory").toInt(-1));
881  const QString designator(json.value(prefix % u"designator").toString());
882  const QString iata(json.value(prefix % u"iata").toString());
883  const QString family(json.value(prefix % u"family").toString());
884  const QString manufacturer(json.value(prefix % u"manufacturer").toString());
885  const QString model(json.value(prefix % u"model").toString());
886  const QString modelIata(json.value(prefix % u"modeliata").toString());
887  const QString modelSwift(json.value(prefix % u"modelswift").toString());
888  const QString type(json.value(prefix % u"type").toString());
889  const QString engine(json.value(prefix % u"engine").toString());
890  const QString combined(createdCombinedString(type, engineCount, engine));
891 
892  QString wtcString(json.value(prefix % u"wtc").toString());
893  if (wtcString.length() > 1 && wtcString.contains("/"))
894  {
895  // "L/M" -> "M"
896  wtcString = wtcString.right(1);
897  }
898  Q_ASSERT_X(wtcString.length() < 2, Q_FUNC_INFO, "WTC too long");
899  const CWakeTurbulenceCategory wtc =
900  wtcString.isEmpty() ? CWakeTurbulenceCategory() : CWakeTurbulenceCategory(wtcString.at(0));
901 
902  const bool real = CDatastoreUtility::dbBoolStringToBool(json.value(prefix % u"realworld").toString());
903  const bool legacy = CDatastoreUtility::dbBoolStringToBool(json.value(prefix % u"legacy").toString());
904  const bool military = CDatastoreUtility::dbBoolStringToBool(json.value(prefix % u"military").toString());
905  const int rank(json.value(prefix % u"rank").toInt(10));
906 
907  CAircraftIcaoCode code(designator, iata, family, combined, manufacturer, model, modelIata, modelSwift, wtc,
908  real, legacy, military, rank);
909  code.setKeyVersionTimestampFromDatabaseJson(json, prefix);
910  if (categoryId >= 0) { code.setCategoryId(categoryId); }
911  return code;
912  }
913 
914  QString CAircraftIcaoCode::createdCombinedString(const QString &type, const QString &engineCount,
915  const QString &engine)
916  {
917  Q_ASSERT_X(engineCount.length() < 2, Q_FUNC_INFO, "Wrong engine count");
918  return (type.isEmpty() ? QStringLiteral("-") : type.trimmed().left(1).toUpper()) %
919  (engineCount.isEmpty() ? QStringLiteral("-") : engineCount.trimmed()) %
920  (engine.isEmpty() ? QStringLiteral("-") : engine.trimmed().left(1).toUpper());
921  }
922 
923  QString CAircraftIcaoCode::createdCombinedString(const QString &type, int engineCount, const QString &engine)
924  {
925  const bool valid = (engineCount >= 0 && engineCount < 10);
926  return createdCombinedString(type, valid ? QString::number(engineCount) : "", engine);
927  }
928 } // namespace swift::misc::aviation
static const QString & aviation()
Aviation specific.
static const QString & validation()
Validation.
Definition: logcategories.h:38
A log category is an arbitrary string tag which can be attached to log messages.
Definition: logcategory.h:28
A sequence of log categories.
static CLogCategoryList fromQStringList(const QStringList &stringList)
Convert a string list, such as that returned by toQStringList(), into a CLogCategoryList.
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.
constexpr static auto SeverityError
Status severities.
Status messages, e.g. from Core -> GUI.
QDateTime getUtcTimestamp() const
Get timestamp.
void setUtcTimestamp(const QDateTime &timestamp)
Set timestamp.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
int comparePropertyByIndex(CPropertyIndexRef index, const CAircraftCategory &compareValue) const
Compare for index.
QString getNameDbKey() const
Designator and DB key.
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Value object for ICAO classification.
CWakeTurbulenceCategory getWtc() const
Get WTC.
QString getCombinedFamilyStringWithKey() const
Combined family descriptive string with key.
static const QString & getUnassignedDesignator()
The unassigned designator ("ZZZZ")
bool isRealWorld() const
Real world aircraft?
bool hasModelDescription() const
Has model description?
QString getCombinedIataStringWithKey() const
Combined IATA descriptive string with key.
bool hasDesignator() const
Aircraft designator?
bool isIataSameAsDesignator() const
IATA code same as designator?
bool hasKnownDesignator() const
Has designator and designator is not "ZZZZ".
bool isVtol() const
Is VTOL aircraft (helicopter, tilt wing)
@ IndexDesignatorManufacturer
designator and manufacturer
static bool isEPTEngineType(const QChar engineType)
Engine tye is Electric, Piston, TurboProp.
QString getCombinedIcaoStringWithKey() const
Combined ICAO descriptive string with key.
const QString & getFamily() const
Family (e.g. A350)
QVariant propertyByIndex(swift::misc::CPropertyIndexRef index) const
Property by index.
static bool isValidCombinedType(const QString &combinedType)
Valid combined type.
static const QStringList & getSpecialDesignators()
List of the special designators ("ZZZZ", "UHEL", ...)
bool hasSpecialDesignator() const
Special designator.
void setCodeFlags(bool military, bool legacy, bool realWorld)
Flags.
bool matchesDesignatorOrIata(const QString &icaoOrIata) const
Matches ICAO or IATA code.
static const CAircraftIcaoCode & unassignedIcao()
Unassigned ICAO code "ZZZZ".
QString getAircraftType() const
Aircraft type, such a L(andplane), S(eaplane), H(elicopter)
bool matchesDesignator(const QString &designator, int fuzzyMatch=-1, int *result=nullptr) const
Matches designator string?
bool matchesDesignatorIataOrFamily(const QString &icaoIataOrFamily) const
Matches ICAO, IATA, family?
QString getEngineType() const
Get engine type, e.g. "J".
static void addLogDetailsToList(CStatusMessageList *log, const CAircraftIcaoCode &icao, const QString &message, const QStringList &extraCategories={}, CStatusMessage::StatusSeverity s=CStatusMessage::SeverityInfo)
Specialized log for matching / reverse lookup.
QChar getEngineTypeChar() const
Get engine type, e.g. "J".
bool hasIataCode() const
Has IATA code?
int getEnginesCount() const
Engine count if any, -1 if no value is set.
const QString & getDesignator() const
Get ICAO designator, e.g. "B737".
void setModelSwiftDescription(const QString &modelDescription)
Set the alternative swift model description.
bool matchesIataCode(const QString &iata, int fuzzyMatch=-1, int *result=nullptr) const
Matches IATA string?
void setMilitary(bool military)
Military.
QString getEngineCountString() const
Engine count as string, if not available "".
void setCategoryId(int id)
Set category id.
QString getCombinedModelDescription() const
Combined description.
QString getDesignatorDbKey() const
Designator and DB key.
const QString & getModelDescription() const
Get IACO model description, e.g. "A-330-200".
void setIataCode(const QString &iata)
Set IATA code.
void setPropertyByIndex(swift::misc::CPropertyIndexRef index, const QVariant &variant)
Set property by index.
void setWtc(CWakeTurbulenceCategory wtc)
Set WTC.
void setManufacturer(const QString &manufacturer)
Set the manufacturer.
const QString & getIataCode() const
IATA code.
static CStatusMessage logMessage(const CAircraftIcaoCode &icaoCode, const QString &message, const QStringList &extraCategories={}, CStatusMessage::StatusSeverity s=CStatusMessage::SeverityInfo)
Specialized log message for matching / reverse lookup.
bool matchesCombinedType(const QString &combinedType) const
Matches given combined code.
void setCombinedType(const QString &type)
Set type.
bool hasValidDesignator() const
Valid aircraft designator?
int comparePropertyByIndex(CPropertyIndexRef index, const CAircraftIcaoCode &compareValue) const
Compare for index.
void setModelIataDescription(const QString &modelDescription)
Set the alternative IATA model description.
void updateMissingParts(const CAircraftIcaoCode &otherIcaoCode)
Update missing parts.
static constexpr int DesignatorMinLength
designator length (min)
CStatusMessageList validate() const
Validate data.
bool matchesAnyDescription(const QString &candidate) const
Matches any of the (unempty) descriptions.
bool isDbDuplicate() const
Is DB duplicate? This means a redundant ICAO DB entry.
static const CAircraftIcaoCode & null()
NULL object.
QString convertToQString(bool i18n=false) const
Cast as QString.
static CAircraftIcaoCode fromDatabaseJson(const QJsonObject &json, const QString &prefix=QString())
From our database JSON format.
int calculateScore(const CAircraftIcaoCode &otherCode, CStatusMessageList *log=nullptr) const
Considers rank, manufacturer and family 0..100.
QString getCombinedIcaoCategoryStringWithKey() const
Combined ICAO descriptive string with category and key.
void setDesignator(const QString &icaoDesignator)
Set ICAO designator, e.g. "B737".
bool isFamilySameAsDesignator() const
Family same as designator?
void setRealWorld(bool realWorld)
Real world.
void setModelDescription(const QString &modelDescription)
Set the model description (ICAO description)
QString asHtmlSummary() const
As a brief HTML summary (e.g. used in tooltips)
static QStringList alternativeCombinedCodes(const QString &combinedCode)
Create relaxed combined codes, e.g "L2J" -> "L3J", ...
bool matchesCombinedTypeAndManufacturer(const QString &combinedType, const QString &manufacturer) const
Matches combined type and.
const QString & getCombinedType() const
Get type, e.g. "L2J".
const CAircraftCategory & getCategory() const
Get category.
bool matchesFamily(const QString &family, int fuzzyMatch=-1, int *result=nullptr) const
Matches family?
static constexpr int DesignatorMaxLength
designator length (max)
const QString & getModelSwiftDescription() const
Get swift model description.
bool isLegacyAircraft() const
Legacy aircraft (no current ICAO code)
static bool isValidDesignator(const QString &designator)
Valid designator?
static const QString & getGliderDesignator()
Get the glider designator.
bool hasModelSwiftDescription() const
Has swift model description?
QChar getAircraftTypeChar() const
Aircraft type, such a L(andplane), S(eaplane), H(elicopter)
bool hasValidCombinedType() const
Combined type available?
void setFamily(const QString &family)
Set family.
const QString & getManufacturer() const
Get manufacturer, e.g. "Airbus".
static QString normalizeDesignator(const QString &candidate)
Normalize designator, remove illegal characters.
bool matchesManufacturer(const QString &manufacturer) const
Matching the manufacturer?
QString getDesignatorManufacturer() const
Designator + Manufacturer.
const QString & getModelIataDescription() const
Get IATA model description.
void guessModelParameters(physical_quantities::CLength &guessedCGOut, physical_quantities::CSpeed &guessedVRotateOut) const
Guess aircraft model parameters.
bool hasModelIataDescription() const
Has IATA model description?
bool hasValidWtc() const
Valid WTC code?
QString getDbKeyAsString() const
DB key as string.
Definition: datastore.cpp:30
bool isLoadedFromDb() const
Loaded from DB.
Definition: datastore.cpp:49
static bool existsKey(const QJsonObject &json, const QString &prefix=QString())
Is a key available?
Definition: datastore.cpp:88
void setDbKey(int key)
Set the DB key.
Definition: datastore.h:96
bool isDbEqual(const IDatastoreObjectWithIntegerKey &other) const
Same DB key and hence equal.
Definition: datastore.h:105
QString getDbKeyAsStringInParentheses(const QString &prefix={}) const
Db key in parentheses, e.g. "(3)".
Definition: datastore.cpp:36
void setKeyVersionTimestampFromDatabaseJson(const QJsonObject &json, const QString &prefix=QString())
Set key and timestamp values.
Definition: datastore.cpp:79
bool hasValidDbKey() const
Has valid DB key.
Definition: datastore.h:102
int comparePropertyByIndex(CPropertyIndexRef index, const Derived &compareValue) const
Compare for index.
Definition: mixinindex.h:187
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
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Physical unit length (length)
Definition: length.h:18
Free functions in swift::misc.
SWIFT_MISC_EXPORT int fuzzyShortStringComparision(const QString &str1, const QString &str2, Qt::CaseSensitivity cs=Qt::CaseSensitive)
Fuzzy compare for short strings (like ICAO designators)
QString removeChars(const QString &s, F predicate)
Return a string with characters removed that match the given predicate.
Definition: stringutils.h:35
int indexOfChar(const QString &s, F predicate)
Index of first character in the string matching the given predicate, or -1 if not found.
Definition: stringutils.h:78
StatusSeverity
Status severities.
Definition: statusmessage.h:35
bool containsChar(const QString &s, F predicate)
True if any character in the string matches the given predicate.
Definition: stringutils.h:65
SWIFT_MISC_EXPORT const QString & boolToYesNo(bool v)
Bool to yes/no.
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67