swift
interpolatorlinear.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <array>
7 
8 #include "config/buildconfig.h"
10 #include "misc/aviation/altitude.h"
12 #include "misc/logmessage.h"
14 #include "misc/range.h"
16 #include "misc/verify.h"
17 
18 using namespace swift::config;
19 using namespace swift::misc::aviation;
20 using namespace swift::misc::geo;
21 using namespace swift::misc::math;
22 using namespace swift::misc::physical_quantities;
23 using namespace swift::misc::simulation;
24 
25 namespace swift::misc::simulation
26 {
27  CInterpolatorLinear::CInterpolant::CInterpolant(const CAircraftSituation &startSituation)
28  : m_startSituation(startSituation), m_pbh(0, startSituation, startSituation)
29  {}
30 
32  const CInterpolatorLinearPbh &pbh)
33  : m_startSituation(startSituation), m_pbh(pbh)
34  {}
35 
37  const CAircraftSituation &endSituation, double timeFraction,
38  qint64 interpolatedTime)
39  : IInterpolant(interpolatedTime), m_startSituation(startSituation), m_endSituation(endSituation),
40  m_simulationTimeFraction(timeFraction)
41  {
42  if (CBuildConfig::isLocalDeveloperDebugBuild())
43  {
44  SWIFT_VERIFY_X(isValidTimeFraction(m_simulationTimeFraction), Q_FUNC_INFO,
45  "Time fraction needs to be within [0;1]");
46  }
47  m_pbh = CInterpolatorLinearPbh(m_simulationTimeFraction, startSituation, endSituation);
48  }
49 
50  void CInterpolatorLinear::anchor() {}
51 
52  std::tuple<geo::CCoordinateGeodetic, aviation::CAltitude>
54  {
55  const std::array<double, 3> startVec(m_startSituation.getPosition().normalVectorDouble());
56  const std::array<double, 3> endVec(m_endSituation.getPosition().normalVectorDouble());
57 
58  if (CBuildConfig::isLocalDeveloperDebugBuild())
59  {
60  SWIFT_VERIFY_X(CAircraftSituation::isValidVector(startVec), Q_FUNC_INFO, "Invalid start vector");
61  SWIFT_VERIFY_X(CAircraftSituation::isValidVector(endVec), Q_FUNC_INFO, "Invalid end vector");
62  SWIFT_VERIFY_X(isAcceptableTimeFraction(m_simulationTimeFraction), Q_FUNC_INFO, "Invalid fraction");
63  }
64 
65  // Interpolate position: pos = (posB - posA) * t + posA
66  CCoordinateGeodetic interpolatedPosition;
67  const double tf = clampValidTimeFraction(m_simulationTimeFraction);
68  interpolatedPosition.setNormalVector((endVec[0] - startVec[0]) * tf + startVec[0],
69  (endVec[1] - startVec[1]) * tf + startVec[1],
70  (endVec[2] - startVec[2]) * tf + startVec[2]);
71 
72  if (CBuildConfig::isLocalDeveloperDebugBuild())
73  {
74  SWIFT_VERIFY_X(interpolatedPosition.isValidVectorRange(), Q_FUNC_INFO, "Invalid vector");
75  }
76 
77  // Interpolate altitude: Alt = (AltB - AltA) * t + AltA
78  // avoid underflow below ground elevation by using getCorrectedAltitude
79  const CAltitude oldAlt(m_startSituation.getCorrectedAltitude());
80  const CAltitude newAlt(m_endSituation.getCorrectedAltitude());
81  Q_ASSERT_X(oldAlt.getReferenceDatum() == CAltitude::MeanSeaLevel &&
82  oldAlt.getReferenceDatum() == newAlt.getReferenceDatum(),
83  Q_FUNC_INFO, "mismatch in reference"); // otherwise no calculation is possible
84  const CAltitude altitude((newAlt - oldAlt) * tf + oldAlt, oldAlt.getReferenceDatum());
85 
86  return { interpolatedPosition, altitude };
87  }
88 
90  {
91  const double startGroundFactor = m_startSituation.getOnGroundInfo().getGroundFactor();
92  const double endGroundFactor = m_endSituation.getOnGroundInfo().getGroundFactor();
93  if (CAircraftSituation::isGfEqualAirborne(startGroundFactor, endGroundFactor))
94  {
95  return { COnGroundInfo::NotOnGround, COnGroundInfo::OnGroundByInterpolation };
96  }
97  else if (CAircraftSituation::isGfEqualOnGround(startGroundFactor, endGroundFactor))
98  {
99  return { COnGroundInfo::OnGround, COnGroundInfo::OnGroundByInterpolation };
100  }
101  else
102  {
103  const double tf = clampValidTimeFraction(m_simulationTimeFraction);
104  const double interpolatedGroundFactor = (endGroundFactor - startGroundFactor) * tf + startGroundFactor;
105  return COnGroundInfo(interpolatedGroundFactor);
106  }
107  }
108 
110  {
111  // set default situations
112  CAircraftSituation startSituation = m_interpolant.getStartSituation();
113  CAircraftSituation endSituation = m_interpolant.getEndSituation();
114 
115  Q_ASSERT_X(endSituation.getAdjustedMSecsSinceEpoch() >= startSituation.getAdjustedMSecsSinceEpoch(),
116  Q_FUNC_INFO, "Wrong order");
117 
119  const bool newSplit = endSituation.getAdjustedMSecsSinceEpoch() < m_currentTimeMsSinceEpoch;
120  const bool recalculate = updated || newSplit;
121 
122  if (recalculate)
123  {
125 
126  // find the first situation earlier than the current time
127  const auto pivot =
128  std::partition_point(m_currentSituations.begin(), m_currentSituations.end(), [=](auto &&s) {
129  return s.getAdjustedMSecsSinceEpoch() > m_currentTimeMsSinceEpoch;
130  });
131  const auto situationsNewer = makeRange(m_currentSituations.begin(), pivot);
132  const auto situationsOlder = makeRange(pivot, m_currentSituations.end());
133 
134  // latest first, now 00:20 split time
135  // time pos
136  // 00:25 10 newer
137  // 00:20 11 newer
138  // <----- split
139  // 00:15 12 older
140  // 00:10 13 older
141  // 00:05 14 older
142 
143  // The first condition covers a situation, when there are no before / after situations.
144  // We just place at the last position until we get before / after situations
145  if (situationsOlder.isEmpty() || situationsNewer.isEmpty())
146  {
147  // no before situations
148  if (situationsOlder.isEmpty())
149  {
150  const CAircraftSituation currentSituation(*(situationsNewer.end() - 1)); // oldest newest
152  m_interpolant = { currentSituation };
153  return m_interpolant;
154  }
155 
156  // only one before situation
157  if (situationsOlder.size() < 2)
158  {
159  const CAircraftSituation currentSituation(situationsOlder.front()); // latest oldest
161  m_interpolant = { currentSituation };
162  return m_interpolant;
163  }
164 
165  // extrapolate from two before situations
166  startSituation = *(situationsOlder.begin() + 1); // before newest
167  endSituation = situationsOlder.front(); // newest
168  }
169  else
170  {
171  startSituation = situationsOlder.front(); // first oldest (aka newest oldest)
172  endSituation = *(situationsNewer.end() - 1); // latest newest (aka oldest of newer block)
173  Q_ASSERT(startSituation.getAdjustedMSecsSinceEpoch() < endSituation.getAdjustedMSecsSinceEpoch());
174  }
175 
176  // adjust ground if required
177  if (!startSituation.canLikelySkipNearGroundInterpolation() && !startSituation.hasGroundElevation())
178  {
179  const CElevationPlane planeOld =
180  this->findClosestElevationWithinRange(startSituation, CElevationPlane::singlePointRadius());
181  startSituation.setGroundElevationChecked(planeOld, CAircraftSituation::FromCache);
182  }
183  if (!endSituation.canLikelySkipNearGroundInterpolation() && !endSituation.hasGroundElevation())
184  {
185  const CElevationPlane planeNew =
186  this->findClosestElevationWithinRange(endSituation, CElevationPlane::singlePointRadius());
187  endSituation.setGroundElevationChecked(planeNew, CAircraftSituation::FromCache);
188  }
189  } // modified situations
190 
191  CAircraftSituation currentSituation(startSituation); // also sets ground elevation if available
192 
193  // Time between start and end packet
194  const qint64 sampleDeltaTimeMs =
195  endSituation.getAdjustedMSecsSinceEpoch() - startSituation.getAdjustedMSecsSinceEpoch();
196  Q_ASSERT_X(sampleDeltaTimeMs >= 0, Q_FUNC_INFO, "Negative delta time");
197  log.interpolator = 'l';
198 
199  // Fraction of the deltaTime, ideally [0.0 - 1.0]
200  // < 0 should not happen due to the split, > 1 can happen if new values are delayed beyond split time
201  // 1) values > 1 mean extrapolation
202  // 2) values > 2 mean no new situations coming in
203  const double distanceToSplitTimeMs = endSituation.getAdjustedMSecsSinceEpoch() - m_currentTimeMsSinceEpoch;
204  double simulationTimeFraction = qMax(1.0 - (distanceToSplitTimeMs / sampleDeltaTimeMs), 0.0);
205  if (simulationTimeFraction >= 1.0)
206  {
207  simulationTimeFraction = 1.0;
208  if (qAbs(distanceToSplitTimeMs) > 100)
209  {
210  CLogMessage(this).debug(u"Distance to split: %1") << distanceToSplitTimeMs;
211  }
212  }
213 
214  const double deltaTimeFractionMs = sampleDeltaTimeMs * simulationTimeFraction;
215  const qint64 interpolatedTime = startSituation.getMSecsSinceEpoch() + qRound(deltaTimeFractionMs);
216 
217  // Ref T297 adjust offset time, but this already the interpolated situation
218  currentSituation.setTimeOffsetMs(
219  startSituation.getTimeOffsetMs() +
220  qRound((endSituation.getTimeOffsetMs() - startSituation.getTimeOffsetMs()) * simulationTimeFraction));
221  currentSituation.setMSecsSinceEpoch(interpolatedTime);
223 
224  if (this->doLogging())
225  {
227  log.deltaSampleTimesMs = sampleDeltaTimeMs;
228  log.simTimeFraction = simulationTimeFraction;
229  log.deltaSampleTimesMs = sampleDeltaTimeMs;
230  log.tsInterpolated = interpolatedTime;
232  log.interpolationSituations.push_back(startSituation); // oldest at front
233  log.interpolationSituations.push_back(endSituation); // latest at back
234  log.interpolantRecalc = recalculate;
235  }
236 
237  m_interpolant = { startSituation, endSituation, simulationTimeFraction, interpolatedTime };
238  m_interpolant.setRecalculated(recalculate);
239 
240  return m_interpolant;
241  }
242 } // namespace swift::misc::simulation
Class for emitting a log message.
Definition: logmessage.h:27
Derived & debug()
Set the severity to debug.
iterator begin()
Returns iterator at the beginning of the sequence.
Definition: sequence.h:163
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
void clear()
Removes all elements in the sequence.
Definition: sequence.h:288
iterator end()
Returns iterator one past the end of the sequence.
Definition: sequence.h:172
qint64 getMSecsSinceEpoch() const
Timestamp as ms value.
void setMSecsSinceEpoch(qint64 mSecsSinceEpoch)
Timestamp as ms value.
qint64 getAdjustedMSecsSinceEpoch() const
Timestamp with offset added for interpolation.
qint64 getTimeOffsetMs() const
Milliseconds to add to timestamp for interpolation.
void setTimeOffsetMs(qint64 offset)
Milliseconds to add to timestamp for interpolation.
Value object encapsulating information of an aircraft's situation.
bool hasGroundElevation() const
Is ground elevation value available.
bool setGroundElevationChecked(const geo::CElevationPlane &elevationPlane, GndElevationInfo info, bool transferred=false)
Set elevation of the ground directly beneath, but checked.
bool canLikelySkipNearGroundInterpolation() const
Situation looks like an aircraft not near ground.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
ReferenceDatum getReferenceDatum() const
Get reference datum (MSL or AGL)
Definition: altitude.h:145
Information about the ground status.
Definition: ongroundinfo.h:19
double getGroundFactor() const
Get the ground factor Use this for interpolation only!! For just checking if the info is OnGround or ...
Definition: ongroundinfo.h:66
void setNormalVector(const QVector3D &normal)
Set normal vector.
Plane of same elevation, can be a single point or larger area (e.g. airport)
bool isValidVectorRange() const
Check values.
void setInterpolatedAndCheckSituation(bool succeeded, const aviation::CAircraftSituation &situation)
Set succeeded.
qint64 m_situationsLastModified
when situations were last modified
Definition: interpolator.h:132
CInterpolationStatus m_currentInterpolationStatus
this step's situation status
Definition: interpolator.h:116
qint64 m_situationsLastModifiedUsed
interpolant based on situations last updated
Definition: interpolator.h:133
qint64 m_currentTimeMsSinceEpoch
current time
Definition: interpolator.h:109
aviation::CAircraftSituationList m_currentSituations
current situations obtained by remoteAircraftSituationsAndChange
Definition: interpolator.h:112
aviation::COnGroundInfo interpolateGroundFactor() const
Interpolate the ground information/factor.
const aviation::CAircraftSituation & getStartSituation() const
Start situation.
std::tuple< geo::CCoordinateGeodetic, aviation::CAltitude > interpolatePositionAndAltitude() const
Perform the interpolation.
const aviation::CAircraftSituation & getEndSituation() const
End situation.
const IInterpolant & getInterpolant(SituationLog &log)
Get the interpolant for the given time point.
Simple linear interpolator for pitch, bank, heading and groundspeed from start to end situation.
geo::CElevationPlane findClosestElevationWithinRange(const geo::ICoordinateGeodetic &reference, const physical_quantities::CLength &range) const
Find closest elevation (or return NULL)
void setRecalculated(bool reCalculated)
Set recalculated interpolant.
Definition: interpolant.h:48
bool isValidTimeFraction(double timeFraction)
Valid time fraction [0,1].
bool isAcceptableTimeFraction(double timeFraction)
Valid time fraction [0,1], this allows minor overshooting.
double clampValidTimeFraction(double timeFraction)
Clamp time fraction [0,1].
auto makeRange(I begin, I2 end) -> CRange< I >
Returns a CRange constructed from begin and end iterators of deduced types.
Definition: range.h:316
Log entry for situation interpolation.
QChar interpolator
what interpolator is used
aviation::CAircraftSituationList interpolationSituations
the interpolator uses 2, 3 situations (latest at end)
qint64 tsInterpolated
timestamp interpolated
double deltaSampleTimesMs
delta time between samples (i.e.
double simTimeFraction
time fraction, expected 0..1
bool interpolantRecalc
interpolant recalculated
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26