// SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 #include "blackmisc/simulation/interpolatorlinear.h" #include "blackmisc/simulation/interpolatorfunctions.h" #include "blackmisc/aviation/aircraftsituationlist.h" #include "blackmisc/aviation/altitude.h" #include "blackmisc/geo/coordinategeodetic.h" #include "blackmisc/pq/length.h" #include "blackmisc/pq/physicalquantity.h" #include "blackmisc/logmessage.h" #include "blackmisc/mixin/mixincompare.h" #include "blackmisc/verify.h" #include "blackmisc/range.h" #include "blackmisc/sequence.h" #include "blackmisc/statusmessage.h" #include "blackconfig/buildconfig.h" #include #include #include using namespace BlackConfig; using namespace BlackMisc::Aviation; using namespace BlackMisc::Geo; using namespace BlackMisc::Math; using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Simulation; namespace BlackMisc::Simulation { CInterpolatorLinear::CInterpolant::CInterpolant(const CAircraftSituation &startSituation) : IInterpolant(1, CInterpolatorPbh(0, startSituation, startSituation)), m_startSituation(startSituation) {} CInterpolatorLinear::CInterpolant::CInterpolant(const CAircraftSituation &startSituation, const CInterpolatorPbh &pbh) : IInterpolant(1, pbh), m_startSituation(startSituation) {} CInterpolatorLinear::CInterpolant::CInterpolant(const CAircraftSituation &startSituation, const CAircraftSituation &endSituation, double timeFraction, qint64 interpolatedTime) : IInterpolant(interpolatedTime, 2), m_startSituation(startSituation), m_endSituation(endSituation), m_simulationTimeFraction(timeFraction) { m_pbh = CInterpolatorPbh(m_simulationTimeFraction, startSituation, endSituation); } void CInterpolatorLinear::anchor() {} CAircraftSituation CInterpolatorLinear::CInterpolant::interpolatePositionAndAltitude(const CAircraftSituation &situation, bool interpolateGndFactor) const { const std::array startVec(m_startSituation.getPosition().normalVectorDouble()); const std::array endVec(m_endSituation.getPosition().normalVectorDouble()); if (CBuildConfig::isLocalDeveloperDebugBuild()) { BLACK_VERIFY_X(CAircraftSituation::isValidVector(startVec), Q_FUNC_INFO, "Invalid start vector"); BLACK_VERIFY_X(CAircraftSituation::isValidVector(endVec), Q_FUNC_INFO, "Invalid end vector"); BLACK_VERIFY_X(isAcceptableTimeFraction(m_simulationTimeFraction), Q_FUNC_INFO, "Invalid fraction"); } // Interpolate position: pos = (posB - posA) * t + posA CCoordinateGeodetic interpolatedPosition; const double tf = clampValidTimeFraction(m_simulationTimeFraction); interpolatedPosition.setNormalVector((endVec[0] - startVec[0]) * tf + startVec[0], (endVec[1] - startVec[1]) * tf + startVec[1], (endVec[2] - startVec[2]) * tf + startVec[2]); if (CBuildConfig::isLocalDeveloperDebugBuild()) { BLACK_VERIFY_X(interpolatedPosition.isValidVectorRange(), Q_FUNC_INFO, "Invalid vector"); } // Interpolate altitude: Alt = (AltB - AltA) * t + AltA // avoid underflow below ground elevation by using getCorrectedAltitude const CAltitude oldAlt(m_startSituation.getCorrectedAltitude()); const CAltitude newAlt(m_endSituation.getCorrectedAltitude()); Q_ASSERT_X(oldAlt.getReferenceDatum() == CAltitude::MeanSeaLevel && oldAlt.getReferenceDatum() == newAlt.getReferenceDatum(), Q_FUNC_INFO, "mismatch in reference"); // otherwise no calculation is possible const CAltitude altitude((newAlt - oldAlt) * tf + oldAlt, oldAlt.getReferenceDatum()); CAircraftSituation interpolatedSituation(situation); interpolatedSituation.setPosition(interpolatedPosition); interpolatedSituation.setAltitude(altitude); interpolatedSituation.setMSecsSinceEpoch(this->getInterpolatedTime()); if (interpolateGndFactor) { const double startGroundFactor = m_startSituation.getOnGroundFactor(); const double endGroundFactor = m_endSituation.getOnGroundFactor(); if (CAircraftSituation::isGfEqualAirborne(startGroundFactor, endGroundFactor)) { interpolatedSituation.setOnGround(false); } else if (CAircraftSituation::isGfEqualOnGround(startGroundFactor, endGroundFactor)) { interpolatedSituation.setOnGround(true); } else { const double interpolatedGroundFactor = (endGroundFactor - startGroundFactor) * tf + startGroundFactor; interpolatedSituation.setOnGroundFactor(interpolatedGroundFactor); interpolatedSituation.setOnGroundFromGroundFactorFromInterpolation(groundInterpolationFactor()); } } return interpolatedSituation; } CInterpolatorLinear::CInterpolant CInterpolatorLinear::getInterpolant(SituationLog &log) { // set default situations CAircraftSituation startSituation = m_interpolant.getStartSituation(); CAircraftSituation endSituation = m_interpolant.getEndSituation(); Q_ASSERT_X(endSituation.getAdjustedMSecsSinceEpoch() >= startSituation.getAdjustedMSecsSinceEpoch(), Q_FUNC_INFO, "Wrong order"); const bool updated = m_situationsLastModifiedUsed < m_situationsLastModified; const bool newSplit = endSituation.getAdjustedMSecsSinceEpoch() < m_currentTimeMsSinceEpoch; const bool recalculate = updated || newSplit; if (recalculate) { m_situationsLastModifiedUsed = m_situationsLastModified; // find the first situation earlier than the current time const auto pivot = std::partition_point(m_currentSituations.begin(), m_currentSituations.end(), [=](auto &&s) { return s.getAdjustedMSecsSinceEpoch() > m_currentTimeMsSinceEpoch; }); const auto situationsNewer = makeRange(m_currentSituations.begin(), pivot); const auto situationsOlder = makeRange(pivot, m_currentSituations.end()); // latest first, now 00:20 split time // time pos // 00:25 10 newer // 00:20 11 newer // <----- split // 00:15 12 older // 00:10 13 older // 00:05 14 older // The first condition covers a situation, when there are no before / after situations. // We just place at the last position until we get before / after situations if (situationsOlder.isEmpty() || situationsNewer.isEmpty()) { // no before situations if (situationsOlder.isEmpty()) { const CAircraftSituation currentSituation(*(situationsNewer.end() - 1)); // oldest newest m_currentInterpolationStatus.setInterpolatedAndCheckSituation(false, currentSituation); m_interpolant = { currentSituation }; return m_interpolant; } // only one before situation if (situationsOlder.size() < 2) { const CAircraftSituation currentSituation(situationsOlder.front()); // latest oldest m_currentInterpolationStatus.setInterpolatedAndCheckSituation(false, currentSituation); m_interpolant = { currentSituation }; return m_interpolant; } // extrapolate from two before situations startSituation = *(situationsOlder.begin() + 1); // before newest endSituation = situationsOlder.front(); // newest } else { startSituation = situationsOlder.front(); // first oldest (aka newest oldest) endSituation = *(situationsNewer.end() - 1); // latest newest (aka oldest of newer block) Q_ASSERT(startSituation.getAdjustedMSecsSinceEpoch() < endSituation.getAdjustedMSecsSinceEpoch()); } // adjust ground if required if (!startSituation.canLikelySkipNearGroundInterpolation() && !startSituation.hasGroundElevation()) { const CElevationPlane planeOld = this->findClosestElevationWithinRange(startSituation, CElevationPlane::singlePointRadius()); startSituation.setGroundElevationChecked(planeOld, CAircraftSituation::FromCache); } if (!endSituation.canLikelySkipNearGroundInterpolation() && !endSituation.hasGroundElevation()) { const CElevationPlane planeNew = this->findClosestElevationWithinRange(endSituation, CElevationPlane::singlePointRadius()); endSituation.setGroundElevationChecked(planeNew, CAircraftSituation::FromCache); } } // modified situations CAircraftSituation currentSituation(startSituation); // also sets ground elevation if available // Time between start and end packet const qint64 sampleDeltaTimeMs = endSituation.getAdjustedMSecsSinceEpoch() - startSituation.getAdjustedMSecsSinceEpoch(); Q_ASSERT_X(sampleDeltaTimeMs >= 0, Q_FUNC_INFO, "Negative delta time"); log.interpolator = 'l'; // Fraction of the deltaTime, ideally [0.0 - 1.0] // < 0 should not happen due to the split, > 1 can happen if new values are delayed beyond split time // 1) values > 1 mean extrapolation // 2) values > 2 mean no new situations coming in const double distanceToSplitTimeMs = endSituation.getAdjustedMSecsSinceEpoch() - m_currentTimeMsSinceEpoch; double simulationTimeFraction = qMax(1.0 - (distanceToSplitTimeMs / sampleDeltaTimeMs), 0.0); if (simulationTimeFraction >= 1.0) { simulationTimeFraction = 1.0; if (qAbs(distanceToSplitTimeMs) > 100) { CLogMessage(this).debug(u"Distance to split: %1") << distanceToSplitTimeMs; } } const double deltaTimeFractionMs = sampleDeltaTimeMs * simulationTimeFraction; const qint64 interpolatedTime = startSituation.getMSecsSinceEpoch() + qRound(deltaTimeFractionMs); // Ref T297 adjust offset time, but this already the interpolated situation currentSituation.setTimeOffsetMs(startSituation.getTimeOffsetMs() + qRound((endSituation.getTimeOffsetMs() - startSituation.getTimeOffsetMs()) * simulationTimeFraction)); currentSituation.setMSecsSinceEpoch(interpolatedTime); m_currentInterpolationStatus.setInterpolatedAndCheckSituation(true, currentSituation); if (this->doLogging()) { log.tsCurrent = m_currentTimeMsSinceEpoch; log.deltaSampleTimesMs = sampleDeltaTimeMs; log.simTimeFraction = simulationTimeFraction; log.deltaSampleTimesMs = sampleDeltaTimeMs; log.tsInterpolated = interpolatedTime; log.interpolationSituations.clear(); log.interpolationSituations.push_back(startSituation); // oldest at front log.interpolationSituations.push_back(endSituation); // latest at back log.interpolantRecalc = recalculate; } m_interpolant = { startSituation, endSituation, simulationTimeFraction, interpolatedTime }; m_interpolant.setRecalculated(recalculate); return m_interpolant; } } // namespace