swift
Loading...
Searching...
No Matches
interpolator.cpp
1// SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3
5
6#include <algorithm>
7
8#include <QDateTime>
9#include <QStringBuilder>
10#include <QTimer>
11
12#include "config/buildconfig.h"
17#include "misc/logmessage.h"
19#include "misc/pq/angle.h"
20#include "misc/pq/length.h"
21#include "misc/pq/speed.h"
22#include "misc/pq/units.h"
26#include "misc/stringutils.h"
27#include "misc/verify.h"
28
29using namespace swift::config;
30using namespace swift::misc::aviation;
31using namespace swift::misc::geo;
32using namespace swift::misc::math;
33using namespace swift::misc::network;
34using namespace swift::misc::physical_quantities;
35
36namespace swift::misc::simulation
37{
39 IInterpolationSetupProvider *setupProvider, IRemoteAircraftProvider *remoteProvider,
41 : m_callsign(callsign)
42 {
43 // normally when created m_cg is still null since there is no CG in the provider yet
44
45 if (simEnvProvider)
46 {
47 this->setSimulationEnvironmentProvider(simEnvProvider);
48 }
49 if (setupProvider)
50 {
51 this->setInterpolationSetupProvider(setupProvider);
52 }
53 if (remoteProvider)
54 {
55 this->setRemoteAircraftProvider(remoteProvider);
56 QObject::connect(&m_initTimer, &QTimer::timeout, [=, this] { this->deferredInit(); });
57 m_initTimer.setSingleShot(true);
58 m_initTimer.start(2500);
59 }
60 this->attachLogger(logger);
61 }
62
63 CLength CInterpolator::getAndFetchModelCG(const CLength &dbCG)
64 {
65 CLength cgDb = dbCG;
66 if (cgDb.isNull())
67 {
68 // no input DB value, try to find one
71 }
72 else if (this->getRemoteAircraftProvider())
73 {
74 // if a value has been passed, remember it
76 {
78 }
80 {
82 }
83 }
84
85 const CLength cg = this->getSimulatorOrDbCG(m_callsign, cgDb); // simulation environment
86 m_model.setCG(cg);
87 m_model.setCallsign(m_callsign);
88 return cg;
89 }
90
91 CAircraftSituationList
92 CInterpolator::remoteAircraftSituationsAndChange(const CInterpolationAndRenderingSetupPerCallsign &setup)
93 {
94 CAircraftSituationList validSituations = this->remoteAircraftSituations(m_callsign);
95
96 // get the changes, we need the second value as we want to look in the past
97 // the first value is already based on the latest situation
98 const CAircraftSituationChangeList changes = this->remoteAircraftSituationChanges(m_callsign);
100
101 // fixing offset
102 if (setup.isFixingSceneryOffset() && m_pastSituationsChange.hasSceneryDeviation() && m_model.hasCG())
103 {
104 const CLength os = m_pastSituationsChange.getGuessedSceneryDeviationCG();
106 if (!os.isNull())
107 {
108 const CLength addValue = os * -1.0; // positive values means too high, negative values too low
109 int changed = validSituations.addAltitudeOffset(addValue);
110 Q_UNUSED(changed)
111 }
112 }
113 else
114 {
115 m_currentSceneryOffset = CLength::null();
116 }
117 return validSituations;
118 }
119
120 bool CInterpolator::presetGroundElevation(CAircraftSituation &situationToPreset,
121 const CAircraftSituation &oldSituation,
122 const CAircraftSituation &newSituation,
123 const CAircraftSituationChange &change)
124 {
125 // IMPORTANT: we do not know what the situation will be (interpolated to), so we cannot transfer
126 situationToPreset.resetGroundElevation();
127 do
128 {
129 if (oldSituation.equalNormalVectorDouble(newSituation))
130 {
131 if (oldSituation.hasGroundElevation())
132 {
133 // same positions, we can use existing elevation
134 // means we were not moving between old an new
135 situationToPreset.transferGroundElevationToMe(oldSituation, true);
136 break;
137 }
138 }
139
140 const CLength distance = newSituation.calculateGreatCircleDistance(oldSituation);
141 if (distance < newSituation.getDistancePerTime250ms(CElevationPlane::singlePointRadius()))
142 {
143 if (oldSituation.hasGroundElevation())
144 {
145 // almost same positions, we can use existing elevation
146 situationToPreset.transferGroundElevationToMe(oldSituation, true);
147 break;
148 }
149 }
150
152 {
153 // not much change in known elevations
154 const CAltitudePair elvDevMean = change.getElevationStdDevAndMean();
155 situationToPreset.setGroundElevation(elvDevMean.second, CAircraftSituation::SituationChange);
156 break;
157 }
158
159 const CElevationPlane epInterpolated = CAircraftSituation::interpolatedElevation(
160 CAircraftSituation::null(), oldSituation, newSituation, distance);
161 if (!epInterpolated.isNull())
162 {
163 situationToPreset.setGroundElevation(epInterpolated, CAircraftSituation::Interpolated);
164 break;
165 }
166 }
167 while (false);
168 return situationToPreset.hasGroundElevation();
169 }
170
171 void CInterpolator::deferredInit()
172 {
173 if (m_model.hasModelString())
174 {
175 return;
176 } // set in-between
178 }
179
181 {
182 static const QStringList cats { CLogCategories::interpolator() };
183 return cats;
184 }
185
188 uint32_t aircraftNumber)
189 {
191
192 const bool init = this->initIniterpolationStepData(currentTimeSinceEpoch, setup, aircraftNumber);
193 Q_ASSERT_X(!m_currentInterpolationStatus.isInterpolated(), Q_FUNC_INFO, "Expect reset status");
194 if (init || m_unitTest) // ignore failure in unittest
195 {
196 Q_ASSERT_X(m_currentTimeMsSinceEpoch > 0, Q_FUNC_INFO, "No valid timestamp, interpolator initialized?");
197 const CAircraftSituation interpolatedSituation = this->getInterpolatedSituation();
198 const CAircraftParts interpolatedParts = this->getInterpolatedOrGuessedParts(aircraftNumber);
199 result.setValues(interpolatedSituation, interpolatedParts);
200 }
201
203 return result;
204 }
205
206 CAircraftSituation CInterpolator::getInterpolatedSituation()
207 {
208 Q_ASSERT_X(!m_currentInterpolationStatus.isInterpolated(), Q_FUNC_INFO, "Expect reset status");
210 {
213 }
214
215 // interpolant as function of derived class
216 // CInterpolatorLinear::Interpolant or CInterpolatorSpline::Interpolant
217 SituationLog log;
218 const IInterpolant &interpolant = getInterpolant(log);
219 const bool isValidInterpolant = interpolant.isValid();
220
221 CAircraftSituation currentSituation = m_lastSituation;
222 CAircraftSituation::AltitudeCorrection altCorrection = CAircraftSituation::NoCorrection;
223
224 bool isValidInterpolation = false;
225 do
226 {
227 if (!isValidInterpolant)
228 {
229 break;
230 }
231
232 const IInterpolatorPbh &pbh = interpolant.pbh();
233
234 // init interpolated situation
235 currentSituation = this->initInterpolatedSituation(pbh.getStartSituation(), pbh.getEndSituation());
236
237 // Pitch bank heading first, so follow up steps could use those values
238 currentSituation.setHeading(pbh.getHeading());
239 currentSituation.setPitch(pbh.getPitch());
240 currentSituation.setBank(pbh.getBank());
241 currentSituation.setGroundSpeed(pbh.getGroundSpeed());
242
243 // use derived interpolant function
244 const auto [interpolatedPosition, interpolatedAltitude] = interpolant.interpolatePositionAndAltitude();
245 currentSituation.setPosition(interpolatedPosition);
246 currentSituation.setAltitude(interpolatedAltitude);
247 currentSituation.setMSecsSinceEpoch(interpolant.getInterpolatedTime());
248
249 const bool interpolateGndFlag = pbh.getEndSituation().hasGroundDetailsForGndInterpolation() &&
250 pbh.getStartSituation().hasGroundDetailsForGndInterpolation();
251 if (interpolateGndFlag)
252 {
253 currentSituation.setOnGroundInfo(interpolant.interpolateGroundFactor());
254 }
255
256 if (currentSituation.isNull())
257 {
258 break;
259 }
260
261 // if we get here and the vector is invalid it means we haven't handled it correctly in one of the
262 // interpolators
263 if (!currentSituation.isValidVectorRange())
264 {
266 {
267 SWIFT_VERIFY_X(false, Q_FUNC_INFO, "Invalid interpolation situation");
268 }
270 }
271
272 // GND flag.
273 if (!interpolateGndFlag)
274 {
276 }
277
278 // as we now have the position and can interpolate elevation
279 currentSituation.interpolateElevation(pbh.getStartSituation(), pbh.getEndSituation());
280 if (!currentSituation.hasGroundElevation())
281 {
282 // we still have no elevation
283 const CLength radius = currentSituation.getDistancePerTime250ms(CElevationPlane::singlePointRadius());
284 if (!m_lastSituation.transferGroundElevationFromMe(currentSituation, radius))
285 {
286 if (currentSituation.canLikelySkipNearGroundInterpolation())
287 {
288 // skipped
289 }
290 else
291 {
292 const CElevationPlane groundElevation =
293 this->findClosestElevationWithinRange(currentSituation, radius);
294 m_lastSituation.setGroundElevationChecked(groundElevation, CAircraftSituation::FromCache);
295 }
296 }
297 }
298
299 // correct altitude itself
300 if (!interpolateGndFlag &&
302 {
303 // just in case
304 altCorrection = currentSituation.correctAltitude(true); // we have CG set
305 }
306
307 // correct pitch on ground
308 if (currentSituation.isOnGround())
309 {
310 const CAngle correctedPitchOnGround = m_currentSetup.getPitchOnGround();
311 if (!correctedPitchOnGround.isNull())
312 {
313 currentSituation.setPitch(correctedPitchOnGround);
314 }
315 }
316
317 isValidInterpolation = true;
318 }
319 while (false);
320
321 const bool valid = isValidInterpolant && isValidInterpolation;
322 if (!valid)
323 {
324 // further handling could go here, mainly we continue with last situation
326
327 // avoid flooding of log.
329 {
331 const bool noSituation = m_lastSituation.isNull();
332
333 // Problem 1, we have no "last situation"
334 // Problem 2, "it takes too long to recover"
335 CStatusMessage m;
336 if (noSituation)
337 {
338 m = CStatusMessage(this).warning(
339 u"No situation #%1 for interpolation reported for '%2' (Interpolant: %3 interpolation: %4)")
340 << m_invalidSituations << m_callsign.asString() << boolToTrueFalse(isValidInterpolant)
341 << boolToTrueFalse(isValidInterpolation);
342 }
343 else
344 {
345 const qint64 diff = m_currentTimeMsSinceEpoch - currentSituation.getAdjustedMSecsSinceEpoch();
346 m = CStatusMessage(this).warning(u"Invalid situation, diff. %1ms #%2 for interpolation reported "
347 u"for '%3' (Interpolant: %4 interpolation: %5)")
348 << diff << m_invalidSituations << m_callsign.asString() << boolToTrueFalse(isValidInterpolant)
349 << boolToTrueFalse(isValidInterpolation);
350 }
351 if (!m.isEmpty())
352 {
353 if (m_interpolationMessages.sizeInt() == 2)
354 {
355 // display second message as a hint in the general log
356 // we DO NOT display the first message, as this can happen due to pilot logging off
357 // if it happens twice we consider it worth displaying
359 }
360 m_interpolationMessages.push_back(m);
361 }
362 }
363 } // valid?
364
365 // situation and status
366 if (valid)
367 {
368 Q_ASSERT_X(currentSituation.hasMSLGeodeticHeight(), Q_FUNC_INFO, "No MSL altitude");
369 m_lastSituation = currentSituation;
370 m_currentInterpolationStatus.setInterpolatedAndCheckSituation(valid, currentSituation);
371 }
372 else
373 {
374 currentSituation = m_lastSituation;
375 m_currentInterpolationStatus.setSameSituation(true);
376 m_currentInterpolationStatus.setInterpolatedAndCheckSituation(valid, currentSituation);
377 }
378
379 // logging
380 if (this->doLogging())
381 {
382 log.tsCurrent = m_currentTimeMsSinceEpoch;
383 log.callsign = m_callsign;
384 log.groundFactor = currentSituation.getOnGroundInfo().getGroundFactor();
385 log.altCorrection = CAircraftSituation::altitudeCorrectionToString(altCorrection);
386 log.situationCurrent = currentSituation;
387 log.interpolantRecalc = interpolant.isRecalculated();
388 log.change = m_pastSituationsChange;
389 log.usedSetup = m_currentSetup;
390 log.elevationInfo = this->getElevationsFoundMissedInfo();
391 log.cgAboveGround = currentSituation.getCG();
392 log.sceneryOffset = m_currentSceneryOffset;
393 log.noInvalidSituations = m_invalidSituations;
394 log.noNetworkSituations = m_currentSituations.sizeInt();
395 log.useParts = this->isRemoteAircraftSupportingParts(m_callsign);
396 m_logger->logInterpolation(log);
397 }
398
399 // bye
400 return currentSituation;
401 }
402
403 CAircraftParts CInterpolator::getInterpolatedParts()
404 {
405 // Parts are supposed to be in correct order, latest first
406 const CAircraftPartsList validParts = this->remoteAircraftParts(m_callsign);
407
408 // log for empty parts aircraft parts
409 if (validParts.isEmpty())
410 {
411 static const CAircraftParts emptyParts;
412 this->logParts(emptyParts, validParts.size(), true);
413 return emptyParts;
414 }
415
416 m_currentPartsStatus.setSupportsParts(true);
417 CAircraftParts currentParts;
418
419 // find the first parts earlier than the current time
420 const auto pivot = std::ranges::partition_point(
421 validParts, [=, this](auto &&p) { return p.getAdjustedMSecsSinceEpoch() > m_currentTimeMsSinceEpoch; });
422 const auto partsNewer = makeRange(validParts.begin(), pivot).reverse();
423 const auto partsOlder = makeRange(pivot, validParts.end());
424
425 if (partsOlder.isEmpty())
426 {
427 currentParts = *(partsNewer.begin());
428 }
429 else
430 {
431 currentParts = partsOlder.front(); // latest older parts
432 }
433
434 this->logParts(currentParts, validParts.size(), false);
435 return currentParts;
436 }
437
438 CAircraftParts CInterpolator::getInterpolatedOrGuessedParts(int aircraftNumber)
439 {
441 "Wrong ratio");
442 const bool needParts = m_unitTest || m_lastParts.isNull();
443 const bool doInterpolation =
444 needParts ||
446 const bool doGuess =
447 needParts || ((m_interpolatedSituationsCounter + aircraftNumber) % m_partsToSituationGuessingRatio == 0);
448
449 if (!doGuess && !doInterpolation)
450 {
451 // reuse
452 return this->logAndReturnNullParts("neither guess nor interpolation", true);
453 }
454
455 CAircraftParts parts = CAircraftParts::null();
456 if (m_currentSetup.isAircraftPartsEnabled())
457 {
458 // this already logs and sets status
459 parts = this->getInterpolatedParts();
460 }
461
462 // if we have supported parts, we skip this step, but it can happen the parts are still empty
463 if (!m_currentPartsStatus.isSupportingParts())
464 {
465 if (!doGuess)
466 {
467 return this->logAndReturnNullParts("not supporting parts, and marked for guessing", true);
468 }
469
470 // check if model has been thru model matching
471 if (!m_lastSituation.isNull())
472 {
473 parts = guessParts(m_lastSituation, m_pastSituationsChange, m_model);
474 this->logParts(parts, 0, false);
475 }
476 else
477 {
478 // quite normal initial situation, just return NULL
479 return this->logAndReturnNullParts("guessing, but no situation yet", false);
480 }
481 }
482
483 m_lastParts = parts;
485 return parts;
486 }
487
488 const CAircraftParts &CInterpolator::logAndReturnNullParts(const QString &info, bool log)
489 {
490 if (!m_lastParts.isNull())
491 {
493 m_currentPartsStatus.setReusedParts(true);
494 return m_lastParts;
495 }
496
497 if (log)
498 {
499 const CStatusMessage m = CStatusMessage(this).warning(u"NULL parts reported for '%1', '%2')")
500 << m_callsign.asString() << info;
501 if (m_interpolationMessages.isEmpty())
502 {
504 }
505 m_interpolationMessages.push_back(m);
506 }
508 return CAircraftParts::null();
509 }
510
511 bool CInterpolator::doLogging() const { return this->hasAttachedLogger() && m_currentSetup.logInterpolation(); }
512
513 CAircraftParts CInterpolator::guessParts(const CAircraftSituation &situation,
514 const CAircraftSituationChange &change, const CAircraftModel &model)
515 {
516 CAircraftParts parts;
517 parts.setMSecsSinceEpoch(situation.getMSecsSinceEpoch());
518 parts.setTimeOffsetMs(situation.getTimeOffsetMs());
519 parts.setPartsDetails(CAircraftParts::GuessedParts);
520 parts.setLights(situation.guessLights());
521
522 QString *details = /*CBuildConfig::isLocalDeveloperDebugBuild() ? &parts.m_guessingDetails :*/ nullptr;
523
524 CAircraftEngineList engines;
525 const bool vtol = model.isVtol();
526 const int engineCount = model.getEngineCount();
527 CSpeed guessedVRotate = CSpeed::null();
528 CLength guessedCG = model.getCG();
529 model.getAircraftIcaoCode().guessModelParameters(guessedCG, guessedVRotate);
530
532 {
533 do
534 {
535 // set some reasonable values
536 const bool isOnGround = situation.isOnGround();
537 engines.initEngines(engineCount, !isOnGround || situation.isMoving(),
538 (!isOnGround || situation.isMoving()) ? 100 : 0);
539 parts.setGearDown(isOnGround);
540 parts.setSpoilersOut(false);
541 parts.setEngines(engines);
542
543 if (!change.isNull())
544 {
545 if (change.isConstDecelarating())
546 {
547 parts.setSpoilersOut(true);
548 parts.setFlapsPercent(10);
549 break;
550 }
551 }
552
553 const CSpeed slowSpeed = guessedVRotate * 0.30;
554 if (situation.getGroundSpeed() < slowSpeed)
555 {
556 if (details)
557 {
558 *details += u"slow speed <" % slowSpeed.valueRoundedWithUnit(1) % u" on ground";
559 }
560 parts.setFlapsPercent(0);
561 break;
562 }
563 else
564 {
565 if (details)
566 {
567 *details += u"faster speed >" % slowSpeed.valueRoundedWithUnit(1) % u" on ground";
568 }
569 parts.setFlapsPercent(0);
570 break;
571 }
572 }
573 while (false);
574 }
575 else
576 {
577 if (details)
578 {
579 *details = QStringLiteral("no ground info");
580 }
581
582 // no idea if on ground or not
583 engines.initEngines(engineCount, true, 100);
584 parts.setEngines(engines);
585 parts.setGearDown(true);
586 parts.setSpoilersOut(false);
587 }
588
589 const double pitchDeg = situation.getPitch().value(CAngleUnit::deg());
590 const bool isLikelyTakeOffOrClimbing =
591 change.isNull() ? pitchDeg > 20 : (change.isRotatingUp() || change.isConstAscending());
592 const bool isLikelyLanding = change.isNull() ? false : change.isConstDescending();
593
594 if (situation.hasGroundElevation())
595 {
596 const CLength aboveGnd = situation.getHeightAboveGround();
597 if (aboveGnd.isNull() || std::isnan(aboveGnd.value()))
598 {
599 SWIFT_VERIFY_X(false, Q_FUNC_INFO, "above gnd.is null");
600 return parts;
601 }
602
603 const double nearGround1Ft = 300;
604 const double nearGround2Ft = isLikelyTakeOffOrClimbing ? 500 : 1000;
605 const double aGroundFt = aboveGnd.value(CLengthUnit::ft());
606 static const QString detailsInfo(
607 "above ground: %1ft near grounds: %2ft %3ft likely takeoff: %4 likely landing: %5");
608
609 if (details)
610 {
611 *details = detailsInfo.arg(aGroundFt)
612 .arg(nearGround1Ft)
613 .arg(nearGround2Ft)
614 .arg(boolToYesNo(isLikelyTakeOffOrClimbing), boolToYesNo(isLikelyLanding));
615 }
616 if (aGroundFt < nearGround1Ft)
617 {
618 if (details)
619 {
620 details->prepend(QStringLiteral("near ground: "));
621 }
622 parts.setGearDown(true);
623 parts.setFlapsPercent(25);
624 }
625 else if (aGroundFt < nearGround2Ft)
626 {
627 if (details)
628 {
629 details->prepend(QStringLiteral("2nd layer: "));
630 }
631 const bool gearDown =
632 !isLikelyTakeOffOrClimbing && (situation.getGroundSpeed() < guessedVRotate || isLikelyLanding);
633 parts.setGearDown(gearDown);
634 parts.setFlapsPercent(10);
635 }
636 else
637 {
638 if (details)
639 {
640 details->prepend(QStringLiteral("airborne: "));
641 }
642 parts.setGearDown(false);
643 parts.setFlapsPercent(0);
644 }
645 }
646 else
647 {
649 {
650 // we have no ground elevation but a ground info
652 {
653 // should be OK
654 if (details)
655 {
656 *details = QStringLiteral("on ground, no elv.");
657 }
658 }
659 else
660 {
661 if (!vtol)
662 {
663 const bool gearDown = situation.getGroundSpeed() < guessedVRotate;
664 parts.setGearDown(gearDown);
665 if (details)
666 {
667 *details =
668 QStringLiteral("not on ground elv., gs < ") + guessedVRotate.valueRoundedWithUnit(1);
669 }
670 }
671 }
672 }
673 }
674
675 return parts;
676 }
677
678 void CInterpolator::logParts(const CAircraftParts &parts, int partsNo, bool empty) const
679 {
680 if (!this->doLogging())
681 {
682 return;
683 }
684 PartsLog logInfo;
685 logInfo.callsign = m_callsign;
686 logInfo.noNetworkParts = partsNo;
687 logInfo.tsCurrent = m_currentTimeMsSinceEpoch;
688 logInfo.parts = parts;
689 logInfo.empty = empty;
690 m_logger->logParts(logInfo);
691 }
692
694 {
695 return QStringLiteral("Callsign: ") % m_callsign.asString() % QStringLiteral(" situations: ") %
696 QString::number(this->remoteAircraftSituationsCount(m_callsign)) % QStringLiteral(" parts: ") %
697 QString::number(this->remoteAircraftPartsCount(m_callsign)) % QStringLiteral(" 1st interpolation: ") %
699 }
700
702
703 bool CInterpolator::initIniterpolationStepData(qint64 currentTimeSinceEpoch,
705 int aircraftNumber)
706 {
707 Q_ASSERT_X(!m_callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign");
708
709 const qint64 lastModifed = this->situationsLastModified(m_callsign);
710 const bool slowUpdateStep = (((m_interpolatedSituationsCounter + aircraftNumber) % 25) ==
711 0); // flag when parts are updated, which need not to be updated every time
712 const bool changedSituations = lastModifed > m_situationsLastModified;
713
714 m_currentTimeMsSinceEpoch = currentTimeSinceEpoch;
717 m_currentSetup = setup;
718
719 if (changedSituations)
720 {
721 m_situationsLastModified = lastModifed;
722 m_currentSituations = this->remoteAircraftSituationsAndChange(setup); // only update when needed
723 }
724
725 if (!m_model.hasCG() || slowUpdateStep)
726 {
727 this->getAndFetchModelCG(CLength::null()); // update CG
728 }
729
730 bool success = false;
731 const int situationsSize = m_currentSituations.sizeInt();
732 m_currentInterpolationStatus.setSituationsCount(situationsSize);
733 if (m_currentSituations.isEmpty())
734 {
735 const bool inRange = this->isAircraftInRange(m_callsign);
736 m_lastSituation = CAircraftSituation::null(); // no interpolation possible for that step
737 static const QString extraNoSituations("No situations, but remote aircraft '%1'");
738 static const QString extraNoRemoteAircraft("Unknown remote aircraft: '%1'");
739 m_currentInterpolationStatus.setExtraInfo(
740 (inRange ? extraNoSituations : extraNoRemoteAircraft).arg(m_callsign.asString()));
741 }
742 else
743 {
744 success = true;
745 m_interpolatedSituationsCounter++; // counter updated in initIniterpolationStepData
746
747 // with the latest updates of T243 the order and the offsets are supposed to be correct
748 // so even mixing fast/slow updates shall work
750 {
751 Q_ASSERT_X(m_currentSituations.isSortedAdjustedLatestFirstWithoutNullPositions(), Q_FUNC_INFO,
752 "Wrong sort order");
754 "Wrong size");
755 }
756 }
757
758 return success;
759 }
760
761 CAircraftSituation CInterpolator::initInterpolatedSituation(const CAircraftSituation &oldSituation,
762 const CAircraftSituation &newSituation) const
763 {
764 if (m_currentSituations.isEmpty())
765 {
767 }
768
769 CAircraftSituation currentSituation = m_lastSituation.isNull() ? m_currentSituations.front() : m_lastSituation;
770 if (currentSituation.getCallsign() != m_callsign)
771 {
772 SWIFT_VERIFY_X(false, Q_FUNC_INFO, "Wrong callsign");
773 currentSituation.setCallsign(m_callsign);
774 }
775
777 {
778 Q_ASSERT_X(currentSituation.isValidVectorRange(), Q_FUNC_INFO, "Invalid range");
779 }
780
781 // preset elevation here, as we do not know where the situation will be after the interpolation step!
782 const bool preset = presetGroundElevation(currentSituation, oldSituation, newSituation, m_pastSituationsChange);
783 Q_UNUSED(preset)
784
785 // fetch CG once
786 const CLength cg(this->getModelCG());
787 currentSituation.setCG(cg);
788 return currentSituation;
789 }
790
792 {
793 if (model.hasModelString())
794 {
795 m_model = model;
796 }
797 else
798 {
800 if (foundModel.hasModelString())
801 {
802 m_model = foundModel;
803 }
804 }
805 this->getAndFetchModelCG(model.getCG());
806 }
807
808 void CInterpolator::markAsUnitTest() { m_unitTest = true; }
809} // namespace swift::misc::simulation
QPair< CAltitude, CAltitude > CAltitudePair
Pair of altitude.
Definition altitude.h:251
static CAngleUnit deg()
Degrees.
Definition units.h:276
static constexpr bool isReleaseBuild()
Release build?
static bool isLocalDeveloperDebugBuild()
Local build for developers.
static const QString & interpolator()
Interpolator.
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
bool isEmpty() const
Message empty.
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
size_type size() const
Returns number of elements in the sequence.
Definition sequence.h:273
iterator begin()
Returns iterator at the beginning of the sequence.
Definition sequence.h:163
bool isEmpty() const
Synonym for empty.
Definition sequence.h:285
iterator end()
Returns iterator one past the end of the sequence.
Definition sequence.h:172
qint64 getMSecsSinceEpoch() const
Timestamp as ms value.
void setMSecsSinceEpoch(qint64 mSecsSinceEpoch)
Timestamp as ms value.
qint64 getAdjustedMSecsSinceEpoch() const
Timestamp with offset added for interpolation.
qint64 getTimeOffsetMs() const
Milliseconds to add to timestamp for interpolation.
void setTimeOffsetMs(qint64 offset)
Milliseconds to add to timestamp for interpolation.
Value object encapsulating a list of aircraft engines.
void initEngines(int engineNumber, bool on, int engineRpmPercentage)
Init some engines.
void guessModelParameters(physical_quantities::CLength &guessedCGOut, physical_quantities::CSpeed &guessedVRotateOut) const
Guess aircraft model parameters.
Value object encapsulating information of aircraft's parts.
static const CAircraftParts & null()
NULL parts object.
void setEngines(const CAircraftEngineList &engines)
Set engines.
void setGearDown(bool down)
Set gear down.
void setPartsDetails(PartsDetails details)
Set parts details.
void setLights(const CAircraftLights &lights)
Set aircraft lights.
void setFlapsPercent(int flapsPercent)
Set flaps position in percent.
void setSpoilersOut(bool out)
Set spoilers out.
Value object about changes in situations.
bool isConstDescending() const
Constantly descending?
CAltitudePair getElevationStdDevAndMean() const
Elevation standard deviation and mean.
bool hasElevationDevWithinAllowedRange() const
Elevation within CAircraftSituation::allowedAltitudeDeviation range.
bool isConstDecelarating() const
Constantly decelarating?
static const CAircraftSituationChange & null()
NULL object.
bool guessOnGround(CAircraftSituation &situation, const simulation::CAircraftModel &model) const
Guess on ground flag.
CAircraftSituationChange indexOrNull(int index) const
Index or NULL.
Value object encapsulating information of an aircraft's situation.
AltitudeCorrection correctAltitude(bool enableDragToGround=true)
Set the corrected altitude from CAircraftSituation::getCorrectedAltitude.
void setCallsign(const CCallsign &callsign)
Corresponding callsign.
void resetGroundElevation()
Reset ground elevation.
bool hasGroundElevation() const
Is ground elevation value available.
void setCG(const physical_quantities::CLength &cg)
Set CG.
void setGroundSpeed(const physical_quantities::CSpeed &groundspeed)
Set ground speed.
const physical_quantities::CLength & getCG() const
Get CG if any.
physical_quantities::CLength getDistancePerTime250ms(const physical_quantities::CLength &min=physical_quantities::CLength::null()) const
Distance per milliseconds (250ms).
bool setGroundElevation(const aviation::CAltitude &altitude, GndElevationInfo info, bool transferred=false)
Elevation of the ground directly beneath at the given situation.
void setBank(const physical_quantities::CAngle &bank)
Set bank (angle).
void setHeading(const CHeading &heading)
Set heading.
aviation::COnGroundInfo getOnGroundInfo() const
On ground info.
static const QString & altitudeCorrectionToString(AltitudeCorrection correction)
Enum to string.
AltitudeCorrection
How was altitude corrected?
bool transferGroundElevationToMe(const CAircraftSituation &fromSituation, const physical_quantities::CLength &radius, bool transferred)
Transfer ground elevation from given situation (to me).
@ SituationChange
from swift::misc::aviation::CAircraftSituationChange
@ Interpolated
interpolated between 2 elevations
void setAltitude(const CAltitude &altitude)
Set altitude.
static const CAircraftSituation & null()
Null situation.
bool canLikelySkipNearGroundInterpolation() const
Situation looks like an aircraft not near ground.
void setOnGroundInfo(const aviation::COnGroundInfo &info)
Set the on ground info.
CAircraftLights guessLights() const
Guessed lights.
const physical_quantities::CAngle & getPitch() const
Get pitch.
void setPitch(const physical_quantities::CAngle &pitch)
Set pitch.
const physical_quantities::CSpeed & getGroundSpeed() const
Get ground speed.
bool interpolateElevation(const aviation::CAircraftSituation &oldSituation, const aviation::CAircraftSituation &newSituation)
Interpolate "this" elevation from the two adjacent positions.
bool isMoving() const
Is moving? Means ground speed > epsilon.
const CCallsign & getCallsign() const
Corresponding callsign.
static geo::CElevationPlane interpolatedElevation(const CAircraftSituation &situation, const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const physical_quantities::CLength &distance=physical_quantities::CLength::null())
Interpolate between the 2 situations for situation.
physical_quantities::CLength getHeightAboveGround() const
Height above ground.
void setPosition(const geo::CCoordinateGeodetic &position)
Set position.
int addAltitudeOffset(const physical_quantities::CLength &offset)
Add an offset to each altitude.
Value object encapsulating information of a callsign.
Definition callsign.h:30
bool isEmpty() const
Is empty?
Definition callsign.h:63
@ NotSetGroundDetails
not set or unknown if correct (e.g.
double getGroundFactor() const
Get the ground factor Use this for interpolation only!! For just checking if the info is OnGround or ...
OnGroundDetails getGroundDetails() const
Get ground details.
static const physical_quantities::CLength & singlePointRadius()
Radius for single point.
bool isNull() const
Existing value?
bool hasMSLGeodeticHeight() const
Geodetic height not null and aviation::CAltitude::MeanSeaLevel.
physical_quantities::CLength calculateGreatCircleDistance(const ICoordinateGeodetic &otherCoordinate) const
Great circle distance.
bool equalNormalVectorDouble(const std::array< double, 3 > &otherVector) const
Is equal? Epsilon considered.
Physical unit length (length).
Definition length.h:18
static CLengthUnit ft()
Foot ft.
Definition units.h:157
double value(MU unit) const
Value in given unit.
QString valueRoundedWithUnit(const MU &unit, int digits=-1, bool withGroupSeparator=false, bool i18n=false) const
Value to QString with the given unit, e.g. "5.00m".
Aircraft model (used by another pilot, my models on disk).
const aviation::CCallsign & getCallsign() const
Corresponding callsign if applicable.
const physical_quantities::CLength & getCG() const
Get center of gravity.
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
int getEngineCount() const
Engine count if any, -1 if no value is set.
bool hasCG() const
CG value available?
bool hasModelString() const
Non empty model string?
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Aircraft ICAO code.
Value object for interpolator and rendering per callsign.
Record internal state of interpolator for debugging.
void setValues(const aviation::CAircraftSituation &situation, const aviation::CAircraftParts &parts)
Set values.
void setStatus(const CInterpolationStatus &interpolation, const CPartsStatus &parts)
Set status values.
void setInterpolationSetupProvider(IInterpolationSetupProvider *provider)
Provider.
bool isInterpolated() const
Did interpolation succeed?
int m_partsToSituationGuessingRatio
ratio between parts guessing and situation interpolation
int m_partsToSituationInterpolationRatio
ratio between parts and situation interpolation, 1..always, 2..every 2nd situation
qint64 m_situationsLastModified
when situations were last modified
aviation::CAircraftParts m_lastParts
latest parts
CInterpolator(const aviation::CCallsign &callsign, ISimulationEnvironmentProvider *simEnvProvider, IInterpolationSetupProvider *setupProvider, IRemoteAircraftProvider *remoteProvider, CInterpolationLogger *logger)
Constructor.
const physical_quantities::CLength & getModelCG() const
Center of gravity.
static const QStringList & getLogCategories()
Log categories.
CInterpolationStatus m_currentInterpolationStatus
this step's situation status
physical_quantities::CLength m_currentSceneryOffset
calculated scenery offset if any
CInterpolationResult getInterpolation(qint64 currentTimeSinceEpoch, const CInterpolationAndRenderingSetupPerCallsign &setup, uint32_t aircraftNumber)
Get interpolated situation.
CInterpolationAndRenderingSetupPerCallsign m_currentSetup
used setup
CPartsStatus m_currentPartsStatus
this step's parts status
aviation::CAircraftSituation m_lastSituation
latest interpolation
void attachLogger(CInterpolationLogger *logger)
Attach an observer to read the interpolator's state for debugging.
void resetLastInterpolation()
Reset last interpolation to null.
int m_interpolatedSituationsCounter
counter for each interpolated situations: used for statistics, every n-th interpolation ....
aviation::CAircraftSituationChange m_pastSituationsChange
situations change of provider (i.e.
CStatusMessageList m_interpolationMessages
interpolation messages
QString getInterpolatorInfo() const
Get an interpolator info string (for debug info).
CPartsStatus m_lastPartsStatus
status for last parts, used when last parts are re-used because of m_partsToSituationInterpolationRat...
aviation::CAircraftSituationList m_currentSituations
current situations obtained by remoteAircraftSituationsAndChange
const aviation::CCallsign m_callsign
corresponding callsign
bool hasAttachedLogger() const
Is logger attached?
virtual const IInterpolant & getInterpolant(SituationLog &log)=0
Get the interpolant for the given time point.
void markAsUnitTest()
Mark as unit test.
qint64 m_lastInvalidLogTs
last invalid situation timestamp
int m_invalidSituations
mainly when there are no new situations
CAircraftModel m_model
corresponding model (required for CG)
void initCorrespondingModel(const CAircraftModel &model={})
Init, or re-init the corressponding model.
bool isAircraftInRange(const aviation::CCallsign &callsign) const
Is aircraft in range?
IRemoteAircraftProvider * getRemoteAircraftProvider() const
Get the aircraft provider.
aviation::CAircraftSituationList remoteAircraftSituations(const aviation::CCallsign &callsign) const
Rendered aircraft situations (per callsign, time history).
qint64 situationsLastModified(const aviation::CCallsign &callsign) const
When last modified.
CSimulatedAircraft getAircraftInRangeForCallsign(const aviation::CCallsign &callsign) const
Aircraft for callsign.
void setRemoteAircraftProvider(IRemoteAircraftProvider *remoteAircraftProvider)
Set remote aircraft provider.
aviation::CAircraftPartsList remoteAircraftParts(const aviation::CCallsign &callsign) const
All parts (per callsign, time history).
aviation::CAircraftSituationChangeList remoteAircraftSituationChanges(const aviation::CCallsign &callsign) const
Aircraft changes.
bool isRemoteAircraftSupportingParts(const aviation::CCallsign &callsign) const
Is remote aircraft supporting parts?
int remoteAircraftSituationsCount(const aviation::CCallsign &callsign) const
Number of remote aircraft situations for callsign.
int remoteAircraftPartsCount(const aviation::CCallsign &callsign) const
All parts (per callsign, time history).
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping).
physical_quantities::CLength getSimulatorOrDbCG(const aviation::CCallsign &callsign, const physical_quantities::CLength &dbCG) const
Get CG per callsign, NULL if not found.
geo::CElevationPlane findClosestElevationWithinRange(const geo::ICoordinateGeodetic &reference, const physical_quantities::CLength &range) const
Find closest elevation (or return NULL).
QString getElevationsFoundMissedInfo() const
Elevations found/missed statistics info as string.
void setSimulationEnvironmentProvider(ISimulationEnvironmentProvider *provider)
Set the provider.
Direct in memory access to interpolation setup, normally implemented by simulator.
Direct thread safe in memory access to remote aircraft.
virtual physical_quantities::CLength getCGFromDB(const aviation::CCallsign &callsign) const =0
CG values from DB.
static constexpr int MaxSituationsPerCallsign
How many situations we keep per callsign.
virtual void rememberCGFromDB(const physical_quantities::CLength &cgFromDB, const aviation::CCallsign &callsign)=0
CG values from DB.
SWIFT_MISC_EXPORT const QString & boolToTrueFalse(bool v)
Bool to true/false.
SWIFT_MISC_EXPORT const QString & boolToYesNo(bool v)
Bool to yes/no.
auto makeRange(I begin, I2 end) -> CRange< I >
Returns a CRange constructed from begin and end iterators of deduced types.
Definition range.h:328
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
void timeout()
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition verify.h:26