From d7bc247469ad9008621df98f6348e89f2eec8be6 Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Thu, 4 Jan 2018 04:26:11 +0100 Subject: [PATCH] Ref T125, altitude parse and FP string functions * asFpAltitudeString * asFpAltitudeSimpleVatsimString (non standard format) * parseFromFpAltitudeString --- src/blackmisc/aviation/altitude.cpp | 188 +++++++++++++++++++++++++++- src/blackmisc/aviation/altitude.h | 57 +++++++-- 2 files changed, 231 insertions(+), 14 deletions(-) diff --git a/src/blackmisc/aviation/altitude.cpp b/src/blackmisc/aviation/altitude.cpp index 8bb7a010d..72c92e3a0 100644 --- a/src/blackmisc/aviation/altitude.cpp +++ b/src/blackmisc/aviation/altitude.cpp @@ -96,10 +96,196 @@ namespace BlackMisc rd = AboveGround; } - CLength l = BlackMisc::PhysicalQuantities::CPqString::parse(v, mode); + const CLength l = BlackMisc::PhysicalQuantities::CPqString::parse(v, mode); *this = CAltitude(l, rd); } + bool CAltitude::parseFromFpAltitudeString(const QString &value, CStatusMessageList *msgs) + { + QString v(value.trimmed()); + this->setNull(); + if (v.isEmpty() || v.length() < 3) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude empty or too short")); } + return false; + } + + if (!fpAltitudeRegExp().globalMatch(v).hasNext()) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude '%1' needs to match format '%2'") << v << fpAltitudeExamples()); } + return false; + } + + // normalize characters to upper/lower + // in same step get numeric value only + bool beforeDigit = true; + QString numericPart; + for (int i = 0; i < v.length(); i++) + { + const QChar c = v[i]; + if (c.isDigit()) + { + beforeDigit = false; + numericPart.push_back(c); + continue; + } + v[i] = beforeDigit ? c.toUpper() : c.toLower(); + } + + if (numericPart.isEmpty()) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude '%1' has no numeric part")); } + return false; + } + + bool ok; + if (v.startsWith("F")) + { + this->setUnit(CLengthUnit::ft()); + this->setValueSameUnit(numericPart.toInt(&ok) * 100); + m_datum = FlightLevel; + } + else if (v.startsWith("S")) + { + this->setUnit(CLengthUnit::m()); + this->setValueSameUnit(numericPart.toInt(&ok) * 10); + m_datum = FlightLevel; + } + else if (v.startsWith("A")) + { + this->setUnit(CLengthUnit::ft()); + this->setValueSameUnit(numericPart.toInt(&ok) * 100); + m_datum = MeanSeaLevel; + } + else if (v.startsWith("M")) + { + this->setUnit(CLengthUnit::m()); + this->setValueSameUnit(numericPart.toInt(&ok) * 10); + m_datum = MeanSeaLevel; + } + else if (v.endsWith(CLengthUnit::m().getSymbol())) + { + this->setUnit(CLengthUnit::m()); + this->setValueSameUnit(numericPart.toInt(&ok)); + m_datum = MeanSeaLevel; + } + else if (v.endsWith(CLengthUnit::ft().getSymbol())) + { + this->setUnit(CLengthUnit::ft()); + this->setValueSameUnit(numericPart.toInt(&ok)); + m_datum = MeanSeaLevel; + } + else + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude '%1' needs to match format '%2'") << v << fpAltitudeExamples()); } + return false; + } + + // further checks + const bool valid = this->isValidFpAltitude(msgs); + if (!valid) { this->setNull(); } + return valid; + } + + bool CAltitude::isValidFpAltitude(CStatusMessageList *msgs) const + { + if (this->isNull()) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude NULL value")); } + return false; + } + if (!(this->getReferenceDatum() == FlightLevel || this->getReferenceDatum() == MeanSeaLevel)) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude, must be FL or MSL")); } + return false; + } + if (!(this->getUnit() == CLengthUnit::m() || this->getUnit() == CLengthUnit::ft())) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude, valid unit must be 'ft' or 'm'")); } + return false; + } + if (this->isNegativeWithEpsilonConsidered()) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("Altitude must be positive")); } + return false; + } + if (this->isFlightLevel()) + { + if (!this->isInteger()) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("FL needs to be positive integer")); } + return false; + } + + if (this->getUnit() == CLengthUnit::ft()) + { + const int flInt = this->valueInteger() / 100; // internally represented as ft: FL10->1000 + if (flInt < 10 || flInt >= 1000) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("FL range is 10-999")); } + return false; + } + if (fmod(flInt, 5) != 0) + { + if (msgs) { msgs->push_back(CStatusMessage(this).validationError("FL needs to end with 0 or 5")); } + return false; + } + } + } + return true; + } + + QString CAltitude::asFpAltitudeString() const + { + if (this->isNull()) { return QStringLiteral(""); } + + + if (this->isFlightLevel()) + { + if (this->getUnit() == CLengthUnit::m()) + { + int m = this->valueInteger() / 10; + return QString("S%1").arg(m, 4, 10, QChar('0')); + } + int ft = this->valueInteger(CLengthUnit::ft()) / 100; + return QString("FL%1").arg(ft, 3, 10, QChar('0')); + } + + if (this->getUnit() == CLengthUnit::m()) + { + int m = this->valueInteger() / 10; + return QString("M%1").arg(m, 4, 10, QChar('0')); + } + int ft = this->valueInteger(CLengthUnit::ft()) / 100; + return QString("A%1").arg(ft, 3, 10, QChar('0')); + } + + QString CAltitude::asFpAltitudeSimpleVatsimString() const + { + CAltitude copy(*this); + copy.switchUnit(CLengthUnit::ft()); + if (copy.isFlightLevel()) { return copy.asFpAltitudeString(); } + return QString::number(copy.valueInteger()); // ft altitude without unit + } + + const QRegularExpression &CAltitude::fpAltitudeRegExp() + { + static thread_local QRegularExpression re("((FL|F)\\d{2,3})|(S\\d{2,4})|(A\\d{2,3})|(M\\d{2,4})|(\\d{3,5}(ft|m))"); + return re; + } + + QString CAltitude::fpAltitudeInfo(const QString &separator) + { + static const QString e("FL085, F85 flight level%1S0150 metric level in tens of metres%1A055 altitude in hundreds of feet%12000ft altitude in ft%1M0610 altitude in tens of metres%16100m altitude in meters"); + return e.arg(separator); + } + + QString CAltitude::fpAltitudeExamples() + { + static const QString e("FL085, F85, S0150, A055, 1200ft, M0610, 6100m"); + return e; + } + CIcon CAltitude::toIcon() const { return BlackMisc::CIcon::iconByIndex(CIcons::GeoPosition); diff --git a/src/blackmisc/aviation/altitude.h b/src/blackmisc/aviation/altitude.h index e4a7c0474..e4bea24e2 100644 --- a/src/blackmisc/aviation/altitude.h +++ b/src/blackmisc/aviation/altitude.h @@ -12,21 +12,23 @@ #ifndef BLACKMISC_AVIATION_ALTITUDE_H #define BLACKMISC_AVIATION_ALTITUDE_H -#include "blackmisc/blackmiscexport.h" +#include "blackmisc/pq/length.h" +#include "blackmisc/pq/pqstring.h" +#include "blackmisc/pq/units.h" +#include "blackmisc/statusmessagelist.h" #include "blackmisc/compare.h" #include "blackmisc/dbus.h" #include "blackmisc/dictionary.h" #include "blackmisc/icon.h" #include "blackmisc/metaclass.h" -#include "blackmisc/pq/length.h" -#include "blackmisc/pq/pqstring.h" -#include "blackmisc/pq/units.h" #include "blackmisc/propertyindexvariantmap.h" #include "blackmisc/stringutils.h" #include "blackmisc/variant.h" +#include "blackmisc/blackmiscexport.h" #include #include +#include namespace BlackMisc { @@ -72,20 +74,20 @@ namespace BlackMisc //! \copydoc BlackMisc::Mixin::String::toQString QString convertToQString(bool i18n = false) const; - //! Default constructor: 0 Altitude true - CAltitude() : CLength(0, BlackMisc::PhysicalQuantities::CLengthUnit::m()), m_datum(MeanSeaLevel) {} + //! Default constructor: 0m Altitude MSL + CAltitude() : CLength(0, PhysicalQuantities::CLengthUnit::m()), m_datum(MeanSeaLevel) {} //! Constructor - CAltitude(double value, ReferenceDatum datum, const BlackMisc::PhysicalQuantities::CLengthUnit &unit) : CLength(value, unit), m_datum(datum) {} + CAltitude(double value, ReferenceDatum datum, const PhysicalQuantities::CLengthUnit &unit) : CLength(value, unit), m_datum(datum) {} //! Constructor, value as CAltitude::MeanSeaLevel - CAltitude(double value, const BlackMisc::PhysicalQuantities::CLengthUnit &unit) : CLength(value, unit), m_datum(MeanSeaLevel) {} + CAltitude(double value, const PhysicalQuantities::CLengthUnit &unit) : CLength(value, unit), m_datum(MeanSeaLevel) {} //! Altitude as string - CAltitude(const QString &altitudeAsString, BlackMisc::PhysicalQuantities::CPqString::SeparatorMode mode = BlackMisc::PhysicalQuantities::CPqString::SeparatorsLocale); + CAltitude(const QString &altitudeAsString, PhysicalQuantities::CPqString::SeparatorMode mode = PhysicalQuantities::CPqString::SeparatorsLocale); //! Constructor by CLength - CAltitude(const BlackMisc::PhysicalQuantities::CLength &altitude, ReferenceDatum datum) : CLength(altitude), m_datum(datum) {} + CAltitude(const PhysicalQuantities::CLength &altitude, ReferenceDatum datum) : CLength(altitude), m_datum(datum) {} //! AGL Above ground level? bool isAboveGroundLevel() const { return AboveGround == this->m_datum; } @@ -109,7 +111,36 @@ namespace BlackMisc void parseFromString(const QString &value); //! Parse value from string, with specified separator - void parseFromString(const QString &value, BlackMisc::PhysicalQuantities::CPqString::SeparatorMode mode); + void parseFromString(const QString &value, PhysicalQuantities::CPqString::SeparatorMode mode); + + //! Parse from FP altitude string + //! \sa CFlightPlan::asFpAltitudeString + bool parseFromFpAltitudeString(const QString &value, CStatusMessageList *msgs = nullptr); + + //! Is this a valid FP altitude + //! \sa CFlightPlan::asFpAltitudeString + bool isValidFpAltitude(CStatusMessageList *msgs = nullptr) const; + + //! Altitude string (official version) + //! * flight level, expressed as "F" followed by 3 figures, example: F085 (which means flight level 085), + //! * standard metric level in tens of meters, expressed as "S" followed by 4 figures, example: S0150 (which means 1500 metres) + //! * altitude in hundreds of feet, expressed as "A" followed by 3 figures, example: A055 (which means 5500 feet altitude) + //! * altitude in tens of meters expressed as "M" followed by 4 figures, example: M0610 (which means 6100 metres altitude) + QString asFpAltitudeString() const; + + //! As simple VATSIM string, only FLxxx or altitude as ft + QString asFpAltitudeSimpleVatsimString() const; + + //! Checking FP altitude strings like "A20", "FL100" + //! \sa CFlightPlan::asFpAltitudeString + static const QRegularExpression &fpAltitudeRegExp(); + + //! Info for FP altitude strings + //! \sa CFlightPlan::asFpAltitudeString + static QString fpAltitudeInfo(const QString &separator = ", "); + + //! Examples of FP altitude strings + static QString fpAltitudeExamples(); //! \copydoc BlackMisc::Mixin::Icon::toIcon BlackMisc::CIcon toIcon() const; @@ -125,8 +156,8 @@ namespace BlackMisc BLACK_METAMEMBER(datum) ); }; - } -} + } // ns +} // ns Q_DECLARE_METATYPE(BlackMisc::Aviation::CAltitude) Q_DECLARE_METATYPE(BlackMisc::Aviation::CAltitude::ReferenceDatum)