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:
Klaus Basan
2017-07-26 14:38:49 +02:00
committed by Mathew Sutcliffe
parent 77546a46b1
commit e55480737e
15 changed files with 328 additions and 102 deletions

View File

@@ -212,6 +212,13 @@ namespace BlackMisc
CCoordinateGeodetic::CCoordinateGeodetic(double latitudeDegrees, double longitudeDegrees, double heightFeet) : 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( { 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 CLatitude CCoordinateGeodetic::latitude() const
{ {
return { std::atan2(m_z, std::hypot(m_x, m_y)), PhysicalQuantities::CAngleUnit::rad() }; return { std::atan2(m_z, std::hypot(m_x, m_y)), PhysicalQuantities::CAngleUnit::rad() };

View File

@@ -187,8 +187,7 @@ namespace BlackMisc
{ {
public: public:
//! Default constructor //! Default constructor
CCoordinateGeodetic() : CCoordinateGeodetic() : CCoordinateGeodetic(0, 0, 0) {}
CCoordinateGeodetic(0, 0, 0) {}
//! Constructor by normal vector //! Constructor by normal vector
CCoordinateGeodetic(const QVector3D &normal) : m_x(normal.x()), m_y(normal.y()), m_z(normal.z()) {} 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 //! Constructor by values
CCoordinateGeodetic(double latitudeDegrees, double longitudeDegrees, double heightFeet); CCoordinateGeodetic(double latitudeDegrees, double longitudeDegrees, double heightFeet);
//! Constructor by interface
CCoordinateGeodetic(const ICoordinateGeodetic &coordinate);
//! \copydoc ICoordinateGeodetic::latitude //! \copydoc ICoordinateGeodetic::latitude
virtual CLatitude latitude() const override; virtual CLatitude latitude() const override;

View File

@@ -17,11 +17,12 @@
#include <QtGlobal> #include <QtGlobal>
#include <QtMath> #include <QtMath>
using namespace BlackMisc::PhysicalQuantities;
namespace BlackMisc namespace BlackMisc
{ {
namespace Geo namespace Geo
{ {
template <class LATorLON> template <class LATorLON>
CEarthAngle<LATorLON> &CEarthAngle<LATorLON>::operator +=(const CEarthAngle &latOrLon) CEarthAngle<LATorLON> &CEarthAngle<LATorLON>::operator +=(const CEarthAngle &latOrLon)
{ {
@@ -70,49 +71,64 @@ namespace BlackMisc
template <class LATorLON> template <class LATorLON>
LATorLON CEarthAngle<LATorLON>::fromWgs84(const QString &wgsCoordinate) LATorLON CEarthAngle<LATorLON>::fromWgs84(const QString &wgsCoordinate)
{ {
// http://www.regular-expressions.info/floatingpoint.html
const QString wgs = wgsCoordinate.simplified().trimmed(); const QString wgs = wgsCoordinate.simplified().trimmed();
thread_local const QRegularExpression rx("([-+]?[0-9]*\\.?[0-9]+)"); if (wgs.isEmpty()) { return LATorLON(); }
qint32 deg = 0;
qint32 min = 0; // support for 5deg, 1.2rad
double sec = 0.0; if (CAngleUnit::deg().endsStringWithNameOrSymbol(wgs) || CAngleUnit::rad().endsStringWithNameOrSymbol(wgs))
double secFragment = 0.0;
int fragmentLength = 0;
int c = 0;
int pos = 0;
QRegularExpressionMatch match = rx.match(wgs, pos);
while (match.hasMatch())
{ {
QString cap = match.captured(1); LATorLON latOrLon;
pos += match.capturedLength(1); latOrLon.parseFromString(wgs);
return latOrLon;
}
// 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++) switch (c++)
{ {
case 0: case 0:
deg = cap.toInt(); deg = cap.toInt(&ok);
break; break;
case 1: case 1:
min = cap.toInt(); min = cap.toInt(&ok);
break; break;
case 2: case 2:
sec = cap.toDouble(); sec = cap.toDouble(&ok);
break;
case 3:
secFragment = cap.toDouble();
fragmentLength = cap.length();
break; break;
default: default:
break; break;
} }
match = rx.match(wgs, pos);
} }
if (fragmentLength > 0) Q_UNUSED(ok); // ok for debugging purposes
{
// we do have given ms in string
sec += secFragment / qPow(10, fragmentLength);
} }
if (wgs.contains('S', Qt::CaseInsensitive) || if (wgs.contains('S', Qt::CaseInsensitive) || wgs.contains('W', Qt::CaseInsensitive))
wgs.contains('W', Qt::CaseInsensitive)) deg *= -1; {
deg *= -1;
min *= -1;
sec *= -1;
}
PhysicalQuantities::CAngle a(deg, min, sec); PhysicalQuantities::CAngle a(deg, min, sec);
return LATorLON(a); return LATorLON(a);
@@ -151,6 +167,19 @@ namespace BlackMisc
return BlackMisc::CIcon::iconByIndex(CIcons::GeoPosition); 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 // see here for the reason of thess forward instantiations
// https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl // https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl
//! \cond PRIVATE //! \cond PRIVATE

View File

@@ -18,7 +18,6 @@ namespace BlackMisc
{ {
namespace Geo namespace Geo
{ {
class CLatitude; class CLatitude;
class CLongitude; class CLongitude;
@@ -66,6 +65,9 @@ namespace BlackMisc
//! Init by CAngle value //! Init by CAngle value
CEarthAngle(const BlackMisc::PhysicalQuantities::CAngle &angle); CEarthAngle(const BlackMisc::PhysicalQuantities::CAngle &angle);
//! To WGS84 string
QString toWgs84(const QChar pos, const QChar neg, int fractionalDigits = 3) const;
public: public:
//! \copydoc BlackMisc::Mixin::String::toQString //! \copydoc BlackMisc::Mixin::String::toQString
QString convertToQString(bool i18n = false) const; 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<CLatitude>;
extern template class BLACKMISC_EXPORT_DECLARE_TEMPLATE CEarthAngle<CLongitude>; extern template class BLACKMISC_EXPORT_DECLARE_TEMPLATE CEarthAngle<CLongitude>;
//! \endcond //! \endcond
} // ns
} } // ns
}
#endif // guard #endif // guard

View File

@@ -19,7 +19,6 @@ namespace BlackMisc
{ {
namespace Geo namespace Geo
{ {
//! Latitude //! Latitude
class BLACKMISC_EXPORT CLatitude : class BLACKMISC_EXPORT CLatitude :
public CEarthAngle<CLatitude>, public CEarthAngle<CLatitude>,
@@ -36,6 +35,12 @@ namespace BlackMisc
BLACKMISC_DECLARE_USING_MIXIN_STRING(CLatitude) BLACKMISC_DECLARE_USING_MIXIN_STRING(CLatitude)
BLACKMISC_DECLARE_USING_MIXIN_INDEX(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 //! \copydoc BlackMisc::Mixin::String::toQString
QString convertToQString(bool i18n = false) const QString convertToQString(bool i18n = false) const
{ {
@@ -54,11 +59,11 @@ namespace BlackMisc
explicit CLatitude(const BlackMisc::PhysicalQuantities::CAngle &angle) : CEarthAngle(angle) {} explicit CLatitude(const BlackMisc::PhysicalQuantities::CAngle &angle) : CEarthAngle(angle) {}
//! Init by double value //! Init by double value
//! \remark Latitude measurements range from 0° to (+/)90°
CLatitude(double value, const BlackMisc::PhysicalQuantities::CAngleUnit &unit) : CEarthAngle(value, unit) {} CLatitude(double value, const BlackMisc::PhysicalQuantities::CAngleUnit &unit) : CEarthAngle(value, unit) {}
}; };
} // ns
} } // ns
}
Q_DECLARE_METATYPE(BlackMisc::Geo::CLatitude) Q_DECLARE_METATYPE(BlackMisc::Geo::CLatitude)

View File

@@ -19,7 +19,6 @@ namespace BlackMisc
{ {
namespace Geo namespace Geo
{ {
//! Longitude //! Longitude
class BLACKMISC_EXPORT CLongitude : class BLACKMISC_EXPORT CLongitude :
public CEarthAngle<CLongitude>, public CEarthAngle<CLongitude>,
@@ -36,12 +35,20 @@ namespace BlackMisc
BLACKMISC_DECLARE_USING_MIXIN_STRING(CLongitude) BLACKMISC_DECLARE_USING_MIXIN_STRING(CLongitude)
BLACKMISC_DECLARE_USING_MIXIN_INDEX(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 //! \copydoc BlackMisc::Mixin::String::toQString
QString convertToQString(bool i18n = false) const QString convertToQString(bool i18n = false) const
{ {
QString s(CEarthAngle::convertToQString(i18n)); QString s(CEarthAngle::convertToQString(i18n));
if (!this->isZeroEpsilonConsidered()) if (!this->isZeroEpsilonConsidered())
{
s.append(this->isNegativeWithEpsilonConsidered() ? " W" : " E"); s.append(this->isNegativeWithEpsilonConsidered() ? " W" : " E");
}
return s; return s;
} }
@@ -52,11 +59,11 @@ namespace BlackMisc
explicit CLongitude(const BlackMisc::PhysicalQuantities::CAngle &angle) : CEarthAngle(angle) {} explicit CLongitude(const BlackMisc::PhysicalQuantities::CAngle &angle) : CEarthAngle(angle) {}
//! Init by double value //! Init by double value
//! Longitude measurements range from 0° to (+/)180°.
CLongitude(double value, const BlackMisc::PhysicalQuantities::CAngleUnit &unit) : CEarthAngle(value, unit) {} CLongitude(double value, const BlackMisc::PhysicalQuantities::CAngleUnit &unit) : CEarthAngle(value, unit) {}
}; };
} // ns
} } // ns
}
Q_DECLARE_METATYPE(BlackMisc::Geo::CLongitude) Q_DECLARE_METATYPE(BlackMisc::Geo::CLongitude)

View File

@@ -53,10 +53,12 @@ namespace BlackMisc
double CMathUtils::roundEpsilon(double value, double epsilon) double CMathUtils::roundEpsilon(double value, double epsilon)
{ {
if (epsilon == 0) { return value; } // avoid division by 0
double fractpart, intpart; double fractpart, intpart;
fractpart = modf(value, &intpart); fractpart = modf(value, &intpart);
if (fractpart == 0) return value; // do not mess any "integers" to the worse if (fractpart == 0) { return value; } // do not mess any "integers" to the worse
qint64 ri = qRound(value / epsilon); const double roundValue = value / epsilon;
qint64 ri = qRound64(roundValue);
double rv = static_cast<double>(ri) * epsilon; // do not loose any range here double rv = static_cast<double>(ri) * epsilon; // do not loose any range here
return rv; return rv;
} }
@@ -113,5 +115,15 @@ namespace BlackMisc
return multiplier * divisor; 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
} // namespace } // namespace

View File

@@ -21,7 +21,6 @@ namespace BlackMisc
{ {
namespace Math namespace Math
{ {
//! Math utils //! Math utils
class BLACKMISC_EXPORT CMathUtils class BLACKMISC_EXPORT CMathUtils
{ {
@@ -103,10 +102,13 @@ namespace BlackMisc
//! Random number between low and high //! Random number between low and high
static int randomInteger(int low, int 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); 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
} // namespace } // namespace

View File

@@ -14,10 +14,60 @@
#include <cmath> #include <cmath>
using namespace BlackMisc::Math;
namespace BlackMisc namespace BlackMisc
{ {
namespace PhysicalQuantities 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 &degrees, 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 &degrees, int &minutes)
{
if ((degrees > 0 && minutes < 0) || (degrees < 0 && minutes > 0))
{
minutes *= -1.0;
}
}
BlackMisc::CIcon CAngle::toIcon() const BlackMisc::CIcon CAngle::toIcon() const
{ {
BlackMisc::CIcon i = CIcon::iconByIndex(CIcons::StandardIconArrowMediumNorth16); BlackMisc::CIcon i = CIcon::iconByIndex(CIcons::StandardIconArrowMediumNorth16);
@@ -25,6 +75,37 @@ namespace BlackMisc
return i; 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 double CAngle::piFactor() const
{ {
return BlackMisc::Math::CMathUtils::round(this->value(CAngleUnit::rad()) / BlackMisc::Math::CMathUtils::PI() , 6); 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())); return std::tan(this->value(CAngleUnit::rad()));
} }
} } // ns
} } // ns

View File

@@ -25,7 +25,6 @@ namespace BlackMisc
{ {
namespace PhysicalQuantities namespace PhysicalQuantities
{ {
//! Physical unit angle (radians, degrees) //! Physical unit angle (radians, degrees)
class BLACKMISC_EXPORT CAngle : public CPhysicalQuantity<CAngleUnit, CAngle> class BLACKMISC_EXPORT CAngle : public CPhysicalQuantity<CAngleUnit, CAngle>
{ {
@@ -39,27 +38,50 @@ namespace BlackMisc
//! \copydoc CPhysicalQuantity(const QString &unitString) //! \copydoc CPhysicalQuantity(const QString &unitString)
CAngle(const QString &unitString) : CPhysicalQuantity(unitString) {} CAngle(const QString &unitString) : CPhysicalQuantity(unitString) {}
/*! //! Value as individual values
* \brief Init as sexagesimal degrees, minutes, seconds struct DegMinSecFractionalSec
* The sign of all parameters must be the same, either all positive or all negative. {
*/ int sign = 1; //!< 1/-1
CAngle(int degrees, int minutes, double seconds) : int deg = 0; //!< 0-359
CPhysicalQuantity( int min = 0; //!< 0-59
degrees + minutes / 100.0 + seconds / 10000.0, int sec = 0; //!< 0-59
CAngleUnit::sexagesimalDeg()) {} double fractionalSec = 0; //!< value < 1.0
/*! //! Degrees as string
* \brief Init as sexagesimal degrees, minutes QString degAsString() const { return QString::number(deg); }
* The sign of both parameters must be the same, either both positive or both negative.
*/ //! Minutes as string
CAngle(int degrees, double minutes) : QString minAsString() const { return QString::number(min); }
CPhysicalQuantity(
degrees + minutes / 100.0, //! Seconds as string
CAngleUnit::sexagesimalDegMin()) {} 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 &degrees, int &minutes, double &seconds);
//! Minutes will get same sign as degrees
void static unifySign(int &degrees, int &minutes);
//! \copydoc BlackMisc::Mixin::Icon::toIcon //! \copydoc BlackMisc::Mixin::Icon::toIcon
BlackMisc::CIcon toIcon() const; BlackMisc::CIcon toIcon() const;
//! As individual values
DegMinSecFractionalSec asSexagesimalDegMinSec(bool range180Degrees = false) const;
//! Value as factor of PI (e.g. 0.5PI) //! Value as factor of PI (e.g. 0.5PI)
double piFactor() const; double piFactor() const;
@@ -75,8 +97,8 @@ namespace BlackMisc
//! Tangent of angle //! Tangent of angle
double tan() const; double tan() const;
}; };
} } // ns
} } // ns
Q_DECLARE_METATYPE(BlackMisc::PhysicalQuantities::CAngle) Q_DECLARE_METATYPE(BlackMisc::PhysicalQuantities::CAngle)

View File

@@ -17,7 +17,6 @@ namespace BlackMisc
{ {
namespace PhysicalQuantities namespace PhysicalQuantities
{ {
bool CMeasurementUnit::operator ==(const CMeasurementUnit &other) const bool CMeasurementUnit::operator ==(const CMeasurementUnit &other) const
{ {
if (this == &other) return true; if (this == &other) return true;
@@ -43,17 +42,23 @@ namespace BlackMisc
double CMeasurementUnit::roundValue(double value, int digits) const 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); 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; if (this->getEpsilon() == 0 || this->isNull()) { return value; }
double v = CMathUtils::round(value, digits); return CMathUtils::roundEpsilon(value, this->getEpsilon());
QString s = QLocale::system().toString(v, 'f', digits);
return s;
} }
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
} // namespace } // namespace

View File

@@ -36,7 +36,6 @@ namespace BlackMisc
{ {
namespace PhysicalQuantities namespace PhysicalQuantities
{ {
/*! /*!
* Base class for all units, such as meter, hertz. * Base class for all units, such as meter, hertz.
*/ */
@@ -291,16 +290,31 @@ namespace BlackMisc
return i18n ? QCoreApplication::translate("CMeasurementUnit", this->m_data->m_symbol.latin1()) : this->m_data->m_symbol; 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 //! Rounded value
//! \note default digits is CMeasurementUnit::getDisplayDigits
double roundValue(double value, int digits = -1) const; 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 //! 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; virtual QString makeRoundedQString(double value, int digits = -1, bool i18n = false) const;
//! Value rounded with unit, e.g. "5.00m", "30kHz" //! 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; virtual QString makeRoundedQStringWithUnit(double value, int digits = -1, bool i18n = false) const;
//! Threshold for rounding //! Threshold for comparions
double getEpsilon() const double getEpsilon() const
{ {
return this->m_data->m_epsilon; return this->m_data->m_epsilon;
@@ -371,8 +385,7 @@ namespace BlackMisc
return none; return none;
} }
}; };
} // ns
} } // ns
}
#endif // guard #endif // guard

View File

@@ -300,6 +300,13 @@ namespace BlackMisc
return this->valueRoundedWithUnit(this->m_unit, digits, i18n); 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> template <class MU, class PQ>
double CPhysicalQuantity<MU, PQ>::valueRounded(MU unit, int digits) const double CPhysicalQuantity<MU, PQ>::valueRounded(MU unit, int digits) const
{ {

View File

@@ -101,20 +101,28 @@ namespace BlackMisc
void setCurrentUnitValue(double value); void setCurrentUnitValue(double value);
//! Rounded value in given unit //! Rounded value in given unit
//! \note default digits is CMeasurementUnit::getDisplayDigits
double valueRounded(MU unit, int digits = -1) const; double valueRounded(MU unit, int digits = -1) const;
//! As integer value //! As integer value
int valueInteger(MU unit) const; int valueInteger(MU unit) const;
//! Rounded value in current unit //! Rounded value in current unit
//! \note default digits is CMeasurementUnit::getDisplayDigits
double valueRounded(int digits = -1) const; double valueRounded(int digits = -1) const;
//! Value to QString with the given unit, e.g. "5.00m" //! 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; QString valueRoundedWithUnit(MU unit, int digits = -1, bool i18n = false) const;
//! Value to QString with the current unit, e.g. "5.00m" //! 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; 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 //! Change value without changing unit
void setValueSameUnit(double value); void setValueSameUnit(double value);

View File

@@ -31,10 +31,25 @@ namespace BlackMiscTest
{ {
void CTestGeo::geoBasics() void CTestGeo::geoBasics()
{ {
CLatitude lati(10, CAngleUnit::deg()); CLatitude lat(10, CAngleUnit::deg());
QVERIFY2(lati * 2 == lati + lati, "Latitude addition should be equal"); QVERIFY2(lat * 2 == lat + lat, "Latitude addition should be equal");
lati += CLatitude(20, CAngleUnit::deg()); lat += CLatitude(20, CAngleUnit::deg());
QVERIFY2(lati.valueRounded() == 30.0, "Latitude should be 30 degrees"); 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() void CTestGeo::coordinateGeodetic()
@@ -44,7 +59,17 @@ namespace BlackMiscTest
QCOMPARE(calculateEuclideanDistance(northPole, southPole), 2.0); QCOMPARE(calculateEuclideanDistance(northPole, southPole), 2.0);
CCoordinateGeodetic equator = { 0.0, 70.354683 }; CCoordinateGeodetic equator = { 0.0, 70.354683 };
QCOMPARE(calculateEuclideanDistance(northPole, equator), std::sqrt(2.0f)); 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 //! \endcond