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 
94  Q_ASSERT_X(sApp && sApp->hasWebDataServices(), Q_FUNC_INFO, "Missing data reader");
95 
96  if (this->supportsVatsimDataFile())
97  {
98  connect(sApp->getWebDataServices()->getVatsimDataFileReader(), &CVatsimDataFileReader::dataFileRead, this,
99  &CAirspaceMonitor::onReceivedVatsimDataFile);
100  }
101 
102  // Force snapshot in the main event loop
105 
106  // Analyzer
107  connect(m_analyzer, &CAirspaceAnalyzer::timeoutAircraft, this, &CAirspaceMonitor::onPilotDisconnected,
109  connect(m_analyzer, &CAirspaceAnalyzer::timeoutAtc, this, &CAirspaceMonitor::onAtcControllerDisconnected,
111 
112  // timers
113  connect(&m_fastProcessTimer, &QTimer::timeout, this, &CAirspaceMonitor::fastProcessing);
114  connect(&m_slowProcessTimer, &QTimer::timeout, this, &CAirspaceMonitor::slowProcessing);
115  m_fastProcessTimer.start(FastProcessIntervalMs);
116  m_slowProcessTimer.start(SlowProcessIntervalMs);
117 
118  // dot command
120  }
121 
122  bool CAirspaceMonitor::updateFastPositionEnabled(const CCallsign &callsign, bool enableFastPositonUpdates)
123  {
124  const bool r = CRemoteAircraftProvider::updateFastPositionEnabled(callsign, enableFastPositonUpdates);
125  if (m_fsdClient && sApp && !sApp->isShuttingDown())
126  {
127  // thread safe update of m_network
128  const QPointer<CAirspaceMonitor> myself(this);
129  QTimer::singleShot(0, m_fsdClient, [=] {
130  if (!myself) { return; }
131  if (m_fsdClient)
132  {
133  if (enableFastPositonUpdates) { m_fsdClient->addInterimPositionReceiver(callsign); }
134  else { m_fsdClient->removeInterimPositionReceiver(callsign); }
135  }
136  });
137  }
138  return r;
139  }
140 
142  {
144  return cats;
145  }
146 
148  {
149  Q_ASSERT_X(m_analyzer, Q_FUNC_INFO, "No analyzer");
150  return m_analyzer->getLatestAirspaceAircraftSnapshot();
151  }
152 
154  {
155  CFlightPlan plan;
156  QPointer<CAirspaceMonitor> myself(this);
157 
158  // use cache, but not for own callsign (always reload)
159  if (m_flightPlanCache.contains(callsign)) { plan = m_flightPlanCache[callsign]; }
160  const bool ownAircraft = this->getOwnCallsign() == callsign;
161  if (ownAircraft || !plan.wasSentOrLoaded() || plan.timeDiffSentOrLoadedMs() > 30 * 1000)
162  {
163  // outdated, or not in cache at all, or NOT own aircraft
164  plan = CFlightPlan(); // reset
165  m_flightPlanCache.remove(callsign); // loading FP from network
166  m_fsdClient->sendClientQueryFlightPlan(callsign);
167 
168  // with this little trick we try to make an asynchronous signal / slot based approach
169  // a synchronous return value
170  CEventLoop eventLoop(this);
171  eventLoop.stopWhen(m_fsdClient, &CFSDClient::flightPlanReceived,
172  [=](const auto &cs, const auto &) { return cs == callsign; });
173  if (eventLoop.exec(1500))
174  {
175  if (!myself || !sApp || sApp->isShuttingDown()) // cppcheck-suppress knownConditionTrueFalse
176  {
177  return {};
178  }
179  if (m_flightPlanCache.contains(callsign)) { plan = m_flightPlanCache[callsign]; }
180  }
181  }
182  return plan;
183  }
184 
186  {
187  if (callsign.isEmpty()) { return {}; }
188 
189  // full flight plan's remarks
190  if (m_flightPlanCache.contains(callsign)) { return m_flightPlanCache[callsign].getFlightPlanRemarks(); }
191 
192  // remarks only
193  if (this->supportsVatsimDataFile())
194  {
196  }
197 
198  // unsupported
199  return {};
200  }
201 
203  {
205  return m_atcStationsOnline;
206  }
207 
209  {
210  CUserList users;
211  for (const CAtcStation &station : m_atcStationsOnline)
212  {
213  CUser user = station.getController();
214  if (!user.hasCallsign()) { user.setCallsign(station.getCallsign()); }
215  users.push_back(user);
216  }
217  for (const CSimulatedAircraft &aircraft : this->getAircraftInRange())
218  {
219  CUser user = aircraft.getPilot();
220  if (!user.hasCallsign()) { user.setCallsign(aircraft.getCallsign()); }
221  users.push_back(user);
222  }
223  return users;
224  }
225 
227  {
228  CUserList users;
229  if (callsigns.isEmpty()) { return users; }
230  CCallsignSet searchList(callsigns);
231 
232  // myself, which is not in the lists below
233  const CSimulatedAircraft myAircraft(getOwnAircraft());
234  if (!myAircraft.getCallsign().isEmpty() && searchList.contains(myAircraft.getCallsign()))
235  {
236  searchList.remove(myAircraft.getCallsign());
237  users.push_back(myAircraft.getPilot());
238  }
239 
240  // do aircraft first, this will handle most callsigns
241  for (const CSimulatedAircraft &aircraft : this->getAircraftInRange())
242  {
243  if (searchList.isEmpty()) { break; }
244  const CCallsign callsign = aircraft.getCallsign();
245  if (searchList.contains(callsign))
246  {
247  const CUser user = aircraft.getPilot();
248  users.push_back(user);
249  searchList.remove(callsign);
250  }
251  }
252 
253  for (const CAtcStation &station : m_atcStationsOnline)
254  {
255  if (searchList.isEmpty()) { break; }
256  const CCallsign callsign = station.getCallsign();
257  if (searchList.contains(callsign))
258  {
259  const CUser user = station.getController();
260  users.push_back(user);
261  searchList.remove(callsign);
262  }
263  }
264 
265  // we might have unresolved callsigns
266  // those are the ones not in range
267  for (const CCallsign &callsign : std::as_const(searchList))
268  {
269  const CUserList usersByCallsign = sApp->getWebDataServices()->getUsersForCallsign(callsign);
270  if (usersByCallsign.isEmpty())
271  {
272  const CUser user(callsign);
273  users.push_back(user);
274  }
275  else { users.push_back(usersByCallsign[0]); }
276  }
277  return users;
278  }
279 
281  {
282  CAtcStationList stations = m_atcStationsOnline.findIfComUnitTunedInChannelSpacing(comSystem);
283  if (stations.isEmpty()) { return {}; }
285  return stations.front();
286  }
287 
289  {
290  if (!this->isConnectedAndNotShuttingDown()) { return; }
291  const CSimulatedAircraftList aircraftInRange(this->getAircraftInRange());
292  for (const CSimulatedAircraft &aircraft : aircraftInRange)
293  {
294  // staggered version
295  const CCallsign cs(aircraft.getCallsign());
296  if (!m_queryPilot.contains(cs)) { m_queryPilot.enqueue(aircraft.getCallsign()); }
297  }
298  }
299 
301  {
302  if (!this->isConnectedAndNotShuttingDown()) { return; }
303  const CAtcStationList stations(this->getAtcStationsOnline());
304  for (const CAtcStation &station : stations)
305  {
306  const CCallsign cs = station.getCallsign();
307 
308  // changed to staggered version
309  // m_network->sendAtisQuery(cs); // for each online station
310  if (!m_queryAtis.contains(cs)) { m_queryAtis.enqueue(cs); }
311  }
312  }
313 
314  void CAirspaceMonitor::testCreateDummyOnlineAtcStations(int number)
315  {
316  if (number < 1) { return; }
317  m_atcStationsOnline.push_back(CTesting::createAtcStations(number));
318  emit this->changedAtcStationsOnline();
319  }
320 
321  void CAirspaceMonitor::testAddAircraftParts(const CCallsign &callsign, const CAircraftParts &parts,
322  bool incremental)
323  {
324  this->onAircraftConfigReceived(callsign, incremental ? parts.toIncrementalJson() : parts.toFullJson(), 5000);
325  }
326 
328  {
329  static const QString nr("not ready");
330  static const QString icao("rec. ICAO");
331  static const QString fsinn("rec. FsInn");
332  static const QString ready("ready sent");
333  static const QString rec("recursive");
334  switch (r)
335  {
336  case NotReady: return nr;
337  case ReceivedIcaoCodes: return icao;
338  case ReceivedFsInnPacket: return fsinn;
339  case ReadyForMatchingSent: return ready;
340  case RecursiveCall: return rec;
341  default: break;
342  }
343  static const QString unknown("????");
344  return unknown;
345  }
346 
348  {
349  QStringList s;
350  if (r.testFlag(NotReady)) { s << enumFlagToString(NotReady); }
351  if (r.testFlag(ReceivedIcaoCodes)) { s << enumFlagToString(ReceivedIcaoCodes); }
352  if (r.testFlag(ReceivedFsInnPacket)) { s << enumFlagToString(ReceivedFsInnPacket); }
354  if (r.testFlag(RecursiveCall)) { s << enumFlagToString(RecursiveCall); }
355  return s.join(", ");
356  }
357 
358  bool CAirspaceMonitor::parseCommandLine(const QString &commandLine, const CIdentifier &originator)
359  {
360  Q_UNUSED(originator;)
361  if (commandLine.isEmpty()) { return false; }
362  static const QStringList cmds({ ".fsd" });
363  CSimpleCommandParser parser(cmds);
364  parser.parse(commandLine);
365  if (!parser.isKnownCommand()) { return false; }
366  if (parser.matchesCommand(".fsd"))
367  {
368  if (parser.countParts() < 2) { return false; }
369  if (parser.matchesPart(1, "range") && parser.countParts() > 2)
370  {
371  const QString r = parser.part(2);
372  const CLength d = CLength::parsedFromString(r);
373  this->setMaxRange(d);
374  }
375  }
376  return false;
377  }
378 
379  void CAirspaceMonitor::fastProcessing()
380  {
381  if (!this->isConnectedAndNotShuttingDown()) { return; }
382 
383  // only send one query
384  const bool send = this->sendNextStaggeredAtisQuery();
385  if (!send) { this->sendNextStaggeredPilotDataQuery(); }
386  }
387 
388  void CAirspaceMonitor::slowProcessing()
389  {
390  if (!this->isConnectedAndNotShuttingDown()) { return; }
391  this->queryAllOnlineAtcStations();
392  }
393 
395  {
396  m_flightPlanCache.clear();
397  m_tempFsInnPackets.clear();
398  m_readiness.clear();
399  this->removeAllOnlineAtcStations();
400  this->removeAllAircraft();
401  this->clearClients();
402 
403  m_foundInNonMovingAircraft = 0;
404  m_foundInElevationsOnGnd = 0;
405  }
406 
408 
410  {
411  const CSimulatedAircraftList aircraft = this->getAircraftInRange();
413  this->asyncReInitializeAllAircraft(aircraft, true);
414  return aircraft.size();
415  }
416 
418  {
419  if (range.isNull() || range.isNegativeWithEpsilonConsidered() || range.isZeroEpsilonConsidered())
420  {
421  // off
422  m_maxDistanceNM = -1;
423  m_maxDistanceNMHysteresis = -1;
424  CLogMessage(this).info(u"No airspace range restriction");
425  return;
426  }
427 
428  const int rIntNM = range.valueInteger(CLengthUnit::NM());
429  CLogMessage(this).info(u"Set airspace max. range to %1NM") << rIntNM;
430  m_maxDistanceNM = rIntNM;
431  m_maxDistanceNMHysteresis = qRound(rIntNM * 1.1);
432  }
433 
434  void CAirspaceMonitor::onRealNameReplyReceived(const CCallsign &callsign, const QString &realname)
435  {
436  if (!this->isConnectedAndNotShuttingDown() || realname.isEmpty()) { return; }
437  if (!sApp || sApp->isShuttingDown() || !sApp->getWebDataServices()) { return; }
438 
439  bool wasAtc = false;
440  const QString rn = CUser::beautifyRealName(realname);
441 
442  if (callsign.hasSuffix())
443  {
444  // very likely and ATC callsign
445  const CPropertyIndexVariantMap vm =
446  CPropertyIndexVariantMap({ CAtcStation::IndexController, CUser::IndexRealName }, rn);
447  const int c1 = this->updateOnlineStation(callsign, vm, false, true);
448  wasAtc = c1 > 0;
449  }
450 
451  if (!wasAtc)
452  {
453  const CPropertyIndexVariantMap vm =
454  CPropertyIndexVariantMap({ CSimulatedAircraft::IndexPilot, CUser::IndexRealName }, rn);
455  this->updateAircraftInRange(callsign, vm);
456  }
457 
458  // Client
460  CPropertyIndexVariantMap vm = CPropertyIndexVariantMap({ CClient::IndexUser, CUser::IndexRealName }, rn);
461  vm.addValue({ CClient::IndexVoiceCapabilities }, voiceCaps);
462  this->updateOrAddClient(callsign, vm, false);
463  }
464 
465  void CAirspaceMonitor::onCapabilitiesReplyReceived(const CCallsign &callsign, CClient::Capabilities clientCaps)
466  {
467  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) { return; }
469  CPropertyIndexVariantMap vm(CClient::IndexCapabilities, CVariant::from(clientCaps));
470  vm.addValue({ CClient::IndexVoiceCapabilities }, voiceCaps);
471  this->updateOrAddClient(callsign, vm, false);
472 
473  // for aircraft parts
474  if (clientCaps.testFlag(CClient::FsdWithAircraftConfig))
475  {
476  m_fsdClient->sendClientQueryAircraftConfig(callsign);
477  }
478  }
479 
480  void CAirspaceMonitor::onServerReplyReceived(const CCallsign &callsign, const QString &server)
481  {
482  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty() || server.isEmpty()) { return; }
483  const CPropertyIndexVariantMap vm(CClient::IndexServer, server);
484  this->updateOrAddClient(callsign, vm);
485  }
486 
487  void CAirspaceMonitor::onFlightPlanReceived(const CCallsign &callsign, const CFlightPlan &flightPlan)
488  {
489  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) { return; }
490  CFlightPlan plan(flightPlan);
491  plan.setWhenLastSentOrLoaded(QDateTime::currentDateTimeUtc());
492  m_flightPlanCache.insert(callsign, plan);
493  }
494 
495  void CAirspaceMonitor::removeAllOnlineAtcStations()
496  {
497  m_atcStationsOnline.clear();
498  m_queryAtis.clear();
499  }
500 
501  void CAirspaceMonitor::removeAllAircraft()
502  {
504 
505  // non thread safe parts
506  m_flightPlanCache.clear();
507  m_readiness.clear();
508  m_queryPilot.clear();
509  }
510 
511  void CAirspaceMonitor::removeFromAircraftCachesAndLogs(const CCallsign &callsign)
512  {
513  if (callsign.isEmpty()) { return; }
514  m_flightPlanCache.remove(callsign);
515  m_readiness.remove(callsign);
516  this->removeReverseLookupMessages(callsign);
517  }
518 
519  void CAirspaceMonitor::onReceivedVatsimDataFile()
520  {
521  Q_ASSERT(CThreadUtils::isInThisThread(this));
522  if (!sApp || sApp->isShuttingDown() || !sApp->getWebDataServices()) { return; }
523  CClientList clients(this->getClients()); // copy
524  bool changed = false;
525  for (auto &client : clients)
526  {
527  if (client.hasSpecifiedVoiceCapabilities()) { continue; } // we already have voice caps
528  const CVoiceCapabilities vc =
529  sApp->getWebDataServices()->getVoiceCapabilityForCallsign(client.getCallsign());
530  if (vc.isUnknown()) { continue; }
531  changed = true;
532  client.setVoiceCapabilities(vc);
533  }
534  if (!changed) { return; }
535  this->setClients(clients);
536  }
537 
538  CAirspaceMonitor::Readiness &CAirspaceMonitor::addMatchingReadinessFlag(const CCallsign &callsign,
540  {
541  Readiness &readiness = m_readiness[callsign].addFlag(mrf);
542  return readiness;
543  }
544 
545  void CAirspaceMonitor::sendReadyForModelMatching(const CCallsign &callsign, MatchingReadinessFlag rf)
546  {
547  if (!this->isConnectedAndNotShuttingDown()) { return; }
548  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign");
549 
550  // set flag and init ts
551  Readiness &readiness = this->addMatchingReadinessFlag(callsign, rf);
552 
553  // skip if already sent in the last x seconds
554  const qint64 ageMs = readiness.getAgeMs();
555  if (readiness.wasMatchingSent() && readiness.getAgeMs() < MMMaxAgeThresholdMs) { return; }
556 
557  // checking for min. situations ensures the aircraft is stable, can be interpolated ...
558  const CSimulatedAircraft remoteAircraft = this->getAircraftInRangeForCallsign(callsign);
559  const bool validRemoteCs = remoteAircraft.hasValidCallsign();
560  const bool minSituations = this->remoteAircraftSituationsCount(callsign) > 1;
561  const bool complete = validRemoteCs && minSituations &&
562  (readiness.receivedAll() ||
563  // disable, because it can be model string is unknown in FsInn data
564  // (remoteAircraft.getModel().getModelType() == CAircraftModel::TypeFSInnData) || // here
565  // we know we have all data
566  (remoteAircraft.hasModelString()) // we cannot expect more info
567  );
568 
569  const ReverseLookupLogging revLogEnabled = this->whatToReverseLog();
570  if (rf != Verified && validRemoteCs && ageMs <= MMMaxAgeMs && !complete)
571  {
572  static const QString ws("Wait for further data, '%1' age: %2ms ts: %3");
573  static const QString format("hh:mm:ss.zzz");
574  if (!revLogEnabled.testFlag(RevLogSimplifiedInfo))
575  {
577  callsign,
578  ws.arg(readiness.toQString()).arg(ageMs).arg(QDateTime::currentDateTimeUtc().toString(format)));
579  }
580 
581  const QPointer<CAirspaceMonitor> myself(this);
582  QTimer::singleShot(MMCheckAgainMs, this, [=]() {
583  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
584  if (!this->isAircraftInRange(callsign))
585  {
586  const CStatusMessage m =
587  CCallsign::logMessage(callsign, "No longer in range", CAirspaceMonitor::getLogCategories());
588  this->addReverseLookupMessage(callsign, m);
589  return;
590  }
591  if (rf != ReceivedFsInnPacket)
592  {
593  // here we call recursively like an FsInn packet was received
594  if (this->recallFsInnPacket(callsign)) { return; }
595  }
596 
597  // check again
598  this->sendReadyForModelMatching(callsign, RecursiveCall); // recursively
599  });
600 
601  // end as we will call again in some time
602  return;
603 
604  } // not yet complete
605 
606  // some checks for special conditions, e.g. logout -> empty list, but still signals pending
607  if (validRemoteCs)
608  {
609  static const QString readyForMatching(
610  "Ready (%1) for matching callsign '%2' with model type '%3', ICAO: '%4' '%5'");
611 
612  readiness.setFlag(ReadyForMatchingSent); // stored in readiness as reference
613  const QString readyMsg = readyForMatching.arg(readiness.toQString(), callsign.toQString(),
614  remoteAircraft.getModel().getModelTypeAsString(),
615  remoteAircraft.getAircraftIcaoCode().getDesignatorDbKey(),
616  remoteAircraft.getAirlineIcaoCode().getDesignatorDbKey());
617  const CStatusMessage m = CCallsign::logMessage(callsign, readyMsg, getLogCategories());
618  this->addReverseLookupMessage(callsign, m);
619 
620  emit this->readyForModelMatching(remoteAircraft);
621  }
622  else
623  {
624  const CStatusMessage m = CCallsign::logMessage(
625  callsign, "Ignoring this aircraft, not found in range list, disconnected, or no callsign",
627  this->addReverseLookupMessage(callsign, m);
628  m_readiness.remove(callsign);
629  }
630  }
631 
632  void CAirspaceMonitor::verifyReceivedIcaoData(const CCallsign &callsign)
633  {
634  if (callsign.isEmpty() || !this->isAircraftInRange(callsign)) { return; }
635 
636  // set flag and init ts
637  Readiness &readiness = m_readiness[callsign];
638  if (readiness.wasMatchingSent()) { return; }
639  if (readiness.wasVerified())
640  {
641  CLogMessage(this).warning(u"Verfied ICAO data of '%1' again, using it as it is! Most likely incomplete "
642  u"data from the other client")
643  << callsign;
644  this->sendReadyForModelMatching(callsign, Verified);
645  return;
646  }
647 
648  // new trial
649  readiness.addFlag(Verified);
650 
651  if (!readiness.receivedIcaoCodes())
652  {
653  CLogMessage(this).info(u"Query ICAO codes for '%1' again") << callsign;
654  this->sendInitialPilotQueries(callsign, true, true);
655 
656  // if the queries now yield a result all will be fine
657  // otherwise we need to check again
658  const QPointer<CAirspaceMonitor> myself(this);
659  QTimer::singleShot(MMVerifyMs, this, [=]() {
660  // makes sure we have ICAO data
661  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
662  this->verifyReceivedIcaoData(callsign);
663  });
664  return;
665  }
666 
667  // normally we should never get here
668  CLogMessage(this).info(u"Verified '%1' again, has ICAO codes, ready for matching!") << callsign;
669  this->sendReadyForModelMatching(callsign, Verified);
670  }
671 
672  void CAirspaceMonitor::onAtcPositionUpdate(const CCallsign &callsign, const CFrequency &frequency,
673  const CCoordinateGeodetic &position,
675  {
676  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
677  if (!this->isConnectedAndNotShuttingDown()) { return; }
678 
679  const CAtcStationList stationsWithCallsign = m_atcStationsOnline.findByCallsign(callsign);
680  if (stationsWithCallsign.isEmpty())
681  {
682  CAtcStation station;
683 
684  // if connected to VATSIM, init with data from data file
685  if (this->getConnectedServer().getEcosystem() == CEcosystem::vatsim())
686  {
688  }
689 
690  station.setCallsign(callsign);
691  station.setRange(range);
692  station.setFrequency(frequency);
693  station.setPosition(position);
694  station.setOnline(true);
696 
697  m_atcStationsOnline.push_back(station);
698 
699  // subsequent queries
700  this->sendInitialAtcQueries(callsign);
701 
702  // update distances
704 
705  emit this->changedAtcStationsOnline();
706  }
707  else
708  {
709  // update
711  vm.addValue(CAtcStation::IndexFrequency, frequency);
712  vm.addValue(CAtcStation::IndexPosition, position);
713  vm.addValue(CAtcStation::IndexRange, range);
714  const int changed = m_atcStationsOnline.applyIfCallsign(callsign, vm, true);
715  if (changed > 0) { emit this->changedAtcStationsOnline(); }
716  }
717  }
718 
719  void CAirspaceMonitor::onAtcControllerDisconnected(const CCallsign &callsign)
720  {
721  Q_ASSERT(CThreadUtils::isInThisThread(this));
722  if (!this->isConnectedAndNotShuttingDown()) { return; }
723 
724  this->removeClient(callsign);
725  if (m_atcStationsOnline.containsCallsign(callsign))
726  {
727  const CAtcStation removedStation = m_atcStationsOnline.findFirstByCallsign(callsign);
728  m_atcStationsOnline.removeByCallsign(callsign);
729  emit this->changedAtcStationsOnline();
730  emit this->atcStationDisconnected(removedStation);
731  }
732  }
733 
734  void CAirspaceMonitor::onAtisReceived(const CCallsign &callsign, const CInformationMessage &atisMessage)
735  {
736  Q_ASSERT(CThreadUtils::isInThisThread(this));
737  if (!this->isConnectedAndNotShuttingDown() || callsign.isEmpty()) return;
738  const bool changedAtis = m_atcStationsOnline.updateIfMessageChanged(atisMessage, callsign, true);
739 
740  // signal
741  if (changedAtis) { emit this->changedAtisReceived(callsign); }
742  }
743 
744  void CAirspaceMonitor::onAtisLogoffTimeReceived(const CCallsign &callsign, const QString &zuluTime)
745  {
746  Q_ASSERT(CThreadUtils::isInThisThread(this));
747  if (!this->isConnectedAndNotShuttingDown()) { return; }
748 
749  if (zuluTime.length() == 5) // for example 2000z
750  {
751  // Logic to set logoff time
752 
753  QStringView zuluTimeView(zuluTime);
754  // TODO Replace with Qt 6.0 QStringView::slice()
755  zuluTimeView.chop(1); // Remove z
756 
757  bool ok {};
758  const int h = zuluTimeView.left(2).toInt(&ok);
759  if (!ok) { return; }
760  const int m = zuluTimeView.right(2).toInt(&ok);
761  if (!ok) { return; }
762  QDateTime logoffDateTime = QDateTime::currentDateTimeUtc();
763  logoffDateTime.setTime(QTime(h, m));
764 
765  const CPropertyIndexVariantMap vm(CAtcStation::IndexLogoffTime, CVariant(logoffDateTime));
766  this->updateOnlineStation(callsign, vm);
767  }
768  }
769 
770  void CAirspaceMonitor::onCustomFSInnPacketReceived(const CCallsign &callsign, const QString &airlineIcaoDesignator,
771  const QString &aircraftIcaoDesignator,
772  const QString &combinedAircraftType, const QString &modelString)
773  {
774  // it can happen this is called before any queries
775  // ES sends FsInn packets for callsigns such as ACCGER1, which are hard to distinguish
776  // 1) checking if they are already in the list checks again ATC position which is safe
777  // 2) the ATC alike callsign check is guessing
778  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "not in main thread");
779  if (!callsign.isValid()) { return; } // aircraft OBS, other invalid callsigns
780  if (!this->isConnectedAndNotShuttingDown()) { return; }
781 
782  const bool isAircraft = this->isAircraftInRange(callsign);
783  const bool isAtc = m_atcStationsOnline.containsCallsign(callsign);
784  if (!isAircraft && !isAtc)
785  {
786  // we have no idea what we are dealing with, so we store it
787  const FsInnPacket fsInn(aircraftIcaoDesignator, airlineIcaoDesignator, combinedAircraftType, modelString);
788  m_tempFsInnPackets[callsign] = fsInn;
789  return;
790  }
791 
792  // Request of other client, I can get the other's model from that
793  // we do not ignore model string here
794  const CPropertyIndexVariantMap vm(CClient::IndexModelString, modelString);
795  this->updateOrAddClient(callsign, vm);
796 
797  if (isAircraft)
798  {
799  this->addMatchingReadinessFlag(callsign, ReceivedFsInnPacket); // in any case we did receive it
800 
801  const ReverseLookupLogging reverseLookupEnabled = this->isReverseLookupMessagesEnabled();
802  CStatusMessageList reverseLookupMessages;
803  CStatusMessageList *pReverseLookupMessages =
804  reverseLookupEnabled.testFlag(RevLogEnabled) ? &reverseLookupMessages : nullptr;
805 
806  CCallsign::addLogDetailsToList(
807  pReverseLookupMessages, callsign,
808  QStringLiteral("FsInn data from network: aircraft '%1', airline '%2', model '%3', combined '%4'")
809  .arg(aircraftIcaoDesignator, airlineIcaoDesignator, modelString, combinedAircraftType));
810 
811  QString usedModelString = modelString;
812 
813  const CAircraftMatcherSetup setup = m_matchingSettings.get();
814  if (!modelString.isEmpty() && !setup.isReverseLookupModelString())
815  {
816  usedModelString.clear();
817  CCallsign::addLogDetailsToList(
818  pReverseLookupMessages, callsign,
819  QStringLiteral("FsInn modelstring '%1' ignored because of setup").arg(modelString));
820  }
821  else if (!CAircraftMatcher::isKnownModelString(modelString, callsign, pReverseLookupMessages))
822  {
823  // from the T701 test, do NOT use if model string is unknown
824  // this can overrride "swift livery strings", FsInn here only is useful with a known model string
825  usedModelString.clear();
826  CCallsign::addLogDetailsToList(
827  pReverseLookupMessages, callsign,
828  QStringLiteral("FsInn modelstring ignored, as modelstring '%1' is not known").arg(modelString));
829  }
830 
831  // if model string is empty, FsInn data are pointless
832  // in order not to override swift livery string data, we ignore those
833  if (!usedModelString.isEmpty())
834  {
835  this->addOrUpdateAircraftInRange(callsign, aircraftIcaoDesignator, airlineIcaoDesignator, QString(),
836  usedModelString, CAircraftModel::TypeFSInnData,
837  pReverseLookupMessages);
838  this->addReverseLookupMessages(callsign, reverseLookupMessages);
839  }
840  this->sendReadyForModelMatching(callsign, ReceivedFsInnPacket); // from FSInn
841  }
842  }
843 
844  void CAirspaceMonitor::onIcaoCodesReceived(const CCallsign &callsign, const QString &aircraftIcaoDesignator,
845  const QString &airlineIcaoDesignator, const QString &livery)
846  {
847  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "not in main thread");
848  if (!this->isConnectedAndNotShuttingDown()) { return; }
849  if (CBuildConfig::isLocalDeveloperDebugBuild())
850  {
851  SWIFT_VERIFY_X(callsign.isValid(), Q_FUNC_INFO, "invalid callsign");
852  }
853  if (!callsign.isValid()) { return; }
854  if (!this->isAircraftInRange(callsign)) { return; } // FSD overload issue, do not do anything if unknown
855 
856  const ReverseLookupLogging reverseLookupEnabled = this->isReverseLookupMessagesEnabled();
857  CStatusMessageList reverseLookupMessages;
858  CStatusMessageList *pReverseLookupMessages =
859  reverseLookupEnabled.testFlag(RevLogEnabled) ? &reverseLookupMessages : nullptr;
860  CCallsign::addLogDetailsToList(pReverseLookupMessages, callsign,
861  QStringLiteral("Data from network: aircraft '%1', airline '%2', livery '%3'")
862  .arg(aircraftIcaoDesignator, airlineIcaoDesignator, livery),
864 
865  const CClient client = this->getClientOrDefaultForCallsign(callsign);
866  this->addOrUpdateAircraftInRange(callsign, aircraftIcaoDesignator, airlineIcaoDesignator, livery,
868  pReverseLookupMessages);
869  this->addReverseLookupMessages(callsign, reverseLookupMessages);
870  this->sendReadyForModelMatching(callsign, ReceivedIcaoCodes); // ICAO codes received
871 
872  emit this->requestedNewAircraft(callsign, aircraftIcaoDesignator, airlineIcaoDesignator, livery);
873  }
874 
875  CAircraftModel CAirspaceMonitor::reverseLookupModelWithFlightplanData(
876  const CCallsign &callsign, const QString &aircraftIcaoString, const QString &airlineIcaoString,
877  const QString &liveryString, const QString &modelString, CAircraftModel::ModelType type,
878  CStatusMessageList *log, bool runMatchinScript)
879  {
880  const int modelSetCount = m_modelSetProvider->getModelSetCount();
881  CCallsign::addLogDetailsToList(
882  log, callsign, QStringLiteral("Reverse lookup (with FP data), model set count: %1").arg(modelSetCount),
884 
885  const DBTripleIds ids = CAircraftModel::parseNetworkLiveryString(liveryString);
886  const bool hasAnyId = ids.hasAnyId();
887  if (hasAnyId) { this->markAsSwiftClient(callsign); }
888 
889  CAircraftModel lookupModel; // result
890  const CAircraftModelList modelSet = m_modelSetProvider->getModelSet();
891  const CAircraftMatcherSetup setup = m_matchingSettings.get();
892  do {
893  // directly check model string
894  if (!modelString.isEmpty())
895  {
896  lookupModel = CAircraftMatcher::reverseLookupModelStringInDB(modelString, callsign,
897  setup.isReverseLookupModelString(), log);
898  if (lookupModel.hasValidDbKey()) { break; } // found by model string
899  }
900 
901  CLivery livery;
902  CAirlineIcaoCode airlineIcao;
903  CAircraftIcaoCode aircraftIcao;
904 
905  if (!setup.isReverseLookupSwiftLiveryIds())
906  {
907  CCallsign::addLogDetailsToList(
908  log, callsign, QStringLiteral("Reverse lookup of livery string '%1' disabled").arg(liveryString));
909  }
910  else if (hasAnyId)
911  {
912  if (ids.model >= 0)
913  {
914  lookupModel = CAircraftMatcher::reverseLookupModelId(ids.model, callsign, log);
915  if (lookupModel.hasValidDbKey()) { break; } // found by model id from livery string
916  }
917 
918  CAircraftMatcher::reverseLookupByIds(ids, aircraftIcao, livery, callsign, log);
919  if (livery.hasValidDbKey()) { airlineIcao = livery.getAirlineIcaoCode(); }
920 
921  if (aircraftIcao.hasValidDbKey() && livery.hasValidDbKey())
922  {
923  CCallsign::addLogDetailsToList(
924  log, callsign,
925  QStringLiteral("Using DB livery %1 and aircraft ICAO %2 to create model")
926  .arg(livery.getDbKeyAsString(), aircraftIcao.getDbKeyAsString()),
928 
929  // we have a valid livery from DB + valid aircraft ICAO from DB
930  lookupModel =
931  CAircraftModel(modelString, type, "By DB livery and aircraft ICAO", aircraftIcao, livery);
932  break;
933  }
934  }
935 
936  // now fuzzy search on aircraft
937  if (!aircraftIcao.hasValidDbKey())
938  {
939  aircraftIcao = CAircraftIcaoCode(aircraftIcaoString);
940  const bool knownAircraftIcao =
941  CAircraftMatcher::isKnownAircraftDesignator(aircraftIcaoString, callsign, log);
942  if (airlineIcao.isLoadedFromDb() && !knownAircraftIcao)
943  {
944  // we have no valid aircraft ICAO, so we do a fuzzy search among those
945  CCallsign::addLogDetailsToList(
946  log, callsign,
947  QStringLiteral("Fuzzy search among airline aircraft because '%1' is not known ICAO designator")
948  .arg(aircraftIcaoString));
949  const CAircraftIcaoCode foundIcao =
950  CAircraftMatcher::searchAmongAirlineAircraft(aircraftIcaoString, airlineIcao, callsign, log);
951  if (foundIcao.isLoadedFromDb()) { aircraftIcao = foundIcao; }
952  }
953  }
954 
955  // if we have a livery, we already know the airline, or the livery is a color livery
956  if (!airlineIcao.hasValidDbKey() && !livery.hasValidDbKey())
957  {
958  const CFlightPlanRemarks fpRemarks = this->tryToGetFlightPlanRemarks(callsign);
959  // const bool hasParsedAirlineRemarks = fpRemarks.hasParsedAirlineRemarks();
960 
961  QString airlineNameLookup;
962  QString telephonyLookup;
963  QString telephonyFromFp;
964  QString airlineNameFromFp;
965  QString airlineIcaoFromFp;
966 
967  if (fpRemarks.isEmpty())
968  {
969  CCallsign::addLogDetailsToList(log, callsign,
970  QStringLiteral("No flight plan remarks, skipping FP resolution"));
971  }
972  else
973  {
974  CCallsign::addLogDetailsToList(log, callsign,
975  QStringLiteral("FP remarks: '%1'").arg(fpRemarks.getRemarks()));
976  CCallsign::addLogDetailsToList(
977  log, callsign, QStringLiteral("FP rem.parsed: '%1'").arg(fpRemarks.toQString(true)));
978 
979  // FP data if any
981  callsign, log);
982  airlineNameFromFp =
984  airlineIcaoFromFp = fpRemarks.getAirlineIcao().getDesignator();
985 
986  // turn into names as in DB
987  airlineNameLookup = CAircraftMatcher::reverseLookupAirlineName(airlineNameFromFp);
988  telephonyLookup = CAircraftMatcher::reverseLookupTelephonyDesignator(telephonyFromFp);
989  if (!airlineNameLookup.isEmpty())
990  {
991  CCallsign::addLogDetailsToList(
992  log, callsign,
993  QStringLiteral("Using resolved airline name '%1' found by FP name '%2'")
994  .arg(airlineNameLookup, airlineNameFromFp),
996  }
997  if (!telephonyLookup.isEmpty())
998  {
999  CCallsign::addLogDetailsToList(
1000  log, callsign,
1001  QStringLiteral("Using resolved telephony designator '%1' found by FP telephoy '%2'")
1002  .arg(telephonyLookup, telephonyFromFp),
1004  }
1005  }
1006 
1007  // This code is needed WITH and WITHOUT FP data
1008 
1009  // INFO: CModelMatcherComponent::reverseLookup() contains the simulated lookup
1010  // changed with T701: resolve first against model set, then all DB data
1011  // if an airline is ambiguous most likely the one in the set is the best choice
1013  callsign, airlineIcaoString, airlineIcaoFromFp, true, airlineNameFromFp, telephonyFromFp, modelSet,
1014  log);
1015 
1016  // not found, create a search patterm
1017  if (!airlineIcao.isLoadedFromDb())
1018  {
1019  if (!airlineIcao.hasValidDesignator())
1020  {
1021  airlineIcao.setDesignator(airlineIcaoString.isEmpty() ? callsign.getAirlinePrefix() :
1022  airlineIcaoString);
1023  }
1024  if (!airlineNameLookup.isEmpty()) { airlineIcao.setName(airlineNameLookup); }
1025  if (!telephonyLookup.isEmpty()) { airlineIcao.setTelephonyDesignator(telephonyLookup); }
1026 
1027  // already try to resolve at this stage by a smart lookup with all the filled data from above
1028  airlineIcao = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcao, callsign, log);
1029  }
1030  }
1031 
1032  CCallsign::addLogDetailsToList(log, callsign,
1033  QStringLiteral("Used aircraft ICAO: '%1'").arg(aircraftIcao.toQString(true)),
1035  CCallsign::addLogDetailsToList(log, callsign,
1036  QStringLiteral("Used airline ICAO: '%1'").arg(airlineIcao.toQString(true)),
1038 
1039  // matching script is used below
1040  lookupModel = CAircraftMatcher::reverseLookupModel(callsign, aircraftIcao, airlineIcao, liveryString,
1041  modelString, setup, modelSet, type, log);
1042  }
1043  while (false);
1044 
1045  // model found
1046  lookupModel.setCallsign(callsign);
1047 
1048  // script
1049  if (runMatchinScript && setup.doRunMsReverseLookupScript())
1050  {
1051  const MatchingScriptReturnValues rv =
1052  CAircraftMatcher::reverseLookupScript(lookupModel, setup, modelSet, log);
1053  if (rv.runScriptAndModified())
1054  {
1055  if (rv.runScriptAndRerun())
1056  {
1057  CCallsign::addLogDetailsToList(
1058  log, callsign, QStringLiteral("Matching script: Re-run reverseLookupModelWithFlightplanData"),
1060  return CAirspaceMonitor::reverseLookupModelWithFlightplanData(
1062  rv.model.getLivery().getCombinedCode(), modelString, type, log, false);
1063  }
1064  lookupModel = rv.model;
1065  CCallsign::addLogDetailsToList(log, callsign,
1066  QStringLiteral("Matching script: Using model from matching script"),
1068  }
1069  }
1070  else { CCallsign::addLogDetailsToList(log, callsign, QStringLiteral("No reverse lookup script used")); }
1071 
1072  // done
1073  lookupModel.setCallsign(callsign); // set again just in case modified by script
1074  return lookupModel;
1075  }
1076 
1077  bool CAirspaceMonitor::addNewAircraftInRange(const CSimulatedAircraft &aircraft)
1078  {
1079  const CCallsign callsign = aircraft.getCallsign();
1080  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign");
1081 
1082  if (!sApp || sApp->isShuttingDown() || !sApp->getWebDataServices()) { return false; }
1083 
1084  CSimulatedAircraft newAircraft(aircraft);
1085  newAircraft.setRendered(false); // reset rendering
1086  newAircraft.calculcateAndUpdateRelativeDistanceAndBearing(
1087  this->getOwnAircraftPosition()); // distance from myself
1088 
1089  if (this->getConnectedServer().getEcosystem() == CEcosystem::vatsim())
1090  {
1092  }
1093  const bool added = CRemoteAircraftProvider::addNewAircraftInRange(newAircraft);
1094  if (added)
1095  {
1096  const QPointer<CAirspaceMonitor> myself(this);
1097  QTimer::singleShot(MMVerifyMs, this, [=]() {
1098  // makes sure we have ICAO data
1099  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
1100  this->verifyReceivedIcaoData(callsign);
1101  });
1102 
1103  if (aircraft.hasModelString())
1104  {
1105  // most likely I could take the CG at this time from aircraft
1106  // to make sure it is really the DB value I query again
1107  const CAircraftModel model =
1109  const CLength cg = model.hasValidDbKey() ? model.getCG() : CLength::null();
1110  this->rememberCGFromDB(cg, aircraft.getModelString());
1111  this->rememberCGFromDB(cg, aircraft.getCallsign());
1112  }
1113  }
1114  return added;
1115  }
1116 
1117  void CAirspaceMonitor::asyncReInitializeAllAircraft(const CSimulatedAircraftList &aircraft,
1118  bool readyForModelMatching)
1119  {
1120  if (aircraft.isEmpty()) { return; }
1121  if (!sApp || sApp->isShuttingDown()) { return; }
1122 
1123  int c = 1;
1124  QPointer<CAirspaceMonitor> myself(this);
1125  for (const CSimulatedAircraft &ac : aircraft)
1126  {
1127  QTimer::singleShot(c++ * 25, this, [=] {
1128  if (!myself) { return; }
1129  myself->addNewAircraftInRange(ac);
1130  if (!readyForModelMatching) { return; }
1131  const CCallsign cs = ac.getCallsign();
1132 
1133  m_readiness.remove(cs); // cleanup
1134  const MatchingReadinessFlag ready = ReceivedAll;
1135  myself->sendReadyForModelMatching(cs, ready); // airspace monitor adding all aicraft
1136  });
1137  }
1138  }
1139 
1140  int CAirspaceMonitor::updateOnlineStation(const CCallsign &callsign, const CPropertyIndexVariantMap &vm,
1141  bool skipEqualValues, bool sendSignal)
1142  {
1143  const int c = m_atcStationsOnline.applyIfCallsign(callsign, vm, skipEqualValues);
1144  if (c > 0 && sendSignal) { emit this->changedAtcStationsOnline(); }
1145  return c;
1146  }
1147 
1148  bool CAirspaceMonitor::handleMaxRange(const CAircraftSituation &situation)
1149  {
1150  if (situation.isNull()) { return false; }
1151  if (m_maxDistanceNM < 0) { return true; }
1152  const int distanceNM =
1153  this->getOwnAircraft().calculateGreatCircleDistance(situation).valueInteger(CLengthUnit::NM());
1154  if (distanceNM > m_maxDistanceNMHysteresis)
1155  {
1156  this->removeAircraft(situation.getCallsign());
1157  return false;
1158  }
1159  return distanceNM <= m_maxDistanceNM;
1160  }
1161 
1162  bool CAirspaceMonitor::recallFsInnPacket(const CCallsign &callsign)
1163  {
1164  if (!m_tempFsInnPackets.contains(callsign)) { return false; }
1165  const FsInnPacket packet = m_tempFsInnPackets[callsign];
1166  m_tempFsInnPackets.remove(callsign);
1167  this->onCustomFSInnPacketReceived(callsign, packet.airlineIcaoDesignator, packet.aircraftIcaoDesignator,
1168  packet.combinedCode, packet.modelString);
1169  return true;
1170  }
1171 
1172  CSimulatedAircraft CAirspaceMonitor::addOrUpdateAircraftInRange(
1173  const CCallsign &callsign, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery,
1174  const QString &modelString, CAircraftModel::ModelType modelType, CStatusMessageList *log)
1175  {
1176  const CSimulatedAircraft aircraft = this->getAircraftInRangeForCallsign(callsign);
1177  if (aircraft.hasValidCallsign())
1178  {
1179  // only if we do not have a DB model yet
1180  if (!aircraft.getModel().hasValidDbKey())
1181  {
1182  CAircraftModel model = this->reverseLookupModelWithFlightplanData(callsign, aircraftIcao, airlineIcao,
1183  livery, modelString, modelType, log);
1184  model.updateMissingParts(aircraft.getModel());
1185  // Use anonymous as originator here, since the remote aircraft provider is ourselves and the call to
1186  // updateAircraftModel() would return without doing anything.
1187  this->updateAircraftModel(callsign, model, CIdentifier::null());
1188  this->updateAircraftNetworkModel(callsign, model, CIdentifier::null());
1189  }
1190  }
1191  else
1192  {
1193  /* FSD overload issue, do NOT to add new aircraft other than from positions
1194  const CAircraftModel model = this->reverseLookupModelWithFlightplanData(callsign, aircraftIcao, airlineIcao,
1195  livery, modelString, modelType, log); const CSimulatedAircraft initAircraft(model);
1196  this->addNewAircraftInRange(initAircraft);
1197  */
1198  }
1199  return aircraft;
1200  }
1201 
1202  bool CAirspaceMonitor::extrapolateElevation(CAircraftSituationList &situations,
1203  const CAircraftSituationChange &change)
1204  {
1205  if (situations.size() < 3) { return false; }
1206  // Q_ASSERT_X(situations.m_tsAdjustedSortHint == CAircraftSituationList::AdjustedTimestampLatestFirst,
1207  // Q_FUNC_INFO, "Need latest first");
1208  const CAircraftSituation old = situations[1];
1209  const CAircraftSituation older = situations[2];
1210  return extrapolateElevation(situations.front(), old, older, change);
1211  }
1212 
1213  bool CAirspaceMonitor::extrapolateElevation(CAircraftSituation &situationToBeUpdated,
1214  const CAircraftSituation &oldSituation,
1215  const CAircraftSituation &olderSituation,
1216  const CAircraftSituationChange &oldChange)
1217  {
1218  if (situationToBeUpdated.hasGroundElevation()) { return false; }
1219 
1220  // if acceptable transfer
1221  if (oldSituation.transferGroundElevationFromMe(situationToBeUpdated))
1222  {
1223  // change or keep type is the question
1224  // situationToBeUpdated.setGroundElevationInfo(Extrapolated);
1225  return true;
1226  }
1227  if (oldSituation.isNull() || olderSituation.isNull()) { return false; }
1228 
1229  if (oldChange.isNull()) { return false; }
1230  if (oldChange.isConstOnGround() && oldChange.hasAltitudeDevWithinAllowedRange() &&
1232  {
1233  // we have almost const altitudes and elevations
1234  const double deltaAltFt = qAbs(situationToBeUpdated.getAltitude().value(CLengthUnit::ft()) -
1235  olderSituation.getAltitude().value(CLengthUnit::ft()));
1236  if (deltaAltFt <= CAircraftSituation::allowedAltitudeDeviation().value(CLengthUnit::ft()))
1237  {
1238  // the current alt is also not much different
1239  situationToBeUpdated.setGroundElevation(oldSituation.getGroundElevation(),
1240  CAircraftSituation::Extrapolated);
1241  return true;
1242  }
1243  }
1244 
1245  return false;
1246  }
1247 
1248  void CAirspaceMonitor::onAircraftUpdateReceived(const CAircraftSituation &situation,
1249  const CTransponder &transponder)
1250  {
1251  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread");
1252  if (!this->isConnectedAndNotShuttingDown()) { return; }
1253 
1254  const CCallsign callsign(situation.getCallsign());
1255  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
1256 
1257  if (this->isCopilotAircraft(callsign)) { return; }
1258 
1259  // range (FSD overload issue)
1260  const bool validMaxRange = this->handleMaxRange(situation);
1261  const bool existsInRange = this->isAircraftInRange(callsign); // AFTER valid max.range check!
1262  if (!validMaxRange && !existsInRange) { return; } // not valid at all
1263 
1264  // update client info
1265  this->autoAdjustCientGndCapability(situation);
1266 
1267  // store situation history
1268  this->storeAircraftSituation(situation); // updates situation
1269 
1270  // in case we only have
1271  if (!existsInRange && validMaxRange)
1272  {
1273  // NEW aircraft
1274  const bool hasFsInnPacket = m_tempFsInnPackets.contains(callsign);
1275 
1276  CSimulatedAircraft aircraft;
1277  aircraft.setCallsign(callsign);
1278  aircraft.setSituation(situation);
1279  aircraft.setTransponder(transponder);
1280  this->addNewAircraftInRange(aircraft);
1281  this->sendInitialPilotQueries(callsign, true, !hasFsInnPacket);
1282 
1283  // new client, there is a chance it has been already created by custom packet
1284  const CClient client(callsign);
1285  this->addNewClient(client);
1286  }
1287  else if (existsInRange)
1288  {
1289  // update, aircraft already exists
1291  vm.addValue(CSimulatedAircraft::IndexTransponder, transponder);
1292  vm.addValue(CSimulatedAircraft::IndexSituation, situation);
1293  vm.addValue(CSimulatedAircraft::IndexRelativeDistance, this->calculateDistanceToOwnAircraft(situation));
1294  vm.addValue(CSimulatedAircraft::IndexRelativeBearing, this->calculateBearingToOwnAircraft(situation));
1295  this->updateAircraftInRange(callsign, vm);
1296  }
1297  }
1298 
1299  void CAirspaceMonitor::onAircraftInterimUpdateReceived(const CAircraftSituation &situation)
1300  {
1301  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread");
1302  if (!this->isConnectedAndNotShuttingDown()) { return; }
1303 
1304  const CCallsign callsign(situation.getCallsign());
1305 
1306  // checks
1307  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
1308 
1309  if (isCopilotAircraft(callsign)) { return; }
1310  if (!this->isAircraftInRange(callsign)) { return; }
1311 
1312  if (CBuildConfig::isLocalDeveloperDebugBuild())
1313  {
1314  Q_ASSERT_X(!situation.isNaNVectorDouble(), Q_FUNC_INFO, "Detected NaN");
1315  Q_ASSERT_X(!situation.isInfVectorDouble(), Q_FUNC_INFO, "Detected inf");
1316  Q_ASSERT_X(situation.isValidVectorRange(), Q_FUNC_INFO, "out of range [-1,1]");
1317  }
1318 
1319  // Interim packets do not have groundspeed, hence set the last known value.
1320  // If there is no full position available yet, throw this interim position away.
1321  CAircraftSituation interimSituation(situation);
1322  CAircraftSituationList history = this->remoteAircraftSituations(callsign);
1323  if (history.isEmpty()) { return; } // we need one full situation at least
1324  const CAircraftSituation lastSituation = history.latestObject();
1325 
1326  // changed position, continue and copy values
1327  interimSituation.setCurrentUtcTime();
1328  interimSituation.setGroundSpeed(lastSituation.getGroundSpeed());
1329 
1330  // store situation history
1331  this->storeAircraftSituation(interimSituation);
1332 
1333  const bool samePosition = lastSituation.equalNormalVectorDouble(interimSituation);
1334  if (samePosition) { return; } // nothing to update
1335 
1336  // update aircraft
1337  this->updateAircraftInRangeDistanceBearing(callsign, interimSituation,
1338  this->calculateDistanceToOwnAircraft(interimSituation),
1339  this->calculateBearingToOwnAircraft(interimSituation));
1340  }
1341 
1342  void CAirspaceMonitor::onAircraftVisualUpdateReceived(const swift::misc::aviation::CAircraftSituation &situation)
1343  {
1345  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread");
1346  if (!this->isConnectedAndNotShuttingDown()) { return; }
1347 
1348  const CCallsign callsign(situation.getCallsign());
1349 
1350  // checks
1351  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
1352 
1353  if (isCopilotAircraft(callsign)) { return; }
1354  if (!this->isAircraftInRange(callsign)) { return; }
1355 
1356  if (CBuildConfig::isLocalDeveloperDebugBuild())
1357  {
1358  Q_ASSERT_X(!situation.isNaNVectorDouble(), Q_FUNC_INFO, "Detected NaN");
1359  Q_ASSERT_X(!situation.isInfVectorDouble(), Q_FUNC_INFO, "Detected inf");
1360  Q_ASSERT_X(situation.isValidVectorRange(), Q_FUNC_INFO, "out of range [-1,1]");
1361  }
1362 
1363  // Visual packets do not have groundspeed, hence set the last known value.
1364  // If there is no full position available yet, throw this interim position away.
1366  CAircraftSituation visualSituation(situation);
1367  CAircraftSituationList history = this->remoteAircraftSituations(callsign);
1368  if (history.empty()) { return; } // we need one full situation at least
1369  const CAircraftSituation lastSituation = history.latestObject();
1370 
1371  // changed position, continue and copy values
1372  visualSituation.setCurrentUtcTime();
1373  visualSituation.setGroundSpeed(lastSituation.getGroundSpeed());
1374 
1375  // store situation history
1376  this->storeAircraftSituation(visualSituation);
1377 
1378  const bool samePosition = lastSituation.equalNormalVectorDouble(visualSituation);
1379  if (samePosition) { return; } // nothing to update
1380 
1381  // update aircraft
1382  this->updateAircraftInRangeDistanceBearing(callsign, visualSituation,
1383  this->calculateDistanceToOwnAircraft(visualSituation),
1384  this->calculateBearingToOwnAircraft(visualSituation));
1385  }
1386 
1387  void CAirspaceMonitor::onAircraftSimDataUpdateReceived(const CAircraftSituation &situation,
1388  const CAircraftParts &parts, qint64 currentOffsetMs,
1389  const QString &aircraftIcao, const QString &airlineIcao)
1390  {
1391  onAircraftUpdateReceived(situation, CTransponder(2000, CTransponder::ModeC));
1392 
1393  const CSimulatedAircraft aircraft = getAircraftInRangeForCallsign(situation.getCallsign());
1394  const CAircraftModel &model = aircraft.getNetworkModel();
1395  if (model.getAircraftIcaoCodeDesignator() != aircraftIcao)
1396  {
1397  onIcaoCodesReceived(situation.getCallsign(), aircraftIcao, airlineIcao, airlineIcao);
1398  }
1399 
1402  onAircraftConfigReceived(situation.getCallsign(), parts.toFullJson(), currentOffsetMs);
1403  }
1404 
1405  void CAirspaceMonitor::onConnectionStatusChanged(CConnectionStatus oldStatus, CConnectionStatus newStatus)
1406  {
1407  Q_UNUSED(oldStatus)
1408  if (newStatus == CConnectionStatus::Connecting && m_fsdClient)
1409  {
1410  const CServer server = m_fsdClient->getServer();
1411  const bool isVatsim = server.getEcosystem().isSystem(CEcosystem::VATSIM);
1412  const CLength maxRange(isVatsim ? 125 : -1, CLengthUnit::NM());
1413  this->setMaxRange(maxRange);
1414  }
1415 
1416  if (newStatus.isDisconnected()) { this->clear(); }
1417  }
1418 
1419  void CAirspaceMonitor::onPilotDisconnected(const CCallsign &callsign)
1420  {
1421  Q_ASSERT(CThreadUtils::isInThisThread(this));
1422 
1423  // in case of inconsistencies I always remove here
1424  this->removeFromAircraftCachesAndLogs(callsign);
1425  const bool removed = CRemoteAircraftProvider::removeAircraft(callsign);
1426  this->removeClient(callsign);
1427  if (removed) { emit this->removedAircraft(callsign); }
1428  }
1429 
1430  void CAirspaceMonitor::onFrequencyReceived(const CCallsign &callsign, const CFrequency &frequency)
1431  {
1432  Q_ASSERT(CThreadUtils::isInThisThread(this));
1433 
1434  // update
1435  const CPropertyIndexVariantMap vm({ CSimulatedAircraft::IndexCom1System, CComSystem::IndexActiveFrequency },
1436  CVariant::from(frequency));
1437  this->updateAircraftInRange(callsign, vm);
1438  }
1439 
1440  void CAirspaceMonitor::onAircraftConfigReceived(const CCallsign &callsign, const QJsonObject &jsonObject,
1441  qint64 currentOffsetMs)
1442  {
1443  Q_ASSERT(CThreadUtils::isInThisThread(this));
1444  SWIFT_AUDIT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1445  if (callsign.isEmpty()) { return; }
1446 
1447  // store parts
1448  this->storeAircraftParts(callsign, jsonObject, currentOffsetMs);
1449 
1450  // update client capability
1451  CClient client = this->getClientOrDefaultForCallsign(callsign);
1452  client.setUserCallsign(callsign); // make valid by setting a callsign
1453  if (client.hasCapability(CClient::FsdWithAircraftConfig)) { return; }
1454  client.addCapability(CClient::FsdWithAircraftConfig);
1455  this->setOtherClient(client);
1456  }
1457 
1458  CAircraftSituation CAirspaceMonitor::storeAircraftSituation(const CAircraftSituation &situation,
1459  bool allowTestOffset)
1460  {
1461  const CCallsign callsign(situation.getCallsign());
1462  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign");
1463  if (callsign.isEmpty()) { return situation; }
1464 
1465  CAircraftSituation correctedSituation(allowTestOffset ? this->addTestAltitudeOffsetToSituation(situation) :
1466  situation);
1467  bool needToRequestElevation = false;
1468  bool canLikelySkipNearGround = correctedSituation.canLikelySkipNearGroundInterpolation();
1469  do {
1470  // Check if we can bail out and ignore all elevation handling
1471  //
1472  // rational:
1473  // a) elevation handling is expensive, and might even requests elevation from sim.
1474  // b) elevations not needed pollute the cache with "useless" values
1475  //
1476  if (canLikelySkipNearGround || correctedSituation.hasGroundElevation()) { break; }
1477 
1478  // set a defined state
1479  correctedSituation.resetGroundElevation();
1480 
1481  // Guessing gives better values, also for smaller planes
1482  // and avoids unnecessary elevation fetching for low flying smaller GA aircraft
1484  if (!icao.hasDesignator()) { break; } // what is that?
1485  if (icao.isVtol())
1486  {
1487  // VTOLs over 60kts likely not on ground
1488  // it could be that a low flying helicopter near ground
1489  if (situation.getGroundSpeed().value(CSpeedUnit::kts()) > 60) { break; }
1490  }
1491  else
1492  {
1493  // NO VTOL
1494  CLength cg(nullptr);
1495  CSpeed rotateSpeed(nullptr);
1496  icao.guessModelParameters(cg, rotateSpeed);
1497  if (!rotateSpeed.isNull())
1498  {
1499  rotateSpeed *= 1.25; // some margin
1500  if (situation.getGroundSpeed() > rotateSpeed) { break; }
1501  }
1502  }
1503 
1504  // fetch from cache or request
1505  const CAircraftSituationChange changesBeforeStoring =
1507 
1508  if (!changesBeforeStoring.isNull())
1509  {
1510  canLikelySkipNearGround = changesBeforeStoring.isConstAscending();
1511  if (canLikelySkipNearGround) { break; }
1512  }
1513 
1514  // we NEED elevation
1515  // actually distance of 200k/h 100ms is just 6.1 meters
1516  using namespace std::chrono_literals;
1517  const CLength dpt = correctedSituation.getDistancePerTime(100ms, CElevationPlane::singlePointRadius());
1518  const CAircraftSituationList situationsBeforeStoring = this->remoteAircraftSituations(callsign);
1519  const CAircraftSituation situationWithElvBeforeStoring =
1520  situationsBeforeStoring.findClosestElevationWithinRange(correctedSituation, dpt);
1521  if (situationWithElvBeforeStoring.transferGroundElevationFromMe(correctedSituation, dpt))
1522  {
1523  // from nearby situations of own aircraft, data was transferred above
1524  // we use transfer first as it is slightly faster as cache
1525  }
1526  else
1527  {
1528  // from cache
1529  const CLength distance(correctedSituation.getDistancePerTime250ms(
1530  CElevationPlane::singlePointRadius())); // distance per ms
1531  const CElevationPlane ep = this->findClosestElevationWithinRange(correctedSituation, distance);
1532  needToRequestElevation = ep.isNull();
1533  Q_ASSERT_X(needToRequestElevation || !ep.getRadius().isNull(), Q_FUNC_INFO, "null radius");
1534 
1535  // also can handle NULL elevations
1536  correctedSituation.setGroundElevation(ep, CAircraftSituation::FromCache);
1537  }
1538 
1539  // we have a new situation, so we try to get the elevation
1540  // we will requested it, but we set it upfront either by
1541  //
1542  // a) average value from other planes in the vicinity (cache, not moving) or
1543  // b) by extrapolating
1544  //
1545  // if we would NOT preset it, we could end up with oscillation
1546  //
1547  if (!correctedSituation.hasGroundElevation())
1548  {
1549  // average elevation
1550  // 1) from cache
1551  // 2) from planes on ground not moving
1552  bool fromNonMoving = false;
1553  bool triedExtrapolation = false;
1554  bool couldNotExtrapolate = false;
1555  int fromWhere = -1; // debugging
1556 
1557  CElevationPlane averagePlane =
1558  this->averageElevationOfOnGroundAircraft(situation, CElevationPlane::minorAirportRadius(), 2, 3);
1559  if (averagePlane.isNull())
1560  {
1561  averagePlane = this->averageElevationOfNonMovingAircraft(
1562  situation, CElevationPlane::minorAirportRadius(), 2, 3);
1563  fromNonMoving = true;
1564  }
1565 
1566  // do we have an elevation yet?
1567  if (!averagePlane.isNull())
1568  {
1569  correctedSituation.setGroundElevation(averagePlane, CAircraftSituation::Average);
1570  if (fromNonMoving) { m_foundInNonMovingAircraft++; }
1571  else { m_foundInElevationsOnGnd++; }
1572  fromWhere = 10;
1573  }
1574  else
1575  {
1576  // values before updating (i.e. "storing") so the new situation is not yet considered
1577  if (situationsBeforeStoring.size() > 1)
1578  {
1579  const bool extrapolated =
1580  extrapolateElevation(correctedSituation, situationsBeforeStoring[0],
1581  situationsBeforeStoring[1], changesBeforeStoring);
1582  triedExtrapolation = true;
1583  couldNotExtrapolate = !extrapolated;
1584  fromWhere = 20;
1585  }
1586  }
1587  Q_UNUSED(fromWhere)
1588 
1589  // still no elevation
1590  if (!correctedSituation.hasGroundElevation())
1591  {
1592  if (CBuildConfig::isLocalDeveloperDebugBuild())
1593  {
1594  // experimental, could become ASSERT
1595  SWIFT_VERIFY_X(needToRequestElevation, Q_FUNC_INFO, "Request should already be set");
1596  }
1597  needToRequestElevation = true; // should be "true" already
1598 
1599  Q_UNUSED(triedExtrapolation)
1600  Q_UNUSED(couldNotExtrapolate)
1601  }
1602  else
1603  {
1604  // sanity check on the situation
1605  if (CBuildConfig::isLocalDeveloperDebugBuild())
1606  {
1607  SWIFT_VERIFY_X(!correctedSituation.getGroundElevation().isZeroEpsilonConsidered(), Q_FUNC_INFO,
1608  "Suspicious elevation");
1609  }
1610  }
1611  } // gnd. elevation
1612  }
1613  while (false); // do we need elevation, find on
1614 
1615  // do we already have ground details?
1616  if (situation.getOnGroundInfo().getGroundDetails() == COnGroundInfo::NotSetGroundDetails)
1617  {
1618  const CClient client = this->getClientOrDefaultForCallsign(callsign);
1619  if (client.hasCapability(CClient::FsdWithGroundFlag))
1620  {
1621  // we rely on situation gnd.flag
1622  correctedSituation.setOnGroundDetails(COnGroundInfo::InFromNetwork);
1623  }
1624  else if (client.hasCapability(CClient::FsdWithAircraftConfig))
1625  {
1626  const CAircraftPartsList parts = this->remoteAircraftParts(callsign);
1627  if (!parts.isEmpty()) { correctedSituation.adjustGroundFlag(parts, true); }
1628  }
1629  }
1630 
1631  // CG from provider
1632  const CLength cg = this->getSimulatorOrDbCG(
1633  callsign,
1634  this->getCGFromDB(
1635  callsign)); // always x-check against simulator to override guessed values and reflect changed CGs
1636  if (!cg.isNull()) { correctedSituation.setCG(cg); }
1637 
1638  // store corrected situation
1639  correctedSituation = CRemoteAircraftProvider::storeAircraftSituation(correctedSituation,
1640  false); // we already added offset if any
1641 
1642  // check if we need want to request
1643  if (needToRequestElevation && !canLikelySkipNearGround)
1644  {
1645  // we have not requested so far, but we are NEAR ground
1646  // we expect at least not transferred cache or we are moving and have no provider elevation yet
1647  if (correctedSituation.isOtherElevationInfoBetter(CAircraftSituation::FromCache, false) ||
1648  (correctedSituation.isMoving() &&
1649  correctedSituation.isOtherElevationInfoBetter(CAircraftSituation::FromProvider, false)))
1650  {
1651  needToRequestElevation = this->requestElevation(correctedSituation);
1652  }
1653  }
1654 
1655  Q_UNUSED(needToRequestElevation)
1656  return correctedSituation;
1657  }
1658 
1659  void CAirspaceMonitor::sendInitialAtcQueries(const CCallsign &callsign)
1660  {
1661  if (!this->isConnectedAndNotShuttingDown()) { return; }
1662  m_fsdClient->sendClientQueryRealName(callsign);
1663  m_fsdClient->sendClientQueryAtis(callsign); // request ATIS and voice rooms
1664  m_fsdClient->sendClientQueryCapabilities(callsign);
1665  m_fsdClient->sendClientQueryServer(callsign);
1666  }
1667 
1668  void CAirspaceMonitor::queryAllOnlineAtcStations()
1669  {
1670  if (!this->isConnectedAndNotShuttingDown()) { return; }
1671  const CAtcStationList onlineStations = this->getAtcStationsOnlineRecalculated();
1672  for (const CAtcStation &station : onlineStations)
1673  {
1674  const CCallsign cs = station.getCallsign();
1675  if (cs.isEmpty()) { continue; }
1676  m_fsdClient->sendClientQueryRealName(cs);
1677  }
1678  }
1679 
1680  bool CAirspaceMonitor::sendNextStaggeredAtisQuery()
1681  {
1682  if (m_queryAtis.isEmpty()) { return false; }
1683  if (!this->isConnectedAndNotShuttingDown()) { return false; }
1684  const CCallsign cs = m_queryAtis.dequeue();
1685  if (!m_atcStationsOnline.containsCallsign(cs)) { return false; }
1686  m_fsdClient->sendClientQueryAtis(cs);
1687  return true;
1688  }
1689 
1690  void CAirspaceMonitor::sendInitialPilotQueries(const CCallsign &callsign, bool withIcaoQuery, bool withFsInn)
1691  {
1692  if (!this->isConnectedAndNotShuttingDown()) { return; }
1693 
1694  if (withIcaoQuery) { m_fsdClient->sendPlaneInfoRequest(callsign); }
1695  if (withFsInn) { m_fsdClient->sendPlaneInfoRequestFsinn(callsign); }
1696 
1697  m_fsdClient->sendClientQueryCom1Freq(callsign);
1698  m_fsdClient->sendClientQueryRealName(callsign);
1699  m_fsdClient->sendClientQueryCapabilities(callsign);
1700  m_fsdClient->sendClientQueryServer(callsign);
1701  }
1702 
1703  bool CAirspaceMonitor::sendNextStaggeredPilotDataQuery()
1704  {
1705  if (m_queryPilot.isEmpty()) { return false; }
1706  if (!this->isConnectedAndNotShuttingDown()) { return false; }
1707  const CCallsign cs = m_queryPilot.dequeue();
1708  if (!this->isAircraftInRange(cs)) { return false; }
1709  m_fsdClient->sendClientQueryCom1Freq(cs);
1710 
1711  // we only query ICAO if we have none yet
1712  // it happens sometimes with some FSD servers (e.g our testserver) a first query is skipped
1713  // Important: this is only a workaround and must not replace a sendInitialPilotQueries
1714  if (!this->getAircraftInRangeForCallsign(cs).hasAircraftDesignator()) { m_fsdClient->sendPlaneInfoRequest(cs); }
1715  return true;
1716  }
1717 
1718  bool CAirspaceMonitor::isConnected() const
1719  {
1720  return m_fsdClient && m_fsdClient->getConnectionStatus().isConnected();
1721  }
1722 
1723  bool CAirspaceMonitor::isConnectedAndNotShuttingDown() const
1724  {
1725  if (!sApp || sApp->isShuttingDown()) { return false; }
1726  return this->isConnected();
1727  }
1728 
1729  const CServer &CAirspaceMonitor::getConnectedServer() const
1730  {
1731  static const CServer empty;
1732  if (!this->isConnected()) { return empty; }
1733  return m_fsdClient->getServer();
1734  }
1735 
1736  const CEcosystem &CAirspaceMonitor::getCurrentEcosystem() const
1737  {
1738  return this->getConnectedServer().getEcosystem();
1739  }
1740 
1741  bool CAirspaceMonitor::supportsVatsimDataFile() const
1742  {
1743  const bool dataFile =
1745  return dataFile && this->getConnectedServer().getEcosystem().isSystem(CEcosystem::VATSIM);
1746  }
1747 
1748  CLength CAirspaceMonitor::calculateDistanceToOwnAircraft(const CAircraftSituation &situation) const
1749  {
1750  CLength distance = this->getOwnAircraft().calculateGreatCircleDistance(situation);
1751  distance.switchUnit(CLengthUnit::NM());
1752  return distance;
1753  }
1754 
1755  CAngle CAirspaceMonitor::calculateBearingToOwnAircraft(const CAircraftSituation &situation) const
1756  {
1757  CAngle angle = this->getOwnAircraft().calculateBearing(situation);
1758  angle.switchUnit(CAngleUnit::deg());
1759  return angle;
1760  }
1761 
1762  bool CAirspaceMonitor::isCopilotAircraft(const CCallsign &callsign) const
1763  {
1764  if (!sApp || sApp->isShuttingDown() || !sApp->getIContextNetwork()) { return false; }
1765 
1766  // It is only relevant if we are logged in as observer
1768 
1769  const CCallsign ownCallsign = this->getOwnAircraft().getCallsign();
1770  return ownCallsign.isMaybeCopilotCallsign(callsign);
1771  }
1772 
1773  CAirspaceMonitor::FsInnPacket::FsInnPacket(const QString &aircraftIcaoDesignator,
1774  const QString &airlineIcaoDesignator, const QString &combinedCode,
1775  const QString &modelString)
1776  : aircraftIcaoDesignator(aircraftIcaoDesignator.trimmed().toUpper()),
1777  airlineIcaoDesignator(airlineIcaoDesignator.trimmed().toUpper()),
1778  combinedCode(combinedCode.trimmed().toUpper()), modelString(modelString.trimmed())
1779  {}
1780 
1781 } // 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.
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:459
void sendClientQueryAtis(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:474
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:718
void sendClientQueryRealName(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:464
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:469
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:484
void sendClientQueryFlightPlan(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:479
void sendPlaneInfoRequestFsinn(const swift::misc::aviation::CCallsign &callsign)
Definition: fsdclient.cpp:733
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:144
OBJ latestObject() const
Latest object.
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 of aircraft's parts.
Definition: aircraftparts.h:26
QJsonObject toIncrementalJson() const
Incremental JSON object.
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.
bool hasGroundElevation() const
Is ground elevation value available.
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.
const CCallsign & getCallsign() const
Corresponding callsign.
const physical_quantities::CSpeed & getGroundSpeed() const
Get ground speed.
const CAltitude & getAltitude() const
Get altitude.
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:116
const CCallsign & getCallsign() const
Get callsign.
Definition: atcstation.h:84
void setCallsign(const CCallsign &callsign)
Set callsign.
Definition: atcstation.cpp:56
void setPosition(const swift::misc::geo::CCoordinateGeodetic &position)
Set position.
Definition: atcstation.h:144
bool setOnline(bool online)
Set online.
Definition: atcstation.cpp:128
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)
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
int removeClient(const aviation::CCallsign &callsign)
Remove client.
int updateOrAddClient(const aviation::CCallsign &callsign, const CPropertyIndexVariantMap &vm, bool skipEqualValues=true)
Update or add a client.
void markAsSwiftClient(const aviation::CCallsign &callsign)
Mark as other swift client.
bool setOtherClient(const swift::misc::network::CClient &client)
Set client for its callsign.
bool addNewClient(const CClient &client)
Add a new client, if existing nothing will be added.
void setClients(const CClientList &clients)
Set other clients.
CClientList getClients() const
clientprovider
void clearClients()
Set other clients.
CClient getClientOrDefaultForCallsign(const aviation::CCallsign &callsign) const
Other client for the given callsigns.
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.
bool isAircraftInRange(const aviation::CCallsign &callsign) const
Is aircraft in range?
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.
int remoteAircraftSituationsCount(const aviation::CCallsign &callsign) const
Number of remote aircraft situations for callsign.
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.
aviation::CAircraftSituationList remoteAircraftSituations(const aviation::CCallsign &callsign) const
Rendered aircraft situations (per callsign, time history)
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.
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.
bool updateAircraftNetworkModel(const aviation::CCallsign &callsign, const CAircraftModel &model, const CIdentifier &originator)
Change network model.
bool updateAircraftModel(const aviation::CCallsign &callsign, const CAircraftModel &model, const CIdentifier &originator)
Change model.
physical_quantities::CLength getCGFromDB(const aviation::CCallsign &callsign) const
CG values from DB.
bool updateFastPositionEnabled(const aviation::CCallsign &callsign, bool enableFastPositonUpdates)
Change fast position updates.
ReverseLookupLogging whatToReverseLog() const
What to log?
ReverseLookupLogging isReverseLookupMessagesEnabled() const
Enabled reverse lookup logging?
CAircraftModel getAircraftInRangeModelForCallsign(const aviation::CCallsign &callsign) const
Aircraft model for callsign.
CSimulatedAircraft getAircraftInRangeForCallsign(const aviation::CCallsign &callsign) const
Aircraft for callsign.
aviation::CAircraftPartsList remoteAircraftParts(const aviation::CCallsign &callsign) const
All parts (per callsign, time history)
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.
QDateTime currentDateTimeUtc()
void setTime(QTime time, QDateTime::TransitionResolution resolve)
QString toString(QStringView format) const const
void clear()
bool contains(const Key &key) const const
QHash< Key, T >::iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
void setObjectName(QAnyStringView name)
T dequeue()
void enqueue(T &&t)
QString arg(Args &&... args) const const
void clear()
bool isEmpty() const const
qsizetype length() const const
QString join(QChar separator) const const
QueuedConnection
QTextStream & ws(QTextStream &stream)
void start()
void timeout()
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