Ref T125, altitude parse and FP string functions

* asFpAltitudeString
* asFpAltitudeSimpleVatsimString (non standard format)
* parseFromFpAltitudeString
This commit is contained in:
Klaus Basan
2018-01-04 04:26:11 +01:00
parent 7b414121df
commit d7bc247469
2 changed files with 231 additions and 14 deletions

View File

@@ -96,10 +96,196 @@ namespace BlackMisc
rd = AboveGround;
}
CLength l = BlackMisc::PhysicalQuantities::CPqString::parse<CLength>(v, mode);
const CLength l = BlackMisc::PhysicalQuantities::CPqString::parse<CLength>(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);

View File

@@ -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 <QMetaType>
#include <QString>
#include <QRegularExpression>
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)