/* Copyright (C) 2015 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, * including this file, may be copied, modified, propagated, or distributed except according to the terms * contained in the LICENSE file. */ #include "interpolator.h" #include "blackconfig/buildconfig.h" #include "blackmisc/simulation/interpolationlogger.h" #include "blackmisc/simulation/interpolatorlinear.h" #include "blackmisc/simulation/interpolatorspline.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/aviation/heading.h" #include "blackmisc/pq/angle.h" #include "blackmisc/pq/speed.h" #include "blackmisc/pq/units.h" #include "blackmisc/pq/length.h" #include "blackmisc/logmessage.h" #include "blackmisc/verify.h" #include #include #include using namespace BlackConfig; using namespace BlackMisc::Aviation; using namespace BlackMisc::Geo; using namespace BlackMisc::Math; using namespace BlackMisc::PhysicalQuantities; namespace BlackMisc { namespace Simulation { template CInterpolator::CInterpolator(const CCallsign &callsign, ISimulationEnvironmentProvider *simEnvProvider, IInterpolationSetupProvider *setupProvider, IRemoteAircraftProvider *remoteAircraftProvider, CInterpolationLogger *logger) : m_callsign(callsign) { // normally when created m_cg is still null since there is no CG in the provider yet if (simEnvProvider) { this->setSimulationEnvironmentProvider(simEnvProvider); } if (setupProvider) { this->setInterpolationSetupProvider(setupProvider); } if (remoteAircraftProvider) { this->setRemoteAircraftProvider(remoteAircraftProvider); QTimer::singleShot(2500, [ = ] { if (m_model.hasModelString()) { return; } // set in-between this->initCorrespondingModel(); }); } this->attachLogger(logger); } template bool CInterpolator::verifyInterpolationSituations(const CAircraftSituation &oldest, const CAircraftSituation &newer, const CAircraftSituation &latest, const CInterpolationAndRenderingSetupPerCallsign &setup) { if (!CBuildConfig::isLocalDeveloperDebugBuild()) { return true; } CAircraftSituationList situations; // oldest last, null ignored if (!latest.isNull()) { situations.push_back(latest); } if (!newer.isNull()) { situations.push_back(newer); } if (!oldest.isNull()) { situations.push_back(oldest); } const bool sort1 = situations.isSortedLatestFirst(); BLACK_VERIFY_X(sort1, Q_FUNC_INFO, "Wrong timestamp order"); const bool sort2 = situations.isSortedAdjustedLatestFirst(); BLACK_VERIFY_X(sort2, Q_FUNC_INFO, "Wrong adjusted timestamp order"); bool details = true; if (setup.isAircraftPartsEnabled()) { if (situations.containsOnGroundDetails(CAircraftSituation::InFromParts)) { // if a client supports parts, all situations are supposed to be parts based details = situations.areAllOnGroundDetailsSame(CAircraftSituation::InFromParts); BLACK_VERIFY_X(details, Q_FUNC_INFO, "Once gnd.from parts -> always gnd. from parts"); } } // result return sort1 && sort2 && details; } template const CLogCategoryList &CInterpolator::getLogCategories() { static const CLogCategoryList cats { CLogCategory::interpolator() }; return cats; } template CAircraftSituation CInterpolator::getInterpolatedSituation( qint64 currentTimeMsSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup, CInterpolationStatus &status) { Q_ASSERT_X(!m_callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign"); // this code is used by linear and spline interpolator status.reset(); SituationLog log; const bool doLogging = this->hasAttachedLogger() && setup.logInterpolation(); // any data at all? const CAircraftSituationList situations = this->remoteAircraftSituations(m_callsign); if (situations.isEmpty()) { return CAircraftSituation(m_callsign); } CAircraftSituation currentSituation = m_lastInterpolation.isNull() ? situations.front() : m_lastInterpolation; if (currentSituation.getCallsign() != m_callsign) { BLACK_VERIFY_X(false, Q_FUNC_INFO, "Wrong callsign"); currentSituation.setCallsign(m_callsign); } // set elevation if available if (!currentSituation.hasGroundElevation()) { const CElevationPlane currentGroundElevation = this->findClosestElevationWithinRange(currentSituation, currentSituation.getDistancePerTime(1000)); currentSituation.setGroundElevationChecked(currentGroundElevation); // set as default } // fetch CG once if (m_cg.isNull()) { m_cg = this->getCG(m_callsign); } // data, split situations by time if (currentTimeMsSinceEpoc < 0) { currentTimeMsSinceEpoc = QDateTime::currentMSecsSinceEpoch(); } // interpolant function from derived class // CInterpolatorLinear::Interpolant or CInterpolatorSpline::Interpolant const auto interpolant = derived()->getInterpolant(currentTimeMsSinceEpoc, setup, status, log); // succeeded so far? if (!status.isInterpolated()) { status.checkIfValidSituation(currentSituation); return currentSituation; } // Pitch bank heading // first, so follow up steps could use those values const auto pbh = interpolant.pbh(); currentSituation.setHeading(pbh.getHeading()); currentSituation.setPitch(pbh.getPitch()); currentSituation.setBank(pbh.getBank()); currentSituation.setGroundSpeed(pbh.getGroundSpeed()); // use derived interpolant function currentSituation = interpolant.interpolatePositionAndAltitude(currentSituation); // correct itself const CAircraftSituation::AltitudeCorrection altCorrection = currentSituation.correctAltitude(m_cg, true); status.setInterpolatedAndCheckSituation(true, currentSituation); // logging if (doLogging) { static const QString elv("found %1 missed %2"); const QPair elvStats = this->getElevationsFoundMissed(); log.tsCurrent = currentTimeMsSinceEpoc; log.callsign = m_callsign; log.groundFactor = currentSituation.getOnGroundFactor(); log.altCorrection = CAircraftSituation::altitudeCorrectionToString(altCorrection); log.situationCurrent = currentSituation; log.usedSetup = setup; log.elevationInfo = elv.arg(elvStats.first).arg(elvStats.second); log.cgAboveGround = m_cg; m_logger->logInterpolation(log); } // bye m_lastInterpolation = currentSituation; return currentSituation; } CHeading CInterpolatorPbh::getHeading() const { // HINT: VTOL aircraft can change pitch/bank without changing position, planes cannot // Interpolate heading: HDG = (HdgB - HdgA) * t + HdgA const CHeading headingBegin = m_oldSituation.getHeading(); CHeading headingEnd = m_newSituation.getHeading(); if ((headingEnd - headingBegin).value(CAngleUnit::deg()) < -180) { headingEnd += CHeading(360, CHeading::Magnetic, CAngleUnit::deg()); } if ((headingEnd - headingBegin).value(CAngleUnit::deg()) > 180) { headingEnd -= CHeading(360, CHeading::Magnetic, CAngleUnit::deg()); } return CHeading((headingEnd - headingBegin) * m_simulationTimeFraction + headingBegin, headingBegin.getReferenceNorth()); } CAngle CInterpolatorPbh::getPitch() const { // Interpolate Pitch: Pitch = (PitchB - PitchA) * t + PitchA const CAngle pitchBegin = m_oldSituation.getPitch(); const CAngle pitchEnd = m_newSituation.getPitch(); const CAngle pitch = (pitchEnd - pitchBegin) * m_simulationTimeFraction + pitchBegin; return pitch; } CAngle CInterpolatorPbh::getBank() const { // Interpolate bank: Bank = (BankB - BankA) * t + BankA const CAngle bankBegin = m_oldSituation.getBank(); const CAngle bankEnd = m_newSituation.getBank(); const CAngle bank = (bankEnd - bankBegin) * m_simulationTimeFraction + bankBegin; return bank; } CSpeed CInterpolatorPbh::getGroundSpeed() const { return (m_newSituation.getGroundSpeed() - m_oldSituation.getGroundSpeed()) * m_simulationTimeFraction + m_oldSituation.getGroundSpeed(); } template CAircraftParts CInterpolator::getInterpolatedParts( qint64 currentTimeMsSinceEpoch, const CInterpolationAndRenderingSetupPerCallsign &setup, CPartsStatus &partsStatus, bool log) const { // (!) this code is used by linear and spline interpolator Q_UNUSED(setup); partsStatus.reset(); if (currentTimeMsSinceEpoch < 0) { currentTimeMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); } // Parts are supposed to be in correct order, latest first const CAircraftPartsList validParts = this->remoteAircraftParts(m_callsign); // log for empty parts aircraft parts if (validParts.isEmpty()) { static const CAircraftParts emptyParts; this->logParts(currentTimeMsSinceEpoch, emptyParts, validParts.size(), true, log); return emptyParts; } partsStatus.setSupportsParts(true); CAircraftParts currentParts; do { // find the first parts earlier than the current time const auto pivot = std::partition_point(validParts.begin(), validParts.end(), [ = ](auto &&p) { return p.getAdjustedMSecsSinceEpoch() > currentTimeMsSinceEpoch; }); const auto partsNewer = makeRange(validParts.begin(), pivot).reverse(); const auto partsOlder = makeRange(pivot, validParts.end()); // if (partsOlder.isEmpty()) { currentParts = *(partsNewer.end() - 1); break; } if (partsOlder.isEmpty()) { currentParts = *(partsNewer.begin()); break; } currentParts = partsOlder.front(); // latest older parts } while (false); this->logParts(currentTimeMsSinceEpoch, currentParts, validParts.size(), false, log); return currentParts; } template CAircraftParts CInterpolator::getInterpolatedOrGuessedParts(qint64 currentTimeMsSinceEpoch, const CInterpolationAndRenderingSetupPerCallsign &setup, CPartsStatus &partsStatus, bool log) const { CAircraftParts parts = this->getInterpolatedParts(currentTimeMsSinceEpoch, setup, partsStatus, log); if (!partsStatus.isSupportingParts()) { if (!m_model.hasModelString()) { const CSimulatedAircraft aircraft = this->getAircraftInRangeForCallsign(m_callsign); // m_model = aircraft.getModel(); } // check if model has been thru model matching if (m_model.hasModelString()) { parts.guessParts(this->getLastInterpolatedSituation(), m_model.isVtol(), m_model.getEngineCount()); } else { // default guess parts.guessParts(this->getLastInterpolatedSituation()); } } this->logParts(currentTimeMsSinceEpoch, parts, 0, false, log); return parts; } template void CInterpolator::logParts(qint64 timestamp, const CAircraftParts &parts, int partsNo, bool empty, bool log) const { if (!log || !m_logger) { return; } PartsLog logInfo; logInfo.callsign = m_callsign; logInfo.noNetworkParts = partsNo; logInfo.tsCurrent = timestamp; logInfo.parts = parts; logInfo.empty = empty; m_logger->logParts(logInfo); } template QString CInterpolator::getInterpolatorInfo() const { return QStringLiteral("Callsign: ") % m_callsign.asString() % QStringLiteral(" situations: ") % QString::number(this->remoteAircraftSituationsCount(m_callsign)) % QStringLiteral(" parts: ") % QString::number(this->remoteAircraftPartsCount(m_callsign)) % QStringLiteral(" 1st interpolation: ") % boolToYesNo(m_lastInterpolation.isNull()); } template void CInterpolator::resetLastInterpolation() { m_lastInterpolation.setNull(); } template void CInterpolator::clear() { this->resetLastInterpolation(); m_model = CAircraftModel(); } template void CInterpolator::initCorrespondingModel(const CAircraftModel &model) { if (model.hasModelString()) { m_model = model; } else { CAircraftModel model = this->getAircraftInRangeForCallsign(m_callsign).getModel(); if (model.hasModelString()) { m_model = model; } } } void CInterpolationStatus::setInterpolatedAndCheckSituation(bool succeeded, const CAircraftSituation &situation) { m_isInterpolated = succeeded; this->checkIfValidSituation(situation); } void CInterpolationStatus::checkIfValidSituation(const CAircraftSituation &situation) { m_isValidSituation = !situation.isGeodeticHeightNull() && !situation.isPositionNull(); } bool CInterpolationStatus::hasValidInterpolatedSituation() const { return m_isInterpolated && m_isValidSituation; } void CInterpolationStatus::reset() { m_isValidSituation = false; m_isInterpolated = false; } QString CInterpolationStatus::toQString() const { return QStringLiteral("Interpolated: ") % boolToYesNo(m_isInterpolated) % QStringLiteral(" | situation valid: ") % boolToYesNo(m_isValidSituation); } bool CPartsStatus::allTrue() const { return m_supportsParts; } void CPartsStatus::reset() { m_supportsParts = false; } // see here for the reason of thess forward instantiations // https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl //! \cond PRIVATE template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CInterpolator; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CInterpolator; //! \endcond } // namespace } // namespace