mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 14:55:36 +08:00
Ref T111, lat/lng/angle changes
* get struct CAngle::DegMinSecFractionalSec to obtain parts * round to epsilon utility functions and fix (qint64)
This commit is contained in:
committed by
Mathew Sutcliffe
parent
77546a46b1
commit
e55480737e
@@ -212,6 +212,13 @@ namespace BlackMisc
|
||||
CCoordinateGeodetic::CCoordinateGeodetic(double latitudeDegrees, double longitudeDegrees, double heightFeet) :
|
||||
CCoordinateGeodetic( { latitudeDegrees, BlackMisc::PhysicalQuantities::CAngleUnit::deg() }, { longitudeDegrees, BlackMisc::PhysicalQuantities::CAngleUnit::deg() }, { heightFeet, BlackMisc::PhysicalQuantities::CLengthUnit::ft() }) {}
|
||||
|
||||
CCoordinateGeodetic::CCoordinateGeodetic(const ICoordinateGeodetic &coordinate) :
|
||||
m_x(coordinate.normalVectorDouble()[0]),
|
||||
m_y(coordinate.normalVectorDouble()[1]),
|
||||
m_z(coordinate.normalVectorDouble()[2]),
|
||||
m_geodeticHeight(coordinate.geodeticHeight())
|
||||
{ }
|
||||
|
||||
CLatitude CCoordinateGeodetic::latitude() const
|
||||
{
|
||||
return { std::atan2(m_z, std::hypot(m_x, m_y)), PhysicalQuantities::CAngleUnit::rad() };
|
||||
|
||||
@@ -187,8 +187,7 @@ namespace BlackMisc
|
||||
{
|
||||
public:
|
||||
//! Default constructor
|
||||
CCoordinateGeodetic() :
|
||||
CCoordinateGeodetic(0, 0, 0) {}
|
||||
CCoordinateGeodetic() : CCoordinateGeodetic(0, 0, 0) {}
|
||||
|
||||
//! Constructor by normal vector
|
||||
CCoordinateGeodetic(const QVector3D &normal) : m_x(normal.x()), m_y(normal.y()), m_z(normal.z()) {}
|
||||
@@ -202,6 +201,9 @@ namespace BlackMisc
|
||||
//! Constructor by values
|
||||
CCoordinateGeodetic(double latitudeDegrees, double longitudeDegrees, double heightFeet);
|
||||
|
||||
//! Constructor by interface
|
||||
CCoordinateGeodetic(const ICoordinateGeodetic &coordinate);
|
||||
|
||||
//! \copydoc ICoordinateGeodetic::latitude
|
||||
virtual CLatitude latitude() const override;
|
||||
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
#include <QtGlobal>
|
||||
#include <QtMath>
|
||||
|
||||
using namespace BlackMisc::PhysicalQuantities;
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
namespace Geo
|
||||
{
|
||||
|
||||
template <class LATorLON>
|
||||
CEarthAngle<LATorLON> &CEarthAngle<LATorLON>::operator +=(const CEarthAngle &latOrLon)
|
||||
{
|
||||
@@ -70,49 +71,64 @@ namespace BlackMisc
|
||||
template <class LATorLON>
|
||||
LATorLON CEarthAngle<LATorLON>::fromWgs84(const QString &wgsCoordinate)
|
||||
{
|
||||
// http://www.regular-expressions.info/floatingpoint.html
|
||||
const QString wgs = wgsCoordinate.simplified().trimmed();
|
||||
thread_local const QRegularExpression rx("([-+]?[0-9]*\\.?[0-9]+)");
|
||||
qint32 deg = 0;
|
||||
qint32 min = 0;
|
||||
double sec = 0.0;
|
||||
double secFragment = 0.0;
|
||||
int fragmentLength = 0;
|
||||
int c = 0;
|
||||
int pos = 0;
|
||||
QRegularExpressionMatch match = rx.match(wgs, pos);
|
||||
while (match.hasMatch())
|
||||
if (wgs.isEmpty()) { return LATorLON(); }
|
||||
|
||||
// support for 5deg, 1.2rad
|
||||
if (CAngleUnit::deg().endsStringWithNameOrSymbol(wgs) || CAngleUnit::rad().endsStringWithNameOrSymbol(wgs))
|
||||
{
|
||||
QString cap = match.captured(1);
|
||||
pos += match.capturedLength(1);
|
||||
switch (c++)
|
||||
{
|
||||
case 0:
|
||||
deg = cap.toInt();
|
||||
break;
|
||||
case 1:
|
||||
min = cap.toInt();
|
||||
break;
|
||||
case 2:
|
||||
sec = cap.toDouble();
|
||||
break;
|
||||
case 3:
|
||||
secFragment = cap.toDouble();
|
||||
fragmentLength = cap.length();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
match = rx.match(wgs, pos);
|
||||
}
|
||||
if (fragmentLength > 0)
|
||||
{
|
||||
// we do have given ms in string
|
||||
sec += secFragment / qPow(10, fragmentLength);
|
||||
LATorLON latOrLon;
|
||||
latOrLon.parseFromString(wgs);
|
||||
return latOrLon;
|
||||
}
|
||||
|
||||
if (wgs.contains('S', Qt::CaseInsensitive) ||
|
||||
wgs.contains('W', Qt::CaseInsensitive)) deg *= -1;
|
||||
// number only -> parsed as degrees
|
||||
bool isDouble;
|
||||
const double valueDegrees = wgs.toDouble(&isDouble);
|
||||
if (isDouble)
|
||||
{
|
||||
CAngle a(valueDegrees, CAngleUnit::deg());
|
||||
return LATorLON(a);
|
||||
}
|
||||
|
||||
// http://www.regular-expressions.info/floatingpoint.html
|
||||
thread_local const QRegularExpression rx("[+-]?\\d+(?:\\.\\d+)?");
|
||||
int deg = 0;
|
||||
int min = 0;
|
||||
double sec = 0.0;
|
||||
int c = 0;
|
||||
QRegularExpressionMatchIterator i = rx.globalMatch(wgs);
|
||||
while (i.hasNext() && c < 3)
|
||||
{
|
||||
const QRegularExpressionMatch match = i.next();
|
||||
bool ok;
|
||||
if (match.hasMatch())
|
||||
{
|
||||
const QString cap = match.captured(0);
|
||||
switch (c++)
|
||||
{
|
||||
case 0:
|
||||
deg = cap.toInt(&ok);
|
||||
break;
|
||||
case 1:
|
||||
min = cap.toInt(&ok);
|
||||
break;
|
||||
case 2:
|
||||
sec = cap.toDouble(&ok);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
Q_UNUSED(ok); // ok for debugging purposes
|
||||
}
|
||||
|
||||
if (wgs.contains('S', Qt::CaseInsensitive) || wgs.contains('W', Qt::CaseInsensitive))
|
||||
{
|
||||
deg *= -1;
|
||||
min *= -1;
|
||||
sec *= -1;
|
||||
}
|
||||
|
||||
PhysicalQuantities::CAngle a(deg, min, sec);
|
||||
return LATorLON(a);
|
||||
@@ -151,6 +167,19 @@ namespace BlackMisc
|
||||
return BlackMisc::CIcon::iconByIndex(CIcons::GeoPosition);
|
||||
}
|
||||
|
||||
template<class LATorLON>
|
||||
QString CEarthAngle<LATorLON>::toWgs84(const QChar pos, const QChar neg, int fractionalDigits) const
|
||||
{
|
||||
const CAngle::DegMinSecFractionalSec v = this->asSexagesimalDegMinSec(true);
|
||||
const QChar pn = v.sign < 0 ? neg : pos;
|
||||
|
||||
static const QString vs("%1° %2' %3\" %4");
|
||||
if (fractionalDigits < 1) { return vs.arg(v.deg).arg(v.min).arg(v.sec).arg(pn); }
|
||||
|
||||
static const QString vsf("%1° %2' %3.%4\" %5");
|
||||
return vsf.arg(v.deg).arg(v.min).arg(v.sec).arg(v.fractionalSecAsString(fractionalDigits)).arg(pn);
|
||||
}
|
||||
|
||||
// see here for the reason of thess forward instantiations
|
||||
// https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl
|
||||
//! \cond PRIVATE
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace Geo
|
||||
{
|
||||
|
||||
class CLatitude;
|
||||
class CLongitude;
|
||||
|
||||
@@ -66,6 +65,9 @@ namespace BlackMisc
|
||||
//! Init by CAngle value
|
||||
CEarthAngle(const BlackMisc::PhysicalQuantities::CAngle &angle);
|
||||
|
||||
//! To WGS84 string
|
||||
QString toWgs84(const QChar pos, const QChar neg, int fractionalDigits = 3) const;
|
||||
|
||||
public:
|
||||
//! \copydoc BlackMisc::Mixin::String::toQString
|
||||
QString convertToQString(bool i18n = false) const;
|
||||
@@ -82,8 +84,7 @@ namespace BlackMisc
|
||||
extern template class BLACKMISC_EXPORT_DECLARE_TEMPLATE CEarthAngle<CLatitude>;
|
||||
extern template class BLACKMISC_EXPORT_DECLARE_TEMPLATE CEarthAngle<CLongitude>;
|
||||
//! \endcond
|
||||
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace Geo
|
||||
{
|
||||
|
||||
//! Latitude
|
||||
class BLACKMISC_EXPORT CLatitude :
|
||||
public CEarthAngle<CLatitude>,
|
||||
@@ -36,6 +35,12 @@ namespace BlackMisc
|
||||
BLACKMISC_DECLARE_USING_MIXIN_STRING(CLatitude)
|
||||
BLACKMISC_DECLARE_USING_MIXIN_INDEX(CLatitude)
|
||||
|
||||
//! To WGS84 string
|
||||
QString toWgs84(int fractionalDigits = 3) const
|
||||
{
|
||||
return CEarthAngle<CLatitude>::toWgs84('N', 'S', fractionalDigits);
|
||||
}
|
||||
|
||||
//! \copydoc BlackMisc::Mixin::String::toQString
|
||||
QString convertToQString(bool i18n = false) const
|
||||
{
|
||||
@@ -54,11 +59,11 @@ namespace BlackMisc
|
||||
explicit CLatitude(const BlackMisc::PhysicalQuantities::CAngle &angle) : CEarthAngle(angle) {}
|
||||
|
||||
//! Init by double value
|
||||
//! \remark Latitude measurements range from 0° to (+/–)90°
|
||||
CLatitude(double value, const BlackMisc::PhysicalQuantities::CAngleUnit &unit) : CEarthAngle(value, unit) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
Q_DECLARE_METATYPE(BlackMisc::Geo::CLatitude)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace Geo
|
||||
{
|
||||
|
||||
//! Longitude
|
||||
class BLACKMISC_EXPORT CLongitude :
|
||||
public CEarthAngle<CLongitude>,
|
||||
@@ -36,12 +35,20 @@ namespace BlackMisc
|
||||
BLACKMISC_DECLARE_USING_MIXIN_STRING(CLongitude)
|
||||
BLACKMISC_DECLARE_USING_MIXIN_INDEX(CLongitude)
|
||||
|
||||
//! To WGS84 string
|
||||
QString toWgs84(int withFragmentSecDigits = 3) const
|
||||
{
|
||||
return CEarthAngle<CLongitude>::toWgs84('E', 'W', withFragmentSecDigits);
|
||||
}
|
||||
|
||||
//! \copydoc BlackMisc::Mixin::String::toQString
|
||||
QString convertToQString(bool i18n = false) const
|
||||
{
|
||||
QString s(CEarthAngle::convertToQString(i18n));
|
||||
if (!this->isZeroEpsilonConsidered())
|
||||
{
|
||||
s.append(this->isNegativeWithEpsilonConsidered() ? " W" : " E");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -52,11 +59,11 @@ namespace BlackMisc
|
||||
explicit CLongitude(const BlackMisc::PhysicalQuantities::CAngle &angle) : CEarthAngle(angle) {}
|
||||
|
||||
//! Init by double value
|
||||
//! Longitude measurements range from 0° to (+/–)180°.
|
||||
CLongitude(double value, const BlackMisc::PhysicalQuantities::CAngleUnit &unit) : CEarthAngle(value, unit) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
Q_DECLARE_METATYPE(BlackMisc::Geo::CLongitude)
|
||||
|
||||
|
||||
@@ -53,10 +53,12 @@ namespace BlackMisc
|
||||
|
||||
double CMathUtils::roundEpsilon(double value, double epsilon)
|
||||
{
|
||||
if (epsilon == 0) { return value; } // avoid division by 0
|
||||
double fractpart, intpart;
|
||||
fractpart = modf(value, &intpart);
|
||||
if (fractpart == 0) return value; // do not mess any "integers" to the worse
|
||||
qint64 ri = qRound(value / epsilon);
|
||||
if (fractpart == 0) { return value; } // do not mess any "integers" to the worse
|
||||
const double roundValue = value / epsilon;
|
||||
qint64 ri = qRound64(roundValue);
|
||||
double rv = static_cast<double>(ri) * epsilon; // do not loose any range here
|
||||
return rv;
|
||||
}
|
||||
@@ -113,5 +115,15 @@ namespace BlackMisc
|
||||
return multiplier * divisor;
|
||||
}
|
||||
|
||||
QString CMathUtils::fractionalPartAsString(double value, int width)
|
||||
{
|
||||
double intpart;
|
||||
const double fractpart = modf(value, &intpart);
|
||||
const QString f = QString::number(fractpart);
|
||||
const QString fInt = f.length() < 3 ? QString("0") : f.mid(2);
|
||||
if (width < 0) { return fInt; }
|
||||
if (fInt.length() >= width) { return fInt.left(width); }
|
||||
return fInt.leftJustified(width, '0');
|
||||
}
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace Math
|
||||
{
|
||||
|
||||
//! Math utils
|
||||
class BLACKMISC_EXPORT CMathUtils
|
||||
{
|
||||
@@ -103,10 +102,13 @@ namespace BlackMisc
|
||||
//! Random number between low and high
|
||||
static int randomInteger(int low, int high);
|
||||
|
||||
//! Round numToRound to the nearest multiple of multiple
|
||||
//! Round numToRound to the nearest multiple of divisor
|
||||
static int roundToMultipleOf(int value, int divisor);
|
||||
};
|
||||
|
||||
//! Fractional part as integer string, e.g. 3.12 -> 12 / 3.012 -> 012
|
||||
//! \remark because of leading 0 returned as string
|
||||
static QString fractionalPartAsString(double value, int width = -1);
|
||||
};
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -14,10 +14,60 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace BlackMisc::Math;
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
namespace PhysicalQuantities
|
||||
{
|
||||
CAngle::CAngle(int degrees, int minutes, double seconds) :
|
||||
CPhysicalQuantity(
|
||||
degrees + minutes / 100.0 + seconds / 10000.0,
|
||||
CAngleUnit::sexagesimalDeg())
|
||||
{
|
||||
Q_ASSERT_X((degrees >= 0 && minutes >= 0 && seconds >= 0) ||
|
||||
(degrees <= 0 && minutes <= 0 && seconds <= 0), Q_FUNC_INFO, "Same sign required");
|
||||
}
|
||||
|
||||
CAngle::CAngle(int degrees, double minutes) :
|
||||
CPhysicalQuantity(
|
||||
degrees + minutes / 100.0,
|
||||
CAngleUnit::sexagesimalDeg())
|
||||
{
|
||||
Q_ASSERT_X((degrees >= 0 && minutes >= 0) || (degrees <= 0 && minutes <= 0),
|
||||
Q_FUNC_INFO, "Same sign required");
|
||||
}
|
||||
|
||||
void CAngle::unifySign(int °rees, int &minutes, double &seconds)
|
||||
{
|
||||
if (degrees < 0)
|
||||
{
|
||||
if (minutes > 0) minutes *= -1;
|
||||
if (seconds > 0) seconds *= -1.0;
|
||||
}
|
||||
else if (degrees > 0)
|
||||
{
|
||||
if (minutes < 0) minutes *= -1;
|
||||
if (seconds < 0) seconds *= -1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// degrees was 0
|
||||
if ((minutes > 0 && seconds < 0) || (minutes < 0 && seconds > 0))
|
||||
{
|
||||
seconds *= -1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CAngle::unifySign(int °rees, int &minutes)
|
||||
{
|
||||
if ((degrees > 0 && minutes < 0) || (degrees < 0 && minutes > 0))
|
||||
{
|
||||
minutes *= -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
BlackMisc::CIcon CAngle::toIcon() const
|
||||
{
|
||||
BlackMisc::CIcon i = CIcon::iconByIndex(CIcons::StandardIconArrowMediumNorth16);
|
||||
@@ -25,6 +75,37 @@ namespace BlackMisc
|
||||
return i;
|
||||
}
|
||||
|
||||
CAngle::DegMinSecFractionalSec CAngle::asSexagesimalDegMinSec(bool range180Degrees) const
|
||||
{
|
||||
double v = this->value(CAngleUnit::deg());
|
||||
v = CAngleUnit::deg().roundToEpsilon(v);
|
||||
|
||||
// range -179-180 ?
|
||||
if (range180Degrees)
|
||||
{
|
||||
v = std::fmod(v + 180.0, 360.0);
|
||||
v += (v < 0) ? 180.0 : -180.0;
|
||||
}
|
||||
|
||||
DegMinSecFractionalSec values;
|
||||
if (v < 0)
|
||||
{
|
||||
values.sign = -1;
|
||||
v *= -1.0;
|
||||
}
|
||||
|
||||
values.deg = v;
|
||||
v -= values.deg;
|
||||
v = v * 100.0 * 0.6;
|
||||
values.min = v;
|
||||
v -= values.min;
|
||||
v = v * 100.0 * 0.6;
|
||||
values.sec = v;
|
||||
v -= values.sec;
|
||||
values.fractionalSec = CMathUtils::round(v, 6);
|
||||
return values;
|
||||
}
|
||||
|
||||
double CAngle::piFactor() const
|
||||
{
|
||||
return BlackMisc::Math::CMathUtils::round(this->value(CAngleUnit::rad()) / BlackMisc::Math::CMathUtils::PI() , 6);
|
||||
@@ -49,5 +130,5 @@ namespace BlackMisc
|
||||
{
|
||||
return std::tan(this->value(CAngleUnit::rad()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace PhysicalQuantities
|
||||
{
|
||||
|
||||
//! Physical unit angle (radians, degrees)
|
||||
class BLACKMISC_EXPORT CAngle : public CPhysicalQuantity<CAngleUnit, CAngle>
|
||||
{
|
||||
@@ -39,27 +38,50 @@ namespace BlackMisc
|
||||
//! \copydoc CPhysicalQuantity(const QString &unitString)
|
||||
CAngle(const QString &unitString) : CPhysicalQuantity(unitString) {}
|
||||
|
||||
/*!
|
||||
* \brief Init as sexagesimal degrees, minutes, seconds
|
||||
* The sign of all parameters must be the same, either all positive or all negative.
|
||||
*/
|
||||
CAngle(int degrees, int minutes, double seconds) :
|
||||
CPhysicalQuantity(
|
||||
degrees + minutes / 100.0 + seconds / 10000.0,
|
||||
CAngleUnit::sexagesimalDeg()) {}
|
||||
//! Value as individual values
|
||||
struct DegMinSecFractionalSec
|
||||
{
|
||||
int sign = 1; //!< 1/-1
|
||||
int deg = 0; //!< 0-359
|
||||
int min = 0; //!< 0-59
|
||||
int sec = 0; //!< 0-59
|
||||
double fractionalSec = 0; //!< value < 1.0
|
||||
|
||||
/*!
|
||||
* \brief Init as sexagesimal degrees, minutes
|
||||
* The sign of both parameters must be the same, either both positive or both negative.
|
||||
*/
|
||||
CAngle(int degrees, double minutes) :
|
||||
CPhysicalQuantity(
|
||||
degrees + minutes / 100.0,
|
||||
CAngleUnit::sexagesimalDegMin()) {}
|
||||
//! Degrees as string
|
||||
QString degAsString() const { return QString::number(deg); }
|
||||
|
||||
//! Minutes as string
|
||||
QString minAsString() const { return QString::number(min); }
|
||||
|
||||
//! Seconds as string
|
||||
QString secAsString() const { return QString::number(sec); }
|
||||
|
||||
//! Fractional seconds as string
|
||||
QString fractionalSecAsString(int width = -1) const { return BlackMisc::Math::CMathUtils::fractionalPartAsString(fractionalSec, width); }
|
||||
};
|
||||
|
||||
//! \brief Init as sexagesimal degrees, minutes, seconds
|
||||
//! The sign of all parameters must be the same, either all positive or all negative.
|
||||
//! \see CAngle::unifySign(int &, int &, double &)
|
||||
CAngle(int degrees, int minutes, double seconds);
|
||||
|
||||
//! \brief Init as sexagesimal degrees, minutes
|
||||
//! The sign of both parameters must be the same, either both positive or both negative.
|
||||
//! \see CAngle::unifySign(int &, double &)
|
||||
CAngle(int degrees, double minutes);
|
||||
|
||||
//! Minutes and secods will get same sign as degrees
|
||||
void static unifySign(int °rees, int &minutes, double &seconds);
|
||||
|
||||
//! Minutes will get same sign as degrees
|
||||
void static unifySign(int °rees, int &minutes);
|
||||
|
||||
//! \copydoc BlackMisc::Mixin::Icon::toIcon
|
||||
BlackMisc::CIcon toIcon() const;
|
||||
|
||||
//! As individual values
|
||||
DegMinSecFractionalSec asSexagesimalDegMinSec(bool range180Degrees = false) const;
|
||||
|
||||
//! Value as factor of PI (e.g. 0.5PI)
|
||||
double piFactor() const;
|
||||
|
||||
@@ -75,8 +97,8 @@ namespace BlackMisc
|
||||
//! Tangent of angle
|
||||
double tan() const;
|
||||
};
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
Q_DECLARE_METATYPE(BlackMisc::PhysicalQuantities::CAngle)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace PhysicalQuantities
|
||||
{
|
||||
|
||||
bool CMeasurementUnit::operator ==(const CMeasurementUnit &other) const
|
||||
{
|
||||
if (this == &other) return true;
|
||||
@@ -43,17 +42,23 @@ namespace BlackMisc
|
||||
|
||||
double CMeasurementUnit::roundValue(double value, int digits) const
|
||||
{
|
||||
if (digits < 0) digits = this->m_data->m_displayDigits;
|
||||
if (digits < 0) { digits = this->m_data->m_displayDigits; }
|
||||
return CMathUtils::round(value, digits);
|
||||
}
|
||||
|
||||
QString CMeasurementUnit::makeRoundedQString(double value, int digits, bool /* i18n */) const
|
||||
double CMeasurementUnit::roundToEpsilon(double value) const
|
||||
{
|
||||
if (digits < 0) digits = this->m_data->m_displayDigits;
|
||||
double v = CMathUtils::round(value, digits);
|
||||
QString s = QLocale::system().toString(v, 'f', digits);
|
||||
return s;
|
||||
if (this->getEpsilon() == 0 || this->isNull()) { return value; }
|
||||
return CMathUtils::roundEpsilon(value, this->getEpsilon());
|
||||
}
|
||||
|
||||
QString CMeasurementUnit::makeRoundedQString(double value, int digits, bool i18n) const
|
||||
{
|
||||
Q_UNUSED(i18n);
|
||||
if (digits < 0) digits = this->m_data->m_displayDigits;
|
||||
const double v = CMathUtils::round(value, digits);
|
||||
const QString s = QLocale::system().toString(v, 'f', digits);
|
||||
return s;
|
||||
}
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace BlackMisc
|
||||
{
|
||||
namespace PhysicalQuantities
|
||||
{
|
||||
|
||||
/*!
|
||||
* Base class for all units, such as meter, hertz.
|
||||
*/
|
||||
@@ -219,11 +218,11 @@ namespace BlackMisc
|
||||
: m_name(name), m_symbol(symbol)
|
||||
{}
|
||||
|
||||
QLatin1String m_name; //!< name, e.g. "meter"
|
||||
QLatin1String m_symbol; //!< unit name, e.g. "m"
|
||||
double m_epsilon = 0.0; //!< values with differences below epsilon are the equal
|
||||
int m_displayDigits = 0; //!< standard rounding for string conversions
|
||||
ConverterFunction m_toDefault = nullptr; //!< convert from this unit to default unit
|
||||
QLatin1String m_name; //!< name, e.g. "meter"
|
||||
QLatin1String m_symbol; //!< unit name, e.g. "m"
|
||||
double m_epsilon = 0.0; //!< values with differences below epsilon are the equal
|
||||
int m_displayDigits = 0; //!< standard rounding for string conversions
|
||||
ConverterFunction m_toDefault = nullptr; //!< convert from this unit to default unit
|
||||
ConverterFunction m_fromDefault = nullptr; //!< convert to this unit from default unit
|
||||
};
|
||||
|
||||
@@ -291,16 +290,31 @@ namespace BlackMisc
|
||||
return i18n ? QCoreApplication::translate("CMeasurementUnit", this->m_data->m_symbol.latin1()) : this->m_data->m_symbol;
|
||||
}
|
||||
|
||||
//! Does a string end with name or symbol? E.g. 3meter, 3m, 3deg
|
||||
bool endsStringWithNameOrSymbol(const QString &candidate, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
||||
{
|
||||
const QString c = candidate.trimmed();
|
||||
return c.endsWith(this->getName(false), cs) || c.endsWith(this->getName(true)) ||
|
||||
c.endsWith(this->getSymbol(false), cs) || c.endsWith(this->getSymbol(true));
|
||||
}
|
||||
|
||||
//! Rounded value
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
double roundValue(double value, int digits = -1) const;
|
||||
|
||||
//! Rounded to the nearest CMeasurementUnit::getEpsilon
|
||||
//! \remark uses CMathUtils::roundEpsilon
|
||||
double roundToEpsilon(double value) const;
|
||||
|
||||
//! Rounded string utility method, virtual so units can have specialized formatting
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
virtual QString makeRoundedQString(double value, int digits = -1, bool i18n = false) const;
|
||||
|
||||
//! Value rounded with unit, e.g. "5.00m", "30kHz"
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
virtual QString makeRoundedQStringWithUnit(double value, int digits = -1, bool i18n = false) const;
|
||||
|
||||
//! Threshold for rounding
|
||||
//! Threshold for comparions
|
||||
double getEpsilon() const
|
||||
{
|
||||
return this->m_data->m_epsilon;
|
||||
@@ -371,8 +385,7 @@ namespace BlackMisc
|
||||
return none;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -300,6 +300,13 @@ namespace BlackMisc
|
||||
return this->valueRoundedWithUnit(this->m_unit, digits, i18n);
|
||||
}
|
||||
|
||||
template<class MU, class PQ>
|
||||
void CPhysicalQuantity<MU, PQ>::roundToEpsilon()
|
||||
{
|
||||
if (this->isNull()) { return; }
|
||||
this->m_value = this->m_unit.roundToEpsilon(this->m_value);
|
||||
}
|
||||
|
||||
template <class MU, class PQ>
|
||||
double CPhysicalQuantity<MU, PQ>::valueRounded(MU unit, int digits) const
|
||||
{
|
||||
|
||||
@@ -101,20 +101,28 @@ namespace BlackMisc
|
||||
void setCurrentUnitValue(double value);
|
||||
|
||||
//! Rounded value in given unit
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
double valueRounded(MU unit, int digits = -1) const;
|
||||
|
||||
//! As integer value
|
||||
int valueInteger(MU unit) const;
|
||||
|
||||
//! Rounded value in current unit
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
double valueRounded(int digits = -1) const;
|
||||
|
||||
//! Value to QString with the given unit, e.g. "5.00m"
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
QString valueRoundedWithUnit(MU unit, int digits = -1, bool i18n = false) const;
|
||||
|
||||
//! Value to QString with the current unit, e.g. "5.00m"
|
||||
//! \note default digits is CMeasurementUnit::getDisplayDigits
|
||||
QString valueRoundedWithUnit(int digits = -1, bool i18n = false) const;
|
||||
|
||||
//! Round current value in current unit to epsilon
|
||||
//! \sa CMeasurementUnit::roundToEpsilon
|
||||
void roundToEpsilon();
|
||||
|
||||
//! Change value without changing unit
|
||||
void setValueSameUnit(double value);
|
||||
|
||||
|
||||
@@ -31,10 +31,25 @@ namespace BlackMiscTest
|
||||
{
|
||||
void CTestGeo::geoBasics()
|
||||
{
|
||||
CLatitude lati(10, CAngleUnit::deg());
|
||||
QVERIFY2(lati * 2 == lati + lati, "Latitude addition should be equal");
|
||||
lati += CLatitude(20, CAngleUnit::deg());
|
||||
QVERIFY2(lati.valueRounded() == 30.0, "Latitude should be 30 degrees");
|
||||
CLatitude lat(10, CAngleUnit::deg());
|
||||
QVERIFY2(lat * 2 == lat + lat, "Latitude addition should be equal");
|
||||
lat += CLatitude(20, CAngleUnit::deg());
|
||||
QVERIFY2(lat.valueRounded() == 30.0, "Latitude should be 30 degrees");
|
||||
|
||||
CAngle a(20, 0);
|
||||
lat = CLatitude(a);
|
||||
double v = lat.valueRounded(CAngleUnit::deg());
|
||||
QVERIFY2(v == 20.0, "Values shall be the same");
|
||||
|
||||
a = CAngle(28, 0);
|
||||
lat = CLatitude(a);
|
||||
v = lat.valueRounded(CAngleUnit::deg());
|
||||
QVERIFY2(v == 28.0, "Values shall be the same");
|
||||
|
||||
a = CAngle(30, 0, 0);
|
||||
lat = CLatitude(a);
|
||||
v = lat.valueRounded(CAngleUnit::deg());
|
||||
QVERIFY2(v == 30.0, "Values shall be the same");
|
||||
}
|
||||
|
||||
void CTestGeo::coordinateGeodetic()
|
||||
@@ -44,7 +59,17 @@ namespace BlackMiscTest
|
||||
QCOMPARE(calculateEuclideanDistance(northPole, southPole), 2.0);
|
||||
CCoordinateGeodetic equator = { 0.0, 70.354683 };
|
||||
QCOMPARE(calculateEuclideanDistance(northPole, equator), std::sqrt(2.0f));
|
||||
|
||||
CCoordinateGeodetic testCoordinate = northPole;
|
||||
double latValue = testCoordinate.latitude().value(CAngleUnit::deg());
|
||||
double lngValue = testCoordinate.longitude().value(CAngleUnit::deg());
|
||||
QVERIFY2(latValue == 90.0, "Latitude value supposed to be 90");
|
||||
QVERIFY2(lngValue == 0.0, "Longitude value supposed to be 0");
|
||||
CLatitude newLat(90.0, CAngleUnit::deg());
|
||||
testCoordinate.setLatitude(newLat);
|
||||
latValue = testCoordinate.latitude().value(CAngleUnit::deg());
|
||||
QVERIFY2(latValue == newLat.value(CAngleUnit::deg()), "Latitude value supposed to be equal");
|
||||
}
|
||||
} // namespace
|
||||
} // ns
|
||||
|
||||
//! \endcond
|
||||
|
||||
Reference in New Issue
Block a user