swift
airspacemonitor.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/airspacemonitor.h"
5 
6 #include <QDateTime>
7 #include <QTime>
8 #include <QVariant>
9 #include <Qt>
10 
11 #include "config/buildconfig.h"
12 #include "core/aircraftmatcher.h"
13 #include "core/airspaceanalyzer.h"
14 #include "core/application.h"
16 #include "core/fsd/fsdclient.h"
18 #include "core/webdataservices.h"
24 #include "misc/eventloop.h"
26 #include "misc/iterator.h"
27 #include "misc/logmessage.h"
29 #include "misc/network/user.h"
31 #include "misc/pq/units.h"
33 #include "misc/range.h"
34 #include "misc/sequence.h"
35 #include "misc/statusmessagelist.h"
36 #include "misc/test/testing.h"
37 #include "misc/threadutils.h"
38 #include "misc/variant.h"
39 #include "misc/verify.h"
40 #include "misc/worker.h"
41 
42 using namespace swift::config;
43 using namespace swift::misc;
44 using namespace swift::misc::aviation;
45 using namespace swift::misc::geo;
46 using namespace swift::misc::simulation;
47 using namespace swift::misc::test;
48 using namespace swift::misc::json;
49 using namespace swift::misc::network;
50 using namespace swift::misc::physical_quantities;
51 using namespace swift::misc::weather;
52 using namespace swift::core::fsd;
53 using namespace swift::core::vatsim;
54 
55 namespace swift::core
56 {
57  CAirspaceMonitor::CAirspaceMonitor(IOwnAircraftProvider *ownAircraftProvider,
58  IAircraftModelSetProvider *modelSetProvider, CFSDClient *fsdClient,
59  QObject *parent)
60  : CRemoteAircraftProvider(parent), COwnAircraftAware(ownAircraftProvider), m_modelSetProvider(modelSetProvider),
61  m_fsdClient(fsdClient), m_analyzer(new CAirspaceAnalyzer(ownAircraftProvider, m_fsdClient, this))
62  {
63  this->setObjectName("CAirspaceMonitor");
64  this->enableReverseLookupMessages(sApp->isDeveloperFlagSet() || CBuildConfig::isLocalDeveloperDebugBuild() ?
65  RevLogEnabled :
66  RevLogEnabledSimplified);
67 
68  // FSD runs in its own thread!
69  connect(m_fsdClient, &CFSDClient::atcDataUpdateReceived, this, &CAirspaceMonitor::onAtcPositionUpdate);
70  connect(m_fsdClient, &CFSDClient::atisReplyReceived, this, &CAirspaceMonitor::onAtisReceived);
71  connect(m_fsdClient, &CFSDClient::atisLogoffTimeReplyReceived, this,
72  &CAirspaceMonitor::onAtisLogoffTimeReceived);
73  connect(m_fsdClient, &CFSDClient::flightPlanReceived, this, &CAirspaceMonitor::onFlightPlanReceived);
74  connect(m_fsdClient, &CFSDClient::realNameResponseReceived, this, &CAirspaceMonitor::onRealNameReplyReceived);
75  connect(m_fsdClient, &CFSDClient::planeInformationReceived, this, &CAirspaceMonitor::onIcaoCodesReceived);
76  connect(m_fsdClient, &CFSDClient::deletePilotReceived, this, &CAirspaceMonitor::onPilotDisconnected);
77  connect(m_fsdClient, &CFSDClient::deleteAtcReceived, this, &CAirspaceMonitor::onAtcControllerDisconnected);
78  connect(m_fsdClient, &CFSDClient::pilotDataUpdateReceived, this, &CAirspaceMonitor::onAircraftUpdateReceived);
79  connect(m_fsdClient, &CFSDClient::interimPilotDataUpdatedReceived, this,
80  &CAirspaceMonitor::onAircraftInterimUpdateReceived);
81  connect(m_fsdClient, &CFSDClient::visualPilotDataUpdateReceived, this,
82  &CAirspaceMonitor::onAircraftVisualUpdateReceived);
83  connect(m_fsdClient, &CFSDClient::euroscopeSimDataUpdatedReceived, this,
84  &CAirspaceMonitor::onAircraftSimDataUpdateReceived);
85  connect(m_fsdClient, &CFSDClient::com1FrequencyResponseReceived, this, &CAirspaceMonitor::onFrequencyReceived);
86  connect(m_fsdClient, &CFSDClient::capabilityResponseReceived, this,
87  &CAirspaceMonitor::onCapabilitiesReplyReceived);
88  connect(m_fsdClient, &CFSDClient::planeInformationFsinnReceived, this,
89  &CAirspaceMonitor::onCustomFSInnPacketReceived);
90  connect(m_fsdClient, &CFSDClient::serverResponseReceived, this, &CAirspaceMonitor::onServerReplyReceived);
91  connect(m_fsdClient, &CFSDClient::aircraftConfigReceived, this, &CAirspaceMonitor::onAircraftConfigReceived);
92  connect(m_fsdClient, &CFSDClient::connectionStatusChanged, this, &CAirspaceMonitor::onConnectionStatusChanged);
93  connect(m_fsdClient, &CFSDClient::revbAircraftConfigReceived, this,
94  &CAirspaceMonitor::onRevBAircraftConfigReceived);
95 
96  Q_ASSERT_X(sApp && sApp->hasWebDataServices(), Q_FUNC_INFO, "Missing data reader");
97 
98  if (this->supportsVatsimDataFile())
99  {
100  connect(sApp->getWebDataServices()->getVatsimDataFileReader(), &CVatsimDataFileReader::dataFileRead, this,
101  &CAirspaceMonitor::onReceivedVatsimDataFile);
102  }
103 
104  // Force snapshot in the main event loop
105  connect(m_analyzer, &CAirspaceAnalyzer::airspaceAircraftSnapshot, this,
106  &CAirspaceMonitor::airspaceAircraftSnapshot, Qt::QueuedConnection);
107 
108  // Analyzer
109  connect(m_analyzer, &CAirspaceAnalyzer::timeoutAircraft, this, &CAirspaceMonitor::onPilotDisconnected,
110  Qt::QueuedConnection);
111  connect(m_analyzer, &CAirspaceAnalyzer::timeoutAtc, this, &CAirspaceMonitor::onAtcControllerDisconnected,
112  Qt::QueuedConnection);
113 
114  // timers
115  connect(&m_fastProcessTimer, &QTimer::timeout, this, &CAirspaceMonitor::fastProcessing);
116  connect(&m_slowProcessTimer, &QTimer::timeout, this, &CAirspaceMonitor::slowProcessing);
117  m_fastProcessTimer.start(FastProcessIntervalMs);
118  m_slowProcessTimer.start(SlowProcessIntervalMs);
119 
120  // dot command
122  }
123 
124  bool CAirspaceMonitor::updateFastPositionEnabled(const CCallsign &callsign, bool enableFastPositonUpdates)
125  {
126  const bool r = CRemoteAircraftProvider::updateFastPositionEnabled(callsign, enableFastPositonUpdates);
127  if (m_fsdClient && sApp && !sApp->isShuttingDown())
128  {
129  // thread safe update of m_network
130  const QPointer<CAirspaceMonitor> myself(this);
131  QTimer::singleShot(0, m_fsdClient, [=] {
132  if (!myself) { return; }
133  if (m_fsdClient)
134  {
135  if (enableFastPositonUpdates) { m_fsdClient->addInterimPositionReceiver(callsign); }
136  else { m_fsdClient->removeInterimPositionReceiver(callsign); }
137  }
138  });
139  }
140  return r;
141  }
142 
144  {
145  static const QStringList cats { CLogCategories::matching(), CLogCategories::network() };
146  return cats;
147  }
148 
150  {
151  Q_ASSERT_X(m_analyzer, Q_FUNC_INFO, "No analyzer");
152  return m_analyzer->getLatestAirspaceAircraftSnapshot();
153  }
154 
156  {
157  CFlightPlan plan;
158  QPointer<CAirspaceMonitor> myself(this);
159 
160  // use cache, but not for own callsign (always reload)
161  if (m_flightPlanCache.contains(callsign)) { plan = m_flightPlanCache[callsign]; }
162  const bool ownAircraft = this->getOwnCallsign() == callsign;
163  if (ownAircraft || !plan.wasSentOrLoaded() || plan.timeDiffSentOrLoadedMs() > 30 * 1000)
164  {
165  // outdated, or not in cache at all, or NOT own aircraft
166  plan = CFlightPlan(); // reset
167  m_flightPlanCache.remove(callsign); // loading FP from network
168  m_fsdClient->sendClientQueryFlightPlan(callsign);
169 
170  // with this little trick we try to make an asynchronous signal / slot based approach
171  // a synchronous return value
172  CEventLoop eventLoop(this);
173  eventLoop.stopWhen(m_fsdClient, &CFSDClient::flightPlanReceived,
174  [=](const auto &cs, const auto &) { return cs == callsign; });
175  if (eventLoop.exec(1500))
176  {
177  if (!myself || !sApp || sApp->isShuttingDown()) // cppcheck-suppress knownConditionTrueFalse
178  {
179  return {};
180  }
181  if (m_flightPlanCache.contains(callsign)) { plan = m_flightPlanCache[callsign]; }
182  }
183  }
184  return plan;
185  }
186 
188  {
189  if (callsign.isEmpty()) { return {}; }
190 
191  // full flight plan's remarks
192  if (m_flightPlanCache.contains(callsign)) { return m_flightPlanCache[callsign].getFlightPlanRemarks(); }
193 
194  // remarks only
195  if (this->supportsVatsimDataFile())
196  {
198  }
199 
200  // unsupported
201  return {};
202  }
203 
205  {
207  return m_atcStationsOnline;
208  }
209 
211  {
212  CUserList users;
213  for (const CAtcStation &station : m_atcStationsOnline)
214  {
215  CUser user = station.getController();
216  if (!user.hasCallsign()) { user.setCallsign(station.getCallsign()); }
217  users.push_back(user);
218  }
219  for (const CSimulatedAircraft &aircraft : this->getAircraftInRange())
220  {
221  CUser user = aircraft.getPilot();
222  if (!user.hasCallsign()) { user.setCallsign(aircraft.getCallsign()); }
223  users.push_back(user);
224  }
225  return users;
226  }
227 
229  {
230  CUserList users;
231  if (callsigns.isEmpty()) { return users; }
232  CCallsignSet searchList(callsigns);
233 
234  // myself, which is not in the lists below
235  const CSimulatedAircraft myAircraft(getOwnAircraft());
236  if (!myAircraft.getCallsign().isEmpty() && searchList.contains(myAircraft.getCallsign()))
237  {
238  searchList.remove(myAircraft.getCallsign());
239  users.push_back(myAircraft.getPilot());
240  }
241 
242  // do aircraft first, this will handle most callsigns
243  for (const CSimulatedAircraft &aircraft : this->getAircraftInRange())
244  {
245  if (searchList.isEmpty()) { break; }
246  const CCallsign callsign = aircraft.getCallsign();
247  if (searchList.contains(callsign))
248  {
249  const CUser user = aircraft.getPilot();
250  users.push_back(user);
251  searchList.remove(callsign);
252  }
253  }
254 
255  for (const CAtcStation &station : m_atcStationsOnline)
256  {
257  if (searchList.isEmpty()) { break; }
258  const CCallsign callsign = station.getCallsign();
259  if (searchList.contains(callsign))
260  {
261  const CUser user = station.getController();
262  users.push_back(user);
263  searchList.remove(callsign);
264  }
265  }
266 
267  // we might have unresolved callsigns
268  // those are the ones not in range
269  for (const CCallsign &callsign : std::as_const(searchList))
270  {
271  const CUserList usersByCallsign = sApp->getWebDataServices()->getUsersForCallsign(callsign);
272  if (usersByCallsign.isEmpty())
273  {
274  const CUser user(callsign);
275  users.push_back(user);
276  }
277  else { users.push_back(usersByCallsign[0]); }
278  }
279  return users;
280  }
281 
283  {
284  CAtcStationList stations = m_atcStationsOnline.findIfComUnitTunedInChannelSpacing(comSystem);
285  if (stations.isEmpty()) { return {}; }
287  return stations.front();
288  }
289 
291  {
292  if (!this->isConnectedAndNotShuttingDown()) { return; }
293  const CSimulatedAircraftList aircraftInRange(this->getAircraftInRange());
294  for (const CSimulatedAircraft &aircraft : aircraftInRange)
295  {
296  // staggered version
297  const CCallsign cs(aircraft.getCallsign());
298  if (!m_queryPilot.contains(cs)) { m_queryPilot.enqueue(aircraft.getCallsign()); }
299  }
300  }
301 
303  {
304  if (!this->isConnectedAndNotShuttingDown()) { return; }
305  const CAtcStationList stations(this->getAtcStationsOnline());
306  for (const CAtcStation &station : stations)
307  {
308  const CCallsign cs = station.getCallsign();
309 
310  // changed to staggered version
311  // m_network->sendAtisQuery(cs); // for each online station
312  if (!m_queryAtis.contains(cs)) { m_queryAtis.enqueue(cs); }
313  }
314  }
315 
316  void CAirspaceMonitor::testCreateDummyOnlineAtcStations(int number)
317  {
318  if (number < 1) { return; }
319  m_atcStationsOnline.push_back(CTesting::createAtcStations(number));
320  emit this->changedAtcStationsOnline();
321  }
322 
323  void CAirspaceMonitor::testAddAircraftParts(const CCallsign &callsign, const CAircraftParts &parts,
324  bool incremental)
325  {
326  this->onAircraftConfigReceived(callsign, incremental ? parts.toIncrementalJson() : parts.toFullJson(), 5000);
327  }
328 
330  {
331  static const QString nr("not ready");
332  static const QString icao("rec. ICAO");
333  static const QString fsinn("rec. FsInn");
334  static const QString ready("ready sent");
335  static const QString rec("recursive");
336  switch (r)
337  {
338  case NotReady: return nr;
339  case ReceivedIcaoCodes: return icao;
340  case ReceivedFsInnPacket: return fsinn;
341  case ReadyForMatchingSent: return ready;
342  case RecursiveCall: return rec;
343  default: break;
344  }
345  static const QString unknown("????");
346  return unknown;
347  }
348 
349  QString CAirspaceMonitor::enumToString(MatchingReadiness r)
350  {
351  QStringList s;
352  if (r.testFlag(NotReady)) { s << enumFlagToString(NotReady); }
353  if (r.testFlag(ReceivedIcaoCodes)) { s << enumFlagToString(ReceivedIcaoCodes); }
354  if (r.testFlag(ReceivedFsInnPacket)) { s << enumFlagToString(ReceivedFsInnPacket); }
356  if (r.testFlag(RecursiveCall)) { s << enumFlagToString(RecursiveCall); }
357  return s.join(", ");
358  }
359 
360  bool CAirspaceMonitor::parseCommandLine(const QString &commandLine, const CIdentifier &originator)
361  {
362  Q_UNUSED(originator;)
363  if (commandLine.isEmpty()) { return false; }
364  static const QStringList cmds({ ".fsd" });
365  CSimpleCommandParser parser(cmds);
366  parser.parse(commandLine);
367  if (!parser.isKnownCommand()) { return false; }
368  if (parser.matchesCommand(".fsd"))
369  {
370  if (parser.countParts() < 2) { return false; }
371  if (parser.matchesPart(1, "range") && parser.countParts() > 2)
372  {
373  const QString r = parser.part(2);
374  const CLength d = CLength::parsedFromString(r);
375  this->setMaxRange(d);
376  }
377  }
378  return false;
379  }
380 
381  void CAirspaceMonitor::fastProcessing()
382  {
383  if (!this->isConnectedAndNotShuttingDown()) { return; }
384 
385  // only send one query
386  const bool send = this->sendNextStaggeredAtisQuery();
387  if (!send) { this->sendNextStaggeredPilotDataQuery(); }
388  }
389 
390  void CAirspaceMonitor::slowProcessing()
391  {
392  if (!this->isConnectedAndNotShuttingDown()) { return; }
393  this->queryAllOnlineAtcStations();
394  }
395 
397  {
398  m_flightPlanCache.clear();
399  m_tempFsInnPackets.clear();
400  m_readiness.clear();
401  this->removeAllOnlineAtcStations();
402  this->removeAllAircraft();
403  this->clearClients();
404 
405  m_foundInNonMovingAircraft = 0;
406  m_foundInElevationsOnGnd = 0;
407  }
408 
409  void CAirspaceMonitor::gracefulShutdown() { QObject::disconnect(this); }
410 
412  {
413  const CSimulatedAircraftList aircraft = this->getAircraftInRange();
415  this->asyncReInitializeAllAircraft(aircraft, true);
416  return aircraft.size();
417  }
418 
420  {
421  if (range.isNull() || range.isNegativeWithEpsilonConsidered() || range.isZeroEpsilonConsidered())
422  {
423  // off
424  m_maxDistanceNM = -1;
425  m_maxDistanceNMHysteresis = -1;
426  CLogMessage(this).info(u"No airspace range restriction");
427  return;
428  }
429 
430  const int rIntNM = range.valueInteger(CLengthUnit::NM());
431  CLogMessage(this).info(u"Set airspace max. range to %1NM") << rIntNM;
432  m_maxDistanceNM = rIntNM;
433  m_maxDistanceNMHysteresis = qRound(rIntNM * 1.1);
434  }
435 
436  void CAirspaceMonitor::onRealNameReplyReceived(const CCallsign &callsign, const QString &realname)
437  {
438  if (!this->isConnectedAndNotShuttingDown() || realname.isEmpty()) { return; }
439  if (!sApp || sApp->isShuttingDown() || !sApp->getWebDataServices()) { return; }
440 
441  bool wasAtc = false;
442  const QString rn = CUser::beautifyRealName(realname);
443 
444  if (callsign.hasSuffix())
445  {
446  // very likely and ATC callsign
447  const CPropertyIndexVariantMap vm =
448  CPropertyIndexVariantMap({ CAtcStation::IndexController, CUser::IndexRealName }, rn);
449  const int c1 = this->updateOnlineStation(callsign, vm, false, true);
450  wasAtc = c1 > 0;
451  }
452 
453  if (!wasAtc)
454  {
455  const CPropertyIndexVariantMap vm =
456  CPropertyIndexVariantMap({ CSimulatedAircraft::IndexPilot, CUser::IndexRealName }, rn);
457  this->updateAircraftInRange(callsign, vm);
458  }
459 
460  // Client
462  CPropertyIndexVariantMap vm = CPropertyIndexVariantMap({ CClient::IndexUser, CUser::IndexRealName }, rn);
463  vm.addValue({ CClient::IndexVoiceCapabilities }, voiceCaps);
464  this->updateOrAddClient(callsign, vm, false);
465  }
466 
467  void CAirspaceMonitor::onCapabilitiesReplyReceived(const CCallsign &callsign, CClient::Capabilities clientCaps)
468  {
469  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) { return; }
471  CPropertyIndexVariantMap vm(CClient::IndexCapabilities, CVariant::from(clientCaps));
472  vm.addValue({ CClient::IndexVoiceCapabilities }, voiceCaps);
473  this->updateOrAddClient(callsign, vm, false);
474 
475  // for aircraft parts
476  if (clientCaps.testFlag(CClient::FsdWithAircraftConfig))
477  {
478  m_fsdClient->sendClientQueryAircraftConfig(callsign);
479  }
480  }
481 
482  void CAirspaceMonitor::onServerReplyReceived(const CCallsign &callsign, const QString &server)
483  {
484  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty() || server.isEmpty()) { return; }
485  const CPropertyIndexVariantMap vm(CClient::IndexServer, server);
486  this->updateOrAddClient(callsign, vm);
487  }
488 
489  void CAirspaceMonitor::onFlightPlanReceived(const CCallsign &callsign, const CFlightPlan &flightPlan)
490  {
491  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) { return; }
492  CFlightPlan plan(flightPlan);
493  plan.setWhenLastSentOrLoaded(QDateTime::currentDateTimeUtc());
494  m_flightPlanCache.insert(callsign, plan);
495  }
496 
497  void CAirspaceMonitor::removeAllOnlineAtcStations()
498  {
499  m_atcStationsOnline.clear();
500  m_queryAtis.clear();
501  }
502 
503  void CAirspaceMonitor::removeAllAircraft()
504  {
506 
507  // non thread safe parts
508  m_flightPlanCache.clear();
509  m_readiness.clear();
510  m_queryPilot.clear();
511  }
512 
513  void CAirspaceMonitor::removeFromAircraftCachesAndLogs(const CCallsign &callsign)
514  {
515  if (callsign.isEmpty()) { return; }
516  m_flightPlanCache.remove(callsign);
517  m_readiness.remove(callsign);
518  this->removeReverseLookupMessages(callsign);
519  }
520 
521  void CAirspaceMonitor::onReceivedVatsimDataFile()
522  {
523  Q_ASSERT(CThreadUtils::isInThisThread(this));
524  if (!sApp || sApp->isShuttingDown() || !sApp->getWebDataServices()) { return; }
525  CClientList clients(this->getClients()); // copy
526  bool changed = false;
527  for (auto &client : clients)
528  {
529  if (client.hasSpecifiedVoiceCapabilities()) { continue; } // we already have voice caps
530  const CVoiceCapabilities vc =
531  sApp->getWebDataServices()->getVoiceCapabilityForCallsign(client.getCallsign());
532  if (vc.isUnknown()) { continue; }
533  changed = true;
534  client.setVoiceCapabilities(vc);
535  }
536  if (!changed) { return; }
537  this->setClients(clients);
538  }
539 
540  CAirspaceMonitor::Readiness &CAirspaceMonitor::addMatchingReadinessFlag(const CCallsign &callsign,
542  {
543  Readiness &readiness = m_readiness[callsign].addFlag(mrf);
544  return readiness;
545  }
546 
547  void CAirspaceMonitor::sendReadyForModelMatching(const CCallsign &callsign, MatchingReadinessFlag rf)
548  {
549  if (!this->isConnectedAndNotShuttingDown()) { return; }
550  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign");
551 
552  // set flag and init ts
553  Readiness &readiness = this->addMatchingReadinessFlag(callsign, rf);
554 
555  // skip if already sent in the last x seconds
556  const qint64 ageMs = readiness.getAgeMs();
557  if (readiness.wasMatchingSent() && readiness.getAgeMs() < MMMaxAgeThresholdMs) { return; }
558 
559  // checking for min. situations ensures the aircraft is stable, can be interpolated ...
560  const CSimulatedAircraft remoteAircraft = this->getAircraftInRangeForCallsign(callsign);
561  const bool validRemoteCs = remoteAircraft.hasValidCallsign();
562  const bool minSituations = this->remoteAircraftSituationsCount(callsign) > 1;
563  const bool complete = validRemoteCs && minSituations &&
564  (readiness.receivedAll() ||
565  // disable, because it can be model string is unknown in FsInn data
566  // (remoteAircraft.getModel().getModelType() == CAircraftModel::TypeFSInnData) || // here
567  // we know we have all data
568  (remoteAircraft.hasModelString()) // we cannot expect more info
569  );
570 
571  const ReverseLookupLogging revLogEnabled = this->whatToReverseLog();
572  if (rf != Verified && validRemoteCs && ageMs <= MMMaxAgeMs && !complete)
573  {
574  static const QString ws("Wait for further data, '%1' age: %2ms ts: %3");
575  static const QString format("hh:mm:ss.zzz");
576  if (!revLogEnabled.testFlag(RevLogSimplifiedInfo))
577  {
579  callsign,
580  ws.arg(readiness.toQString()).arg(ageMs).arg(QDateTime::currentDateTimeUtc().toString(format)));
581  }
582 
583  const QPointer<CAirspaceMonitor> myself(this);
584  QTimer::singleShot(MMCheckAgainMs, this, [=]() {
585  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
586  if (!this->isAircraftInRange(callsign))
587  {
588  const CStatusMessage m =
589  CCallsign::logMessage(callsign, "No longer in range", CAirspaceMonitor::getLogCategories());
590  this->addReverseLookupMessage(callsign, m);
591  return;
592  }
593  if (rf != ReceivedFsInnPacket)
594  {
595  // here we call recursively like an FsInn packet was received
596  if (this->recallFsInnPacket(callsign)) { return; }
597  }
598 
599  // check again
600  this->sendReadyForModelMatching(callsign, RecursiveCall); // recursively
601  });
602 
603  // end as we will call again in some time
604  return;
605 
606  } // not yet complete
607 
608  // some checks for special conditions, e.g. logout -> empty list, but still signals pending
609  if (validRemoteCs)
610  {
611  static const QString readyForMatching(
612  "Ready (%1) for matching callsign '%2' with model type '%3', ICAO: '%4' '%5'");
613 
614  readiness.setFlag(ReadyForMatchingSent); // stored in readiness as reference
615  const QString readyMsg = readyForMatching.arg(readiness.toQString(), callsign.toQString(),
616  remoteAircraft.getModel().getModelTypeAsString(),
617  remoteAircraft.getAircraftIcaoCode().getDesignatorDbKey(),
618  remoteAircraft.getAirlineIcaoCode().getDesignatorDbKey());
619  const CStatusMessage m = CCallsign::logMessage(callsign, readyMsg, getLogCategories());
620  this->addReverseLookupMessage(callsign, m);
621 
622  emit this->readyForModelMatching(remoteAircraft);
623  }
624  else
625  {
626  const CStatusMessage m = CCallsign::logMessage(
627  callsign, "Ignoring this aircraft, not found in range list, disconnected, or no callsign",
629  this->addReverseLookupMessage(callsign, m);
630  m_readiness.remove(callsign);
631  }
632  }
633 
634  void CAirspaceMonitor::verifyReceivedIcaoData(const CCallsign &callsign)
635  {
636  if (callsign.isEmpty() || !this->isAircraftInRange(callsign)) { return; }
637 
638  // set flag and init ts
639  Readiness &readiness = m_readiness[callsign];
640  if (readiness.wasMatchingSent()) { return; }
641  if (readiness.wasVerified())
642  {
643  CLogMessage(this).warning(u"Verfied ICAO data of '%1' again, using it as it is! Most likely incomplete "
644  u"data from the other client")
645  << callsign;
646  this->sendReadyForModelMatching(callsign, Verified);
647  return;
648  }
649 
650  // new trial
651  readiness.addFlag(Verified);
652 
653  if (!readiness.receivedIcaoCodes())
654  {
655  CLogMessage(this).info(u"Query ICAO codes for '%1' again") << callsign;
656  this->sendInitialPilotQueries(callsign, true, true);
657 
658  // if the queries now yield a result all will be fine
659  // otherwise we need to check again
660  const QPointer<CAirspaceMonitor> myself(this);
661  QTimer::singleShot(MMVerifyMs, this, [=]() {
662  // makes sure we have ICAO data
663  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
664  this->verifyReceivedIcaoData(callsign);
665  });
666  return;
667  }
668 
669  // normally we should never get here
670  CLogMessage(this).info(u"Verified '%1' again, has ICAO codes, ready for matching!") << callsign;
671  this->sendReadyForModelMatching(callsign, Verified);
672  }
673 
674  void CAirspaceMonitor::onAtcPositionUpdate(const CCallsign &callsign, const CFrequency &frequency,
675  const CCoordinateGeodetic &position,
677  {
678  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
679  if (!this->isConnectedAndNotShuttingDown()) { return; }
680 
681  const CAtcStationList stationsWithCallsign = m_atcStationsOnline.findByCallsign(callsign);
682  if (stationsWithCallsign.isEmpty())
683  {
684  CAtcStation station;
685 
686  // if connected to VATSIM, init with data from data file
687  if (this->getConnectedServer().getEcosystem() == CEcosystem::vatsim())
688  {
690  }
691 
692  station.setCallsign(callsign);
693  station.setRange(range);
694  station.setFrequency(frequency);
695  station.setPosition(position);
696  station.setOnline(true);
698 
699  m_atcStationsOnline.push_back(station);
700 
701  // subsequent queries
702  this->sendInitialAtcQueries(callsign);
703 
704  // update distances
706 
707  emit this->changedAtcStationsOnline();
708  }
709  else
710  {
711  // update
713  vm.addValue(CAtcStation::IndexFrequency, frequency);
714  vm.addValue(CAtcStation::IndexPosition, position);
715  vm.addValue(CAtcStation::IndexRange, range);
716  const int changed = m_atcStationsOnline.applyIfCallsign(callsign, vm, true);
717  if (changed > 0) { emit this->changedAtcStationsOnline(); }
718  }
719  }
720 
721  void CAirspaceMonitor::onAtcControllerDisconnected(const CCallsign &callsign)
722  {
723  Q_ASSERT(CThreadUtils::isInThisThread(this));
724  if (!this->isConnectedAndNotShuttingDown()) { return; }
725 
726  this->removeClient(callsign);
727  if (m_atcStationsOnline.containsCallsign(callsign))
728  {
729  const CAtcStation removedStation = m_atcStationsOnline.findFirstByCallsign(callsign);
730  m_atcStationsOnline.removeByCallsign(callsign);
731  emit this->changedAtcStationsOnline();
732  emit this->atcStationDisconnected(removedStation);
733  }
734  }
735 
736  void CAirspaceMonitor::onAtisReceived(const CCallsign &callsign, const CInformationMessage &atisMessage)
737  {
738  Q_ASSERT(CThreadUtils::isInThisThread(this));
739  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) return;
740  const bool changedAtis = m_atcStationsOnline.updateIfMessageChanged(atisMessage, callsign, true);
741 
742  // signal
743  if (changedAtis) { emit this->changedAtisReceived(callsign); }
744  }
745 
746  void CAirspaceMonitor::onAtisLogoffTimeReceived(const CCallsign &callsign, const QString &zuluTime)
747  {
748  Q_ASSERT(CThreadUtils::isInThisThread(this));
749  if (!this->isConnectedAndNotShuttingDown()) { return; }
750 
751  if (zuluTime.length() == 5) // for example 2000z
752  {
753  // Logic to set logoff time
754 
755  QStringView zuluTimeView(zuluTime);
756  // TODO Replace with Qt 6.0 QStringView::slice()
757  zuluTimeView.chop(1); // Remove z
758 
759  bool ok {};
760  const int h = zuluTimeView.left(2).toInt(&ok);
761  if (!ok) { return; }
762  const int m = zuluTimeView.right(2).toInt(&ok);
763  if (!ok) { return; }
764  QDateTime logoffDateTime = QDateTime::currentDateTimeUtc();
765  logoffDateTime.setTime(QTime(h, m));
766 
767  const CPropertyIndexVariantMap vm(CAtcStation::IndexLogoffTime, CVariant(logoffDateTime));
768  this->updateOnlineStation(callsign, vm);
769  }
770  }
771 
772  void CAirspaceMonitor::onCustomFSInnPacketReceived(const CCallsign &callsign, const QString &airlineIcaoDesignator,
773  const QString &aircraftIcaoDesignator,
774  const QString &combinedAircraftType, const QString &modelString)
775  {
776  // it can happen this is called before any queries
777  // ES sends FsInn packets for callsigns such as ACCGER1, which are hard to distinguish
778  // 1) checking if they are already in the list checks again ATC position which is safe
779  // 2) the ATC alike callsign check is guessing
780  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "not in main thread");
781  if (!callsign.isValid()) { return; } // aircraft OBS, other invalid callsigns
782  if (!this->isConnectedAndNotShuttingDown()) { return; }
783 
784  const bool isAircraft = this->isAircraftInRange(callsign);
785  const bool isAtc = m_atcStationsOnline.containsCallsign(callsign);
786  if (!isAircraft && !isAtc)
787  {
788  // we have no idea what we are dealing with, so we store it
789  const FsInnPacket fsInn(aircraftIcaoDesignator, airlineIcaoDesignator, combinedAircraftType, modelString);
790  m_tempFsInnPackets[callsign] = fsInn;
791  return;
792  }
793 
794  // Request of other client, I can get the other's model from that
795  // we do not ignore model string here
796  const CPropertyIndexVariantMap vm(CClient::IndexModelString, modelString);
797  this->updateOrAddClient(callsign, vm);
798 
799  if (isAircraft)
800  {
801  this->addMatchingReadinessFlag(callsign, ReceivedFsInnPacket); // in any case we did receive it
802 
803  const ReverseLookupLogging reverseLookupEnabled = this->isReverseLookupMessagesEnabled();
804  CStatusMessageList reverseLookupMessages;
805  CStatusMessageList *pReverseLookupMessages =
806  reverseLookupEnabled.testFlag(RevLogEnabled) ? &reverseLookupMessages : nullptr;
807 
808  CCallsign::addLogDetailsToList(
809  pReverseLookupMessages, callsign,
810  QStringLiteral("FsInn data from network: aircraft '%1', airline '%2', model '%3', combined '%4'")
811  .arg(aircraftIcaoDesignator, airlineIcaoDesignator, modelString, combinedAircraftType));
812 
813  QString usedModelString = modelString;
814 
815  const CAircraftMatcherSetup setup = m_matchingSettings.get();
816  if (!modelString.isEmpty() && !setup.isReverseLookupModelString())
817  {
818  usedModelString.clear();
819  CCallsign::addLogDetailsToList(
820  pReverseLookupMessages, callsign,
821  QStringLiteral("FsInn modelstring '%1' ignored because of setuo").arg(modelString));
822  }
823  else if (!CAircraftMatcher::isKnownModelString(modelString, callsign, pReverseLookupMessages))
824  {
825  // from the T701 test, do NOT use if model string is unknown
826  // this can overrride "swift livery strings", FsInn here only is useful with a known model string
827  usedModelString.clear();
828  CCallsign::addLogDetailsToList(
829  pReverseLookupMessages, callsign,
830  QStringLiteral("FsInn modelstring ignored, as modelstring '%1' is not known").arg(modelString));
831  }
832 
833  // if model string is empty, FsInn data are pointless
834  // in order not to override swift livery string data, we ignore those
835  if (!usedModelString.isEmpty())
836  {
837  this->addOrUpdateAircraftInRange(callsign, aircraftIcaoDesignator, airlineIcaoDesignator, QString(),
838  usedModelString, CAircraftModel::TypeFSInnData,
839  pReverseLookupMessages);
840  this->addReverseLookupMessages(callsign, reverseLookupMessages);
841  }
842  this->sendReadyForModelMatching(callsign, ReceivedFsInnPacket); // from FSInn
843  }
844  }
845 
846  void CAirspaceMonitor::onIcaoCodesReceived(const CCallsign &callsign, const QString &aircraftIcaoDesignator,
847  const QString &airlineIcaoDesignator, const QString &livery)
848  {
849  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "not in main thread");
850  if (!this->isConnectedAndNotShuttingDown()) { return; }
851  if (CBuildConfig::isLocalDeveloperDebugBuild())
852  {
853  SWIFT_VERIFY_X(callsign.isValid(), Q_FUNC_INFO, "invalid callsign");
854  }
855  if (!callsign.isValid()) { return; }
856  if (!this->isAircraftInRange(callsign)) { return; } // FSD overload issue, do not do anything if unknown
857 
858  const ReverseLookupLogging reverseLookupEnabled = this->isReverseLookupMessagesEnabled();
859  CStatusMessageList reverseLookupMessages;
860  CStatusMessageList *pReverseLookupMessages =
861  reverseLookupEnabled.testFlag(RevLogEnabled) ? &reverseLookupMessages : nullptr;
862  CCallsign::addLogDetailsToList(pReverseLookupMessages, callsign,
863  QStringLiteral("Data from network: aircraft '%1', airline '%2', livery '%3'")
864  .arg(aircraftIcaoDesignator, airlineIcaoDesignator, livery),
866 
867  const CClient client = this->getClientOrDefaultForCallsign(callsign);
868  this->addOrUpdateAircraftInRange(callsign, aircraftIcaoDesignator, airlineIcaoDesignator, livery,
870  pReverseLookupMessages);
871  this->addReverseLookupMessages(callsign, reverseLookupMessages);
872  this->sendReadyForModelMatching(callsign, ReceivedIcaoCodes); // ICAO codes received
873 
874  emit this->requestedNewAircraft(callsign, aircraftIcaoDesignator, airlineIcaoDesignator, livery);
875  }
876 
877  CAircraftModel CAirspaceMonitor::reverseLookupModelWithFlightplanData(
878  const CCallsign &callsign, const QString &aircraftIcaoString, const QString &airlineIcaoString,
879  const QString &liveryString, const QString &modelString, CAircraftModel::ModelType type,
880  CStatusMessageList *log, bool runMatchinScript)
881  {
882  const int modelSetCount = m_modelSetProvider->getModelSetCount();
883  CCallsign::addLogDetailsToList(
884  log, callsign, QStringLiteral("Reverse lookup (with FP data), model set count: %1").arg(modelSetCount),
886 
887  const DBTripleIds ids = CAircraftModel::parseNetworkLiveryString(liveryString);
888  const bool hasAnyId = ids.hasAnyId();
889  if (hasAnyId) { this->markAsSwiftClient(callsign); }
890 
891  CAircraftModel lookupModel; // result
892  const CAircraftModelList modelSet = m_modelSetProvider->getModelSet();
893  const CAircraftMatcherSetup setup = m_matchingSettings.get();
894  do {
895  // directly check model string
896  if (!modelString.isEmpty())
897  {
898  lookupModel = CAircraftMatcher::reverseLookupModelStringInDB(modelString, callsign,
899  setup.isReverseLookupModelString(), log);
900  if (lookupModel.hasValidDbKey()) { break; } // found by model string
901  }
902 
903  CLivery livery;
904  CAirlineIcaoCode airlineIcao;
905  CAircraftIcaoCode aircraftIcao;
906 
907  if (!setup.isReverseLookupSwiftLiveryIds())
908  {
909  CCallsign::addLogDetailsToList(
910  log, callsign, QStringLiteral("Reverse lookup of livery string '%1' disabled").arg(liveryString));
911  }
912  else if (hasAnyId)
913  {
914  if (ids.model >= 0)
915  {
916  lookupModel = CAircraftMatcher::reverseLookupModelId(ids.model, callsign, log);
917  if (lookupModel.hasValidDbKey()) { break; } // found by model id from livery string
918  }
919 
920  CAircraftMatcher::reverseLookupByIds(ids, aircraftIcao, livery, callsign, log);
921  if (livery.hasValidDbKey()) { airlineIcao = livery.getAirlineIcaoCode(); }
922 
923  if (aircraftIcao.hasValidDbKey() && livery.hasValidDbKey())
924  {
925  CCallsign::addLogDetailsToList(
926  log, callsign,
927  QStringLiteral("Using DB livery %1 and aircraft ICAO %2 to create model")
928  .arg(livery.getDbKeyAsString(), aircraftIcao.getDbKeyAsString()),
930 
931  // we have a valid livery from DB + valid aircraft ICAO from DB
932  lookupModel =
933  CAircraftModel(modelString, type, "By DB livery and aircraft ICAO", aircraftIcao, livery);
934  break;
935  }
936  }
937 
938  // now fuzzy search on aircraft
939  if (!aircraftIcao.hasValidDbKey())
940  {
941  aircraftIcao = CAircraftIcaoCode(aircraftIcaoString);
942  const bool knownAircraftIcao =
943  CAircraftMatcher::isKnownAircraftDesignator(aircraftIcaoString, callsign, log);
944  if (airlineIcao.isLoadedFromDb() && !knownAircraftIcao)
945  {
946  // we have no valid aircraft ICAO, so we do a fuzzy search among those
947  CCallsign::addLogDetailsToList(
948  log, callsign,
949  QStringLiteral("Fuzzy search among airline aircraft because '%1' is not known ICAO designator")
950  .arg(aircraftIcaoString));
951  const CAircraftIcaoCode foundIcao =
952  CAircraftMatcher::searchAmongAirlineAircraft(aircraftIcaoString, airlineIcao, callsign, log);
953  if (foundIcao.isLoadedFromDb()) { aircraftIcao = foundIcao; }
954  }
955  }
956 
957  // if we have a livery, we already know the airline, or the livery is a color livery
958  if (!airlineIcao.hasValidDbKey() && !livery.hasValidDbKey())
959  {
960  const CFlightPlanRemarks fpRemarks = this->tryToGetFlightPlanRemarks(callsign);
961  // const bool hasParsedAirlineRemarks = fpRemarks.hasParsedAirlineRemarks();
962 
963  QString airlineNameLookup;
964  QString telephonyLookup;
965  QString telephonyFromFp;
966  QString airlineNameFromFp;
967  QString airlineIcaoFromFp;
968 
969  if (fpRemarks.isEmpty())
970  {
971  CCallsign::addLogDetailsToList(log, callsign,
972  QStringLiteral("No flight plan remarks, skipping FP resolution"));
973  }
974  else
975  {
976  CCallsign::addLogDetailsToList(log, callsign,
977  QStringLiteral("FP remarks: '%1'").arg(fpRemarks.getRemarks()));
978  CCallsign::addLogDetailsToList(
979  log, callsign, QStringLiteral("FP rem.parsed: '%1'").arg(fpRemarks.toQString(true)));
980 
981  // FP data if any
983  callsign, log);
984  airlineNameFromFp =
986  airlineIcaoFromFp = fpRemarks.getAirlineIcao().getDesignator();
987 
988  // turn into names as in DB
989  airlineNameLookup = CAircraftMatcher::reverseLookupAirlineName(airlineNameFromFp);
990  telephonyLookup = CAircraftMatcher::reverseLookupTelephonyDesignator(telephonyFromFp);
991  if (!airlineNameLookup.isEmpty())
992  {
993  CCallsign::addLogDetailsToList(
994  log, callsign,
995  QStringLiteral("Using resolved airline name '%1' found by FP name '%2'")
996  .arg(airlineNameLookup, airlineNameFromFp),
998  }
999  if (!telephonyLookup.isEmpty())
1000  {
1001  CCallsign::addLogDetailsToList(
1002  log, callsign,
1003  QStringLiteral("Using resolved telephony designator '%1' found by FP telephoy '%2'")
1004  .arg(telephonyLookup, telephonyFromFp),
1006  }
1007  }
1008 
1009  // This code is needed WITH and WITHOUT FP data
1010 
1011  // INFO: CModelMatcherComponent::reverseLookup() contains the simulated lookup
1012  // changed with T701: resolve first against model set, then all DB data
1013  // if an airline is ambiguous most likely the one in the set is the best choice
1015  callsign, airlineIcaoString, airlineIcaoFromFp, true, airlineNameFromFp, telephonyFromFp, modelSet,
1016  log);
1017 
1018  // not found, create a search patterm
1019  if (!airlineIcao.isLoadedFromDb())
1020  {
1021  if (!airlineIcao.hasValidDesignator())
1022  {
1023  airlineIcao.setDesignator(airlineIcaoString.isEmpty() ? callsign.getAirlinePrefix() :
1024  airlineIcaoString);
1025  }
1026  if (!airlineNameLookup.isEmpty()) { airlineIcao.setName(airlineNameLookup); }
1027  if (!telephonyLookup.isEmpty()) { airlineIcao.setTelephonyDesignator(telephonyLookup); }
1028 
1029  // already try to resolve at this stage by a smart lookup with all the filled data from above
1030  airlineIcao = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcao, callsign, log);
1031  }
1032  }
1033 
1034  CCallsign::addLogDetailsToList(log, callsign,
1035  QStringLiteral("Used aircraft ICAO: '%1'").arg(aircraftIcao.toQString(true)),
1037  CCallsign::addLogDetailsToList(log, callsign,
1038  QStringLiteral("Used airline ICAO: '%1'").arg(airlineIcao.toQString(true)),
1040 
1041  // matching script is used below
1042  lookupModel = CAircraftMatcher::reverseLookupModel(callsign, aircraftIcao, airlineIcao, liveryString,
1043  modelString, setup, modelSet, type, log);
1044  }
1045  while (false);
1046 
1047  // model found
1048  lookupModel.setCallsign(callsign);
1049 
1050  // script
1051  if (runMatchinScript && setup.doRunMsReverseLookupScript())
1052  {
1053  const MatchingScriptReturnValues rv =
1054  CAircraftMatcher::reverseLookupScript(lookupModel, setup, modelSet, log);
1055  if (rv.runScriptAndModified())
1056  {
1057  if (rv.runScriptAndRerun())
1058  {
1059  CCallsign::addLogDetailsToList(
1060  log, callsign, QStringLiteral("Matching script: Re-run reverseLookupModelWithFlightplanData"),
1062  return CAirspaceMonitor::reverseLookupModelWithFlightplanData(
1064  rv.model.getLivery().getCombinedCode(), modelString, type, log, false);
1065  }
1066  lookupModel = rv.model;
1067  CCallsign::addLogDetailsToList(log, callsign,
1068  QStringLiteral("Matching script: Using model from matching script"),
1070  }
1071  }
1072  else { CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("No reverse lookup script used")); }
1073 
1074  // done
1075  lookupModel.setCallsign(callsign); // set again just in case modified by script
1076  return lookupModel;
1077  }
1078 
1079  bool CAirspaceMonitor::addNewAircraftInRange(const CSimulatedAircraft &aircraft)
1080  {
1081  const CCallsign callsign = aircraft.getCallsign();
1082  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign");
1083 
1084  if (!sApp || sApp->isShuttingDown() || !sApp->getWebDataServices()) { return false; }
1085 
1086  CSimulatedAircraft newAircraft(aircraft);
1087  newAircraft.setRendered(false); // reset rendering
1088  newAircraft.calculcateAndUpdateRelativeDistanceAndBearing(
1089  this->getOwnAircraftPosition()); // distance from myself
1090 
1091  if (this->getConnectedServer().getEcosystem() == CEcosystem::vatsim())
1092  {
1094  }
1095  const bool added = CRemoteAircraftProvider::addNewAircraftInRange(newAircraft);
1096  if (added)
1097  {
1098  const QPointer<CAirspaceMonitor> myself(this);
1099  QTimer::singleShot(MMVerifyMs, this, [=]() {
1100  // makes sure we have ICAO data
1101  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
1102  this->verifyReceivedIcaoData(callsign);
1103  });
1104 
1105  if (aircraft.hasModelString())
1106  {
1107  // most likely I could take the CG at this time from aircraft
1108  // to make sure it is really the DB value I query again
1109  const CAircraftModel model =
1111  const CLength cg = model.hasValidDbKey() ? model.getCG() : CLength::null();
1112  this->rememberCGFromDB(cg, aircraft.getModelString());
1113  this->rememberCGFromDB(cg, aircraft.getCallsign());
1114  }
1115  }
1116  return added;
1117  }
1118 
1119  void CAirspaceMonitor::asyncReInitializeAllAircraft(const CSimulatedAircraftList &aircraft,
1120  bool readyForModelMatching)
1121  {
1122  if (aircraft.isEmpty()) { return; }
1123  if (!sApp || sApp->isShuttingDown()) { return; }
1124 
1125  int c = 1;
1126  QPointer<CAirspaceMonitor> myself(this);
1127  for (const CSimulatedAircraft &ac : aircraft)
1128  {
1129  QTimer::singleShot(c++ * 25, this, [=] {
1130  if (!myself) { return; }
1131  myself->addNewAircraftInRange(ac);
1132  if (!readyForModelMatching) { return; }
1133  const CCallsign cs = ac.getCallsign();
1134 
1135  m_readiness.remove(cs); // cleanup
1136  const MatchingReadinessFlag ready = ReceivedAll;
1137  myself->sendReadyForModelMatching(cs, ready); // airspace monitor adding all aicraft
1138  });
1139  }
1140  }
1141 
1142  int CAirspaceMonitor::updateOnlineStation(const CCallsign &callsign, const CPropertyIndexVariantMap &vm,
1143  bool skipEqualValues, bool sendSignal)
1144  {
1145  const int c = m_atcStationsOnline.applyIfCallsign(callsign, vm, skipEqualValues);
1146  if (c > 0 && sendSignal) { emit this->changedAtcStationsOnline(); }
1147  return c;
1148  }
1149 
1150  bool CAirspaceMonitor::handleMaxRange(const CAircraftSituation &situation)
1151  {
1152  if (situation.isNull()) { return false; }
1153  if (m_maxDistanceNM < 0) { return true; }
1154  const int distanceNM =
1155  this->getOwnAircraft().calculateGreatCircleDistance(situation).valueInteger(CLengthUnit::NM());
1156  if (distanceNM > m_maxDistanceNMHysteresis)
1157  {
1158  this->removeAircraft(situation.getCallsign());
1159  return false;
1160  }
1161  return distanceNM <= m_maxDistanceNM;
1162  }
1163 
1164  bool CAirspaceMonitor::recallFsInnPacket(const CCallsign &callsign)
1165  {
1166  if (!m_tempFsInnPackets.contains(callsign)) { return false; }
1167  const FsInnPacket packet = m_tempFsInnPackets[callsign];
1168  m_tempFsInnPackets.remove(callsign);
1169  this->onCustomFSInnPacketReceived(callsign, packet.airlineIcaoDesignator, packet.aircraftIcaoDesignator,
1170  packet.combinedCode, packet.modelString);
1171  return true;
1172  }
1173 
1174  CSimulatedAircraft CAirspaceMonitor::addOrUpdateAircraftInRange(
1175  const CCallsign &callsign, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery,
1176  const QString &modelString, CAircraftModel::ModelType modelType, CStatusMessageList *log)
1177  {
1178  const CSimulatedAircraft aircraft = this->getAircraftInRangeForCallsign(callsign);
1179  if (aircraft.hasValidCallsign())
1180  {
1181  // only if we do not have a DB model yet
1182  if (!aircraft.getModel().hasValidDbKey())
1183  {
1184  CAircraftModel model = this->reverseLookupModelWithFlightplanData(callsign, aircraftIcao, airlineIcao,
1185  livery, modelString, modelType, log);
1186  model.updateMissingParts(aircraft.getModel());
1187  // Use anonymous as originator here, since the remote aircraft provider is ourselves and the call to
1188  // updateAircraftModel() would return without doing anything.
1189  this->updateAircraftModel(callsign, model, CIdentifier::null());
1190  this->updateAircraftNetworkModel(callsign, model, CIdentifier::null());
1191  }
1192  }
1193  else
1194  {
1195  /* FSD overload issue, do NOT to add new aircraft other than from positions
1196  const CAircraftModel model = this->reverseLookupModelWithFlightplanData(callsign, aircraftIcao, airlineIcao,
1197  livery, modelString, modelType, log); const CSimulatedAircraft initAircraft(model);
1198  this->addNewAircraftInRange(initAircraft);
1199  */
1200  }
1201  return aircraft;
1202  }
1203 
1204  bool CAirspaceMonitor::extrapolateElevation(CAircraftSituationList &situations,
1205  const CAircraftSituationChange &change)
1206  {
1207  if (situations.size() < 3) { return false; }
1208  // Q_ASSERT_X(situations.m_tsAdjustedSortHint == CAircraftSituationList::AdjustedTimestampLatestFirst,
1209  // Q_FUNC_INFO, "Need latest first");
1210  const CAircraftSituation old = situations[1];
1211  const CAircraftSituation older = situations[2];
1212  return extrapolateElevation(situations.front(), old, older, change);
1213  }
1214 
1215  bool CAirspaceMonitor::extrapolateElevation(CAircraftSituation &situationToBeUpdated,
1216  const CAircraftSituation &oldSituation,
1217  const CAircraftSituation &olderSituation,
1218  const CAircraftSituationChange &oldChange)
1219  {
1220  if (situationToBeUpdated.hasGroundElevation()) { return false; }
1221 
1222  // if acceptable transfer
1223  if (oldSituation.transferGroundElevationFromMe(situationToBeUpdated))
1224  {
1225  // change or keep type is the question
1226  // situationToBeUpdated.setGroundElevationInfo(Extrapolated);
1227  return true;
1228  }
1229  if (oldSituation.isNull() || olderSituation.isNull()) { return false; }
1230 
1231  if (oldChange.isNull()) { return false; }
1232  if (oldChange.isConstOnGround() && oldChange.hasAltitudeDevWithinAllowedRange() &&
1234  {
1235  // we have almost const altitudes and elevations
1236  const double deltaAltFt = qAbs(situationToBeUpdated.getAltitude().value(CLengthUnit::ft()) -
1237  olderSituation.getAltitude().value(CLengthUnit::ft()));
1238  if (deltaAltFt <= CAircraftSituation::allowedAltitudeDeviation().value(CLengthUnit::ft()))
1239  {
1240  // the current alt is also not much different
1241  situationToBeUpdated.setGroundElevation(oldSituation.getGroundElevation(),
1242  CAircraftSituation::Extrapolated);
1243  return true;
1244  }
1245  }
1246 
1247  return false;
1248  }
1249 
1250  void CAirspaceMonitor::onAircraftUpdateReceived(const CAircraftSituation &situation,
1251  const CTransponder &transponder)
1252  {
1253  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread");
1254  if (!this->isConnectedAndNotShuttingDown()) { return; }
1255 
1256  const CCallsign callsign(situation.getCallsign());
1257  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
1258 
1259  if (this->isCopilotAircraft(callsign)) { return; }
1260 
1261  // range (FSD overload issue)
1262  const bool validMaxRange = this->handleMaxRange(situation);
1263  const bool existsInRange = this->isAircraftInRange(callsign); // AFTER valid max.range check!
1264  if (!validMaxRange && !existsInRange) { return; } // not valid at all
1265 
1266  // update client info
1267  this->autoAdjustCientGndCapability(situation);
1268 
1269  // store situation history
1270  this->storeAircraftSituation(situation); // updates situation
1271 
1272  // in case we only have
1273  if (!existsInRange && validMaxRange)
1274  {
1275  // NEW aircraft
1276  const bool hasFsInnPacket = m_tempFsInnPackets.contains(callsign);
1277 
1278  CSimulatedAircraft aircraft;
1279  aircraft.setCallsign(callsign);
1280  aircraft.setSituation(situation);
1281  aircraft.setTransponder(transponder);
1282  this->addNewAircraftInRange(aircraft);
1283  this->sendInitialPilotQueries(callsign, true, !hasFsInnPacket);
1284 
1285  // new client, there is a chance it has been already created by custom packet
1286  const CClient client(callsign);
1287  this->addNewClient(client);
1288  }
1289  else if (existsInRange)
1290  {
1291  // update, aircraft already exists
1293  vm.addValue(CSimulatedAircraft::IndexTransponder, transponder);
1294  vm.addValue(CSimulatedAircraft::IndexSituation, situation);
1295  vm.addValue(CSimulatedAircraft::IndexRelativeDistance, this->calculateDistanceToOwnAircraft(situation));
1296  vm.addValue(CSimulatedAircraft::IndexRelativeBearing, this->calculateBearingToOwnAircraft(situation));
1297  this->updateAircraftInRange(callsign, vm);
1298  }
1299  }
1300 
1301  void CAirspaceMonitor::onAircraftInterimUpdateReceived(const CAircraftSituation &situation)
1302  {
1303  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread");
1304  if (!this->isConnectedAndNotShuttingDown()) { return; }
1305 
1306  const CCallsign callsign(situation.getCallsign());
1307 
1308  // checks
1309  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
1310 
1311  if (isCopilotAircraft(callsign)) { return; }
1312  if (!this->isAircraftInRange(callsign)) { return; }
1313 
1314  if (CBuildConfig::isLocalDeveloperDebugBuild())
1315  {
1316  Q_ASSERT_X(!situation.isNaNVectorDouble(), Q_FUNC_INFO, "Detected NaN");
1317  Q_ASSERT_X(!situation.isInfVectorDouble(), Q_FUNC_INFO, "Detected inf");
1318  Q_ASSERT_X(situation.isValidVectorRange(), Q_FUNC_INFO, "out of range [-1,1]");
1319  }
1320 
1321  // Interim packets do not have groundspeed, hence set the last known value.
1322  // If there is no full position available yet, throw this interim position away.
1323  CAircraftSituation interimSituation(situation);
1324  CAircraftSituationList history = this->remoteAircraftSituations(callsign);
1325  if (history.isEmpty()) { return; } // we need one full situation at least
1326  const CAircraftSituation lastSituation = history.latestObject();
1327 
1328  // changed position, continue and copy values
1329  interimSituation.setCurrentUtcTime();
1330  interimSituation.setGroundSpeed(lastSituation.getGroundSpeed());
1331 
1332  // store situation history
1333  this->storeAircraftSituation(interimSituation);
1334 
1335  const bool samePosition = lastSituation.equalNormalVectorDouble(interimSituation);
1336  if (samePosition) { return; } // nothing to update
1337 
1338  // update aircraft
1339  this->updateAircraftInRangeDistanceBearing(callsign, interimSituation,
1340  this->calculateDistanceToOwnAircraft(interimSituation),
1341  this->calculateBearingToOwnAircraft(interimSituation));
1342  }
1343 
1344  void CAirspaceMonitor::onAircraftVisualUpdateReceived(const swift::misc::aviation::CAircraftSituation &situation)
1345  {
1347  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread");
1348  if (!this->isConnectedAndNotShuttingDown()) { return; }
1349 
1350  const CCallsign callsign(situation.getCallsign());
1351 
1352  // checks
1353  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
1354 
1355  if (isCopilotAircraft(callsign)) { return; }
1356  if (!this->isAircraftInRange(callsign)) { return; }
1357 
1358  if (CBuildConfig::isLocalDeveloperDebugBuild())
1359  {
1360  Q_ASSERT_X(!situation.isNaNVectorDouble(), Q_FUNC_INFO, "Detected NaN");
1361  Q_ASSERT_X(!situation.isInfVectorDouble(), Q_FUNC_INFO, "Detected inf");
1362  Q_ASSERT_X(situation.isValidVectorRange(), Q_FUNC_INFO, "out of range [-1,1]");
1363  }
1364 
1365  // Visual packets do not have groundspeed, hence set the last known value.
1366  // If there is no full position available yet, throw this interim position away.
1368  CAircraftSituation visualSituation(situation);
1369  CAircraftSituationList history = this->remoteAircraftSituations(callsign);
1370  if (history.empty()) { return; } // we need one full situation at least
1371  const CAircraftSituation lastSituation = history.latestObject();
1372 
1373  // changed position, continue and copy values
1374  visualSituation.setCurrentUtcTime();
1375  visualSituation.setGroundSpeed(lastSituation.getGroundSpeed());
1376 
1377  // store situation history
1378  this->storeAircraftSituation(visualSituation);
1379 
1380  const bool samePosition = lastSituation.equalNormalVectorDouble(visualSituation);
1381  if (samePosition) { return; } // nothing to update
1382 
1383  // update aircraft
1384  this->updateAircraftInRangeDistanceBearing(callsign, visualSituation,
1385  this->calculateDistanceToOwnAircraft(visualSituation),
1386  this->calculateBearingToOwnAircraft(visualSituation));
1387  }
1388 
1389  void CAirspaceMonitor::onAircraftSimDataUpdateReceived(const CAircraftSituation &situation,
1390  const CAircraftParts &parts, qint64 currentOffsetMs,
1391  const QString &aircraftIcao, const QString &airlineIcao)
1392  {
1393  onAircraftUpdateReceived(situation, CTransponder(2000, CTransponder::ModeC));
1394 
1395  const CSimulatedAircraft aircraft = getAircraftInRangeForCallsign(situation.getCallsign());
1396  const CAircraftModel &model = aircraft.getNetworkModel();
1397  if (model.getAircraftIcaoCodeDesignator() != aircraftIcao)
1398  {
1399  onIcaoCodesReceived(situation.getCallsign(), aircraftIcao, airlineIcao, airlineIcao);
1400  }
1401 
1404  onAircraftConfigReceived(situation.getCallsign(), parts.toFullJson(), currentOffsetMs);
1405  }
1406 
1407  void CAirspaceMonitor::onConnectionStatusChanged(CConnectionStatus oldStatus, CConnectionStatus newStatus)
1408  {
1409  Q_UNUSED(oldStatus)
1410  if (newStatus == CConnectionStatus::Connecting && m_fsdClient)
1411  {
1412  const CServer server = m_fsdClient->getServer();
1413  const bool isVatsim = server.getEcosystem().isSystem(CEcosystem::VATSIM);
1414  const CLength maxRange(isVatsim ? 125 : -1, CLengthUnit::NM());
1415  this->setMaxRange(maxRange);
1416  }
1417 
1418  if (newStatus.isDisconnected()) { this->clear(); }
1419  }
1420 
1421  void CAirspaceMonitor::onPilotDisconnected(const CCallsign &callsign)
1422  {
1423  Q_ASSERT(CThreadUtils::isInThisThread(this));
1424 
1425  // in case of inconsistencies I always remove here
1426  this->removeFromAircraftCachesAndLogs(callsign);
1427  const bool removed = CRemoteAircraftProvider::removeAircraft(callsign);
1428  this->removeClient(callsign);
1429  if (removed) { emit this->removedAircraft(callsign); }
1430  }
1431 
1432  void CAirspaceMonitor::onFrequencyReceived(const CCallsign &callsign, const CFrequency &frequency)
1433  {
1434  Q_ASSERT(CThreadUtils::isInThisThread(this));
1435 
1436  // update
1437  const CPropertyIndexVariantMap vm({ CSimulatedAircraft::IndexCom1System, CComSystem::IndexActiveFrequency },
1438  CVariant::from(frequency));
1439  this->updateAircraftInRange(callsign, vm);
1440  }
1441 
1442  void CAirspaceMonitor::onRevBAircraftConfigReceived(const CCallsign &callsign, const QString &config,
1443  qint64 currentOffsetMs)
1444  {
1445 
1446  Q_ASSERT(CThreadUtils::isInThisThread(this));
1447  SWIFT_AUDIT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1448  if (callsign.isEmpty()) { return; }
1449 
1450  unsigned long pp = 0;
1451  bool ok {};
1452  pp = config.toULong(&ok, 10);
1453 
1454  bool gear = (pp & 1U);
1455  bool landLight = (pp & 2U);
1456  bool navLight = (pp & 4U);
1457  bool strobeLight = (pp & 8U);
1458  bool beaconLight = (pp & 16U);
1459  bool taxiLight = (pp & 32U);
1460  bool engine1Running = (pp & 64U);
1461  bool engine2Running = (pp & 128U);
1462  bool engine3Running = (pp & 256U);
1463  bool engine4Running = (pp & 512U);
1464 
1465  // CLogMessage(this).info(u"taxiLight %1 landLight %2 beaconLight %3 strobeLight %4 gear %5") << taxiLight <<
1466  // landLight << beaconLight << strobeLight << gear; CLogMessage(this).info(u"engine1Running %1 engine2Running %2
1467  // engine3Running %3 engine4Running %4") << engine1Running << engine2Running << engine3Running <<
1468  // engine4Running;
1469 
1470  CAircraftParts aircraftparts;
1471  aircraftparts.setGearDown(gear);
1472 
1473  CAircraftLights lights;
1474  lights.setStrobeOn(strobeLight);
1475  lights.setLandingOn(landLight);
1476  lights.setTaxiOn(taxiLight);
1477  lights.setBeaconOn(beaconLight);
1478  lights.setNavOn(navLight);
1479  aircraftparts.setLights(lights);
1480 
1481  CAircraftEngineList engines;
1482  engines.initEngines(4, false);
1483  engines.setEngineOn(1, engine1Running);
1484  engines.setEngineOn(2, engine2Running);
1485  engines.setEngineOn(3, engine3Running);
1486  engines.setEngineOn(4, engine4Running);
1487  aircraftparts.setEngines(engines);
1488 
1489  // make sure in any case right time and correct details
1490  aircraftparts.setCurrentUtcTime();
1491  aircraftparts.setTimeOffsetMs(currentOffsetMs);
1492  aircraftparts.setPartsDetails(CAircraftParts::FSDAircraftParts);
1493 
1494  // store parts
1495  this->storeAircraftParts(callsign, aircraftparts, true);
1496 
1497  // update client capability
1498  CClient client = this->getClientOrDefaultForCallsign(callsign);
1499  client.setUserCallsign(callsign); // make valid by setting a callsign
1500  if (client.hasCapability(CClient::FsdWithAircraftConfig)) { return; }
1501  client.addCapability(CClient::FsdWithAircraftConfig);
1502  this->setOtherClient(client);
1503  }
1504 
1505  void CAirspaceMonitor::onAircraftConfigReceived(const CCallsign &callsign, const QJsonObject &jsonObject,
1506  qint64 currentOffsetMs)
1507  {
1508  Q_ASSERT(CThreadUtils::isInThisThread(this));
1509  SWIFT_AUDIT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1510  if (callsign.isEmpty()) { return; }
1511 
1512  // store parts
1513  this->storeAircraftParts(callsign, jsonObject, currentOffsetMs);
1514 
1515  // update client capability
1516  CClient client = this->getClientOrDefaultForCallsign(callsign);
1517  client.setUserCallsign(callsign); // make valid by setting a callsign
1518  if (client.hasCapability(CClient::FsdWithAircraftConfig)) { return; }
1519  client.addCapability(CClient::FsdWithAircraftConfig);
1520  this->setOtherClient(client);
1521  }
1522 
1524  bool allowTestOffset)
1525  {
1526  const CCallsign callsign(situation.getCallsign());
1527  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign");
1528  if (callsign.isEmpty()) { return situation; }
1529 
1530  CAircraftSituation correctedSituation(allowTestOffset ? this->addTestAltitudeOffsetToSituation(situation) :
1531  situation);
1532  bool needToRequestElevation = false;
1533  bool canLikelySkipNearGround = correctedSituation.canLikelySkipNearGroundInterpolation();
1534  do {
1535  // Check if we can bail out and ignore all elevation handling
1536  //
1537  // rational:
1538  // a) elevation handling is expensive, and might even requests elevation from sim.
1539  // b) elevations not needed pollute the cache with "useless" values
1540  //
1541  if (canLikelySkipNearGround || correctedSituation.hasGroundElevation()) { break; }
1542 
1543  // set a defined state
1544  correctedSituation.resetGroundElevation();
1545 
1546  // Guessing gives better values, also for smaller planes
1547  // and avoids unnecessary elevation fetching for low flying smaller GA aircraft
1549  if (!icao.hasDesignator()) { break; } // what is that?
1550  if (icao.isVtol())
1551  {
1552  // VTOLs over 60kts likely not on ground
1553  // it could be that a low flying helicopter near ground
1554  if (situation.getGroundSpeed().value(CSpeedUnit::kts()) > 60) { break; }
1555  }
1556  else
1557  {
1558  // NO VTOL
1559  CLength cg(nullptr);
1560  CSpeed rotateSpeed(nullptr);
1561  icao.guessModelParameters(cg, rotateSpeed);
1562  if (!rotateSpeed.isNull())
1563  {
1564  rotateSpeed *= 1.25; // some margin
1565  if (situation.getGroundSpeed() > rotateSpeed) { break; }
1566  }
1567  }
1568 
1569  // fetch from cache or request
1570  const CAircraftSituationChange changesBeforeStoring =
1572 
1573  if (!changesBeforeStoring.isNull())
1574  {
1575  canLikelySkipNearGround = changesBeforeStoring.isConstAscending();
1576  if (canLikelySkipNearGround) { break; }
1577  }
1578 
1579  // we NEED elevation
1580  // actually distance of 200k/h 100ms is just 6.1 meters
1581  using namespace std::chrono_literals;
1582  const CLength dpt = correctedSituation.getDistancePerTime(100ms, CElevationPlane::singlePointRadius());
1583  const CAircraftSituationList situationsBeforeStoring = this->remoteAircraftSituations(callsign);
1584  const CAircraftSituation situationWithElvBeforeStoring =
1585  situationsBeforeStoring.findClosestElevationWithinRange(correctedSituation, dpt);
1586  if (situationWithElvBeforeStoring.transferGroundElevationFromMe(correctedSituation, dpt))
1587  {
1588  // from nearby situations of own aircraft, data was transferred above
1589  // we use transfer first as it is slightly faster as cache
1590  }
1591  else
1592  {
1593  // from cache
1594  const CLength distance(correctedSituation.getDistancePerTime250ms(
1595  CElevationPlane::singlePointRadius())); // distance per ms
1596  const CElevationPlane ep = this->findClosestElevationWithinRange(correctedSituation, distance);
1597  needToRequestElevation = ep.isNull();
1598  Q_ASSERT_X(needToRequestElevation || !ep.getRadius().isNull(), Q_FUNC_INFO, "null radius");
1599 
1600  // also can handle NULL elevations
1601  correctedSituation.setGroundElevation(ep, CAircraftSituation::FromCache);
1602  }
1603 
1604  // we have a new situation, so we try to get the elevation
1605  // we will requested it, but we set it upfront either by
1606  //
1607  // a) average value from other planes in the vicinity (cache, not moving) or
1608  // b) by extrapolating
1609  //
1610  // if we would NOT preset it, we could end up with oscillation
1611  //
1612  if (!correctedSituation.hasGroundElevation())
1613  {
1614  // average elevation
1615  // 1) from cache
1616  // 2) from planes on ground not moving
1617  bool fromNonMoving = false;
1618  bool triedExtrapolation = false;
1619  bool couldNotExtrapolate = false;
1620  int fromWhere = -1; // debugging
1621 
1622  CElevationPlane averagePlane =
1623  this->averageElevationOfOnGroundAircraft(situation, CElevationPlane::minorAirportRadius(), 2, 3);
1624  if (averagePlane.isNull())
1625  {
1626  averagePlane = this->averageElevationOfNonMovingAircraft(
1627  situation, CElevationPlane::minorAirportRadius(), 2, 3);
1628  fromNonMoving = true;
1629  }
1630 
1631  // do we have an elevation yet?
1632  if (!averagePlane.isNull())
1633  {
1634  correctedSituation.setGroundElevation(averagePlane, CAircraftSituation::Average);
1635  if (fromNonMoving) { m_foundInNonMovingAircraft++; }
1636  else { m_foundInElevationsOnGnd++; }
1637  fromWhere = 10;
1638  }
1639  else
1640  {
1641  // values before updating (i.e. "storing") so the new situation is not yet considered
1642  if (situationsBeforeStoring.size() > 1)
1643  {
1644  const bool extrapolated =
1645  extrapolateElevation(correctedSituation, situationsBeforeStoring[0],
1646  situationsBeforeStoring[1], changesBeforeStoring);
1647  triedExtrapolation = true;
1648  couldNotExtrapolate = !extrapolated;
1649  fromWhere = 20;
1650  }
1651  }
1652  Q_UNUSED(fromWhere)
1653 
1654  // still no elevation
1655  if (!correctedSituation.hasGroundElevation())
1656  {
1657  if (CBuildConfig::isLocalDeveloperDebugBuild())
1658  {
1659  // experimental, could become ASSERT
1660  SWIFT_VERIFY_X(needToRequestElevation, Q_FUNC_INFO, "Request should already be set");
1661  }
1662  needToRequestElevation = true; // should be "true" already
1663 
1664  Q_UNUSED(triedExtrapolation)
1665  Q_UNUSED(couldNotExtrapolate)
1666  }
1667  else
1668  {
1669  // sanity check on the situation
1670  if (CBuildConfig::isLocalDeveloperDebugBuild())
1671  {
1672  SWIFT_VERIFY_X(!correctedSituation.getGroundElevation().isZeroEpsilonConsidered(), Q_FUNC_INFO,
1673  "Suspicious elevation");
1674  }
1675  }
1676  } // gnd. elevation
1677  }
1678  while (false); // do we need elevation, find on
1679 
1680  // do we already have ground details?
1681  if (situation.getOnGroundInfo().getGroundDetails() == COnGroundInfo::NotSetGroundDetails)
1682  {
1683  const CClient client = this->getClientOrDefaultForCallsign(callsign);
1684  if (client.hasCapability(CClient::FsdWithGroundFlag))
1685  {
1686  // we rely on situation gnd.flag
1687  correctedSituation.setOnGroundDetails(COnGroundInfo::InFromNetwork);
1688  }
1689  else if (client.hasCapability(CClient::FsdWithAircraftConfig))
1690  {
1691  const CAircraftPartsList parts = this->remoteAircraftParts(callsign);
1692  if (!parts.isEmpty()) { correctedSituation.adjustGroundFlag(parts, true); }
1693  }
1694  }
1695 
1696  // CG from provider
1697  const CLength cg = this->getSimulatorOrDbCG(
1698  callsign,
1699  this->getCGFromDB(
1700  callsign)); // always x-check against simulator to override guessed values and reflect changed CGs
1701  if (!cg.isNull()) { correctedSituation.setCG(cg); }
1702 
1703  // store corrected situation
1704  correctedSituation = CRemoteAircraftProvider::storeAircraftSituation(correctedSituation,
1705  false); // we already added offset if any
1706 
1707  // check if we need want to request
1708  if (needToRequestElevation && !canLikelySkipNearGround)
1709  {
1710  // we have not requested so far, but we are NEAR ground
1711  // we expect at least not transferred cache or we are moving and have no provider elevation yet
1712  if (correctedSituation.isOtherElevationInfoBetter(CAircraftSituation::FromCache, false) ||
1713  (correctedSituation.isMoving() &&
1714  correctedSituation.isOtherElevationInfoBetter(CAircraftSituation::FromProvider, false)))
1715  {
1716  needToRequestElevation = this->requestElevation(correctedSituation);
1717  }
1718  }
1719 
1720  Q_UNUSED(needToRequestElevation)
1721  return correctedSituation;
1722  }
1723 
1724  void CAirspaceMonitor::sendInitialAtcQueries(const CCallsign &callsign)
1725  {
1726  if (!this->isConnectedAndNotShuttingDown()) { return; }
1727  m_fsdClient->sendClientQueryRealName(callsign);
1728  m_fsdClient->sendClientQueryAtis(callsign); // request ATIS and voice rooms
1729  m_fsdClient->sendClientQueryCapabilities(callsign);
1730  m_fsdClient->sendClientQueryServer(callsign);
1731  }
1732 
1733  void CAirspaceMonitor::queryAllOnlineAtcStations()
1734  {
1735  if (!this->isConnectedAndNotShuttingDown()) { return; }
1736  const CAtcStationList onlineStations = this->getAtcStationsOnlineRecalculated();
1737  for (const CAtcStation &station : onlineStations)
1738  {
1739  const CCallsign cs = station.getCallsign();
1740  if (cs.isEmpty()) { continue; }
1741  m_fsdClient->sendClientQueryRealName(cs);
1742  }
1743  }
1744 
1745  bool CAirspaceMonitor::sendNextStaggeredAtisQuery()
1746  {
1747  if (m_queryAtis.isEmpty()) { return false; }
1748  if (!this->isConnectedAndNotShuttingDown()) { return false; }
1749  const CCallsign cs = m_queryAtis.dequeue();
1750  if (!m_atcStationsOnline.containsCallsign(cs)) { return false; }
1751  m_fsdClient->sendClientQueryAtis(cs);
1752  return true;
1753  }
1754 
1755  void CAirspaceMonitor::sendInitialPilotQueries(const CCallsign &callsign, bool withIcaoQuery, bool withFsInn)
1756  {
1757  if (!this->isConnectedAndNotShuttingDown()) { return; }
1758 
1759  if (withIcaoQuery) { m_fsdClient->sendPlaneInfoRequest(callsign); }
1760  if (withFsInn) { m_fsdClient->sendPlaneInfoRequestFsinn(callsign); }
1761 
1762  m_fsdClient->sendClientQueryCom1Freq(callsign);
1763  m_fsdClient->sendClientQueryRealName(callsign);
1764  m_fsdClient->sendClientQueryCapabilities(callsign);
1765  m_fsdClient->sendClientQueryServer(callsign);
1766  }
1767 
1768  bool CAirspaceMonitor::sendNextStaggeredPilotDataQuery()
1769  {
1770  if (m_queryPilot.isEmpty()) { return false; }
1771  if (!this->isConnectedAndNotShuttingDown()) { return false; }
1772  const CCallsign cs = m_queryPilot.dequeue();
1773  if (!this->isAircraftInRange(cs)) { return false; }
1774  m_fsdClient->sendClientQueryCom1Freq(cs);
1775 
1776  // we only query ICAO if we have none yet
1777  // it happens sometimes with some FSD servers (e.g our testserver) a first query is skipped
1778  // Important: this is only a workaround and must not replace a sendInitialPilotQueries
1779  if (!this->getAircraftInRangeForCallsign(cs).hasAircraftDesignator()) { m_fsdClient->sendPlaneInfoRequest(cs); }
1780  return true;
1781  }
1782 
1783  bool CAirspaceMonitor::isConnected() const
1784  {
1785  return m_fsdClient && m_fsdClient->getConnectionStatus().isConnected();
1786  }
1787 
1788  bool CAirspaceMonitor::isConnectedAndNotShuttingDown() const
1789  {
1790  if (!sApp || sApp->isShuttingDown()) { return false; }
1791  return this->isConnected();
1792  }
1793 
1794  const CServer &CAirspaceMonitor::getConnectedServer() const
1795  {
1796  static const CServer empty;
1797  if (!this->isConnected()) { return empty; }
1798  return m_fsdClient->getServer();
1799  }
1800 
1801  const CEcosystem &CAirspaceMonitor::getCurrentEcosystem() const
1802  {
1803  return this->getConnectedServer().getEcosystem();
1804  }
1805 
1806  bool CAirspaceMonitor::supportsVatsimDataFile() const
1807  {
1808  const bool dataFile =
1810  return dataFile && this->getConnectedServer().getEcosystem().isSystem(CEcosystem::VATSIM);
1811  }
1812 
1813  CLength CAirspaceMonitor::calculateDistanceToOwnAircraft(const CAircraftSituation &situation) const
1814  {
1815  CLength distance = this->getOwnAircraft().calculateGreatCircleDistance(situation);
1816  distance.switchUnit(CLengthUnit::NM());
1817  return distance;
1818  }
1819 
1820  CAngle CAirspaceMonitor::calculateBearingToOwnAircraft(const CAircraftSituation &situation) const
1821  {
1822  CAngle angle = this->getOwnAircraft().calculateBearing(situation);
1823  angle.switchUnit(CAngleUnit::deg());
1824  return angle;
1825  }
1826 
1827  bool CAirspaceMonitor::isCopilotAircraft(const CCallsign &callsign) const
1828  {
1829  if (!sApp || sApp->isShuttingDown() || !sApp->getIContextNetwork()) { return false; }
1830 
1831  // It is only relevant if we are logged in as observer
1833 
1834  const CCallsign ownCallsign = this->getOwnAircraft().getCallsign();
1835  return ownCallsign.isMaybeCopilotCallsign(callsign);
1836  }
1837 
1838  CAirspaceMonitor::FsInnPacket::FsInnPacket(const QString &aircraftIcaoDesignator,
1839  const QString &airlineIcaoDesignator, const QString &combinedCode,
1840  const QString &modelString)
1841  : aircraftIcaoDesignator(aircraftIcaoDesignator.trimmed().toUpper()),
1842  airlineIcaoDesignator(airlineIcaoDesignator.trimmed().toUpper()),
1843  combinedCode(combinedCode.trimmed().toUpper()), modelString(modelString.trimmed())
1844  {}
1845 
1846 } // namespace swift::core
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
static swift::misc::simulation::CAircraftModel reverseLookupModelId(int id, const swift::misc::aviation::CCallsign &callsign, swift::misc::CStatusMessageList *log)
Try to find model by id.
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::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.
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 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::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.
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.
static bool isKnownAircraftDesignator(const QString &candidate, const swift::misc::aviation::CCallsign &callsign={}, swift::misc::CStatusMessageList *log=nullptr)
Is this aircraft designator known?
Class monitoring and analyzing (closest aircraft, outdated aircraft / watchdog) airspace in backgroun...
void timeoutAircraft(const swift::misc::aviation::CCallsign &callsign)
Callsign has timed out.
void timeoutAtc(const swift::misc::aviation::CCallsign &callsign)
Callsign has timed out.
void airspaceAircraftSnapshot(const swift::misc::simulation::CAirspaceAircraftSnapshot &snapshot)
New aircraft snapshot.
swift::misc::simulation::CAirspaceAircraftSnapshot getLatestAirspaceAircraftSnapshot() const
Get the latest snapshot.
misc::network::CUserList getUsers() const
Returns the list of users we know about.
MatchingReadinessFlag
Matching readiness.
@ ReceivedFsInnPacket
FsInn pcket received.
@ ReadyForMatchingSent
Read for matching sending.
@ ReceivedIcaoCodes
ICAO codes received.
virtual swift::misc::aviation::CAircraftSituation storeAircraftSituation(const swift::misc::aviation::CAircraftSituation &situation, bool allowTestOffset=true)
Store an aircraft situation under consideration of gnd.flags/CG and elevation.
void requestAtisUpdates()
Request to update ATC stations' ATIS data from the network.
misc::aviation::CAtcStationList getAtcStationsOnlineRecalculated()
Recalculate distance to own aircraft.
misc::aviation::CAtcStationList getAtcStationsOnline() const
Returns the current online ATC stations.
static const QStringList & getLogCategories()
Log categories.
misc::aviation::CFlightPlanRemarks tryToGetFlightPlanRemarks(const misc::aviation::CCallsign &callsign) const
Try to get flight plan remarks.
void atcStationDisconnected(const swift::misc::aviation::CAtcStation &station)
ATC station disconnected.
misc::network::CUserList getUsersForCallsigns(const misc::aviation::CCallsignSet &callsigns) const
Returns a list of the users corresponding to the given callsigns.
void requestAircraftDataUpdates()
Request to update other clients' data from the network.
misc::aviation::CFlightPlan loadFlightPlanFromNetwork(const misc::aviation::CCallsign &callsign)
Returns the loaded flight plan for the given callsign.
void readyForModelMatching(const swift::misc::simulation::CSimulatedAircraft &remoteAircraft)
Ready for model matching.
void changedAtisReceived(const swift::misc::aviation::CCallsign &callsign)
An ATIS has been received.
void changedAtcStationsOnline()
Online ATC stations were changed.
int reInitializeAllAircraft()
Re-init all aircrft.
misc::aviation::CAtcStation getAtcStationForComUnit(const misc::aviation::CComSystem &comSystem) const
Returns the closest ATC station operating on the given frequency, if any.
static QString enumToString(MatchingReadiness r)
As string.
void setMaxRange(const swift::misc::physical_quantities::CLength &range)
Max (FSD) range.
void clear()
Clear the contents.
static void registerHelp()
Register help.
static const QString & enumFlagToString(MatchingReadinessFlag r)
As string.
void gracefulShutdown()
Gracefully shut down, e.g. for thread safety.
bool updateFastPositionEnabled(const misc::aviation::CCallsign &callsign, bool enableFastPositonUpdates)
Members not implenented or fully implenented by CRemoteAircraftProvider.
misc::simulation::CAirspaceAircraftSnapshot getLatestAirspaceAircraftSnapshot() const
Members not implenented or fully implenented by CRemoteAircraftProvider.
bool isDeveloperFlagSet() const
Running with dev.flag?
Definition: application.h:173
bool hasWebDataServices() const
Web data services available?
const context::IContextNetwork * getIContextNetwork() const
Direct access to contexts if a CCoreFacade has been initialized.
bool isShuttingDown() const
Is application shutting down?
CWebDataServices * getWebDataServices() const
Get the web data services.
swift::misc::network::CUserList getUsersForCallsign(const swift::misc::aviation::CCallsign &callsign) const
Users by callsign.
void updateWithVatsimDataFileData(swift::misc::simulation::CSimulatedAircraft &aircraftToBeUdpated) const
Update with web data.
swift::misc::network::CVoiceCapabilities getVoiceCapabilityForCallsign(const swift::misc::aviation::CCallsign &callsign) const
Voice capabilities for given callsign.
swift::misc::simulation::CAircraftModel getModelForModelString(const QString &modelString) const
Model for model string if any.
vatsim::CVatsimDataFileReader * getVatsimDataFileReader() const
Data file reader.
swift::misc::aviation::CAtcStationList getAtcStationsForCallsign(const swift::misc::aviation::CCallsign &callsign) const
ATC stations by callsign.
virtual swift::misc::network::CLoginMode getLoginMode() const =0
Login mode.
FSD client Todo: Send (interim) data updates automatically Todo Check ':' in FSD messages....
Definition: fsdclient.h:85
void sendClientQueryCom1Freq(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:460
void sendClientQueryAtis(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:475
const swift::misc::network::CServer & getServer() const
Get the server.
Definition: fsdclient.h:161
void sendPlaneInfoRequest(const swift::misc::aviation::CCallsign &receiver)
Definition: fsdclient.cpp:719
void sendClientQueryRealName(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:465
void addInterimPositionReceiver(const swift::misc::aviation::CCallsign &receiver)
Interim positions.
Definition: fsdclient.h:211
void sendClientQueryServer(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:470
void removeInterimPositionReceiver(const swift::misc::aviation::CCallsign &receiver)
Interim positions.
Definition: fsdclient.h:215
void sendClientQueryAircraftConfig(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:485
void sendClientQueryFlightPlan(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:480
void sendPlaneInfoRequestFsinn(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:734
swift::misc::network::CConnectionStatus getConnectionStatus() const
Connection status.
Definition: fsdclient.h:249
swift::misc::aviation::CFlightPlanRemarks getFlightPlanRemarksForCallsign(const swift::misc::aviation::CCallsign &callsign) const
Flight plan remarks for callsign.
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
void remove(const T &object)
Efficient remove using the find and erase of the implementation container. Typically O(log n).
Definition: collection.h:307
bool isEmpty() const
Synonym for empty.
Definition: collection.h:191
Utility class which blocks until a signal is emitted or timeout reached.
Definition: eventloop.h:20
bool exec(int timeoutMs)
Begin processing events until the timeout or stop condition occurs.
Definition: eventloop.h:51
void stopWhen(const T *sender, F signal)
Event loop will stop if the given signal is received.
Definition: eventloop.h:34
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
static const CIdentifier & null()
Null (empty) identifier.
Definition: identifier.cpp:84
static const QString & matching()
Matching.
static const QString & network()
Network specific, but not necessarily one specific flight network.
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 & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Specialized value object compliant map for variants, based on indexes.
void addValue(const CPropertyIndex &index, const CVariant &value)
Add a value.
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
const_reference frontOrDefault() const
Access the first element, or a default-initialized value if the sequence is empty.
Definition: sequence.h:239
bool empty() const
Returns true if the sequence is empty.
Definition: sequence.h:282
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
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Utility methods for simple line parsing used with the command line.
Streamable status message, e.g.
constexpr static auto SeverityWarning
Status severities.
Status messages, e.g. from Core -> GUI.
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
Wrapper around QVariant which provides transparent access to CValueObject methods of the contained ob...
Definition: variant.h:66
static CVariant from(T &&value)
Synonym for fromValue().
Definition: variant.h:147
void setCurrentUtcTime()
Set the current time as timestamp.
OBJ latestObject() const
Latest object.
void setTimeOffsetMs(qint64 offset)
Milliseconds to add to timestamp for interpolation.
Value object encapsulating a list of aircraft engines.
void initEngines(int engineNumber, bool on)
Init some engines.
void setEngineOn(int engineNumber, bool on)
Set engine on/off.
Value object for ICAO classification.
bool hasDesignator() const
Aircraft designator?
bool isVtol() const
Is VTOL aircraft (helicopter, tilt wing)
QString getDesignatorDbKey() const
Designator and DB key.
void guessModelParameters(physical_quantities::CLength &guessedCGOut, physical_quantities::CSpeed &guessedVRotateOut) const
Guess aircraft model parameters.
Value object encapsulating information about aircraft's lights.
void setStrobeOn(bool on)
Set strobe lights.
void setBeaconOn(bool on)
Set beacon lights.
void setTaxiOn(bool on)
Set taxi lights.
void setNavOn(bool on)
Set nav lights.
void setLandingOn(bool on)
Set landing lights.
Value object encapsulating information of aircraft's parts.
Definition: aircraftparts.h:26
QJsonObject toIncrementalJson() const
Incremental JSON object.
void setEngines(const CAircraftEngineList &engines)
Set engines.
void setGearDown(bool down)
Set gear down.
Definition: aircraftparts.h:97
void setPartsDetails(PartsDetails details)
Set parts details.
void setLights(const CAircraftLights &lights)
Set aircraft lights.
Definition: aircraftparts.h:81
QJsonObject toFullJson() const
Full JSON Object.
Value object encapsulating a list of aircraft parts.
Value object about changes in situations.
bool isConstAscending() const
Constantly ascending?
bool hasAltitudeDevWithinAllowedRange() const
Altitude within CAircraftSituation::allowedAltitudeDeviation range.
bool hasElevationDevWithinAllowedRange() const
Elevation within CAircraftSituation::allowedAltitudeDeviation range.
bool isConstOnGround() const
Are all situations on ground?
Value object encapsulating information of an aircraft's situation.
const CAltitude & getGroundElevation() const
Elevation of the ground directly beneath.
void resetGroundElevation()
Reset ground elevation.
bool hasGroundElevation() const
Is ground elevation value available.
void setCG(const physical_quantities::CLength &cg)
Set CG.
physical_quantities::CLength getDistancePerTime250ms(const physical_quantities::CLength &min=physical_quantities::CLength::null()) const
Distance per milliseconds (250ms)
bool setGroundElevation(const aviation::CAltitude &altitude, GndElevationInfo info, bool transferred=false)
Elevation of the ground directly beneath at the given situation.
bool transferGroundElevationFromMe(CAircraftSituation &transferToSituation, const physical_quantities::CLength &radius=geo::CElevationPlane::singlePointRadius()) const
Transfer from "this" situation to otherSituation.
aviation::COnGroundInfo getOnGroundInfo() const
On ground info.
bool isOtherElevationInfoBetter(GndElevationInfo otherInfo, bool transferred) const
Is given info better (more accurate)?
bool canLikelySkipNearGroundInterpolation() const
Situation looks like an aircraft not near ground.
const CCallsign & getCallsign() const
Corresponding callsign.
const physical_quantities::CSpeed & getGroundSpeed() const
Get ground speed.
const CAltitude & getAltitude() const
Get altitude.
physical_quantities::CLength getDistancePerTime(std::chrono::milliseconds, const physical_quantities::CLength &min=physical_quantities::CLength::null()) const
Distance per milliseconds.
virtual bool isNull() const
Null situation.
bool isMoving() const
Is moving? Means ground speed > epsilon.
void setOnGroundDetails(COnGroundInfo::OnGroundDetails details)
On ground details.
bool adjustGroundFlag(const CAircraftParts &parts, bool alwaysSetDetails, double timeDeviationFactor=0.1, qint64 *differenceMs=nullptr)
Transfer ground flag from parts.
CAircraftSituation findClosestElevationWithinRange(const geo::ICoordinateGeodetic &coordinate, const physical_quantities::CLength &range=geo::CElevationPlane::singlePointRadius()) const
CLosest elevation within given range.
Value object for ICAO classification.
void setTelephonyDesignator(const QString &telephony)
Telephony designator such as "Speedbird".
QString getDesignatorDbKey() const
Designator and DB key.
const QString & getDesignator() const
Get airline, e.g. "DLH".
bool hasValidDesignator() const
Airline designator available?
void setName(const QString &name)
Set name.
void setDesignator(const QString &icaoDesignator)
Set airline, e.g. "DLH".
Value object encapsulating information about an ATC station.
Definition: atcstation.h:38
void setFrequency(const swift::misc::physical_quantities::CFrequency &frequency)
Set frequency.
Definition: atcstation.cpp:118
const CCallsign & getCallsign() const
Get callsign.
Definition: atcstation.h:84
void setCallsign(const CCallsign &callsign)
Set callsign.
Definition: atcstation.cpp:58
void setPosition(const swift::misc::geo::CCoordinateGeodetic &position)
Set position.
Definition: atcstation.h:144
bool setOnline(bool online)
Set online.
Definition: atcstation.cpp:130
void setRange(const physical_quantities::CLength &range)
Set range.
Definition: atcstation.h:150
Value object for a list of ATC stations.
CAtcStationList findIfComUnitTunedInChannelSpacing(const CComSystem &comUnit) const
Find 0..n stations tuned in frequency of COM unit (with channel spacing)
bool updateIfMessageChanged(const CInformationMessage &im, const CCallsign &callsign, bool overrideWithNewer)
Update if message changed.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
bool isMaybeCopilotCallsign(const CCallsign &pilotCallsign) const
Returns true if this is a co-pilot callsign of pilot. The logic is that the callsign is the same as t...
Definition: callsign.cpp:169
bool hasSuffix() const
Suffix such as "_TWR"?
Definition: callsign.cpp:294
bool isEmpty() const
Is empty?
Definition: callsign.h:63
QString getAirlinePrefix() const
Airline suffix (e.g. DLH1234 -> DLH) if applicable.
Definition: callsign.cpp:219
bool isValid() const
Valid callsign?
Definition: callsign.cpp:359
Value object for a set of callsigns.
Definition: callsignset.h:26
COM system (aka "radio")
Definition: comsystem.h:37
Value object for a flight plan.
Definition: flightplan.h:148
bool wasSentOrLoaded() const
Flight plan already sent.
Definition: flightplan.h:369
qint64 timeDiffSentOrLoadedMs() const
Received before n ms.
Definition: flightplan.h:372
Flight plan remarks, parsed values.
Definition: flightplan.h:46
const CAirlineIcaoCode & getAirlineIcao() const
Airline ICAO if provided in flight plan.
Definition: flightplan.h:67
bool isEmpty() const
Empty remarks?
Definition: flightplan.h:101
const QString & getFlightOperator() const
Operator, i.e. normally the airline name.
Definition: flightplan.h:64
const QString & getRadioTelephony() const
Radio telephony designator.
Definition: flightplan.h:61
const QString & getRemarks() const
The unparsed remarks.
Definition: flightplan.h:58
Value object encapsulating information message (ATIS, METAR, TAF)
Value object encapsulating information about an airpot.
Definition: livery.h:29
const CAirlineIcaoCode & getAirlineIcaoCode() const
Corresponding airline, if any.
Definition: livery.h:65
const QString & getCombinedCode() const
Combined code.
Definition: livery.h:71
OnGroundDetails getGroundDetails() const
Get ground details.
CONTAINER findByCallsign(const CCallsign &callsign) const
Find 0..n stations by callsign.
OBJ findFirstByCallsign(const CCallsign &callsign, const OBJ &ifNotFound={}) const
Find the first aircraft by callsign, if none return given one.
int applyIfCallsign(const CCallsign &callsign, const CPropertyIndexVariantMap &variantMap, bool skipEqualValues=true)
Apply for given callsign.
int removeByCallsign(const CCallsign &callsign)
Remove all objects with callsign.
bool containsCallsign(const CCallsign &callsign) const
Contains callsign?
QString getDbKeyAsString() const
DB key as string.
Definition: datastore.cpp:30
bool isLoadedFromDb() const
Loaded from DB.
Definition: datastore.cpp:49
bool hasValidDbKey() const
Has valid DB key.
Definition: datastore.h:102
Plane of same elevation, can be a single point or larger area (e.g. airport)
virtual bool isNull() const
Existing value?
const physical_quantities::CLength & getRadius() const
Radius.
bool isNaNVectorDouble() const
Check values.
physical_quantities::CLength calculateGreatCircleDistance(const ICoordinateGeodetic &otherCoordinate) const
Great circle distance.
bool isInfVectorDouble() const
Check values.
bool isValidVectorRange() const
Check values.
bool equalNormalVectorDouble(const std::array< double, 3 > &otherVector) const
Is equal? Epsilon considered.
physical_quantities::CAngle calculateBearing(const ICoordinateGeodetic &otherCoordinate) const
Initial bearing.
physical_quantities::CLength calculcateAndUpdateRelativeDistanceAndBearing(const geo::ICoordinateGeodetic &position)
Calculcate distance and bearing to plane, set it, and return distance.
void calculcateAndUpdateRelativeDistanceAndBearing(const ICoordinateGeodetic &position)
Calculate distances.
void sortByDistanceToReferencePosition()
If distance is already set, just sort container.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:74
Another client software.
Definition: client.h:27
bool hasCapability(Capability capability) const
Has capability?
Definition: client.cpp:64
const QString & getQueriedModelString() const
Model.
Definition: client.h:140
void addCapability(Capability capability)
Add capability.
Definition: client.cpp:33
bool setUserCallsign(const swift::misc::aviation::CCallsign &callsign)
User's callsign.
Definition: client.cpp:70
Value object encapsulating a list of voice rooms.
Definition: clientlist.h:27
virtual int removeClient(const aviation::CCallsign &callsign)
Remove client.
virtual int updateOrAddClient(const aviation::CCallsign &callsign, const CPropertyIndexVariantMap &vm, bool skipEqualValues=true)
Update or add a client.
virtual void markAsSwiftClient(const aviation::CCallsign &callsign)
Mark as other swift client.
virtual bool setOtherClient(const swift::misc::network::CClient &client)
Set client for its callsign.
virtual bool addNewClient(const CClient &client)
Add a new client, if existing nothing will be added.
virtual void setClients(const CClientList &clients)
Set other clients.
virtual CClientList getClients() const
clientprovider
virtual void clearClients()
Set other clients.
virtual CClient getClientOrDefaultForCallsign(const aviation::CCallsign &callsign) const
Other client for the given callsigns.
virtual bool autoAdjustCientGndCapability(const aviation::CAircraftSituation &situation)
Adjust gnd.flag capability from situation.
Value object encapsulating information about a connection status.
bool isConnected() const
Query status.
bool isDisconnected() const
Query status.
Ecosystem of server belonging together.
Definition: ecosystem.h:21
bool isSystem(System s) const
Is system?
Definition: ecosystem.h:54
@ Observer
Login as observer.
Definition: loginmode.h:24
Value object encapsulating information of a server.
Definition: server.h:28
const CEcosystem & getEcosystem() const
Get the ecosystem.
Definition: server.h:122
Value object encapsulating information of a user.
Definition: user.h:28
bool setCallsign(const aviation::CCallsign &callsign)
Set associated callsign.
Definition: user.cpp:62
bool hasCallsign() const
Has associated callsign?
Definition: user.h:89
Value object encapsulating a list of voice rooms.
Definition: userlist.h:26
Value object encapsulating information for voice capabilities.
bool isUnknown() const
Is capability known.
Physical unit angle (radians, degrees)
Definition: angle.h:23
Physical unit length (length)
Definition: length.h:18
bool isNegativeWithEpsilonConsidered() const
Value <= 0 epsilon considered.
int valueInteger(MU unit) const
As integer value.
PQ & switchUnit(const MU &newUnit)
Change unit, and convert value to maintain the same quantity.
double value(MU unit) const
Value in given unit.
bool isZeroEpsilonConsidered() const
Quantity value <= epsilon.
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
static DBTripleIds parseNetworkLiveryString(const QString &liveryString)
Split swift network string.
const aviation::CLivery & getLivery() const
Get livery.
const QString & getAircraftIcaoCodeDesignator() const
Aircraft ICAO code designator.
@ TypeFSInnData
model based on FSD ICAO data
Definition: aircraftmodel.h:78
@ TypeQueriedFromNetwork
model was queried by network protocol (ICAO data)
Definition: aircraftmodel.h:77
void setCallsign(const aviation::CCallsign &callsign)
Corresponding callsign if applicable.
const physical_quantities::CLength & getCG() const
Get center of gravity.
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Aircraft ICAO code.
const QString & getModelTypeAsString() const
Model type.
const QString getAirlineIcaoCodeVDesignator() const
Airline ICAO code designator.
void updateMissingParts(const CAircraftModel &otherModel, bool dbModelPriority=true)
Update missing parts from another model.
Value object encapsulating a list of aircraft models.
Delegating class which can be directly used to access an.
aviation::CAircraftSituation getOwnAircraftSituation() const
Own aircraft's position.
aviation::CCallsign getOwnCallsign() const
Own aircraft's callsign.
swift::misc::geo::CCoordinateGeodetic getOwnAircraftPosition() const
Own aircraft's position.
CSimulatedAircraft getOwnAircraft() const
Own aircraft.
Implementaion of the interface, which can also be used for testing.
virtual bool isAircraftInRange(const aviation::CCallsign &callsign) const
Is aircraft in range?
virtual CSimulatedAircraftList getAircraftInRange() const
All remote aircraft.
virtual aviation::CAircraftSituation storeAircraftSituation(const aviation::CAircraftSituation &situation, bool allowTestAltitudeOffset=true)
Store an aircraft situation.
bool addNewAircraftInRange(const CSimulatedAircraft &aircraft)
Add new aircraft, ignored if aircraft already exists.
aviation::CAircraftSituation addTestAltitudeOffsetToSituation(const aviation::CAircraftSituation &situation) const
Add an offset for testing.
bool removeAircraft(const aviation::CCallsign &callsign)
Remove all aircraft in range.
virtual int remoteAircraftSituationsCount(const aviation::CCallsign &callsign) const
Number of remote aircraft situations for callsign.
virtual geo::CElevationPlane averageElevationOfNonMovingAircraft(const aviation::CAircraftSituation &reference, const physical_quantities::CLength &range, int minValues=1, int sufficientValues=2) const
Average elevation of aircraft in given range, which are NOT moving.
virtual aviation::CAircraftSituationList remoteAircraftSituations(const aviation::CCallsign &callsign) const
Rendered aircraft situations (per callsign, time history)
virtual aviation::CAircraftSituationChangeList remoteAircraftSituationChanges(const aviation::CCallsign &callsign) const
Aircraft changes.
int updateAircraftInRange(const aviation::CCallsign &callsign, const CPropertyIndexVariantMap &vm, bool skipEqualValues=true)
Update aircraft.
void storeAircraftParts(const aviation::CCallsign &callsign, const aviation::CAircraftParts &parts, bool removeOutdated)
Store an aircraft part.
bool updateAircraftInRangeDistanceBearing(const aviation::CCallsign &callsign, const aviation::CAircraftSituation &situation, const physical_quantities::CLength &distance, const physical_quantities::CAngle &bearing)
Update aircraft bearing, distance and situation.
virtual void rememberCGFromDB(const physical_quantities::CLength &cgFromDB, const aviation::CCallsign &callsign)
CG values from DB.
void removeReverseLookupMessages(const aviation::CCallsign &callsign)
Remove the lookup messages.
void addReverseLookupMessages(const aviation::CCallsign &callsign, const CStatusMessageList &messages)
Reverse lookup messages.
void airspaceAircraftSnapshot(const swift::misc::simulation::CAirspaceAircraftSnapshot &snapshot)
New aircraft snapshot.
virtual bool updateAircraftNetworkModel(const aviation::CCallsign &callsign, const CAircraftModel &model, const CIdentifier &originator)
Change network model.
virtual bool updateAircraftModel(const aviation::CCallsign &callsign, const CAircraftModel &model, const CIdentifier &originator)
Change model.
virtual physical_quantities::CLength getCGFromDB(const aviation::CCallsign &callsign) const
CG values from DB.
virtual bool updateFastPositionEnabled(const aviation::CCallsign &callsign, bool enableFastPositonUpdates)
Change fast position updates.
ReverseLookupLogging whatToReverseLog() const
What to log?
virtual ReverseLookupLogging isReverseLookupMessagesEnabled() const
Enabled reverse lookup logging?
virtual CAircraftModel getAircraftInRangeModelForCallsign(const aviation::CCallsign &callsign) const
Aircraft model for callsign.
virtual CSimulatedAircraft getAircraftInRangeForCallsign(const aviation::CCallsign &callsign) const
Aircraft for callsign.
virtual aviation::CAircraftPartsList remoteAircraftParts(const aviation::CCallsign &callsign) const
All parts (per callsign, time history)
virtual void enableReverseLookupMessages(ReverseLookupLogging enable)
Enable reverse lookup logging.
void removeAllAircraft()
Remove all aircraft in range.
void addReverseLookupMessage(const aviation::CCallsign &callsign, const CStatusMessage &message)
Reverse lookup messages.
void removedAircraft(const swift::misc::aviation::CCallsign &callsign)
An aircraft disappeared.
Comprehensive information of an aircraft.
void setCallsign(const aviation::CCallsign &callsign)
Set callsign.
bool hasModelString() const
Has model string?
void setSituation(const aviation::CAircraftSituation &situation)
Set situation. Won't overwrite the velocity unless it held the default value.
const network::CUser & getPilot() const
Get user.
const simulation::CAircraftModel & getNetworkModel() const
Get network model.
const aviation::CCallsign & getCallsign() const
Get callsign.
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Get aircraft ICAO info.
bool hasValidCallsign() const
Valid callsign?
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping)
void setTransponder(const aviation::CTransponder &transponder)
Set transponder.
const aviation::CAirlineIcaoCode & getAirlineIcaoCode() const
Airline ICAO code if any.
const QString & getModelString() const
Get model string.
Value object encapsulating a list of aircraft.
bool requestElevation(const geo::ICoordinateGeodetic &reference, const aviation::CCallsign &callsign)
Request elevation, there is no guarantee the requested elevation will be available in the provider.
physical_quantities::CLength getSimulatorOrDbCG(const aviation::CCallsign &callsign, const physical_quantities::CLength &dbCG) const
Get CG per callsign, NULL if not found.
geo::CElevationPlane averageElevationOfOnGroundAircraft(const aviation::CAircraftSituation &reference, const physical_quantities::CLength &range, int minValues, int sufficientValues) const
Average elevation of "on ground" cached values.
geo::CElevationPlane findClosestElevationWithinRange(const geo::ICoordinateGeodetic &reference, const physical_quantities::CLength &range) const
Find closest elevation (or return NULL)
Value object encapsulating a list of aircraft models.
virtual int getModelSetCount() const =0
Get the model set models count.
virtual CAircraftModelList getModelSet() const =0
Get the model set models.
Direct threadsafe in memory access to own aircraft.
bool parseCommandLine(const QString &commandLine, const swift::misc::CIdentifier &originator)
Parse a given command line.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Generate data for testing purposes.
Definition: testdata.h:45
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
bool hasAnyId() const
Any valid id?
Definition: aircraftmodel.h:56
swift::misc::simulation::CAircraftModel model
the model
bool runScriptAndModified() const
Did run the script with modified result.
bool runScriptAndRerun() const
Did run the script and re-run requested.
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26