swift
aircrafticaocodelist.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 <QJsonValue>
7 #include <Qt>
8 
10 #include "misc/setbuilder.h"
11 
12 SWIFT_DEFINE_SEQUENCE_MIXINS(swift::misc::aviation, CAircraftIcaoCode, CAircraftIcaoCodeList)
13 
14 namespace swift::misc::aviation
15 {
17 
20  {}
21 
22  CAircraftIcaoCodeList::CAircraftIcaoCodeList(std::initializer_list<CAircraftIcaoCode> il)
24  {}
25 
26  CAircraftIcaoCodeList CAircraftIcaoCodeList::findByDesignator(const QString &designator, int fuzzySearch) const
27  {
28  if (!fuzzySearch && !CAircraftIcaoCode::isValidDesignator(designator)) { return CAircraftIcaoCodeList(); }
29  if (fuzzySearch && designator.length() < CAircraftIcaoCode::DesignatorMinLength)
30  {
31  return CAircraftIcaoCodeList();
32  }
33  return this->findBy(
34  [&](const CAircraftIcaoCode &code) { return code.matchesDesignator(designator, fuzzySearch); });
35  }
36 
37  CAircraftIcaoCode CAircraftIcaoCodeList::findBestFuzzyMatchOrDefault(const QString &designator, int cutoff) const
38  {
39  if (designator.length() < CAircraftIcaoCode::DesignatorMinLength) { return CAircraftIcaoCode(); }
40  int best = 0;
41  int current = 0;
42  CAircraftIcaoCode found;
43  const QString d(designator.trimmed().toUpper());
44  for (const CAircraftIcaoCode &code : *this)
45  {
46  if (!code.matchesDesignator(d, cutoff, &current)) { continue; }
47  if (current == 100.0) { return code; }
48  if (best < current) { found = code; }
49  }
50  return found;
51  }
52 
54  {
55  return this->findBy([](const CAircraftIcaoCode &code) { return code.hasValidDesignator(); });
56  }
57 
59  {
60  return this->findBy([](const CAircraftIcaoCode &code) { return !code.hasValidDesignator(); });
61  }
62 
64  {
65  if (icaoOrIata.isEmpty()) { return CAircraftIcaoCodeList(); }
66  return this->findBy([&](const CAircraftIcaoCode &code) { return code.matchesDesignatorOrIata(icaoOrIata); });
67  }
68 
70  {
71  if (icaoIataOrFamily.isEmpty()) { return CAircraftIcaoCodeList(); }
72  return this->findBy(
73  [&](const CAircraftIcaoCode &code) { return code.matchesDesignatorIataOrFamily(icaoIataOrFamily); });
74  }
75 
77  {
78  const QString ends = icaoEnding.trimmed().toUpper();
79  if (ends.isEmpty()) { return CAircraftIcaoCodeList(); }
80  CAircraftIcaoCodeList icaosDesignator;
81  CAircraftIcaoCodeList icaosFamily;
82  for (const CAircraftIcaoCode &icao : *this)
83  {
84  if (icao.getDesignator().endsWith(ends)) { icaosDesignator.push_back(icao); }
85  else if (icao.getFamily().endsWith(ends)) { icaosFamily.push_back(icao); }
86  }
87  return icaosDesignator.isEmpty() ? icaosFamily : icaosDesignator;
88  }
89 
90  CAircraftIcaoCodeList CAircraftIcaoCodeList::findByIataCode(const QString &iata, int fuzzySearch) const
91  {
92  if (iata.isEmpty()) { return CAircraftIcaoCodeList(); }
93  return this->findBy([&](const CAircraftIcaoCode &code) { return code.matchesIataCode(iata, fuzzySearch); });
94  }
95 
96  CAircraftIcaoCodeList CAircraftIcaoCodeList::findByFamily(const QString &family, int fuzzySearch) const
97  {
98  if (family.isEmpty()) { return CAircraftIcaoCodeList(); }
99  return this->findBy([&](const CAircraftIcaoCode &code) { return code.matchesFamily(family, fuzzySearch); });
100  }
101 
103  {
104  if (manufacturer.isEmpty()) { return CAircraftIcaoCodeList(); }
105  return this->findBy([&](const CAircraftIcaoCode &code) {
106  return code.getManufacturer().startsWith(manufacturer, Qt::CaseInsensitive);
107  });
108  }
109 
111  {
112  if (description.isEmpty()) { return CAircraftIcaoCodeList(); }
113  return this->findBy([&](const CAircraftIcaoCode &code) {
114  return code.getModelDescription().startsWith(description, Qt::CaseInsensitive);
115  });
116  }
117 
119  {
120  return this->findBy([&](const CAircraftIcaoCode &code) { return code.matchesAnyDescription(description); });
121  }
122 
123  CAircraftIcaoCodeList CAircraftIcaoCodeList::findWithIataCode(bool removeWhenSameAsDesignator) const
124  {
125  return this->findBy([&](const CAircraftIcaoCode &code) {
126  if (!code.hasIataCode()) { return false; }
127  return !removeWhenSameAsDesignator || !code.isIataSameAsDesignator();
128  });
129  }
130 
131  CAircraftIcaoCodeList CAircraftIcaoCodeList::findWithFamily(bool removeWhenSameAsDesignator) const
132  {
133  return this->findBy([&](const CAircraftIcaoCode &code) {
134  if (!code.hasFamily()) { return false; }
135  return !removeWhenSameAsDesignator || !code.isFamilySameAsDesignator();
136  });
137  }
138 
140  {
141  return this->findBy([&](const CAircraftIcaoCode &code) { return (code.isMilitary() == military); });
142  }
143 
145  {
146  if (!CAircraftIcaoCode::isValidDesignator(designator)) { return CAircraftIcaoCode(); }
147  CAircraftIcaoCodeList codes(this->findByDesignator(designator));
148  if (codes.isEmpty()) { return CAircraftIcaoCode(); }
149  if (codes.size() < 2) { return codes.front(); }
151  return codes.front();
152  }
153 
154  bool CAircraftIcaoCodeList::containsDesignator(const QString &designator) const
155  {
156  if (designator.isEmpty()) { return false; }
157  return this->contains(&CAircraftIcaoCode::getDesignator, designator);
158  }
159 
161 
163  {
165  }
166 
168  {
171  }
172 
174  {
175  this->removeIf([](const CAircraftIcaoCode &icao) { return !icao.hasValidCombinedType(); });
176  }
177 
179 
180  QStringList CAircraftIcaoCodeList::toCompleterStrings(bool withIataCodes, bool withFamily, bool withCategory,
181  bool sort) const
182  {
183  QStringList c;
184  CAircraftIcaoCodeList icaos(*this);
185  if (sort) { icaos.sortByDesignatorAndRank(); }
186 
187  // 3 steps to get a proper sort order of the string list
188  for (const CAircraftIcaoCode &icao : std::as_const(icaos))
189  {
190  c.append(withCategory ? icao.getCombinedIcaoCategoryStringWithKey() : icao.getCombinedIcaoStringWithKey());
191  }
192 
193  if (withFamily)
194  {
195  const CAircraftIcaoCodeList icaosFamily = icaos.findWithFamily(true);
196  for (const CAircraftIcaoCode &icao : icaosFamily) { c.append(icao.getCombinedFamilyStringWithKey()); }
197  }
198 
199  if (withIataCodes)
200  {
201  icaos = icaos.findWithIataCode(true);
203  for (const CAircraftIcaoCode &icao : std::as_const(icaos))
204  {
205  c.append(icao.getCombinedIataStringWithKey());
206  }
207  }
208 
209  return c;
210  }
211 
212  QSet<QString> CAircraftIcaoCodeList::allDesignators(bool noUnspecified) const
213  {
215  for (const CAircraftIcaoCode &icao : *this)
216  {
217  if (noUnspecified && !icao.hasKnownDesignator()) { continue; }
218  const QString d(icao.getDesignator());
219  c.insert(d);
220  }
221  return c;
222  }
223 
224  QSet<QString> CAircraftIcaoCodeList::allDesignatorsAndKey(bool noUnspecified) const
225  {
227  for (const CAircraftIcaoCode &icao : *this)
228  {
229  if (noUnspecified && !icao.hasKnownDesignator()) { continue; }
230  const QString d(icao.getDesignatorDbKey());
231  c.insert(d);
232  }
233  return c;
234  }
235 
237  {
239  for (const CAircraftIcaoCode &icao : *this)
240  {
241  if (!icao.hasFamily()) { continue; }
242  const QString d(icao.getFamily());
243  c.insert(d);
244  }
245  return c;
246  }
247 
248  QSet<QString> CAircraftIcaoCodeList::allManufacturers(bool onlyKnownDesignators) const
249  {
251  for (const CAircraftIcaoCode &icao : *this)
252  {
253  if (onlyKnownDesignators && !icao.hasKnownDesignator()) { continue; }
254  const QString m(icao.getManufacturer());
255  if (m.isEmpty()) { continue; }
256  c.insert(m); // checks if already contains m
257  }
258  return c;
259  }
260 
262  {
263  QMap<QString, int> count;
264  for (const CAircraftIcaoCode &icao : *this)
265  {
266  if (!icao.hasManufacturer()) { continue; }
267  count[icao.getManufacturer()]++;
268  }
269  return count;
270  }
271 
273  {
274  const QMap<QString, int> counts(countManufacturers());
275  if (counts.isEmpty()) return { {}, 0 };
276  const auto pair = *std::max_element(counts.keyValueBegin(), counts.keyValueEnd(),
277  [](const auto &a, const auto &b) { return a.second < b.second; });
278  return { pair.first, pair.second };
279  }
280 
282  const CAircraftCategoryList &categories,
283  bool ignoreIncompleteAndDuplicates,
284  CAircraftIcaoCodeList *inconsistent)
285  {
286  CAircraftIcaoCodeList codes;
287  for (const QJsonValue &value : array)
288  {
290  const int catId = icao.getCategory().getDbKey();
291  if (!categories.isEmpty() && catId >= 0)
292  {
293  const CAircraftCategory category = categories.findByKey(catId);
294  if (!category.isNull()) { icao.setCategory(category); }
295  }
296 
297  if (!icao.hasSpecialDesignator() && !icao.hasCompleteData())
298  {
299  if (ignoreIncompleteAndDuplicates) { continue; }
300  if (inconsistent)
301  {
302  inconsistent->push_back(icao);
303  continue;
304  }
305  }
306  if (icao.isDbDuplicate())
307  {
308  if (ignoreIncompleteAndDuplicates) { continue; }
309  if (inconsistent)
310  {
311  inconsistent->push_back(icao);
312  continue;
313  }
314  }
315  codes.push_back(icao);
316  }
317  return codes;
318  }
319 
321  {
322  if (icaoPattern.hasValidDbKey())
323  {
324  const int k = icaoPattern.getDbKey();
325  const CAircraftIcaoCode c(this->findByKey(k));
326  if (c.hasCompleteData()) { return c; }
327  }
328 
329  // get an initial set of data we can choose from
330  const QString designator(icaoPattern.getDesignator());
331  if (designator.isEmpty()) { return CAircraftIcaoCode(); }
332  CAircraftIcaoCodeList codes;
333  do {
334  codes = this->findByDesignator(designator);
335  if (!codes.isEmpty()) break;
336 
337  // now we search if the ICAO designator is actually an IATA code
338  codes = this->findByIataCode(designator);
339  if (!codes.isEmpty()) break;
340 
341  // search fuzzy and restrict length
342  const CAircraftIcaoCode bestMatch =
343  this->findBestFuzzyMatchOrDefault(designator.length() < 5 ? designator : designator.left(5), 70);
344  if (bestMatch.hasValidDesignator()) { return bestMatch; }
345 
346  // still empty, try to find by family
347  codes = this->findByFamily(designator);
348  if (!codes.isEmpty()) break;
349 
350  // by any description
351  codes = this->findMatchingByAnyDescription(designator);
352  }
353  while (false);
354 
355  if (codes.isEmpty()) { return icaoPattern; }
356  if (codes.size() == 1) { return codes.front(); }
357 
358  // further reduce by manufacturer
359  codes.sortByRank();
360  if (icaoPattern.hasManufacturer() &&
362  {
363  const QString m(icaoPattern.getManufacturer());
364  codes = codes.findByManufacturer(m);
365  if (codes.size() == 1) { return codes.front(); }
366 
367  // intentionally continue here
368  }
369 
370  // further reduce by IATA
371  if (icaoPattern.hasIataCode() && codes.contains(&CAircraftIcaoCode::getIataCode, icaoPattern.getIataCode()))
372  {
373  const QString i(icaoPattern.getIataCode());
374  codes = codes.findByIataCode(i);
375  if (codes.size() == 1) { return codes.front(); }
376 
377  // intentionally continue here
378  }
379 
380  // lucky punch on description?
381  if (icaoPattern.hasModelDescription() &&
383  {
384  // do not affect codes here, it might return no results
385  const QString d(icaoPattern.getModelDescription());
387  if (cm.size() == 1) { return cm.front(); }
388  }
389  return codes.frontOrDefault(); // sorted by rank
390  }
391 
393  {
394  CAircraftIcaoCodeList copy(*this);
396  CAircraftIcaoCodeList grouped; // will contain the entries with the best rank
397  QString designator;
398  QString manufacturer;
399  for (const CAircraftIcaoCode &code : std::as_const(copy))
400  {
401  if (code.getDesignator() != designator || code.getManufacturer() != manufacturer)
402  {
403  designator = code.getDesignator();
404  manufacturer = code.getManufacturer();
405  grouped.push_back(code);
406  }
407  }
408  return grouped;
409  }
410 } // namespace swift::misc::aviation
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
void sortBy(K1 key1, Keys... keys)
In-place sort by some particular key(s).
Definition: sequence.h:576
const_reference frontOrDefault() const
Access the first element, or a default-initialized value if the sequence is empty.
Definition: sequence.h:239
CSequence findBy(Predicate p) const
Return a copy containing only those elements for which a given predicate returns true.
Definition: sequence.h:398
int removeIf(Predicate p)
Remove elements for which a given predicate returns true.
Definition: sequence.h:446
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
reference front()
Access the first element.
Definition: sequence.h:225
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
void sort(Predicate p)
In-place sort by a given comparator predicate.
Definition: sequence.h:560
Build a QSet more efficiently when calling insert() in a for loop.
Definition: setbuilder.h:25
void insert(const T &value)
Add an element to the set. Runs in amortized constant time.
Definition: setbuilder.h:29
Value object for aircraft categories.
Value object encapsulating a list of ICAO codes.
Value object for ICAO classification.
bool hasModelDescription() const
Has model description?
bool isIataSameAsDesignator() const
IATA code same as designator?
bool hasKnownDesignator() const
Has designator and designator is not "ZZZZ".
const QString & getFamily() const
Family (e.g. A350)
bool hasSpecialDesignator() const
Special designator.
bool matchesDesignatorOrIata(const QString &icaoOrIata) const
Matches ICAO or IATA code.
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?
bool hasIataCode() const
Has IATA code?
const QString & getDesignator() const
Get ICAO designator, e.g. "B737".
bool matchesIataCode(const QString &iata, int fuzzyMatch=-1, int *result=nullptr) const
Matches IATA string?
QString getDesignatorDbKey() const
Designator and DB key.
const QString & getModelDescription() const
Get IACO model description, e.g. "A-330-200".
const QString & getIataCode() const
IATA code.
bool hasValidDesignator() const
Valid aircraft designator?
static constexpr int DesignatorMinLength
designator length (min)
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 CAircraftIcaoCode fromDatabaseJson(const QJsonObject &json, const QString &prefix=QString())
From our database JSON format.
bool isFamilySameAsDesignator() const
Family same as designator?
const CAircraftCategory & getCategory() const
Get category.
bool matchesFamily(const QString &family, int fuzzyMatch=-1, int *result=nullptr) const
Matches family?
void setCategory(const CAircraftCategory &category)
Set category.
static bool isValidDesignator(const QString &designator)
Valid designator?
bool hasValidCombinedType() const
Combined type available?
const QString & getManufacturer() const
Get manufacturer, e.g. "Airbus".
Value object encapsulating a list of ICAO codes.
QSet< QString > allFamilies() const
All families, no duplicates.
CAircraftIcaoCodeList findByManufacturer(const QString &manufacturer) const
Find by manufacturer.
QSet< QString > allManufacturers(bool onlyKnownDesignators=true) const
All manufacturers.
static CAircraftIcaoCodeList fromDatabaseJson(const QJsonArray &array, const CAircraftCategoryList &categories, bool ignoreIncompleteAndDuplicates=true, CAircraftIcaoCodeList *inconsistent=nullptr)
From our database JSON format.
CAircraftIcaoCodeList findByInvalidDesignator() const
Ones with an invalid designator.
CAircraftIcaoCodeList groupByDesignatorAndManufacturer() const
Group by designator and manufacturer.
QStringList toCompleterStrings(bool withIataCodes=false, bool withFamily=false, bool withCategory=true, bool sort=true) const
For selection completion.
QSet< QString > allDesignatorsAndKey(bool noUnspecified=true) const
All ICAO codes and DB key, no duplicates.
CAircraftIcaoCodeList findWithFamily(bool removeWhenSameAsDesignator) const
Those with family.
CAircraftIcaoCodeList findWithIataCode(bool removeWhenSameAsDesignator) const
Those with IATA code.
CAircraftIcaoCodeList findByDescription(const QString &description) const
Find by model description.
CAircraftIcaoCodeList findByDesignator(const QString &designator, int fuzzySearch=-1) const
Find by designator.
CAircraftIcaoCodeList findByFamily(const QString &family, int fuzzySearch=-1) const
Find by family.
CAircraftIcaoCodeList findByMilitaryFlag(bool military) const
By military flag.
CAircraftIcaoCode findFirstByDesignatorAndRank(const QString &designator) const
Find by designator, then best match by rank.
CAircraftIcaoCodeList findMatchingByAnyDescription(const QString &description) const
Find matching by any model description.
void sortByDesignatorManufacturerAndRank()
Sort by designator first, then by manufacturer and rank.
void removeDuplicates()
Remove duplicates as marked by CAircraftIcaoCode::isDbDuplicate.
void sortByDesignatorAndRank()
Sort by designator first, then by rank.
QSet< QString > allDesignators(bool noUnspecified=true) const
All ICAO codes, no duplicates.
bool containsDesignator(const QString &designator) const
Contains designator?
CAircraftIcaoCode findBestFuzzyMatchOrDefault(const QString &designator, int cutoff=50) const
Find by designator.
void removeInvalidCombinedCodes()
Remove invalid combined codes.
CAircraftIcaoCode smartAircraftIcaoSelector(const CAircraftIcaoCode &icaoPattern) const
Best selection by given pattern, also searches IATA and family information.
CAircraftIcaoCodeList findByDesignatorOrIataCode(const QString &icaoOrIata) const
Find by ICAO/IATA code.
CAircraftIcaoCodeList findEndingWith(const QString &icaoEnding) const
Find code ending with string, e.g. "738" finds "B738".
CAircraftIcaoCodeList findByDesignatorIataOrFamily(const QString &icaoIataOrFamily) const
Find by ICAO/IATA code or family.
QPair< QString, int > maxCountManufacturer() const
Uses countManufacturers to find "most important" manufacturer.
CAircraftIcaoCodeList findByIataCode(const QString &iata, int fuzzySearch=-1) const
Find by IATA code.
CAircraftIcaoCodeList findByValidDesignator() const
Ones with a valid designator.
QMap< QString, int > countManufacturers() const
Count by manufacturer.
OBJ findByKey(KEYTYPE key, const OBJ &notFound=OBJ()) const
Object with key, notFound otherwise.
bool hasValidDbKey() const
Has valid DB key.
Definition: datastore.h:102
#define SWIFT_DEFINE_SEQUENCE_MIXINS(Namespace, T, List)
Explicit template definition of mixins for a CSequence subclass.
Definition: sequence.h:63