From e55480737ec0950fe0eb57bb2ee5853eb3e91008 Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Wed, 26 Jul 2017 14:38:49 +0200 Subject: [PATCH] Ref T111, lat/lng/angle changes * get struct CAngle::DegMinSecFractionalSec to obtain parts * round to epsilon utility functions and fix (qint64) --- src/blackmisc/geo/coordinategeodetic.cpp | 7 ++ src/blackmisc/geo/coordinategeodetic.h | 6 +- src/blackmisc/geo/earthangle.cpp | 109 ++++++++++++++--------- src/blackmisc/geo/earthangle.h | 9 +- src/blackmisc/geo/latitude.h | 13 ++- src/blackmisc/geo/longitude.h | 15 +++- src/blackmisc/math/mathutils.cpp | 16 +++- src/blackmisc/math/mathutils.h | 8 +- src/blackmisc/pq/angle.cpp | 85 +++++++++++++++++- src/blackmisc/pq/angle.h | 60 +++++++++---- src/blackmisc/pq/measurementunit.cpp | 19 ++-- src/blackmisc/pq/measurementunit.h | 33 ++++--- src/blackmisc/pq/physicalquantity.cpp | 7 ++ src/blackmisc/pq/physicalquantity.h | 8 ++ tests/blackmisc/testgeo.cpp | 35 ++++++-- 15 files changed, 328 insertions(+), 102 deletions(-) diff --git a/src/blackmisc/geo/coordinategeodetic.cpp b/src/blackmisc/geo/coordinategeodetic.cpp index 4ae7566ec..00001a424 100644 --- a/src/blackmisc/geo/coordinategeodetic.cpp +++ b/src/blackmisc/geo/coordinategeodetic.cpp @@ -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() }; diff --git a/src/blackmisc/geo/coordinategeodetic.h b/src/blackmisc/geo/coordinategeodetic.h index 5a9eccd44..b0e2dd951 100644 --- a/src/blackmisc/geo/coordinategeodetic.h +++ b/src/blackmisc/geo/coordinategeodetic.h @@ -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; diff --git a/src/blackmisc/geo/earthangle.cpp b/src/blackmisc/geo/earthangle.cpp index b624eb642..19fb91264 100644 --- a/src/blackmisc/geo/earthangle.cpp +++ b/src/blackmisc/geo/earthangle.cpp @@ -17,11 +17,12 @@ #include #include +using namespace BlackMisc::PhysicalQuantities; + namespace BlackMisc { namespace Geo { - template CEarthAngle &CEarthAngle::operator +=(const CEarthAngle &latOrLon) { @@ -70,49 +71,64 @@ namespace BlackMisc template LATorLON CEarthAngle::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 + QString CEarthAngle::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 diff --git a/src/blackmisc/geo/earthangle.h b/src/blackmisc/geo/earthangle.h index 6fc892d2d..96bf5462d 100644 --- a/src/blackmisc/geo/earthangle.h +++ b/src/blackmisc/geo/earthangle.h @@ -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; extern template class BLACKMISC_EXPORT_DECLARE_TEMPLATE CEarthAngle; //! \endcond - - } -} + } // ns +} // ns #endif // guard diff --git a/src/blackmisc/geo/latitude.h b/src/blackmisc/geo/latitude.h index e12b756a2..cf80728fe 100644 --- a/src/blackmisc/geo/latitude.h +++ b/src/blackmisc/geo/latitude.h @@ -19,7 +19,6 @@ namespace BlackMisc { namespace Geo { - //! Latitude class BLACKMISC_EXPORT CLatitude : public CEarthAngle, @@ -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::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) diff --git a/src/blackmisc/geo/longitude.h b/src/blackmisc/geo/longitude.h index 940d9d354..73bbec403 100644 --- a/src/blackmisc/geo/longitude.h +++ b/src/blackmisc/geo/longitude.h @@ -19,7 +19,6 @@ namespace BlackMisc { namespace Geo { - //! Longitude class BLACKMISC_EXPORT CLongitude : public CEarthAngle, @@ -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::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) diff --git a/src/blackmisc/math/mathutils.cpp b/src/blackmisc/math/mathutils.cpp index c1e01acc6..23387c017 100644 --- a/src/blackmisc/math/mathutils.cpp +++ b/src/blackmisc/math/mathutils.cpp @@ -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(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 diff --git a/src/blackmisc/math/mathutils.h b/src/blackmisc/math/mathutils.h index e57579ebc..16ae90b09 100644 --- a/src/blackmisc/math/mathutils.h +++ b/src/blackmisc/math/mathutils.h @@ -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 diff --git a/src/blackmisc/pq/angle.cpp b/src/blackmisc/pq/angle.cpp index ccd3bbe78..cc4f4d4d2 100644 --- a/src/blackmisc/pq/angle.cpp +++ b/src/blackmisc/pq/angle.cpp @@ -14,10 +14,60 @@ #include +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 diff --git a/src/blackmisc/pq/angle.h b/src/blackmisc/pq/angle.h index 9a9038927..afb7d1b0a 100644 --- a/src/blackmisc/pq/angle.h +++ b/src/blackmisc/pq/angle.h @@ -25,7 +25,6 @@ namespace BlackMisc { namespace PhysicalQuantities { - //! Physical unit angle (radians, degrees) class BLACKMISC_EXPORT CAngle : public CPhysicalQuantity { @@ -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) diff --git a/src/blackmisc/pq/measurementunit.cpp b/src/blackmisc/pq/measurementunit.cpp index 6925a350c..cc6102417 100644 --- a/src/blackmisc/pq/measurementunit.cpp +++ b/src/blackmisc/pq/measurementunit.cpp @@ -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 diff --git a/src/blackmisc/pq/measurementunit.h b/src/blackmisc/pq/measurementunit.h index 73b8ed593..f2ed164af 100644 --- a/src/blackmisc/pq/measurementunit.h +++ b/src/blackmisc/pq/measurementunit.h @@ -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 diff --git a/src/blackmisc/pq/physicalquantity.cpp b/src/blackmisc/pq/physicalquantity.cpp index aebeb2adb..30bffddba 100644 --- a/src/blackmisc/pq/physicalquantity.cpp +++ b/src/blackmisc/pq/physicalquantity.cpp @@ -300,6 +300,13 @@ namespace BlackMisc return this->valueRoundedWithUnit(this->m_unit, digits, i18n); } + template + void CPhysicalQuantity::roundToEpsilon() + { + if (this->isNull()) { return; } + this->m_value = this->m_unit.roundToEpsilon(this->m_value); + } + template double CPhysicalQuantity::valueRounded(MU unit, int digits) const { diff --git a/src/blackmisc/pq/physicalquantity.h b/src/blackmisc/pq/physicalquantity.h index d2f7e1518..feb7b40d8 100644 --- a/src/blackmisc/pq/physicalquantity.h +++ b/src/blackmisc/pq/physicalquantity.h @@ -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); diff --git a/tests/blackmisc/testgeo.cpp b/tests/blackmisc/testgeo.cpp index 1dffc7e78..3a98d2f46 100644 --- a/tests/blackmisc/testgeo.cpp +++ b/tests/blackmisc/testgeo.cpp @@ -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