/* Copyright (C) 2013 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, * including this file, may be copied, modified, propagated, or distributed except according to the terms * 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" #include "blackmisc/pq/units.h" #include "blackmisc/propertyindex.h" #include "blackmisc/comparefunctions.h" #include "blackmisc/variant.h" #include "blackmisc/verify.h" #include "blackconfig/buildconfig.h" #include "QStringBuilder" #include using namespace BlackMisc::Geo; using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Simulation; using namespace BlackConfig; namespace BlackMisc { namespace Aviation { CAircraftSituation::CAircraftSituation() {} CAircraftSituation::CAircraftSituation(const CCallsign &correspondingCallsign) : m_correspondingCallsign(correspondingCallsign) {} CAircraftSituation::CAircraftSituation(const CCoordinateGeodetic &position, const CHeading &heading, const CAngle &pitch, const CAngle &bank, const CSpeed &gs, const CElevationPlane &groundElevation) : m_position(position), m_heading(heading), m_pitch(pitch), m_bank(bank), m_groundSpeed(gs), m_groundElevationPlane(groundElevation) { m_pressureAltitude = position.geodeticHeight().toPressureAltitude(CPressure(1013.25, CPressureUnit::mbar())); } CAircraftSituation::CAircraftSituation(const CCallsign &correspondingCallsign, const CCoordinateGeodetic &position, const CHeading &heading, const CAngle &pitch, const CAngle &bank, const CSpeed &gs, const CElevationPlane &groundElevation) : m_correspondingCallsign(correspondingCallsign), m_position(position), m_heading(heading), m_pitch(pitch), m_bank(bank), m_groundSpeed(gs), m_groundElevationPlane(groundElevation) { m_correspondingCallsign.setTypeHint(CCallsign::Aircraft); m_pressureAltitude = position.geodeticHeight().toPressureAltitude(CPressure(1013.25, CPressureUnit::mbar())); } QString CAircraftSituation::convertToQString(bool i18n) const { return QStringLiteral("ts: ") % this->getFormattedTimestampAndOffset(true) % QStringLiteral(" | ") % m_position.toQString(i18n) % 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)); } const QString &CAircraftSituation::isOnGroundToString(CAircraftSituation::IsOnGround onGround) { static const QString notog("not on ground"); static const QString og("on ground"); static const QString unknown("unknown"); switch (onGround) { case CAircraftSituation::NotOnGround: return notog; case CAircraftSituation::OnGround: return og; case CAircraftSituation::OnGroundSituationUnknown: default: return unknown; } } const QString &CAircraftSituation::onGroundDetailsToString(CAircraftSituation::OnGroundDetails reliability) { static const QString intElv("elevation"); static const QString intElvCg("elevation/CG"); static const QString intInter("interpolation"); static const QString intGuess("guessing"); static const QString unknown("unknown"); static const QString outOwnAircraft("own aircraft"); static const QString inNetwork("from network"); static const QString inFromParts("from parts"); static const QString InNoGroundInfo("no gnd.info"); switch (reliability) { case CAircraftSituation::OnGroundByElevation: return intElv; case CAircraftSituation::OnGroundByElevationAndCG: return intElvCg; case CAircraftSituation::OnGroundByGuessing: return intGuess; case CAircraftSituation::OnGroundByInterpolation: return intInter; case CAircraftSituation::OutOnGroundOwnAircraft: return outOwnAircraft; case CAircraftSituation::InFromNetwork: return inNetwork; case CAircraftSituation::InFromParts: return inFromParts; case CAircraftSituation::InNoGroundInfo: return InNoGroundInfo; case CAircraftSituation::NotSetGroundDetails: default: return unknown; } } const QString &CAircraftSituation::altitudeCorrectionToString(CAircraftSituation::AltitudeCorrection correction) { static const QString under("underflow"); static const QString dragged("dragged to gnd"); static const QString no("no correction"); static const QString noElv("no elv."); static const QString unknown("unknown"); static const QString agl("AGL"); switch (correction) { case Underflow: return under; case DraggedToGround: return dragged; case NoElevation: return noElv; case NoCorrection: return no; case AGL: return agl; default: break; } return unknown; } const CLength &CAircraftSituation::deltaNearGround() { static const CLength small(0.5, CLengthUnit::m()); return small; } const CAircraftSituation &CAircraftSituation::null() { static const CAircraftSituation n; return n; } const CLength &CAircraftSituation::defaultCG() { static const CLength cg(2.5, CLengthUnit::m()); return cg; } CVariant CAircraftSituation::propertyByIndex(const BlackMisc::CPropertyIndex &index) const { if (index.isMyself()) { return CVariant::from(*this); } if (ITimestampWithOffsetBased::canHandleIndex(index)) { return ITimestampWithOffsetBased::propertyByIndex(index); } if (ICoordinateGeodetic::canHandleIndex(index)) { return ICoordinateGeodetic::propertyByIndex(index); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexPosition: return m_position.propertyByIndex(index.copyFrontRemoved()); case IndexLatitude: return this->latitude().propertyByIndex(index.copyFrontRemoved()); case IndexLongitude: return this->longitude().propertyByIndex(index.copyFrontRemoved()); case IndexAltitude: return this->getAltitude().propertyByIndex(index.copyFrontRemoved()); case IndexHeading: return m_heading.propertyByIndex(index.copyFrontRemoved()); case IndexPitch: return m_pitch.propertyByIndex(index.copyFrontRemoved()); case IndexBank: return m_bank.propertyByIndex(index.copyFrontRemoved()); case IndexCG: return m_cg.propertyByIndex(index.copyFrontRemoved()); case IndexGroundSpeed: return m_groundSpeed.propertyByIndex(index.copyFrontRemoved()); case IndexGroundElevationPlane: return m_groundElevationPlane.propertyByIndex(index.copyFrontRemoved()); case IndexCallsign: return m_correspondingCallsign.propertyByIndex(index.copyFrontRemoved()); case IndexIsOnGround: return CVariant::fromValue(m_onGround); case IndexIsOnGroundString: return CVariant::fromValue(this->onGroundAsString()); case IndexOnGroundReliability: return CVariant::fromValue(m_onGroundDetails); case IndexOnGroundReliabilityString: return CVariant::fromValue(this->getOnDetailsAsString()); default: return CValueObject::propertyByIndex(index); } } void CAircraftSituation::setPropertyByIndex(const CPropertyIndex &index, const CVariant &variant) { if (index.isMyself()) { (*this) = variant.to(); return; } if (ITimestampWithOffsetBased::canHandleIndex(index)) { ITimestampWithOffsetBased::setPropertyByIndex(index, variant); return; } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexPosition: m_position.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexPitch: m_pitch.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexBank: m_bank.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexCG: m_cg.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexGroundSpeed: m_groundSpeed.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexGroundElevationPlane: m_groundElevationPlane.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexCallsign: m_correspondingCallsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break; case IndexIsOnGround: m_onGround = variant.toInt(); break; case IndexOnGroundReliability: m_onGroundDetails = variant.toInt(); break; default: CValueObject::setPropertyByIndex(index, variant); break; } } int CAircraftSituation::comparePropertyByIndex(const CPropertyIndex &index, const CAircraftSituation &compareValue) const { if (ITimestampWithOffsetBased::canHandleIndex(index)) { return ITimestampWithOffsetBased::comparePropertyByIndex(index, compareValue); } if (ICoordinateGeodetic::canHandleIndex(index)) { return ICoordinateGeodetic::comparePropertyByIndex(index, compareValue); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexPosition: return m_position.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getPosition()); case IndexAltitude: return this->getAltitude().comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getAltitude()); case IndexPitch: return m_pitch.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getPitch()); case IndexBank: return m_bank.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getBank()); case IndexCG: return m_cg.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getCG()); case IndexGroundSpeed: return m_groundSpeed.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getGroundSpeed()); case IndexGroundElevationPlane: return m_groundElevationPlane.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getGroundElevationPlane()); case IndexCallsign: return m_correspondingCallsign.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getCallsign()); case IndexIsOnGround: case IndexIsOnGroundString: return Compare::compare(m_onGround, compareValue.m_onGround); case IndexOnGroundReliability: case IndexOnGroundReliabilityString: return Compare::compare(m_onGroundDetails, compareValue.m_onGroundDetails); default: break; } const QString assertMsg("No comparison for index " + index.toQString()); BLACK_VERIFY_X(false, Q_FUNC_INFO, qUtf8Printable(assertMsg)); return 0; } bool CAircraftSituation::isNull() const { return this->isPositionNull(); } void CAircraftSituation::setNull() { m_position.setNull(); m_pressureAltitude.setNull(); m_heading.setNull(); m_pitch.setNull(); m_bank.setNull(); m_groundElevationPlane.setNull(); m_groundSpeed.setNull(); m_onGroundDetails = CAircraftSituation::NotSetGroundDetails; } bool CAircraftSituation::isOnGroundFromParts() const { return this->isOnGround() && this->getOnGroundDetails() == InFromParts; } bool CAircraftSituation::isOnGroundFromNetwork() const { return this->isOnGround() && this->getOnGroundDetails() == InFromNetwork; } const QString &CAircraftSituation::onGroundAsString() const { return CAircraftSituation::isOnGroundToString(this->getOnGround()); } bool CAircraftSituation::isOnGroundInfoAvailable() const { if (this->hasInboundGroundDetails()) { return true; } return this->getOnGround() != CAircraftSituation::OnGroundSituationUnknown && this->getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails; } bool CAircraftSituation::setOnGround(bool onGround) { return this->setOnGround(onGround ? OnGround : NotOnGround); } bool CAircraftSituation::setOnGround(CAircraftSituation::IsOnGround 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; } bool CAircraftSituation::setOnGround(CAircraftSituation::IsOnGround onGround, CAircraftSituation::OnGroundDetails details) { const bool set = this->setOnGround(onGround); this->setOnGroundDetails(details); if (details != OnGroundByGuessing) { m_onGroundGuessingDetails.clear(); } return set; } void CAircraftSituation::setOnGroundFactor(double groundFactor) { double gf = groundFactor; do { if (groundFactor < 0.0) { gf = -1.0; break; } if (groundFactor < 0.001) { gf = 0.0; break; } if (groundFactor > 0.999) { gf = 1.0; break; } } while (false); m_onGroundFactor = gf; } bool CAircraftSituation::shouldGuessOnGround() const { return !this->hasInboundGroundDetails(); } 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) { 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 = QStringLiteral("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(); } const CAltitude groundPlusCG = this->getGroundElevation().withOffset(centerOfGravity); const CLength groundDistance = (this->getAltitude() - groundPlusCG); return groundDistance; } bool CAircraftSituation::hasGroundDetailsForGndInterpolation() const { return this->getOnGroundDetails() != CAircraftSituation::NotSetGroundDetails; } const QString &CAircraftSituation::getOnDetailsAsString() const { return CAircraftSituation::onGroundDetailsToString(this->getOnGroundDetails()); } bool CAircraftSituation::setOnGroundDetails(CAircraftSituation::OnGroundDetails details) { if (details != OnGroundByGuessing) { m_onGroundGuessingDetails.clear(); } if (this->getOnGroundDetails() == details) { return false; } m_onGroundDetails = static_cast(details); return true; } bool CAircraftSituation::setOnGroundFromGroundFactorFromInterpolation(double threshold) { this->setOnGroundDetails(OnGroundByInterpolation); if (this->getOnGroundFactor() < 0.0) { this->setOnGround(NotSetGroundDetails); return false; } // set on ground but leave factor untouched const bool og = this->getOnGroundFactor() > threshold; // 1.0 means on ground m_onGround = og ? OnGround : NotOnGround; return true; } bool CAircraftSituation::setOnGroundByUnderflowDetection(const CLength &cg) { IsOnGround og = this->isOnGroundByElevation(cg); if (og == OnGroundSituationUnknown) { return false; } this->setOnGround(og, OnGroundByElevationAndCG); return true; } QString CAircraftSituation::getOnGroundInfo() const { 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"); const CLength groundDistance = this->getGroundDistance(cg); if (groundDistance.isNull()) { return OnGroundSituationUnknown; } if (groundDistance.isNegativeWithEpsilonConsidered()) { return OnGround; } if (groundDistance.abs() < deltaNearGround()) { return OnGround; } if (!cg.isNull()) { // smaller than percentage from CG const CLength cgFactor(cg * 0.1); if (groundDistance.abs() < cgFactor) { return OnGround; } } return NotOnGround; } bool CAircraftSituation::hasGroundElevation() const { return !this->getGroundElevation().isNull(); } bool CAircraftSituation::hasInboundGroundDetails() const { return this->getOnGroundDetails() == CAircraftSituation::InFromParts || this->getOnGroundDetails() == CAircraftSituation::InFromNetwork; } void CAircraftSituation::setGroundElevation(const CAltitude &altitude) { if (altitude.isNull()) { m_groundElevationPlane = CElevationPlane::null(); } else { m_groundElevationPlane = CElevationPlane(*this); m_groundElevationPlane.setSinglePointRadius(); m_groundElevationPlane.setGeodeticHeight(altitude.switchedUnit(this->getAltitudeUnit())); } } void CAircraftSituation::setGroundElevation(const CElevationPlane &elevationPlane) { m_groundElevationPlane = elevationPlane; m_groundElevationPlane.switchUnit(this->getAltitudeOrDefaultUnit()); // we use ft as internal unit, no "must" but simplification } bool CAircraftSituation::setGroundElevationChecked(const CElevationPlane &elevationPlane) { if (elevationPlane.isNull()) { return false; } const CLength distance = this->calculateGreatCircleDistance(elevationPlane); if (distance > elevationPlane.getRadius()) { return false; } if (m_groundElevationPlane.isNull() || distance < m_groundElevationPlane.getRadius()) { // better values this->setGroundElevation(elevationPlane); m_groundElevationPlane.setRadius(distance); return true; } return false; } const CLength &CAircraftSituation::getGroundElevationRadius() const { if (!this->hasGroundElevation()) { return CLength::null(); } return m_groundElevationPlane.getRadius(); } CLength CAircraftSituation::getHeightAboveGround() const { if (this->getAltitude().isNull()) { return { 0, nullptr }; } if (this->getAltitude().getReferenceDatum() == CAltitude::AboveGround) { // we have a sure value explicitly set return this->getAltitude(); } const CLength gh(this->getGroundElevation()); if (gh.isNull()) { return { 0, nullptr }; } return this->getAltitude() - gh; } const CLengthUnit &CAircraftSituation::getAltitudeOrDefaultUnit() const { if (this->getAltitude().isNull()) { return CAltitude::defaultUnit(); } return m_position.geodeticHeight().getUnit(); } CAltitude CAircraftSituation::getCorrectedAltitude(bool enableDragToGround, CAircraftSituation::AltitudeCorrection *correction) const { 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 (correction) { *correction = NoElevation; } return this->getAltitude(); } // above ground if (this->getAltitude().getReferenceDatum() == CAltitude::AboveGround) { BLACK_VERIFY_X(false, Q_FUNC_INFO, "Unsupported"); if (correction) { *correction = AGL; } return this->getAltitude(); } else { const CAltitude groundPlusCG = this->getGroundElevation().withOffset(centerOfGravity).switchedUnit(this->getAltitudeOrDefaultUnit()); if (groundPlusCG.isNull()) { if (correction) { *correction = NoElevation; } return this->getAltitude(); } const CLength groundDistance = this->getAltitude() - groundPlusCG; const bool underflow = groundDistance.isNegativeWithEpsilonConsidered(); if (underflow) { if (correction) { *correction = Underflow; } return groundPlusCG; } const bool nearGround = groundDistance.abs() < deltaNearGround(); if (nearGround) { if (correction) { *correction = NoCorrection; } return groundPlusCG; } const bool forceDragToGround = (enableDragToGround && this->getOnGround() == OnGround) && (this->hasInboundGroundDetails() || this->getOnGroundDetails() == OnGroundByGuessing); if (forceDragToGround) { if (correction) { *correction = DraggedToGround; } return groundPlusCG; } 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; } void CAircraftSituation::setAltitude(const CAltitude &altitude) { m_position.setGeodeticHeight(altitude.switchedUnit(CAltitude::defaultUnit())); } CAltitude CAircraftSituation::addAltitudeOffset(const CLength &offset) { if (offset.isNull()) { return this->getAltitude(); } const CAltitude alt = this->getAltitude().withOffset(offset); this->setAltitude(alt); return alt; } void CAircraftSituation::setPressureAltitude(const CAltitude &altitude) { Q_ASSERT(altitude.getAltitudeType() == CAltitude::PressureAltitude); m_pressureAltitude = altitude; } bool CAircraftSituation::isMoving() const { const double gsKmh = this->getGroundSpeed().value(CSpeedUnit::km_h()); return gsKmh >= 2.5; } bool CAircraftSituation::canLikelySkipNearGroundInterpolation() const { // those we can exclude if (this->isOnGround() && this->hasInboundGroundDetails()) { return false; } // cases where we can skip if (this->isNull()) { return true; } if (this->getGroundSpeed().value(CSpeedUnit::kts()) > 250) { return true; } if (this->hasGroundElevation()) { static const CLength threshold(400, CLengthUnit::m()); const CLength a = this->getHeightAboveGround(); if (!a.isNull() && a >= threshold) { return true; } // too high for ground } return false; } CLength CAircraftSituation::getDistancePerTime(const CTime &time) const { if (this->getGroundSpeed().isNull()) { return CLength(0, CLengthUnit::nullUnit()); } const int ms = time.valueInteger(CTimeUnit::ms()); return this->getDistancePerTime(ms); } CLength CAircraftSituation::getDistancePerTime(int milliseconds) const { if (this->getGroundSpeed().isNull()) { return CLength(0, CLengthUnit::nullUnit()); } const double seconds = milliseconds / 1000; const double gsMeterSecond = this->getGroundSpeed().value(CSpeedUnit::m_s()); const CLength d(seconds * gsMeterSecond, CLengthUnit::m()); return d; } void CAircraftSituation::setCallsign(const CCallsign &callsign) { m_correspondingCallsign = callsign; m_correspondingCallsign.setTypeHint(CCallsign::Aircraft); } void CAircraftSituation::setCG(const CLength &cg) { m_cg = cg.switchedUnit(this->getAltitudeOrDefaultUnit()); } bool CAircraftSituation::adjustGroundFlag(const CAircraftParts &parts, bool alwaysSetDetails, double timeDeviationFactor, qint64 *differenceMs) { Q_ASSERT_X(timeDeviationFactor >= 0 && timeDeviationFactor <= 1.0, Q_FUNC_INFO, "Expect 0..1"); static const qint64 Max = std::numeric_limits::max(); if (differenceMs) { *differenceMs = Max; } if (this->getOnGroundDetails() == CAircraftSituation::InFromNetwork) { return false; } if (alwaysSetDetails) { this->setOnGroundDetails(InFromParts); } const qint64 d = this->getAdjustedTimeDifferenceMs(parts.getAdjustedMSecsSinceEpoch()); const bool adjust = (d >= 0) || qAbs(d) < (timeDeviationFactor * parts.getTimeOffsetMs()); // future or past within deviation range if (!adjust) { return false; } if (differenceMs) { *differenceMs = d; } this->setOnGround(parts.isOnGround() ? CAircraftSituation::OnGround : CAircraftSituation::NotOnGround, CAircraftSituation::InFromParts); return true; } bool CAircraftSituation::adjustGroundFlag(const CAircraftPartsList &partsList, bool alwaysSetDetails, double timeDeviationFactor, qint64 *differenceMs) { Q_ASSERT_X(timeDeviationFactor >= 0 && timeDeviationFactor <= 1.0, Q_FUNC_INFO, "Expect 0..1"); static const qint64 Max = std::numeric_limits::max(); if (differenceMs) { *differenceMs = Max; } if (this->getOnGroundDetails() == CAircraftSituation::InFromNetwork) { return false; } if (alwaysSetDetails) { this->setOnGroundDetails(InFromParts); } if (partsList.isEmpty()) { return false; } CAircraftParts bestParts; bool adjust = false; qint64 bestDistance = Max; for (const CAircraftParts &parts : partsList) { const qint64 d = this->getAdjustedTimeDifferenceMs(parts.getAdjustedMSecsSinceEpoch()); const qint64 posD = qAbs(d); const bool candidate = (d >= 0) || posD < (timeDeviationFactor * parts.getTimeOffsetMs()); // future or past within deviation range if (!candidate || bestDistance <= posD) { continue; } bestDistance = posD; if (differenceMs) { *differenceMs = d; } adjust = true; bestParts = parts; if (bestDistance == 0) { break; } } if (!adjust) { return false; } const CAircraftSituation::IsOnGround og = bestParts.isOnGround() ? CAircraftSituation::OnGround : CAircraftSituation::NotOnGround; this->setOnGround(og, CAircraftSituation::InFromParts); return true; } } // namespace } // namespace