Ref T259, Ref T243 interpolator changes

* interpolators use providers for client/situations/parts/ground elevation
* interpolators do no longer use own situations/parts, but those from provider
* interpolators are no longer QObjects (as it is not needed)
* use gnd flag from situation for gnd interpolation, there is no longer a parts gnd flag interpolation
* guess parts during interpolation
* changed iterators to m_s[i] as it makes clearer which values are used
** the flag is transferred from parts -> situation in airspace monitor
** if the other client already provides and gnd.flag in situation this also works
* adjusted logging
* use providers in unit tests / adjusted tests
* improved situation verification/assert
This commit is contained in:
Klaus Basan
2018-04-06 00:25:30 +02:00
committed by Roland Winklmeier
parent 5c6a37c669
commit cd1ce37ec3
12 changed files with 440 additions and 571 deletions

View File

@@ -310,7 +310,7 @@ namespace BlackMisc
QStringLiteral("<td>") % msSinceEpochToTime(log.tsInterpolated) % QStringLiteral("</td>") %
QStringLiteral("<td>") % QString::number(log.deltaSampleTimesMs) % QStringLiteral("ms</td>") %
QStringLiteral("<td>") % QString::number(log.simulationTimeFraction) % QStringLiteral("</td>");
QStringLiteral("<td>") % QString::number(log.simTimeFraction) % QStringLiteral("</td>");
tableRows +=
QStringLiteral("<td class=\"old\">") % situationOld.latitudeAsString() % QStringLiteral("</td>") %
@@ -376,14 +376,8 @@ namespace BlackMisc
void CInterpolationLogger::clearLog()
{
{
QWriteLocker l(&m_lockSituations);
m_situationLogs.clear();
}
{
QWriteLocker l(&m_lockParts);
m_partsLogs.clear();
}
{ QWriteLocker l(&m_lockSituations); m_situationLogs.clear(); }
{ QWriteLocker l(&m_lockParts); m_partsLogs.clear(); }
}
QString CInterpolationLogger::msSinceEpochToTime(qint64 ms)
@@ -418,6 +412,8 @@ namespace BlackMisc
QStringLiteral("ts: ") % CInterpolationLogger::msSinceEpochToTimeAndTimestamp(tsCurrent) %
QStringLiteral(" | type: ") % this->interpolationType() %
QStringLiteral(" | gnd.fa.: ") % QString::number(groundFactor) %
QStringLiteral(" | CG: ") % cgAboveGround.valueRoundedWithUnit(CLengthUnit::m(), 1) %
QStringLiteral(" | alt.cor.: ") % altCorrection %
QStringLiteral(" | #nw.sit.: ") % QString::number(noNetworkSituations) %
(
withSetup ?
@@ -427,9 +423,7 @@ namespace BlackMisc
(
withElevation ?
separator %
QStringLiteral("Elev.: ") %
QStringLiteral("transf.elv.: ") % QString::number(noTransferredElevations) %
QStringLiteral(" | elv.info: ") % elevationInfo :
QStringLiteral("Elev info.: ") % elevationInfo :
QStringLiteral("")
) %
(
@@ -439,7 +433,7 @@ namespace BlackMisc
QStringLiteral(" | int.time: ") % CInterpolationLogger::msSinceEpochToTimeAndTimestamp(tsInterpolated) %
QStringLiteral(" | dt.cur.int.: ") % QString::number(deltaCurrentToInterpolatedTime()) % QStringLiteral("ms") %
QStringLiteral(" | sample dt: ") % QString::number(deltaSampleTimesMs) % QStringLiteral("ms") %
QStringLiteral(" | fr.[0-1]: ") % QString::number(simulationTimeFraction) %
QStringLiteral(" | fr.[0-1]: ") % QString::number(simTimeFraction) %
QStringLiteral(" | old int.pos.: ") % situationOldInterpolation.getTimestampAndOffset(true) %
QStringLiteral(" | new int.pos.: ") % situationNewInterpolation.getTimestampAndOffset(true) %
QStringLiteral(" | #int.pos.: ") % QString::number(interpolationSituations.size()) :

View File

@@ -35,17 +35,17 @@ namespace BlackMisc
qint64 tsInterpolated = -1; //!< timestamp interpolated
double groundFactor = -1; //!< current ground factor
double vtolAircraft = false; //!< VTOL aircraft
double simulationTimeFraction = -1; //!< time fraction, expected 0..1
double deltaSampleTimesMs = -1; //!< delta time between samples (i.e. 2 situations)
bool useParts = false; //!< supporting aircraft parts
int noNetworkSituations = 0; //!< available network situations
int noTransferredElevations = 0; //!< transferred elevation to n situations
QString elevationInfo; //!< info about elevation retrieval
Aviation::CCallsign callsign; //!< current callsign
Aviation::CAircraftParts parts; //!< corresponding parts used in interpolator
double simTimeFraction = -1; //!< time fraction, expected 0..1
double deltaSampleTimesMs = -1; //!< delta time between samples (i.e. 2 situations)
bool useParts = false; //!< supporting aircraft parts
int noNetworkSituations = 0; //!< available network situations
QString elevationInfo; //!< info about elevation retrieval
QString altCorrection; //!< info about altitude correction as CAircraftSituation::AltitudeCorrection
Aviation::CCallsign callsign; //!< current callsign
Aviation::CAircraftParts parts; //!< corresponding parts used in interpolator
Aviation::CAircraftSituationList interpolationSituations; //!< the interpolator uses 2, 3 situations (oldest at end)
Aviation::CAircraftSituation situationCurrent; //!< interpolated situation
PhysicalQuantities::CLength cgAboveGround; //!< center of gravity
PhysicalQuantities::CLength cgAboveGround; //!< center of gravity
CInterpolationAndRenderingSetupPerCallsign usedSetup; //!< used setup
//! Delta time between interpolation and current time

View File

@@ -20,27 +20,71 @@
#include "blackmisc/pq/length.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/verify.h"
#include <QTimer>
#include <QDateTime>
#include <QStringBuilder>
using namespace BlackConfig;
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Geo;
using namespace BlackMisc::Math;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackMisc::Simulation;
namespace BlackMisc
{
namespace Simulation
{
template <typename Derived>
CInterpolator<Derived>::CInterpolator(const QString &objectName, const CCallsign &callsign, QObject *parent) :
QObject(parent),
m_callsign(callsign)
CInterpolator<Derived>::CInterpolator(const CCallsign &callsign,
ISimulationEnvironmentProvider *simEnvProvider, IInterpolationSetupProvider *setupProvider,
IRemoteAircraftProvider *remoteAircraftProvider,
CInterpolationLogger *logger) : m_callsign(callsign)
{
this->setObjectName(objectName + " for " + callsign.asString());
// 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<typename Derived>
bool CInterpolator<Derived>::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 <typename Derived>
@@ -60,28 +104,28 @@ namespace BlackMisc
// this code is used by linear and spline interpolator
status.reset();
const bool doLogging = this->hasAttachedLogger() && setup.logInterpolation();
SituationLog log;
SituationLog *logP = doLogging ? &log : nullptr;
const bool doLogging = this->hasAttachedLogger() && setup.logInterpolation();
// any data at all?
if (m_aircraftSituations.isEmpty()) { return CAircraftSituation(m_callsign); }
CAircraftSituation currentSituation = m_lastInterpolation.isNull() ? m_aircraftSituations.front() : m_lastInterpolation;
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);
}
//! \todo KB 2018-03 ground flag refactoring
// Update current position by hints' elevation
// * for XP provided by hints.getElevationProvider at current position
// * for FSX/P3D provided as hints.getElevation which is set to current position of remote aircraft in simulator
// * As XP uses lazy init we will call getGroundElevation only when needed
// * default here via getElevationPlane
// CAltitude currentGroundElevation(hints.getGroundElevation(currentSituation, currentSituation.getDistancePerTime(1000), true, false, logP));
const CElevationPlane currentGroundElevation = this->findClosestElevationWithinRange(currentSituation, currentSituation.getDistancePerTime(1000));
currentSituation.setGroundElevationChecked(currentGroundElevation); // set as default
// 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(); }
@@ -97,75 +141,34 @@ namespace BlackMisc
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.setPosition(interpolant.interpolatePosition(setup));
currentSituation.setAltitude(interpolant.interpolateAltitude(setup));
currentSituation.setMSecsSinceEpoch(interpolant.getInterpolatedTime());
currentSituation = interpolant.interpolatePositionAndAltitude(currentSituation);
// PBH before ground so we can use PBH in guessing ground
if (setup.isForcingFullInterpolation() || status.isInterpolated())
{
const auto pbh = interpolant.pbh();
currentSituation.setHeading(pbh.getHeading());
currentSituation.setPitch(pbh.getPitch());
currentSituation.setBank(pbh.getBank());
currentSituation.setGroundSpeed(pbh.getGroundSpeed());
status.setInterpolatedAndCheckSituation(true, currentSituation);
}
// Interpolate between altitude and ground elevation, with proportions weighted according to interpolated onGround flag
constexpr double NoGroundFactor = -1;
double groundFactor = NoGroundFactor;
if (setup.isAircraftPartsEnabled())
{
// groundFactor = hints.getAircraftParts().isOnGroundInterpolated();
if (groundFactor > 0.0)
{
// if not having an ground elevation yet, we fetch from provider (if there is a provider)
if (!currentGroundElevation.isNull())
{
currentGroundElevation = hints.getGroundElevation(currentSituation, true, false, logP); // "expensive on XPlane" if provider is called
}
if (!currentGroundElevation.isNull())
{
Q_ASSERT_X(currentGroundElevation.getAltitude().getReferenceDatum() == CAltitude::MeanSeaLevel, Q_FUNC_INFO, "Need MSL value");
const CLength cg = this->getCG(m_callsign);
const CAltitude groundElevationCG = currentGroundElevation.getAltitude().withOffset(cg);
currentSituation.setGroundElevationChecked(currentGroundElevation);
// alt = ground + aboveGround * groundFactor
// = ground + (altitude - ground) * groundFactor
// = ground (1 - groundFactor) + altitude * groundFactor
currentSituation.setAltitude(CAltitude(currentSituation.getAltitude() * (1.0 - groundFactor) +
groundElevationCG * groundFactor,
CAltitude::MeanSeaLevel));
}
}
}
// depending on ground factor set ground flag and reliability
// it will use the hints ground flag or elevation/CG or guessing
// CInterpolator::setGroundFlagFromInterpolator(hints, groundFactor, currentSituation);
// we transfer ground elevation for future usage
if (currentSituation.hasGroundElevation())
{
CElevationPlane ep(currentSituation);
ep.setSinglePointRadius();
// transfer to newer situations
log.noTransferredElevations = m_aircraftSituations.setGroundElevationChecked(ep, currentTimeMsSinceEpoc);
}
// 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<int, int> elvStats = this->getElevationsFoundMissed();
log.tsCurrent = currentTimeMsSinceEpoc;
log.callsign = m_callsign;
log.groundFactor = groundFactor;
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);
}
@@ -223,163 +226,94 @@ namespace BlackMisc
}
template <typename Derived>
CAircraftParts CInterpolator<Derived>::getInterpolatedParts(qint64 currentTimeMsSinceEpoch,
const CInterpolationAndRenderingSetupPerCallsign &setup, CPartsStatus &partsStatus, bool log) const
CAircraftParts CInterpolator<Derived>::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 (m_aircraftParts.isEmpty())
if (validParts.isEmpty())
{
static const CAircraftParts emptyParts;
this->logParts(currentTimeMsSinceEpoch, emptyParts, true, log);
this->logParts(currentTimeMsSinceEpoch, emptyParts, validParts.size(), true, log);
return emptyParts;
}
// Parts are supposed to be in correct order, latest first
const CAircraftPartsList &validParts = m_aircraftParts;
// stop if we don't have any parts
if (validParts.isEmpty()) { return {}; }
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 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
if (currentParts.isOnGround()) { break; }
// here we know aircraft is not on ground, and we check if it was recently on ground or if it will be on ground soon
const auto latestTakeoff = std::adjacent_find(partsOlder.begin(), partsOlder.end(), [](auto &&, auto && p) { return p.isOnGround(); });
const auto soonestLanding = std::find_if(partsNewer.begin(), partsNewer.end(), [](auto && p) { return p.isOnGround(); });
// maxSecs is the maximum effective value of `secondsSinceTakeoff` and `secondsUntilLanding`. If `secondsSinceTakeoff > significantPast` then `takeoffFactor > 1`
// and if `secondsUntilLanding > predictableFuture` then `landingFactor > 1`, and `std::min(std::min(takeoffFactor, landingFactor), 1.0)` ensures `>1` is ignored.
// but if the offset < 5s then we must use a smaller value for the landing, hence `std::min(max, static_cast<double>(soonestLanding->getTimeOffsetMs()) / 1000.0)`.
const double maxSecs = 5.0; // preferred length of time over which to blend the onground flag, when possible
// our clairvoyance is limited by the time offset (all times here in seconds)
const double significantPastSecs = maxSecs;
const double predictableFutureSecs = soonestLanding == partsNewer.end() ? maxSecs : std::min(maxSecs, static_cast<double>(soonestLanding->getTimeOffsetMs()) / 1000.0);
const double secondsSinceTakeoff = latestTakeoff == partsOlder.end() ? maxSecs : (currentTimeMsSinceEpoch - latestTakeoff->getAdjustedMSecsSinceEpoch()) / 1000.0;
const double secondsUntilLanding = soonestLanding == partsNewer.end() ? maxSecs : (soonestLanding->getAdjustedMSecsSinceEpoch() - currentTimeMsSinceEpoch) / 1000.0;
Q_ASSERT(secondsSinceTakeoff >= 0.0);
Q_ASSERT(secondsUntilLanding >= 0.0);
//! \fixme In future, will we need to be able to support time offsets of zero?
BLACK_VERIFY(predictableFutureSecs != 0);
if (predictableFutureSecs == 0) { break; } // avoid divide by zero
const double takeoffFactor = secondsSinceTakeoff / significantPastSecs;
const double landingFactor = secondsUntilLanding / predictableFutureSecs;
const double airborneFactor = std::min(std::min(takeoffFactor, landingFactor), 1.0);
currentParts.setOnGroundInterpolated(1.0 - smootherStep(airborneFactor));
}
while (false);
this->logParts(currentTimeMsSinceEpoch, currentParts, false, log);
this->logParts(currentTimeMsSinceEpoch, currentParts, validParts.size(), false, log);
return currentParts;
}
template<typename Derived>
void CInterpolator<Derived>::logParts(qint64 timestamp, const CAircraftParts &parts, bool empty, bool log) const
CAircraftParts CInterpolator<Derived>::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<typename Derived>
void CInterpolator<Derived>::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 = m_aircraftParts.size();
logInfo.noNetworkParts = partsNo;
logInfo.tsCurrent = timestamp;
logInfo.parts = parts;
logInfo.empty = empty;
m_logger->logParts(logInfo);
}
template <typename Derived>
void CInterpolator<Derived>::addAircraftSituation(const CAircraftSituation &situation)
{
Q_ASSERT_X(!m_callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign");
Q_ASSERT_X(situation.getCallsign() == m_callsign, Q_FUNC_INFO, "Wrong callsign");
if (m_aircraftSituations.isEmpty())
{
this->resetLastInterpolation(); // delete any leftover
// make sure we have enough situations to do start interpolating immediately without waiting for more updates
// the offsets here (addMSecs) do not really matter
CAircraftSituation copy(situation);
copy.addMsecs(-2 * IRemoteAircraftProvider::DefaultOffsetTimeMs);
m_aircraftSituations.push_frontKeepLatestFirst(copy);
copy.addMsecs(IRemoteAircraftProvider::DefaultOffsetTimeMs);
m_aircraftSituations.push_frontKeepLatestFirst(copy);
}
// we add new situations at front and keep the latest values (real time) first
m_aircraftSituations.push_frontKeepLatestFirstAdjustOffset(situation, IRemoteAircraftProvider::MaxSituationsPerCallsign);
// with the latest updates of T243 the order and the offsets are supposed to be correct
// so even mixing fast/slow updates shall work
Q_ASSERT_X(!m_aircraftSituations.containsZeroOrNegativeOffsetTime(), Q_FUNC_INFO, "Missing offset time");
Q_ASSERT_X(m_aircraftSituations.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
}
template <typename Derived>
void CInterpolator<Derived>::addAircraftParts(const CAircraftParts &parts, bool adjustZeroOffset)
{
const bool adjustOffset = adjustZeroOffset && !parts.hasNonZeroOffsetTime();
if (adjustOffset)
{
const qint64 offset = m_aircraftSituations.isEmpty() ? IRemoteAircraftProvider::DefaultOffsetTimeMs : m_aircraftSituations.front().getTimeOffsetMs();
CAircraftParts partsCopy(parts);
partsCopy.setTimeOffsetMs(offset); // we set the offset of the situation
CInterpolator<Derived>::addAircraftParts(partsCopy);
return;
}
// here we have an offset
// unlike situations we do not add parts for spline interpolation
// this is not needed, as parts do not need 3 values
Q_ASSERT_X(!adjustZeroOffset || parts.hasNonZeroOffsetTime(), Q_FUNC_INFO, "Missing parts offset");
// we add new situations at front and keep the latest values (real time) first
m_aircraftParts.push_frontKeepLatestFirstAdjustOffset(parts, IRemoteAircraftProvider::MaxSituationsPerCallsign);
// force remote provider to cleanup
IRemoteAircraftProvider::removeOutdatedParts(m_aircraftParts);
// with the latest updates of T243 the order and the offsets are supposed to be correct
// so even mixing fast/slow updates shall work
Q_ASSERT_X(adjustZeroOffset ? !m_aircraftParts.containsZeroOrNegativeOffsetTime() : !m_aircraftParts.containsNegativeOffsetTime(), Q_FUNC_INFO, "Missing offset time");
Q_ASSERT_X(m_aircraftParts.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
}
template<typename Derived>
void CInterpolator<Derived>::addAircraftParts(const CAircraftPartsList &parts, bool adjustZeroOffset)
{
for (const CAircraftParts &p : parts)
{
this->addAircraftParts(p, adjustZeroOffset);
}
}
template<typename Derived>
QString CInterpolator<Derived>::getInterpolatorInfo() const
{
return QStringLiteral("Callsign: ") %
m_callsign.asString() %
QStringLiteral(" situations: ") %
QString::number(m_aircraftSituations.size()) %
QString::number(this->remoteAircraftSituationsCount(m_callsign)) %
QStringLiteral(" parts: ") %
QString::number(m_aircraftParts.size()) %
QString::number(this->remoteAircraftPartsCount(m_callsign)) %
QStringLiteral(" 1st interpolation: ") %
boolToYesNo(m_lastInterpolation.isNull());
}
@@ -394,92 +328,24 @@ namespace BlackMisc
void CInterpolator<Derived>::clear()
{
this->resetLastInterpolation();
m_aircraftParts.clear();
m_aircraftSituations.clear();
m_model = CAircraftModel();
}
template<typename Derived>
int CInterpolator<Derived>::maxSituations() const
void CInterpolator<Derived>::initCorrespondingModel(const CAircraftModel &model)
{
return IRemoteAircraftProvider::MaxSituationsPerCallsign;
}
template<typename Derived>
int CInterpolator<Derived>::maxParts() const
{
return IRemoteAircraftProvider::MaxSituationsPerCallsign;
}
template <typename Derived>
void CInterpolator<Derived>::setGroundFlagFromInterpolator(double groundFactor, CAircraftSituation &situation) const
{
// by interpolation
if (groundFactor >= 1.0)
if (model.hasModelString())
{
situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByInterpolation);
return;
m_model = model;
}
if (groundFactor < 1.0 && groundFactor >= 0.0)
else
{
situation.setOnGround(CAircraftSituation::NotOnGround, CAircraftSituation::OnGroundByInterpolation);
return;
CAircraftModel model = this->getAircraftInRangeForCallsign(m_callsign).getModel();
if (model.hasModelString())
{
m_model = model;
}
}
// on elevation and CG
// remark: to some extend redundant as situation.getCorrectedAltitude() already corrects altitude
Q_ASSERT_X(!m_callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
if (situation.hasGroundElevation())
{
static const CLength onGroundThresholdLimit(1.0, CLengthUnit::m());
static const CLength notOnGroundThresholdLimit(10.0, CLengthUnit::m()); // upper boundary
CLength offset = onGroundThresholdLimit; // very small offset from allowed
CAircraftSituation::OnGroundDetails reliability = CAircraftSituation::OnGroundByElevation;
CLength cg = this->getCG(m_callsign);
if (!cg.isNull())
{
offset += cg;
reliability = CAircraftSituation::OnGroundByElevationAndCG;
}
else
{
// increase offset a bit
offset += CLength(1.0, CLengthUnit::m());
}
Q_ASSERT_X(situation.getGroundElevation().getReferenceDatum() == CAltitude::MeanSeaLevel, Q_FUNC_INFO, "Need MSL elevation");
if (situation.getHeightAboveGround() <= offset)
{
// lower boundary underflow, we can tell we are on ground
const CAircraftSituation::IsOnGround og = CAircraftSituation::OnGround;
situation.setOnGround(og, reliability);
return; // for underflow we can stop here
}
else if (situation.getHeightAboveGround() >= notOnGroundThresholdLimit)
{
// upper boundary
const CAircraftSituation::IsOnGround og = CAircraftSituation::NotOnGround;
situation.setOnGround(og, reliability);
return;
}
// within an interval were we cannot really tell and continue
}
// for VTOL aircraft we give up
if (this->isVtolAircraft(m_callsign))
{
situation.setOnGround(CAircraftSituation::OnGroundSituationUnknown, CAircraftSituation::OnGroundReliabilityNoSet);
return;
}
// we guess on speed, pitch and bank by excluding situations
situation.setOnGround(CAircraftSituation::NotOnGround, CAircraftSituation::OnGroundByGuessing);
if (qAbs(situation.getPitch().value(CAngleUnit::deg())) > 10) { return; }
if (qAbs(situation.getBank().value(CAngleUnit::deg())) > 10) { return; }
if (situation.getGroundSpeed().value(CSpeedUnit::km_h()) > 50) { return; }
// not sure, but this is a guess
situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing);
}
void CInterpolationStatus::setInterpolatedAndCheckSituation(bool succeeded, const CAircraftSituation &situation)
@@ -528,45 +394,3 @@ namespace BlackMisc
//! \endcond
} // namespace
} // namespace
/**
template <typename Derived>
void CInterpolator<Derived>::addAircraftParts(const CAircraftParts &parts, bool adjustZeroOffset)
{
const bool adjustOffset = adjustZeroOffset && !parts.hasNonZeroOffsetTime();
if (adjustOffset)
{
const qint64 offset = m_aircraftSituations.isEmpty() ? IRemoteAircraftProvider::DefaultOffsetTimeMs : m_aircraftSituations.front().getTimeOffsetMs();
CAircraftParts partsCopy(parts);
partsCopy.setTimeOffsetMs(offset); // we set the offset of the situation
CInterpolator<Derived>::addAircraftParts(partsCopy);
return;
}
// here we have an offset
Q_ASSERT_X(!adjustZeroOffset || parts.hasNonZeroOffsetTime(), Q_FUNC_INFO, "Missing parts offset");
if (m_aircraftParts.isEmpty())
{
// make sure we have enough parts to do start interpolating immediately without waiting for more updates
// the offsets here (addMSecs) do not really matter
const qint64 minOffset = 100;
CAircraftParts copy(parts);
copy.addMsecs(-2 * std::max(parts.getTimeOffsetMs(), minOffset));
m_aircraftParts.push_frontKeepLatestFirstAdjustOffset(copy);
copy.addMsecs(parts.getTimeOffsetMs());
m_aircraftParts.push_frontKeepLatestFirstAdjustOffset(copy);
}
// we add new situations at front and keep the latest values (real time) first
m_aircraftParts.push_frontKeepLatestFirstAdjustOffset(parts, IRemoteAircraftProvider::MaxSituationsPerCallsign);
// force remote provider to cleanup
IRemoteAircraftProvider::removeOutdatedParts(m_aircraftParts);
// with the latest updates of T243 the order and the offsets are supposed to be correct
// so even mixing fast/slow updates shall work
Q_ASSERT_X(adjustZeroOffset ? !m_aircraftParts.containsZeroOrNegativeOffsetTime() : !m_aircraftParts.containsNegativeOffsetTime(), Q_FUNC_INFO, "Missing offset time");
Q_ASSERT_X(m_aircraftParts.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
}
**/

View File

@@ -16,9 +16,11 @@
#include "blackmisc/simulation/remoteaircraftprovider.h"
#include "blackmisc/simulation/interpolationsetupprovider.h"
#include "blackmisc/simulation/simulationenvironmentprovider.h"
#include "blackmisc/simulation/aircraftmodel.h"
#include "blackmisc/aviation/aircraftpartslist.h"
#include "blackmisc/aviation/aircraftsituation.h"
#include "blackmisc/aviation/aircraftpartslist.h"
#include "blackmisc/aviation/callsign.h"
#include "blackmisc/logcategorylist.h"
#include <QObject>
@@ -27,8 +29,6 @@
namespace BlackMisc
{
class CWorker;
namespace Aviation { class CCallsign; }
namespace Simulation
{
class CInterpolationLogger;
@@ -42,8 +42,7 @@ namespace BlackMisc
class CInterpolator :
public CSimulationEnvironmentAware,
public CInterpolationSetupAware,
public CRemoteAircraftAware,
public QObject
public CRemoteAircraftAware
{
public:
//! Log categories
@@ -52,25 +51,14 @@ namespace BlackMisc
//! Current interpolated situation
Aviation::CAircraftSituation getInterpolatedSituation(qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup, CInterpolationStatus &status);
//! Parts before given offset time (aka pending parts)
Aviation::CAircraftParts getInterpolatedParts(
qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup, CPartsStatus &partsStatus, bool log = false) const;
//! Parts before given offset time
Aviation::CAircraftParts getInterpolatedParts(qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup, CPartsStatus &partsStatus, bool log = false) const;
//! Add a new aircraft situation
void addAircraftSituation(const Aviation::CAircraftSituation &situation);
//! Interpolated parts, if not available guessed parts
Aviation::CAircraftParts getInterpolatedOrGuessedParts(qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup, CPartsStatus &partsStatus, bool log = false) const;
//! Any aircraft situations?
bool hasAircraftSituations() const { return !m_aircraftSituations.isEmpty(); }
//! Add a new aircraft parts
void addAircraftParts(const Aviation::CAircraftParts &parts, bool adjustZeroOffset = true);
//! Add a new aircraft parts
//! \remark mainly needed in unit tests
void addAircraftParts(const Aviation::CAircraftPartsList &parts, bool adjustZeroOffset = true);
//! Any aircraft parts?
bool hasAircraftParts() const { return !m_aircraftParts.isEmpty(); }
//! Latest interpolation result
const Aviation::CAircraftSituation &getLastInterpolatedSituation() const { return m_lastInterpolation; }
//! Takes input between 0 and 1 and returns output between 0 and 1 smoothed with an S-shaped curve.
//!
@@ -97,32 +85,62 @@ namespace BlackMisc
void resetLastInterpolation();
//! Clear all data
//! \remark mainly needed in interpolation
//! \remark mainly needed in UNIT tests
void clear();
//! Max situations kept
int maxSituations() const;
//! Max parts kept
int maxParts() const;
//! Init, or re-init the corressponding model
//! \remark either by passing a model or using the provider
void initCorrespondingModel(const CAircraftModel &model = {});
protected:
Aviation::CAircraftSituationList m_aircraftSituations; //!< recent situations for one aircraft
Aviation::CAircraftPartsList m_aircraftParts; //!< recent parts for one aircraft
Aviation::CCallsign m_callsign; //!< callsign
Aviation::CAircraftSituation m_lastInterpolation; //!< last interpolation
//! Constructor
CInterpolator(const QString &objectName, const Aviation::CCallsign &callsign, QObject *parent);
CInterpolator(const Aviation::CCallsign &callsign,
ISimulationEnvironmentProvider *simEnvProvider, IInterpolationSetupProvider *setupProvider, IRemoteAircraftProvider *p3,
CInterpolationLogger *logger);
//! Set on ground flag
void setGroundFlagFromInterpolator(double groundFactor, Aviation::CAircraftSituation &situation) const;
const Aviation::CCallsign m_callsign; //!< corresponding callsign
PhysicalQuantities::CLength m_cg { 0, nullptr } ; //!< fetched once, stays constant
Aviation::CAircraftSituation m_lastInterpolation { Aviation::CAircraftSituation::null() }; //!< latest interpolation
CAircraftModel m_model; //!< corresponding model
//! Equal double values?
static bool doubleEpsilonEqual(double d1, double d2)
{
return qAbs(d1 - d2) < std::numeric_limits<double>::epsilon();
}
//! Both on ground
static bool gfEqualOnGround(double oldGroundFactor, double newGroundFactor)
{
return doubleEpsilonEqual(1.0, oldGroundFactor) && doubleEpsilonEqual(1.0, newGroundFactor);
}
//! Both not on ground
static bool gfEqualAirborne(double oldGroundFactor, double newGroundFactor)
{
return doubleEpsilonEqual(0.0, oldGroundFactor) && doubleEpsilonEqual(0.0, newGroundFactor);
}
//! Plane is starting
static bool gfStarting(double oldGroundFactor, double newGroundFactor)
{
return doubleEpsilonEqual(0.0, oldGroundFactor) && doubleEpsilonEqual(1.0, newGroundFactor);
}
//! Plane is landing
static bool gfLanding(double oldGroundFactor, double newGroundFactor)
{
return doubleEpsilonEqual(1.0, oldGroundFactor) && doubleEpsilonEqual(0.0, newGroundFactor);
}
//! Verify gnd flag, times, ... true means "OK"
bool verifyInterpolationSituations(const Aviation::CAircraftSituation &oldest, const Aviation::CAircraftSituation &newer, const Aviation::CAircraftSituation &latest, const CInterpolationAndRenderingSetupPerCallsign &setup);
private:
CInterpolationLogger *m_logger = nullptr;
//! Log parts
void logParts(qint64 timestamp, const Aviation::CAircraftParts &parts, bool empty, bool log) const;
void logParts(qint64 timestamp, const Aviation::CAircraftParts &parts, int partsNo, bool empty, bool log) const;
Derived *derived() { return static_cast<Derived *>(this); }
const Derived *derived() const { return static_cast<const Derived *>(this); }
@@ -134,14 +152,9 @@ namespace BlackMisc
public:
//! Constructor
//! @{
CInterpolatorPbh()
{}
CInterpolatorPbh(const Aviation::CAircraftSituation &older, const Aviation::CAircraftSituation &newer) :
m_oldSituation(older), m_newSituation(newer)
{}
CInterpolatorPbh(double time, const Aviation::CAircraftSituation &older, const Aviation::CAircraftSituation &newer) :
m_simulationTimeFraction(time), m_oldSituation(older), m_newSituation(newer)
{}
CInterpolatorPbh() {}
CInterpolatorPbh(const Aviation::CAircraftSituation &older, const Aviation::CAircraftSituation &newer) : m_oldSituation(older), m_newSituation(newer) {}
CInterpolatorPbh(double time, const Aviation::CAircraftSituation &older, const Aviation::CAircraftSituation &newer) : m_simulationTimeFraction(time), m_oldSituation(older), m_newSituation(newer) {}
//! @}
//! Getter

View File

@@ -8,7 +8,6 @@
*/
#include "interpolatorlinear.h"
#include "blackmisc/aviation/aircraftsituation.h"
#include "blackmisc/aviation/aircraftsituationlist.h"
#include "blackmisc/aviation/altitude.h"
#include "blackmisc/geo/coordinategeodetic.h"
@@ -48,6 +47,46 @@ namespace BlackMisc
m_pbh(m_simulationTimeFraction, situation1, situation2)
{}
CAircraftSituation CInterpolatorLinear::Interpolant::interpolatePositionAndAltitude(const CAircraftSituation &situation) const
{
const std::array<double, 3> oldVec(m_oldSituation.getPosition().normalVectorDouble());
const std::array<double, 3> newVec(m_newSituation.getPosition().normalVectorDouble());
// Interpolate position: pos = (posB - posA) * t + posA
CCoordinateGeodetic newPosition;
newPosition.setNormalVector((newVec[0] - oldVec[0]) * m_simulationTimeFraction + oldVec[0],
(newVec[1] - oldVec[1]) * m_simulationTimeFraction + oldVec[1],
(newVec[2] - oldVec[2]) * m_simulationTimeFraction + oldVec[2]);
// Interpolate altitude: Alt = (AltB - AltA) * t + AltA
// avoid underflow below ground elevation by using getCorrectedAltitude
const CAltitude oldAlt(m_oldSituation.getCorrectedAltitude());
const CAltitude newAlt(m_newSituation.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)
* m_simulationTimeFraction
+ oldAlt,
oldAlt.getReferenceDatum());
CAircraftSituation newSituation(situation);
newSituation.setPosition(newPosition);
newSituation.setAltitude(altitude);
newSituation.setMSecsSinceEpoch(this->getInterpolatedTime());
const double oldGroundFactor = m_oldSituation.getOnGroundFactor();
const double newGroundFactor = m_newSituation.getOnGroundFactor();
do
{
if (gfEqualAirborne(oldGroundFactor, newGroundFactor)) { newSituation.setOnGround(false); break; }
if (gfEqualOnGround(oldGroundFactor, newGroundFactor)) { newSituation.setOnGround(true); break; }
const double groundFactor = (newGroundFactor - oldGroundFactor) * m_simulationTimeFraction + oldGroundFactor;
newSituation.setOnGroundFactor(groundFactor);
newSituation.setOnGroundFromGroundFactorFromInterpolation();
}
while (false);
return newSituation;
}
CInterpolatorLinear::Interpolant CInterpolatorLinear::getInterpolant(
qint64 currentTimeMsSinceEpoc,
const CInterpolationAndRenderingSetupPerCallsign &setup,
@@ -58,16 +97,11 @@ namespace BlackMisc
// with the latest updates of T243 the order and the offsets are supposed to be correct
// so even mixing fast/slow updates shall work
BLACK_VERIFY_X(m_aircraftSituations.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
Q_ASSERT_X(m_aircraftSituations.size() <= IRemoteAircraftProvider::MaxSituationsPerCallsign, Q_FUNC_INFO, "Wrong size");
// Ref T243, KB 2018-02, can be removed in future, we verify situations above
// Situations are supposed to be in correct order
// const auto end = std::is_sorted_until(m_aircraftSituations.begin(), m_aircraftSituations.end(), [](auto && a, auto && b) { return b.getAdjustedMSecsSinceEpoch() < a.getAdjustedMSecsSinceEpoch(); });
// const auto validSituations = makeRange(m_aircraftSituations.begin(), end);
const CAircraftSituationList validSituations = this->remoteAircraftSituations(m_callsign); // if needed, we could also copy here
BLACK_VERIFY_X(validSituations.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
Q_ASSERT_X(validSituations.size() <= IRemoteAircraftProvider::MaxSituationsPerCallsign, Q_FUNC_INFO, "Wrong size");
// find the first situation earlier than the current time
const CAircraftSituationList &validSituations = m_aircraftSituations; // if needed, we could also copy here
const auto pivot = std::partition_point(validSituations.begin(), validSituations.end(), [ = ](auto &&s) { return s.getAdjustedMSecsSinceEpoch() > currentTimeMsSinceEpoc; });
const auto situationsNewer = makeRange(validSituations.begin(), pivot);
const auto situationsOlder = makeRange(pivot, validSituations.end());
@@ -147,7 +181,7 @@ namespace BlackMisc
{
log.tsCurrent = currentTimeMsSinceEpoc;
log.deltaSampleTimesMs = sampleDeltaTimeMs;
log.simulationTimeFraction = simulationTimeFraction;
log.simTimeFraction = simulationTimeFraction;
log.deltaSampleTimesMs = sampleDeltaTimeMs;
log.tsInterpolated = interpolatedTime;
log.interpolationSituations.clear();
@@ -157,35 +191,5 @@ namespace BlackMisc
return { oldSituation, newSituation, simulationTimeFraction, interpolatedTime };
}
CCoordinateGeodetic CInterpolatorLinear::Interpolant::interpolatePosition(const CInterpolationAndRenderingSetupPerCallsign &setup) const
{
Q_UNUSED(setup);
const std::array<double, 3> oldVec(m_oldSituation.getPosition().normalVectorDouble());
const std::array<double, 3> newVec(m_newSituation.getPosition().normalVectorDouble());
// Interpolate position: pos = (posB - posA) * t + posA
CCoordinateGeodetic currentPosition;
currentPosition.setNormalVector((newVec[0] - oldVec[0]) * m_simulationTimeFraction + oldVec[0],
(newVec[1] - oldVec[1]) * m_simulationTimeFraction + oldVec[1],
(newVec[2] - oldVec[2]) * m_simulationTimeFraction + oldVec[2]);
return currentPosition;
}
CAltitude CInterpolatorLinear::Interpolant::interpolateAltitude(const CInterpolationAndRenderingSetupPerCallsign &setup) const
{
Q_UNUSED(setup);
// Interpolate altitude: Alt = (AltB - AltA) * t + AltA
// avoid underflow below ground elevation by using getCorrectedAltitude
const CAltitude oldAlt(m_oldSituation.getCorrectedAltitude());
const CAltitude newAlt(m_newSituation.getCorrectedAltitude());
Q_ASSERT_X(oldAlt.getReferenceDatum() == CAltitude::MeanSeaLevel && oldAlt.getReferenceDatum() == newAlt.getReferenceDatum(), Q_FUNC_INFO, "mismatch in reference"); // otherwise no calculation is possible
return CAltitude((newAlt - oldAlt)
* m_simulationTimeFraction
+ oldAlt,
oldAlt.getReferenceDatum());
}
} // namespace
} // namespace

View File

@@ -29,13 +29,12 @@ namespace BlackMisc
//! Linear interpolator, calculation inbetween positions
class BLACKMISC_EXPORT CInterpolatorLinear : public CInterpolator<CInterpolatorLinear>
{
Q_OBJECT
public:
//! Constructor
CInterpolatorLinear(const BlackMisc::Aviation::CCallsign &callsign, QObject *parent = nullptr) :
CInterpolator("CInterpolatorLinear", callsign, parent)
{}
CInterpolatorLinear(const Aviation::CCallsign &callsign,
ISimulationEnvironmentProvider *p1, IInterpolationSetupProvider *p2, IRemoteAircraftProvider *p3,
CInterpolationLogger *logger = nullptr) :
CInterpolator(callsign, p1, p2, p3, logger) {}
//! Linear function that performs the actual interpolation
class Interpolant
@@ -48,10 +47,7 @@ namespace BlackMisc
//! @}
//! Perform the interpolation
//! @{
Geo::CCoordinateGeodetic interpolatePosition(const CInterpolationAndRenderingSetupPerCallsign &setup) const;
Aviation::CAltitude interpolateAltitude(const CInterpolationAndRenderingSetupPerCallsign &setup) const;
//! @}
Aviation::CAircraftSituation interpolatePositionAndAltitude(const Aviation::CAircraftSituation &situation) const;
//! Interpolator for pitch, bank, heading, groundspeed
const CInterpolatorPbh &pbh() const { return m_pbh; }
@@ -71,7 +67,7 @@ namespace BlackMisc
Aviation::CAircraftSituation m_newSituation;
double m_simulationTimeFraction = 0.0; //!< 0..1
qint64 m_interpolatedTime = 0; //!< "Real time "of interpolated situation
const CInterpolatorPbh m_pbh;
const CInterpolatorPbh m_pbh; //!< pitch, bank, ground speed and heading
};
//! Get the interpolant for the given time point

View File

@@ -17,10 +17,9 @@ namespace BlackMisc
{
namespace Simulation
{
CInterpolatorMulti::CInterpolatorMulti(const CCallsign &callsign, QObject *parent) :
QObject(parent),
m_spline(callsign, this),
m_linear(callsign, this)
CInterpolatorMulti::CInterpolatorMulti(const CCallsign &callsign, ISimulationEnvironmentProvider *p1, IInterpolationSetupProvider *p2, IRemoteAircraftProvider *p3, CInterpolationLogger *logger) :
m_spline(callsign, p1, p2, p3, logger),
m_linear(callsign, p1, p2, p3, logger)
{}
CAircraftSituation CInterpolatorMulti::getInterpolatedSituation(qint64 currentTimeSinceEpoc,
@@ -42,6 +41,7 @@ namespace BlackMisc
{
switch (m_mode)
{
// currently calls the same interpolation for parts
case ModeLinear: return m_linear.getInterpolatedParts(currentTimeSinceEpoc, setup, partsStatus, log);
case ModeSpline: return m_spline.getInterpolatedParts(currentTimeSinceEpoc, setup, partsStatus, log);
default: break;
@@ -49,38 +49,29 @@ namespace BlackMisc
return {};
}
void CInterpolatorMulti::addAircraftSituation(const CAircraftSituation &situation)
{
m_linear.addAircraftSituation(situation);
m_spline.addAircraftSituation(situation);
}
bool CInterpolatorMulti::hasAircraftSituations() const
CAircraftParts CInterpolatorMulti::getInterpolatedOrGuessedParts(
qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup,
CPartsStatus &partsStatus, bool log) const
{
switch (m_mode)
{
case ModeLinear: return m_linear.hasAircraftSituations();
case ModeSpline: return m_spline.hasAircraftSituations();
// currently calls the same interpolation for parts
case ModeLinear: return m_linear.getInterpolatedOrGuessedParts(currentTimeSinceEpoc, setup, partsStatus, log);
case ModeSpline: return m_spline.getInterpolatedOrGuessedParts(currentTimeSinceEpoc, setup, partsStatus, log);
default: break;
}
return false;
return {};
}
void CInterpolatorMulti::addAircraftParts(const CAircraftParts &parts)
{
m_linear.addAircraftParts(parts);
m_spline.addAircraftParts(parts);
}
bool CInterpolatorMulti::hasAircraftParts() const
const CAircraftSituation &CInterpolatorMulti::getLastInterpolatedSituation() const
{
switch (m_mode)
{
case ModeLinear: return m_linear.hasAircraftParts();
case ModeSpline: return m_spline.hasAircraftParts();
case ModeLinear: return m_linear.getLastInterpolatedSituation();
case ModeSpline: return m_spline.getLastInterpolatedSituation();
default: break;
}
return false;
return CAircraftSituation::null();
}
void CInterpolatorMulti::attachLogger(CInterpolationLogger *logger)
@@ -89,18 +80,17 @@ namespace BlackMisc
m_spline.attachLogger(logger);
}
void CInterpolatorMulti::initCorrespondingModel(const CAircraftModel &model)
{
m_linear.initCorrespondingModel(model);
m_spline.initCorrespondingModel(model);
}
bool CInterpolatorMulti::setMode(Mode mode)
{
#ifdef QT_DEBUG
if (m_mode != mode)
{
m_mode = mode;
return true;
}
#else
Q_UNUSED(mode);
#endif
return false;
if (m_mode == mode) { return false; }
m_mode = mode;
return true;
}
bool CInterpolatorMulti::setMode(const QString &mode)
@@ -156,14 +146,9 @@ namespace BlackMisc
CInterpolatorMultiWrapper::CInterpolatorMultiWrapper()
{ }
CInterpolatorMultiWrapper::CInterpolatorMultiWrapper(const CCallsign &callsign, QObject *parent)
CInterpolatorMultiWrapper::CInterpolatorMultiWrapper(const Aviation::CCallsign &callsign, ISimulationEnvironmentProvider *p1, IInterpolationSetupProvider *p2, IRemoteAircraftProvider *p3, CInterpolationLogger *logger)
{
m_interpolator.reset(new CInterpolatorMulti(callsign, parent));
}
CInterpolatorMultiWrapper::CInterpolatorMultiWrapper(const CCallsign &callsign, CInterpolationLogger *logger, QObject *parent)
{
m_interpolator.reset(new CInterpolatorMulti(callsign, parent));
m_interpolator.reset(new CInterpolatorMulti(callsign, p1, p2, p3));
m_interpolator->attachLogger(logger);
}
} // ns

View File

@@ -19,43 +19,39 @@ namespace BlackMisc
{
namespace Simulation
{
/*!
* Multiplexed interpolator which allows switching between modes at runtime.
* \remark currently switching mode is only a developer feature, see https://swift-project.slack.com/archives/C04J6J76N/p1504536854000049
*/
class BLACKMISC_EXPORT CInterpolatorMulti : public QObject
//! Multiplexed interpolator which allows switching between modes at runtime.
class BLACKMISC_EXPORT CInterpolatorMulti
{
public:
//! Constructor
CInterpolatorMulti(const Aviation::CCallsign &callsign, QObject *parent = nullptr);
CInterpolatorMulti(const Aviation::CCallsign &callsign,
ISimulationEnvironmentProvider *p1, IInterpolationSetupProvider *p2, IRemoteAircraftProvider *p3,
CInterpolationLogger *logger = nullptr);
//! \copydoc CInterpolator::getInterpolatedSituation
Aviation::CAircraftSituation getInterpolatedSituation(
qint64 currentTimeSinceEpoc,
const CInterpolationAndRenderingSetupPerCallsign &setup,
qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup,
CInterpolationStatus &status);
//! \copydoc CInterpolator::getInterpolatedParts
Aviation::CAircraftParts getInterpolatedParts(
qint64 currentTimeSinceEpoc,
const CInterpolationAndRenderingSetupPerCallsign &setup,
qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup,
CPartsStatus &partsStatus, bool log = false) const;
//! \copydoc CInterpolator::addAircraftSituation
void addAircraftSituation(const Aviation::CAircraftSituation &situation);
//! \copydoc CInterpolator::getInterpolatedOrGuessedParts
Aviation::CAircraftParts getInterpolatedOrGuessedParts(
qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetupPerCallsign &setup,
CPartsStatus &partsStatus, bool log) const;
//! \copydoc CInterpolator::hasAircraftSituations
bool hasAircraftSituations() const;
//! \copydoc CInterpolator::addAircraftParts
void addAircraftParts(const Aviation::CAircraftParts &parts);
//! \copydoc CInterpolator::hasAircraftParts
bool hasAircraftParts() const;
//! \copydoc CInterpolator::getLastInterpolatedSituation
const Aviation::CAircraftSituation &getLastInterpolatedSituation() const;
//! \copydoc CInterpolator::attachLogger
void attachLogger(CInterpolationLogger *logger);
//! \copydoc CInterpolator::initCorrespondingModel
void initCorrespondingModel(const CAircraftModel &model);
//! Supported interpolation modes.
enum Mode
{
@@ -102,10 +98,10 @@ namespace BlackMisc
CInterpolatorMultiWrapper();
//! Constructor
CInterpolatorMultiWrapper(const Aviation::CCallsign &callsign, QObject *parent = nullptr);
//! Constructor
CInterpolatorMultiWrapper(const Aviation::CCallsign &callsign, CInterpolationLogger *logger, QObject *parent = nullptr);
CInterpolatorMultiWrapper(
const Aviation::CCallsign &callsign,
ISimulationEnvironmentProvider *p1, IInterpolationSetupProvider *p2, IRemoteAircraftProvider *p3,
CInterpolationLogger *logger = nullptr);
//! Has interpolator initialized?
bool hasInterpolator() const { return m_interpolator; }

View File

@@ -10,7 +10,9 @@
#include "blackmisc/simulation/interpolatorspline.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/verify.h"
#include "blackconfig/buildconfig.h"
using namespace BlackConfig;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Geo;
using namespace BlackMisc::Math;
@@ -103,70 +105,67 @@ namespace BlackMisc
Q_UNUSED(setup);
// recalculate derivatives only if they changed
int situationsSize = -1;
if (currentTimeMsSinceEpoc > m_nextSampleAdjustedTime)
{
// with the latest updates of T243 the order and the offsets are supposed to be correct
// so even mixing fast/slow updates shall work
Q_ASSERT_X(m_aircraftSituations.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
Q_ASSERT_X(m_aircraftSituations.size() <= IRemoteAircraftProvider::MaxSituationsPerCallsign, Q_FUNC_INFO, "Wrong size");
// Ref T243, KB 2018-02, can be removed in future, we verify situations above
// Situations are supposed to be in correct order
// const auto end = std::is_sorted_until(m_aircraftSituations.begin(), m_aircraftSituations.end(), [](auto && a, auto && b) { return b.getAdjustedMSecsSinceEpoch() < a.getAdjustedMSecsSinceEpoch(); });
// const auto validSituations = makeRange(m_aircraftSituations.begin(), end);
const CAircraftSituationList validSituations = this->remoteAircraftSituations(m_callsign);
situationsSize = validSituations.size();
Q_ASSERT_X(validSituations.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order");
Q_ASSERT_X(validSituations.size() <= IRemoteAircraftProvider::MaxSituationsPerCallsign, Q_FUNC_INFO, "Wrong size");
// find the first situation earlier than the current time
const CAircraftSituationList &validSituations = m_aircraftSituations; // if needed, we could also copy here
const auto pivot = std::partition_point(validSituations.begin(), validSituations.end(), [ = ](auto &&s) { return s.getAdjustedMSecsSinceEpoch() > currentTimeMsSinceEpoc; });
const auto situationsNewer = makeRange(validSituations.begin(), pivot);
const auto situationsOlder = makeRange(pivot, validSituations.end());
if (situationsNewer.isEmpty() || situationsOlder.size() < 2)
{
return m_interpolant;
}
// m_s[0] .. oldest -> m_[2] .. latest
if (situationsNewer.isEmpty() || situationsOlder.size() < 2) { return m_interpolant; }
m_s = std::array<CAircraftSituation, 3> {{ *(situationsOlder.begin() + 1), *situationsOlder.begin(), *(situationsNewer.end() - 1) }};
// - altitude unit must be the same for all three, but the unit itself does not matter
// - ground elevantion here normally is not available
// - only use elevation plane here, do not call provider
// - some info how has a plane moves: 100km/h => 1sec 27,7m => 5 secs 136m
// - on an airport the plane does not move very fast, or not at all
// - and the elevation remains (almost) constant for a wider area
// - flying the ground elevation not really matters
const CElevationPlane plane0 = this->findClosestElevationWithinRange(m_s[0], CElevationPlane::singlePointRadius());
const CElevationPlane plane1 = this->findClosestElevationWithinRange(m_s[1], CElevationPlane::singlePointRadius());
const CElevationPlane plane2 = this->findClosestElevationWithinRange(m_s[2], CElevationPlane::singlePointRadius());
// do not override existing values
m_s[0].setGroundElevationChecked(plane0);
m_s[1].setGroundElevationChecked(plane1);
m_s[2].setGroundElevationChecked(plane2);
const CLength cg = this->getCG(m_callsign);
const double a0 = m_s[0].getCorrectedAltitude(cg).value();
const double a1 = m_s[1].getCorrectedAltitude(cg).value();
const double a2 = m_s[2].getCorrectedAltitude(cg).value();
const std::array<std::array<double, 3>, 3> normals {{ m_s[0].getPosition().normalVectorDouble(), m_s[1].getPosition().normalVectorDouble(), m_s[2].getPosition().normalVectorDouble() }};
PosArray pa;
pa.x = {{ normals[0][0], normals[1][0], normals[2][0] }};
pa.x = {{ normals[0][0], normals[1][0], normals[2][0] }}; // oldest
pa.y = {{ normals[0][1], normals[1][1], normals[2][1] }};
pa.z = {{ normals[0][2], normals[1][2], normals[2][2] }};
pa.a = {{ a0, a1, a2 }};
pa.z = {{ normals[0][2], normals[1][2], normals[2][2] }}; // latest
pa.t = {{ static_cast<double>(m_s[0].getAdjustedMSecsSinceEpoch()), static_cast<double>(m_s[1].getAdjustedMSecsSinceEpoch()), static_cast<double>(m_s[2].getAdjustedMSecsSinceEpoch()) }};
pa.dx = getDerivatives(pa.t, pa.x);
pa.dy = getDerivatives(pa.t, pa.y);
pa.dz = getDerivatives(pa.t, pa.z);
pa.da = getDerivatives(pa.t, pa.a);
m_prevSampleAdjustedTime = situationsOlder.begin()->getAdjustedMSecsSinceEpoch();
m_nextSampleAdjustedTime = (situationsNewer.end() - 1)->getAdjustedMSecsSinceEpoch();
m_prevSampleTime = situationsOlder.begin()->getMSecsSinceEpoch();
m_nextSampleTime = (situationsNewer.end() - 1)->getMSecsSinceEpoch();
m_interpolant = Interpolant(pa, situationsOlder.begin()->getAltitude().getUnit(), { *situationsOlder.begin(), *(situationsNewer.end() - 1) });
// - altitude unit must be the same for all three, but the unit itself does not matter
// - ground elevantion here normally is not available
// - some info how fast a plane moves: 100km/h => 1sec 27,7m => 5 secs 136m
// - on an airport the plane does not move very fast, or not at all
// - and the elevation remains (almost) constant for a wider area
// - during flying the ground elevation not really matters
this->updateElevations();
const double a0 = m_s[0].getCorrectedAltitude(m_cg).value(); // oldest
const double a1 = m_s[1].getCorrectedAltitude(m_cg).value();
const double a2 = m_s[2].getCorrectedAltitude(m_cg).value(); // latest
pa.a = {{ a0, a1, a2 }};
pa.gnd = {{ m_s[0].getOnGroundFactor(), m_s[1].getOnGroundFactor(), m_s[2].getOnGroundFactor() }};
pa.da = getDerivatives(pa.t, pa.a);
pa.dgnd = getDerivatives(pa.t, pa.gnd);
Q_ASSERT_X(this->areAltitudeUnitsSame(), Q_FUNC_INFO, "Altitude unit mismatch");
// m_prevSampleAdjustedTime = situationsOlder.begin()->getAdjustedMSecsSinceEpoch(); // m_s[1]
// m_nextSampleAdjustedTime = (situationsNewer.end() - 1)->getAdjustedMSecsSinceEpoch(); // m_s[2]
// m_prevSampleTime = situationsOlder.begin()->getMSecsSinceEpoch(); // m_s[1]
// m_nextSampleTime = (situationsNewer.end() - 1)->getMSecsSinceEpoch(); // m_s[2]
// m_interpolant = Interpolant(pa, situationsOlder.begin()->getAltitude().getUnit(), { *situationsOlder.begin(), *(situationsNewer.end() - 1) });
m_prevSampleAdjustedTime = m_s[1].getAdjustedMSecsSinceEpoch();
m_nextSampleAdjustedTime = m_s[2].getAdjustedMSecsSinceEpoch(); // latest
m_prevSampleTime = m_s[1].getMSecsSinceEpoch();
m_nextSampleTime = m_s[2].getMSecsSinceEpoch(); // latest
m_interpolant = Interpolant(pa, m_s[2].getAltitudeUnit(), CInterpolatorPbh(m_s[1], m_s[2]));
Q_ASSERT_X(m_prevSampleAdjustedTime < m_nextSampleAdjustedTime, Q_FUNC_INFO, "Wrong time order");
// VERIFY
this->verifyInterpolationSituations(m_s[0], m_s[1], m_s[2], setup); // oldest -> latest
}
// Example:
@@ -194,38 +193,93 @@ namespace BlackMisc
if (this->hasAttachedLogger() && setup.logInterpolation())
{
if (situationsSize < 0) { situationsSize = this->remoteAircraftSituationsCount(m_callsign); }
log.interpolationSituations.push_back(m_s[0]);
log.interpolationSituations.push_back(m_s[1]);
log.interpolationSituations.push_back(m_s[2]); // latest at end
log.interpolator = 's';
log.deltaSampleTimesMs = dt2;
log.simulationTimeFraction = timeFraction;
log.noNetworkSituations = m_aircraftSituations.size();
log.simTimeFraction = timeFraction;
log.noNetworkSituations = situationsSize;
log.tsInterpolated = interpolatedTime; // without offsets
}
return m_interpolant;
}
CCoordinateGeodetic CInterpolatorSpline::Interpolant::interpolatePosition(const CInterpolationAndRenderingSetupPerCallsign &setup) const
bool CInterpolatorSpline::updateElevations()
{
Q_UNUSED(setup);
const double newX = evalSplineInterval(m_currentTimeMsSinceEpoc, m_pa.t[1], m_pa.t[2], m_pa.x[1], m_pa.x[2], m_pa.dx[1], m_pa.dx[2]);
const double newY = evalSplineInterval(m_currentTimeMsSinceEpoc, m_pa.t[1], m_pa.t[2], m_pa.y[1], m_pa.y[2], m_pa.dy[1], m_pa.dy[2]);
const double newZ = evalSplineInterval(m_currentTimeMsSinceEpoc, m_pa.t[1], m_pa.t[2], m_pa.z[1], m_pa.z[2], m_pa.dz[1], m_pa.dz[2]);
CCoordinateGeodetic currentPosition;
currentPosition.setNormalVector(newX, newY, newZ);
return currentPosition;
bool updated = false;
for (unsigned int i = 0; i < m_s.size(); i++)
{
if (m_s[i].hasGroundElevation()) { continue; } // do not override existing values
const CElevationPlane plane = this->findClosestElevationWithinRange(m_s[i], CElevationPlane::singlePointRadius());
const bool u = m_s[i].setGroundElevationChecked(plane);
updated |= u;
}
return updated;
}
CAltitude CInterpolatorSpline::Interpolant::interpolateAltitude(const CInterpolationAndRenderingSetupPerCallsign &setup) const
bool CInterpolatorSpline::areAnyElevationsMissing() const
{
Q_UNUSED(setup);
for (unsigned int i = 0; i < m_s.size(); i++)
{
if (!m_s[i].hasGroundElevation()) { return true; }
}
return false;
}
const double newA = evalSplineInterval(m_currentTimeMsSinceEpoc, m_pa.t[1], m_pa.t[2], m_pa.a[1], m_pa.a[2], m_pa.da[1], m_pa.da[2]);
return CAltitude(newA, m_altitudeUnit);
bool CInterpolatorSpline::isAnySituationNearGroundRelevant() const
{
for (unsigned int i = 0; i < m_s.size(); i++)
{
if (!m_s[i].canLikelySkipNearGroundInterpolation()) { return true; }
}
return false;
}
bool CInterpolatorSpline::areAltitudeUnitsSame(const CLengthUnit &compare) const
{
if (m_s.size() < 1) { return true; }
const CLengthUnit c = compare.isNull() ? m_s[0].getAltitudeUnit() : compare;
for (unsigned int i = 0; i < m_s.size(); i++)
{
if (m_s[i].getAltitudeUnit() != c) { return false; }
}
return true;
}
CAircraftSituation CInterpolatorSpline::Interpolant::interpolatePositionAndAltitude(const CAircraftSituation &situation) const
{
const double t1 = m_pa.t[1];
const double t2 = m_pa.t[2];
const double newX = evalSplineInterval(m_currentTimeMsSinceEpoc, t1, t2, m_pa.x[1], m_pa.x[2], m_pa.dx[1], m_pa.dx[2]);
const double newY = evalSplineInterval(m_currentTimeMsSinceEpoc, t1, t2, m_pa.y[1], m_pa.y[2], m_pa.dy[1], m_pa.dy[2]);
const double newZ = evalSplineInterval(m_currentTimeMsSinceEpoc, t1, t2, m_pa.z[1], m_pa.z[2], m_pa.dz[1], m_pa.dz[2]);
CAircraftSituation newSituation(situation);
const std::array<double, 3> normalVector = {{ newX, newY, newZ }};
const CCoordinateGeodetic currentPosition(normalVector);
const double newA = evalSplineInterval(m_currentTimeMsSinceEpoc, t1, t2, m_pa.a[1], m_pa.a[2], m_pa.da[1], m_pa.da[2]);
const CAltitude alt(newA, m_altitudeUnit);
newSituation.setPosition(currentPosition);
newSituation.setAltitude(alt);
newSituation.setMSecsSinceEpoch(this->getInterpolatedTime());
const double gnd1 = m_pa.gnd[1];
const double gnd2 = m_pa.gnd[2];
do
{
if (gfEqualAirborne(gnd1, gnd2)) { newSituation.setOnGround(false); break; }
if (gfEqualOnGround(gnd1, gnd2)) { newSituation.setOnGround(true); break; }
const double newGnd = evalSplineInterval(m_currentTimeMsSinceEpoc, t1, t2, gnd1, gnd2, m_pa.dgnd[1], m_pa.dgnd[2]);
newSituation.setOnGroundFactor(newGnd);
newSituation.setOnGroundFromGroundFactorFromInterpolation();
}
while (false);
return newSituation;
}
void CInterpolatorSpline::Interpolant::setTimes(qint64 currentTimeMs, double timeFraction, qint64 interpolatedTimeMs)
@@ -243,6 +297,7 @@ namespace BlackMisc
a[i] = 0; t[i] = 0;
dx[i] = 0; dy[i] = 0; dz[i] = 0;
da[i] = 0;
gnd[i] = 0; dgnd[i] = 0;
}
}

View File

@@ -26,13 +26,12 @@ namespace BlackMisc
//! Cubic spline interpolator
class BLACKMISC_EXPORT CInterpolatorSpline : public CInterpolator<CInterpolatorSpline>
{
Q_OBJECT
public:
//! Constructor
CInterpolatorSpline(const Aviation::CCallsign &callsign, QObject *parent = nullptr) :
CInterpolator("CInterpolatorSpline", callsign, parent)
{}
CInterpolatorSpline(const Aviation::CCallsign &callsign,
ISimulationEnvironmentProvider *p1, IInterpolationSetupProvider *p2, IRemoteAircraftProvider *p3,
CInterpolationLogger *logger = nullptr) :
CInterpolator(callsign, p1, p2, p3, logger) {}
//! Position arrays for interpolation
struct BLACKMISC_EXPORT PosArray
@@ -43,9 +42,8 @@ namespace BlackMisc
//! Zero initialized position array
static const PosArray &zeroPosArray();
//! 3 coordinates for spline interpolation
//! @{
std::array<double, 3> x, y, z, a, t, dx, dy, dz, da;
//! 3 coordinates for spline interpolation @{
std::array<double, 3> x, y, z, a, gnd, t, dx, dy, dz, da, dgnd;
//! @}
};
@@ -62,10 +60,7 @@ namespace BlackMisc
m_pa(pa), m_altitudeUnit(altitudeUnit), m_pbh(pbh) {}
//! Perform the interpolation
//! @{
Geo::CCoordinateGeodetic interpolatePosition(const CInterpolationAndRenderingSetupPerCallsign &setup) const;
Aviation::CAltitude interpolateAltitude(const CInterpolationAndRenderingSetupPerCallsign &setup) const;
//! @}
Aviation::CAircraftSituation interpolatePositionAndAltitude(const Aviation::CAircraftSituation &situation) const;
//! Interpolator for pitch, bank, heading, groundspeed
const CInterpolatorPbh &pbh() const { return m_pbh; }
@@ -83,11 +78,11 @@ namespace BlackMisc
void setTimes(qint64 currentTimeMs, double timeFraction, qint64 interpolatedTimeMs);
private:
PosArray m_pa;
PosArray m_pa; //! current positions array, latest values last
PhysicalQuantities::CLengthUnit m_altitudeUnit;
CInterpolatorPbh m_pbh;
qint64 m_currentTimeMsSinceEpoc { 0 };
qint64 m_interpolatedTime { 0 }; //!< represented "real time" at interpolated situation
qint64 m_currentTimeMsSinceEpoc { -1 };
qint64 m_interpolatedTime { -1 }; //!< represented "real time" at interpolated situation
};
//! Strategy used by CInterpolator::getInterpolatedSituation
@@ -95,11 +90,23 @@ namespace BlackMisc
const CInterpolationAndRenderingSetupPerCallsign &setup, CInterpolationStatus &status, SituationLog &log);
private:
//! Update the elevations used in CInterpolatorSpline::m_s
bool updateElevations();
//! Are any elevations missing in CInterpolatorSpline::m_s
bool areAnyElevationsMissing() const;
//! Ground relevant
bool isAnySituationNearGroundRelevant() const;
//! Are the altitude units all the same
bool areAltitudeUnitsSame(const PhysicalQuantities::CLengthUnit &compare = PhysicalQuantities::CLengthUnit::nullUnit()) const;
qint64 m_prevSampleAdjustedTime = 0; //!< previous sample time + offset
qint64 m_nextSampleAdjustedTime = 0; //!< previous sample time + offset
qint64 m_prevSampleTime = 0; //!< previous sample "real time"
qint64 m_nextSampleTime = 0; //!< next sample "real time"
std::array<Aviation::CAircraftSituation, 3> m_s;
std::array<Aviation::CAircraftSituation, 3> m_s; //!< used situations
Interpolant m_interpolant;
};
} // ns

View File

@@ -56,8 +56,7 @@ namespace BlackMiscTest
{
CCallsign cs("SWIFT");
CRemoteAircraftProviderDummy provider;
CInterpolatorLinear interpolator(cs);
interpolator.setRemoteAircraftProvider(&provider);
CInterpolatorLinear interpolator(cs, nullptr, nullptr, &provider);
// fixed time so everything can be debugged
const qint64 ts = 1425000000000; // QDateTime::currentMSecsSinceEpoch();

View File

@@ -32,8 +32,7 @@ namespace BlackMiscTest
{
CCallsign cs("SWIFT");
CRemoteAircraftProviderDummy provider;
CInterpolatorSpline interpolator(cs);
interpolator.setRemoteAircraftProvider(&provider);
CInterpolatorSpline interpolator(cs, nullptr, nullptr, &provider);
// fixed time so everything can be debugged
const qint64 ts = 1425000000000; // QDateTime::currentMSecsSinceEpoch()
@@ -64,12 +63,10 @@ namespace BlackMiscTest
qint64 pTs = p.getAdjustedMSecsSinceEpoch();
QVERIFY2(status.isSupportingParts(), "Should support parts");
QVERIFY2(pTs == ts, "Expect latest ts");
QCOMPARE(p.isOnGroundInterpolated(), 1.0);
p = interpolator.getInterpolatedParts(farPast, setup, status);
pTs = p.getAdjustedMSecsSinceEpoch();
QVERIFY2(status.isSupportingParts(), "Should support parts");
QVERIFY2(pTs == oldestTs, "Expect oldest ts");
QCOMPARE(p.isOnGroundInterpolated(), 1.0);
// Testing for a time >> last time
// all on ground flags true
@@ -82,7 +79,6 @@ namespace BlackMiscTest
pTs = p.getAdjustedMSecsSinceEpoch();
QVERIFY2(status.isSupportingParts(), "Should support parts");
QVERIFY2(p.getAdjustedMSecsSinceEpoch() == pTs, "Expect latest ts");
QCOMPARE(p.isOnGroundInterpolated(), 0.0);
}
void CTestInterpolatorParts::partsToSituationGndFlag()