swift
Loading...
Searching...
No Matches
altitude.cpp
1// SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3
5
6#include <QStringBuilder>
7#include <Qt>
8#include <QtGlobal>
9
11#include "misc/iconlist.h"
12#include "misc/icons.h"
13#include "misc/math/mathutils.h"
14#include "misc/pq/constants.h"
16#include "misc/pq/pqstring.h"
17#include "misc/stringutils.h"
18
19using namespace swift::misc::physical_quantities;
20using namespace swift::misc::math;
21
22namespace swift::misc::aviation
23{
24
26 {
28 qRegisterMetaType<CAltitude::ReferenceDatum>();
29 qRegisterMetaType<CAltitude::AltitudeType>();
30 }
31
32 const QVector<CAltitude::MetricTuple> &CAltitude::metricTuples()
33 {
34 static const QVector<MetricTuple> v = {
35 { 300, 1000 }, { 600, 2000 }, { 900, 3000 }, { 1200, 3900 }, { 1200, 4000 }, { 1500, 4900 },
36 { 1500, 5000 }, { 1800, 5900 }, { 1850, 6000 }, { 2100, 6900 }, { 2150, 7000 }, { 2400, 7900 },
37 { 2450, 8000 }, { 2700, 8900 }, { 2750, 9000 }, { 3000, 9800 }, { 3050, 10000 }, { 3300, 10800 },
38 { 3350, 11000 }, { 3600, 11800 }, { 3650, 12000 }, { 3900, 12800 }, { 3950, 13000 }, { 4200, 13800 },
39 { 4250, 14000 }, { 4500, 14800 }, { 4550, 15000 }, { 4800, 15700 }, { 4900, 16000 }, { 5100, 16700 },
40 { 5200, 17000 }, { 5400, 17700 }, { 5500, 18000 }, { 5700, 18700 }, { 5800, 19000 }, { 6000, 19700 },
41 { 6100, 20000 }, { 6300, 20700 }, { 6400, 21000 }, { 6600, 21700 }, { 6700, 22000 }, { 6900, 22600 },
42 { 7000, 23000 }, { 7200, 23600 }, { 7300, 24000 }, { 7500, 24600 }, { 7600, 25000 }, { 7800, 25600 },
43 { 7900, 26000 }, { 8100, 26600 }, { 8250, 27000 }, { 8400, 27600 }, { 8550, 28000 }, { 8600, 28200 },
44 { 8850, 29000 }, { 8900, 29100 }, { 9100, 29900 }, { 9150, 30000 }, { 9200, 30100 }, { 9450, 31000 },
45 { 9500, 31100 }, { 9600, 31500 }, { 9750, 32000 }, { 9800, 32100 }, { 10050, 33000 }, { 10100, 33100 },
46 { 10350, 34000 }, { 10400, 34100 }, { 10600, 34800 }, { 10650, 35000 }, { 10700, 35100 }, { 10950, 36000 },
47 { 11000, 36100 }, { 11100, 36400 }, { 11300, 37000 }, { 11300, 37100 }, { 11600, 38000 }, { 11600, 38100 },
48 { 11900, 39000 }, { 11900, 39100 }, { 12100, 39700 }, { 12200, 40000 }, { 12200, 40100 }, { 12500, 41000 },
49 { 13100, 43000 }, { 13700, 44900 }, { 13700, 45000 }, { 14100, 46300 }, { 14300, 46900 }, { 14350, 47000 },
50 { 14900, 48900 }, { 14950, 49000 }, { 15100, 49500 }, { 15550, 51000 },
51 };
52 return v;
53 }
54
56 : CLength(0, CLengthUnit::m()), m_datum(MeanSeaLevel)
57 {
58 this->parseFromString(altitudeAsString, mode);
59 }
60
62 {
63 if (this->isNull())
64 {
65 return CAltitude::null();
66 }
67 CAltitude copy(*this);
68 if (!offset.isNull() && !offset.isZeroEpsilonConsidered())
69 {
70 copy += offset.switchedUnit(this->getUnit());
71 }
72 return copy;
73 }
74
75 void CAltitude::addOffset(const CLength &offset) { *this = this->withOffset(offset); }
76
78 {
79 if (newUnit.isNull() || this->getUnit().isNull() || this->getUnit() == newUnit)
80 {
81 return *this;
82 }
83 CLength::switchUnit(newUnit);
84 return *this;
85 }
86
88 {
89 if (newUnit.isNull() || this->getUnit().isNull() || this->getUnit() == newUnit)
90 {
91 return *this;
92 }
93 CAltitude copy(*this);
94 copy.switchUnit(newUnit);
95 return copy;
96 }
97
99 {
100 static const QString n("null");
101 if (this->isNull())
102 {
103 return n;
104 }
105
106 if (m_datum == FlightLevel)
107 {
108 const int fl = qRound(this->CLength::value(CLengthUnit::ft()) / 100.0);
109 return QStringLiteral("FL%1").arg(fl);
110 }
111 else
112 {
113 return this->CLength::valueRoundedWithUnit(1, i18n) % (this->isMeanSeaLevel() ? u" MSL" : u" AGL");
114 }
115 }
116
118 {
119 if (index.isMyself())
120 {
121 return this->compare(compareValue);
122 }
123 return CLength::comparePropertyByIndex(index, compareValue);
124 }
125
127 {
128 if (m_datum != MeanSeaLevel && m_datum != FlightLevel)
129 {
130 return false;
131 }
132 m_datum = FlightLevel;
133 return true;
134 }
135
137 {
138 if (m_datum != MeanSeaLevel && m_datum != FlightLevel)
139 {
140 return false;
141 }
142 m_datum = MeanSeaLevel;
143 return true;
144 }
145
147 {
148 if (m_altitudeType == PressureAltitude)
149 {
150 return;
151 }
152 if (this->isNull())
153 {
154 return;
155 }
156 const CPressure deltaPressure = standardISASeaLevelPressure() - seaLevelPressure;
157 const double deltaPressureV = deltaPressure.value(CPressureUnit::mbar());
158 const double deltaAltitudeV = deltaPressureV * 30.0; // 30.0 ft per mbar
159 CLength deltaAltitude(deltaAltitudeV, CLengthUnit::ft());
160 *this += deltaAltitude;
161 m_altitudeType = PressureAltitude;
162 }
163
164 CAltitude CAltitude::toPressureAltitude(const CPressure &seaLevelPressure) const
165 {
166 if (seaLevelPressure.isNull())
167 {
168 return CAltitude::null();
169 }
170 if (this->isNull())
171 {
172 return CAltitude::null();
173 }
174 CAltitude other(*this);
175 other.convertToPressureAltitude(seaLevelPressure);
176 return other;
177 }
178
183
185 {
186 QString v = value.trimmed();
187
188 // special case FL
189 if (v.contains("FL", Qt::CaseInsensitive) || v.startsWith("F"))
190 {
192 bool ok = false;
193 const int dv = v.toInt(&ok) * 100;
194 const CAltitude a(ok ? dv : 0.0, FlightLevel, ok ? CLengthUnit::ft() : nullptr);
195 *this = a;
196 return;
197 }
198
199 // special case A (altitude
200 if (v.contains("ALT", Qt::CaseInsensitive) || v.startsWith("A"))
201 {
203 bool ok = false;
204 const int dv = v.toInt(&ok) * 100;
205 const CAltitude a(ok ? dv : 0.0, MeanSeaLevel, ok ? CLengthUnit::ft() : nullptr);
206 *this = a;
207 return;
208 }
209
210 // normal altitude, AGL/MSL
212 if (v.contains("MSL", Qt::CaseInsensitive))
213 {
214 v = v.replace("MSL", "", Qt::CaseInsensitive).trimmed();
215 rd = MeanSeaLevel;
216 }
217 else if (v.contains("AGL"))
218 {
219 v = v.replace("AGL", "", Qt::CaseInsensitive).trimmed();
220 rd = AboveGround;
221 }
222
223 const auto l = CPqString::parse<CLength>(v, mode);
224 *this = CAltitude(l, rd);
225 }
226
228 {
229 QString v(value.trimmed()); // do not convert case because of units
230 if (v.startsWith("VFR", Qt::CaseInsensitive))
231 {
232 // we set a more or less meaningful value
233 *this = CAltitude(5000, MeanSeaLevel, CLengthUnit::ft());
234 return true;
235 }
236
237 this->setNull();
238 if (v.length() < 3)
239 {
240 if (msgs)
241 {
242 msgs->push_back(CStatusMessage(this).validationError(u"Altitude empty or too short"));
243 }
244 return false;
245 }
246
247 if (!fpAltitudeRegExp().globalMatch(v).hasNext())
248 {
249 if (msgs)
250 {
251 msgs->push_back(CStatusMessage(this).validationError(u"Altitude '%1' needs to match format '%2'")
252 << v << fpAltitudeExamples());
253 }
254 return false;
255 }
256
257 // normalize characters to upper/lower
258 // in same step get numeric value only
259 bool beforeDigit = true;
260 QString numericPart;
261 for (int i = 0; i < v.length(); i++) // NOLINT
262 {
263 const QChar c = v[i];
264 if (c.isDigit())
265 {
266 beforeDigit = false;
267 numericPart.push_back(c);
268 continue;
269 }
270 v[i] = beforeDigit ? c.toUpper() : c.toLower();
271 }
272
273 if (numericPart.isEmpty())
274 {
275 if (msgs)
276 {
277 msgs->push_back(CStatusMessage(this).validationError(u"Altitude '%1' has no numeric part"));
278 }
279 return false;
280 }
281
282 bool ok {};
284 {
285 this->setUnit(CLengthUnit::ft());
286 this->setValueSameUnit(numericPart.toInt(&ok) * 100);
287 m_datum = FlightLevel;
288 }
289 else if (v.startsWith("S", Qt::CaseInsensitive))
290 {
291 this->setUnit(CLengthUnit::m());
292 this->setValueSameUnit(numericPart.toInt(&ok) * 10);
293 m_datum = FlightLevel;
294 }
295 else if (v.startsWith("A", Qt::CaseInsensitive))
296 {
297 this->setUnit(CLengthUnit::ft());
298 this->setValueSameUnit(numericPart.toInt(&ok) * 100);
299 m_datum = MeanSeaLevel;
300 }
301 else if (v.startsWith("M", Qt::CaseInsensitive))
302 {
303 this->setUnit(CLengthUnit::m());
304 this->setValueSameUnit(numericPart.toInt(&ok) * 10);
305 m_datum = MeanSeaLevel;
306 }
307 else if (v.endsWith(CLengthUnit::m().getSymbol()))
308 {
309 this->setUnit(CLengthUnit::m());
310 this->setValueSameUnit(numericPart.toInt(&ok));
311 m_datum = MeanSeaLevel;
312 }
313 else if (v.endsWith(CLengthUnit::ft().getSymbol()))
314 {
315 this->setUnit(CLengthUnit::ft());
316 this->setValueSameUnit(numericPart.toInt(&ok));
317 m_datum = MeanSeaLevel;
318 }
319 else
320 {
321 if (msgs)
322 {
323 msgs->push_back(CStatusMessage(this).validationError(u"Altitude '%1' needs to match format '%2'")
324 << v << fpAltitudeExamples());
325 }
326 return false;
327 }
328
329 // further checks
330 const bool valid = this->isValidFpAltitude(msgs);
331 if (!valid)
332 {
333 this->setNull();
334 }
335 return valid;
336 }
337
339 {
340 if (this->isNull())
341 {
342 if (msgs)
343 {
344 msgs->push_back(CStatusMessage(this).validationError(u"Altitude NULL value"));
345 }
346 return false;
347 }
348 if (this->getReferenceDatum() != FlightLevel && this->getReferenceDatum() != MeanSeaLevel)
349 {
350 if (msgs)
351 {
352 msgs->push_back(CStatusMessage(this).validationError(u"Altitude, must be FL or MSL"));
353 }
354 return false;
355 }
356 if (!(this->getUnit() == CLengthUnit::m() || this->getUnit() == CLengthUnit::ft()))
357 {
358 if (msgs)
359 {
360 msgs->push_back(CStatusMessage(this).validationError(u"Altitude, valid unit must be 'ft' or 'm'"));
361 }
362 return false;
363 }
365 {
366 if (msgs)
367 {
368 msgs->push_back(CStatusMessage(this).validationError(u"Altitude must be positive"));
369 }
370 return false;
371 }
372 if (this->isFlightLevel())
373 {
374 if (!this->isInteger())
375 {
376 if (msgs)
377 {
378 msgs->push_back(CStatusMessage(this).validationError(u"FL needs to be positive integer"));
379 }
380 return false;
381 }
382
383 if (this->getUnit() == CLengthUnit::ft())
384 {
385 const int flInt = this->valueInteger() / 100; // internally represented as ft: FL10->1000
386 if (flInt < 10 || flInt >= 1000)
387 {
388 if (msgs)
389 {
390 msgs->push_back(CStatusMessage(this).validationError(u"FL range is 10-999"));
391 }
392 return false;
393 }
394 if (!CMathUtils::epsilonZero(fmod(flInt, 5)))
395 {
396 if (msgs)
397 {
398 msgs->push_back(CStatusMessage(this).validationWarning(u"FL should end with 0 or 5"));
399 }
400 }
401 }
402 }
403 return true;
404 }
405
407 {
408 if (this->isNull())
409 {
410 return {};
411 }
412 if (this->isFlightLevel())
413 {
414 if (this->getUnit() == CLengthUnit::m())
415 {
416 int m = this->valueInteger() / 10;
417 return QStringLiteral("S%1").arg(m, 4, 10, QChar('0'));
418 }
419 int ft = this->valueInteger(CLengthUnit::ft()) / 100;
420 return QStringLiteral("FL%1").arg(ft, 3, 10, QChar('0'));
421 }
422
423 if (this->getUnit() == CLengthUnit::m())
424 {
425 int m = this->valueInteger() / 10;
426 return QStringLiteral("M%1").arg(m, 4, 10, QChar('0'));
427 }
428 int ft = this->valueInteger(CLengthUnit::ft()) / 100;
429 return QStringLiteral("A%1").arg(ft, 3, 10, QChar('0'));
430 }
431
433 {
434 if (this->isFlightLevel())
435 {
436 return this->asFpICAOAltitudeString();
437 }
438 // the M/A formats are not supported by VATSIM, means by other clients
439
440 // as feed, as none of the other clients
441 const CAltitude a = this->roundedToNearest100ft(false);
442 // return a.valueRoundedWithUnit(CLengthUnit::ft(), 0);
444 }
445
447 {
448 thread_local const QRegularExpression re(
449 R"(([Ff][Ll]?\d{2,3})|([Ss]\d{2,4})|([Aa]\d{2,3})|([Mm]\d{2,4})|(\d{3,5}(ft|m)))");
450 return re;
451 }
452
454 {
455 // remark use arg %01 to avoid clash with numbers, see
456 // https://stackoverflow.com/questions/35517025/qstringarg-with-number-after-placeholder
457 static const QString e(
458 "FL085, F85 flight level in hecto feets%1S0150 metric level in tens of meters%1A055 altitude in hundreds "
459 "of feet%012000ft altitude in ft%1M0610 altitude in tens of meters%016100m altitude in meters");
460 return e.arg(separator);
461 }
462
464 {
465 static const QString e("FL085, F85, S0150, A055, 1200ft, M0610, 6100m");
466 return e;
467 }
468
469 CIcons::IconIndex CAltitude::toIcon() const { return CIcons::GeoPosition; }
470
471 int CAltitude::compare(const CAltitude &otherAltitude) const
472 {
473 if (this->getReferenceDatum() != otherAltitude.getReferenceDatum())
474 {
475 return Compare::compare(static_cast<int>(this->getReferenceDatum()),
476 static_cast<int>(otherAltitude.getReferenceDatum()));
477 }
478 return CLength::compare(*this, otherAltitude);
479 }
480
482 {
483 // 23453 => 234.53
485 const double ft = a.value(CLengthUnit::ft()) / 100.0;
486 const int ftR = roundDown ? static_cast<int>(floor(ft)) * 100 : qRound(ft) * 100;
487 a.setValueSameUnit(ftR);
488 return a;
489 }
490
492 {
493 // m/ft/FL
494 for (const MetricTuple &m : metricTuples())
495 {
496 if (std::get<1>(m) == feet) return std::get<0>(m);
497 }
498 return -1;
499 }
500
502 {
503 // m/ft/FL
504 for (const MetricTuple &m : metricTuples())
505 {
506 if (std::get<0>(m) == metric) return std::get<1>(m);
507 }
508 return -1;
509 }
510
512 {
514 return null;
515 }
516
518
520 {
521 // Average sea-level pressure is 1013.25mbar or 1013.25hPa
522 static const CPressure standardPressure(CPhysicalQuantitiesConstants::ISASeaLevelPressure());
523 return standardPressure;
524 }
525} // namespace swift::misc::aviation
IconIndex
Index for each icon, allows to send them via DBus, efficiently store them, etc.
Definition icons.h:32
Non-owning reference to a CPropertyIndex with a subset of its features.
bool isMyself() const
Myself index, used with nesting.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition sequence.h:308
Streamable status message, e.g.
Status messages, e.g. from Core -> GUI.
QString asFpVatsimAltitudeString() const
As simple VATSIM string, only FLxxx or altitude as ft.
Definition altitude.cpp:432
CAltitude switchedUnit(const physical_quantities::CLengthUnit &newUnit) const
Value in switched unit.
Definition altitude.cpp:87
void parseFromString(const QString &value)
Parse value from string.
Definition altitude.cpp:179
bool isValidFpAltitude(CStatusMessageList *msgs=nullptr) const
Is this a valid FP altitude.
Definition altitude.cpp:338
static physical_quantities::CLengthUnit defaultUnit()
Default unit for calculations.
Definition altitude.cpp:517
static QString fpAltitudeExamples()
Examples of FP altitude strings.
Definition altitude.cpp:463
static int findMetricAltitude(int feet)
Search the corresponding feet <-> metric / metric <-> feet.
Definition altitude.cpp:491
bool toFlightLevel()
MSL to flightlevel.
Definition altitude.cpp:126
int comparePropertyByIndex(CPropertyIndexRef index, const CAltitude &compareValue) const
Set property by index.
Definition altitude.cpp:117
QString asFpICAOAltitudeString() const
Altitude string (official version).
Definition altitude.cpp:406
bool parseFromFpAltitudeString(const QString &value, CStatusMessageList *msgs=nullptr)
Parse from FP altitude string.
Definition altitude.cpp:227
static void registerMetadata()
Register metadata.
Definition altitude.cpp:25
bool isMeanSeaLevel() const
MSL Mean sea level?
Definition altitude.h:139
CAltitude withOffset(const CLength &offset) const
Altitude with offset.
Definition altitude.cpp:61
static int findAltitudeForMetricAltitude(int metric)
Search the corresponding feet <-> metric / metric <-> feet.
Definition altitude.cpp:501
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition altitude.cpp:98
bool isFlightLevel() const
Flight level?
Definition altitude.h:142
CAltitude & switchUnit(const physical_quantities::CLengthUnit &newUnit)
Value in switched unit.
Definition altitude.cpp:77
static QString fpAltitudeInfo(const QString &separator=", ")
Info for FP altitude strings.
Definition altitude.cpp:453
int compare(const CAltitude &otherAltitude) const
Return negative, zero, or positive if a is less than, equal to, or greater than b.
Definition altitude.cpp:471
CAltitude()
Default constructor: 0m Altitude MSL.
Definition altitude.h:87
@ PressureAltitude
Altitude above the standard datum plane.
Definition altitude.h:82
void convertToPressureAltitude(const physical_quantities::CPressure &seaLevelPressure)
Converts this to pressure altitude. Requires the current barometric pressure at MSL.
Definition altitude.cpp:146
CAltitude roundedToNearest100ft(bool roundDown) const
Round to the nearest 100ft, like needed for China and Russia.
Definition altitude.cpp:481
void addOffset(const CLength &offset)
Add offset value.
Definition altitude.cpp:75
ReferenceDatum
Enum type to distinguish between MSL and AGL.
Definition altitude.h:73
static const QRegularExpression & fpAltitudeRegExp()
Checking FP altitude strings like "A20", "FL100".
Definition altitude.cpp:446
static const CAltitude & null()
Null altitude (MSL).
Definition altitude.cpp:511
std::tuple< int, int > MetricTuple
Metric tuple.
Definition altitude.h:58
swift::misc::CIcons::IconIndex toIcon() const
As icon, not implemented by all classes.
Definition altitude.cpp:469
ReferenceDatum getReferenceDatum() const
Get reference datum (MSL or AGL).
Definition altitude.h:145
CAltitude toPressureAltitude(const physical_quantities::CPressure &seaLevelPressure) const
Returns the altitude converted to pressure altitude. Requires the current barometric pressure at MSL.
Definition altitude.cpp:164
static const physical_quantities::CPressure & standardISASeaLevelPressure()
Standard pressure 1013.25mbar/hPa.
Definition altitude.cpp:519
bool toMeanSeaLevel()
Flightlevel to MSL.
Definition altitude.cpp:136
static bool epsilonZero(double v, double epsilon)
Epsilon safe zero.
Definition mathutils.h:46
int comparePropertyByIndex(CPropertyIndexRef index, const CLength &compareValue) const
Definition mixinindex.h:203
static void registerMetadata()
Register metadata.
Specialized class for distance units (meter, foot, nautical miles).
Definition units.h:95
static CLengthUnit nullUnit()
Null unit.
Definition units.h:133
static CLengthUnit m()
Meter m.
Definition units.h:140
static CLengthUnit ft()
Foot ft.
Definition units.h:157
static const CPressure & ISASeaLevelPressure()
International Standard Atmosphere pressure at mean sea level, 1013.25hPa.
Definition constants.h:22
PQ switchedUnit(const MU &newUnit) const
Return copy with switched unit.
QString valueIntegerAsString(MU unit) const
As integer value.
QString valueRoundedWithUnit(const CLengthUnit &unit, int digits=-1, bool withGroupSeparator=false, bool i18n=false) const
bool isZeroEpsilonConsidered() const
Quantity value <= epsilon.
static PQ parse(const QString &value, SeparatorMode mode=SeparatorQtDefault)
Parse into concrete type.
Definition pqstring.h:58
SeparatorMode
Number separators / group separators.
Definition pqstring.h:34
static CPressureUnit mbar()
Millibar, actually the same as hPa.
Definition units.h:609
int compare(T a, T b)
Compare arithmetic values.
QString char09OnlyString(const QString &string)
String only with 0-9.
bool isDigit(char32_t ucs4)
char32_t toLower(char32_t ucs4)
char32_t toUpper(char32_t ucs4)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
void push_back(QChar ch)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString trimmed() const const
CaseInsensitive