diff --git a/cppcheck.supp b/cppcheck.supp index 735246a4b..7cb061761 100644 --- a/cppcheck.supp +++ b/cppcheck.supp @@ -5,6 +5,10 @@ // [error id]:[filename2] // [error id] +// Issues introduced by refactoring - expected to be fixed +knownConditionTrueFalse:src/blackmisc/simulation/interpolator.cpp +knownConditionTrueFalse:src/blackmisc/aviation/aircraftsituationchange.cpp + // Ignore noExplicitConstructor for now. noExplicitConstructor diff --git a/src/blackcore/airspacemonitor.cpp b/src/blackcore/airspacemonitor.cpp index c20907fad..38f911f13 100644 --- a/src/blackcore/airspacemonitor.cpp +++ b/src/blackcore/airspacemonitor.cpp @@ -1215,6 +1215,44 @@ namespace BlackCore return aircraft; } + bool CAirspaceMonitor::extrapolateElevation(CAircraftSituationList &situations, const CAircraftSituationChange &change) + { + if (situations.size() < 3) { return false; } + //Q_ASSERT_X(situations.m_tsAdjustedSortHint == CAircraftSituationList::AdjustedTimestampLatestFirst, Q_FUNC_INFO, "Need latest first"); + const CAircraftSituation old = situations[1]; + const CAircraftSituation older = situations[2]; + return extrapolateElevation(situations.front(), old, older, change); + } + + bool CAirspaceMonitor::extrapolateElevation(CAircraftSituation &situationToBeUpdated, const CAircraftSituation &oldSituation, const CAircraftSituation &olderSituation, const CAircraftSituationChange &oldChange) + { + if (situationToBeUpdated.hasGroundElevation()) { return false; } + + // if acceptable transfer + if (oldSituation.transferGroundElevationFromMe(situationToBeUpdated)) + { + // change or keep type is the question + // situationToBeUpdated.setGroundElevationInfo(Extrapolated); + return true; + } + if (oldSituation.isNull() || olderSituation.isNull()) { return false; } + + if (oldChange.isNull()) { return false; } + if (oldChange.isConstOnGround() && oldChange.hasAltitudeDevWithinAllowedRange() && oldChange.hasElevationDevWithinAllowedRange()) + { + // we have almost const altitudes and elevations + const double deltaAltFt = qAbs(situationToBeUpdated.getAltitude().value(CLengthUnit::ft()) - olderSituation.getAltitude().value(CLengthUnit::ft())); + if (deltaAltFt <= CAircraftSituation::allowedAltitudeDeviation().value(CLengthUnit::ft())) + { + // the current alt is also not much different + situationToBeUpdated.setGroundElevation(oldSituation.getGroundElevation(), CAircraftSituation::Extrapolated); + return true; + } + } + + return false; + } + void CAirspaceMonitor::onAircraftUpdateReceived(const CAircraftSituation &situation, const CTransponder &transponder) { Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Called in different thread"); @@ -1539,7 +1577,7 @@ namespace BlackCore // values before updating (i.e. "storing") so the new situation is not yet considered if (situationsBeforeStoring.size() > 1) { - const bool extrapolated = correctedSituation.extrapolateElevation(situationsBeforeStoring[0], situationsBeforeStoring[1], changesBeforeStoring); + const bool extrapolated = extrapolateElevation(correctedSituation, situationsBeforeStoring[0], situationsBeforeStoring[1], changesBeforeStoring); triedExtrapolation = true; couldNotExtrapolate = !extrapolated; fromWhere = 20; diff --git a/src/blackcore/airspacemonitor.h b/src/blackcore/airspacemonitor.h index 0f6c7221d..0dbe80723 100644 --- a/src/blackcore/airspacemonitor.h +++ b/src/blackcore/airspacemonitor.h @@ -419,6 +419,16 @@ namespace BlackCore //! Set matching readiness flag Readiness &addMatchingReadinessFlag(const BlackMisc::Aviation::CCallsign &callsign, MatchingReadinessFlag mrf); + //! Extrapolates elevation into front (first) element from 2nd and 3rd element + //! \pre the list must be sorted latest first and containt at least 3 elements + static bool extrapolateElevation(BlackMisc::Aviation::CAircraftSituationList &situations, const BlackMisc::Aviation::CAircraftSituationChange &change); + + //! Extrapolated between the 2 situations for situation + //! \remark normally used if situationToBeUpdated is not between oldSituation and olderSituation (that would be interpolation) + //! \return false if there are no two elevations, there is already an elevation, or no extrapolation is possible (too much deviation) + static bool extrapolateElevation(BlackMisc::Aviation::CAircraftSituation &situationToBeUpdated, const BlackMisc::Aviation::CAircraftSituation &oldSituation, + const BlackMisc::Aviation::CAircraftSituation &olderSituation, const BlackMisc::Aviation::CAircraftSituationChange &oldChange); + //! Create aircraft in range, this is the only place where a new aircraft should be added void onAircraftUpdateReceived(const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); diff --git a/src/blackmisc/aviation/aircraftlights.cpp b/src/blackmisc/aviation/aircraftlights.cpp index 7a3e6e468..ac2e1524d 100644 --- a/src/blackmisc/aviation/aircraftlights.cpp +++ b/src/blackmisc/aviation/aircraftlights.cpp @@ -7,13 +7,10 @@ */ #include "blackmisc/aviation/aircraftlights.h" -#include "blackmisc/aviation/aircraftsituation.h" #include "blackmisc/stringutils.h" #include "blackmisc/comparefunctions.h" #include -using namespace BlackMisc::PhysicalQuantities; - namespace BlackMisc { namespace Aviation @@ -36,52 +33,6 @@ namespace BlackMisc return CAircraftLights {false, false, false, false, false, false, false, false}; } - CAircraftLights CAircraftLights::guessedLights(const CAircraftSituation &situation) - { - const bool isOnGround = situation.getOnGround() == CAircraftSituation::OnGround; - const double gsKts = situation.getGroundSpeed().value(CSpeedUnit::kts()); - CAircraftLights lights; - lights.setCabinOn(true); - lights.setRecognitionOn(true); - - // when first detected moving, lights on - if (isOnGround) - { - lights.setTaxiOn(true); - lights.setBeaconOn(true); - lights.setNavOn(true); - - if (gsKts > 5) - { - // mode taxi - lights.setTaxiOn(true); - lights.setLandingOn(false); - } - else if (gsKts > 30) - { - // mode accelaration for takeoff - lights.setTaxiOn(false); - lights.setLandingOn(true); - } - else - { - // slow movements or parking - lights.setTaxiOn(false); - lights.setLandingOn(false); - } - } - else - { - // not on ground - lights.setTaxiOn(false); - lights.setBeaconOn(true); - lights.setNavOn(true); - // landing lights for < 10000ft (normally MSL, here ignored) - lights.setLandingOn(situation.getAltitude().value(CLengthUnit::ft()) < 10000); - } - return lights; - } - QString CAircraftLights::convertToQString(bool i18n) const { Q_UNUSED(i18n); @@ -178,10 +129,5 @@ namespace BlackMisc m_recognition = false; m_cabin = false; } - - void CAircraftLights::guessLights(const CAircraftSituation &situation) - { - *this = guessedLights(situation); - } } // namespace } // namespace diff --git a/src/blackmisc/aviation/aircraftlights.h b/src/blackmisc/aviation/aircraftlights.h index bf1b41aa6..2adcf5013 100644 --- a/src/blackmisc/aviation/aircraftlights.h +++ b/src/blackmisc/aviation/aircraftlights.h @@ -23,8 +23,6 @@ namespace BlackMisc { namespace Aviation { - class CAircraftSituation; - //! Value object encapsulating information about aircraft's lights class BLACKMISC_EXPORT CAircraftLights : public CValueObject { @@ -109,9 +107,6 @@ namespace BlackMisc //! All off void setAllOff(); - //! Guess the lights - void guessLights(const CAircraftSituation &situation); - //! \copydoc BlackMisc::Mixin::Index::propertyByIndex QVariant propertyByIndex(CPropertyIndexRef index) const; @@ -130,9 +125,6 @@ namespace BlackMisc //! Returns object with all lights switched off static CAircraftLights allLightsOff(); - //! Guessed lights - static CAircraftLights guessedLights(const CAircraftSituation &situation); - //! Null? bool isNull() const { return m_isNull; } diff --git a/src/blackmisc/aviation/aircraftparts.cpp b/src/blackmisc/aviation/aircraftparts.cpp index 16c21a8fb..52d69f217 100644 --- a/src/blackmisc/aviation/aircraftparts.cpp +++ b/src/blackmisc/aviation/aircraftparts.cpp @@ -9,8 +9,6 @@ #include "blackmisc/simulation/aircraftmodel.h" #include "aircraftparts.h" #include "aircraftlights.h" -#include "aircraftsituation.h" -#include "aircraftsituationchange.h" #include "blackmisc/comparefunctions.h" #include "blackmisc/stringutils.h" #include "blackmisc/verify.h" @@ -88,135 +86,6 @@ namespace BlackMisc return null; } - CAircraftParts CAircraftParts::guessedParts(const CAircraftSituation &situation, const CAircraftSituationChange &change, const CAircraftModel &model) - { - CAircraftParts parts; - parts.setMSecsSinceEpoch(situation.getMSecsSinceEpoch()); - parts.setTimeOffsetMs(situation.getTimeOffsetMs()); - parts.setPartsDetails(GuessedParts); - parts.setLights(CAircraftLights::guessedLights(situation)); - - QString *details = CBuildConfig::isLocalDeveloperDebugBuild() ? &parts.m_guessingDetails : nullptr; - - CAircraftEngineList engines; - const bool vtol = model.isVtol(); - const int engineCount = model.getEngineCount(); - CSpeed guessedVRotate = CSpeed::null(); - CLength guessedCG = model.getCG(); - model.getAircraftIcaoCode().guessModelParameters(guessedCG, guessedVRotate); - - if (situation.getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails) - { - do - { - // set some reasonable values - const bool isOnGround = situation.isOnGround(); - engines.initEngines(engineCount, !isOnGround || situation.isMoving()); - parts.setGearDown(isOnGround); - parts.setSpoilersOut(false); - parts.setEngines(engines); - - if (!change.isNull()) - { - if (change.isConstDecelarating()) - { - parts.setSpoilersOut(true); - parts.setFlapsPercent(10); - break; - } - } - - const CSpeed slowSpeed = guessedVRotate * 0.30; - if (situation.getGroundSpeed() < slowSpeed) - { - if (details) { *details += u"slow speed <" % slowSpeed.valueRoundedWithUnit(1) % u" on ground"; } - parts.setFlapsPercent(0); - break; - } - else - { - if (details) { *details += u"faster speed >" % slowSpeed.valueRoundedWithUnit(1) % u" on ground"; } - parts.setFlapsPercent(0); - break; - } - } - while (false); - } - else - { - if (details) { *details = QStringLiteral("no ground info"); } - - // no idea if on ground or not - engines.initEngines(engineCount, true); - parts.setEngines(engines); - parts.setGearDown(true); - parts.setSpoilersOut(false); - } - - const double pitchDeg = situation.getPitch().value(CAngleUnit::deg()); - const bool isLikelyTakeOffOrClimbing = change.isNull() ? pitchDeg > 20 : (change.isRotatingUp() || change.isConstAscending()); - const bool isLikelyLanding = change.isNull() ? false : change.isConstDescending(); - - if (situation.hasGroundElevation()) - { - const CLength aboveGnd = situation.getHeightAboveGround(); - if (aboveGnd.isNull() || std::isnan(aboveGnd.value())) - { - BLACK_VERIFY_X(false, Q_FUNC_INFO, "above gnd.is null"); - return parts; - } - - const double nearGround1Ft = 300; - const double nearGround2Ft = isLikelyTakeOffOrClimbing ? 500 : 1000; - const double aGroundFt = aboveGnd.value(CLengthUnit::ft()); - static const QString detailsInfo("above ground: %1ft near grounds: %2ft %3ft likely takeoff: %4 likely landing: %5"); - - if (details) { *details = detailsInfo.arg(aGroundFt).arg(nearGround1Ft).arg(nearGround2Ft).arg(boolToYesNo(isLikelyTakeOffOrClimbing), boolToYesNo(isLikelyLanding)); } - if (aGroundFt < nearGround1Ft) - { - if (details) { details->prepend(QStringLiteral("near ground: ")); } - parts.setGearDown(true); - parts.setFlapsPercent(25); - } - else if (aGroundFt < nearGround2Ft) - { - if (details) { details->prepend(QStringLiteral("2nd layer: ")); } - const bool gearDown = !isLikelyTakeOffOrClimbing && (situation.getGroundSpeed() < guessedVRotate || isLikelyLanding); - parts.setGearDown(gearDown); - parts.setFlapsPercent(10); - } - else - { - if (details) { details->prepend(QStringLiteral("airborne: ")); } - parts.setGearDown(false); - parts.setFlapsPercent(0); - } - } - else - { - if (situation.getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails) - { - // we have no ground elevation but a ground info - if (situation.getOnGroundDetails() == CAircraftSituation::OnGroundByGuessing) - { - // should be OK - if (details) { *details = QStringLiteral("on ground, no elv."); } - } - else - { - if (!vtol) - { - const bool gearDown = situation.getGroundSpeed() < guessedVRotate; - parts.setGearDown(gearDown); - if (details) { *details = QStringLiteral("not on ground elv., gs < ") + guessedVRotate.valueRoundedWithUnit(1); } - } - } - } - } - - return parts; - } - const QString &CAircraftParts::partsDetailsToString(CAircraftParts::PartsDetails details) { static const QString guessed("guessed"); @@ -341,10 +210,5 @@ namespace BlackMisc engines.setEngines(engine, engineNumber); m_engines = engines; } - - void CAircraftParts::guessParts(const CAircraftSituation &situation, const CAircraftSituationChange &change, const CAircraftModel &model) - { - *this = CAircraftParts::guessedParts(situation, change, model); - } } // namespace } // namespace diff --git a/src/blackmisc/aviation/aircraftparts.h b/src/blackmisc/aviation/aircraftparts.h index da0e565c5..8f22ae40e 100644 --- a/src/blackmisc/aviation/aircraftparts.h +++ b/src/blackmisc/aviation/aircraftparts.h @@ -27,9 +27,6 @@ namespace BlackMisc namespace Simulation { class CAircraftModel; } namespace Aviation { - class CAircraftSituation; - class CAircraftSituationChange; - //! Value object encapsulating information of aircraft's parts class BLACKMISC_EXPORT CAircraftParts : public CValueObject, @@ -158,9 +155,6 @@ namespace BlackMisc //! Set parts details void setPartsDetails(PartsDetails details) { m_partsDetails = static_cast(details); } - //! Guess the parts - void guessParts(const CAircraftSituation &situation, const CAircraftSituationChange &change, const Simulation::CAircraftModel &model); - //! \copydoc BlackMisc::Mixin::String::toQString QString convertToQString(bool i18n = false) const; @@ -181,9 +175,6 @@ namespace BlackMisc //! NULL parts object static const CAircraftParts &null(); - //! Guessed parts - static CAircraftParts guessedParts(const CAircraftSituation &situation, const CAircraftSituationChange &change, const Simulation::CAircraftModel &model); - //! Convert to QString static const QString &partsDetailsToString(PartsDetails details); diff --git a/src/blackmisc/aviation/aircraftsituation.cpp b/src/blackmisc/aviation/aircraftsituation.cpp index 3454b0658..e0b5ce8c9 100644 --- a/src/blackmisc/aviation/aircraftsituation.cpp +++ b/src/blackmisc/aviation/aircraftsituation.cpp @@ -8,8 +8,8 @@ #include "blackmisc/simulation/aircraftmodel.h" #include "blackmisc/aviation/aircraftsituation.h" -#include "blackmisc/aviation/aircraftsituationchange.h" #include "blackmisc/aviation/aircraftpartslist.h" +#include "blackmisc/aviation/aircraftlights.h" #include "blackmisc/geo/elevationplane.h" #include "blackmisc/pq/length.h" #include "blackmisc/pq/units.h" @@ -31,6 +31,59 @@ namespace BlackMisc { namespace Aviation { + const CLength &CAircraftSituation::allowedAltitudeDeviation() + { + // approx. 1 meter + static const CLength allowedStdDev(3, CLengthUnit::ft()); + return allowedStdDev; + } + + CAircraftLights CAircraftSituation::guessLights() const + { + const bool isOnGround = getOnGround() == CAircraftSituation::OnGround; + const double gsKts = getGroundSpeed().value(CSpeedUnit::kts()); + CAircraftLights lights; + lights.setCabinOn(true); + lights.setRecognitionOn(true); + + // when first detected moving, lights on + if (isOnGround) + { + lights.setTaxiOn(true); + lights.setBeaconOn(true); + lights.setNavOn(true); + + if (gsKts > 5) + { + // mode taxi + lights.setTaxiOn(true); + lights.setLandingOn(false); + } + else if (gsKts > 30) + { + // mode accelaration for takeoff + lights.setTaxiOn(false); + lights.setLandingOn(true); + } + else + { + // slow movements or parking + lights.setTaxiOn(false); + lights.setLandingOn(false); + } + } + else + { + // not on ground + lights.setTaxiOn(false); + lights.setBeaconOn(true); + lights.setNavOn(true); + // landing lights for < 10000ft (normally MSL, here ignored) + lights.setLandingOn(getAltitude().value(CLengthUnit::ft()) < 10000); + } + return lights; + } + void CAircraftSituation::registerMetadata() { CValueObject::registerMetadata(); @@ -207,53 +260,6 @@ namespace BlackMisc return cg; } - bool CAircraftSituation::presetGroundElevation(CAircraftSituation &situationToPreset, const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const CAircraftSituationChange &change) - { - // IMPORTANT: we do not know what the situation will be (interpolated to), so we cannot transfer - situationToPreset.resetGroundElevation(); - do - { - if (oldSituation.equalNormalVectorDouble(newSituation)) - { - if (oldSituation.hasGroundElevation()) - { - // same positions, we can use existing elevation - // means we were not moving between old an new - situationToPreset.transferGroundElevationToMe(oldSituation, true); - break; - } - } - - const CLength distance = newSituation.calculateGreatCircleDistance(oldSituation); - if (distance < newSituation.getDistancePerTime250ms(CElevationPlane::singlePointRadius())) - { - if (oldSituation.hasGroundElevation()) - { - // almost same positions, we can use existing elevation - situationToPreset.transferGroundElevationToMe(oldSituation, true); - break; - } - } - - if (change.hasElevationDevWithinAllowedRange()) - { - // not much change in known elevations - const CAltitudePair elvDevMean = change.getElevationStdDevAndMean(); - situationToPreset.setGroundElevation(elvDevMean.second, CAircraftSituation::SituationChange); - break; - } - - const CElevationPlane epInterpolated = CAircraftSituation::interpolatedElevation(CAircraftSituation::null(), oldSituation, newSituation, distance); - if (!epInterpolated.isNull()) - { - situationToPreset.setGroundElevation(epInterpolated, CAircraftSituation::Interpolated); - break; - } - } - while (false); - return situationToPreset.hasGroundElevation(); - } - CElevationPlane CAircraftSituation::interpolatedElevation(const CAircraftSituation &situation, const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const CLength &distance) { if (oldSituation.isNull() || newSituation.isNull()) { return CElevationPlane::null(); } @@ -297,35 +303,6 @@ namespace BlackMisc } } - bool CAircraftSituation::extrapolateElevation(CAircraftSituation &situationToBeUpdated, const CAircraftSituation &oldSituation, const CAircraftSituation &olderSituation, const CAircraftSituationChange &oldChange) - { - if (situationToBeUpdated.hasGroundElevation()) { return false; } - - // if acceptable transfer - if (oldSituation.transferGroundElevationFromMe(situationToBeUpdated)) - { - // change or keep type is the question - // situationToBeUpdated.setGroundElevationInfo(Extrapolated); - return true; - } - if (oldSituation.isNull() || olderSituation.isNull()) { return false; } - - if (oldChange.isNull()) { return false; } - if (oldChange.isConstOnGround() && oldChange.hasAltitudeDevWithinAllowedRange() && oldChange.hasElevationDevWithinAllowedRange()) - { - // we have almost const altitudes and elevations - const double deltaAltFt = qAbs(situationToBeUpdated.getAltitude().value(CLengthUnit::ft()) - olderSituation.getAltitude().value(CLengthUnit::ft())); - if (deltaAltFt <= CAircraftSituationChange::allowedAltitudeDeviation().value(CLengthUnit::ft())) - { - // the current alt is also not much different - situationToBeUpdated.setGroundElevation(oldSituation.getGroundElevation(), Extrapolated); - return true; - } - } - - return false; - } - QVariant CAircraftSituation::propertyByIndex(BlackMisc::CPropertyIndexRef index) const { if (index.isMyself()) { return QVariant::fromValue(*this); } @@ -556,117 +533,6 @@ namespace BlackMisc return !this->hasInboundGroundDetails(); } - bool CAircraftSituation::guessOnGround(const CAircraftSituationChange &change, const CAircraftModel &model) - { - 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) - { - if (this->getGroundSpeed().isNegativeWithEpsilonConsidered()) - { - this->setOnGround(OnGround, CAircraftSituation::OnGroundByGuessing); - if (details) { *details = QStringLiteral("No VTOL, push back"); } - return true; - } - - if (!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); - - CLength cg = m_cg.isNull() ? model.getCG() : m_cg; - CSpeed guessedRotateSpeed = CSpeed::null(); - CSpeed sureRotateSpeed = CSpeed(130, CSpeedUnit::kts()); - model.getAircraftIcaoCode().guessModelParameters(cg, guessedRotateSpeed); - if (!guessedRotateSpeed.isNull()) - { - // does the value make any sense? - const bool validGuessedSpeed = (guessedRotateSpeed.value(CSpeedUnit::km_h()) > 5.0); - BLACK_VERIFY_X(validGuessedSpeed, Q_FUNC_INFO, "Wrong guessed value for lift off"); - if (!validGuessedSpeed) { guessedRotateSpeed = CSpeed(80, CSpeedUnit::kts()); } // fix - sureRotateSpeed = guessedRotateSpeed * 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() > sureRotateSpeed) { if (details) { *details = u"gs. > vr " % sureRotateSpeed.valueRoundedWithUnit(1); } return true; } - - // use the most accurate or reliable guesses here first - // ------------------------------------------------------ - // by elevation - // 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; - } - - 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) - { - // no idea - this->setOnGround(OnGroundSituationUnknown, NotSetGroundDetails); - return false; - } - - // guessed speed null -> vtol - if (!guessedRotateSpeed.isNull()) - { - // does the value make any sense? - if (this->getGroundSpeed() < guessedRotateSpeed) - { - this->setOnGround(OnGround, CAircraftSituation::OnGroundByGuessing); - if (details) { *details = QStringLiteral("Guessing, max.guessed gs.") + guessedRotateSpeed.valueRoundedWithUnit(CSpeedUnit::kts(), 1); } - return true; - } - } - - // not sure, but this is a guess - if (details) { *details = QStringLiteral("Fall through"); } - return true; - } - CLength CAircraftSituation::getGroundDistance(const CLength ¢erOfGravity) const { if (centerOfGravity.isNull() || !this->hasGroundElevation()) { return CLength::null(); } @@ -783,16 +649,6 @@ namespace BlackMisc return this->setGroundElevation(fromSituation.getGroundElevationPlane(), fromSituation.getGroundElevationInfo(), transferred); } - bool CAircraftSituation::presetGroundElevation(const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const CAircraftSituationChange &change) - { - return CAircraftSituation::presetGroundElevation(*this, oldSituation, newSituation, change); - } - - bool CAircraftSituation::extrapolateElevation(const CAircraftSituation &oldSituation, const CAircraftSituation &olderSituation, const CAircraftSituationChange &change) - { - return CAircraftSituation::extrapolateElevation(*this, oldSituation, olderSituation, change); - } - bool CAircraftSituation::interpolateElevation(const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation) { const CElevationPlane ep = CAircraftSituation::interpolatedElevation(*this, oldSituation, newSituation); diff --git a/src/blackmisc/aviation/aircraftsituation.h b/src/blackmisc/aviation/aircraftsituation.h index d577e334f..fd4508ff6 100644 --- a/src/blackmisc/aviation/aircraftsituation.h +++ b/src/blackmisc/aviation/aircraftsituation.h @@ -41,7 +41,7 @@ namespace BlackMisc { class CAircraftParts; class CAircraftPartsList; - class CAircraftSituationChange; + class CAircraftLights; //! Value object encapsulating information of an aircraft's situation class BLACKMISC_EXPORT CAircraftSituation : @@ -241,9 +241,6 @@ namespace BlackMisc //! Should we guess on ground? bool shouldGuessOnGround() const; - //! Guess on ground flag - 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; @@ -316,19 +313,6 @@ namespace BlackMisc //! Transfer ground elevation from given situation (to me) bool transferGroundElevationToMe(const CAircraftSituation &fromSituation, bool transferred); - //! Preset "this" elevation from the two adjacent positions - //! \remark it is not required that the position of "this" is already known - //! \remark "transfer" can be used, if the positions are known, "preset" if they are still unknown - //! \sa CAircraftSituation::transferGroundElevation - //! \sa CAircraftSituation::interpolateElevation - bool presetGroundElevation(const Aviation::CAircraftSituation &oldSituation, const Aviation::CAircraftSituation &newSituation, const CAircraftSituationChange &change); - - //! Set "my" elevation from older situations. - //! \remark this object normally is a future value - //! \sa CAircraftSituation::transferGroundElevation - //! \sa CAircraftSituation::interpolateElevation - bool extrapolateElevation(const Aviation::CAircraftSituation &oldSituation, const Aviation::CAircraftSituation &olderSituation, const CAircraftSituationChange &change); - //! Interpolate "this" elevation from the two adjacent positions //! \remark "transfer" can be used, if the positions are known, "preset" if they are still unknown //! \sa CAircraftSituation::transferGroundElevation @@ -553,18 +537,6 @@ namespace BlackMisc } //! @} - //! Preset the ground elevation based on info we already have, either by transfer or elevation - //! \remark either sets a gnd. elevation or sets it to null - //! \remark situationToPreset position is unknown - //! \remark situationToPreset needs to be between oldSituation and newSituation - //! \sa CAircraftSituation::transferGroundElevation - static bool presetGroundElevation(CAircraftSituation &situationToPreset, const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const CAircraftSituationChange &change); - - //! Extrapolated between the 2 situations for situation - //! \remark normally used if situationToBeUpdated is not between oldSituation and olderSituation (that would be interpolation) - //! \return false if there are no two elevations, there is already an elevation, or no extrapolation is possible (too much deviation) - static bool extrapolateElevation(CAircraftSituation &situationToBeUpdated, const CAircraftSituation &oldSituation, const CAircraftSituation &olderSituation, const CAircraftSituationChange &oldChange); - //! Interpolate between the 2 situations for situation //! \remark NULL if there are no two elevations or threshold MaxDeltaElevationFt is exceeded static Geo::CElevationPlane interpolatedElevation(const CAircraftSituation &situation, const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const PhysicalQuantities::CLength &distance = PhysicalQuantities::CLength::null()); @@ -572,6 +544,12 @@ namespace BlackMisc //! Threshold until we interpolate elevations static constexpr double MaxDeltaElevationFt = 25.0; + //! Within this range deviation is so small we consider values "almost constant" + static const PhysicalQuantities::CLength &allowedAltitudeDeviation(); + + //! Guessed lights + CAircraftLights guessLights() const; + //! Register metadata static void registerMetadata(); diff --git a/src/blackmisc/aviation/aircraftsituationchange.cpp b/src/blackmisc/aviation/aircraftsituationchange.cpp index 508bf2dd4..8a8d1f573 100644 --- a/src/blackmisc/aviation/aircraftsituationchange.cpp +++ b/src/blackmisc/aviation/aircraftsituationchange.cpp @@ -9,6 +9,7 @@ #include "blackmisc/aviation/aircraftsituationchange.h" #include "blackmisc/aviation/aircraftsituationlist.h" #include "blackmisc/aviation/callsign.h" +#include "blackmisc/simulation/aircraftmodel.h" #include "blackmisc/pq/length.h" #include "blackmisc/pq/angle.h" #include "blackmisc/pq/units.h" @@ -87,6 +88,117 @@ namespace BlackMisc } } + bool CAircraftSituationChange::guessOnGround(CAircraftSituation &situation, const Simulation::CAircraftModel &model) const + { + if (!situation.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) + { + if (situation.getGroundSpeed().isNegativeWithEpsilonConsidered()) + { + situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("No VTOL, push back"); } + return true; + } + + if (!situation.isMoving()) + { + situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("No VTOL, not moving => on ground"); } + return true; + } + } + + // not on ground is default + situation.setOnGround(CAircraftSituation::NotOnGround, CAircraftSituation::OnGroundByGuessing); + + CLength cg = situation.hasCG() ? situation.getCG() : model.getCG(); + CSpeed guessedRotateSpeed = CSpeed::null(); + CSpeed sureRotateSpeed = CSpeed(130, CSpeedUnit::kts()); + model.getAircraftIcaoCode().guessModelParameters(cg, guessedRotateSpeed); + if (!guessedRotateSpeed.isNull()) + { + // does the value make any sense? + const bool validGuessedSpeed = (guessedRotateSpeed.value(CSpeedUnit::km_h()) > 5.0); + BLACK_VERIFY_X(validGuessedSpeed, Q_FUNC_INFO, "Wrong guessed value for lift off"); + if (!validGuessedSpeed) { guessedRotateSpeed = CSpeed(80, CSpeedUnit::kts()); } // fix + sureRotateSpeed = guessedRotateSpeed * 1.25; + } + + // "extreme" values for which we are surely not on ground + if (qAbs(situation.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(situation.getBank().value(CAngleUnit::deg())) > 10) { if (details) { *details = QStringLiteral("max.bank"); } return true; } + if (situation.getGroundSpeed() > sureRotateSpeed) { if (details) { *details = u"gs. > vr " % sureRotateSpeed.valueRoundedWithUnit(1); } return true; } + + // use the most accurate or reliable guesses here first + // ------------------------------------------------------ + // by elevation + // 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) + CAircraftSituation::IsOnGround og = situation.isOnGroundByElevation(cg); + if (og == CAircraftSituation::OnGround) + { + if (details) { *details = QStringLiteral("elevation on ground"); } + situation.setOnGround(og, CAircraftSituation::OnGroundByGuessing); + return true; + } + + if (!isNull()) + { + if (!vtol && wasConstOnGround()) + { + if (isRotatingUp()) + { + // not OG + if (details) { *details = QStringLiteral("rotating up detected"); } + return true; + } + + // here we stick to ground until we detect rotate up + situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("waiting for rotating up"); } + return true; + } + + if (isConstAscending()) + { + // not OG + if (details) { *details = QStringLiteral("const ascending"); } + return true; + } + } + + // on VTOL we stop here + if (vtol) + { + // no idea + situation.setOnGround(CAircraftSituation::OnGroundSituationUnknown, CAircraftSituation::NotSetGroundDetails); + return false; + } + + // guessed speed null -> vtol + if (!guessedRotateSpeed.isNull()) + { + // does the value make any sense? + if (situation.getGroundSpeed() < guessedRotateSpeed) + { + situation.setOnGround(CAircraftSituation::OnGround, CAircraftSituation::OnGroundByGuessing); + if (details) { *details = QStringLiteral("Guessing, max.guessed gs.") + guessedRotateSpeed.valueRoundedWithUnit(CSpeedUnit::kts(), 1); } + return true; + } + } + + // not sure, but this is a guess + if (details) { *details = QStringLiteral("Fall through"); } + return true; + } + bool CAircraftSituationChange::hasSceneryDeviation() const { return !m_guessedSceneryDeviation.isNull(); @@ -95,13 +207,13 @@ namespace BlackMisc bool CAircraftSituationChange::hasElevationDevWithinAllowedRange() const { if (m_elvStdDev.isNull()) { return false; } - return m_elvStdDev < allowedAltitudeDeviation(); + return m_elvStdDev < CAircraftSituation::allowedAltitudeDeviation(); } bool CAircraftSituationChange::hasAltitudeDevWithinAllowedRange() const { if (m_altStdDev.isNull()) { return false; } - return m_altStdDev < allowedAltitudeDeviation(); + return m_altStdDev < CAircraftSituation::allowedAltitudeDeviation(); } QString CAircraftSituationChange::convertToQString(bool i18n) const @@ -286,13 +398,6 @@ namespace BlackMisc return noInfo; } - const CLength &CAircraftSituationChange::allowedAltitudeDeviation() - { - // approx. 1 meter - static const CLength allowedStdDev(3, CLengthUnit::ft()); - return allowedStdDev; - } - void CAircraftSituationChange::setSceneryDeviation(const CLength &deviation, const CLength &cg, CAircraftSituationChange::GuessedSceneryDeviation hint) { m_guessedSceneryDeviation = deviation; diff --git a/src/blackmisc/aviation/aircraftsituationchange.h b/src/blackmisc/aviation/aircraftsituationchange.h index 5a5fbc83e..983d3f908 100644 --- a/src/blackmisc/aviation/aircraftsituationchange.h +++ b/src/blackmisc/aviation/aircraftsituationchange.h @@ -23,6 +23,10 @@ namespace BlackMisc { + namespace Simulation + { + class CAircraftModel; + } namespace Aviation { class CAircraftSituation; @@ -134,6 +138,9 @@ namespace BlackMisc //! \copydoc BlackMisc::Aviation::CAircraftSituationList::minMaxGroundDistance PhysicalQuantities::CLengthPair getMinMaxGroundDistance() const { return PhysicalQuantities::CLengthPair(m_minGroundDistance, m_maxGroundDistance); } + //! Guess on ground flag + bool guessOnGround(CAircraftSituation &situation, const Simulation::CAircraftModel &model) const; + //! Scnenery deviation (if it can be calculated, otherwise PhysicalQuantities::CLength::null) //! \remark This is without CG, so substract CG to get deviation const PhysicalQuantities::CLength &getGuessedSceneryDeviation() const { return m_guessedSceneryDeviation; } @@ -177,9 +184,6 @@ namespace BlackMisc //! The enum as string static const QString &guessedSceneryDeviationToString(GuessedSceneryDeviation hint); - //! Within this range deviation is so small we consider values "almost constant" - static const PhysicalQuantities::CLength &allowedAltitudeDeviation(); - //! Register metadata static void registerMetadata(); diff --git a/src/blackmisc/aviation/aircraftsituationlist.cpp b/src/blackmisc/aviation/aircraftsituationlist.cpp index 72ab6ba8c..c9bd068f8 100644 --- a/src/blackmisc/aviation/aircraftsituationlist.cpp +++ b/src/blackmisc/aviation/aircraftsituationlist.cpp @@ -7,7 +7,6 @@ */ #include "blackmisc/simulation/aircraftmodel.h" -#include "blackmisc/aviation/aircraftsituationchange.h" #include "blackmisc/aviation/aircraftsituationlist.h" #include "blackmisc/geo/elevationplane.h" #include "blackmisc/math/mathutils.h" @@ -64,52 +63,6 @@ namespace BlackMisc return c; } - int CAircraftSituationList::setGroundElevationCheckedAndGuessGround( - const CElevationPlane &elevationPlane, CAircraftSituation::GndElevationInfo info, const CAircraftModel &model, - CAircraftSituationChange *changeOut, bool *setForOnGroundPosition) - { - if (setForOnGroundPosition) { *setForOnGroundPosition = false; } // set a default - if (elevationPlane.isNull()) { return 0; } - if (this->isEmpty()) { return 0; } - - // the change has the timestamps of the latest situation - Q_ASSERT_X(m_tsAdjustedSortHint == CAircraftSituationList::AdjustedTimestampLatestFirst || this->isSortedAdjustedLatestFirstWithoutNullPositions(), Q_FUNC_INFO, "Need sorted situations without NULL positions"); - const CAircraftSituationChange simpleChange(*this, model.getCG(), model.isVtol(), true, false); - int c = 0; // changed elevations - bool latest = true; - bool setForOnGndPosition = false; - - for (CAircraftSituation &s : *this) - { - const bool set = s.setGroundElevationChecked(elevationPlane, info); - if (set) - { - // simpleChange is only valid for the latest situation - // this will do nothing if not appropriate! - const bool guessed = s.guessOnGround(latest ? simpleChange : CAircraftSituationChange::null(), model); - Q_UNUSED(guessed) - c++; - - // if not guessed and "on ground" we mark the "elevation" - // as an elevation for a ground position - if (!setForOnGndPosition && s.hasInboundGroundDetails() && s.isOnGround()) - { - setForOnGndPosition = true; - } - } - latest = false; // only first pos. is "the latest" one - } - - if (setForOnGroundPosition) { *setForOnGroundPosition = setForOnGndPosition; } - if (changeOut) - { - const CAircraftSituationChange change(*this, model.getCG(), model.isVtol(), true, true); - *changeOut = change; - } - - return c; - } - int CAircraftSituationList::adjustGroundFlag(const CAircraftParts &parts, double timeDeviationFactor) { int c = 0; @@ -141,15 +94,6 @@ namespace BlackMisc return c; } - bool CAircraftSituationList::extrapolateElevation(const CAircraftSituationChange &change) - { - if (this->size() < 3) { return false; } - Q_ASSERT_X(m_tsAdjustedSortHint == CAircraftSituationList::AdjustedTimestampLatestFirst, Q_FUNC_INFO, "Need latest first"); - const CAircraftSituation old = (*this)[1]; - const CAircraftSituation older = (*this)[2]; - return this->front().extrapolateElevation(old, older, change); - } - CAircraftSituationList CAircraftSituationList::findByInboundGroundInformation(bool hasGroundInfo) const { return this->findBy(&CAircraftSituation::hasInboundGroundDetails, hasGroundInfo); @@ -632,7 +576,7 @@ namespace BlackMisc if (valuesInFt.size() < minValues) { return CElevationPlane::null(); } - static const double MaxDevFt = CAircraftSituationChange::allowedAltitudeDeviation().value(CLengthUnit::ft()); + static const double MaxDevFt = CAircraftSituation::allowedAltitudeDeviation().value(CLengthUnit::ft()); const QPair elvStdDevMean = CMathUtils::standardDeviationAndMean(valuesInFt); if (elvStdDevMean.first > MaxDevFt) { return CElevationPlane::null(); } return CElevationPlane(reference, elvStdDevMean.second, CElevationPlane::singlePointRadius()); diff --git a/src/blackmisc/aviation/aircraftsituationlist.h b/src/blackmisc/aviation/aircraftsituationlist.h index 96a51ff98..85c00a602 100644 --- a/src/blackmisc/aviation/aircraftsituationlist.h +++ b/src/blackmisc/aviation/aircraftsituationlist.h @@ -63,21 +63,12 @@ namespace BlackMisc //! Set ground elevation from elevation plane int setGroundElevationChecked(const Geo::CElevationPlane &elevationPlane, CAircraftSituation::GndElevationInfo info, qint64 newerThanAdjustedMs = -1); - //! Set ground elevation from elevation plane and guess ground - //! \note requires a sorted list latest first - int setGroundElevationCheckedAndGuessGround(const Geo::CElevationPlane &elevationPlane, CAircraftSituation::GndElevationInfo info, const Simulation::CAircraftModel &model, CAircraftSituationChange *changeOut, bool *setForOnGroundPosition); - //! Adjust flag from parts by using CAircraftSituation::adjustGroundFlag int adjustGroundFlag(const CAircraftParts &parts, double timeDeviationFactor = 0.1); //! Extrapolate ground flag into the future int extrapolateGroundFlag(); - //! Extrapolates elevation into front (first) element from 2nd and 3rd element - //! \sa CAircraftSituation::extrapolateElevation - //! \pre the list must be sorted latest first and containt at least 3 elements - bool extrapolateElevation(const CAircraftSituationChange &change); - //! Find if having inbound information CAircraftSituationList findByInboundGroundInformation(bool hasGroundInfo) const; diff --git a/src/blackmisc/simulation/interpolator.cpp b/src/blackmisc/simulation/interpolator.cpp index 12c073271..1d4f2aa4d 100644 --- a/src/blackmisc/simulation/interpolator.cpp +++ b/src/blackmisc/simulation/interpolator.cpp @@ -11,6 +11,8 @@ #include "blackmisc/simulation/interpolationlogger.h" #include "blackmisc/simulation/interpolatorlinear.h" #include "blackmisc/simulation/interpolatorspline.h" +#include "blackmisc/aviation/aircraftsituationchange.h" +#include "blackmisc/aviation/aircraftsituation.h" #include "blackmisc/network/fsdsetup.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/aviation/heading.h" @@ -117,6 +119,54 @@ namespace BlackMisc return validSituations; } + template + bool CInterpolator::presetGroundElevation(CAircraftSituation &situationToPreset, const CAircraftSituation &oldSituation, const CAircraftSituation &newSituation, const CAircraftSituationChange &change) + { + // IMPORTANT: we do not know what the situation will be (interpolated to), so we cannot transfer + situationToPreset.resetGroundElevation(); + do + { + if (oldSituation.equalNormalVectorDouble(newSituation)) + { + if (oldSituation.hasGroundElevation()) + { + // same positions, we can use existing elevation + // means we were not moving between old an new + situationToPreset.transferGroundElevationToMe(oldSituation, true); + break; + } + } + + const CLength distance = newSituation.calculateGreatCircleDistance(oldSituation); + if (distance < newSituation.getDistancePerTime250ms(CElevationPlane::singlePointRadius())) + { + if (oldSituation.hasGroundElevation()) + { + // almost same positions, we can use existing elevation + situationToPreset.transferGroundElevationToMe(oldSituation, true); + break; + } + } + + if (change.hasElevationDevWithinAllowedRange()) + { + // not much change in known elevations + const CAltitudePair elvDevMean = change.getElevationStdDevAndMean(); + situationToPreset.setGroundElevation(elvDevMean.second, CAircraftSituation::SituationChange); + break; + } + + const CElevationPlane epInterpolated = CAircraftSituation::interpolatedElevation(CAircraftSituation::null(), oldSituation, newSituation, distance); + if (!epInterpolated.isNull()) + { + situationToPreset.setGroundElevation(epInterpolated, CAircraftSituation::Interpolated); + break; + } + } + while (false); + return situationToPreset.hasGroundElevation(); + } + template void CInterpolator::deferredInit() { @@ -238,7 +288,7 @@ namespace BlackMisc } // GND flag. - if (!interpolateGndFlag) { currentSituation.guessOnGround(CAircraftSituationChange::null(), m_model); } + if (!interpolateGndFlag) { CAircraftSituationChange::null().guessOnGround(currentSituation, m_model); } // as we now have the position and can interpolate elevation currentSituation.interpolateElevation(pbh.getOldSituation(), pbh.getNewSituation()); @@ -428,7 +478,7 @@ namespace BlackMisc // check if model has been thru model matching if (!m_lastSituation.isNull()) { - parts.guessParts(m_lastSituation, m_pastSituationsChange, m_model); + parts = guessParts(m_lastSituation, m_pastSituationsChange, m_model); this->logParts(parts, 0, false); } else @@ -470,6 +520,136 @@ namespace BlackMisc return this->hasAttachedLogger() && m_currentSetup.logInterpolation(); } + template + CAircraftParts CInterpolator::guessParts(const CAircraftSituation &situation, const CAircraftSituationChange &change, const CAircraftModel &model) + { + CAircraftParts parts; + parts.setMSecsSinceEpoch(situation.getMSecsSinceEpoch()); + parts.setTimeOffsetMs(situation.getTimeOffsetMs()); + parts.setPartsDetails(CAircraftParts::GuessedParts); + parts.setLights(situation.guessLights()); + + QString *details = /*CBuildConfig::isLocalDeveloperDebugBuild() ? &parts.m_guessingDetails :*/ nullptr; + + CAircraftEngineList engines; + const bool vtol = model.isVtol(); + const int engineCount = model.getEngineCount(); + CSpeed guessedVRotate = CSpeed::null(); + CLength guessedCG = model.getCG(); + model.getAircraftIcaoCode().guessModelParameters(guessedCG, guessedVRotate); + + if (situation.getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails) + { + do + { + // set some reasonable values + const bool isOnGround = situation.isOnGround(); + engines.initEngines(engineCount, !isOnGround || situation.isMoving()); + parts.setGearDown(isOnGround); + parts.setSpoilersOut(false); + parts.setEngines(engines); + + if (!change.isNull()) + { + if (change.isConstDecelarating()) + { + parts.setSpoilersOut(true); + parts.setFlapsPercent(10); + break; + } + } + + const CSpeed slowSpeed = guessedVRotate * 0.30; + if (situation.getGroundSpeed() < slowSpeed) + { + if (details) { *details += u"slow speed <" % slowSpeed.valueRoundedWithUnit(1) % u" on ground"; } + parts.setFlapsPercent(0); + break; + } + else + { + if (details) { *details += u"faster speed >" % slowSpeed.valueRoundedWithUnit(1) % u" on ground"; } + parts.setFlapsPercent(0); + break; + } + } + while (false); + } + else + { + if (details) { *details = QStringLiteral("no ground info"); } + + // no idea if on ground or not + engines.initEngines(engineCount, true); + parts.setEngines(engines); + parts.setGearDown(true); + parts.setSpoilersOut(false); + } + + const double pitchDeg = situation.getPitch().value(CAngleUnit::deg()); + const bool isLikelyTakeOffOrClimbing = change.isNull() ? pitchDeg > 20 : (change.isRotatingUp() || change.isConstAscending()); + const bool isLikelyLanding = change.isNull() ? false : change.isConstDescending(); + + if (situation.hasGroundElevation()) + { + const CLength aboveGnd = situation.getHeightAboveGround(); + if (aboveGnd.isNull() || std::isnan(aboveGnd.value())) + { + BLACK_VERIFY_X(false, Q_FUNC_INFO, "above gnd.is null"); + return parts; + } + + const double nearGround1Ft = 300; + const double nearGround2Ft = isLikelyTakeOffOrClimbing ? 500 : 1000; + const double aGroundFt = aboveGnd.value(CLengthUnit::ft()); + static const QString detailsInfo("above ground: %1ft near grounds: %2ft %3ft likely takeoff: %4 likely landing: %5"); + + if (details) { *details = detailsInfo.arg(aGroundFt).arg(nearGround1Ft).arg(nearGround2Ft).arg(boolToYesNo(isLikelyTakeOffOrClimbing), boolToYesNo(isLikelyLanding)); } + if (aGroundFt < nearGround1Ft) + { + if (details) { details->prepend(QStringLiteral("near ground: ")); } + parts.setGearDown(true); + parts.setFlapsPercent(25); + } + else if (aGroundFt < nearGround2Ft) + { + if (details) { details->prepend(QStringLiteral("2nd layer: ")); } + const bool gearDown = !isLikelyTakeOffOrClimbing && (situation.getGroundSpeed() < guessedVRotate || isLikelyLanding); + parts.setGearDown(gearDown); + parts.setFlapsPercent(10); + } + else + { + if (details) { details->prepend(QStringLiteral("airborne: ")); } + parts.setGearDown(false); + parts.setFlapsPercent(0); + } + } + else + { + if (situation.getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails) + { + // we have no ground elevation but a ground info + if (situation.getOnGroundDetails() == CAircraftSituation::OnGroundByGuessing) + { + // should be OK + if (details) { *details = QStringLiteral("on ground, no elv."); } + } + else + { + if (!vtol) + { + const bool gearDown = situation.getGroundSpeed() < guessedVRotate; + parts.setGearDown(gearDown); + if (details) { *details = QStringLiteral("not on ground elv., gs < ") + guessedVRotate.valueRoundedWithUnit(1); } + } + } + } + } + + return parts; + } + template void CInterpolator::logParts(const CAircraftParts &parts, int partsNo, bool empty) const { @@ -592,7 +772,7 @@ namespace BlackMisc } // preset elevation here, as we do not know where the situation will be after the interpolation step! - const bool preset = currentSituation.presetGroundElevation(oldSituation, newSituation, m_pastSituationsChange); + const bool preset = presetGroundElevation(currentSituation, oldSituation, newSituation, m_pastSituationsChange); Q_UNUSED(preset) // fetch CG once diff --git a/src/blackmisc/simulation/interpolator.h b/src/blackmisc/simulation/interpolator.h index 6c83f2bcd..bd23449c1 100644 --- a/src/blackmisc/simulation/interpolator.h +++ b/src/blackmisc/simulation/interpolator.h @@ -30,6 +30,11 @@ namespace BlackMisc { + namespace Aviation + { + class CAircraftSituation; + class CAircraftSituationChange; + } namespace Simulation { class CInterpolationLogger; @@ -309,6 +314,9 @@ namespace BlackMisc CInterpolationLogger *m_logger = nullptr; //!< optional interpolation logger QTimer m_initTimer; //!< timer to init model, will be deleted when interpolator is deleted and cancel the call + //! Guessed parts + static Aviation::CAircraftParts guessParts(const Aviation::CAircraftSituation &situation, const Aviation::CAircraftSituationChange &change, const Simulation::CAircraftModel &model); + //! Log parts void logParts(const Aviation::CAircraftParts &parts, int partsNo, bool empty) const; @@ -319,6 +327,13 @@ namespace BlackMisc //! Center of gravity, fetched from provider in case needed PhysicalQuantities::CLength getAndFetchModelCG(const PhysicalQuantities::CLength &dbCG); + //! Preset the ground elevation based on info we already have, either by transfer or elevation + //! \remark either sets a gnd. elevation or sets it to null + //! \remark situationToPreset position is unknown + //! \remark situationToPreset needs to be between oldSituation and newSituation + //! \sa CAircraftSituation::transferGroundElevation + static bool presetGroundElevation(Aviation::CAircraftSituation &situationToPreset, const Aviation::CAircraftSituation &oldSituation, const Aviation::CAircraftSituation &newSituation, const Aviation::CAircraftSituationChange &change); + //! Deferred init void deferredInit(); diff --git a/src/blackmisc/simulation/remoteaircraftprovider.cpp b/src/blackmisc/simulation/remoteaircraftprovider.cpp index 0e7aeb4b1..7e9ab3c70 100644 --- a/src/blackmisc/simulation/remoteaircraftprovider.cpp +++ b/src/blackmisc/simulation/remoteaircraftprovider.cpp @@ -321,7 +321,7 @@ namespace BlackMisc const CAircraftSituationChange simpleChange(updatedSituations, situationCorrected.getCG(), aircraftModel.isVtol(), true, false); // guess GND - newSituationsList.front().guessOnGround(simpleChange, aircraftModel); + simpleChange.guessOnGround(newSituationsList.front(), aircraftModel); } updatedSituations = m_situationsByCallsign[cs]; @@ -504,7 +504,7 @@ namespace BlackMisc { if (aircraftModel.hasCG() && !situation.hasCG()) { situation.setCG(aircraftModel.getCG()); } if (!situation.shouldGuessOnGround()) { return false; } - return situation.guessOnGround(change, aircraftModel); + return change.guessOnGround(situation, aircraftModel); } bool CRemoteAircraftProvider::updateAircraftEnabled(const CCallsign &callsign, bool enabledForRendering) @@ -590,7 +590,7 @@ namespace BlackMisc QWriteLocker l(&m_lockSituations); CAircraftSituationList &situations = m_situationsByCallsign[callsign]; if (situations.isEmpty()) { return 0; } - updated = situations.setGroundElevationCheckedAndGuessGround(elevation, info, model, &change, &setForOnGndPosition); + updated = setGroundElevationCheckedAndGuessGround(situations, elevation, info, model, &change, &setForOnGndPosition); if (updated < 1) { return 0; } m_situationsLastModified[callsign] = now; const CAircraftSituation latestSituation = situations.front(); @@ -772,6 +772,52 @@ namespace BlackMisc return m_enableReverseLookupMsgs; } + int CRemoteAircraftProvider::setGroundElevationCheckedAndGuessGround( + CAircraftSituationList &situations, const CElevationPlane &elevationPlane, CAircraftSituation::GndElevationInfo info, const CAircraftModel &model, + CAircraftSituationChange *changeOut, bool *setForOnGroundPosition) + { + if (setForOnGroundPosition) { *setForOnGroundPosition = false; } // set a default + if (elevationPlane.isNull()) { return 0; } + if (situations.isEmpty()) { return 0; } + + // the change has the timestamps of the latest situation + //Q_ASSERT_X(situations.m_tsAdjustedSortHint == CAircraftSituationList::AdjustedTimestampLatestFirst || situations.isSortedAdjustedLatestFirstWithoutNullPositions(), Q_FUNC_INFO, "Need sorted situations without NULL positions"); + const CAircraftSituationChange simpleChange(situations, model.getCG(), model.isVtol(), true, false); + int c = 0; // changed elevations + bool latest = true; + bool setForOnGndPosition = false; + + for (CAircraftSituation &s : situations) + { + const bool set = s.setGroundElevationChecked(elevationPlane, info); + if (set) + { + // simpleChange is only valid for the latest situation + // this will do nothing if not appropriate! + const bool guessed = (latest ? simpleChange : CAircraftSituationChange::null()).guessOnGround(s, model); + Q_UNUSED(guessed) + c++; + + // if not guessed and "on ground" we mark the "elevation" + // as an elevation for a ground position + if (!setForOnGndPosition && s.hasInboundGroundDetails() && s.isOnGround()) + { + setForOnGndPosition = true; + } + } + latest = false; // only first pos. is "the latest" one + } + + if (setForOnGroundPosition) { *setForOnGroundPosition = setForOnGndPosition; } + if (changeOut) + { + const CAircraftSituationChange change(situations, model.getCG(), model.isVtol(), true, true); + *changeOut = change; + } + + return c; + } + CStatusMessageList CRemoteAircraftProvider::getAircraftPartsHistory(const CCallsign &callsign) const { QReadLocker l(&m_lockPartsHistory); diff --git a/src/blackmisc/simulation/remoteaircraftprovider.h b/src/blackmisc/simulation/remoteaircraftprovider.h index a27dcdc26..d4c545ee1 100644 --- a/src/blackmisc/simulation/remoteaircraftprovider.h +++ b/src/blackmisc/simulation/remoteaircraftprovider.h @@ -473,6 +473,10 @@ namespace BlackMisc //! \threadsafe ReverseLookupLogging whatToReverseLog() const; + //! Set ground elevation from elevation plane and guess ground + //! \note requires a sorted list latest first + static int setGroundElevationCheckedAndGuessGround(Aviation::CAircraftSituationList &situations, const Geo::CElevationPlane &elevationPlane, Aviation::CAircraftSituation::GndElevationInfo info, const Simulation::CAircraftModel &model, Aviation::CAircraftSituationChange *changeOut, bool *setForOnGroundPosition); + private: //! Store the latest changes //! \remark latest first diff --git a/src/blackmisc/simulation/simulationenvironmentprovider.cpp b/src/blackmisc/simulation/simulationenvironmentprovider.cpp index ac512532e..ddc2d366f 100644 --- a/src/blackmisc/simulation/simulationenvironmentprovider.cpp +++ b/src/blackmisc/simulation/simulationenvironmentprovider.cpp @@ -287,7 +287,7 @@ namespace BlackMisc CElevationPlane ISimulationEnvironmentProvider::averageElevationOfOnGroundAircraft(const CAircraftSituation &reference, const CLength &range, int minValues, int sufficientValues) const { const CCoordinateGeodeticList coordinates = this->getElevationCoordinatesOnGround(); - return coordinates.averageGeodeticHeight(reference, range, CAircraftSituationChange::allowedAltitudeDeviation(), minValues, sufficientValues); + return coordinates.averageGeodeticHeight(reference, range, CAircraftSituation::allowedAltitudeDeviation(), minValues, sufficientValues); } CAltitude ISimulationEnvironmentProvider::highestElevation() const