swift
physicalquantity.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 <cmath>
7 #include <limits>
8 
9 #include <QDBusArgument>
10 #include <QJsonObject>
11 #include <QJsonValue>
12 #include <QString>
13 #include <QtGlobal>
14 
15 #include "misc/comparefunctions.h"
16 #include "misc/dictionary.h"
17 #include "misc/pq/acceleration.h"
18 #include "misc/pq/angle.h"
19 #include "misc/pq/frequency.h"
20 #include "misc/pq/length.h"
21 #include "misc/pq/mass.h"
23 #include "misc/pq/pqstring.h"
24 #include "misc/pq/pressure.h"
25 #include "misc/pq/speed.h"
26 #include "misc/pq/temperature.h"
27 #include "misc/pq/time.h"
28 #include "misc/propertyindexref.h"
30 #include "misc/stringutils.h"
31 #include "misc/verify.h"
32 
33 SWIFT_DEFINE_PQ_MIXINS(CAngleUnit, CAngle)
34 SWIFT_DEFINE_PQ_MIXINS(CLengthUnit, CLength)
35 SWIFT_DEFINE_PQ_MIXINS(CPressureUnit, CPressure)
36 SWIFT_DEFINE_PQ_MIXINS(CFrequencyUnit, CFrequency)
37 SWIFT_DEFINE_PQ_MIXINS(CMassUnit, CMass)
38 SWIFT_DEFINE_PQ_MIXINS(CTemperatureUnit, CTemperature)
39 SWIFT_DEFINE_PQ_MIXINS(CSpeedUnit, CSpeed)
40 SWIFT_DEFINE_PQ_MIXINS(CTimeUnit, CTime)
41 SWIFT_DEFINE_PQ_MIXINS(CAccelerationUnit, CAcceleration)
42 
43 namespace swift::misc::physical_quantities
44 {
45  template <class MU, class PQ>
47  {
48  return m_unit;
49  }
50 
51  template <class MU, class PQ>
53  {
54  m_unit = unit;
55  }
56 
57  template <class MU, class PQ>
58  void CPhysicalQuantity<MU, PQ>::setUnitBySymbol(const QString &unitName)
59  {
60  m_unit = CMeasurementUnit::unitFromSymbol<MU>(unitName);
61  }
62 
63  template <class MU, class PQ>
65  {
66  return m_unit.getSymbol(true);
67  }
68 
69  template <class MU, class PQ>
71  : m_value(unit.isNull() ? 0.0 : value), m_unit(unit)
72  {
73  Q_ASSERT_X(!std::isnan(value), Q_FUNC_INFO, "nan value");
74  Q_ASSERT_X(!std::isinf(value), Q_FUNC_INFO, "infinity");
75  }
76 
77  template <class MU, class PQ>
78  CPhysicalQuantity<MU, PQ>::CPhysicalQuantity(const QString &unitString) : m_value(0.0), m_unit(MU::nullUnit())
79  {
80  this->parseFromString(unitString);
81  }
82 
83  template <class MU, class PQ>
85  {
86  if (this == &other) return true;
87 
88  if (this->isNull()) return other.isNull();
89  if (other.isNull()) return false;
90 
91  double diff = std::abs(m_value - other.value(m_unit));
92  return diff <= m_unit.getEpsilon();
93  }
94 
95  template <class MU, class PQ>
97  {
98  m_value += other.value(m_unit);
99  return *this;
100  }
101 
102  template <class MU, class PQ>
104  {
105  m_value += value;
106  }
107 
108  template <class MU, class PQ>
110  {
111  m_value -= value;
112  }
113 
114  template <class MU, class PQ>
116  {
117  m_value -= other.value(m_unit);
118  return *this;
119  }
120 
121  template <class MU, class PQ>
123  {
124  return m_unit.isEpsilon(m_value);
125  }
126 
127  template <class MU, class PQ>
129  {
130  return !this->isZeroEpsilonConsidered() && m_value > 0;
131  }
132 
133  template <class MU, class PQ>
135  {
136  return !this->isZeroEpsilonConsidered() && m_value < 0;
137  }
138 
139  template <class MU, class PQ>
141  {
142  if (this->isNull() || qFuzzyIsNull(m_value)) { return *this->derived(); }
143  if (m_value < 0) { m_value *= -1.0; }
144  return *this->derived();
145  }
146 
147  template <class MU, class PQ>
149  {
150  if (this->isNull() || qFuzzyIsNull(m_value)) { return *this->derived(); }
151  if (m_value > 0) { m_value *= -1.0; }
152  return *this->derived();
153  }
154 
155  template <class MU, class PQ>
157  {
158  if (this->isNull() || qFuzzyIsNull(m_value)) { return *this->derived(); }
159  if (m_value >= 0) { return *this->derived(); }
160  PQ copy(*this->derived());
161  return copy.makePositive();
162  }
163 
164  template <class MU, class PQ>
165  void CPhysicalQuantity<MU, PQ>::marshallToDbus(QDBusArgument &argument) const
166  {
167  constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
168  argument << (this->isNull() ? NaN : this->value(UnitClass::defaultUnit()));
169  }
170 
171  template <class MU, class PQ>
172  void CPhysicalQuantity<MU, PQ>::unmarshallFromDbus(const QDBusArgument &argument)
173  {
174  argument >> m_value;
175  m_unit = UnitClass::defaultUnit();
176  if (std::isnan(m_value)) { this->setNull(); }
177  }
178 
179  template <class MU, class PQ>
180  void CPhysicalQuantity<MU, PQ>::marshallToDbus(QDBusArgument &argument, LosslessTag) const
181  {
182  argument << m_value;
183  argument << m_unit;
184  }
185 
186  template <class MU, class PQ>
187  void CPhysicalQuantity<MU, PQ>::unmarshallFromDbus(const QDBusArgument &argument, LosslessTag)
188  {
189  argument >> m_value;
190  argument >> m_unit;
191  }
192 
193  template <class MU, class PQ>
194  void CPhysicalQuantity<MU, PQ>::marshalToDataStream(QDataStream &stream) const
195  {
196  constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
197  stream << (this->isNull() ? NaN : this->value(UnitClass::defaultUnit()));
198  }
199 
200  template <class MU, class PQ>
202  {
203  stream >> m_value;
204  m_unit = UnitClass::defaultUnit();
205  if (std::isnan(m_value)) { this->setNull(); }
206  }
207 
208  template <class MU, class PQ>
210  {
211  m_value *= factor;
212  return *this;
213  }
214 
215  template <class MU, class PQ>
216  PQ CPhysicalQuantity<MU, PQ>::operator*(double factor) const
217  {
218  PQ copy = *derived();
219  copy *= factor;
220  return copy;
221  }
222 
223  template <class MU, class PQ>
225  {
226  m_value /= divisor;
227  return *this;
228  }
229 
230  template <class MU, class PQ>
231  PQ CPhysicalQuantity<MU, PQ>::operator/(double divisor) const
232  {
233  PQ copy = *derived();
234  copy /= divisor;
235  return copy;
236  }
237 
238  template <class MU, class PQ>
240  {
241  PQ copy = *derived();
242  copy *= -1;
243  return copy;
244  }
245 
246  template <class MU, class PQ>
248  {
249  if (*this == other) return false;
250 
251  if (isNull() < other.isNull()) { return true; }
252  if (isNull() > other.isNull()) { return false; }
253  if (isNull() && other.isNull()) { return false; }
254 
255  return (m_value < other.value(m_unit));
256  }
257 
258  template <class MU, class PQ>
260  {
261  // NULL check: https://discordapp.com/channels/539048679160676382/539925070550794240/593151683698229258
262  if (m_unit == newUnit || this->isNull()) { return *derived(); }
263  if (newUnit.isNull()) { this->setNull(); }
264  else
265  {
266  m_value = newUnit.convertFrom(m_value, m_unit);
267  m_unit = newUnit;
268  }
269  return *derived();
270  }
271 
272  template <class MU, class PQ>
273  PQ CPhysicalQuantity<MU, PQ>::switchedUnit(const MU &newUnit) const
274  {
275  if (m_unit == newUnit || this->isNull()) { return *derived(); }
276  PQ copy(*derived());
277  copy.switchUnit(newUnit);
278  return copy;
279  }
280 
281  template <class MU, class PQ>
283  {
284  return m_unit.isNull();
285  }
286 
287  template <class MU, class PQ>
289  {
290  m_value = 0;
291  m_unit = MU::nullUnit();
292  }
293 
294  template <class MU, class PQ>
296  {
297  if (this->isNull()) { return 0.0; }
298  return m_value;
299  }
300 
301  template <class MU, class PQ>
303  {
304  if (!this->isNull()) { m_value = value; }
305  }
306 
307  template <class MU, class PQ>
309  {
310  m_value = baseValue;
311  }
312 
313  template <class MU, class PQ>
314  QString CPhysicalQuantity<MU, PQ>::valueRoundedWithUnit(const MU &unit, int digits, bool withGroupSeparator,
315  bool i18n) const
316  {
317  Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null");
318  if (this->isNull()) { return this->convertToQString(i18n); }
319  return unit.makeRoundedQStringWithUnit(this->value(unit), digits, withGroupSeparator, i18n);
320  }
321 
322  template <class MU, class PQ>
323  QString CPhysicalQuantity<MU, PQ>::valueRoundedWithUnit(int digits, bool withGroupSeparator, bool i18n) const
324  {
325  if (this->isNull()) { return QStringLiteral("null"); }
326  return this->valueRoundedWithUnit(m_unit, digits, withGroupSeparator, i18n);
327  }
328 
329  template <class MU, class PQ>
331  {
332  if (this->isNull()) { return; }
333  m_value = m_unit.roundToEpsilon(m_value);
334  }
335 
336  template <class MU, class PQ>
337  double CPhysicalQuantity<MU, PQ>::valueRounded(MU unit, int digits) const
338  {
339  Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null");
340  return unit.roundValue(this->value(unit), digits);
341  }
342 
343  template <class MU, class PQ>
345  {
346  Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null");
347  const double v = this->value(unit);
348  return qRound(v);
349  }
350 
351  template <class MU, class PQ>
353  {
354  return this->valueInteger(m_unit);
355  }
356 
357  template <class MU, class PQ>
359  {
360  if (this->isNull()) { return false; }
361 
362  const double diff = std::abs(this->value() - this->valueInteger());
363  return diff <= m_unit.getEpsilon();
364  }
365 
366  template <class MU, class PQ>
368  {
369  return this->valueRounded(m_unit, digits);
370  }
371 
372  template <class MU, class PQ>
373  QString CPhysicalQuantity<MU, PQ>::valueRoundedAsString(MU unit, int digits) const
374  {
375  if (this->isNull()) { return QStringLiteral("null"); }
376  const double v = this->valueRounded(unit, digits);
377  return QString::number(v, 'f', digits);
378  }
379 
380  template <class MU, class PQ>
381  double CPhysicalQuantity<MU, PQ>::value(MU unit) const
382  {
383  Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null");
384  return unit.convertFrom(m_value, m_unit);
385  }
386 
387  template <class MU, class PQ>
389  {
390  if (this->isNull()) { return QStringLiteral("null"); }
391  return this->valueRoundedWithUnit(this->getUnit(), -1, i18n);
392  }
393 
394  template <class MU, class PQ>
395  const PQ &CPhysicalQuantity<MU, PQ>::maxValue(const PQ &pq1, const PQ &pq2)
396  {
397  if (pq1.isNull()) { return pq2; }
398  if (pq2.isNull()) { return pq1; }
399  return pq1 > pq2 ? pq1 : pq2;
400  }
401 
402  template <class MU, class PQ>
403  const PQ &CPhysicalQuantity<MU, PQ>::minValue(const PQ &pq1, const PQ &pq2)
404  {
405  if (pq1.isNull()) { return pq2; }
406  if (pq2.isNull()) { return pq1; }
407  return pq1 < pq2 ? pq1 : pq2;
408  }
409 
410  template <class MU, class PQ>
412  {
413  static const PQ n(0, MU::nullUnit());
414  return n;
415  }
416 
417  template <class MU, class PQ>
419  {
420  // there is no double qHash
421  // also unit and rounding has to be considered
422  return qHash(this->valueRoundedWithUnit(MU::defaultUnit()));
423  }
424 
425  template <class MU, class PQ>
427  {
428  QJsonObject json;
429  json.insert("value", QJsonValue(m_value));
430  json.insert("unit", QJsonValue(m_unit.getSymbol()));
431  return json;
432  }
433 
434  template <class MU, class PQ>
435  void CPhysicalQuantity<MU, PQ>::convertFromJson(const QJsonObject &json)
436  {
437  const QJsonValue unit = json.value("unit");
438  const QJsonValue value = json.value("value");
439  if (unit.isUndefined()) { throw CJsonException("Missing 'unit'"); }
440  if (value.isUndefined()) { throw CJsonException("Missing 'value'"); }
441 
442  this->setUnitBySymbol(unit.toString());
443  m_value = value.toDouble();
444  }
445 
446  template <class MU, class PQ>
448  {
449  *this = CPqString::parse<PQ>(value, mode);
450  }
451 
452  template <class MU, class PQ>
454  const MU &defaultUnitIfMissing)
455  {
456  if (is09OrSeparatorOnlyString(value))
457  {
458  const QString v = value + defaultUnitIfMissing.getSymbol();
459  this->parseFromString(v, mode);
460  }
461  else { this->parseFromString(value, mode); }
462  }
463 
464  template <class MU, class PQ>
466  {
467  *this = CPqString::parse<PQ>(value, CPqString::SeparatorQtDefault);
468  }
469 
470  template <class MU, class PQ>
472  const MU &defaultUnitIfMissing)
473  {
474  QString v = value;
475  if (is09OrSeparatorOnlyString(value)) { v = value + defaultUnitIfMissing.getSymbol(); }
476 
477  // no idea why I cannot call pq.parseFromString(v, mode, defaultUnitIfMissing);
478  PQ pq;
479  pq.parseFromString(v, mode);
480  return pq;
481  }
482 
483  template <class MU, class PQ>
485  {
486  PQ pq;
487  pq.parseFromString(value, mode);
488  return pq;
489  }
490 
491  template <class MU, class PQ>
493  {
494  if (index.isMyself()) { return QVariant::fromValue(*derived()); }
495  const ColumnIndex i = index.frontCasted<ColumnIndex>();
496  switch (i)
497  {
498  case IndexValue: return QVariant::fromValue(m_value);
499  case IndexUnit: return QVariant::fromValue(m_unit);
500  case IndexValueRounded0DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(0));
501  case IndexValueRounded1DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(1));
502  case IndexValueRounded2DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(2));
503  case IndexValueRounded3DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(3));
504  case IndexValueRounded6DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(6));
505  default: return mixin::Index<PQ>::propertyByIndex(index);
506  }
507  }
508 
509  template <class MU, class PQ>
511  {
512  if (index.isMyself())
513  {
514  (*this) = variant.value<PQ>();
515  return;
516  }
517  const ColumnIndex i = index.frontCasted<ColumnIndex>();
518  switch (i)
519  {
520  case IndexValue: m_value = variant.toDouble(); break;
521  case IndexUnit: m_unit = variant.value<MU>(); break;
522  case IndexValueRounded0DigitsWithUnit:
523  case IndexValueRounded1DigitsWithUnit:
524  case IndexValueRounded2DigitsWithUnit:
525  case IndexValueRounded3DigitsWithUnit:
526  case IndexValueRounded6DigitsWithUnit: this->parseFromString(variant.toString()); break;
527  default: mixin::Index<PQ>::setPropertyByIndex(index, variant); break;
528  }
529  }
530 
531  template <class MU, class PQ>
533  {
534  if (index.isMyself()) { return compareImpl(*derived(), pq); }
535  const ColumnIndex i = index.frontCasted<ColumnIndex>();
536  switch (i)
537  {
538  case IndexValue: return Compare::compare(m_value, pq.m_value);
539  default: break;
540  }
541  SWIFT_VERIFY_X(false, Q_FUNC_INFO, qUtf8Printable("No comparison for index " + index.toQString()));
542  return 0;
543  }
544 
545  template <class MU, class PQ>
546  int CPhysicalQuantity<MU, PQ>::compareImpl(const PQ &a, const PQ &b)
547  {
548  if (a < b) { return -1; }
549  else if (a > b) { return 1; }
550  else { return 0; }
551  }
552 
553  template <class MU, class PQ>
554  PQ const *CPhysicalQuantity<MU, PQ>::derived() const
555  {
556  return static_cast<PQ const *>(this);
557  }
558 
559  template <class MU, class PQ>
560  PQ *CPhysicalQuantity<MU, PQ>::derived()
561  {
562  return static_cast<PQ *>(this);
563  }
564 
565  // see here for the reason of thess forward instantiations
566  // https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl
568  template class CPhysicalQuantity<CLengthUnit, CLength>;
569  template class CPhysicalQuantity<CPressureUnit, CPressure>;
570  template class CPhysicalQuantity<CFrequencyUnit, CFrequency>;
571  template class CPhysicalQuantity<CMassUnit, CMass>;
572  template class CPhysicalQuantity<CTemperatureUnit, CTemperature>;
573  template class CPhysicalQuantity<CSpeedUnit, CSpeed>;
574  template class CPhysicalQuantity<CAngleUnit, CAngle>;
575  template class CPhysicalQuantity<CTimeUnit, CTime>;
576  template class CPhysicalQuantity<CAccelerationUnit, CAcceleration>;
578 
579 } // namespace swift::misc::physical_quantities
Thrown when a convertFromJson method encounters an unrecoverable error in JSON data.
Definition: jsonexception.h:24
Non-owning reference to a CPropertyIndex with a subset of its features.
QString toQString(bool i18n=false) const
Cast as QString.
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
bool isMyself() const
Myself index, used with nesting.
Tag type signifying overloaded marshalling methods that preserve data at the expense of size.
Definition: mixindbus.h:26
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: mixinindex.h:160
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: mixinindex.h:167
A physical quantity such as "5m", "20s", "1500ft/s".
CPhysicalQuantity & operator+=(const CPhysicalQuantity &other)
Plus operator +=.
bool isNegativeWithEpsilonConsidered() const
Value <= 0 epsilon considered.
void setValueSameUnit(double value)
Change value without changing unit.
QString convertToQString(bool i18n=false) const
Cast as QString.
void parseFromString(const QString &value)
Parse value from string.
void unmarshallFromDbus(const QDBusArgument &argument)
Unmarshall without begin/endStructure, for when composed within another object.
static PQ parsedFromString(const QString &value, CPqString::SeparatorMode mode=CPqString::SeparatorBestGuess)
Parsed from given string.
PQ switchedUnit(const MU &newUnit) const
Return copy with switched unit.
PQ & switchUnit(const MU &newUnit)
Change unit, and convert value to maintain the same quantity.
void setUnitBySymbol(const QString &unitName)
Set unit by string.
size_t getValueHash() const
qHash overload, needed for storing value in a QSet.
QJsonObject toJson() const
Cast to JSON object.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
static const PQ & minValue(const PQ &pq1, const PQ &pq2)
Minimum of 2 quantities.
int comparePropertyByIndex(CPropertyIndexRef index, const PQ &pq) const
Compare for index.
CPhysicalQuantity & operator-=(const CPhysicalQuantity &other)
Minus operator-=.
double value(MU unit) const
Value in given unit.
void unmarshalFromDataStream(QDataStream &stream)
Unmarshal a value from a QDataStream.
CPhysicalQuantity(double value, MU unit)
Constructor with double.
QString valueRoundedAsString(MU unit, int digits=-1) const
Rounded value in given unit.
void roundToEpsilon()
Round current value in current unit to epsilon.
void addValueSameUnit(double value)
Add to the value in the current unit.
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
void setUnit(const MU &unit)
Simply set unit, do no calclulate conversion.
void substractValueSameUnit(double value)
Substract from the value in the current unit.
CPhysicalQuantity & operator/=(double divide)
Divide operator /=.
void marshalToDataStream(QDataStream &stream) const
Marshal a value to a QDataStream.
int valueInteger() const
As integer value in current unit.
void convertFromJson(const QJsonObject &json)
Assign from JSON object.
void setCurrentUnitValue(double value)
Set value in current unit.
const PQ & makePositive()
Make value always positive.
static const PQ & maxValue(const PQ &pq1, const PQ &pq2)
Maximum of 2 quantities.
void marshallToDbus(QDBusArgument &argument) const
Marshall without begin/endStructure, for when composed within another object.
CPhysicalQuantity & operator*=(double multiply)
Multiply operator *=.
QString valueRoundedWithUnit(const MU &unit, int digits=-1, bool withGroupSeparator=false, bool i18n=false) const
Value to QString with the given unit, e.g. "5.00m".
bool isPositiveWithEpsilonConsidered() const
Value >= 0 epsilon considered.
double valueRounded(MU unit, int digits=-1) const
Rounded value in given unit.
const PQ & makeNegative()
Make value always negative.
PQ operator*(double multiply) const
Operator *.
bool isZeroEpsilonConsidered() const
Quantity value <= epsilon.
SeparatorMode
Number separators / group separators.
Definition: pqstring.h:34
@ SeparatorQtDefault
100000.00 no group separator
Definition: pqstring.h:35
size_t qHash(const std::string &key, uint seed)
std::string qHash
Definition: metaclass.h:86
bool is09OrSeparatorOnlyString(const QString &testString)
String with 0-9/separator only.
Definition: stringutils.h:180
#define SWIFT_DEFINE_PQ_MIXINS(MU, PQ)
Explicit template definition of mixins for a CPhysicalQuantity subclass.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26