mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 14:55:36 +08:00
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:
committed by
Roland Winklmeier
parent
5c6a37c669
commit
cd1ce37ec3
@@ -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()) :
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
**/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user