swift
aircraftmatcher.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 
4 #include "core/aircraftmatcher.h"
5 
6 #include <QJSEngine>
7 #include <QList>
8 #include <QPair>
9 #include <QStringBuilder>
10 #include <QStringList>
11 #include <QtGlobal>
12 
13 #include "core/application.h"
14 #include "core/webdataservices.h"
15 #include "core/webdataservicesms.h"
18 #include "misc/aviation/callsign.h"
19 #include "misc/aviation/livery.h"
20 #include "misc/fileutils.h"
21 #include "misc/logcategories.h"
22 #include "misc/logmessage.h"
26 #include "misc/statusmessagelist.h"
27 #include "misc/swiftdirectories.h"
28 
29 using namespace swift::misc;
30 using namespace swift::misc::aviation;
31 using namespace swift::misc::network;
32 using namespace swift::misc::simulation;
33 
34 namespace swift::core
35 {
36  const QStringList &CAircraftMatcher::getLogCategories()
37  {
38  static const QStringList cats { CLogCategories::matching() };
39  return cats;
40  }
41 
42  CAircraftMatcher::CAircraftMatcher(const CAircraftMatcherSetup &setup, QObject *parent)
43  : QObject(parent), m_setup(setup)
44  {
45  if (sApp && sApp->hasWebDataServices())
46  {
47  sApp->getWebDataServices()->synchronizeDbCaches(CEntityFlags::AircraftCategoryEntity);
49  m_categoryMatcher.setCategories(categories);
50  }
51  }
52 
54 
55  CAircraftMatcher::~CAircraftMatcher() { this->saveDisabledForMatchingModels(); }
56 
58  {
59  if (m_setup == setup) { return false; }
60  m_setup = setup;
61  emit this->setupChanged();
62  return true;
63  }
64 
66  const CCallsign &callsign, const QString &primaryIcao, const QString &secondaryIcao, bool airlineFromCallsign,
67  const QString &airlineName, const QString &airlineTelephony, bool useWebServices, CStatusMessageList *log)
68  {
69  CCallsign::addLogDetailsToList(
70  log, callsign,
71  QStringLiteral("Find airline designator from 1st: '%1' 2nd: '%2' callsign: '%3', use airline callsign: %4, "
72  "name: '%5' telephony: '%6' use web service: %7")
73  .arg(primaryIcao, secondaryIcao, callsign.toQString(), boolToYesNo(airlineFromCallsign), airlineName,
74  airlineTelephony, boolToYesNo(useWebServices)),
76  CAirlineIcaoCode code;
77  do {
78  if (CAircraftMatcher::isValidAirlineIcaoDesignator(primaryIcao, useWebServices))
79  {
80  CCallsign::addLogDetailsToList(log, callsign,
81  QStringLiteral("Using primary airline ICAO '%1'").arg(primaryIcao),
83  code = stringToAirlineIcaoObject(callsign, primaryIcao, airlineName, airlineTelephony, useWebServices,
84  log);
85  break;
86  }
87  if (CAircraftMatcher::isValidAirlineIcaoDesignator(secondaryIcao, useWebServices))
88  {
89  CCallsign::addLogDetailsToList(
90  log, callsign,
91  QStringLiteral("Using secondary airline ICAO '%1', primary '%2' not valid")
92  .arg(secondaryIcao, primaryIcao),
94  code = stringToAirlineIcaoObject(callsign, secondaryIcao, airlineName, airlineTelephony, useWebServices,
95  log);
96  break;
97  }
98 
99  CCallsign::addLogDetailsToList(
100  log, callsign,
101  QStringLiteral("Two invalid airline ICAO codes (primary/secondary) '%1', '%2'")
102  .arg(primaryIcao, secondaryIcao),
103  getLogCategories());
104  if (airlineFromCallsign)
105  {
106  QString flightNumber;
107  const QString airlinePrefix = callsign.getAirlinePrefix(flightNumber);
108  if (airlinePrefix.isEmpty() || flightNumber.isEmpty())
109  {
110  CCallsign::addLogDetailsToList(
111  log, callsign,
112  QStringLiteral("Callsign '%1' cannot be split in airline '%1'/ flight number '%2'")
113  .arg(callsign.toQString(), flightNumber),
114  getLogCategories());
115  break;
116  }
117  if (CAircraftMatcher::isValidAirlineIcaoDesignator(airlinePrefix, useWebServices))
118  {
119  CCallsign::addLogDetailsToList(log, callsign,
120  QStringLiteral("Using airline from callsign '%1', suffix: '%2'")
121  .arg(callsign.toQString(), airlinePrefix),
122  getLogCategories());
123  code = stringToAirlineIcaoObject(callsign, airlinePrefix, airlineName, airlineTelephony,
124  useWebServices, log);
125  break;
126  }
127  }
128  }
129  while (false);
130 
131  if (code.hasValidDesignator())
132  {
133  CCallsign::addLogDetailsToList(
134  log, callsign, QStringLiteral("Resolved to airline designator: %1").arg(code.toQString(true)));
135  }
136  else
137  {
138 
139  CCallsign::addLogDetailsToList(
140  log, callsign,
141  QStringLiteral("Cannot find airline designator from 1st: '%1' 2nd: '%2' callsign: '%3', use airline "
142  "callsign: %4, name: '%5' telephony: '%6' use web service: %7")
143  .arg(primaryIcao, secondaryIcao, callsign.toQString(), boolToYesNo(airlineFromCallsign),
144  airlineName, airlineTelephony, boolToYesNo(useWebServices)),
145  getLogCategories());
146  }
147  return code;
148  }
149 
151  CAircraftMatcher::failoverValidAirlineIcaoDesignator(const CCallsign &callsign, const QString &primaryIcao,
152  const QString &secondaryIcao, bool airlineFromCallsign,
153  const QString &airlineName, const QString &airlineTelephony,
154  const CAircraftModelList &models, CStatusMessageList *log)
155  {
156  CCallsign::addLogDetailsToList(
157  log, callsign,
158  QStringLiteral("Find airline designator from 1st: '%1' 2nd: '%2' callsign: '%3', use airline callsign: %4, "
159  "airline name: '%5' telephony: '%6', models: %7")
160  .arg(primaryIcao, secondaryIcao, callsign.toQString(), boolToYesNo(airlineFromCallsign), airlineName,
161  airlineTelephony, models.sizeString()),
162  getLogCategories());
163 
164  if (models.isEmpty())
165  {
166  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("No models to find airline from"));
167  return CAirlineIcaoCode();
168  }
169 
170  static const QString info("Multiple models (%1) with airline ICAOs for '%2'");
171  CAirlineIcaoCode code;
172 
173  do {
174  bool reduced = false;
175  if (!primaryIcao.isEmpty())
176  {
177  CAircraftModelList modelsWithAirline = models.findByIcaoDesignators({}, primaryIcao);
178  const QMap<CAirlineIcaoCode, int> countPerAirline = modelsWithAirline.countPerAirlineIcao();
179  if (countPerAirline.size() == 1)
180  {
181  code = countPerAirline.firstKey();
182  CCallsign::addLogDetailsToList(log, callsign,
183  QStringLiteral("Found only 1 airline ICAO '%1' in %2 models")
184  .arg(countPerAirline.firstKey().getDesignatorDbKey())
185  .arg(models.size()),
186  getLogCategories());
187  break;
188  }
189 
190  if (!modelsWithAirline.isEmpty())
191  {
192  if (modelsWithAirline.size() > 1)
193  {
194  modelsWithAirline = CAircraftMatcher::ifPossibleReduceModelsByAirlineNameTelephonyDesignator(
195  callsign, airlineName, airlineTelephony, modelsWithAirline,
196  info.arg(modelsWithAirline.size()).arg(primaryIcao), reduced, log);
197  }
198  code = modelsWithAirline.getAirlineWithMaxCount();
199  CCallsign::addLogDetailsToList(log, callsign,
200  QStringLiteral("Using primary airline ICAO '%1' found '%2'")
201  .arg(primaryIcao, code.getDesignatorDbKey()),
202  getLogCategories());
203  break;
204  }
205  }
206 
207  if (!secondaryIcao.isEmpty())
208  {
209  CAircraftModelList modelsWithAirline = models.findByIcaoDesignators({}, secondaryIcao);
210  const QMap<CAirlineIcaoCode, int> countPerAirline = modelsWithAirline.countPerAirlineIcao();
211  if (countPerAirline.size() == 1)
212  {
213  code = countPerAirline.firstKey();
214  CCallsign::addLogDetailsToList(log, callsign,
215  QStringLiteral("Found only 1 airline ICAO '%1' in %2 models")
216  .arg(countPerAirline.firstKey().getDesignatorDbKey())
217  .arg(models.size()),
218  getLogCategories());
219  break;
220  }
221 
222  if (!modelsWithAirline.isEmpty())
223  {
224  if (modelsWithAirline.size() > 1)
225  {
226  modelsWithAirline = CAircraftMatcher::ifPossibleReduceModelsByAirlineNameTelephonyDesignator(
227  callsign, airlineName, airlineTelephony, modelsWithAirline,
228  info.arg(modelsWithAirline.size()).arg(secondaryIcao), reduced, log);
229  }
230  code = modelsWithAirline.getAirlineWithMaxCount();
231  CCallsign::addLogDetailsToList(log, callsign,
232  QStringLiteral("Using secondary airline ICAO '%1' found '%2'")
233  .arg(primaryIcao, code.getDesignatorDbKey()),
234  getLogCategories());
235  break;
236  }
237  }
238 
239  if (airlineFromCallsign)
240  {
241  QString flightNumber;
242  const QString airlinePrefix = callsign.getAirlinePrefix(flightNumber);
243  if (airlinePrefix.isEmpty() || flightNumber.isEmpty())
244  {
245  CCallsign::addLogDetailsToList(
246  log, callsign,
247  QStringLiteral("Callsign '%1' cannot be split in airline '%1'/ flight number '%2'")
248  .arg(callsign.toQString(), flightNumber),
249  getLogCategories());
250  break;
251  }
252 
253  CAircraftModelList modelsWithAirline = models.findByIcaoDesignators({}, airlinePrefix);
254  const QMap<CAirlineIcaoCode, int> countPerAirline = modelsWithAirline.countPerAirlineIcao();
255  if (countPerAirline.size() == 1)
256  {
257  code = countPerAirline.firstKey();
258  CCallsign::addLogDetailsToList(log, callsign,
259  QStringLiteral("Found only 1 airline ICAO '%1' in %2 models")
260  .arg(countPerAirline.firstKey().getDesignatorDbKey())
261  .arg(models.size()),
262  getLogCategories());
263  break;
264  }
265  if (!modelsWithAirline.isEmpty())
266  {
267  if (modelsWithAirline.size() > 1)
268  {
269  modelsWithAirline = CAircraftMatcher::ifPossibleReduceModelsByAirlineNameTelephonyDesignator(
270  callsign, airlineName, airlineTelephony, modelsWithAirline,
271  info.arg(modelsWithAirline.size()).arg(airlinePrefix), reduced, log);
272  }
273  code = modelsWithAirline.getAirlineWithMaxCount();
274  CCallsign::addLogDetailsToList(log, callsign,
275  QStringLiteral("Using callsign airline ICAO '%1' found '%2'")
276  .arg(airlinePrefix, code.getDesignatorDbKey()),
277  getLogCategories());
278  break;
279  }
280  }
281  }
282  while (false);
283 
284  // return message
285  if (code.hasValidDesignator())
286  {
287  CCallsign::addLogDetailsToList(
288  log, callsign, QStringLiteral("Resolved to airline designator: %1").arg(code.toQString(true)));
289  }
290  else
291  {
292  CCallsign::addLogDetailsToList(
293  log, callsign,
294  QStringLiteral("Cannot find airline designator from 1st: '%1' 2nd: '%2' callsign: '%3', use airline "
295  "callsign: %4, airline name: '%5' telephony: '%6', models: %7")
296  .arg(primaryIcao, secondaryIcao, callsign.toQString(), boolToYesNo(airlineFromCallsign),
297  airlineName, airlineTelephony, models.sizeString()),
298  getLogCategories());
299  }
300  return code;
301  }
302 
304  const CCallsign &callsign, const QString &primaryIcao, const QString &secondaryIcao, bool airlineFromCallsign,
305  const QString &airlineName, const QString &airlineTelephony, const CAircraftModelList &models,
306  CStatusMessageList *log)
307  {
308  if (!models.isEmpty())
309  {
310  CCallsign::addLogDetailsToList(
311  log, callsign, QStringLiteral("Using %1 models to resolve airline designator").arg(models.size()));
313  callsign, primaryIcao, secondaryIcao, airlineFromCallsign, airlineName, airlineTelephony, models, log);
314  if (airline.hasValidDbKey()) { return airline; }
315  }
316  CCallsign::addLogDetailsToList(log, callsign,
317  QStringLiteral("Now using resolution of airline ICAO without specific models"));
319  callsign, primaryIcao, secondaryIcao, airlineFromCallsign, airlineName, airlineTelephony, true, log);
320  }
321 
322  CAircraftModel CAircraftMatcher::getClosestMatch(const CSimulatedAircraft &remoteAircraft, MatchingLog whatToLog,
323  CStatusMessageList *log, bool useMatchingScript) const
324  {
325  CAircraftModelList modelSet(m_modelSet); // Models for this matching
326  const CAircraftMatcherSetup setup = m_setup;
327 
328  static const QString format("hh:mm:ss.zzz");
329  static const QString m1("--- Start matching: UTC %1 ---");
330  static const QString m2("Input model: '%1' '%2'");
331  static const QString m3("Matching uses model set of %1 models\n%2");
332  static const QString m4("Setup %1");
333  static const QString summary("Matching summary\n"
334  "-----------------------------------------\n"
335  "- Combined: %1 -> %2\n"
336  "- Aircraft: %3 -> %4\n"
337  "- Airline: %5 -> %6\n"
338  "- Livery: %7 -> %8\n"
339  "- Model: %9 -> %10\n"
340  "- Script modifed value: %11\n"
341  "-----------------------------------------\n");
342 
343  const QDateTime startTime = QDateTime::currentDateTimeUtc();
344  if (whatToLog == MatchingLogNothing) { log = nullptr; }
345  if (log) { log->clear(); }
346 
347  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, m1.arg(startTime.toString(format)));
349  log, remoteAircraft,
350  m2.arg(remoteAircraft.getCallsignAsString(),
351  removeSurroundingApostrophes(remoteAircraft.getModel().toQString())));
353  log, remoteAircraft,
354  m3.arg(modelSet.size()).arg(modelSet.coverageSummaryForModel(remoteAircraft.getModel())));
355  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, m4.arg(setup.toQString(true)));
356 
357  // Before I really search I check some special conditions
358  // 1) Manually set model (by user)
359  // 2) No model set at all
360  // 3) Exact match by model string
361 
362  // Manually set string?
363  CAircraftModel matchedModel;
364  bool resolvedInPrephase = false;
365  if (remoteAircraft.getModel().hasManuallySetString())
366  {
367  // the user did a manual mapping "by hand", so he really should know what he is doing
368  // no matching
370  log, remoteAircraft, u"Manually set model " % remoteAircraft.getModelString(), getLogCategories());
371  matchedModel = remoteAircraft.getModel();
372  resolvedInPrephase = true;
373  }
374  else if (modelSet.isEmpty())
375  {
376  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
377  QStringLiteral("No models for matching, using default"),
379  matchedModel = this->getDefaultModel();
380  resolvedInPrephase = true;
381  }
382  else if (remoteAircraft.hasModelString())
383  {
384  // try to find in installed models by model string
386  {
387  matchedModel = matchByExactModelString(remoteAircraft, modelSet, whatToLog, log);
388  if (matchedModel.hasModelString())
389  {
390  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
391  u"Exact match by model string '" %
392  matchedModel.getModelStringAndDbKey() % "'",
394  resolvedInPrephase = true;
395  }
396  }
397  else
398  {
399  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, "Skipping model string match",
400  getLogCategories());
401  }
402  }
403 
404  if (!resolvedInPrephase)
405  {
406  // sanity
407  const int noString = modelSet.removeAllWithoutModelString();
408  static const QString noModelStr("Excluded %1 models without model string");
409  if (noString > 0 && log)
410  {
411  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, noModelStr.arg(noString));
412  }
413 
414  // exclusion
415  if (setup.getMatchingMode().testFlag(CAircraftMatcherSetup::ExcludeNoDbData))
416  {
417  const int noDbKey = modelSet.removeObjectsWithoutDbKey();
418  static const QString excludedStr("Excluded %1 models without DB key");
419  if (noDbKey > 0 && log)
420  {
421  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, excludedStr.arg(noDbKey));
422  }
423  }
424 
425  if (setup.getMatchingMode().testFlag(CAircraftMatcherSetup::ExcludeNoExcluded))
426  {
427  const int excluded = modelSet.removeIfExcluded();
428  static const QString excludedStr("Excluded %1 models marked 'Excluded'");
429  if (excluded > 0 && log)
430  {
431  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, excludedStr.arg(excluded));
432  }
433  }
434 
435  // Reduce by ICAO if the flag is set
436  static const QString msInfo("Using '%1' with model set with %2 models");
437  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
438  msInfo.arg(setup.getMatchingAlgorithmAsString()).arg(modelSet.size()),
439  getLogCategories());
440  CAircraftModelList candidates;
441  int maxScore = -1;
442 
443  switch (setup.getMatchingAlgorithm())
444  {
445  case CAircraftMatcherSetup::MatchingStepwiseReduce:
446  candidates = CAircraftMatcher::getClosestMatchStepwiseReduceImplementation(
447  modelSet, setup, m_categoryMatcher, remoteAircraft, whatToLog, log);
448  break;
449  case CAircraftMatcherSetup::MatchingScoreBased:
450  candidates = CAircraftMatcher::getClosestMatchScoreImplementation(modelSet, setup, remoteAircraft,
451  maxScore, whatToLog, log);
452  break;
453  case CAircraftMatcherSetup::MatchingStepwiseReducePlusScoreBased:
454  default:
455  candidates = CAircraftMatcher::getClosestMatchStepwiseReduceImplementation(
456  modelSet, setup, m_categoryMatcher, remoteAircraft, whatToLog, log);
457  candidates = CAircraftMatcher::getClosestMatchScoreImplementation(candidates, setup, remoteAircraft,
458  maxScore, whatToLog, log);
459  break;
460  }
461 
462  if (candidates.isEmpty())
463  {
464  matchedModel = CAircraftMatcher::getCombinedTypeDefaultModel(modelSet, remoteAircraft,
465  this->getDefaultModel(), whatToLog, log);
466  }
467  else
468  {
470  switch (usedStrategy)
471  {
472  case CAircraftMatcherSetup::PickRandom:
473  matchedModel = candidates.randomElement<CAircraftModel>();
474  break;
475  case CAircraftMatcherSetup::PickByOrder:
476  if (!candidates.needsOrder())
477  {
478  matchedModel = candidates.minOrderOrDefault();
479  break;
480  }
481  [[fallthrough]];
482  case CAircraftMatcherSetup::PickFirst: // fallthru intentionally
483  default:
484  usedStrategy = CAircraftMatcherSetup::PickFirst; // re-assigned if fall-through
485  matchedModel = candidates.front();
486  break;
487  }
488 
489  if (log)
490  {
492  log, remoteAircraft,
493  QStringLiteral("Picking among %1 by strategy '%2'")
494  .arg(candidates.size())
495  .arg(CAircraftMatcherSetup::strategyToString(usedStrategy)));
496  }
497  }
498  }
499 
500  // copy over callsign validate
501  matchedModel.setCallsign(remoteAircraft.getCallsign());
503 
504  // matching script
505  bool didRunAndModifyMatchingScript = false;
506  if (useMatchingScript && setup.doRunMsMatchingStageScript())
507  {
508  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
509  QStringLiteral("Matching script: Matching stage script used"));
510  const MatchingScriptReturnValues rv =
511  CAircraftMatcher::matchingStageScript(remoteAircraft.getModel(), matchedModel, setup, modelSet, log);
512  CAircraftModel matchedModelMs = matchedModel;
513 
514  if (rv.runScriptAndModified())
515  {
516  matchedModelMs = rv.model;
517  didRunAndModifyMatchingScript = true;
518  }
519 
520  if (rv.runScriptModifiedAndRerun())
521  {
523  log, remoteAircraft, QStringLiteral("Matching script: Modified values and re-run requested"));
525  log, remoteAircraft,
526  QStringLiteral("Matching script: Now using model: '%1'").arg(matchedModel.toQString(true)));
527 
528  CSimulatedAircraft rerunAircraft(remoteAircraft);
529  rerunAircraft.setModel(matchedModelMs);
530  CStatusMessageList log2ndRun;
531  matchedModelMs =
532  CAircraftMatcher::getClosestMatch(rerunAircraft, whatToLog, log ? &log2ndRun : nullptr, false);
533  if (log) { log->push_back(log2ndRun); }
534 
535  // the script can fuckup the model, leading to an empty model string or such
536  matchedModelMs.setCallsign(remoteAircraft.getCallsign());
537  if (matchedModelMs.hasModelString()) { matchedModel = matchedModelMs; }
538  else
539  {
541  log, remoteAircraft,
542  QStringLiteral(
543  "Matching script: Ignoring model without model string after running the script"));
544  }
545  }
546  }
547  else
548  {
549  if (log)
550  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
551  QStringLiteral("Matching script: No matching stage script used"));
552  }
553 
554  // copy over callsign validate (again, just in case it was changed in matching script)
555  matchedModel.setCallsign(remoteAircraft.getCallsign());
557 
558  // reported here by LT: https://discordapp.com/channels/539048679160676382/539925070550794240/701439918815051846
559  if (!matchedModel.hasModelString())
560  {
561  if (log)
562  {
563  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
564  QStringLiteral("All matching yielded no result, VERY odd..."));
565  }
566  CAircraftModel defaultModel = this->getDefaultModel();
567  if (defaultModel.hasModelString())
568  {
569  matchedModel = defaultModel;
570  matchedModel.setCallsign(remoteAircraft.getCallsign());
571  if (log)
572  {
574  log, remoteAircraft,
575  QStringLiteral("Using default model '%1'").arg(matchedModel.getModelString()));
576  }
577  }
578  else
579  {
580  if (log)
581  {
583  log, remoteAircraft,
584  QStringLiteral("Not even a default model. Giving up").arg(matchedModel.getModelString()));
585  }
586  }
587  }
588 
589  Q_ASSERT_X(!matchedModel.getCallsign().isEmpty(), Q_FUNC_INFO, "Missing callsign for matched model");
590  // Q_ASSERT_X(matchedModel.hasModelString(), Q_FUNC_INFO, "Missing model string for matched model");
591 
592  if (log)
593  {
594  static const QString nms = "no model string";
596  log, remoteAircraft,
597  summary
598  .arg(remoteAircraft.getAircraftIcaoCode().getCombinedType(),
599  matchedModel.getAircraftIcaoCode().getCombinedType(),
600  remoteAircraft.getAircraftIcaoCode().getDesignatorDbKey(),
601  matchedModel.getAircraftIcaoCode().getDesignatorDbKey(),
602  remoteAircraft.getAirlineIcaoCode().getVDesignatorDbKey(),
603  matchedModel.getAirlineIcaoCode().getVDesignatorDbKey())
604  .arg(remoteAircraft.getLivery().getCombinedCodePlusInfoAndId(),
605  matchedModel.getLivery().getCombinedCodePlusInfoAndId(),
606  defaultIfEmpty(remoteAircraft.getModel().getModelStringAndDbKey(), nms),
607  matchedModel.getModelStringAndDbKey(), boolToYesNo(didRunAndModifyMatchingScript)));
608  } // log
609 
610  const QDateTime endTime = QDateTime::currentDateTimeUtc();
611  const qint64 matchingTime = startTime.msecsTo(endTime);
612  static const QString em("--- Matching end: UTC %1, time %2ms ---");
613  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, em.arg(endTime.toString(format)).arg(matchingTime));
614  return matchedModel;
615  }
616 
618  CAircraftMatcher::reverseLookupModel(const CCallsign &callsign, const CAircraftIcaoCode &networkAircraftIcao,
619  const CAirlineIcaoCode &networkAirlineIcao, const QString &networkLiveryInfo,
620  const QString &networkModelString, const CAircraftMatcherSetup &setup,
621  const CAircraftModelList &modelSet, CAircraftModel::ModelType type,
622  CStatusMessageList *log)
623  {
624  Q_UNUSED(setup)
625 
626  CLivery livery;
627  livery.setAirlineIcaoCode(networkAirlineIcao);
628 
629  CAircraftModel model(networkModelString, type, {}, networkAircraftIcao, livery);
630  model.setCallsign(callsign);
631  model = CAircraftMatcher::reverseLookupModel(model, networkLiveryInfo, setup, modelSet, log);
632  model.setModelType(CAircraftModel::TypeReverseLookup);
633 
634  return model;
635  }
636 
638  const CAircraftMatcherSetup &setup,
639  const CAircraftModelList &modelSet,
640  CStatusMessageList *log)
641  {
642  if (!setup.doRunMsReverseLookupScript()) { return MatchingScriptReturnValues(inModel); }
643  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return inModel; }
644  const QString js = CFileUtils::readFileToString(setup.getMsReverseLookupFile());
645  const MatchingScriptReturnValues rv =
646  CAircraftMatcher::matchingScript(js, inModel, inModel, setup, modelSet, ReverseLookup, log);
647  return rv;
648  }
649 
651  const CAircraftModel &matchedModel,
652  const CAircraftMatcherSetup &setup,
653  const CAircraftModelList &modelSet,
654  CStatusMessageList *log)
655  {
656  if (!setup.doRunMsMatchingStageScript()) { return MatchingScriptReturnValues(inModel); }
657  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return inModel; }
658  const QString js = CFileUtils::readFileToString(setup.getMsMatchingStageFile());
659  const MatchingScriptReturnValues rv =
660  CAircraftMatcher::matchingScript(js, inModel, matchedModel, setup, modelSet, MatchingStage, log);
661  return rv;
662  }
663 
665  const CAircraftModel &matchedModel,
666  const CAircraftMatcherSetup &setup,
667  const CAircraftModelList &modelSet,
668  MatchingScript script, CStatusMessageList *log)
669  {
670  MatchingScriptReturnValues rv(inModel);
671  QString logMessage;
672  const CCallsign callsign = inModel.getCallsign();
673 
674  if (js.isEmpty() && log)
675  {
676  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Matching script is empty"));
677  }
678 
679  while (!js.isEmpty() && sApp && sApp->hasWebDataServices())
680  {
681  rv.runScript = true;
682 
683  // matching script
684  const bool msReverse = (script == ReverseLookup);
685  const QString lf = msReverse ? setup.getMsReverseLookupFile() : setup.getMsMatchingStageFile();
686  static const QString logFileR =
687  CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), "logMatchingScriptReverseLookup.log");
688  static const QString logFileM =
689  CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), "logMatchingScriptMatchingStage.log");
690 
691  if (log)
692  {
693  CCallsign::addLogDetailsToList(
694  log, callsign, QStringLiteral("Matching script (%1): '%2'").arg(msToString(script), lf));
695  CCallsign::addLogDetailsToList(
696  log, callsign,
697  QStringLiteral("Matching script input model (%1): '%2'").arg(inModel.toQString(true)));
698  CCallsign::addLogDetailsToList(
699  log, callsign, QStringLiteral("Matching script models: %1").arg(modelSet.coverageSummary()));
700  }
701 
702  QJSEngine engine;
703  // engine.installExtensions(QJSEngine::ConsoleExtension);
704 
705  // Meta objects to create new JS objects, here causing JSValue can't be reassigned to another engine.
706  // static const QJSValue jsInOutMetaObject = engine.newQMetaObject(&MSInOutValues::staticMetaObject);
707  // engine.globalObject().setProperty("SwiftInOutObject", jsInOutMetaObject); // JS: new SwiftInOutObject();
708  // static const QJSValue jsSetMetaObject = engine.newQMetaObject(&MSModelSet::staticMetaObject);
709  // engine.globalObject().setProperty("SwiftModelSet", jsSetMetaObject); // JS: new SwiftModelSet
710  // static const QJSValue jsWebServicesMetaObject = engine.newQMetaObject(&MSWebServices::staticMetaObject);
711  // engine.globalObject().setProperty("SwiftWebServices", jsWebServicesMetaObject); // JS: new
712  // SwiftWebServices
713 
714  // init models and set
715  MSInOutValues inObject(inModel);
716  MSInOutValues matchedObject(matchedModel); // same as inModel for reverse lookup
717  matchedObject.evaluateChanges(inModel.getAircraftIcaoCode(), inModel.getAirlineIcaoCode());
718  MSInOutValues outObject(matchedModel); // set default values for out object
719  MSModelSet modelSetObject(modelSet); // as passed
720  modelSetObject.initByAircraftAndAirline(inModel.getAircraftIcaoCode(), inModel.getAirlineIcaoCode());
721  MSWebServices webServices; // web services encapsulated
722 
723  // object as from network
724  const QJSValue jsInObject = engine.newQObject(&inObject);
725  engine.globalObject().setProperty("inObject", jsInObject);
726 
727  // object that will be returned
728  const QJSValue jsOutObject = engine.newQObject(&outObject);
729  engine.globalObject().setProperty("outObject", jsOutObject);
730 
731  // object as matched so far, same as inObject in reverse lookup
732  const QJSValue jsMatchedObject = engine.newQObject(&matchedObject);
733  engine.globalObject().setProperty("matchedObject", jsMatchedObject);
734 
735  // wrapper for model set
736  const QJSValue jsModelSetObject = engine.newQObject(&modelSetObject);
737  engine.globalObject().setProperty("modelSet", jsModelSetObject);
738 
739  // wrapper for web services
740  const QJSValue jsWebServices = engine.newQObject(&webServices);
741  engine.globalObject().setProperty("webServices", jsWebServices);
742 
743  QJSValue ms = engine.evaluate(js, msReverse ? logFileR : logFileM);
744  ms = ms.call();
745  if (ms.isError())
746  {
747  const QString msg = QStringLiteral("Matching script error: %1 '%2'")
748  .arg(ms.property("lineNumber").toInt())
749  .arg(ms.toString());
750  CLogMessage(static_cast<CAircraftMatcher *>(nullptr)).warning(msg);
751  if (log) { CCallsign::addLogDetailsToList(log, callsign, msg); }
752  }
753  else
754  {
755  if (ms.isQObject())
756  {
757  const MSInOutValues *reverseModelProcessed = qobject_cast<const MSInOutValues *>(ms.toQObject());
758  logMessage = reverseModelProcessed->getLogMessage();
759  if (!reverseModelProcessed->isModified()) { break; }
760 
761  // rerun
762  rv.rerun = reverseModelProcessed->isRerun();
763 
764  // changed model by model id?
765  if (reverseModelProcessed->hasChangedModelId(inModel))
766  {
767  const CAircraftModel model =
768  sApp->getWebDataServices()->getModelForDbKey(reverseModelProcessed->getDbModelId());
769  if (model.hasValidDbKey())
770  {
771  // found full model from DB
772  rv.model = model;
773  rv.modified = true;
774  break;
775  }
776  }
777 
778  // changed model by model string?
779  if (reverseModelProcessed->hasChangedModelString(inModel.getModelString()))
780  {
781  const QString modelString = reverseModelProcessed->getModelString();
782  const CAircraftModel model = sApp->getWebDataServices()->getModelForModelString(modelString);
783  if (model.hasValidDbKey())
784  {
785  // found full model from DB
786  rv.model = model;
787  rv.modified = true;
788  break;
789  }
790 
791  // search for model string in set, even if it is not in the DB
792  const CAircraftModel modeSetModel =
793  CAircraftMatcher::reverseLookupModelStringInSet(modelString, callsign, modelSet, true, log);
794  if (modeSetModel.hasModelString())
795  {
796  CCallsign::addLogDetailsToList(
797  log, callsign,
798  QStringLiteral("Matching script using model from set: '%1'").arg(modelString));
799 
800  // NON DB model from model set
801  rv.model = modeSetModel;
802  rv.modified = true;
803  break;
804  }
805  }
806 
807  // changed aircraft ICAO
808  if (reverseModelProcessed->hasChangedAircraftIcao(matchedModel.getAircraftIcaoCode()))
809  {
810  CAircraftIcaoCode icao(reverseModelProcessed->getAircraftIcao());
811  if (reverseModelProcessed->hasChangedAircraftIcaoId(matchedModel.getAircraftIcaoCode()))
812  {
814  reverseModelProcessed->getDbAircraftIcaoId());
815  }
816  rv.modified = true;
817  rv.model.setAircraftIcaoCode(icao);
818  }
819 
820  if (reverseModelProcessed->hasChangedLiveryId(matchedModel.getLivery()))
821  {
822  const CLivery livery(
823  sApp->getWebDataServices()->getLiveryForDbKey(reverseModelProcessed->getDbLiveryId()));
824  rv.model.setLivery(livery);
825  rv.modified = true;
826  }
827  else if (reverseModelProcessed->hasChangedAirlineIcao(matchedModel.getAirlineIcaoCode()))
828  {
829  CAirlineIcaoCode icao;
830  if (reverseModelProcessed->hasChangedAirlineIcaoId(matchedModel.getAirlineIcaoCode()))
831  {
833  reverseModelProcessed->getDbAirlineIcaoId());
834  }
835  else
836  {
838  reverseModelProcessed->getAirlineIcao(), true);
839  }
840 
842  rv.model.setLivery(livery);
843  if (log)
844  {
845  CCallsign::addLogDetailsToList(
846  log, callsign,
847  QStringLiteral("Matching script, changed airline ICAO: '%1' -> '%2'")
848  .arg(matchedModel.getAirlineIcaoCode().toQString(true), icao.toQString(true)));
849  }
850  rv.modified = true;
851  }
852  }
853  else if (ms.isString()) { logMessage = ms.toString(); }
854  }
855 
856  // end this
857  break;
858  }
859 
860  // log message
861  if (log && !logMessage.isEmpty())
862  {
863  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Matching script log: '%1'").arg(logMessage));
864  }
865 
866  // end
867  return rv;
868  }
869 
871  const QString &networkLiveryInfo,
872  const CAircraftMatcherSetup &setup,
873  const CAircraftModelList &modelSet, CStatusMessageList *log)
874  {
875  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
876 
877  // already DB model?
878  CAircraftModel model(modelToLookup); // copy
879  if (modelToLookup.isLoadedFromDb()) { return modelToLookup; }
880 
881  // --- now I try to fill in as many DB data as possible ---
882  // 1) This will unify data where possible
883  // 2) I have full information of what the other pilot flies where possible
884  // 3) This is not model matching here (!), it is a process of getting the most accurate data from that fuzzy
885  // information I get via FSD
886  //
887  // Reverse lookup, use DB data wherever possible
888  // 1) If I cannot resolve the ICAO codes here, they are either wrong (most likely in most cases) or
889  // 2) not in the DB yet
890  const CCallsign callsign(model.getCallsign());
891 
892  do {
893  if (modelToLookup.hasModelString())
894  {
895  if (!setup.isReverseLookupModelString())
896  {
897  if (log)
898  {
899  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Model string looup disabled"));
900  }
901  }
902  else
903  {
904  const QString modelString = modelToLookup.getModelString();
905 
906  // if we find the model here we have a fully defined DB model
908  modelString, callsign, setup.isReverseLookupModelString(), log);
909  if (modelFromDb.hasValidDbKey())
910  {
911  model = modelFromDb;
912  break; // done here
913  }
914 
915  // const bool useNonDbEntries = setup.isDbDataOnly();
916  const bool useNonDbEntries = true;
918  modelString, callsign, modelSet, useNonDbEntries, log);
919  if (modelFromSet.hasModelString())
920  {
921  model = modelFromSet;
922  break; // done here
923  }
924  }
925  }
926 
927  // lookup if model is not yet from DB
928  const DBTripleIds ids = CAircraftModel::parseNetworkLiveryString(networkLiveryInfo);
929  if (log)
930  {
931  CCallsign::addLogDetailsToList(log, callsign,
932  QStringLiteral("Livery string with ids: '%1'").arg(ids.toQString()));
933  }
934 
935  if (!setup.isReverseLookupSwiftLiveryIds())
936  {
937  if (log)
938  {
939  CCallsign::addLogDetailsToList(
940  log, callsign,
941  QStringLiteral("Ignoring livery ids '%1', because of setup").arg(ids.toQString()));
942  }
943  }
944  else if (ids.model >= 0 && !modelToLookup.hasModelString())
945  {
946  if (log)
947  {
948  CCallsign::addLogDetailsToList(log, callsign,
949  QStringLiteral("Model lookup with id %1 from triple ids '%2'")
950  .arg(ids.model)
951  .arg(ids.toQString()));
952  }
953  const CAircraftModel modelFromDb = CAircraftMatcher::reverseLookupModelId(ids.model, callsign, log);
954  if (modelFromDb.hasValidDbKey())
955  {
956  model = modelFromDb;
957  break;
958  }
959  }
960 
961  // no direct resolution of model, try livery and aircraft ICAO
962  if (!modelToLookup.getAircraftIcaoCode().hasValidDbKey() && ids.aircraft >= 0)
963  {
964  if (log)
965  {
966  CCallsign::addLogDetailsToList(
967  log, callsign,
968  QStringLiteral("Aircraft ICAO lookup with id %1 from triple ids '%2'")
969  .arg(ids.aircraft)
970  .arg(ids.toQString()));
971  }
972  const CAircraftIcaoCode icaoFromDb =
974  if (icaoFromDb.hasValidDbKey()) { model.setAircraftIcaoCode(icaoFromDb); }
975  }
976 
977  if (!modelToLookup.getLivery().hasValidDbKey() && ids.livery >= 0)
978  {
979  if (log)
980  {
981  CCallsign::addLogDetailsToList(log, callsign,
982  QStringLiteral("Livery lookup with id %1 from triple ids '%2'")
983  .arg(ids.livery)
984  .arg(ids.toQString()));
985  }
986  const CLivery liveryFromDb = CAircraftMatcher::reverseLookupLiveryId(ids.livery, callsign, log);
987  if (liveryFromDb.hasValidDbKey()) { model.setLivery(liveryFromDb); }
988  }
989 
990  // aircraft ICAO if not from DB yet
991  if (!model.getAircraftIcaoCode().hasValidDbKey())
992  {
993  CAircraftIcaoCode reverseIcaoCode(model.getAircraftIcaoCode());
994  if (!reverseIcaoCode.isLoadedFromDb())
995  {
996  reverseIcaoCode = CAircraftMatcher::reverseLookupAircraftIcao(reverseIcaoCode, callsign, log);
997  if (reverseIcaoCode.isLoadedFromDb())
998  {
999  if (log)
1000  {
1001  CCallsign::addLogDetailsToList(log, callsign,
1002  QStringLiteral("Set aircraft ICAO to '%1' from DB")
1003  .arg(reverseIcaoCode.getCombinedIcaoStringWithKey()));
1004  }
1005  model.setAircraftIcaoCode(reverseIcaoCode);
1006  }
1007  else
1008  {
1009  // no DB data
1010  if (log)
1011  {
1012  CCallsign::addLogDetailsToList(
1013  log, callsign,
1014  QStringLiteral("Reverse lookup, ICAO '%1' not resolved from DB")
1015  .arg(reverseIcaoCode.getDesignator()));
1016  }
1017  }
1018  }
1019  }
1020 
1021  // check if livery is already from DB
1022  if (!model.getLivery().isLoadedFromDb())
1023  {
1024  CAirlineIcaoCode airlineIcaoCode(model.getAirlineIcaoCode());
1025  if (!airlineIcaoCode.isLoadedFromDb())
1026  {
1027  airlineIcaoCode = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcaoCode, callsign, log);
1028  }
1029 
1030  // try to match by livery
1031  QString liveryCode = networkLiveryInfo;
1032  if (liveryCode.isEmpty() && airlineIcaoCode.hasValidDesignator())
1033  {
1034  // we create a standard livery code, then we try to find based on this
1035  liveryCode = CLivery::getStandardCode(airlineIcaoCode);
1036  }
1037 
1038  if (CLivery::isValidCombinedCode(liveryCode))
1039  {
1040  // search livery by combined code
1041  const CLivery reverseLivery(sApp->getWebDataServices()->getLiveryForCombinedCode(liveryCode));
1042  if (reverseLivery.hasValidDbKey())
1043  {
1044  // we have found a livery in the DB
1045  model.setLivery(reverseLivery);
1046  if (log)
1047  {
1048  CCallsign::addLogDetailsToList(log, callsign,
1049  QStringLiteral("Reverse lookup of livery found '%1'")
1050  .arg(reverseLivery.getCombinedCodePlusInfoAndId()),
1051  getLogCategories());
1052  }
1053  }
1054  else
1055  {
1056  // no livery data found
1057  if (log)
1058  {
1059  CCallsign::addLogDetailsToList(
1060  log, callsign,
1061  QStringLiteral("Reverse lookup of livery '%1' yielded no result")
1062  .arg(reverseLivery.getCombinedCodePlusInfo()),
1063  getLogCategories());
1064  }
1065  }
1066  } // livery lookup
1067 
1068  // if no DB livery yet, create own livery
1069  if (!model.hasValidDbKey() && !model.getLivery().hasValidDbKey())
1070  {
1071  if (airlineIcaoCode.hasValidDesignator())
1072  {
1073  if (airlineIcaoCode.hasValidDbKey())
1074  {
1075  const CLivery stdLivery(
1076  sApp->getWebDataServices()->getStdLiveryForAirlineCode(airlineIcaoCode));
1077  if (stdLivery.hasValidDbKey())
1078  {
1079  model.setLivery(stdLivery);
1080  if (log)
1081  {
1082  CCallsign::addLogDetailsToList(log, callsign,
1083  QStringLiteral("Set standardlivery `%1`")
1084  .arg(stdLivery.getCombinedCodePlusInfo()));
1085  }
1086  }
1087  }
1088 
1089  if (!model.getLivery().hasValidDbKey())
1090  {
1091  // create a pseudo livery, try to find airline first
1092  const CLivery liveryDummy(CLivery::getStandardCode(airlineIcaoCode), airlineIcaoCode,
1093  "Generated");
1094  model.setLivery(liveryDummy);
1095  if (log)
1096  {
1097  CCallsign::addLogDetailsToList(log, callsign,
1098  QStringLiteral("Generated livery, set livery `%1`")
1099  .arg(liveryDummy.getCombinedCodePlusInfo()));
1100  }
1101  }
1102  }
1103  } // pseudo livery
1104  } // livery from DB
1105  }
1106  while (false);
1107 
1108  model.setCallsign(callsign);
1109  model.setModelType(modelToLookup.getModelType());
1110 
1111  if (log)
1112  {
1113  CCallsign::addLogDetailsToList(log, callsign,
1114  QStringLiteral("Using model: ICAO '%1', livery '%2', model '%3', type '%4'")
1117  model.getModelTypeAsString()));
1118  }
1119  return model;
1120  }
1121 
1123  const QString &networkLiveryInfo,
1124  const CAircraftMatcherSetup &setup,
1125  const CAircraftModelList &modelSet, CStatusMessageList *log)
1126  {
1127  CAircraftModel reverseModel = reverseLookupModel(modelToLookup, networkLiveryInfo, setup, modelSet, log);
1128  if (!setup.doRunMsReverseLookupScript()) { return reverseModel; }
1129  const CCallsign cs = modelToLookup.getCallsign();
1130  const MatchingScriptReturnValues rv = CAircraftMatcher::reverseLookupScript(reverseModel, setup, modelSet, log);
1131  if (rv.runScriptModifiedAndRerun())
1132  {
1133  CCallsign::addLogDetailsToList(log, cs,
1134  QStringLiteral("Matching script: Modified value and requested rerun"));
1135 
1136  // no script the 2nd time
1137  CAircraftMatcherSetup setupRerun(setup);
1138  setupRerun.resetReverseLookup();
1139  reverseModel = CAircraftMatcher::reverseLookupModel(rv.model, networkLiveryInfo, setupRerun, modelSet, log);
1140  return reverseModel;
1141  }
1142  return (rv.runScriptAndModified() ? rv.model : reverseModel);
1143  }
1144 
1145  CAircraftModel CAircraftMatcher::reverseLookupModelStringInDB(const QString &modelString, const CCallsign &callsign,
1146  bool doLookupString, CStatusMessageList *log)
1147  {
1148  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
1149  if (!doLookupString)
1150  {
1151  if (log)
1152  {
1153  CCallsign::addLogDetailsToList(
1154  log, callsign,
1155  QStringLiteral("Ignore model string in reverse lookup (disabled), ignoring '%1'").arg(modelString));
1156  }
1157  return CAircraftModel();
1158  }
1160  const bool isDBModel = model.hasValidDbKey();
1161  if (log)
1162  {
1163  if (isDBModel)
1164  {
1165  CCallsign::addLogDetailsToList(
1166  log, callsign,
1167  QStringLiteral("Found model in DB for model string '%1' dist: '%2' descr.: '%3'")
1168  .arg(model.getModelStringAndDbKey(), model.getDistributor().getDbKey(),
1169  model.getDescription()));
1170  }
1171  else
1172  {
1173  CCallsign::addLogDetailsToList(
1174  log, callsign, QStringLiteral("Did not find model in DB for model string '%1'").arg(modelString));
1175  }
1176  }
1177 
1178  if (!isDBModel) { return CAircraftModel(); } // not found
1179 
1180  // found
1181  model.setCallsign(callsign);
1183  return model;
1184  }
1185 
1187  const CCallsign &callsign,
1188  const CAircraftModelList &modelSet,
1189  bool useNonDbEntries, CStatusMessageList *log)
1190  {
1191  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
1192  if (modelString.isEmpty())
1193  {
1194  if (log)
1195  {
1196  CCallsign::addLogDetailsToList(
1197  log, callsign, QStringLiteral("Empty model string for lookup in %1 models").arg(modelSet.size()));
1198  }
1199  return CAircraftModel();
1200  }
1201  if (modelSet.isEmpty())
1202  {
1203  if (log)
1204  {
1205  CCallsign::addLogDetailsToList(log, callsign,
1206  QStringLiteral("Empty models, ignoring '%1'").arg(modelString));
1207  }
1208  return CAircraftModel();
1209  }
1210 
1211  CAircraftModel model = modelSet.findFirstByModelStringOrDefault(modelString, Qt::CaseInsensitive);
1212  if (!model.hasModelString())
1213  {
1214  if (log)
1215  {
1216  CCallsign::addLogDetailsToList(
1217  log, callsign,
1218  QStringLiteral("Model '%1' not found in %2 models").arg(modelString).arg(modelSet.size()));
1219  }
1220  return CAircraftModel();
1221  }
1222 
1223  const bool isDBModel = model.hasValidDbKey();
1224  if (log)
1225  {
1226  if (isDBModel)
1227  {
1228  CCallsign::addLogDetailsToList(log, callsign,
1229  QStringLiteral("Found DB model in %1 models for model string '%2'")
1230  .arg(modelSet.size())
1231  .arg(model.getModelStringAndDbKey()));
1232  }
1233  else
1234  {
1235  CCallsign::addLogDetailsToList(log, callsign,
1236  QStringLiteral("Found NON DB model in %1 models for model string '%2'")
1237  .arg(modelSet.size())
1238  .arg(model.getModelString()));
1239  }
1240  }
1241 
1242  if (!isDBModel && !useNonDbEntries) { return CAircraftModel(); } // ignore DB entries
1243 
1244  // found
1245  model.setCallsign(callsign);
1247  return model;
1248  }
1249 
1251  {
1252  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
1254  if (log)
1255  {
1256  if (model.hasValidDbKey())
1257  {
1258  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Found model in DB for id '%1'").arg(id));
1259  }
1260  else
1261  {
1262  CCallsign::addLogDetailsToList(log, callsign,
1263  QStringLiteral("Did not find model in DB for id '%1'").arg(id));
1264  }
1265  }
1266  model.setCallsign(callsign);
1268  return model;
1269  }
1270 
1272  const CCallsign &logCallsign, CStatusMessageList *log)
1273  {
1274  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftIcaoCode(); }
1275 
1276  const QString designator(icaoCandidate.getDesignator());
1278 
1279  if (foundIcaos.isEmpty())
1280  {
1281  CAircraftIcaoCode icao(designator);
1282 
1283  // sometimes from network we receive something like "CESSNA C172"
1284  if (CAircraftIcaoCode::isValidDesignator(designator))
1285  {
1286  CCallsign::addLogDetailsToList(
1287  log, logCallsign,
1288  QStringLiteral("Reverse lookup of aircraft ICAO '%1' did not find anything, using smart search")
1289  .arg(designator),
1291  icao = sApp->getWebDataServices()->smartAircraftIcaoSelector(icaoCandidate);
1292  }
1293  else
1294  {
1295  CCallsign::addLogDetailsToList(
1296  log, logCallsign,
1297  QStringLiteral("Reverse lookup of invalid ICAO code '%1' did not find anything so far")
1298  .arg(designator),
1300  const QStringList parts(designator.split(' '));
1301  for (const QString &p : parts)
1302  {
1303  CCallsign::addLogDetailsToList(
1304  log, logCallsign,
1305  QStringLiteral("Trying parts, now reverse lookup of aircraft ICAO '%1' using smart search")
1306  .arg(p),
1309  if (icao.isLoadedFromDb()) break;
1310  }
1311  }
1312  if (icao.isLoadedFromDb())
1313  {
1314  // smart search found DB data
1315  foundIcaos = CAircraftIcaoCodeList({ icao });
1316  }
1317  else
1318  {
1319  CCallsign::addLogDetailsToList(log, logCallsign,
1320  QStringLiteral("No DB data for ICAO '%1', valid ICAO?").arg(designator),
1322  return CAircraftIcaoCode(icaoCandidate);
1323  }
1324  }
1325 
1326  if (foundIcaos.isEmpty())
1327  {
1328  CCallsign::addLogDetailsToList(
1329  log, logCallsign, QStringLiteral("Reverse lookup of aircraft ICAO '%1', nothing found").arg(designator),
1331  return CAircraftIcaoCode(icaoCandidate);
1332  }
1333  else if (foundIcaos.size() == 1)
1334  {
1335  const CAircraftIcaoCode icao(foundIcaos.front());
1336  CCallsign::addLogDetailsToList(
1337  log, logCallsign,
1338  QStringLiteral("Reverse lookup of aircraft ICAO '%1', found one manufacturer '%2' in DB")
1339  .arg(designator, icao.getDesignatorManufacturer()),
1341  return icao;
1342  }
1343  else
1344  {
1345  // multiple ICAOs
1346  Q_ASSERT_X(foundIcaos.size() > 1, Q_FUNC_INFO, "Wrong size");
1347  const QPair<QString, int> maxManufacturer = foundIcaos.maxCountManufacturer();
1348  CCallsign::addLogDetailsToList(
1349  log, logCallsign,
1350  QStringLiteral("Reverse lookup of aircraft ICAO '%1', found %2 values (ambiguous): %3")
1351  .arg(designator)
1352  .arg(foundIcaos.size())
1353  .arg(foundIcaos.dbKeysAsString(", ")),
1355  if (maxManufacturer.second < foundIcaos.size())
1356  {
1357  foundIcaos = foundIcaos.findByManufacturer(maxManufacturer.first);
1358  CCallsign::addLogDetailsToList(log, logCallsign,
1359  QStringLiteral("Reducing by manufacturer '%1', now %2 values")
1360  .arg(maxManufacturer.first)
1361  .arg(foundIcaos.size()),
1363  }
1364  foundIcaos.sortByRank();
1365  const CAircraftIcaoCode icao = foundIcaos.front(); // best rank
1366  CCallsign::addLogDetailsToList(
1367  log, logCallsign,
1368  QStringLiteral("Reverse lookup of aircraft ICAO '%1', using ICAO '%2' with rank %3")
1369  .arg(designator, icao.toQString(), icao.getRankString()),
1371  return icao;
1372  }
1373  }
1374 
1376  CStatusMessageList *log)
1377  {
1378  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftIcaoCode(); }
1380  if (log)
1381  {
1382  if (icao.hasValidDbKey())
1383  {
1384  CCallsign::addLogDetailsToList(log, logCallsign,
1385  QStringLiteral("Found aircraft ICAO in DB for id '%1'").arg(id));
1386  }
1387  else
1388  {
1389  CCallsign::addLogDetailsToList(log, logCallsign,
1390  QStringLiteral("Did not find aircraft ICAO in DB for id '%1'").arg(id));
1391  }
1392  }
1393  return icao;
1394  }
1395 
1397  const CCallsign &callsign, CStatusMessageList *log)
1398  {
1399  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAirlineIcaoCode(); }
1400  const CAirlineIcaoCode icao = sApp->getWebDataServices()->smartAirlineIcaoSelector(icaoPattern, callsign);
1401  if (log)
1402  {
1403  if (icao.hasValidDbKey())
1404  {
1405  CCallsign::addLogDetailsToList(
1406  log, callsign,
1407  QStringLiteral("Reverse lookup of airline ICAO '%1' and callsign '%2' found '%3' '%4' in DB")
1408  .arg(icaoPattern.getDesignator(), callsign.asString(), icao.getVDesignatorDbKey(),
1409  icao.getName()),
1411  }
1412  else
1413  {
1414  CCallsign::addLogDetailsToList(
1415  log, callsign,
1416  QStringLiteral("Reverse lookup of airline ICAO '%1' and callsign '%2', nothing found in DB")
1417  .arg(icaoPattern.getDesignator(), callsign.asString()),
1419  }
1420  }
1421  return icao;
1422  }
1423 
1425  CStatusMessageList *log)
1426  {
1427  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CLivery(); }
1428  if (!airline.hasValidDesignator())
1429  {
1430  if (log)
1431  {
1432  CCallsign::addLogDetailsToList(
1433  log, callsign, QStringLiteral("Reverse lookup of standard livery skipped, no airline designator"),
1435  }
1436  return CLivery();
1437  }
1438 
1439  const CLivery livery = sApp->getWebDataServices()->getStdLiveryForAirlineCode(airline);
1440  if (log)
1441  {
1442  if (livery.hasValidDbKey())
1443  {
1444  CCallsign::addLogDetailsToList(log, callsign,
1445  QStringLiteral("Reverse lookup of standard livery for '%1' found '%2'")
1446  .arg(airline.getDesignator(), livery.getCombinedCode()),
1448  }
1449  else
1450  {
1451  CCallsign::addLogDetailsToList(
1452  log, callsign,
1453  QStringLiteral("Not standard livery for airline '%1' in DB").arg(airline.getDesignator()),
1455  }
1456  }
1457  return livery;
1458  }
1459 
1461  {
1462  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CLivery(); }
1463  const CLivery livery = sApp->getWebDataServices()->getLiveryForDbKey(id);
1464  if (log)
1465  {
1466  if (livery.hasValidDbKey())
1467  {
1468  CCallsign::addLogDetailsToList(log, logCallsign,
1469  QStringLiteral("Found livery in DB for id '%1'").arg(id));
1470  }
1471  else
1472  {
1473  CCallsign::addLogDetailsToList(log, logCallsign,
1474  QStringLiteral("Did not find livery in DB for id '%1'").arg(id));
1475  }
1476  }
1477  return livery;
1478  }
1479 
1481  const CCallsign &logCallsign, CStatusMessageList *log)
1482  {
1483  int found = 0;
1484  if (ids.livery >= 0)
1485  {
1486  livery = CAircraftMatcher::reverseLookupLiveryId(ids.livery, logCallsign, log);
1487  if (livery.hasValidDbKey()) { found++; }
1488  }
1489 
1490  if (ids.aircraft >= 0)
1491  {
1492  aircraftIcao = CAircraftMatcher::reverseLookupAircraftIcaoId(ids.aircraft, logCallsign, log);
1493  if (aircraftIcao.hasValidDbKey()) { found++; }
1494  }
1495  return found;
1496  }
1497 
1498  QString CAircraftMatcher::reverseLookupAirlineName(const QString &candidate, const CCallsign &callsign,
1499  CStatusMessageList *log)
1500  {
1501  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return {}; }
1502  if (candidate.isEmpty()) { return {}; }
1503  const QStringList names = sApp->getWebDataServices()->getAirlineNames();
1504  if (names.contains(candidate, Qt::CaseInsensitive))
1505  {
1506  CCallsign::addLogDetailsToList(log, callsign,
1507  QStringLiteral("Airline name '%1' found in DB").arg(candidate));
1508  return candidate;
1509  }
1510 
1511  CCallsign::addLogDetailsToList(log, callsign,
1512  QStringLiteral("Airline name '%1' not found in DB").arg(candidate));
1513  return {};
1514  }
1515 
1516  QString CAircraftMatcher::reverseLookupTelephonyDesignator(const QString &candidate, const CCallsign &callsign,
1517  CStatusMessageList *log)
1518  {
1519  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return {}; }
1520  if (candidate.isEmpty()) { return {}; }
1521  const QStringList designators = sApp->getWebDataServices()->getTelephonyDesignators();
1522  if (designators.contains(candidate, Qt::CaseInsensitive))
1523  {
1524  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Airline name '%1' found").arg(candidate));
1525  return candidate;
1526  }
1527 
1528  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Airline name '%1' not found").arg(candidate));
1529  return {};
1530  }
1531 
1532  bool CAircraftMatcher::isKnownAircraftDesignator(const QString &candidate, const CCallsign &callsign,
1533  CStatusMessageList *log)
1534  {
1535  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return false; }
1536  if (!CAircraftIcaoCode::isValidDesignator(candidate))
1537  {
1538  CCallsign::addLogDetailsToList(log, callsign,
1539  QStringLiteral("No valid ICAO designator '%1'").arg(candidate));
1540  return false;
1541  }
1542 
1543  const bool known = sApp->getWebDataServices()->containsAircraftIcaoDesignator(candidate);
1544  static const QString sKnown("Known ICAO designator '%1'");
1545  static const QString sUnknown("Unknown ICAO designator '%1'");
1546  CCallsign::addLogDetailsToList(log, callsign, known ? sKnown.arg(candidate) : sUnknown.arg(candidate));
1547  return known;
1548  }
1549 
1550  bool CAircraftMatcher::isKnownModelString(const QString &candidate, const CCallsign &callsign,
1551  CStatusMessageList *log)
1552  {
1553  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return false; }
1554  const bool known = sApp->getWebDataServices()->containsModelString(candidate);
1555  static const QString sKnown("Known modelstring '%1'");
1556  static const QString sUnknown("Unknown modelstring '%1'");
1557  CCallsign::addLogDetailsToList(log, callsign, known ? sKnown.arg(candidate) : sUnknown.arg(candidate));
1558  return known;
1559  }
1560 
1562  const CAirlineIcaoCode &airline,
1563  const CCallsign &callsign, CStatusMessageList *log)
1564  {
1565  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftIcaoCode(); }
1566  if (!airline.isLoadedFromDb())
1567  {
1568  CCallsign::addLogDetailsToList(
1569  log, callsign, QStringLiteral("No valid airline from DB '%1'").arg(airline.getDesignator()));
1570  return CAircraftIcaoCode();
1571  }
1572 
1573  Q_ASSERT_X(sApp->getWebDataServices(), Q_FUNC_INFO, "No web services");
1574 
1576  if (aircraft.isEmpty())
1577  {
1578  CCallsign::addLogDetailsToList(
1579  log, callsign, QStringLiteral("No aircraft known for airline '%1'").arg(airline.getDesignator()));
1580  return CAircraftIcaoCode();
1581  }
1582 
1583  const QSet<QString> allIcaos = aircraft.allDesignators();
1584  const QString allIcaosStr = allIcaos.values().join(", ");
1585  CCallsign::addLogDetailsToList(
1586  log, callsign,
1587  QStringLiteral("Aircraft '%1' known for airline '%2'").arg(allIcaosStr, airline.getDesignator()));
1588 
1589  const CAircraftIcaoCode code = aircraft.findBestFuzzyMatchOrDefault(candidateString);
1590  if (code.hasValidDesignator())
1591  {
1592  CCallsign::addLogDetailsToList(log, callsign,
1593  QStringLiteral("Aircraft '%1' is best fuzzy search of '%2' for airline '%3'")
1594  .arg(code.toQString(), candidateString, airline.getDesignator()));
1595  return code;
1596  }
1597 
1598  return aircraft.front();
1599  }
1600 
1602  {
1603  if (callsign.isEmpty() || !sApp || !sApp->getWebDataServices()) { return CAirlineIcaoCode(); }
1605 
1606  if (icao.hasValidDesignator())
1607  {
1608  CCallsign::addLogDetailsToList(
1609  log, callsign,
1610  QStringLiteral("Turned callsign %1 into airline %2").arg(callsign.asString(), icao.getDesignator()),
1611  getLogCategories());
1612  }
1613  else
1614  {
1615  CCallsign::addLogDetailsToList(
1616  log, callsign, QStringLiteral("Cannot turn callsign %1 into airline").arg(callsign.asString()),
1617  getLogCategories());
1618  }
1619  return icao;
1620  }
1621 
1622  int CAircraftMatcher::setModelSet(const CAircraftModelList &models, const CSimulatorInfo &simulator, bool forced)
1623  {
1624  if (!simulator.isSingleSimulator()) { return 0; }
1625 
1626  CAircraftModelList modelsCleaned(models);
1627  const int r1 = modelsCleaned.removeAllWithoutModelString();
1628  const int r2 = modelsCleaned.removeIfExcluded();
1629 
1630  // sane simulator and model count? (minor risk of not updating when a model was changed)
1631  if (!forced && m_simulator == simulator && m_modelSet.size() == modelsCleaned.size())
1632  {
1633  return m_modelSet.size();
1634  }
1635 
1636  QString warnings;
1637  if ((r1 + r2) > 0)
1638  {
1639  warnings =
1640  QStringLiteral("Removed models for matcher, without string #: %1, excluded #: %2.").arg(r1).arg(r2);
1641  if (r1 > 0)
1642  {
1643  warnings += QStringLiteral(" Without string: '%1'.")
1644  .arg(models.findEmptyModelStrings().getModelStringList().join(", "));
1645  }
1646  if (r2 > 0)
1647  {
1648  warnings += QStringLiteral(" Excluded: '%1'.")
1649  .arg(models.findByModelMode(CAircraftModel::Exclude).getModelStringList().join(", "));
1650  }
1651  }
1652  const CAircraftModelList duplicateModels = modelsCleaned.findDuplicateModelStrings();
1653 
1654  // Warning info
1655  if (modelsCleaned.isEmpty())
1656  {
1657  // error to force popup
1658  CLogMessage(this).error(u"No models for matching ('%1'), swift without a model set will not work!")
1659  << simulator.toQString();
1660  }
1661  else if (!duplicateModels.isEmpty())
1662  {
1663  CLogMessage(this).error(u"Found model duplicate strings, check models: '%1'")
1664  << duplicateModels.dbKeysAsString(", ");
1665  }
1666  else if (!warnings.isEmpty()) { CLogMessage(this).validationWarning(warnings); }
1667  else
1668  {
1669  CLogMessage(this).validationInfo(u"Set %1 models in matcher, simulator '%2'")
1670  << modelsCleaned.size() << simulator.toQString();
1671  }
1672 
1673  // set values
1674  m_modelSet = modelsCleaned;
1675  m_simulator = simulator;
1676  m_modelSetInfo = QStringLiteral("Set: '%1' entries: %2").arg(simulator.toQString()).arg(modelsCleaned.size());
1677  return models.size();
1678  }
1679 
1680  void CAircraftMatcher::disableModelsForMatching(const CAircraftModelList &removedModels, bool incremental)
1681  {
1682  if (incremental)
1683  {
1684  m_modelSet.removeModelsWithString(removedModels, Qt::CaseInsensitive);
1685  m_disabledModels.push_back(removedModels);
1686  }
1687  else
1688  {
1689  this->restoreDisabledModels();
1690  m_disabledModels = removedModels;
1691  m_modelSet.removeModelsWithString(removedModels, Qt::CaseInsensitive);
1692  }
1693  }
1694 
1696  {
1697  m_modelSet.replaceOrAddModelsWithString(m_disabledModels, Qt::CaseInsensitive);
1698  }
1699 
1701  {
1702  m_defaultModel = defaultModel;
1704  }
1705 
1706  void CAircraftMatcher::evaluateStatisticsEntry(const QString &sessionId, const CCallsign &callsign,
1707  const QString &aircraftIcao, const QString &airlineIcao,
1708  const QString &livery)
1709  {
1710  Q_UNUSED(livery)
1711  Q_ASSERT_X(sApp && sApp->hasWebDataServices(), Q_FUNC_INFO, "Missing web data services");
1712  if (m_modelSet.isEmpty()) { return; } // ignore empty sets to not create silly stats
1713  if (sessionId.isEmpty()) { return; }
1714  if (aircraftIcao.isEmpty()) { return; }
1715 
1716  QString description;
1718  {
1719  description = QStringLiteral("ICAO: '%1' not known, typo?").arg(aircraftIcao);
1720  }
1721 
1722  // resolve airline, mostly needed because of vPilot not sending airline ICAO codes in version 1
1723  CAirlineIcaoCode airlineIcaoChecked(airlineIcao);
1724  if (airlineIcao.isEmpty())
1725  {
1726  const CAirlineIcaoCode al = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcao, callsign);
1727  if (al.isLoadedFromDb()) { airlineIcaoChecked = al; }
1728  }
1729 
1730  CMatchingStatisticsEntry::EntryType type = CMatchingStatisticsEntry::Missing;
1731  if (airlineIcaoChecked.hasValidDesignator())
1732  {
1733  type = m_modelSet.containsModelsWithAircraftAndAirlineIcaoDesignator(aircraftIcao, airlineIcao) ?
1734  CMatchingStatisticsEntry::Found :
1735  CMatchingStatisticsEntry::Missing;
1736  }
1737  else
1738  {
1739  type = m_modelSet.containsModelsWithAircraftAndAirlineIcaoDesignator(aircraftIcao, airlineIcao) ?
1740  CMatchingStatisticsEntry::Found :
1741  CMatchingStatisticsEntry::Missing;
1742  }
1743  m_statistics.addAircraftAirlineCombination(type, sessionId, m_modelSetInfo, description, aircraftIcao,
1744  airlineIcao);
1745  }
1746 
1748  {
1749  if (!m_setup.removeFromSetIfFailed()) { return; }
1750  if (remoteAircraft.hasCallsign() && remoteAircraft.hasModelString())
1751  {
1752  const QString modelString = remoteAircraft.getModelString();
1753  const CAircraftModelList disabledModels({ remoteAircraft.getModel() });
1754  this->disableModelsForMatching(disabledModels, true);
1755  CLogMessage(this).warning(u"Disabled CS: '%1' model '%2' for matching")
1756  << remoteAircraft.getCallsignAsString() << modelString;
1757  }
1758  else { CLogMessage(this).warning(u"Disabled '%1' for matching") << remoteAircraft.toQString(true); }
1759  }
1760 
1761  bool CAircraftMatcher::saveDisabledForMatchingModels()
1762  {
1763  if (m_disabledModels.isEmpty()) { return false; }
1764 
1765  // log the models
1766  const QString ts = QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss");
1767  const QString json = m_disabledModels.toJsonString();
1770  QStringLiteral("removed models %1.json").arg(ts)));
1771  }
1772 
1773  CAircraftModelList CAircraftMatcher::getClosestMatchStepwiseReduceImplementation(
1774  const CAircraftModelList &modelSet, const CAircraftMatcherSetup &setup, const CCategoryMatcher &categoryMatcher,
1775  const CSimulatedAircraft &remoteAircraft, MatchingLog whatToLog, CStatusMessageList *log)
1776  {
1777  CAircraftModelList matchedModels(modelSet);
1778  CAircraftModel matchedModel(remoteAircraft.getModel());
1779  Q_UNUSED(whatToLog)
1780 
1781  const CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
1782  CStatusMessageList *reduceLog = log && whatToLog.testFlag(MatchingLogStepwiseReduce) ? log : nullptr;
1783  bool reduced = false;
1784  do {
1785  // by livery, then by ICAO
1786  if (mode.testFlag(CAircraftMatcherSetup::ByLivery))
1787  {
1788  matchedModels =
1789  ifPossibleReduceByLiveryAndAircraftIcaoCode(remoteAircraft, matchedModels, reduced, log);
1790  if (reduced) { break; } // almost perfect, we stop here (we have ICAO + livery match)
1791  }
1792  else if (reduceLog)
1793  {
1794  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1795  QStringLiteral("Skipping livery reduction"), getLogCategories());
1796  }
1797 
1798  if (setup.getMatchingMode().testFlag(CAircraftMatcherSetup::ByIcaoData))
1799  {
1800  // by airline/aircraft or by aircraft/airline depending on setup
1801  // family is also considered
1802  matchedModels = ifPossibleReduceByIcaoData(remoteAircraft, matchedModels, setup, reduced, log);
1803  }
1804  else if (reduceLog)
1805  {
1806  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1807  QStringLiteral("Skipping ICAO reduction"), getLogCategories());
1808 
1809  // family only because aircraft ICAO is not used
1810  if (mode.testFlag(CAircraftMatcherSetup::ByFamily))
1811  {
1812  QString usedFamily;
1813  matchedModels = ifPossibleReduceByFamily(remoteAircraft, UsePseudoFamily, matchedModels, reduced,
1814  usedFamily, log);
1815  if (reduced) { break; }
1816  }
1817  else if (reduceLog)
1818  {
1819  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1820  QStringLiteral("Skipping family match"), getLogCategories());
1821  }
1822  }
1823 
1824  if (setup.useCategoryMatching())
1825  {
1826  matchedModels = categoryMatcher.reduceByCategories(matchedModels, modelSet, setup, remoteAircraft,
1827  reduced, whatToLog, log);
1828  // ?? break here ??
1829  }
1830  else if (reduceLog)
1831  {
1832  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1833  QStringLiteral("category matching disabled"), getLogCategories());
1834  }
1835 
1836  // if not yet reduced, reduce to VTOL
1837  if (!reduced && remoteAircraft.isVtol() && matchedModels.containsVtol() &&
1838  mode.testFlag(CAircraftMatcherSetup::ByVtol))
1839  {
1840  matchedModels = matchedModels.findByVtolFlag(true);
1842  log, remoteAircraft, QStringLiteral("Aircraft is VTOL, reduced to VTOL"), getLogCategories());
1843  }
1844 
1845  // military / civilian
1846  bool milFlagReduced = false;
1847  if (mode.testFlag(CAircraftMatcherSetup::ByMilitary) && remoteAircraft.isMilitary())
1848  {
1849  matchedModels = ifPossibleReduceByMilitaryFlag(remoteAircraft, matchedModels, reduced, reduceLog);
1850  milFlagReduced = true;
1851  }
1852 
1853  if (!milFlagReduced && mode.testFlag(CAircraftMatcherSetup::ByCivilian) && !remoteAircraft.isMilitary())
1854  {
1855  matchedModels = ifPossibleReduceByMilitaryFlag(remoteAircraft, matchedModels, reduced, reduceLog);
1856  milFlagReduced = true;
1857  }
1858 
1859  // combined code
1860  if (mode.testFlag(CAircraftMatcherSetup::ByCombinedType))
1861  {
1862  matchedModels =
1863  ifPossibleReduceByCombinedType(remoteAircraft, matchedModels, setup, reduced, reduceLog);
1864  if (reduced) { break; }
1865  }
1866  else if (log)
1867  {
1868  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("Skipping combined code match"),
1869  getLogCategories());
1870  }
1871  }
1872  while (false);
1873 
1874  // here we have a list of possible models, we reduce/refine further
1875  if (matchedModels.size() > 1 && mode.testFlag(CAircraftMatcherSetup::ByManufacturer))
1876  {
1877  matchedModels = ifPossibleReduceByManufacturer(remoteAircraft, matchedModels,
1878  QStringLiteral("2nd trial to reduce by manufacturer. "),
1879  reduced, reduceLog);
1880  }
1881 
1882  return matchedModels;
1883  }
1884 
1885  CAircraftModelList CAircraftMatcher::getClosestMatchScoreImplementation(const CAircraftModelList &modelSet,
1886  const CAircraftMatcherSetup &setup,
1887  const CSimulatedAircraft &remoteAircraft,
1888  int &maxScore, MatchingLog whatToLog,
1889  CStatusMessageList *log)
1890  {
1891  CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
1892  const bool noZeroScores = mode.testFlag(CAircraftMatcherSetup::ScoreIgnoreZeros);
1893  const bool preferColorLiveries = mode.testFlag(CAircraftMatcherSetup::ScorePreferColorLiveries);
1894  CStatusMessageList *scoreLog = log && whatToLog.testFlag(MatchingLogScoring) ? log : nullptr;
1895 
1896  // VTOL
1897  ScoredModels map;
1898  map = modelSet.scoreFull(remoteAircraft.getModel(), preferColorLiveries, noZeroScores, scoreLog);
1899 
1900  CAircraftModel matchedModel;
1901  if (map.isEmpty()) { return CAircraftModelList(); }
1902 
1903  maxScore = map.lastKey();
1904  const CAircraftModelList maxScoreAircraft(map.values(maxScore));
1905  CMatchingUtils::addLogDetailsToList(scoreLog, remoteAircraft,
1906  QStringLiteral("Scores: %1").arg(scoresToString(map)), getLogCategories());
1907  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
1908  QStringLiteral("Scoring with score %1 out of %2 models yielded %3 models")
1909  .arg(maxScore)
1910  .arg(map.size())
1911  .arg(maxScoreAircraft.size()),
1912  getLogCategories());
1913  return maxScoreAircraft;
1914  }
1915 
1916  CAircraftModel CAircraftMatcher::getCombinedTypeDefaultModel(const CAircraftModelList &modelSet,
1917  const CSimulatedAircraft &remoteAircraft,
1918  const CAircraftModel &defaultModel,
1919  MatchingLog whatToLog, CStatusMessageList *log)
1920  {
1921  const QString combinedType = remoteAircraft.getAircraftIcaoCombinedType();
1922  CStatusMessageList *combinedLog = log && whatToLog.testFlag(MatchingLogCombinedDefaultType) ? log : nullptr;
1923 
1924  if (combinedType.isEmpty())
1925  {
1926  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft,
1927  QStringLiteral("No combined type, using default"), getLogCategories(),
1929  return defaultModel;
1930  }
1931  if (modelSet.isEmpty())
1932  {
1933  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft, QStringLiteral("No models, using default"),
1935  return defaultModel;
1936  }
1937 
1938  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft,
1939  u"Searching by combined type with color livery '" % combinedType % "'",
1940  getLogCategories());
1941  CAircraftModelList matchedModels = modelSet.findByCombinedTypeWithColorLivery(combinedType);
1942  if (!matchedModels.isEmpty())
1943  {
1944  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft,
1945  u"Found " % QString::number(matchedModels.size()) %
1946  u" by combined type w/color livery '" % combinedType % "'",
1947  getLogCategories());
1948  }
1949  else
1950  {
1952  combinedLog, remoteAircraft, u"Searching by combined type '" % combinedType % "'", getLogCategories());
1953  matchedModels = matchedModels.findByCombinedType(combinedType);
1954  if (!matchedModels.isEmpty())
1955  {
1956  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
1957  u"Found " % QString::number(matchedModels.size()) %
1958  u" by combined '" % combinedType % "'",
1959  getLogCategories());
1960  }
1961  }
1962 
1963  // return
1964  if (matchedModels.isEmpty()) { return defaultModel; }
1965  return matchedModels.front();
1966  }
1967 
1968  CAircraftModel CAircraftMatcher::matchByExactModelString(const CSimulatedAircraft &remoteAircraft,
1969  const CAircraftModelList &models, MatchingLog whatToLog,
1970  CStatusMessageList *log)
1971  {
1972  CStatusMessageList *msLog = log && whatToLog.testFlag(MatchingLogModelstring) ? log : nullptr;
1973  if (remoteAircraft.getModelString().isEmpty())
1974  {
1975  if (msLog)
1976  {
1977  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
1978  QStringLiteral("No model string, no exact match possible"));
1979  }
1980  return CAircraftModel();
1981  }
1982 
1983  CAircraftModel model = models.findFirstByModelStringAliasOrDefault(remoteAircraft.getModelString());
1984  if (msLog)
1985  {
1986  if (model.hasModelString())
1987  {
1989  msLog, remoteAircraft, QStringLiteral("Found exact match for '%1'").arg(model.getModelString()));
1990  }
1991  else
1992  {
1994  msLog, remoteAircraft,
1995  QStringLiteral("No exact match for '%1'").arg(remoteAircraft.getModelString()));
1996  }
1997  }
1999  model.setCallsign(remoteAircraft.getCallsign());
2000  return model;
2001  }
2002 
2004  CAircraftMatcher::ifPossibleReduceByLiveryAndAircraftIcaoCode(const CSimulatedAircraft &remoteAircraft,
2005  const CAircraftModelList &inList, bool &reduced,
2006  CStatusMessageList *log)
2007  {
2008  reduced = false;
2009  if (!remoteAircraft.getLivery().hasCombinedCode())
2010  {
2011  if (log)
2012  {
2014  log, remoteAircraft, QStringLiteral("No livery code, no reduction possible"), getLogCategories());
2015  }
2016  return inList;
2017  }
2018 
2020  remoteAircraft.getLivery().getCombinedCode(), remoteAircraft.getAircraftIcaoCodeDesignator()));
2021 
2022  if (byLivery.isEmpty())
2023  {
2024  if (log)
2025  {
2027  log, remoteAircraft, u"Not found by livery code " % remoteAircraft.getLivery().getCombinedCode(),
2028  getLogCategories());
2029  }
2030  return inList;
2031  }
2032  reduced = true;
2033  return byLivery;
2034  }
2035 
2036  CAircraftModelList CAircraftMatcher::ifPossibleReduceByIcaoData(const CSimulatedAircraft &remoteAircraft,
2037  const CAircraftModelList &inList,
2038  const CAircraftMatcherSetup &setup, bool &reduced,
2039  CStatusMessageList *log)
2040  {
2041  const CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
2042  if (inList.isEmpty())
2043  {
2044  if (log)
2045  {
2046  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("Empty list, skipping step"),
2047  getLogCategories());
2048  }
2049  return inList;
2050  }
2051 
2052  reduced = false;
2053  if (mode.testFlag(CAircraftMatcherSetup::ByIcaoOrderAirlineFirst))
2054  {
2055  bool r1 = false;
2056  bool r2 = false;
2057  CAircraftModelList models = ifPossibleReduceByAirline(remoteAircraft, inList, setup,
2058  QStringLiteral("Reduce by airline first."), r1, log);
2059  models = ifPossibleReduceByAircraftOrFamily(remoteAircraft, UsePseudoFamily, models, setup,
2060  QStringLiteral("Reduce by aircraft ICAO second."), r2, log);
2061  reduced = r1 || r2;
2062  if (reduced) { return models; }
2063  }
2064  else if (mode.testFlag(CAircraftMatcherSetup::ByIcaoData))
2065  {
2066  bool r1 = false;
2067  bool r2 = false;
2068  CAircraftModelList models =
2069  ifPossibleReduceByAircraftOrFamily(remoteAircraft, UsePseudoFamily, inList, setup,
2070  QStringLiteral("Reduce by aircraft ICAO first."), r1, log);
2071  models = ifPossibleReduceByAirline(remoteAircraft, models, setup,
2072  QStringLiteral("Reduce aircraft ICAO by airline second."), r2, log);
2073 
2074  // not finding anything so far means we have no valid aircraft/airline ICAO combination
2075  // but it can happen we found B738, and for DLH there is no B738 but B737, so we search again
2076  if (!remoteAircraft.hasAirlineDesignator())
2077  {
2078  if (log)
2079  {
2080  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2081  "No airline, no secondary search for airline/aircraft",
2082  getLogCategories());
2083  }
2084  }
2085  else if (!r2 && mode.testFlag(CAircraftMatcherSetup::ByFamily))
2086  {
2087  if (log)
2088  {
2089  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2090  u"No exact ICAO match of '" %
2091  remoteAircraft.getAirlineAndAircraftIcaoCodeDesignators() %
2092  u"', will try family combination",
2093  getLogCategories());
2094  }
2095 
2096  bool r3 = false;
2097  QString usedFamily;
2098  CAircraftModelList models2nd =
2099  ifPossibleReduceByFamily(remoteAircraft, UsePseudoFamily, inList, r3, usedFamily, log);
2100  models2nd = ifPossibleReduceByAirline(remoteAircraft, models2nd, setup,
2101  "Reduce family by airline second.", r3, log);
2102  if (r3)
2103  {
2104  // we found family / airline combination
2105  if (log)
2106  {
2107  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2108  u"Found " % QString::number(models2nd.sizeInt()) %
2109  " aircraft family/airline '" % usedFamily %
2110  u"' combination",
2111  getLogCategories());
2112  }
2113  return models2nd;
2114  }
2115  }
2116 
2117  reduced = r1 || r2;
2118  if (reduced)
2119  {
2120  if (log)
2121  {
2122  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2123  u"Reduced by aircraft ICAO: " % boolToYesNo(r1) %
2124  u" airline: " % boolToYesNo(r2),
2125  getLogCategories());
2126  }
2127  return models;
2128  }
2129  }
2130 
2131  if (log)
2132  {
2133  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("No reduction by ICAO data"),
2134  getLogCategories());
2135  }
2136  return inList;
2137  }
2138 
2139  CAircraftModelList CAircraftMatcher::ifPossibleReduceByFamily(const CSimulatedAircraft &remoteAircraft,
2140  bool allowPseudoFamily,
2141  const CAircraftModelList &inList, bool &reduced,
2142  QString &usedFamily, CStatusMessageList *log)
2143  {
2144  reduced = false;
2145  usedFamily = remoteAircraft.getAircraftIcaoCode().getFamily();
2146  if (!usedFamily.isEmpty())
2147  {
2148  CAircraftModelList matchedModels =
2149  ifPossibleReduceByFamily(remoteAircraft, usedFamily, allowPseudoFamily, inList,
2150  QStringLiteral("real family from ICAO"), reduced, log);
2151  if (reduced) { return matchedModels; }
2152  }
2153 
2154  // scenario: the ICAO actually is the family
2155  usedFamily = remoteAircraft.getAircraftIcaoCodeDesignator();
2156  return ifPossibleReduceByFamily(remoteAircraft, usedFamily, allowPseudoFamily, inList,
2157  QStringLiteral("ICAO treated as family"), reduced, log);
2158  }
2159 
2160  CAircraftModelList CAircraftMatcher::ifPossibleReduceByFamily(const CSimulatedAircraft &remoteAircraft,
2161  const QString &family, bool allowPseudoFamily,
2162  const CAircraftModelList &inList, const QString &hint,
2163  bool &reduced, CStatusMessageList *log)
2164  {
2165  // Use an algorithm to find the best match
2166  reduced = false;
2167  if (family.isEmpty() && !allowPseudoFamily)
2168  {
2169  if (log)
2170  {
2171  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"No family, skipping step (" % hint % u")",
2172  getLogCategories());
2173  }
2174  return inList;
2175  }
2176 
2177  if (inList.isEmpty())
2178  {
2179  if (log)
2180  {
2181  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"No models for family match (" % hint % u")",
2182  getLogCategories());
2183  }
2184  return inList;
2185  }
2186 
2187  CAircraftModelList foundByFamily(inList.findByFamily(family));
2188  if (foundByFamily.isEmpty())
2189  {
2190  if (log)
2191  {
2192  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2193  u"Not found by family '" % family % u"' (" % hint % ")");
2194  }
2195  if (!allowPseudoFamily) { return inList; }
2196  // fallthru
2197  }
2198  else
2199  {
2200  if (log)
2201  {
2202  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2203  u"Found by family '" % family % u"' (" % hint % u") size " %
2204  QString::number(foundByFamily.sizeInt()),
2205  getLogCategories());
2206  }
2207  }
2208 
2209  CAircraftModelList foundByCM;
2210  if (allowPseudoFamily)
2211  {
2212  foundByCM = inList.findByCombinedAndManufacturer(remoteAircraft.getAircraftIcaoCode());
2213  const QString pseudo = remoteAircraft.getAircraftIcaoCode().getCombinedType() % "/" %
2214  remoteAircraft.getAircraftIcaoCode().getManufacturer();
2215  if (foundByCM.isEmpty())
2216  {
2217  if (log)
2218  {
2219  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2220  u"Not found by pseudo family '" % pseudo % u"' (" % hint % ")");
2221  }
2222  }
2223  else
2224  {
2225  if (log)
2226  {
2227  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2228  u"Found by pseudo family '" % pseudo % u"' (" % hint %
2229  u") size " % QString::number(foundByCM.sizeInt()),
2230  getLogCategories());
2231  }
2232  }
2233  }
2234 
2235  if (foundByCM.isEmpty() && foundByFamily.isEmpty()) { return inList; }
2236  reduced = true;
2237 
2238  // avoid dpulicates, then add
2239  if (!foundByFamily.isEmpty())
2240  {
2241  foundByCM.removeModelsWithString(foundByFamily.getModelStringList(), Qt::CaseInsensitive);
2242  }
2243  foundByFamily.push_back(foundByCM);
2244 
2245  if (log)
2246  {
2247  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2248  u"Found by family (totally) '" % family % u"' (" % hint % u") size " %
2249  QString::number(foundByFamily.sizeInt()),
2250  getLogCategories());
2251  }
2252  return foundByFamily;
2253  }
2254 
2255  CAircraftModelList CAircraftMatcher::ifPossibleReduceByManufacturer(const CSimulatedAircraft &remoteAircraft,
2256  const CAircraftModelList &inList,
2257  const QString &info, bool &reduced,
2258  CStatusMessageList *log)
2259  {
2260  reduced = false;
2261  if (inList.isEmpty())
2262  {
2263  if (log)
2264  {
2265  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, info % u" Empty input list, cannot reduce",
2266  getLogCategories());
2267  }
2268  return inList;
2269  }
2270 
2271  const QString m = remoteAircraft.getAircraftIcaoCode().getManufacturer();
2272  if (m.isEmpty())
2273  {
2274  if (log)
2275  {
2276  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2277  info % u" No manufacturer, cannot reduce " %
2278  QString::number(inList.size()) % u" entries",
2279  getLogCategories());
2280  }
2281  return inList;
2282  }
2283 
2284  const CAircraftModelList outList(inList.findByManufacturer(m));
2285  if (outList.isEmpty())
2286  {
2287  if (log)
2288  {
2290  log, remoteAircraft, info % u" Not found '" % m % u"', cannot reduce", getLogCategories());
2291  }
2292  return inList;
2293  }
2294 
2295  if (log)
2296  {
2298  log, remoteAircraft, info % u" Reduced by '" % m % u"' results: " % QString::number(outList.size()),
2299  getLogCategories());
2300  }
2301  reduced = true;
2302  return outList;
2303  }
2304 
2305  CAircraftIcaoCodeList CAircraftMatcher::ifPossibleReduceAircraftIcaoByManufacturer(
2306  const CAircraftIcaoCode &icaoCode, const CAircraftIcaoCodeList &inList, const QString &info, bool &reduced,
2307  const CCallsign &logCallsign, CStatusMessageList *log)
2308  {
2309  reduced = false;
2310  if (inList.isEmpty())
2311  {
2312  if (log)
2313  {
2314  CCallsign::addLogDetailsToList(log, logCallsign, info % u" Empty input list, cannot reduce",
2315  getLogCategories());
2316  }
2317  return inList;
2318  }
2319 
2320  const QString m = icaoCode.getManufacturer();
2321  if (m.isEmpty())
2322  {
2323  if (log)
2324  {
2325  CCallsign::addLogDetailsToList(log, logCallsign,
2326  info % u" No manufacturer, cannot reduce " %
2327  QString::number(inList.size()) % u" entries",
2328  getLogCategories());
2329  }
2330  return inList;
2331  }
2332 
2333  const CAircraftIcaoCodeList outList(inList.findByManufacturer(m));
2334  if (outList.isEmpty())
2335  {
2336  if (log)
2337  {
2338  CCallsign::addLogDetailsToList(log, logCallsign, info % " Not found " % m % ", cannot reduce",
2339  getLogCategories());
2340  }
2341  return inList;
2342  }
2343 
2344  if (log)
2345  {
2346  CCallsign::addLogDetailsToList(log, logCallsign,
2347  info % u" Reduced by " % m % u" results: " % QString::number(outList.size()),
2348  getLogCategories());
2349  }
2350  reduced = true;
2351  return outList;
2352  }
2353 
2354  CAircraftModelList CAircraftMatcher::ifPossibleReduceByAircraft(const CSimulatedAircraft &remoteAircraft,
2355  const CAircraftModelList &inList,
2356  const QString &info, bool &reduced,
2357  CStatusMessageList *log)
2358  {
2359  reduced = false;
2360  if (inList.isEmpty())
2361  {
2362  if (log)
2363  {
2364  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, info % " Empty input list, cannot reduce",
2365  getLogCategories());
2366  }
2367  return inList;
2368  }
2369 
2370  if (!remoteAircraft.hasAircraftDesignator())
2371  {
2372  if (log)
2373  {
2374  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2375  info % " No aircraft designator, cannot reduce " %
2376  QString::number(inList.size()) % " entries",
2377  getLogCategories());
2378  }
2379  return inList;
2380  }
2381 
2382  const CAircraftModelList outList(
2383  inList.findByIcaoDesignators(remoteAircraft.getAircraftIcaoCode(), CAirlineIcaoCode::null()));
2384  if (outList.isEmpty())
2385  {
2386  if (log)
2387  {
2388  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2389  info % u" Cannot reduce by '" %
2390  remoteAircraft.getAircraftIcaoCodeDesignator() %
2391  u"' results: " % QString::number(outList.size()),
2392  getLogCategories());
2393  }
2394  return inList;
2395  }
2396 
2397  if (log)
2398  {
2399  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2400  info % u" Reduced by '" %
2401  remoteAircraft.getAircraftIcaoCodeDesignator() % u"' to " %
2402  QString::number(outList.size()),
2403  getLogCategories());
2404  }
2405  reduced = true;
2406  return outList;
2407  }
2408 
2409  CAircraftModelList CAircraftMatcher::ifPossibleReduceByAircraftOrFamily(
2410  const CSimulatedAircraft &remoteAircraft, bool allowPseudoFamily, const CAircraftModelList &inList,
2411  const CAircraftMatcherSetup &setup, const QString &info, bool &reduced, CStatusMessageList *log)
2412  {
2413  reduced = false;
2414  const CAircraftModelList outList = ifPossibleReduceByAircraft(remoteAircraft, inList, info, reduced, log);
2415  if (reduced || !setup.getMatchingMode().testFlag(CAircraftMatcherSetup::ByFamily)) { return outList; }
2416  QString family;
2417  return ifPossibleReduceByFamily(remoteAircraft, allowPseudoFamily, inList, reduced, family, log);
2418  }
2419 
2420  CAircraftModelList CAircraftMatcher::ifPossibleReduceByAirline(const CSimulatedAircraft &remoteAircraft,
2421  const CAircraftModelList &inList,
2422  const CAircraftMatcherSetup &setup,
2423  const QString &info, bool &reduced,
2424  CStatusMessageList *log)
2425  {
2426  reduced = false;
2427  if (inList.isEmpty())
2428  {
2429  if (log)
2430  {
2431  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, info % u" Empty input list, cannot reduce",
2432  getLogCategories());
2433  }
2434  return inList;
2435  }
2436 
2437  if (!remoteAircraft.hasAirlineDesignator())
2438  {
2439  if (log)
2440  {
2441  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2442  info % u" No airline designator, cannot reduce " %
2443  QString::number(inList.size()) % u" entries",
2444  getLogCategories());
2445  }
2446  return inList;
2447  }
2448 
2449  CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
2450  CAircraftModelList outList(
2451  inList.findByIcaoDesignators(CAircraftIcaoCode::null(), remoteAircraft.getAirlineIcaoCode()));
2452  if (mode.testFlag(CAircraftMatcherSetup::ByAirlineGroupSameAsAirline) ||
2453  (outList.isEmpty() || mode.testFlag(CAircraftMatcherSetup::ByAirlineGroupIfNoAirline)))
2454  {
2455  if (remoteAircraft.getAirlineIcaoCode().hasGroupMembership())
2456  {
2457  const CAircraftModelList groupModels = inList.findByAirlineGroup(remoteAircraft.getAirlineIcaoCode());
2458  outList.replaceOrAddModelsWithString(groupModels, Qt::CaseInsensitive);
2459  if (log)
2460  {
2462  log, remoteAircraft,
2463  groupModels.isEmpty() ?
2464  QStringLiteral("No group models found by using airline group '%1'")
2465  .arg(remoteAircraft.getAirlineIcaoCode().getGroupDesignator()) :
2466  QStringLiteral("Added %1 model(s) by using airline group '%2', all members: '%3'")
2467  .arg(groupModels.sizeInt())
2468  .arg(remoteAircraft.getAirlineIcaoCode().getGroupDesignator(),
2469  joinStringSet(groupModels.getAirlineVDesignators(), ", ")),
2470  getLogCategories());
2471  }
2472  } // group membership
2473  }
2474 
2475  if (outList.isEmpty())
2476  {
2477  if (log)
2478  {
2479  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2480  info % u" Cannot reduce by '" %
2481  remoteAircraft.getAirlineIcaoCodeDesignator() % u"' results: " %
2482  QString::number(outList.size()),
2483  getLogCategories());
2484  }
2485  return inList;
2486  }
2487 
2488  if (log)
2489  {
2490  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2491  info % u" Reduced by '" %
2492  remoteAircraft.getAirlineIcaoCodeDesignator() % u"' to " %
2493  QString::number(outList.size()),
2494  getLogCategories());
2495  }
2496  reduced = true;
2497  return outList;
2498  }
2499 
2500  CAircraftModelList CAircraftMatcher::ifPossibleReduceModelsByAirlineNameTelephonyDesignator(
2501  const CCallsign &cs, const QString &airlineName, const QString &telephony, const CAircraftModelList &inList,
2502  const QString &info, bool &reduced, CStatusMessageList *log)
2503  {
2504  reduced = false;
2505  if (inList.isEmpty())
2506  {
2507  if (log)
2508  {
2509  CCallsign::addLogDetailsToList(log, cs, info % u" Empty input list, cannot reduce", getLogCategories());
2510  }
2511  return inList;
2512  }
2513 
2514  if (telephony.isEmpty() && airlineName.isEmpty())
2515  {
2516  if (log)
2517  {
2518  CCallsign::addLogDetailsToList(log, cs,
2519  info % u" No name/telephony, cannot reduce " %
2520  QString::number(inList.size()) % u" entries",
2521  getLogCategories());
2522  }
2523  return inList;
2524  }
2525 
2526  CAircraftModelList step1Data = inList.findByAirlineNamesOrTelephonyDesignator(airlineName);
2527  if (step1Data.isEmpty() || step1Data.size() == inList.size())
2528  {
2529  if (log)
2530  {
2531  CCallsign::addLogDetailsToList(
2532  log, cs, info % QStringLiteral(" cannot reduce by '%1'").arg(airlineName), getLogCategories());
2533  }
2534  step1Data = inList;
2535  }
2536  else
2537  {
2538  reduced = true;
2539  if (log)
2540  {
2541  CCallsign::addLogDetailsToList(log, cs, info % QStringLiteral(" reduced by '%1'").arg(airlineName),
2542  getLogCategories());
2543  }
2544  }
2545  if (step1Data.size() == 1) { return step1Data; }
2546 
2547  CAircraftModelList step2Data = inList.findByAirlineNamesOrTelephonyDesignator(telephony);
2548  if (step2Data.isEmpty() || step2Data.size() == inList.size())
2549  {
2550  if (log)
2551  {
2552  CCallsign::addLogDetailsToList(log, cs, info % QStringLiteral(" cannot reduce by '%1'").arg(telephony),
2553  getLogCategories());
2554  }
2555  step2Data = step1Data;
2556  }
2557  else
2558  {
2559  reduced = true;
2560  if (log)
2561  {
2562  CCallsign::addLogDetailsToList(log, cs, info % QStringLiteral(" reduced by '%1'").arg(telephony),
2563  getLogCategories());
2564  }
2565  }
2566  return step2Data;
2567 
2579  }
2580 
2581  CAircraftModelList CAircraftMatcher::ifPossibleReduceByCombinedType(const CSimulatedAircraft &remoteAircraft,
2582  const CAircraftModelList &inList,
2583  const CAircraftMatcherSetup &setup,
2584  bool &reduced, CStatusMessageList *log)
2585  {
2586  reduced = false;
2587  if (!remoteAircraft.getAircraftIcaoCode().hasValidCombinedType())
2588  {
2589  if (log)
2590  {
2591  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("No valid combined code"),
2592  getLogCategories());
2593  }
2594  return inList;
2595  }
2596 
2597  const QString cc = remoteAircraft.getAircraftIcaoCode().getCombinedType();
2598  CAircraftModelList modelsByCombinedCode(inList.findByCombinedType(cc));
2599  if (modelsByCombinedCode.isEmpty())
2600  {
2601  if (log)
2602  {
2603  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"Not found by combined code " % cc,
2604  getLogCategories());
2605  }
2606  return inList;
2607  }
2608 
2609  if (log)
2610  {
2611  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2612  u"Found by combined code " % cc % u", possible " %
2613  QString::number(modelsByCombinedCode.size()),
2614  getLogCategories());
2615  }
2616  if (modelsByCombinedCode.size() > 1)
2617  {
2618  modelsByCombinedCode =
2619  ifPossibleReduceByAirline(remoteAircraft, modelsByCombinedCode, setup,
2620  QStringLiteral("Combined code airline reduction. "), reduced, log);
2621  modelsByCombinedCode =
2622  ifPossibleReduceByManufacturer(remoteAircraft, modelsByCombinedCode,
2623  QStringLiteral("Combined code manufacturer reduction. "), reduced, log);
2624  reduced = true;
2625  }
2626  return modelsByCombinedCode;
2627  }
2628 
2629  CAircraftModelList CAircraftMatcher::ifPossibleReduceByMilitaryFlag(const CSimulatedAircraft &remoteAircraft,
2630  const CAircraftModelList &inList, bool &reduced,
2631  CStatusMessageList *log)
2632  {
2633  reduced = false;
2634  const bool military = remoteAircraft.getModel().isMilitary();
2635  const CAircraftModelList byMilitaryFlag(inList.findByMilitaryFlag(military));
2636  const QString mil(military ? "military" : "civilian");
2637  if (byMilitaryFlag.isEmpty())
2638  {
2639  if (log)
2640  {
2641  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"Models not found by " % mil,
2642  getLogCategories());
2643  }
2644  return inList;
2645  }
2646 
2647  if (log)
2648  {
2649  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2650  u"Models reduced to " % mil % u" aircraft, size " %
2651  QString::number(byMilitaryFlag.size()),
2652  getLogCategories());
2653  }
2654  return byMilitaryFlag;
2655  }
2656 
2657  CAircraftModelList CAircraftMatcher::ifPossibleReduceByVTOLFlag(const CSimulatedAircraft &remoteAircraft,
2658  const CAircraftModelList &inList, bool &reduced,
2659  CStatusMessageList *log)
2660  {
2661  reduced = false;
2662  if (!inList.containsVtol())
2663  {
2664  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, "Cannot reduce to VTOL aircraft",
2665  getLogCategories());
2666  return inList;
2667  }
2668  CAircraftModelList vtolModels = inList.findByVtolFlag(true);
2669  if (log)
2670  {
2672  log, remoteAircraft, u"Models reduced to " % QString::number(vtolModels.size()) % u" VTOL aircraft",
2673  getLogCategories());
2674  }
2675  return vtolModels;
2676  }
2677 
2678  QString CAircraftMatcher::scoresToString(const ScoredModels &scores, int lastElements)
2679  {
2680  if (scores.isEmpty()) { return {}; }
2681  QMultiMapIterator<int, CAircraftModel> i(scores);
2682  i.toBack();
2683  int c = 0;
2684  QString str;
2685  while (i.hasPrevious() && c++ < lastElements)
2686  {
2687  i.previous();
2688  const CAircraftModel m(i.value());
2689  if (!str.isEmpty()) { str += '\n'; }
2690  str += QString::number(c) % u": score: " % QString::number(i.key()) % u" model: '" % m.getModelString() %
2691  u"' aircraft: '" % m.getAircraftIcaoCodeDesignator() % u"' livery: '" %
2692  m.getLivery().getCombinedCodePlusInfo() % u'\'';
2693  }
2694  return str;
2695  }
2696 
2697  CAirlineIcaoCode CAircraftMatcher::stringToAirlineIcaoObject(const CCallsign &cs, const QString &designator,
2698  const QString &airlineName,
2699  const QString &airlineTelephony, bool useSwiftDbData,
2700  CStatusMessageList *log)
2701  {
2702  if (!useSwiftDbData) { return CAirlineIcaoCode(designator); }
2703  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAirlineIcaoCode(designator); }
2705  if (codes.isEmpty()) { return CAirlineIcaoCode(designator); }
2706  if (codes.size() == 1) { return codes.front(); }
2707 
2708  // more than 1
2709  bool reduced = false;
2710  static const QString info("Try reducing airline '%1' by name/telephony '%2'/'%3'");
2712  cs, airlineName, airlineTelephony, QString(), reduced, info, log);
2713  return reducedIcaos.frontOrDefault();
2714  }
2715 
2716  bool CAircraftMatcher::isValidAirlineIcaoDesignator(const QString &designator, bool checkAgainstSwiftDb)
2717  {
2718  if (!CAirlineIcaoCode::isValidAirlineDesignator(designator)) { return false; }
2719  if (!checkAgainstSwiftDb) { return true; }
2720  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return true; }
2721  return (sApp->getWebDataServices()->containsAirlineIcaoDesignator(designator));
2722  }
2723 } // namespace swift::core
QMultiMap< int, CAircraftModel > ScoredModels
Individual (matching) score for each model.
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
Matcher for all models.
void setupChanged()
Setup changed.
static swift::misc::simulation::CAircraftModel reverseLookupModelId(int id, const swift::misc::aviation::CCallsign &callsign, swift::misc::CStatusMessageList *log)
Try to find model by id.
void evaluateStatisticsEntry(const QString &sessionId, const swift::misc::aviation::CCallsign &callsign, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery)
Evaluate if a statistics entry makes sense and add it.
static swift::misc::simulation::CAircraftModel reverseLookupModelStringInDB(const QString &modelString, const swift::misc::aviation::CCallsign &callsign, bool doLookupString, swift::misc::CStatusMessageList *log)
Try to find model by model string.
static swift::misc::aviation::CAirlineIcaoCode failoverValidAirlineIcaoDesignatorModelsFirst(const swift::misc::aviation::CCallsign &callsign, const QString &primaryIcao, const QString &secondaryIcao, bool airlineFromCallsign, const QString &airlineName, const QString &airlineTelephony, const swift::misc::simulation::CAircraftModelList &models, swift::misc::CStatusMessageList *log=nullptr)
Return an valid airline ICAO code from a given model list and use webservices if NOT found.
static QString reverseLookupTelephonyDesignator(const QString &candidate, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Lookup of telephony designator.
static swift::misc::aviation::CAircraftIcaoCode reverseLookupAircraftIcaoId(int id, const swift::misc::aviation::CCallsign &logCallsign, swift::misc::CStatusMessageList *log=nullptr)
Lookup of ICAO by id.
static swift::misc::aviation::CAirlineIcaoCode reverseLookupAirlineIcao(const swift::misc::aviation::CAirlineIcaoCode &icaoPattern, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Try to find the DB corresponding ICAO code.
void setDefaultModel(const swift::misc::simulation::CAircraftModel &defaultModel)
Set default model, can be set by driver specific for simulator.
static swift::misc::simulation::MatchingScriptReturnValues matchingScript(const QString &js, const swift::misc::simulation::CAircraftModel &inModel, const swift::misc::simulation::CAircraftModel &matchedModel, const swift::misc::simulation::CAircraftMatcherSetup &setup, const swift::misc::simulation::CAircraftModelList &modelSet, swift::misc::simulation::MatchingScript ms, swift::misc::CStatusMessageList *log)
Run the matching script.
static swift::misc::aviation::CLivery reverseLookupLiveryId(int id, const swift::misc::aviation::CCallsign &logCallsign, swift::misc::CStatusMessageList *log=nullptr)
Lookup of livery by id.
static swift::misc::simulation::CAircraftModel reverseLookupModelMs(const swift::misc::simulation::CAircraftModel &modelToLookup, const QString &networkLiveryInfo, const swift::misc::simulation::CAircraftMatcherSetup &setup, const swift::misc::simulation::CAircraftModelList &modelSet, swift::misc::CStatusMessageList *log)
Try to find the corresponding data in DB and get best information for following matching.
void addingRemoteModelFailed(const swift::misc::simulation::CSimulatedAircraft &remoteAircraft)
Adding a model failed.
static QString reverseLookupAirlineName(const QString &candidate, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Lookup of airline name.
static swift::misc::simulation::MatchingScriptReturnValues reverseLookupScript(const swift::misc::simulation::CAircraftModel &inModel, const swift::misc::simulation::CAircraftMatcherSetup &setup, const swift::misc::simulation::CAircraftModelList &modelSet, swift::misc::CStatusMessageList *log)
Run the network reverse lookup script.
static bool isKnownModelString(const QString &candidate, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Is this aircraft designator known?
static swift::misc::aviation::CAircraftIcaoCode reverseLookupAircraftIcao(const swift::misc::aviation::CAircraftIcaoCode &icaoDesignator, const swift::misc::aviation::CCallsign &logCallsign={}, swift::misc::CStatusMessageList *log=nullptr)
Try to find the DB corresponding ICAO code.
static swift::misc::aviation::CAirlineIcaoCode callsignToAirline(const swift::misc::aviation::CCallsign &callsign, swift::misc::CStatusMessageList *log=nullptr)
Turn callsign into airline.
static swift::misc::aviation::CLivery reverseLookupStandardLivery(const swift::misc::aviation::CAirlineIcaoCode &airline, const swift::misc::aviation::CCallsign &callsign, swift::misc::CStatusMessageList *log=nullptr)
Lookup of standard livery.
static swift::misc::aviation::CAircraftIcaoCode searchAmongAirlineAircraft(const QString &icaoString, const swift::misc::aviation::CAirlineIcaoCode &airline, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Search among the airline aircraft.
static swift::misc::simulation::MatchingScriptReturnValues matchingStageScript(const swift::misc::simulation::CAircraftModel &inModel, const swift::misc::simulation::CAircraftModel &matchedModel, const swift::misc::simulation::CAircraftMatcherSetup &setup, const swift::misc::simulation::CAircraftModelList &modelSet, swift::misc::CStatusMessageList *log)
Run the matching stage lookup script.
int setModelSet(const swift::misc::simulation::CAircraftModelList &models, const swift::misc::simulation::CSimulatorInfo &simulator, bool forced)
Set the models we want to use.
static swift::misc::aviation::CAirlineIcaoCode failoverValidAirlineIcaoDesignator(const swift::misc::aviation::CCallsign &callsign, const QString &primaryIcao, const QString &secondaryIcao, bool airlineFromCallsign, const QString &airlineName, const QString &airlineTelephony, bool useWebServices, swift::misc::CStatusMessageList *log=nullptr)
Return an valid airline ICAO code.
static swift::misc::simulation::CAircraftModel reverseLookupModelStringInSet(const QString &modelString, const swift::misc::aviation::CCallsign &callsign, const swift::misc::simulation::CAircraftModelList &modelSet, bool useNonDbEntries, swift::misc::CStatusMessageList *log)
Try to find model by model string in set.
virtual ~CAircraftMatcher()
Destructor.
static swift::misc::simulation::CAircraftModel reverseLookupModel(const swift::misc::aviation::CCallsign &callsign, const swift::misc::aviation::CAircraftIcaoCode &networkAircraftIcao, const swift::misc::aviation::CAirlineIcaoCode &networkAirlineIcao, const QString &networkLiveryInfo, const QString &networkModelString, const swift::misc::simulation::CAircraftMatcherSetup &setup, const swift::misc::simulation::CAircraftModelList &modelSet, swift::misc::simulation::CAircraftModel::ModelType type, swift::misc::CStatusMessageList *log)
Try to find the corresponding data in DB and get best information for given data.
CAircraftMatcher(const swift::misc::simulation::CAircraftMatcherSetup &setup, QObject *parent=nullptr)
Constructor.
bool setSetup(const swift::misc::simulation::CAircraftMatcherSetup &setup)
Set the setup.
swift::misc::simulation::CAircraftModel getClosestMatch(const swift::misc::simulation::CSimulatedAircraft &remoteAircraft, swift::misc::simulation::MatchingLog whatToLog, swift::misc::CStatusMessageList *log, bool useMatchingScript) const
Get the closest matching aircraft model from set. Result depends on setup.
static int reverseLookupByIds(const swift::misc::simulation::DBTripleIds &ids, swift::misc::aviation::CAircraftIcaoCode &aircraftIcao, swift::misc::aviation::CLivery &livery, const swift::misc::aviation::CCallsign &logCallsign, swift::misc::CStatusMessageList *log=nullptr)
Lookup by ids.
void disableModelsForMatching(const swift::misc::simulation::CAircraftModelList &removedModels, bool incremental)
Remove a model for matching.
const swift::misc::simulation::CAircraftModel & getDefaultModel() const
Default model.
static const QStringList & getLogCategories()
Log categories.
static bool isKnownAircraftDesignator(const QString &candidate, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Is this aircraft designator known?
void restoreDisabledModels()
Restore the models removed with CAircraftMatcher::disableModelForMatching.
bool hasWebDataServices() const
Web data services available?
bool isShuttingDown() const
Is application shutting down?
CWebDataServices * getWebDataServices() const
Get the web data services.
swift::misc::aviation::CAirlineIcaoCode getAirlineIcaoCodeForDbKey(int id) const
ICAO code for id.
swift::misc::aviation::CAircraftIcaoCode smartAircraftIcaoSelector(const swift::misc::aviation::CAircraftIcaoCode &icao) const
Use an ICAO object to select the best complete ICAO object from DB for it.
bool containsModelString(const QString &modelString) const
Existing modelstring?
swift::misc::aviation::CAirlineIcaoCode findBestMatchByCallsign(const swift::misc::aviation::CCallsign &callsign) const
ICAO code for callsign (e.g. DLH123 -> DLH)
swift::misc::aviation::CAirlineIcaoCodeList getAirlineIcaoCodesForDesignator(const QString &designator) const
Airline ICAO codes for designator.
swift::misc::aviation::CAircraftIcaoCode getAircraftIcaoCodeForDbKey(int id) const
ICAO code for id.
swift::misc::aviation::CAircraftIcaoCode getAircraftIcaoCodeForDesignator(const QString &designator) const
ICAO code for designator.
swift::misc::aviation::CLivery getStdLiveryForAirlineCode(const swift::misc::aviation::CAirlineIcaoCode &icao) const
Standard livery for airline code.
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodesForAirline(const swift::misc::aviation::CAirlineIcaoCode &airline) const
Aircraft ICAO codes for airline.
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodesForDesignator(const QString &designator) const
ICAO codes for designator.
swift::misc::aviation::CAirlineIcaoCode smartAirlineIcaoSelector(const swift::misc::aviation::CAirlineIcaoCode &code, const swift::misc::aviation::CCallsign &callsign=swift::misc::aviation::CCallsign()) const
Smart airline selector.
swift::misc::simulation::CAircraftModel getModelForDbKey(int dbKey) const
Model for key if any.
void synchronizeDbCaches(swift::misc::network::CEntityFlags::Entity entities)
Synchronize all DB caches specified.
QStringList getTelephonyDesignators() const
Airline telephony designators.
swift::misc::simulation::CAircraftModel getModelForModelString(const QString &modelString) const
Model for model string if any.
swift::misc::aviation::CAircraftCategoryList getAircraftCategories() const
Aircraft categories.
bool containsAircraftIcaoDesignator(const QString &designator) const
Contains the given designator?
swift::misc::aviation::CLivery getLiveryForDbKey(int id) const
Livery for id.
swift::misc::aviation::CLivery getLiveryForCombinedCode(const QString &combinedCode) const
Livery for its combined code.
bool containsAirlineIcaoDesignator(const QString &designator) const
Contains the given designator?
swift::misc::aviation::CAirlineIcaoCode getAirlineIcaoCodeForUniqueDesignatorOrDefault(const QString &designator, bool preferOperatingAirlines) const
ICAO code if unique, otherwise default.
QStringList getAirlineNames() const
Airline names.
Encapsulates reading data from web sources.
QString toJsonString(QJsonDocument::JsonFormat format=QJsonDocument::Indented) const
Convenience function JSON as string.
static bool writeStringToFile(const QString &content, const QString &fileNameAndPath)
Write string to text file.
Definition: fileutils.cpp:40
static QString appendFilePathsAndFixUnc(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:108
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:95
static QString readFileToString(const QString &fileNameAndPath)
Read file into string.
Definition: fileutils.cpp:68
static const QString & matching()
Matching.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & validationWarning(const char16_t(&format)[N])
Set the severity to warning, providing a format string, and adding the validation category.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & validationInfo(const char16_t(&format)[N])
Set the severity to info, providing a format string, and adding the validation category.
T randomElement() const
Pick one random element.
Definition: range.h:142
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
const_reference frontOrDefault() const
Access the first element, or a default-initialized value if the sequence is empty.
Definition: sequence.h:239
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
void clear()
Removes all elements in the sequence.
Definition: sequence.h:288
QString sizeString() const
Convenience function.
Definition: sequence.h:279
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
int sizeInt() const
Avoid compiler warnings when using with int.
Definition: sequence.h:276
constexpr static auto SeverityError
Status severities.
constexpr static auto SeverityInfo
Status severities.
constexpr static auto SeverityWarning
Status severities.
Status messages, e.g. from Core -> GUI.
static const QString & logDirectory()
Directory for log files.
OBJ minOrderOrDefault() const
Object with min.order or default.
bool needsOrder() const
All order values set or missing some?
Definition: orderablelist.h:41
Value object encapsulating a list of ICAO codes.
Value object for ICAO classification.
QString getCombinedIcaoStringWithKey() const
Combined ICAO descriptive string with key.
const QString & getFamily() const
Family (e.g. A350)
const QString & getDesignator() const
Get ICAO designator, e.g. "B737".
QString getDesignatorDbKey() const
Designator and DB key.
bool hasValidDesignator() const
Valid aircraft designator?
const QString & getCombinedType() const
Get type, e.g. "L2J".
bool hasValidCombinedType() const
Combined type available?
const QString & getManufacturer() const
Get manufacturer, e.g. "Airbus".
QString getDesignatorManufacturer() const
Designator + Manufacturer.
Value object encapsulating a list of ICAO codes.
CAircraftIcaoCodeList findByManufacturer(const QString &manufacturer) const
Find by manufacturer.
QSet< QString > allDesignators(bool noUnspecified=true) const
All ICAO codes, no duplicates.
CAircraftIcaoCode findBestFuzzyMatchOrDefault(const QString &designator, int cutoff=50) const
Find by designator.
QPair< QString, int > maxCountManufacturer() const
Uses countManufacturers to find "most important" manufacturer.
Value object for ICAO classification.
const QString & getGroupDesignator() const
Group designator.
QString getVDesignatorDbKey() const
Get VDesignator plus key.
QString getDesignatorDbKey() const
Designator and DB key.
const QString & getDesignator() const
Get airline, e.g. "DLH".
bool hasValidDesignator() const
Airline designator available?
const QString & getName() const
Get name, e.g. "Lufthansa".
bool hasGroupMembership() const
Are we a member of a group?
Value object encapsulating a list of ICAO codes.
CAirlineIcaoCodeList ifPossibleReduceNameTelephonyCountry(const swift::misc::aviation::CCallsign &cs, const QString &airlineName, const QString &telephony, const QString &countryIso, bool &reduced, const QString &logInfo, CStatusMessageList *log) const
Reduce by airline name/telephone designator, ISO country.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
bool isEmpty() const
Is empty?
Definition: callsign.h:63
QString getAirlinePrefix() const
Airline suffix (e.g. DLH1234 -> DLH) if applicable.
Definition: callsign.cpp:219
Value object encapsulating information about an airpot.
Definition: livery.h:29
bool setAirlineIcaoCode(const CAirlineIcaoCode &airlineIcao)
Airline ICAO code.
Definition: livery.cpp:81
QString getCombinedCodePlusInfoAndId() const
Combined code, info, plus id.
Definition: livery.cpp:71
const QString & getCombinedCode() const
Combined code.
Definition: livery.h:71
bool hasCombinedCode() const
Livery combined code available?
Definition: livery.cpp:161
QString getCombinedCodePlusInfo() const
Combined code plus info.
Definition: livery.cpp:60
int removeObjectsWithoutDbKey()
Remove objects without key.
QString dbKeysAsString(const QString &separator) const
The DB keys as string.
bool isLoadedFromDb() const
Loaded from DB.
Definition: datastore.cpp:49
bool hasValidDbKey() const
Has valid DB key.
Definition: datastore.h:102
const QString & getDbKey() const
Get DB key.
Definition: datastore.h:180
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
MatchingMode getMatchingMode() const
Matching mode.
bool useCategoryMatching() const
Use category matching.
PickSimilarStrategy
How to pick among similar candiates.
const QString & getMsMatchingStageFile() const
Get matching files.
@ ByIcaoData
ICAO airline and aircraft codes.
@ ByMilitary
military (in) will only search in military
@ ByCivilian
civilian (in) will only search in civilian
MatchingAlgorithm getMatchingAlgorithm() const
Algorithm.
const QString & getMatchingAlgorithmAsString() const
Algorithm as string.
static const QString & strategyToString(PickSimilarStrategy strategy)
Strategy to string.
PickSimilarStrategy getPickStrategy() const
Strategy among equally suitable models.
const QString & getMsReverseLookupFile() const
Get matching files.
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
const aviation::CCallsign & getCallsign() const
Corresponding callsign if applicable.
const aviation::CAirlineIcaoCode & getAirlineIcaoCode() const
Airline ICAO code.
bool hasManuallySetString() const
Model string which was manually set.
void setModelType(ModelType type)
Set type.
static DBTripleIds parseNetworkLiveryString(const QString &liveryString)
Split swift network string.
const aviation::CLivery & getLivery() const
Get livery.
@ TypeModelMatchingDefaultModel
a default model assigned by model matching
Definition: aircraftmodel.h:81
@ TypeOwnSimulatorModel
represents own simulator model (AI model, model on disk)
Definition: aircraftmodel.h:84
@ TypeReverseLookup
reverse lookup model
Definition: aircraftmodel.h:79
@ TypeModelMatching
model is result of model matching
Definition: aircraftmodel.h:80
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
void setCallsign(const aviation::CCallsign &callsign)
Corresponding callsign if applicable.
const QString & getDescription() const
Descriptive text.
const CDistributor & getDistributor() const
Get distributor.
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Aircraft ICAO code.
ModelType getModelType() const
Model type.
QString getModelStringAndDbKey() const
Model string and DB key (if available)
const QString & getModelTypeAsString() const
Model type.
bool isMilitary() const
Military model?
bool hasModelString() const
Non empty model string?
void setLivery(const aviation::CLivery &livery)
Livery.
bool setAircraftIcaoCode(const aviation::CAircraftIcaoCode &aircraftIcaoCode)
Set aircraft ICAO code.
Value object encapsulating a list of aircraft models.
CAircraftModelList findByCombinedAndManufacturer(const aviation::CAircraftIcaoCode &icao) const
Combined type and manufacturer.
ScoredModels scoreFull(const CAircraftModel &remoteModel, bool preferColorLiveries, bool ignoreZeroScores=true, CStatusMessageList *log=nullptr) const
Score by aircraft ICAO code.
QStringList getModelStringList(bool sort=true) const
Model strings.
CAircraftModel findFirstByModelStringAliasOrDefault(const QString &modelString, Qt::CaseSensitivity sensitivity=Qt::CaseInsensitive) const
Find first by model string.
CAircraftModelList findByMilitaryFlag(bool military) const
Find by military flag, false returns civilian models.
CAircraftModelList findEmptyModelStrings() const
Find empty model strings.
int replaceOrAddModelsWithString(const CAircraftModelList &addOrReplaceList, Qt::CaseSensitivity sensitivity)
Replace or add based on model string.
CAircraftModel findFirstByModelStringOrDefault(const QString &modelString, Qt::CaseSensitivity sensitivity=Qt::CaseInsensitive) const
Find first by model string.
CAircraftModelList findByAircraftDesignatorAndLiveryCombinedCode(const QString &aircraftDesignator, const QString &combinedCode) const
Find by designator and livery code.
int removeIfExcluded()
Remove if excluded CAircraftModel::Exclude.
QString coverageSummary(const QString &separator="\n") const
What kind of models are represented here?
CAircraftModelList findByAirlineGroup(const swift::misc::aviation::CAirlineIcaoCode &airline) const
Find by the corresponding airline group.
CAircraftModelList findByFamily(const QString &family) const
Models with aircraft family.
CAircraftModelList findByCombinedType(const QString &combinedType) const
Find by combined code, wildcards possible, e.g. L*P, *2J.
QMap< swift::misc::aviation::CAirlineIcaoCode, int > countPerAirlineIcao() const
Airline ICAO plus count.
QString coverageSummaryForModel(const CAircraftModel &checkModel, const QString &separator="\n") const
What kind of models are represented here?
QSet< QString > getAirlineVDesignators() const
Airline virtual designators.
CAircraftModelList findByManufacturer(const QString &manufacturer) const
Find by manufacturer.
CAircraftModelList findByIcaoDesignators(const aviation::CAircraftIcaoCode &aircraftIcaoCode, const aviation::CAirlineIcaoCode &airlineIcaoCode) const
Find by ICAO designators.
int removeModelsWithString(const CAircraftModelList &models, Qt::CaseSensitivity sensitivity)
Remove those models with given model strings.
int removeAllWithoutModelString()
Remove if having no model string.
CAircraftModelList findDuplicateModelStrings() const
Find duplicate model strings and return those models with at least 1 duplicate model string.
CAircraftModelList findByAirlineNamesOrTelephonyDesignator(const QString &name) const
Find by airline name and telephony, similar to CAirlineIcaoCodeList::findByNamesOrTelephonyDesignator...
aviation::CAirlineIcaoCode getAirlineWithMaxCount() const
The airline with the max count.
CAircraftModelList findByModelMode(CAircraftModel::ModelMode mode) const
Find by model mode.
CAircraftModelList findByCombinedTypeWithColorLivery(const QString &combinedType) const
Combined type and color livery.
bool containsModelsWithAircraftAndAirlineIcaoDesignator(const QString &aircraftDesignator, const QString &airlineDesignator) const
Contains any model with aircraft and airline ICAO designator?
CAircraftModelList findByVtolFlag(bool vtol) const
Find by VTOL flag, false returns non VTOL models.
bool containsVtol() const
Contains VTOL models?
Category matcher, uses the DB categories.
CAircraftModelList reduceByCategories(const CAircraftModelList &alreadyMatchedModels, const CAircraftModelList &modelSet, const CAircraftMatcherSetup &setup, const CSimulatedAircraft &remoteAircraft, bool &reduced, bool shortLog, CStatusMessageList *log=nullptr) const
Reduce by categories.
void setCategories(const aviation::CAircraftCategoryList &categories)
Used categories.
EntryType
Represents type of entry.
void addAircraftAirlineCombination(CMatchingStatisticsEntry::EntryType type, const QString &sessionId, const QString &modelSetId, const QString &description, const QString &aircraftDesignator, const QString &airlineDesignator, bool avoidDuplicates=true)
Add a combination, normally with no duplicates (in that case count is increased.
static void addLogDetailsToList(CStatusMessageList *log, const CSimulatedAircraft &remoteAircraft, const QString &message, const QStringList &extraCategories={}, CStatusMessage::StatusSeverity s=CStatusMessage::SeverityInfo)
Specialized log for matching / reverse lookup.
Comprehensive information of an aircraft.
const QString & getAirlineIcaoCodeDesignator() const
Airline ICAO code designator.
bool hasAirlineDesignator() const
Valid airline designator.
bool hasModelString() const
Has model string?
void setModel(const CAircraftModel &model)
Set model.
bool isMilitary() const
Is military aircraft.
bool hasCallsign() const
Callsign not empty, no further checks.
const aviation::CCallsign & getCallsign() const
Get callsign.
const aviation::CLivery & getLivery() const
Get livery.
bool hasAircraftDesignator() const
Valid designator?
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Get aircraft ICAO info.
const QString & getAircraftIcaoCombinedType() const
Aircraft ICAO combined code.
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping)
QString getAirlineAndAircraftIcaoCodeDesignators() const
Aircraft and Airline ICAO code designators.
QString getCallsignAsString() const
Get callsign.
const aviation::CAirlineIcaoCode & getAirlineIcaoCode() const
Airline ICAO code if any.
const QString & getModelString() const
Get model string.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
bool isSingleSimulator() const
Single simulator selected.
const QString & getLogMessage() const
Log. message.
int getDbAircraftIcaoId() const
Values found in DB?
bool hasChangedLiveryId(const swift::misc::aviation::CLivery &livery) const
Changed values.
int getDbAirlineIcaoId() const
Values found in DB?
bool hasChangedModelString(const QString &modelString) const
Changed values.
bool hasChangedAircraftIcaoId(const swift::misc::aviation::CAircraftIcaoCode &aircraftIcao) const
Changed values.
bool hasChangedAirlineIcaoId(const swift::misc::aviation::CAirlineIcaoCode &airlineIcao) const
Changed values.
bool isRerun() const
Request re-run.
bool hasChangedAircraftIcao(const swift::misc::aviation::CAircraftIcaoCode &aircraftIcao) const
Changed values.
int getDbLiveryId() const
Values found in DB?
const QString & getModelString() const
Livery, airline, aircraft, model string.
bool isModified() const
Modified flag.
int getDbModelId() const
Values found in DB?
bool hasChangedAirlineIcao(const swift::misc::aviation::CAirlineIcaoCode &airlineIcao) const
Changed values.
bool hasChangedModelId(const swift::misc::simulation::CAircraftModel &model) const
Changed values.
const QString & getAirlineIcao() const
Livery, airline, aircraft, model string.
void evaluateChanges(const swift::misc::aviation::CAircraftIcaoCode &aircraft, const swift::misc::aviation::CAirlineIcaoCode &airline)
Changed values such as modified values.
const QString & getAircraftIcao() const
Livery, airline, aircraft, model string.
void initByAircraftAndAirline(const swift::misc::aviation::CAircraftIcaoCode &aircraft, const swift::misc::aviation::CAirlineIcaoCode &airline)
Init by aircraft/airline.
MatchingScript
Matching script type.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
SWIFT_MISC_EXPORT QString removeSurroundingApostrophes(const QString &in)
Remove surrounding apostrophes 'foo' -> foo.
SWIFT_MISC_EXPORT QString joinStringSet(const QSet< QString > &set, const QString &separator)
Convert string to bool.
SWIFT_MISC_EXPORT const QString & defaultIfEmpty(const QString &candidate, const QString &defaultIfEmpty)
Default string if string is empty.
const std::string & boolToYesNo(bool t)
Yes/no from bool.
Definition: qtfreeutils.h:129
QString toQString() const
Return as string.
Definition: aircraftmodel.h:59
int livery
livery id, by that I have airline id
Definition: aircraftmodel.h:52
swift::misc::simulation::CAircraftModel model
the model
bool runScriptAndModified() const
Did run the script with modified result.
bool runScriptModifiedAndRerun() const
Did run the script, modified value and re-run requested.