diff --git a/src/blackmisc/aviation/aircraftsituation.cpp b/src/blackmisc/aviation/aircraftsituation.cpp index c1003766b..bba3aef1b 100644 --- a/src/blackmisc/aviation/aircraftsituation.cpp +++ b/src/blackmisc/aviation/aircraftsituation.cpp @@ -7,7 +7,9 @@ * contained in the LICENSE file. */ +#include "blackmisc/simulation/aircraftmodel.h" #include "blackmisc/aviation/aircraftsituation.h" +#include "blackmisc/aviation/aircraftsituationchange.h" #include "blackmisc/aviation/aircraftpartslist.h" #include "blackmisc/geo/elevationplane.h" #include "blackmisc/pq/length.h" @@ -16,11 +18,15 @@ #include "blackmisc/comparefunctions.h" #include "blackmisc/variant.h" #include "blackmisc/verify.h" +#include "blackconfig/buildconfig.h" + #include "QStringBuilder" #include -using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Geo; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Simulation; +using namespace BlackConfig; namespace BlackMisc { @@ -51,11 +57,16 @@ namespace BlackMisc { return QStringLiteral("ts: ") % this->getFormattedTimestampAndOffset(true) % QStringLiteral(" | ") % m_position.toQString(i18n) % - QStringLiteral(" | bank: ") % (m_bank.toQString(i18n)) % - QStringLiteral(" | pitch: ") % (m_pitch.toQString(i18n)) % - QStringLiteral(" | heading: ") % (m_heading.toQString(i18n)) % - QStringLiteral(" | og: ") % this->getOnGroundInfo() % - QStringLiteral(" | factor: ") % QString::number(m_onGroundFactor, 'f', 2) % + QStringLiteral(" | alt: ") % this->getAltitude().valueRoundedWithUnit(CLengthUnit::ft(), 1) % + QStringLiteral(" ") % this->getCorrectedAltitude().valueRoundedWithUnit(CLengthUnit::ft(), 1) % + QStringLiteral("[cor] | og: ") % this->getOnGroundInfo() % + (m_onGroundGuessingDetails.isEmpty() ? QStringLiteral("") : QStringLiteral(" ") % m_onGroundGuessingDetails) % + QStringLiteral(" | cg: ") % + (m_cg.isNull() ? QStringLiteral("null") : m_cg.valueRoundedWithUnit(CLengthUnit::m(), 1) % QStringLiteral(" ") % m_cg.valueRoundedWithUnit(CLengthUnit::ft(), 1)) % + QStringLiteral(" | factor [0..1]: ") % QString::number(m_onGroundFactor, 'f', 2) % + QStringLiteral(" | bank: ") % m_bank.toQString(i18n) % + QStringLiteral(" | pitch: ") % m_pitch.toQString(i18n) % + QStringLiteral(" | heading: ") % m_heading.toQString(i18n) % QStringLiteral(" | gs: ") % m_groundSpeed.valueRoundedWithUnit(CSpeedUnit::kts(), 1, true) % QStringLiteral(" ") % m_groundSpeed.valueRoundedWithUnit(CSpeedUnit::m_s(), 1, true) % QStringLiteral(" | elevation: ") % (m_groundElevationPlane.toQString(i18n)); @@ -244,21 +255,30 @@ namespace BlackMisc this->getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails; } - void CAircraftSituation::setOnGround(bool onGround) + bool CAircraftSituation::setOnGround(bool onGround) { - this->setOnGround(onGround ? OnGround : NotOnGround); + return this->setOnGround(onGround ? OnGround : NotOnGround); } - void CAircraftSituation::setOnGround(CAircraftSituation::IsOnGround onGround) + bool CAircraftSituation::setOnGround(CAircraftSituation::IsOnGround onGround) { - m_onGround = static_cast(onGround); + if (this->getOnGround() == onGround) { return false; } + const int og = static_cast(onGround); + m_onGround = og; m_onGroundFactor = (onGround == OnGround) ? 1.0 : 0.0; + return true; } - void CAircraftSituation::setOnGround(CAircraftSituation::IsOnGround onGround, CAircraftSituation::OnGroundDetails details) + bool CAircraftSituation::setOnGround(CAircraftSituation::IsOnGround onGround, CAircraftSituation::OnGroundDetails details) { - this->setOnGround(onGround); + const bool set = this->setOnGround(onGround); this->setOnGroundDetails(details); + if (details != OnGroundByGuessing) + { + m_onGroundGuessingDetails.clear(); + } + + return set; } void CAircraftSituation::setOnGroundFactor(double groundFactor) @@ -276,40 +296,111 @@ namespace BlackMisc bool CAircraftSituation::shouldGuessOnGround() const { - return (!this->isOnGroundInfoAvailable()); + return !this->hasInboundGroundDetails(); } - bool CAircraftSituation::guessOnGround(bool vtol, const PhysicalQuantities::CLength &cg) + + bool CAircraftSituation::guessOnGround(const CAircraftSituationChange &change, const CAircraftModel &model) { + Q_UNUSED(change); if (!this->shouldGuessOnGround()) { return false; } + // for debugging purposed + QString *details = CBuildConfig::isLocalDeveloperDebugBuild() ? &m_onGroundGuessingDetails : nullptr; + // Non VTOL aircraft have to move to be not on ground + const bool vtol = model.isVtol(); if (!vtol && !this->isMoving()) { this->setOnGround(OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("No VTOL, not moving => on ground"); } return true; } + // not on ground is default + this->setOnGround(CAircraftSituation::NotOnGround, CAircraftSituation::OnGroundByGuessing); + + // guess some values we use for guessing + CLength cg = m_cg.isNull() ? model.getCG() : m_cg; + CSpeed guessedLiftOffSpeed; + CSpeed sureLiftOffSpeed = CSpeed(130, CSpeedUnit::kts()); + model.getAircraftIcaoCode().guessModelParameters(cg, guessedLiftOffSpeed); + if (!guessedLiftOffSpeed.isNull()) + { + // does the value make any sense? + const bool validGuessedSpeed = guessedLiftOffSpeed.value(CSpeedUnit::km_h()) > 5.0; + BLACK_VERIFY_X(validGuessedSpeed, Q_FUNC_INFO, "Wrong guessed value for lift off"); + if (!validGuessedSpeed) { guessedLiftOffSpeed = CSpeed(80, CSpeedUnit::kts()); } // fix + sureLiftOffSpeed = guessedLiftOffSpeed * 1.25; + } + + // "extreme" values for which we are surely not on ground + if (qAbs(this->getPitch().value(CAngleUnit::deg())) > 20) { if (details) { *details = QStringLiteral("max.pitch"); }; return true; } // some tail wheel aircraft already have 11° pitch on ground + if (qAbs(this->getBank().value(CAngleUnit::deg())) > 10) { if (details) { *details = QStringLiteral("max.bank"); }; return true; } + if (this->getGroundSpeed() > sureLiftOffSpeed) { if (details) { *details = QStringLiteral("max.gs. > ") % sureLiftOffSpeed.valueRoundedWithUnit(1); }; return true; } + + + // use the most accurate or reliable guesses here first + // ------------------------------------------------------ + // by elevation - // we can detect "on ground" but not "not on ground" because of overflow + // we can detect "on ground" (underflow, near ground), but not "not on ground" because of overflow + + // we can detect on ground for underflow, but not for overflow (so we can not rely on NotOnGround) IsOnGround og = this->isOnGroundByElevation(cg); if (og == OnGround) { + if (details) { *details = QStringLiteral("elevation on ground"); } this->setOnGround(og, CAircraftSituation::OnGroundByGuessing); return true; } - // we guess on speed, pitch and bank by excluding situations - this->setOnGround(CAircraftSituation::NotOnGround, CAircraftSituation::OnGroundByGuessing); - if (qAbs(this->getPitch().value(CAngleUnit::deg())) > 10) { return true; } - if (qAbs(this->getBank().value(CAngleUnit::deg())) > 10) { return true; } - if (this->getGroundSpeed().value(CSpeedUnit::km_h()) > 50) { return true; } + if (!change.isNull()) + { + if (!vtol && change.wasConstOnGround()) + { + if (change.isRotatingUp()) + { + // not OG + if (details) { *details = QStringLiteral("rotating up detected"); } + return true; + } + + // here we stick to ground until we detect rotate up + this->setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("waiting for rotating up"); } + return true; + } + + if (change.isConstAscending()) + { + // not OG + if (details) { *details = QStringLiteral("const ascending"); } + return true; + } + } // on VTOL we stop here - if (vtol) { return false; } + if (vtol) + { + // no idea + this->setOnGround(OnGroundSituationUnknown, NotSetGroundDetails); + return false; + } + + // guessed speed null -> vtol + if (!guessedLiftOffSpeed.isNull()) + { + // does the value make any sense? + if (this->getGroundSpeed() < guessedLiftOffSpeed) + { + this->setOnGround(OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("Guessing, max.guessed gs.") + guessedLiftOffSpeed.valueRoundedWithUnit(CSpeedUnit::kts(), 1); }; return true; + } + } // not sure, but this is a guess - this->setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("Fall through"); } return true; } @@ -370,6 +461,11 @@ namespace BlackMisc return this->onGroundAsString() % QLatin1Char(' ') % this->getOnDetailsAsString(); } + CAircraftSituation::IsOnGround CAircraftSituation::isOnGroundByElevation() const + { + return this->isOnGroundByElevation(m_cg); + } + CAircraftSituation::IsOnGround CAircraftSituation::isOnGroundByElevation(const CLength &cg) const { Q_ASSERT_X(!cg.isNegativeWithEpsilonConsidered(), Q_FUNC_INFO, "CG must not be negative"); @@ -444,12 +540,17 @@ namespace BlackMisc return this->getAltitude() - gh; } - CAltitude CAircraftSituation::getCorrectedAltitude(const CLength ¢erOfGravity, bool enableDragToGround, AltitudeCorrection *correctetion) const + CAltitude CAircraftSituation::getCorrectedAltitude(bool enableDragToGround, CAircraftSituation::AltitudeCorrection *correction) const { - if (correctetion) { *correctetion = UnknownCorrection; } + return this->getCorrectedAltitude(m_cg, enableDragToGround, correction); + } + + CAltitude CAircraftSituation::getCorrectedAltitude(const CLength ¢erOfGravity, bool enableDragToGround, AltitudeCorrection *correction) const + { + if (correction) { *correction = UnknownCorrection; } if (!this->hasGroundElevation()) { - if (correctetion) { *correctetion = NoElevation; } + if (correction) { *correction = NoElevation; } return this->getAltitude(); } @@ -457,7 +558,7 @@ namespace BlackMisc if (this->getAltitude().getReferenceDatum() == CAltitude::AboveGround) { BLACK_VERIFY_X(false, Q_FUNC_INFO, "Unsupported"); - if (correctetion) { *correctetion = AGL; } + if (correction) { *correction = AGL; } return this->getAltitude(); } else @@ -465,38 +566,44 @@ namespace BlackMisc const CAltitude groundPlusCG = this->getGroundElevation().withOffset(centerOfGravity); if (groundPlusCG.isNull()) { - if (correctetion) { *correctetion = NoElevation; } + if (correction) { *correction = NoElevation; } return this->getAltitude(); } const CLength groundDistance = this->getAltitude() - groundPlusCG; const bool underflow = groundDistance.isNegativeWithEpsilonConsidered(); if (underflow) { - if (correctetion) { *correctetion = Underflow; } + if (correction) { *correction = Underflow; } return groundPlusCG; } const bool nearGround = groundDistance.abs() < deltaNearGround(); if (nearGround) { - if (correctetion) { *correctetion = NoCorrection; } + if (correction) { *correction = NoCorrection; } return groundPlusCG; } const bool forceDragToGround = (enableDragToGround && this->getOnGround() == OnGround) && (this->hasInboundGroundDetails() || this->getOnGroundDetails() == OnGroundByGuessing); if (forceDragToGround) { - if (correctetion) { *correctetion = DraggedToGround; } + if (correction) { *correction = DraggedToGround; } return groundPlusCG; } - if (correctetion) { *correctetion = NoCorrection; } + if (correction) { *correction = NoCorrection; } return this->getAltitude(); } } + CAircraftSituation::AltitudeCorrection CAircraftSituation::correctAltitude(bool enableDragToGround) + { + return this->correctAltitude(m_cg, enableDragToGround); + } + CAircraftSituation::AltitudeCorrection CAircraftSituation::correctAltitude(const CLength ¢erOfGravity, bool enableDragToGround) { CAircraftSituation::AltitudeCorrection altCor = CAircraftSituation::UnknownCorrection; this->setAltitude(this->getCorrectedAltitude(centerOfGravity, enableDragToGround, &altCor)); + this->setCG(centerOfGravity); return altCor; } @@ -509,7 +616,7 @@ namespace BlackMisc bool CAircraftSituation::isMoving() const { const double gsKmh = this->getGroundSpeed().value(CSpeedUnit::km_h()); - return gsKmh >= 1.0; + return gsKmh >= 2.5; } bool CAircraftSituation::canLikelySkipNearGroundInterpolation() const diff --git a/src/blackmisc/aviation/aircraftsituation.h b/src/blackmisc/aviation/aircraftsituation.h index 50d59aaf6..7a09e2e48 100644 --- a/src/blackmisc/aviation/aircraftsituation.h +++ b/src/blackmisc/aviation/aircraftsituation.h @@ -38,10 +38,12 @@ namespace BlackMisc { namespace Geo { class CElevationPlane; } + namespace Simulation { class CAircraftModel; } namespace Aviation { class CAircraftParts; class CAircraftPartsList; + class CAircraftSituationChange; //! Value object encapsulating information of an aircraft's situation class BLACKMISC_EXPORT CAircraftSituation : @@ -127,6 +129,9 @@ namespace BlackMisc const PhysicalQuantities::CSpeed &gs = {}, const Geo::CElevationPlane &groundElevation = {}); + //! \copydoc Mixin::String::toQString + QString convertToQString(bool i18n = false) const; + //! \copydoc Mixin::Index::propertyByIndex CVariant propertyByIndex(const CPropertyIndex &index) const; @@ -142,6 +147,9 @@ namespace BlackMisc //! Position null? bool isPositionNull() const { return m_position.isNull(); } + //! Position or altitude null? + bool isPositionOrAltitudeNull() const { return this->isPositionNull() || this->getAltitude().isNull(); } + //! Null situation virtual bool isNull() const override; @@ -170,13 +178,13 @@ namespace BlackMisc bool isOnGroundInfoAvailable() const; //! Set on ground - void setOnGround(bool onGround); + bool setOnGround(bool onGround); //! Set on ground - void setOnGround(CAircraftSituation::IsOnGround onGround); + bool setOnGround(CAircraftSituation::IsOnGround onGround); //! Set on ground - void setOnGround(CAircraftSituation::IsOnGround onGround, CAircraftSituation::OnGroundDetails details); + bool setOnGround(CAircraftSituation::IsOnGround onGround, CAircraftSituation::OnGroundDetails details); //! On ground factor 0..1 (on ground), -1 not set double getOnGroundFactor() const { return m_onGroundFactor; } @@ -188,7 +196,7 @@ namespace BlackMisc bool shouldGuessOnGround() const; //! Guess on ground flag - bool guessOnGround(bool vtol = false, const PhysicalQuantities::CLength &cg = PhysicalQuantities::CLength::null()); + bool guessOnGround(const CAircraftSituationChange &change, const Simulation::CAircraftModel &model); //! Distance to ground, null if impossible to calculate PhysicalQuantities::CLength getGroundDistance(const PhysicalQuantities::CLength ¢erOfGravity) const; @@ -230,7 +238,10 @@ namespace BlackMisc const Geo::CElevationPlane &getGroundElevationPlane() const { return m_groundElevationPlane; } //! Is on ground by elevation data, requires elevation and CG + //! @{ + IsOnGround isOnGroundByElevation() const; IsOnGround isOnGroundByElevation(const PhysicalQuantities::CLength &cg) const; + //! @} //! Is ground elevation value available bool hasGroundElevation() const; @@ -268,10 +279,16 @@ namespace BlackMisc //! Get altitude under consideration of ground elevation and ground flag //! \remark with dragToGround it will also compensate overflows, otherwise only underflow - CAltitude getCorrectedAltitude(const PhysicalQuantities::CLength ¢erOfGravity = PhysicalQuantities::CLength::null(), bool enableDragToGround = true, AltitudeCorrection *correctetion = nullptr) const; + //! @{ + CAltitude getCorrectedAltitude(bool enableDragToGround = true, AltitudeCorrection *correction = nullptr) const; + CAltitude getCorrectedAltitude(const PhysicalQuantities::CLength ¢erOfGravity, bool enableDragToGround = true, AltitudeCorrection *correction = nullptr) const; + //! @} //! Set the corrected altitude from CAircraftSituation::getCorrectedAltitude + //! @{ + AltitudeCorrection correctAltitude(bool enableDragToGround = true); AltitudeCorrection correctAltitude(const PhysicalQuantities::CLength ¢erOfGravity = PhysicalQuantities::CLength::null(), bool enableDragToGround = true); + //! @} //! Set altitude void setAltitude(const CAltitude &altitude) { m_position.setGeodeticHeight(altitude); } @@ -318,6 +335,15 @@ namespace BlackMisc //! Corresponding callsign void setCallsign(const CCallsign &callsign); + //! Get CG if any + const PhysicalQuantities::CLength &getCG() const { return m_cg; } + + //! Set CG + void setCG(const PhysicalQuantities::CLength &cg) { m_cg = cg; } + + //! Has CG set? + bool hasCG() const { return !m_cg.isNull(); } + //! Set flag indicating this is an interim position update void setInterimFlag(bool flag) { m_isInterim = flag; } @@ -338,9 +364,6 @@ namespace BlackMisc //! Get flag indicating this is an interim position update bool isInterim() const { return m_isInterim; } - //! \copydoc Mixin::String::toQString - QString convertToQString(bool i18n = false) const; - //! Enum to string static const QString &isOnGroundToString(IsOnGround onGround); @@ -359,6 +382,32 @@ namespace BlackMisc //! A default CG if not other value is available static const PhysicalQuantities::CLength &defaultCG(); + //! Both on ground + static bool isGfEqualOnGround(double oldGroundFactor, double newGroundFactor) + { + return isDoubleEpsilonEqual(1.0, oldGroundFactor) && isDoubleEpsilonEqual(1.0, newGroundFactor); + } + + //! Ground flag comparisons @{ + //! Both not on ground + static bool isGfEqualAirborne(double oldGroundFactor, double newGroundFactor) + { + return isDoubleEpsilonEqual(0.0, oldGroundFactor) && isDoubleEpsilonEqual(0.0, newGroundFactor); + } + + //! Plane is starting + static bool isGfStarting(double oldGroundFactor, double newGroundFactor) + { + return isDoubleEpsilonEqual(0.0, oldGroundFactor) && isDoubleEpsilonEqual(1.0, newGroundFactor); + } + + //! Plane is landing + static bool isGfLanding(double oldGroundFactor, double newGroundFactor) + { + return isDoubleEpsilonEqual(1.0, oldGroundFactor) && isDoubleEpsilonEqual(0.0, newGroundFactor); + } + //! @} + private: CCallsign m_correspondingCallsign; Geo::CCoordinateGeodetic m_position; //!< NULL position as default @@ -367,11 +416,19 @@ namespace BlackMisc PhysicalQuantities::CAngle m_pitch { 0, nullptr }; PhysicalQuantities::CAngle m_bank { 0, nullptr }; PhysicalQuantities::CSpeed m_groundSpeed { 0, nullptr }; - bool m_isInterim = false; + PhysicalQuantities::CLength m_cg { 0, nullptr }; Geo::CElevationPlane m_groundElevationPlane; //!< NULL elevation as default + bool m_isInterim = false; int m_onGround = static_cast(CAircraftSituation::OnGroundSituationUnknown); int m_onGroundDetails = static_cast(CAircraftSituation::NotSetGroundDetails); double m_onGroundFactor = -1; //!< interpolated ground flag, 1..on ground, 0..not on ground, -1 no info + QString m_onGroundGuessingDetails; //!< only for debugging, not transferred via DBus etc. + + //! Equal double values? + static bool isDoubleEpsilonEqual(double d1, double d2) + { + return qAbs(d1 - d2) <= std::numeric_limits::epsilon(); + } BLACK_METACLASS( CAircraftSituation, @@ -382,6 +439,7 @@ namespace BlackMisc BLACK_METAMEMBER(pitch), BLACK_METAMEMBER(bank), BLACK_METAMEMBER(groundSpeed), + BLACK_METAMEMBER(cg), BLACK_METAMEMBER(groundElevationPlane), BLACK_METAMEMBER(onGround), BLACK_METAMEMBER(onGroundDetails), @@ -397,5 +455,6 @@ namespace BlackMisc Q_DECLARE_METATYPE(BlackMisc::Aviation::CAircraftSituation) Q_DECLARE_METATYPE(BlackMisc::Aviation::CAircraftSituation::IsOnGround) Q_DECLARE_METATYPE(BlackMisc::Aviation::CAircraftSituation::OnGroundDetails) +Q_DECLARE_METATYPE(BlackMisc::Aviation::CAircraftSituation::AltitudeCorrection) #endif // guard diff --git a/src/blackmisc/aviation/aircraftsituationlist.cpp b/src/blackmisc/aviation/aircraftsituationlist.cpp index 3a7808cd9..82148731e 100644 --- a/src/blackmisc/aviation/aircraftsituationlist.cpp +++ b/src/blackmisc/aviation/aircraftsituationlist.cpp @@ -7,13 +7,19 @@ * contained in the LICENSE file. */ +#include "blackmisc/simulation/aircraftmodel.h" +#include "blackmisc/aviation/aircraftsituationchange.h" #include "blackmisc/aviation/aircraftsituationlist.h" -#include "blackmisc/aviation/aircraftsituation.h" #include "blackmisc/geo/elevationplane.h" +#include "blackmisc/math/mathutils.h" +#include "blackmisc/pq/speed.h" +#include "blackmisc/verify.h" #include -using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Geo; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Simulation; +using namespace BlackMisc::Math; namespace BlackMisc { @@ -43,19 +49,30 @@ namespace BlackMisc return c; } - int CAircraftSituationList::setGroundElevationCheckedAndGuessGround(const CElevationPlane &elevationPlane, bool isVtol, const CLength &cg) + int CAircraftSituationList::setGroundElevationCheckedAndGuessGround(const CElevationPlane &elevationPlane, const CAircraftModel &model) { if (elevationPlane.isNull()) { return 0; } + if (this->isEmpty()) { return 0; } + + Q_ASSERT_X(this->isSortedAdjustedLatestFirstWithoutNullPositions(), Q_FUNC_INFO, "Need sorted situations without NULL positions"); + const CAircraftSituationChange change(*this, true, true); int c = 0; + bool first = true; + if (this->front().getCallsign().equalsString("AFL2353")) + { + c = 0; + } + for (CAircraftSituation &s : *this) { const bool set = s.setGroundElevationChecked(elevationPlane); - if (!set) { continue; } - if (s.shouldGuessOnGround()) + if (set) { - s.guessOnGround(isVtol, cg); + // change is only valid for the latest situation + s.guessOnGround(first ? change : CAircraftSituationChange::null(), model); + c++; } - c++; + first = false; } return c; } @@ -125,6 +142,179 @@ namespace BlackMisc return true; } + bool CAircraftSituationList::isConstOnGround() const + { + if (this->isEmpty()) { return false; } + if (this->containsNullPositionOrHeight()) { return false; } + for (const CAircraftSituation &situation : *this) + { + const CAircraftSituation::IsOnGround og = situation.getOnGround(); + if (og != CAircraftSituation::OnGround) { return false; } + } + return true; + } + + bool CAircraftSituationList::isConstNotOnGround() const + { + if (this->isEmpty()) { return false; } + if (this->containsNullPositionOrHeight()) { return false; } + for (const CAircraftSituation &situation : *this) + { + const CAircraftSituation::IsOnGround og = situation.getOnGround(); + if (og != CAircraftSituation::NotOnGround) { return false; } + } + return true; + } + + bool CAircraftSituationList::isConstDescending(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + if (this->containsNullPositionOrHeight()) { return false; } + + const CAircraftSituationList sorted(alreadySortedLatestFirst ? (*this) : this->getSortedAdjustedLatestFirst()); + CAircraftSituation newerSituation = CAircraftSituation::null(); + for (const CAircraftSituation &situation : sorted) + { + if (!newerSituation.isNull()) + { + Q_ASSERT_X(situation.getAltitude().getReferenceDatum() == newerSituation.getAltitude().getReferenceDatum(), Q_FUNC_INFO, "Wrong reference"); + const CLength delta = newerSituation.getAltitude() - situation.getAltitude(); + if (!delta.isNegativeWithEpsilonConsidered()) { return false; } + } + newerSituation = situation; + } + return true; + } + + bool CAircraftSituationList::isConstAscending(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + if (this->containsNullPositionOrHeight()) { return false; } + + const CAircraftSituationList sorted(alreadySortedLatestFirst ? (*this) : this->getSortedAdjustedLatestFirst()); + CAircraftSituation newerSituation = CAircraftSituation::null(); + for (const CAircraftSituation &situation : sorted) + { + if (!newerSituation.isNull()) + { + Q_ASSERT_X(situation.getAltitude().getReferenceDatum() == newerSituation.getAltitude().getReferenceDatum(), Q_FUNC_INFO, "Wrong reference"); + const CLength delta = newerSituation.getAltitude() - situation.getAltitude(); + if (!delta.isPositiveWithEpsilonConsidered()) { return false; } + } + newerSituation = situation; + } + return true; + } + + bool CAircraftSituationList::isConstAccelerating(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + if (this->containsNullPositionOrHeight()) { return false; } + + const CAircraftSituationList sorted(alreadySortedLatestFirst ? (*this) : this->getSortedAdjustedLatestFirst()); + CSpeed newerGs = CSpeed::null(); + for (const CAircraftSituation &situation : sorted) + { + if (!newerGs.isNull()) + { + const CSpeed deltaSpeed = newerGs - situation.getGroundSpeed(); + if (!deltaSpeed.isPositiveWithEpsilonConsidered()) { return false; } + } + newerGs = situation.getGroundSpeed(); + } + return true; + } + + bool CAircraftSituationList::isConstDecelarating(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + if (this->containsNullPositionOrHeight()) { return false; } + + const CAircraftSituationList sorted(alreadySortedLatestFirst ? (*this) : this->getSortedAdjustedLatestFirst()); + CSpeed newerGs = CSpeed::null(); + for (const CAircraftSituation &situation : sorted) + { + if (!newerGs.isNull()) + { + const CSpeed deltaSpeed = newerGs - situation.getGroundSpeed(); + if (!deltaSpeed.isNegativeWithEpsilonConsidered()) { return false; } + } + newerGs = situation.getGroundSpeed(); + } + return true; + } + + bool CAircraftSituationList::isGndFlagChanging(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + + const CAircraftSituationList sorted(this->getLatestAdjustedTwoObjects(alreadySortedLatestFirst)); + const CAircraftSituation s1 = sorted.front(); + const CAircraftSituation s2 = sorted.back(); + return (s1.getOnGround() == CAircraftSituation::OnGround && s2.getOnGround() == CAircraftSituation::NotOnGround) || + (s2.getOnGround() == CAircraftSituation::OnGround && s1.getOnGround() == CAircraftSituation::NotOnGround); + } + + bool CAircraftSituationList::isJustTakingOff(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + + const CAircraftSituationList sorted(this->getLatestAdjustedTwoObjects(alreadySortedLatestFirst)); + const CAircraftSituation latest = sorted.front(); + const CAircraftSituation oldest = sorted.back(); + return (latest.getOnGround() == CAircraftSituation::NotOnGround && oldest.getOnGround() == CAircraftSituation::OnGround); + } + + bool CAircraftSituationList::isJustTouchingDown(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + + const CAircraftSituationList sorted(this->getLatestAdjustedTwoObjects(alreadySortedLatestFirst)); + const CAircraftSituation latest = sorted.front(); + const CAircraftSituation oldest = sorted.back(); + return (latest.getOnGround() == CAircraftSituation::OnGround && oldest.getOnGround() == CAircraftSituation::NotOnGround); + } + + bool CAircraftSituationList::isRotatingUp(bool alreadySortedLatestFirst) const + { + if (this->size() < 2) { return false; } + const CAircraftSituationList sorted(alreadySortedLatestFirst ? (*this) : this->getSortedAdjustedLatestFirst()); + const QList pitches = sorted.pitchValues(CAngleUnit::deg()); + const QPair stdDevAndMean = CMathUtils::standardDeviationAndMean(pitches); + const double minRotate = stdDevAndMean.first + stdDevAndMean.second; // outside std deviation range + const bool rotate = pitches.front() > minRotate; + return rotate; + } + + bool CAircraftSituationList::containsPushBack() const + { + for (const CAircraftSituation &situation : *this) + { + if (situation.getGroundSpeed().isNegativeWithEpsilonConsidered()) { return true; } + } + return false; + } + + int CAircraftSituationList::countOnGround(CAircraftSituation::IsOnGround og) const + { + int c = 0; + for (const CAircraftSituation &situation : *this) + { + if (situation.getOnGround() == og) { c++; } + } + return c; + } + + int CAircraftSituationList::setOnGround(CAircraftSituation::IsOnGround og) + { + int c = 0; + for (CAircraftSituation &situation : *this) + { + if (situation.setOnGround(og)) { c++; } + } + return c; + } + int CAircraftSituationList::setOnGroundDetails(CAircraftSituation::OnGroundDetails details) { int c = 0; @@ -134,5 +324,74 @@ namespace BlackMisc } return c; } + + bool CAircraftSituationList::isSortedAdjustedLatestFirstWithoutNullPositions() const + { + return this->isSortedAdjustedLatestFirst() && !this->containsNullPosition(); + } + + CAircraftSituationList CAircraftSituationList::withoutFrontSituation() const + { + if (this->empty()) { return CAircraftSituationList(); } + CAircraftSituationList copy(*this); + copy.pop_front(); + return copy; + } + + QList CAircraftSituationList::pitchValues(const CAngleUnit &unit) const + { + QList values; + for (const CAircraftSituation &s : *this) + { + values.push_back(s.getPitch().value(unit)); + } + return values; + } + + QList CAircraftSituationList::groundSpeedValues(const CSpeedUnit &unit) const + { + QList values; + for (const CAircraftSituation &s : *this) + { + if (s.getGroundSpeed().isNull()) { continue; } + values.push_back(s.getGroundSpeed().value(unit)); + } + return values; + } + + QList CAircraftSituationList::elevationValues(const CLengthUnit &unit) const + { + QList values; + for (const CAircraftSituation &s : *this) + { + if (s.getGroundElevation().isNull()) { continue; } + values.push_back(s.getGroundElevation().value(unit)); + } + return values; + } + + QList CAircraftSituationList::altitudeValues(const CLengthUnit &unit) const + { + QList values; + for (const CAircraftSituation &s : *this) + { + const CAltitude alt(s.getAltitude()); + if (alt.isNull()) { continue; } + values.push_back(alt.value(unit)); + } + return values; + } + + QList CAircraftSituationList::correctedAltitudeValues(const CLengthUnit &unit, const CLength &cg) const + { + QList values; + for (const CAircraftSituation &s : *this) + { + const CAltitude alt(s.getCorrectedAltitude(cg)); + if (alt.isNull()) { continue; } + values.push_back(alt.value(unit)); + } + return values; + } } // namespace } // namespace diff --git a/src/blackmisc/aviation/aircraftsituationlist.h b/src/blackmisc/aviation/aircraftsituationlist.h index 2f4db283b..22adcef1c 100644 --- a/src/blackmisc/aviation/aircraftsituationlist.h +++ b/src/blackmisc/aviation/aircraftsituationlist.h @@ -23,10 +23,12 @@ #include "blackmisc/variant.h" #include +#include namespace BlackMisc { namespace Geo { class CElevationPlane; } + namespace Simulation { class CAircraftModel; } namespace Aviation { class CAircraftParts; @@ -54,8 +56,9 @@ namespace BlackMisc //! Set ground elevation from elevation plane int setGroundElevationChecked(const Geo::CElevationPlane &elevationPlane, qint64 newerThanAdjustedMs = -1); - //! Set ground elevation from elevation plane - int setGroundElevationCheckedAndGuessGround(const Geo::CElevationPlane &elevationPlane, bool isVtol, const PhysicalQuantities::CLength &cg); + //! Set ground elevation from elevation plane and guess ground + //! \note requires a sorted list latest first + int setGroundElevationCheckedAndGuessGround(const Geo::CElevationPlane &elevationPlane, const Simulation::CAircraftModel &model); //! Adjust flag from parts by using CAircraftSituation::adjustGroundFlag int adjustGroundFlag(const CAircraftParts &parts, double timeDeviationFactor = 0.1); @@ -78,8 +81,70 @@ namespace BlackMisc //! Are all on ground details the same bool areAllOnGroundDetailsSame(CAircraftSituation::OnGroundDetails details) const; + //! Are all situations on ground? + bool isConstOnGround() const; + + //! Are all situations not on ground? + bool isConstNotOnGround() const; + + //! Constantly descending? + bool isConstDescending(bool alreadySortedLatestFirst = false) const; + + //! Constantly ascending? + bool isConstAscending(bool alreadySortedLatestFirst = false) const; + + //! Constantly accelerating? + bool isConstAccelerating(bool alreadySortedLatestFirst = false) const; + + //! Constantly decelarating? + bool isConstDecelarating(bool alreadySortedLatestFirst = false) const; + + //! Is the ground flag changing for the recent situations + bool isGndFlagChanging(bool alreadySortedLatestFirst = false) const; + + //! Is just taking off? + bool isJustTakingOff(bool alreadySortedLatestFirst = false) const; + + //! Is just touch down? + bool isJustTouchingDown(bool alreadySortedLatestFirst = false) const; + + //! Is rotating up? + bool isRotatingUp(bool alreadySortedLatestFirst = false) const; + + //! Contains any push back + //! \remark only valid for non VTOL aircraft + bool containsPushBack() const; + + //! Count the number of situations with CAircraftSituation::IsOnGround + int countOnGround(CAircraftSituation::IsOnGround og) const; + + //! Set on ground + int setOnGround(CAircraftSituation::IsOnGround og); + //! Set on ground details for all situations int setOnGroundDetails(CAircraftSituation::OnGroundDetails details); + + //! Latest first and no null positions? + bool isSortedAdjustedLatestFirstWithoutNullPositions() const; + + //! Remove the first situation + //! \remark normally used when the first situation represents the latest situation + CAircraftSituationList withoutFrontSituation() const; + + //! All pitch values + QList pitchValues(const PhysicalQuantities::CAngleUnit &unit) const; + + //! All ground speed values + QList groundSpeedValues(const PhysicalQuantities::CSpeedUnit &unit) const; + + //! All elevation values + QList elevationValues(const PhysicalQuantities::CLengthUnit &unit) const; + + //! All corrected altitude values + QList altitudeValues(const PhysicalQuantities::CLengthUnit &unit) const; + + //! All corrected altitude values + QList correctedAltitudeValues(const PhysicalQuantities::CLengthUnit &unit, const PhysicalQuantities::CLength &cg) const; }; } // namespace } // namespace diff --git a/src/blackmisc/geo/geoobjectlist.cpp b/src/blackmisc/geo/geoobjectlist.cpp index c4bc14f33..eecc9c8c4 100644 --- a/src/blackmisc/geo/geoobjectlist.cpp +++ b/src/blackmisc/geo/geoobjectlist.cpp @@ -69,6 +69,24 @@ namespace BlackMisc }); } + template + bool IGeoObjectList::containsNullPosition() const + { + return this->container().containsBy([&](const ICoordinateGeodetic & geoObj) + { + return geoObj.isNull(); + }); + } + + template + bool IGeoObjectList::containsNullPositionOrHeight() const + { + return this->container().containsBy([&](const ICoordinateGeodetic & geoObj) + { + return geoObj.isNull() || geoObj.isGeodeticHeightNull(); + }); + } + template typename IGeoObjectList::MinMaxAverageHeight IGeoObjectList::findMinMaxAverageHeight() const { diff --git a/src/blackmisc/geo/geoobjectlist.h b/src/blackmisc/geo/geoobjectlist.h index 0d0a5bea1..cdcc5d392 100644 --- a/src/blackmisc/geo/geoobjectlist.h +++ b/src/blackmisc/geo/geoobjectlist.h @@ -66,9 +66,15 @@ namespace BlackMisc //! Elements with geodetic height (only MSL) CONTAINER findWithGeodeticMSLHeight() const; - //! Any object in range + //! Any object in range? bool containsObjectInRange(const ICoordinateGeodetic &coordinate, const PhysicalQuantities::CLength &range) const; + //! Any NULL position? + bool containsNullPosition() const; + + //! Any NULL position or NULL height + bool containsNullPositionOrHeight() const; + //! Find min/max/average height MinMaxAverageHeight findMinMaxAverageHeight() const;