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";
595  const QString modelString = remoteAircraft.getModel().getModelStringAndDbKey().isEmpty() ?
596  nms :
597  remoteAircraft.getModel().getModelStringAndDbKey();
598  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
599  summary
600  .arg(remoteAircraft.getAircraftIcaoCode().getCombinedType(),
601  matchedModel.getAircraftIcaoCode().getCombinedType(),
602  remoteAircraft.getAircraftIcaoCode().getDesignatorDbKey(),
603  matchedModel.getAircraftIcaoCode().getDesignatorDbKey(),
604  remoteAircraft.getAirlineIcaoCode().getVDesignatorDbKey(),
605  matchedModel.getAirlineIcaoCode().getVDesignatorDbKey())
606  .arg(remoteAircraft.getLivery().getCombinedCodePlusInfoAndId(),
607  matchedModel.getLivery().getCombinedCodePlusInfoAndId(),
608  modelString, matchedModel.getModelStringAndDbKey(),
609  boolToYesNo(didRunAndModifyMatchingScript)));
610  } // log
611 
612  const QDateTime endTime = QDateTime::currentDateTimeUtc();
613  const qint64 matchingTime = startTime.msecsTo(endTime);
614  static const QString em("--- Matching end: UTC %1, time %2ms ---");
615  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, em.arg(endTime.toString(format)).arg(matchingTime));
616  return matchedModel;
617  }
618 
620  CAircraftMatcher::reverseLookupModel(const CCallsign &callsign, const CAircraftIcaoCode &networkAircraftIcao,
621  const CAirlineIcaoCode &networkAirlineIcao, const QString &networkLiveryInfo,
622  const QString &networkModelString, const CAircraftMatcherSetup &setup,
623  const CAircraftModelList &modelSet, CAircraftModel::ModelType type,
624  CStatusMessageList *log)
625  {
626  Q_UNUSED(setup)
627 
628  CLivery livery;
629  livery.setAirlineIcaoCode(networkAirlineIcao);
630 
631  CAircraftModel model(networkModelString, type, {}, networkAircraftIcao, livery);
632  model.setCallsign(callsign);
633  model = CAircraftMatcher::reverseLookupModel(model, networkLiveryInfo, setup, modelSet, log);
634  model.setModelType(CAircraftModel::TypeReverseLookup);
635 
636  return model;
637  }
638 
640  const CAircraftMatcherSetup &setup,
641  const CAircraftModelList &modelSet,
642  CStatusMessageList *log)
643  {
644  if (!setup.doRunMsReverseLookupScript()) { return MatchingScriptReturnValues(inModel); }
645  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return inModel; }
646  const QString js = CFileUtils::readFileToString(setup.getMsReverseLookupFile());
647  const MatchingScriptReturnValues rv =
648  CAircraftMatcher::matchingScript(js, inModel, inModel, setup, modelSet, ReverseLookup, log);
649  return rv;
650  }
651 
653  const CAircraftModel &matchedModel,
654  const CAircraftMatcherSetup &setup,
655  const CAircraftModelList &modelSet,
656  CStatusMessageList *log)
657  {
658  if (!setup.doRunMsMatchingStageScript()) { return MatchingScriptReturnValues(inModel); }
659  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return inModel; }
660  const QString js = CFileUtils::readFileToString(setup.getMsMatchingStageFile());
661  const MatchingScriptReturnValues rv =
662  CAircraftMatcher::matchingScript(js, inModel, matchedModel, setup, modelSet, MatchingStage, log);
663  return rv;
664  }
665 
667  const CAircraftModel &matchedModel,
668  const CAircraftMatcherSetup &setup,
669  const CAircraftModelList &modelSet,
670  MatchingScript script, CStatusMessageList *log)
671  {
672  MatchingScriptReturnValues rv(inModel);
673  QString logMessage;
674  const CCallsign callsign = inModel.getCallsign();
675 
676  if (js.isEmpty() && log)
677  {
678  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Matching script is empty"));
679  }
680 
681  while (!js.isEmpty() && sApp && sApp->hasWebDataServices())
682  {
683  rv.runScript = true;
684 
685  // matching script
686  const bool msReverse = (script == ReverseLookup);
687  const QString lf = msReverse ? setup.getMsReverseLookupFile() : setup.getMsMatchingStageFile();
688  static const QString logFileR =
689  CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), "logMatchingScriptReverseLookup.log");
690  static const QString logFileM =
691  CFileUtils::appendFilePaths(CSwiftDirectories::logDirectory(), "logMatchingScriptMatchingStage.log");
692 
693  if (log)
694  {
695  CCallsign::addLogDetailsToList(
696  log, callsign, QStringLiteral("Matching script (%1): '%2'").arg(msToString(script), lf));
697  CCallsign::addLogDetailsToList(
698  log, callsign,
699  QStringLiteral("Matching script input model (%1): '%2'").arg(inModel.toQString(true)));
700  CCallsign::addLogDetailsToList(
701  log, callsign, QStringLiteral("Matching script models: %1").arg(modelSet.coverageSummary()));
702  }
703 
704  QJSEngine engine;
705  // engine.installExtensions(QJSEngine::ConsoleExtension);
706 
707  // Meta objects to create new JS objects, here causing JSValue can't be reassigned to another engine.
708  // static const QJSValue jsInOutMetaObject = engine.newQMetaObject(&MSInOutValues::staticMetaObject);
709  // engine.globalObject().setProperty("SwiftInOutObject", jsInOutMetaObject); // JS: new SwiftInOutObject();
710  // static const QJSValue jsSetMetaObject = engine.newQMetaObject(&MSModelSet::staticMetaObject);
711  // engine.globalObject().setProperty("SwiftModelSet", jsSetMetaObject); // JS: new SwiftModelSet
712  // static const QJSValue jsWebServicesMetaObject = engine.newQMetaObject(&MSWebServices::staticMetaObject);
713  // engine.globalObject().setProperty("SwiftWebServices", jsWebServicesMetaObject); // JS: new
714  // SwiftWebServices
715 
716  // init models and set
717  MSInOutValues inObject(inModel);
718  MSInOutValues matchedObject(matchedModel); // same as inModel for reverse lookup
719  matchedObject.evaluateChanges(inModel.getAircraftIcaoCode(), inModel.getAirlineIcaoCode());
720  MSInOutValues outObject(matchedModel); // set default values for out object
721  MSModelSet modelSetObject(modelSet); // as passed
722  modelSetObject.initByAircraftAndAirline(inModel.getAircraftIcaoCode(), inModel.getAirlineIcaoCode());
723  MSWebServices webServices; // web services encapsulated
724 
725  // object as from network
726  const QJSValue jsInObject = engine.newQObject(&inObject);
727  engine.globalObject().setProperty("inObject", jsInObject);
728 
729  // object that will be returned
730  const QJSValue jsOutObject = engine.newQObject(&outObject);
731  engine.globalObject().setProperty("outObject", jsOutObject);
732 
733  // object as matched so far, same as inObject in reverse lookup
734  const QJSValue jsMatchedObject = engine.newQObject(&matchedObject);
735  engine.globalObject().setProperty("matchedObject", jsMatchedObject);
736 
737  // wrapper for model set
738  const QJSValue jsModelSetObject = engine.newQObject(&modelSetObject);
739  engine.globalObject().setProperty("modelSet", jsModelSetObject);
740 
741  // wrapper for web services
742  const QJSValue jsWebServices = engine.newQObject(&webServices);
743  engine.globalObject().setProperty("webServices", jsWebServices);
744 
745  QJSValue ms = engine.evaluate(js, msReverse ? logFileR : logFileM);
746  ms = ms.call();
747  if (ms.isError())
748  {
749  const QString msg = QStringLiteral("Matching script error: %1 '%2'")
750  .arg(ms.property("lineNumber").toInt())
751  .arg(ms.toString());
752  CLogMessage(static_cast<CAircraftMatcher *>(nullptr)).warning(msg);
753  if (log) { CCallsign::addLogDetailsToList(log, callsign, msg); }
754  }
755  else
756  {
757  if (ms.isQObject())
758  {
759  const MSInOutValues *reverseModelProcessed = qobject_cast<const MSInOutValues *>(ms.toQObject());
760  logMessage = reverseModelProcessed->getLogMessage();
761  if (!reverseModelProcessed->isModified()) { break; }
762 
763  // rerun
764  rv.rerun = reverseModelProcessed->isRerun();
765 
766  // changed model by model id?
767  if (reverseModelProcessed->hasChangedModelId(inModel))
768  {
769  const CAircraftModel model =
770  sApp->getWebDataServices()->getModelForDbKey(reverseModelProcessed->getDbModelId());
771  if (model.hasValidDbKey())
772  {
773  // found full model from DB
774  rv.model = model;
775  rv.modified = true;
776  break;
777  }
778  }
779 
780  // changed model by model string?
781  if (reverseModelProcessed->hasChangedModelString(inModel.getModelString()))
782  {
783  const QString modelString = reverseModelProcessed->getModelString();
784  const CAircraftModel model = sApp->getWebDataServices()->getModelForModelString(modelString);
785  if (model.hasValidDbKey())
786  {
787  // found full model from DB
788  rv.model = model;
789  rv.modified = true;
790  break;
791  }
792 
793  // search for model string in set, even if it is not in the DB
794  const CAircraftModel modeSetModel =
795  CAircraftMatcher::reverseLookupModelStringInSet(modelString, callsign, modelSet, true, log);
796  if (modeSetModel.hasModelString())
797  {
798  CCallsign::addLogDetailsToList(
799  log, callsign,
800  QStringLiteral("Matching script using model from set: '%1'").arg(modelString));
801 
802  // NON DB model from model set
803  rv.model = modeSetModel;
804  rv.modified = true;
805  break;
806  }
807  }
808 
809  // changed aircraft ICAO
810  if (reverseModelProcessed->hasChangedAircraftIcao(matchedModel.getAircraftIcaoCode()))
811  {
812  CAircraftIcaoCode icao(reverseModelProcessed->getAircraftIcao());
813  if (reverseModelProcessed->hasChangedAircraftIcaoId(matchedModel.getAircraftIcaoCode()))
814  {
816  reverseModelProcessed->getDbAircraftIcaoId());
817  }
818  rv.modified = true;
819  rv.model.setAircraftIcaoCode(icao);
820  }
821 
822  if (reverseModelProcessed->hasChangedLiveryId(matchedModel.getLivery()))
823  {
824  const CLivery livery(
825  sApp->getWebDataServices()->getLiveryForDbKey(reverseModelProcessed->getDbLiveryId()));
826  rv.model.setLivery(livery);
827  rv.modified = true;
828  }
829  else if (reverseModelProcessed->hasChangedAirlineIcao(matchedModel.getAirlineIcaoCode()))
830  {
831  CAirlineIcaoCode icao;
832  if (reverseModelProcessed->hasChangedAirlineIcaoId(matchedModel.getAirlineIcaoCode()))
833  {
835  reverseModelProcessed->getDbAirlineIcaoId());
836  }
837  else
838  {
840  reverseModelProcessed->getAirlineIcao(), true);
841  }
842 
844  rv.model.setLivery(livery);
845  if (log)
846  {
847  CCallsign::addLogDetailsToList(
848  log, callsign,
849  QStringLiteral("Matching script, changed airline ICAO: '%1' -> '%2'")
850  .arg(matchedModel.getAirlineIcaoCode().toQString(true), icao.toQString(true)));
851  }
852  rv.modified = true;
853  }
854  }
855  else if (ms.isString()) { logMessage = ms.toString(); }
856  }
857 
858  // end this
859  break;
860  }
861 
862  // log message
863  if (log && !logMessage.isEmpty())
864  {
865  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Matching script log: '%1'").arg(logMessage));
866  }
867 
868  // end
869  return rv;
870  }
871 
873  const QString &networkLiveryInfo,
874  const CAircraftMatcherSetup &setup,
875  const CAircraftModelList &modelSet, CStatusMessageList *log)
876  {
877  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
878 
879  // already DB model?
880  CAircraftModel model(modelToLookup); // copy
881  if (modelToLookup.isLoadedFromDb()) { return modelToLookup; }
882 
883  // --- now I try to fill in as many DB data as possible ---
884  // 1) This will unify data where possible
885  // 2) I have full information of what the other pilot flies where possible
886  // 3) This is not model matching here (!), it is a process of getting the most accurate data from that fuzzy
887  // information I get via FSD
888  //
889  // Reverse lookup, use DB data wherever possible
890  // 1) If I cannot resolve the ICAO codes here, they are either wrong (most likely in most cases) or
891  // 2) not in the DB yet
892  const CCallsign callsign(model.getCallsign());
893 
894  do {
895  if (modelToLookup.hasModelString())
896  {
897  if (!setup.isReverseLookupModelString())
898  {
899  if (log)
900  {
901  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Model string looup disabled"));
902  }
903  }
904  else
905  {
906  const QString modelString = modelToLookup.getModelString();
907 
908  // if we find the model here we have a fully defined DB model
910  modelString, callsign, setup.isReverseLookupModelString(), log);
911  if (modelFromDb.hasValidDbKey())
912  {
913  model = modelFromDb;
914  break; // done here
915  }
916 
917  // const bool useNonDbEntries = setup.isDbDataOnly();
918  const bool useNonDbEntries = true;
920  modelString, callsign, modelSet, useNonDbEntries, log);
921  if (modelFromSet.hasModelString())
922  {
923  model = modelFromSet;
924  break; // done here
925  }
926  }
927  }
928 
929  // lookup if model is not yet from DB
930  const DBTripleIds ids = CAircraftModel::parseNetworkLiveryString(networkLiveryInfo);
931  if (log)
932  {
933  CCallsign::addLogDetailsToList(log, callsign,
934  QStringLiteral("Livery string with ids: '%1'").arg(ids.toQString()));
935  }
936 
937  if (!setup.isReverseLookupSwiftLiveryIds())
938  {
939  if (log)
940  {
941  CCallsign::addLogDetailsToList(
942  log, callsign,
943  QStringLiteral("Ignoring livery ids '%1', because of setup").arg(ids.toQString()));
944  }
945  }
946  else if (ids.model >= 0 && !modelToLookup.hasModelString())
947  {
948  if (log)
949  {
950  CCallsign::addLogDetailsToList(log, callsign,
951  QStringLiteral("Model lookup with id %1 from triple ids '%2'")
952  .arg(ids.model)
953  .arg(ids.toQString()));
954  }
955  const CAircraftModel modelFromDb = CAircraftMatcher::reverseLookupModelId(ids.model, callsign, log);
956  if (modelFromDb.hasValidDbKey())
957  {
958  model = modelFromDb;
959  break;
960  }
961  }
962 
963  // no direct resolution of model, try livery and aircraft ICAO
964  if (!modelToLookup.getAircraftIcaoCode().hasValidDbKey() && ids.aircraft >= 0)
965  {
966  if (log)
967  {
968  CCallsign::addLogDetailsToList(
969  log, callsign,
970  QStringLiteral("Aircraft ICAO lookup with id %1 from triple ids '%2'")
971  .arg(ids.aircraft)
972  .arg(ids.toQString()));
973  }
974  const CAircraftIcaoCode icaoFromDb =
976  if (icaoFromDb.hasValidDbKey()) { model.setAircraftIcaoCode(icaoFromDb); }
977  }
978 
979  if (!modelToLookup.getLivery().hasValidDbKey() && ids.livery >= 0)
980  {
981  if (log)
982  {
983  CCallsign::addLogDetailsToList(log, callsign,
984  QStringLiteral("Livery lookup with id %1 from triple ids '%2'")
985  .arg(ids.livery)
986  .arg(ids.toQString()));
987  }
988  const CLivery liveryFromDb = CAircraftMatcher::reverseLookupLiveryId(ids.livery, callsign, log);
989  if (liveryFromDb.hasValidDbKey()) { model.setLivery(liveryFromDb); }
990  }
991 
992  // aircraft ICAO if not from DB yet
993  if (!model.getAircraftIcaoCode().hasValidDbKey())
994  {
995  CAircraftIcaoCode reverseIcaoCode(model.getAircraftIcaoCode());
996  if (!reverseIcaoCode.isLoadedFromDb())
997  {
998  reverseIcaoCode = CAircraftMatcher::reverseLookupAircraftIcao(reverseIcaoCode, callsign, log);
999  if (reverseIcaoCode.isLoadedFromDb())
1000  {
1001  if (log)
1002  {
1003  CCallsign::addLogDetailsToList(log, callsign,
1004  QStringLiteral("Set aircraft ICAO to '%1' from DB")
1005  .arg(reverseIcaoCode.getCombinedIcaoStringWithKey()));
1006  }
1007  model.setAircraftIcaoCode(reverseIcaoCode);
1008  }
1009  else
1010  {
1011  // no DB data
1012  if (log)
1013  {
1014  CCallsign::addLogDetailsToList(
1015  log, callsign,
1016  QStringLiteral("Reverse lookup, ICAO '%1' not resolved from DB")
1017  .arg(reverseIcaoCode.getDesignator()));
1018  }
1019  }
1020  }
1021  }
1022 
1023  // check if livery is already from DB
1024  if (!model.getLivery().isLoadedFromDb())
1025  {
1026  CAirlineIcaoCode airlineIcaoCode(model.getAirlineIcaoCode());
1027  if (!airlineIcaoCode.isLoadedFromDb())
1028  {
1029  airlineIcaoCode = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcaoCode, callsign, log);
1030  }
1031 
1032  // try to match by livery
1033  QString liveryCode = networkLiveryInfo;
1034  if (liveryCode.isEmpty() && airlineIcaoCode.hasValidDesignator())
1035  {
1036  // we create a standard livery code, then we try to find based on this
1037  liveryCode = CLivery::getStandardCode(airlineIcaoCode);
1038  }
1039 
1040  if (CLivery::isValidCombinedCode(liveryCode))
1041  {
1042  // search livery by combined code
1043  const CLivery reverseLivery(sApp->getWebDataServices()->getLiveryForCombinedCode(liveryCode));
1044  if (reverseLivery.hasValidDbKey())
1045  {
1046  // we have found a livery in the DB
1047  model.setLivery(reverseLivery);
1048  if (log)
1049  {
1050  CCallsign::addLogDetailsToList(log, callsign,
1051  QStringLiteral("Reverse lookup of livery found '%1'")
1052  .arg(reverseLivery.getCombinedCodePlusInfoAndId()),
1053  getLogCategories());
1054  }
1055  }
1056  else
1057  {
1058  // no livery data found
1059  if (log)
1060  {
1061  CCallsign::addLogDetailsToList(
1062  log, callsign,
1063  QStringLiteral("Reverse lookup of livery '%1' yielded no result")
1064  .arg(reverseLivery.getCombinedCodePlusInfo()),
1065  getLogCategories());
1066  }
1067  }
1068  } // livery lookup
1069 
1070  // if no DB livery yet, create own livery
1071  if (!model.hasValidDbKey() && !model.getLivery().hasValidDbKey())
1072  {
1073  if (airlineIcaoCode.hasValidDesignator())
1074  {
1075  if (airlineIcaoCode.hasValidDbKey())
1076  {
1077  const CLivery stdLivery(
1078  sApp->getWebDataServices()->getStdLiveryForAirlineCode(airlineIcaoCode));
1079  if (stdLivery.hasValidDbKey())
1080  {
1081  model.setLivery(stdLivery);
1082  if (log)
1083  {
1084  CCallsign::addLogDetailsToList(log, callsign,
1085  QStringLiteral("Set standardlivery `%1`")
1086  .arg(stdLivery.getCombinedCodePlusInfo()));
1087  }
1088  }
1089  }
1090 
1091  if (!model.getLivery().hasValidDbKey())
1092  {
1093  // create a pseudo livery, try to find airline first
1094  const CLivery liveryDummy(CLivery::getStandardCode(airlineIcaoCode), airlineIcaoCode,
1095  "Generated");
1096  model.setLivery(liveryDummy);
1097  if (log)
1098  {
1099  CCallsign::addLogDetailsToList(log, callsign,
1100  QStringLiteral("Generated livery, set livery `%1`")
1101  .arg(liveryDummy.getCombinedCodePlusInfo()));
1102  }
1103  }
1104  }
1105  } // pseudo livery
1106  } // livery from DB
1107  }
1108  while (false);
1109 
1110  model.setCallsign(callsign);
1111  model.setModelType(modelToLookup.getModelType());
1112 
1113  if (log)
1114  {
1115  CCallsign::addLogDetailsToList(log, callsign,
1116  QStringLiteral("Using model: ICAO '%1', livery '%2', model '%3', type '%4'")
1119  model.getModelTypeAsString()));
1120  }
1121  return model;
1122  }
1123 
1125  const QString &networkLiveryInfo,
1126  const CAircraftMatcherSetup &setup,
1127  const CAircraftModelList &modelSet, CStatusMessageList *log)
1128  {
1129  CAircraftModel reverseModel = reverseLookupModel(modelToLookup, networkLiveryInfo, setup, modelSet, log);
1130  if (!setup.doRunMsReverseLookupScript()) { return reverseModel; }
1131  const CCallsign cs = modelToLookup.getCallsign();
1132  const MatchingScriptReturnValues rv = CAircraftMatcher::reverseLookupScript(reverseModel, setup, modelSet, log);
1133  if (rv.runScriptModifiedAndRerun())
1134  {
1135  CCallsign::addLogDetailsToList(log, cs,
1136  QStringLiteral("Matching script: Modified value and requested rerun"));
1137 
1138  // no script the 2nd time
1139  CAircraftMatcherSetup setupRerun(setup);
1140  setupRerun.resetReverseLookup();
1141  reverseModel = CAircraftMatcher::reverseLookupModel(rv.model, networkLiveryInfo, setupRerun, modelSet, log);
1142  return reverseModel;
1143  }
1144  return (rv.runScriptAndModified() ? rv.model : reverseModel);
1145  }
1146 
1147  CAircraftModel CAircraftMatcher::reverseLookupModelStringInDB(const QString &modelString, const CCallsign &callsign,
1148  bool doLookupString, CStatusMessageList *log)
1149  {
1150  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
1151  if (!doLookupString)
1152  {
1153  if (log)
1154  {
1155  CCallsign::addLogDetailsToList(
1156  log, callsign,
1157  QStringLiteral("Ignore model string in reverse lookup (disabled), ignoring '%1'").arg(modelString));
1158  }
1159  return CAircraftModel();
1160  }
1162  const bool isDBModel = model.hasValidDbKey();
1163  if (log)
1164  {
1165  if (isDBModel)
1166  {
1167  CCallsign::addLogDetailsToList(
1168  log, callsign,
1169  QStringLiteral("Found model in DB for model string '%1' dist: '%2' descr.: '%3'")
1170  .arg(model.getModelStringAndDbKey(), model.getDistributor().getDbKey(),
1171  model.getDescription()));
1172  }
1173  else
1174  {
1175  CCallsign::addLogDetailsToList(
1176  log, callsign, QStringLiteral("Did not find model in DB for model string '%1'").arg(modelString));
1177  }
1178  }
1179 
1180  if (!isDBModel) { return CAircraftModel(); } // not found
1181 
1182  // found
1183  model.setCallsign(callsign);
1185  return model;
1186  }
1187 
1189  const CCallsign &callsign,
1190  const CAircraftModelList &modelSet,
1191  bool useNonDbEntries, CStatusMessageList *log)
1192  {
1193  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
1194  if (modelString.isEmpty())
1195  {
1196  if (log)
1197  {
1198  CCallsign::addLogDetailsToList(
1199  log, callsign, QStringLiteral("Empty model string for lookup in %1 models").arg(modelSet.size()));
1200  }
1201  return CAircraftModel();
1202  }
1203  if (modelSet.isEmpty())
1204  {
1205  if (log)
1206  {
1207  CCallsign::addLogDetailsToList(log, callsign,
1208  QStringLiteral("Empty models, ignoring '%1'").arg(modelString));
1209  }
1210  return CAircraftModel();
1211  }
1212 
1213  CAircraftModel model = modelSet.findFirstByModelStringOrDefault(modelString, Qt::CaseInsensitive);
1214  if (!model.hasModelString())
1215  {
1216  if (log)
1217  {
1218  CCallsign::addLogDetailsToList(
1219  log, callsign,
1220  QStringLiteral("Model '%1' not found in %2 models").arg(modelString).arg(modelSet.size()));
1221  }
1222  return CAircraftModel();
1223  }
1224 
1225  const bool isDBModel = model.hasValidDbKey();
1226  if (log)
1227  {
1228  if (isDBModel)
1229  {
1230  CCallsign::addLogDetailsToList(log, callsign,
1231  QStringLiteral("Found DB model in %1 models for model string '%2'")
1232  .arg(modelSet.size())
1233  .arg(model.getModelStringAndDbKey()));
1234  }
1235  else
1236  {
1237  CCallsign::addLogDetailsToList(log, callsign,
1238  QStringLiteral("Found NON DB model in %1 models for model string '%2'")
1239  .arg(modelSet.size())
1240  .arg(model.getModelString()));
1241  }
1242  }
1243 
1244  if (!isDBModel && !useNonDbEntries) { return CAircraftModel(); } // ignore DB entries
1245 
1246  // found
1247  model.setCallsign(callsign);
1249  return model;
1250  }
1251 
1253  {
1254  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftModel(); }
1256  if (log)
1257  {
1258  if (model.hasValidDbKey())
1259  {
1260  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Found model in DB for id '%1'").arg(id));
1261  }
1262  else
1263  {
1264  CCallsign::addLogDetailsToList(log, callsign,
1265  QStringLiteral("Did not find model in DB for id '%1'").arg(id));
1266  }
1267  }
1268  model.setCallsign(callsign);
1270  return model;
1271  }
1272 
1274  const CCallsign &logCallsign, CStatusMessageList *log)
1275  {
1276  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftIcaoCode(); }
1277 
1278  const QString designator(icaoCandidate.getDesignator());
1280 
1281  if (foundIcaos.isEmpty())
1282  {
1283  CAircraftIcaoCode icao(designator);
1284 
1285  // sometimes from network we receive something like "CESSNA C172"
1286  if (CAircraftIcaoCode::isValidDesignator(designator))
1287  {
1288  CCallsign::addLogDetailsToList(
1289  log, logCallsign,
1290  QStringLiteral("Reverse lookup of aircraft ICAO '%1' did not find anything, using smart search")
1291  .arg(designator),
1293  icao = sApp->getWebDataServices()->smartAircraftIcaoSelector(icaoCandidate);
1294  }
1295  else
1296  {
1297  CCallsign::addLogDetailsToList(
1298  log, logCallsign,
1299  QStringLiteral("Reverse lookup of invalid ICAO code '%1' did not find anything so far")
1300  .arg(designator),
1302  const QStringList parts(designator.split(' '));
1303  for (const QString &p : parts)
1304  {
1305  CCallsign::addLogDetailsToList(
1306  log, logCallsign,
1307  QStringLiteral("Trying parts, now reverse lookup of aircraft ICAO '%1' using smart search")
1308  .arg(p),
1311  if (icao.isLoadedFromDb()) break;
1312  }
1313  }
1314  if (icao.isLoadedFromDb())
1315  {
1316  // smart search found DB data
1317  foundIcaos = CAircraftIcaoCodeList({ icao });
1318  }
1319  else
1320  {
1321  CCallsign::addLogDetailsToList(log, logCallsign,
1322  QStringLiteral("No DB data for ICAO '%1', valid ICAO?").arg(designator),
1324  return CAircraftIcaoCode(icaoCandidate);
1325  }
1326  }
1327 
1328  if (foundIcaos.isEmpty())
1329  {
1330  CCallsign::addLogDetailsToList(
1331  log, logCallsign, QStringLiteral("Reverse lookup of aircraft ICAO '%1', nothing found").arg(designator),
1333  return CAircraftIcaoCode(icaoCandidate);
1334  }
1335  else if (foundIcaos.size() == 1)
1336  {
1337  const CAircraftIcaoCode icao(foundIcaos.front());
1338  CCallsign::addLogDetailsToList(
1339  log, logCallsign,
1340  QStringLiteral("Reverse lookup of aircraft ICAO '%1', found one manufacturer '%2' in DB")
1341  .arg(designator, icao.getDesignatorManufacturer()),
1343  return icao;
1344  }
1345  else
1346  {
1347  // multiple ICAOs
1348  Q_ASSERT_X(foundIcaos.size() > 1, Q_FUNC_INFO, "Wrong size");
1349  const QPair<QString, int> maxManufacturer = foundIcaos.maxCountManufacturer();
1350  CCallsign::addLogDetailsToList(
1351  log, logCallsign,
1352  QStringLiteral("Reverse lookup of aircraft ICAO '%1', found %2 values (ambiguous): %3")
1353  .arg(designator)
1354  .arg(foundIcaos.size())
1355  .arg(foundIcaos.dbKeysAsString(", ")),
1357  if (maxManufacturer.second < foundIcaos.size())
1358  {
1359  foundIcaos = foundIcaos.findByManufacturer(maxManufacturer.first);
1360  CCallsign::addLogDetailsToList(log, logCallsign,
1361  QStringLiteral("Reducing by manufacturer '%1', now %2 values")
1362  .arg(maxManufacturer.first)
1363  .arg(foundIcaos.size()),
1365  }
1366  foundIcaos.sortByRank();
1367  const CAircraftIcaoCode icao = foundIcaos.front(); // best rank
1368  CCallsign::addLogDetailsToList(
1369  log, logCallsign,
1370  QStringLiteral("Reverse lookup of aircraft ICAO '%1', using ICAO '%2' with rank %3")
1371  .arg(designator, icao.toQString(), icao.getRankString()),
1373  return icao;
1374  }
1375  }
1376 
1378  CStatusMessageList *log)
1379  {
1380  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftIcaoCode(); }
1382  if (log)
1383  {
1384  if (icao.hasValidDbKey())
1385  {
1386  CCallsign::addLogDetailsToList(log, logCallsign,
1387  QStringLiteral("Found aircraft ICAO in DB for id '%1'").arg(id));
1388  }
1389  else
1390  {
1391  CCallsign::addLogDetailsToList(log, logCallsign,
1392  QStringLiteral("Did not find aircraft ICAO in DB for id '%1'").arg(id));
1393  }
1394  }
1395  return icao;
1396  }
1397 
1399  const CCallsign &callsign, CStatusMessageList *log)
1400  {
1401  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAirlineIcaoCode(); }
1402  const CAirlineIcaoCode icao = sApp->getWebDataServices()->smartAirlineIcaoSelector(icaoPattern, callsign);
1403  if (log)
1404  {
1405  if (icao.hasValidDbKey())
1406  {
1407  CCallsign::addLogDetailsToList(
1408  log, callsign,
1409  QStringLiteral("Reverse lookup of airline ICAO '%1' and callsign '%2' found '%3' '%4' in DB")
1410  .arg(icaoPattern.getDesignator(), callsign.asString(), icao.getVDesignatorDbKey(),
1411  icao.getName()),
1413  }
1414  else
1415  {
1416  CCallsign::addLogDetailsToList(
1417  log, callsign,
1418  QStringLiteral("Reverse lookup of airline ICAO '%1' and callsign '%2', nothing found in DB")
1419  .arg(icaoPattern.getDesignator(), callsign.asString()),
1421  }
1422  }
1423  return icao;
1424  }
1425 
1427  CStatusMessageList *log)
1428  {
1429  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CLivery(); }
1430  if (!airline.hasValidDesignator())
1431  {
1432  if (log)
1433  {
1434  CCallsign::addLogDetailsToList(
1435  log, callsign, QStringLiteral("Reverse lookup of standard livery skipped, no airline designator"),
1437  }
1438  return CLivery();
1439  }
1440 
1441  const CLivery livery = sApp->getWebDataServices()->getStdLiveryForAirlineCode(airline);
1442  if (log)
1443  {
1444  if (livery.hasValidDbKey())
1445  {
1446  CCallsign::addLogDetailsToList(log, callsign,
1447  QStringLiteral("Reverse lookup of standard livery for '%1' found '%2'")
1448  .arg(airline.getDesignator(), livery.getCombinedCode()),
1450  }
1451  else
1452  {
1453  CCallsign::addLogDetailsToList(
1454  log, callsign,
1455  QStringLiteral("Not standard livery for airline '%1' in DB").arg(airline.getDesignator()),
1457  }
1458  }
1459  return livery;
1460  }
1461 
1463  {
1464  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CLivery(); }
1465  const CLivery livery = sApp->getWebDataServices()->getLiveryForDbKey(id);
1466  if (log)
1467  {
1468  if (livery.hasValidDbKey())
1469  {
1470  CCallsign::addLogDetailsToList(log, logCallsign,
1471  QStringLiteral("Found livery in DB for id '%1'").arg(id));
1472  }
1473  else
1474  {
1475  CCallsign::addLogDetailsToList(log, logCallsign,
1476  QStringLiteral("Did not find livery in DB for id '%1'").arg(id));
1477  }
1478  }
1479  return livery;
1480  }
1481 
1483  const CCallsign &logCallsign, CStatusMessageList *log)
1484  {
1485  int found = 0;
1486  if (ids.livery >= 0)
1487  {
1488  livery = CAircraftMatcher::reverseLookupLiveryId(ids.livery, logCallsign, log);
1489  if (livery.hasValidDbKey()) { found++; }
1490  }
1491 
1492  if (ids.aircraft >= 0)
1493  {
1494  aircraftIcao = CAircraftMatcher::reverseLookupAircraftIcaoId(ids.aircraft, logCallsign, log);
1495  if (aircraftIcao.hasValidDbKey()) { found++; }
1496  }
1497  return found;
1498  }
1499 
1500  QString CAircraftMatcher::reverseLookupAirlineName(const QString &candidate, const CCallsign &callsign,
1501  CStatusMessageList *log)
1502  {
1503  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return {}; }
1504  if (candidate.isEmpty()) { return {}; }
1505  const QStringList names = sApp->getWebDataServices()->getAirlineNames();
1506  if (names.contains(candidate, Qt::CaseInsensitive))
1507  {
1508  CCallsign::addLogDetailsToList(log, callsign,
1509  QStringLiteral("Airline name '%1' found in DB").arg(candidate));
1510  return candidate;
1511  }
1512 
1513  CCallsign::addLogDetailsToList(log, callsign,
1514  QStringLiteral("Airline name '%1' not found in DB").arg(candidate));
1515  return {};
1516  }
1517 
1518  QString CAircraftMatcher::reverseLookupTelephonyDesignator(const QString &candidate, const CCallsign &callsign,
1519  CStatusMessageList *log)
1520  {
1521  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return {}; }
1522  if (candidate.isEmpty()) { return {}; }
1523  const QStringList designators = sApp->getWebDataServices()->getTelephonyDesignators();
1524  if (designators.contains(candidate, Qt::CaseInsensitive))
1525  {
1526  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Airline name '%1' found").arg(candidate));
1527  return candidate;
1528  }
1529 
1530  CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("Airline name '%1' not found").arg(candidate));
1531  return {};
1532  }
1533 
1534  bool CAircraftMatcher::isKnownAircraftDesignator(const QString &candidate, const CCallsign &callsign,
1535  CStatusMessageList *log)
1536  {
1537  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return false; }
1538  if (!CAircraftIcaoCode::isValidDesignator(candidate))
1539  {
1540  CCallsign::addLogDetailsToList(log, callsign,
1541  QStringLiteral("No valid ICAO designator '%1'").arg(candidate));
1542  return false;
1543  }
1544 
1545  const bool known = sApp->getWebDataServices()->containsAircraftIcaoDesignator(candidate);
1546  static const QString sKnown("Known ICAO designator '%1'");
1547  static const QString sUnknown("Unknown ICAO designator '%1'");
1548  CCallsign::addLogDetailsToList(log, callsign, known ? sKnown.arg(candidate) : sUnknown.arg(candidate));
1549  return known;
1550  }
1551 
1552  bool CAircraftMatcher::isKnownModelString(const QString &candidate, const CCallsign &callsign,
1553  CStatusMessageList *log)
1554  {
1555  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return false; }
1556  const bool known = sApp->getWebDataServices()->containsModelString(candidate);
1557  static const QString sKnown("Known modelstring '%1'");
1558  static const QString sUnknown("Unknown modelstring '%1'");
1559  CCallsign::addLogDetailsToList(log, callsign, known ? sKnown.arg(candidate) : sUnknown.arg(candidate));
1560  return known;
1561  }
1562 
1564  const CAirlineIcaoCode &airline,
1565  const CCallsign &callsign, CStatusMessageList *log)
1566  {
1567  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAircraftIcaoCode(); }
1568  if (!airline.isLoadedFromDb())
1569  {
1570  CCallsign::addLogDetailsToList(
1571  log, callsign, QStringLiteral("No valid airline from DB '%1'").arg(airline.getDesignator()));
1572  return CAircraftIcaoCode();
1573  }
1574 
1575  Q_ASSERT_X(sApp->getWebDataServices(), Q_FUNC_INFO, "No web services");
1576 
1578  if (aircraft.isEmpty())
1579  {
1580  CCallsign::addLogDetailsToList(
1581  log, callsign, QStringLiteral("No aircraft known for airline '%1'").arg(airline.getDesignator()));
1582  return CAircraftIcaoCode();
1583  }
1584 
1585  const QSet<QString> allIcaos = aircraft.allDesignators();
1586  const QString allIcaosStr = allIcaos.values().join(", ");
1587  CCallsign::addLogDetailsToList(
1588  log, callsign,
1589  QStringLiteral("Aircraft '%1' known for airline '%2'").arg(allIcaosStr, airline.getDesignator()));
1590 
1591  const CAircraftIcaoCode code = aircraft.findBestFuzzyMatchOrDefault(candidateString);
1592  if (code.hasValidDesignator())
1593  {
1594  CCallsign::addLogDetailsToList(log, callsign,
1595  QStringLiteral("Aircraft '%1' is best fuzzy search of '%2' for airline '%3'")
1596  .arg(code.toQString(), candidateString, airline.getDesignator()));
1597  return code;
1598  }
1599 
1600  return aircraft.front();
1601  }
1602 
1604  {
1605  if (callsign.isEmpty() || !sApp || !sApp->getWebDataServices()) { return CAirlineIcaoCode(); }
1607 
1608  if (icao.hasValidDesignator())
1609  {
1610  CCallsign::addLogDetailsToList(
1611  log, callsign,
1612  QStringLiteral("Turned callsign %1 into airline %2").arg(callsign.asString(), icao.getDesignator()),
1613  getLogCategories());
1614  }
1615  else
1616  {
1617  CCallsign::addLogDetailsToList(
1618  log, callsign, QStringLiteral("Cannot turn callsign %1 into airline").arg(callsign.asString()),
1619  getLogCategories());
1620  }
1621  return icao;
1622  }
1623 
1624  int CAircraftMatcher::setModelSet(const CAircraftModelList &models, const CSimulatorInfo &simulator, bool forced)
1625  {
1626  if (!simulator.isSingleSimulator()) { return 0; }
1627 
1628  CAircraftModelList modelsCleaned(models);
1629  const int r1 = modelsCleaned.removeAllWithoutModelString();
1630  const int r2 = modelsCleaned.removeIfExcluded();
1631 
1632  // sane simulator and model count? (minor risk of not updating when a model was changed)
1633  if (!forced && m_simulator == simulator && m_modelSet.size() == modelsCleaned.size())
1634  {
1635  return m_modelSet.size();
1636  }
1637 
1638  QString warnings;
1639  if ((r1 + r2) > 0)
1640  {
1641  warnings =
1642  QStringLiteral("Removed models for matcher, without string #: %1, excluded #: %2.").arg(r1).arg(r2);
1643  if (r1 > 0)
1644  {
1645  warnings += QStringLiteral(" Without string: '%1'.")
1646  .arg(models.findEmptyModelStrings().getModelStringList().join(", "));
1647  }
1648  if (r2 > 0)
1649  {
1650  warnings += QStringLiteral(" Excluded: '%1'.")
1651  .arg(models.findByModelMode(CAircraftModel::Exclude).getModelStringList().join(", "));
1652  }
1653  }
1654  const CAircraftModelList duplicateModels = modelsCleaned.findDuplicateModelStrings();
1655 
1656  // Warning info
1657  if (modelsCleaned.isEmpty())
1658  {
1659  // error to force popup
1660  CLogMessage(this).error(u"No models for matching ('%1'), swift without a model set will not work!")
1661  << simulator.toQString();
1662  }
1663  else if (!duplicateModels.isEmpty())
1664  {
1665  CLogMessage(this).error(u"Found model duplicate strings, check models: '%1'")
1666  << duplicateModels.dbKeysAsString(", ");
1667  }
1668  else if (!warnings.isEmpty()) { CLogMessage(this).validationWarning(warnings); }
1669  else
1670  {
1671  CLogMessage(this).validationInfo(u"Set %1 models in matcher, simulator '%2'")
1672  << modelsCleaned.size() << simulator.toQString();
1673  }
1674 
1675  // set values
1676  m_modelSet = modelsCleaned;
1677  m_simulator = simulator;
1678  m_modelSetInfo = QStringLiteral("Set: '%1' entries: %2").arg(simulator.toQString()).arg(modelsCleaned.size());
1679  return models.size();
1680  }
1681 
1682  void CAircraftMatcher::disableModelsForMatching(const CAircraftModelList &removedModels, bool incremental)
1683  {
1684  if (incremental)
1685  {
1686  m_modelSet.removeModelsWithString(removedModels, Qt::CaseInsensitive);
1687  m_disabledModels.push_back(removedModels);
1688  }
1689  else
1690  {
1691  this->restoreDisabledModels();
1692  m_disabledModels = removedModels;
1693  m_modelSet.removeModelsWithString(removedModels, Qt::CaseInsensitive);
1694  }
1695  }
1696 
1698  {
1699  m_modelSet.replaceOrAddModelsWithString(m_disabledModels, Qt::CaseInsensitive);
1700  }
1701 
1703  {
1704  m_defaultModel = defaultModel;
1706  }
1707 
1708  void CAircraftMatcher::evaluateStatisticsEntry(const QString &sessionId, const CCallsign &callsign,
1709  const QString &aircraftIcao, const QString &airlineIcao,
1710  const QString &livery)
1711  {
1712  Q_UNUSED(livery)
1713  Q_ASSERT_X(sApp && sApp->hasWebDataServices(), Q_FUNC_INFO, "Missing web data services");
1714  if (m_modelSet.isEmpty()) { return; } // ignore empty sets to not create silly stats
1715  if (sessionId.isEmpty()) { return; }
1716  if (aircraftIcao.isEmpty()) { return; }
1717 
1718  QString description;
1720  {
1721  description = QStringLiteral("ICAO: '%1' not known, typo?").arg(aircraftIcao);
1722  }
1723 
1724  // resolve airline, mostly needed because of vPilot not sending airline ICAO codes in version 1
1725  CAirlineIcaoCode airlineIcaoChecked(airlineIcao);
1726  if (airlineIcao.isEmpty())
1727  {
1728  const CAirlineIcaoCode al = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcao, callsign);
1729  if (al.isLoadedFromDb()) { airlineIcaoChecked = al; }
1730  }
1731 
1732  CMatchingStatisticsEntry::EntryType type = CMatchingStatisticsEntry::Missing;
1733 
1734  type = m_modelSet.containsModelsWithAircraftAndAirlineIcaoDesignator(aircraftIcao, airlineIcao) ?
1735  CMatchingStatisticsEntry::Found :
1736  CMatchingStatisticsEntry::Missing;
1737 
1738  m_statistics.addAircraftAirlineCombination(type, sessionId, m_modelSetInfo, description, aircraftIcao,
1739  airlineIcao);
1740  }
1741 
1743  {
1744  if (!m_setup.removeFromSetIfFailed()) { return; }
1745  if (remoteAircraft.hasCallsign() && remoteAircraft.hasModelString())
1746  {
1747  const QString modelString = remoteAircraft.getModelString();
1748  const CAircraftModelList disabledModels({ remoteAircraft.getModel() });
1749  this->disableModelsForMatching(disabledModels, true);
1750  CLogMessage(this).warning(u"Disabled CS: '%1' model '%2' for matching")
1751  << remoteAircraft.getCallsignAsString() << modelString;
1752  }
1753  else { CLogMessage(this).warning(u"Disabled '%1' for matching") << remoteAircraft.toQString(true); }
1754  }
1755 
1756  bool CAircraftMatcher::saveDisabledForMatchingModels()
1757  {
1758  if (m_disabledModels.isEmpty()) { return false; }
1759 
1760  // log the models
1761  const QString ts = QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss");
1762  const QString json = m_disabledModels.toJsonString();
1765  QStringLiteral("removed models %1.json").arg(ts)));
1766  }
1767 
1768  CAircraftModelList CAircraftMatcher::getClosestMatchStepwiseReduceImplementation(
1769  const CAircraftModelList &modelSet, const CAircraftMatcherSetup &setup, const CCategoryMatcher &categoryMatcher,
1770  const CSimulatedAircraft &remoteAircraft, MatchingLog whatToLog, CStatusMessageList *log)
1771  {
1772  CAircraftModelList matchedModels(modelSet);
1773  CAircraftModel matchedModel(remoteAircraft.getModel());
1774  Q_UNUSED(whatToLog)
1775 
1776  const CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
1777  CStatusMessageList *reduceLog = log && whatToLog.testFlag(MatchingLogStepwiseReduce) ? log : nullptr;
1778  bool reduced = false;
1779  do {
1780  // by livery, then by ICAO
1781  if (mode.testFlag(CAircraftMatcherSetup::ByLivery))
1782  {
1783  matchedModels =
1784  ifPossibleReduceByLiveryAndAircraftIcaoCode(remoteAircraft, matchedModels, reduced, log);
1785  if (reduced) { break; } // almost perfect, we stop here (we have ICAO + livery match)
1786  }
1787  else if (reduceLog)
1788  {
1789  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1790  QStringLiteral("Skipping livery reduction"), getLogCategories());
1791  }
1792 
1793  if (setup.getMatchingMode().testFlag(CAircraftMatcherSetup::ByIcaoData))
1794  {
1795  // by airline/aircraft or by aircraft/airline depending on setup
1796  // family is also considered
1797  matchedModels = ifPossibleReduceByIcaoData(remoteAircraft, matchedModels, setup, reduced, log);
1798  }
1799  else if (reduceLog)
1800  {
1801  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1802  QStringLiteral("Skipping ICAO reduction"), getLogCategories());
1803 
1804  // family only because aircraft ICAO is not used
1805  if (mode.testFlag(CAircraftMatcherSetup::ByFamily))
1806  {
1807  QString usedFamily;
1808  matchedModels = ifPossibleReduceByFamily(remoteAircraft, UsePseudoFamily, matchedModels, reduced,
1809  usedFamily, log);
1810  if (reduced) { break; }
1811  }
1812  else if (reduceLog)
1813  {
1814  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1815  QStringLiteral("Skipping family match"), getLogCategories());
1816  }
1817  }
1818 
1819  if (setup.useCategoryMatching())
1820  {
1821  matchedModels = categoryMatcher.reduceByCategories(matchedModels, modelSet, setup, remoteAircraft,
1822  reduced, whatToLog, log);
1823  // ?? break here ??
1824  }
1825  else if (reduceLog)
1826  {
1827  CMatchingUtils::addLogDetailsToList(reduceLog, remoteAircraft,
1828  QStringLiteral("category matching disabled"), getLogCategories());
1829  }
1830 
1831  // if not yet reduced, reduce to VTOL
1832  if (!reduced && remoteAircraft.isVtol() && matchedModels.containsVtol() &&
1833  mode.testFlag(CAircraftMatcherSetup::ByVtol))
1834  {
1835  matchedModels = matchedModels.findByVtolFlag(true);
1837  log, remoteAircraft, QStringLiteral("Aircraft is VTOL, reduced to VTOL"), getLogCategories());
1838  }
1839 
1840  // military / civilian
1841  bool milFlagReduced = false;
1842  if (mode.testFlag(CAircraftMatcherSetup::ByMilitary) && remoteAircraft.isMilitary())
1843  {
1844  matchedModels = ifPossibleReduceByMilitaryFlag(remoteAircraft, matchedModels, reduced, reduceLog);
1845  milFlagReduced = true;
1846  }
1847 
1848  if (!milFlagReduced && mode.testFlag(CAircraftMatcherSetup::ByCivilian) && !remoteAircraft.isMilitary())
1849  {
1850  matchedModels = ifPossibleReduceByMilitaryFlag(remoteAircraft, matchedModels, reduced, reduceLog);
1851  milFlagReduced = true;
1852  }
1853 
1854  // combined code
1855  if (mode.testFlag(CAircraftMatcherSetup::ByCombinedType))
1856  {
1857  matchedModels =
1858  ifPossibleReduceByCombinedType(remoteAircraft, matchedModels, setup, reduced, reduceLog);
1859  if (reduced) { break; }
1860  }
1861  else if (log)
1862  {
1863  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("Skipping combined code match"),
1864  getLogCategories());
1865  }
1866  }
1867  while (false);
1868 
1869  // here we have a list of possible models, we reduce/refine further
1870  if (matchedModels.size() > 1 && mode.testFlag(CAircraftMatcherSetup::ByManufacturer))
1871  {
1872  matchedModels = ifPossibleReduceByManufacturer(remoteAircraft, matchedModels,
1873  QStringLiteral("2nd trial to reduce by manufacturer. "),
1874  reduced, reduceLog);
1875  }
1876 
1877  return matchedModels;
1878  }
1879 
1880  CAircraftModelList CAircraftMatcher::getClosestMatchScoreImplementation(const CAircraftModelList &modelSet,
1881  const CAircraftMatcherSetup &setup,
1882  const CSimulatedAircraft &remoteAircraft,
1883  int &maxScore, MatchingLog whatToLog,
1884  CStatusMessageList *log)
1885  {
1886  CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
1887  const bool noZeroScores = mode.testFlag(CAircraftMatcherSetup::ScoreIgnoreZeros);
1888  const bool preferColorLiveries = mode.testFlag(CAircraftMatcherSetup::ScorePreferColorLiveries);
1889  CStatusMessageList *scoreLog = log && whatToLog.testFlag(MatchingLogScoring) ? log : nullptr;
1890 
1891  // VTOL
1892  ScoredModels map;
1893  map = modelSet.scoreFull(remoteAircraft.getModel(), preferColorLiveries, noZeroScores, scoreLog);
1894 
1895  CAircraftModel matchedModel;
1896  if (map.isEmpty()) { return CAircraftModelList(); }
1897 
1898  maxScore = map.lastKey();
1899  const CAircraftModelList maxScoreAircraft(map.values(maxScore));
1900  CMatchingUtils::addLogDetailsToList(scoreLog, remoteAircraft,
1901  QStringLiteral("Scores: %1").arg(scoresToString(map)), getLogCategories());
1902  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
1903  QStringLiteral("Scoring with score %1 out of %2 models yielded %3 models")
1904  .arg(maxScore)
1905  .arg(map.size())
1906  .arg(maxScoreAircraft.size()),
1907  getLogCategories());
1908  return maxScoreAircraft;
1909  }
1910 
1911  CAircraftModel CAircraftMatcher::getCombinedTypeDefaultModel(const CAircraftModelList &modelSet,
1912  const CSimulatedAircraft &remoteAircraft,
1913  const CAircraftModel &defaultModel,
1914  MatchingLog whatToLog, CStatusMessageList *log)
1915  {
1916  const QString combinedType = remoteAircraft.getAircraftIcaoCombinedType();
1917  CStatusMessageList *combinedLog = log && whatToLog.testFlag(MatchingLogCombinedDefaultType) ? log : nullptr;
1918 
1919  if (combinedType.isEmpty())
1920  {
1921  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft,
1922  QStringLiteral("No combined type, using default"), getLogCategories(),
1924  return defaultModel;
1925  }
1926  if (modelSet.isEmpty())
1927  {
1928  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft, QStringLiteral("No models, using default"),
1930  return defaultModel;
1931  }
1932 
1933  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft,
1934  u"Searching by combined type with color livery '" % combinedType % "'",
1935  getLogCategories());
1936  CAircraftModelList matchedModels = modelSet.findByCombinedTypeWithColorLivery(combinedType);
1937  if (!matchedModels.isEmpty())
1938  {
1939  CMatchingUtils::addLogDetailsToList(combinedLog, remoteAircraft,
1940  u"Found " % QString::number(matchedModels.size()) %
1941  u" by combined type w/color livery '" % combinedType % "'",
1942  getLogCategories());
1943  }
1944  else
1945  {
1947  combinedLog, remoteAircraft, u"Searching by combined type '" % combinedType % "'", getLogCategories());
1948  matchedModels = matchedModels.findByCombinedType(combinedType);
1949  if (!matchedModels.isEmpty())
1950  {
1951  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
1952  u"Found " % QString::number(matchedModels.size()) %
1953  u" by combined '" % combinedType % "'",
1954  getLogCategories());
1955  }
1956  }
1957 
1958  // return
1959  if (matchedModels.isEmpty()) { return defaultModel; }
1960  return matchedModels.front();
1961  }
1962 
1963  CAircraftModel CAircraftMatcher::matchByExactModelString(const CSimulatedAircraft &remoteAircraft,
1964  const CAircraftModelList &models, MatchingLog whatToLog,
1965  CStatusMessageList *log)
1966  {
1967  CStatusMessageList *msLog = log && whatToLog.testFlag(MatchingLogModelstring) ? log : nullptr;
1968  if (remoteAircraft.getModelString().isEmpty())
1969  {
1970  if (msLog)
1971  {
1972  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
1973  QStringLiteral("No model string, no exact match possible"));
1974  }
1975  return CAircraftModel();
1976  }
1977 
1978  CAircraftModel model = models.findFirstByModelStringAliasOrDefault(remoteAircraft.getModelString());
1979  if (msLog)
1980  {
1981  if (model.hasModelString())
1982  {
1984  msLog, remoteAircraft, QStringLiteral("Found exact match for '%1'").arg(model.getModelString()));
1985  }
1986  else
1987  {
1989  msLog, remoteAircraft,
1990  QStringLiteral("No exact match for '%1'").arg(remoteAircraft.getModelString()));
1991  }
1992  }
1994  model.setCallsign(remoteAircraft.getCallsign());
1995  return model;
1996  }
1997 
1999  CAircraftMatcher::ifPossibleReduceByLiveryAndAircraftIcaoCode(const CSimulatedAircraft &remoteAircraft,
2000  const CAircraftModelList &inList, bool &reduced,
2001  CStatusMessageList *log)
2002  {
2003  reduced = false;
2004  if (!remoteAircraft.getLivery().hasCombinedCode())
2005  {
2006  if (log)
2007  {
2009  log, remoteAircraft, QStringLiteral("No livery code, no reduction possible"), getLogCategories());
2010  }
2011  return inList;
2012  }
2013 
2015  remoteAircraft.getLivery().getCombinedCode(), remoteAircraft.getAircraftIcaoCodeDesignator()));
2016 
2017  if (byLivery.isEmpty())
2018  {
2019  if (log)
2020  {
2022  log, remoteAircraft, u"Not found by livery code " % remoteAircraft.getLivery().getCombinedCode(),
2023  getLogCategories());
2024  }
2025  return inList;
2026  }
2027  reduced = true;
2028  return byLivery;
2029  }
2030 
2031  CAircraftModelList CAircraftMatcher::ifPossibleReduceByIcaoData(const CSimulatedAircraft &remoteAircraft,
2032  const CAircraftModelList &inList,
2033  const CAircraftMatcherSetup &setup, bool &reduced,
2034  CStatusMessageList *log)
2035  {
2036  const CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
2037  if (inList.isEmpty())
2038  {
2039  if (log)
2040  {
2041  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("Empty list, skipping step"),
2042  getLogCategories());
2043  }
2044  return inList;
2045  }
2046 
2047  reduced = false;
2048  if (mode.testFlag(CAircraftMatcherSetup::ByIcaoOrderAirlineFirst))
2049  {
2050  bool r1 = false;
2051  bool r2 = false;
2052  CAircraftModelList models = ifPossibleReduceByAirline(remoteAircraft, inList, setup,
2053  QStringLiteral("Reduce by airline first."), r1, log);
2054  models = ifPossibleReduceByAircraftOrFamily(remoteAircraft, UsePseudoFamily, models, setup,
2055  QStringLiteral("Reduce by aircraft ICAO second."), r2, log);
2056  reduced = r1 || r2;
2057  if (reduced) { return models; }
2058  }
2059  else if (mode.testFlag(CAircraftMatcherSetup::ByIcaoData))
2060  {
2061  bool r1 = false;
2062  bool r2 = false;
2063  CAircraftModelList models =
2064  ifPossibleReduceByAircraftOrFamily(remoteAircraft, UsePseudoFamily, inList, setup,
2065  QStringLiteral("Reduce by aircraft ICAO first."), r1, log);
2066  models = ifPossibleReduceByAirline(remoteAircraft, models, setup,
2067  QStringLiteral("Reduce aircraft ICAO by airline second."), r2, log);
2068 
2069  // not finding anything so far means we have no valid aircraft/airline ICAO combination
2070  // but it can happen we found B738, and for DLH there is no B738 but B737, so we search again
2071  if (!remoteAircraft.hasAirlineDesignator())
2072  {
2073  if (log)
2074  {
2075  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2076  "No airline, no secondary search for airline/aircraft",
2077  getLogCategories());
2078  }
2079  }
2080  else if (!r2 && mode.testFlag(CAircraftMatcherSetup::ByFamily))
2081  {
2082  if (log)
2083  {
2084  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2085  u"No exact ICAO match of '" %
2086  remoteAircraft.getAirlineAndAircraftIcaoCodeDesignators() %
2087  u"', will try family combination",
2088  getLogCategories());
2089  }
2090 
2091  bool r3 = false;
2092  QString usedFamily;
2093  CAircraftModelList models2nd =
2094  ifPossibleReduceByFamily(remoteAircraft, UsePseudoFamily, inList, r3, usedFamily, log);
2095  models2nd = ifPossibleReduceByAirline(remoteAircraft, models2nd, setup,
2096  "Reduce family by airline second.", r3, log);
2097  if (r3)
2098  {
2099  // we found family / airline combination
2100  if (log)
2101  {
2102  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2103  u"Found " % QString::number(models2nd.sizeInt()) %
2104  " aircraft family/airline '" % usedFamily %
2105  u"' combination",
2106  getLogCategories());
2107  }
2108  return models2nd;
2109  }
2110  }
2111 
2112  reduced = r1 || r2;
2113  if (reduced)
2114  {
2115  if (log)
2116  {
2117  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2118  u"Reduced by aircraft ICAO: " % boolToYesNo(r1) %
2119  u" airline: " % boolToYesNo(r2),
2120  getLogCategories());
2121  }
2122  return models;
2123  }
2124  }
2125 
2126  if (log)
2127  {
2128  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("No reduction by ICAO data"),
2129  getLogCategories());
2130  }
2131  return inList;
2132  }
2133 
2134  CAircraftModelList CAircraftMatcher::ifPossibleReduceByFamily(const CSimulatedAircraft &remoteAircraft,
2135  bool allowPseudoFamily,
2136  const CAircraftModelList &inList, bool &reduced,
2137  QString &usedFamily, CStatusMessageList *log)
2138  {
2139  reduced = false;
2140  usedFamily = remoteAircraft.getAircraftIcaoCode().getFamily();
2141  if (!usedFamily.isEmpty())
2142  {
2143  CAircraftModelList matchedModels =
2144  ifPossibleReduceByFamily(remoteAircraft, usedFamily, allowPseudoFamily, inList,
2145  QStringLiteral("real family from ICAO"), reduced, log);
2146  if (reduced) { return matchedModels; }
2147  }
2148 
2149  // scenario: the ICAO actually is the family
2150  usedFamily = remoteAircraft.getAircraftIcaoCodeDesignator();
2151  return ifPossibleReduceByFamily(remoteAircraft, usedFamily, allowPseudoFamily, inList,
2152  QStringLiteral("ICAO treated as family"), reduced, log);
2153  }
2154 
2155  CAircraftModelList CAircraftMatcher::ifPossibleReduceByFamily(const CSimulatedAircraft &remoteAircraft,
2156  const QString &family, bool allowPseudoFamily,
2157  const CAircraftModelList &inList, const QString &hint,
2158  bool &reduced, CStatusMessageList *log)
2159  {
2160  // Use an algorithm to find the best match
2161  reduced = false;
2162  if (family.isEmpty() && !allowPseudoFamily)
2163  {
2164  if (log)
2165  {
2166  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"No family, skipping step (" % hint % u")",
2167  getLogCategories());
2168  }
2169  return inList;
2170  }
2171 
2172  if (inList.isEmpty())
2173  {
2174  if (log)
2175  {
2176  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"No models for family match (" % hint % u")",
2177  getLogCategories());
2178  }
2179  return inList;
2180  }
2181 
2182  CAircraftModelList foundByFamily(inList.findByFamily(family));
2183  if (foundByFamily.isEmpty())
2184  {
2185  if (log)
2186  {
2187  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2188  u"Not found by family '" % family % u"' (" % hint % ")");
2189  }
2190  if (!allowPseudoFamily) { return inList; }
2191  // fallthru
2192  }
2193  else
2194  {
2195  if (log)
2196  {
2197  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2198  u"Found by family '" % family % u"' (" % hint % u") size " %
2199  QString::number(foundByFamily.sizeInt()),
2200  getLogCategories());
2201  }
2202  }
2203 
2204  CAircraftModelList foundByCM;
2205  if (allowPseudoFamily)
2206  {
2207  foundByCM = inList.findByCombinedAndManufacturer(remoteAircraft.getAircraftIcaoCode());
2208  const QString pseudo = remoteAircraft.getAircraftIcaoCode().getCombinedType() % "/" %
2209  remoteAircraft.getAircraftIcaoCode().getManufacturer();
2210  if (foundByCM.isEmpty())
2211  {
2212  if (log)
2213  {
2214  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2215  u"Not found by pseudo family '" % pseudo % u"' (" % hint % ")");
2216  }
2217  }
2218  else
2219  {
2220  if (log)
2221  {
2222  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2223  u"Found by pseudo family '" % pseudo % u"' (" % hint %
2224  u") size " % QString::number(foundByCM.sizeInt()),
2225  getLogCategories());
2226  }
2227  }
2228  }
2229 
2230  if (foundByCM.isEmpty() && foundByFamily.isEmpty()) { return inList; }
2231  reduced = true;
2232 
2233  // avoid dpulicates, then add
2234  if (!foundByFamily.isEmpty())
2235  {
2236  foundByCM.removeModelsWithString(foundByFamily.getModelStringList(), Qt::CaseInsensitive);
2237  }
2238  foundByFamily.push_back(foundByCM);
2239 
2240  if (log)
2241  {
2242  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2243  u"Found by family (totally) '" % family % u"' (" % hint % u") size " %
2244  QString::number(foundByFamily.sizeInt()),
2245  getLogCategories());
2246  }
2247  return foundByFamily;
2248  }
2249 
2250  CAircraftModelList CAircraftMatcher::ifPossibleReduceByManufacturer(const CSimulatedAircraft &remoteAircraft,
2251  const CAircraftModelList &inList,
2252  const QString &info, bool &reduced,
2253  CStatusMessageList *log)
2254  {
2255  reduced = false;
2256  if (inList.isEmpty())
2257  {
2258  if (log)
2259  {
2260  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, info % u" Empty input list, cannot reduce",
2261  getLogCategories());
2262  }
2263  return inList;
2264  }
2265 
2266  const QString m = remoteAircraft.getAircraftIcaoCode().getManufacturer();
2267  if (m.isEmpty())
2268  {
2269  if (log)
2270  {
2271  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2272  info % u" No manufacturer, cannot reduce " %
2273  QString::number(inList.size()) % u" entries",
2274  getLogCategories());
2275  }
2276  return inList;
2277  }
2278 
2279  const CAircraftModelList outList(inList.findByManufacturer(m));
2280  if (outList.isEmpty())
2281  {
2282  if (log)
2283  {
2285  log, remoteAircraft, info % u" Not found '" % m % u"', cannot reduce", getLogCategories());
2286  }
2287  return inList;
2288  }
2289 
2290  if (log)
2291  {
2293  log, remoteAircraft, info % u" Reduced by '" % m % u"' results: " % QString::number(outList.size()),
2294  getLogCategories());
2295  }
2296  reduced = true;
2297  return outList;
2298  }
2299 
2300  CAircraftIcaoCodeList CAircraftMatcher::ifPossibleReduceAircraftIcaoByManufacturer(
2301  const CAircraftIcaoCode &icaoCode, const CAircraftIcaoCodeList &inList, const QString &info, bool &reduced,
2302  const CCallsign &logCallsign, CStatusMessageList *log)
2303  {
2304  reduced = false;
2305  if (inList.isEmpty())
2306  {
2307  if (log)
2308  {
2309  CCallsign::addLogDetailsToList(log, logCallsign, info % u" Empty input list, cannot reduce",
2310  getLogCategories());
2311  }
2312  return inList;
2313  }
2314 
2315  const QString m = icaoCode.getManufacturer();
2316  if (m.isEmpty())
2317  {
2318  if (log)
2319  {
2320  CCallsign::addLogDetailsToList(log, logCallsign,
2321  info % u" No manufacturer, cannot reduce " %
2322  QString::number(inList.size()) % u" entries",
2323  getLogCategories());
2324  }
2325  return inList;
2326  }
2327 
2328  const CAircraftIcaoCodeList outList(inList.findByManufacturer(m));
2329  if (outList.isEmpty())
2330  {
2331  if (log)
2332  {
2333  CCallsign::addLogDetailsToList(log, logCallsign, info % " Not found " % m % ", cannot reduce",
2334  getLogCategories());
2335  }
2336  return inList;
2337  }
2338 
2339  if (log)
2340  {
2341  CCallsign::addLogDetailsToList(log, logCallsign,
2342  info % u" Reduced by " % m % u" results: " % QString::number(outList.size()),
2343  getLogCategories());
2344  }
2345  reduced = true;
2346  return outList;
2347  }
2348 
2349  CAircraftModelList CAircraftMatcher::ifPossibleReduceByAircraft(const CSimulatedAircraft &remoteAircraft,
2350  const CAircraftModelList &inList,
2351  const QString &info, bool &reduced,
2352  CStatusMessageList *log)
2353  {
2354  reduced = false;
2355  if (inList.isEmpty())
2356  {
2357  if (log)
2358  {
2359  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, info % " Empty input list, cannot reduce",
2360  getLogCategories());
2361  }
2362  return inList;
2363  }
2364 
2365  if (!remoteAircraft.hasAircraftDesignator())
2366  {
2367  if (log)
2368  {
2369  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2370  info % " No aircraft designator, cannot reduce " %
2371  QString::number(inList.size()) % " entries",
2372  getLogCategories());
2373  }
2374  return inList;
2375  }
2376 
2377  const CAircraftModelList outList(
2378  inList.findByIcaoDesignators(remoteAircraft.getAircraftIcaoCode(), CAirlineIcaoCode::null()));
2379  if (outList.isEmpty())
2380  {
2381  if (log)
2382  {
2383  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2384  info % u" Cannot reduce by '" %
2385  remoteAircraft.getAircraftIcaoCodeDesignator() %
2386  u"' results: " % QString::number(outList.size()),
2387  getLogCategories());
2388  }
2389  return inList;
2390  }
2391 
2392  if (log)
2393  {
2394  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2395  info % u" Reduced by '" %
2396  remoteAircraft.getAircraftIcaoCodeDesignator() % u"' to " %
2397  QString::number(outList.size()),
2398  getLogCategories());
2399  }
2400  reduced = true;
2401  return outList;
2402  }
2403 
2404  CAircraftModelList CAircraftMatcher::ifPossibleReduceByAircraftOrFamily(
2405  const CSimulatedAircraft &remoteAircraft, bool allowPseudoFamily, const CAircraftModelList &inList,
2406  const CAircraftMatcherSetup &setup, const QString &info, bool &reduced, CStatusMessageList *log)
2407  {
2408  reduced = false;
2409  const CAircraftModelList outList = ifPossibleReduceByAircraft(remoteAircraft, inList, info, reduced, log);
2410  if (reduced || !setup.getMatchingMode().testFlag(CAircraftMatcherSetup::ByFamily)) { return outList; }
2411  QString family;
2412  return ifPossibleReduceByFamily(remoteAircraft, allowPseudoFamily, inList, reduced, family, log);
2413  }
2414 
2415  CAircraftModelList CAircraftMatcher::ifPossibleReduceByAirline(const CSimulatedAircraft &remoteAircraft,
2416  const CAircraftModelList &inList,
2417  const CAircraftMatcherSetup &setup,
2418  const QString &info, bool &reduced,
2419  CStatusMessageList *log)
2420  {
2421  reduced = false;
2422  if (inList.isEmpty())
2423  {
2424  if (log)
2425  {
2426  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, info % u" Empty input list, cannot reduce",
2427  getLogCategories());
2428  }
2429  return inList;
2430  }
2431 
2432  if (!remoteAircraft.hasAirlineDesignator())
2433  {
2434  if (log)
2435  {
2436  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2437  info % u" No airline designator, cannot reduce " %
2438  QString::number(inList.size()) % u" entries",
2439  getLogCategories());
2440  }
2441  return inList;
2442  }
2443 
2444  CAircraftMatcherSetup::MatchingMode mode = setup.getMatchingMode();
2445  CAircraftModelList outList(
2446  inList.findByIcaoDesignators(CAircraftIcaoCode::null(), remoteAircraft.getAirlineIcaoCode()));
2447  if (mode.testFlag(CAircraftMatcherSetup::ByAirlineGroupSameAsAirline) ||
2448  (outList.isEmpty() || mode.testFlag(CAircraftMatcherSetup::ByAirlineGroupIfNoAirline)))
2449  {
2450  if (remoteAircraft.getAirlineIcaoCode().hasGroupMembership())
2451  {
2452  const CAircraftModelList groupModels = inList.findByAirlineGroup(remoteAircraft.getAirlineIcaoCode());
2453  outList.replaceOrAddModelsWithString(groupModels, Qt::CaseInsensitive);
2454  if (log)
2455  {
2457  log, remoteAircraft,
2458  groupModels.isEmpty() ?
2459  QStringLiteral("No group models found by using airline group '%1'")
2460  .arg(remoteAircraft.getAirlineIcaoCode().getGroupDesignator()) :
2461  QStringLiteral("Added %1 model(s) by using airline group '%2', all members: '%3'")
2462  .arg(groupModels.sizeInt())
2463  .arg(remoteAircraft.getAirlineIcaoCode().getGroupDesignator(),
2464  joinStringSet(groupModels.getAirlineVDesignators(), ", ")),
2465  getLogCategories());
2466  }
2467  } // group membership
2468  }
2469 
2470  if (outList.isEmpty())
2471  {
2472  if (log)
2473  {
2474  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2475  info % u" Cannot reduce by '" %
2476  remoteAircraft.getAirlineIcaoCodeDesignator() % u"' results: " %
2477  QString::number(outList.size()),
2478  getLogCategories());
2479  }
2480  return inList;
2481  }
2482 
2483  if (log)
2484  {
2485  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2486  info % u" Reduced by '" %
2487  remoteAircraft.getAirlineIcaoCodeDesignator() % u"' to " %
2488  QString::number(outList.size()),
2489  getLogCategories());
2490  }
2491  reduced = true;
2492  return outList;
2493  }
2494 
2495  CAircraftModelList CAircraftMatcher::ifPossibleReduceModelsByAirlineNameTelephonyDesignator(
2496  const CCallsign &cs, const QString &airlineName, const QString &telephony, const CAircraftModelList &inList,
2497  const QString &info, bool &reduced, CStatusMessageList *log)
2498  {
2499  reduced = false;
2500  if (inList.isEmpty())
2501  {
2502  if (log)
2503  {
2504  CCallsign::addLogDetailsToList(log, cs, info % u" Empty input list, cannot reduce", getLogCategories());
2505  }
2506  return inList;
2507  }
2508 
2509  if (telephony.isEmpty() && airlineName.isEmpty())
2510  {
2511  if (log)
2512  {
2513  CCallsign::addLogDetailsToList(log, cs,
2514  info % u" No name/telephony, cannot reduce " %
2515  QString::number(inList.size()) % u" entries",
2516  getLogCategories());
2517  }
2518  return inList;
2519  }
2520 
2521  CAircraftModelList step1Data = inList.findByAirlineNamesOrTelephonyDesignator(airlineName);
2522  if (step1Data.isEmpty() || step1Data.size() == inList.size())
2523  {
2524  if (log)
2525  {
2526  CCallsign::addLogDetailsToList(
2527  log, cs, info % QStringLiteral(" cannot reduce by '%1'").arg(airlineName), getLogCategories());
2528  }
2529  step1Data = inList;
2530  }
2531  else
2532  {
2533  reduced = true;
2534  if (log)
2535  {
2536  CCallsign::addLogDetailsToList(log, cs, info % QStringLiteral(" reduced by '%1'").arg(airlineName),
2537  getLogCategories());
2538  }
2539  }
2540  if (step1Data.size() == 1) { return step1Data; }
2541 
2542  CAircraftModelList step2Data = inList.findByAirlineNamesOrTelephonyDesignator(telephony);
2543  if (step2Data.isEmpty() || step2Data.size() == inList.size())
2544  {
2545  if (log)
2546  {
2547  CCallsign::addLogDetailsToList(log, cs, info % QStringLiteral(" cannot reduce by '%1'").arg(telephony),
2548  getLogCategories());
2549  }
2550  step2Data = step1Data;
2551  }
2552  else
2553  {
2554  reduced = true;
2555  if (log)
2556  {
2557  CCallsign::addLogDetailsToList(log, cs, info % QStringLiteral(" reduced by '%1'").arg(telephony),
2558  getLogCategories());
2559  }
2560  }
2561  return step2Data;
2562 
2574  }
2575 
2576  CAircraftModelList CAircraftMatcher::ifPossibleReduceByCombinedType(const CSimulatedAircraft &remoteAircraft,
2577  const CAircraftModelList &inList,
2578  const CAircraftMatcherSetup &setup,
2579  bool &reduced, CStatusMessageList *log)
2580  {
2581  reduced = false;
2582  if (!remoteAircraft.getAircraftIcaoCode().hasValidCombinedType())
2583  {
2584  if (log)
2585  {
2586  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, QStringLiteral("No valid combined code"),
2587  getLogCategories());
2588  }
2589  return inList;
2590  }
2591 
2592  const QString cc = remoteAircraft.getAircraftIcaoCode().getCombinedType();
2593  CAircraftModelList modelsByCombinedCode(inList.findByCombinedType(cc));
2594  if (modelsByCombinedCode.isEmpty())
2595  {
2596  if (log)
2597  {
2598  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"Not found by combined code " % cc,
2599  getLogCategories());
2600  }
2601  return inList;
2602  }
2603 
2604  if (log)
2605  {
2606  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2607  u"Found by combined code " % cc % u", possible " %
2608  QString::number(modelsByCombinedCode.size()),
2609  getLogCategories());
2610  }
2611  if (modelsByCombinedCode.size() > 1)
2612  {
2613  modelsByCombinedCode =
2614  ifPossibleReduceByAirline(remoteAircraft, modelsByCombinedCode, setup,
2615  QStringLiteral("Combined code airline reduction. "), reduced, log);
2616  modelsByCombinedCode =
2617  ifPossibleReduceByManufacturer(remoteAircraft, modelsByCombinedCode,
2618  QStringLiteral("Combined code manufacturer reduction. "), reduced, log);
2619  reduced = true;
2620  }
2621  return modelsByCombinedCode;
2622  }
2623 
2624  CAircraftModelList CAircraftMatcher::ifPossibleReduceByMilitaryFlag(const CSimulatedAircraft &remoteAircraft,
2625  const CAircraftModelList &inList, bool &reduced,
2626  CStatusMessageList *log)
2627  {
2628  reduced = false;
2629  const bool military = remoteAircraft.getModel().isMilitary();
2630  const CAircraftModelList byMilitaryFlag(inList.findByMilitaryFlag(military));
2631  const QString mil(military ? "military" : "civilian");
2632  if (byMilitaryFlag.isEmpty())
2633  {
2634  if (log)
2635  {
2636  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, u"Models not found by " % mil,
2637  getLogCategories());
2638  }
2639  return inList;
2640  }
2641 
2642  if (log)
2643  {
2644  CMatchingUtils::addLogDetailsToList(log, remoteAircraft,
2645  u"Models reduced to " % mil % u" aircraft, size " %
2646  QString::number(byMilitaryFlag.size()),
2647  getLogCategories());
2648  }
2649  return byMilitaryFlag;
2650  }
2651 
2652  CAircraftModelList CAircraftMatcher::ifPossibleReduceByVTOLFlag(const CSimulatedAircraft &remoteAircraft,
2653  const CAircraftModelList &inList, bool &reduced,
2654  CStatusMessageList *log)
2655  {
2656  reduced = false;
2657  if (!inList.containsVtol())
2658  {
2659  CMatchingUtils::addLogDetailsToList(log, remoteAircraft, "Cannot reduce to VTOL aircraft",
2660  getLogCategories());
2661  return inList;
2662  }
2663  CAircraftModelList vtolModels = inList.findByVtolFlag(true);
2664  if (log)
2665  {
2667  log, remoteAircraft, u"Models reduced to " % QString::number(vtolModels.size()) % u" VTOL aircraft",
2668  getLogCategories());
2669  }
2670  return vtolModels;
2671  }
2672 
2673  QString CAircraftMatcher::scoresToString(const ScoredModels &scores, int lastElements)
2674  {
2675  if (scores.isEmpty()) { return {}; }
2676  QMultiMapIterator<int, CAircraftModel> i(scores);
2677  i.toBack();
2678  int c = 0;
2679  QString str;
2680  while (i.hasPrevious() && c++ < lastElements)
2681  {
2682  i.previous();
2683  const CAircraftModel m(i.value());
2684  if (!str.isEmpty()) { str += '\n'; }
2685  str += QString::number(c) % u": score: " % QString::number(i.key()) % u" model: '" % m.getModelString() %
2686  u"' aircraft: '" % m.getAircraftIcaoCodeDesignator() % u"' livery: '" %
2687  m.getLivery().getCombinedCodePlusInfo() % u'\'';
2688  }
2689  return str;
2690  }
2691 
2692  CAirlineIcaoCode CAircraftMatcher::stringToAirlineIcaoObject(const CCallsign &cs, const QString &designator,
2693  const QString &airlineName,
2694  const QString &airlineTelephony, bool useSwiftDbData,
2695  CStatusMessageList *log)
2696  {
2697  if (!useSwiftDbData) { return CAirlineIcaoCode(designator); }
2698  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAirlineIcaoCode(designator); }
2700  if (codes.isEmpty()) { return CAirlineIcaoCode(designator); }
2701  if (codes.size() == 1) { return codes.front(); }
2702 
2703  // more than 1
2704  bool reduced = false;
2705  static const QString info("Try reducing airline '%1' by name/telephony '%2'/'%3'");
2707  cs, airlineName, airlineTelephony, QString(), reduced, info, log);
2708  return reducedIcaos.frontOrDefault();
2709  }
2710 
2711  bool CAircraftMatcher::isValidAirlineIcaoDesignator(const QString &designator, bool checkAgainstSwiftDb)
2712  {
2713  if (!CAirlineIcaoCode::isValidAirlineDesignator(designator)) { return false; }
2714  if (!checkAgainstSwiftDb) { return true; }
2715  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return true; }
2716  return (sApp->getWebDataServices()->containsAirlineIcaoDesignator(designator));
2717  }
2718 } // 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::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 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::CAirlineIcaoCode getAirlineIcaoCodeForDbKey(int key) const
ICAO code for id.
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodesForDesignator(const QString &designator) const
ICAO codes for designator.
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::CAircraftIcaoCode getAircraftIcaoCodeForDbKey(int key) const
ICAO code for id.
swift::misc::aviation::CAircraftCategoryList getAircraftCategories() const
Aircraft categories.
swift::misc::aviation::CAirlineIcaoCode smartAirlineIcaoSelector(const swift::misc::aviation::CAirlineIcaoCode &icaoPattern, const swift::misc::aviation::CCallsign &callsign=swift::misc::aviation::CCallsign()) const
Smart airline selector.
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:74
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.
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.