From 8bc330ea750eac15f6b48fd5ccc39fdcac2a9557 Mon Sep 17 00:00:00 2001 From: Vahan Aghajanyan Date: Tue, 6 Feb 2024 15:04:07 +0100 Subject: [PATCH] Implement the coordinates conversion to string. --- include/coordinate.h | 10 +++ src/coordinate.cpp | 56 +++++++++++++++ test/test.cpp | 162 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+) diff --git a/include/coordinate.h b/include/coordinate.h index a454ed3..e2bea5c 100644 --- a/include/coordinate.h +++ b/include/coordinate.h @@ -47,6 +47,9 @@ class ERKIR_EXPORT Coordinate //! Constructs a coordinate by the given decimal degrees. Coordinate(double degrees); + /// Returns string representation of the coordinate in the specified \p format. + virtual std::string toString(Format format, int precision) const = 0; + //! Returns this coordinate's value in decimal degrees. double degrees() const; @@ -64,6 +67,9 @@ class ERKIR_EXPORT Coordinate /// Constrain degrees to range 0..360.0 (e.g. for bearings); -1 => 359, 361 => 1. static double wrap360(double degrees); +protected: + std::string toBaseString(Format format, int precision) const; + private: double m_degrees; }; @@ -80,6 +86,8 @@ class ERKIR_EXPORT Latitude : public Coordinate */ Latitude(double degree); + std::string toString(Format format, int precision = 2) const override; + /// Returns the latitude from the human readable coordinates (formatted). /*! Latitude/Longitude coordinates in three formats: Degrees Minutes Seconds (D° M' S"), @@ -114,6 +122,8 @@ class ERKIR_EXPORT Longitude : public Coordinate */ Longitude(double degree); + std::string toString(Format format, int precision = 2) const override; + /// Returns the longitude from the human readable coordinates (formatted). /*! Latitude/Longitude coordinates in three formats: Degrees Minutes Seconds (D° M' S"), diff --git a/src/coordinate.cpp b/src/coordinate.cpp index 21a9cc4..4415e2e 100644 --- a/src/coordinate.cpp +++ b/src/coordinate.cpp @@ -28,9 +28,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -210,6 +212,50 @@ double Coordinate::wrap360(double degrees) return fmod(degrees + 360.0, 360.0); } +std::string Coordinate::toBaseString(Format format, int precision) const +{ + static const std::string degreeSign{ "°"}; + static const std::string minSign { "'" }; + static const std::string secSign { "\"" }; + + double degrees{}; + const auto min = std::abs(std::modf(m_degrees, °rees)) * 60.0; + + std::ostringstream ss; + ss.fill('0'); + + switch (format) { + case Format::DMS: + { + const auto sec = std::abs((min - int(min))) * 60.0; + + ss << std::setw(2) + << std::abs(int(degrees)) << degreeSign << ' ' + << std::setw(2) + << int(min) << minSign << ' ' + << std::setw(2) + << std::setprecision(precision + 2) + << sec << secSign; + break; + } + case Format::DDM: + ss << std::setw(2) + << std::abs(int(degrees)) << degreeSign << ' ' + << std::setw(2) + << std::setprecision(precision + 2) + << min << minSign; + break; + case Format::DD: + ss << std::setw(2) + << m_degrees << degreeSign; + break; + default: + break; + } + + return ss.str(); +} + //////////////////////////////////////////////////////////////////////////////// Latitude::Latitude(double degree) @@ -239,6 +285,11 @@ Latitude Latitude::fromString(const std::string &coord) return { parseRx(coord, rxList, 'S')}; } +std::string Latitude::toString(Coordinate::Format format, int precision) const +{ + return toBaseString(format, precision) + ' ' + (degrees() < 0 ? 'S' : 'N'); +} + //////////////////////////////////////////////////////////////////////////////// Longitude::Longitude(double degree) @@ -268,5 +319,10 @@ Longitude Longitude::fromString(const std::string &coord) return { parseRx(coord, rxList, 'W')}; } +std::string Longitude::toString(Coordinate::Format format, int precision) const +{ + return toBaseString(format, precision) + ' ' + (degrees() < 0 ? 'W' : 'E'); +} + } // namespace erkir diff --git a/test/test.cpp b/test/test.cpp index 5cb6c2b..ecdd718 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -60,6 +60,18 @@ static bool verifyDouble(double value, double expected, const std::string &locat return true; } +static bool verifyString(const std::string &expected, const std::string &actual) +{ + if (expected != actual) { + fprintf(stderr, "%s VALUE ERROR: expected '%s' but got '%s'\n", LOCATION.c_str(), + expected.c_str(), actual.c_str()); + ++s_failed; + return false; + } + ++s_passed; + return true; +} + static bool verifyPoint(const Point &point, const Point &expectedPoint, const std::string &location) { @@ -329,6 +341,156 @@ int main() } } + // Coordinates to string DMS + { + static const std::vector> lonVariants = + { + { "45.76260" , "45° 45' 45.36\" E"}, + { "-45.76260" , "45° 45' 45.36\" W" }, + { " 45.76260 " , "45° 45' 45.36\" E"}, + { "45.76260°" , "45° 45' 45.36\" E" }, + { "45.76260° W " , "45° 45' 45.36\" W" }, + { "45 E" , "45° 00' 00\" E" }, + { ".76260" , "00° 45' 45.36\" E" }, + + { "45° 46.1' " , "45° 46' 06\" E" }, + { " 45° 46.1' E " , "45° 46' 06\" E" }, + { "45°46.756′E" , "45° 46' 45.36\" E" }, + { "45° 46.756′ E" , "45° 46' 45.36\" E" }, + { "45 46.756 W" , "45° 46' 45.36\" W" }, + { "45 46 W" , "45° 45' 60\" W" }, + { "45 .756 W" , "45° 00' 45.36\" W" }, + { "45 59.9999 W" , "45° 59' 59.99\" W" }, + + { "45°46′45.36″ " , "45° 46' 45.36\" E" }, + { "45°46′45.36″ E" , "45° 46' 45.36\" E" }, + { "45º46'47.36\" e" , "45° 46' 47.36\" E" }, + { "45º 46' 47.36\" e", "45° 46' 47.36\" E" }, + { "45°46’47.36” E" , "45° 46' 47.36\" E" }, + { "45 46 47.36 W" , "45° 46' 47.36\" W" }, + { "45° 46′ 47.36″ e" , "45° 46' 47.36\" E" }, + { "45° 46’ 47.36” w" , "45° 46' 47.36\" W" }, + { "45° 46’ 47” W" , "45° 46' 47\" W" }, + { "45° 46’ .36” w" , "45° 46' 0.36\" W" }, + }; + + static const std::vector> latVariants = + { + { "45.76260" , "45° 45' 45.36\" N" }, + { "-45.76260" , "45° 45' 45.36\" S" }, + { " 45.76260 " , "45° 45' 45.36\" N"}, + { "45.76260°" , "45° 45' 45.36\" N" }, + { "45.76260° N " , "45° 45' 45.36\" N" }, + { "45" , "45° 00' 00\" N"}, + { ".76260 S" , "00° 45' 45.36\" S" }, + + { "45° 46.1' " , "45° 46' 06\" N" }, + { " 45° 46.1' N " , "45° 46' 06\" N" }, + { "45°46.756′N" , "45° 46' 45.36\" N" }, + { "45° 46.756′ N" , "45° 46' 45.36\" N" }, + { "45 46.756 S" , "45° 46' 45.36\" S" }, + { "45 46 S" , "45° 45' 60\" S" }, + { "45 .756 S" , "45° 00' 45.36\" S" }, + { "45 59.9999 S" , "45° 59' 59.99\" S" }, + + { "45°46′45.36″ " , "45° 46' 45.36\" N" }, + { "45°46′45.36″ N" , "45° 46' 45.36\" N" }, + { "45º46'47.36\" S" , "45° 46' 47.36\" S" }, + { "45º 46' 47.36\" n", "45° 46' 47.36\" N" }, + { "45°46’47.36” N" , "45° 46' 47.36\" N" }, + { "45 46 47.36 S" , "45° 46' 47.36\" S" }, + { "45° 46′ 47.36″ n" , "45° 46' 47.36\" N" }, + { "45° 46’ 47.36” s" , "45° 46' 47.36\" S" }, + { "45° 46’ 47” S" , "45° 46' 47\" S" }, + { "45° 46’ .36” s" , "45° 46' 0.36\" S" }, + }; + + for (auto && v : lonVariants) { + const auto lon = Longitude::fromString(v.first); + verifyString(v.second, lon.toString(Coordinate::Format::DMS)); + } + + for (auto && v : latVariants) { + const auto lat = Latitude::fromString(v.first); + verifyString(v.second, lat.toString(Coordinate::Format::DMS)); + } + } + + // Coordinates to string DDM + { + static const std::vector> lonVariants = + { + { "45.76260" , "45° 45.756' E"}, + { "-45.76260" , "45° 45.756' W" }, + { " 45.76260 " , "45° 45.756' E"}, + { "45.76260°" , "45° 45.756' E" }, + { "45.76260° W " , "45° 45.756' W" }, + { "45 E" , "45° 00' E" }, + { ".76260" , "00° 45.756' E" }, + + { "45° 46.1' " , "45° 46.1' E" }, + { " 45° 46.1' E " , "45° 46.1' E" }, + { "45°46.756′E" , "45° 46.756' E" }, + { "45° 46.756′ E" , "45° 46.756' E" }, + { "45 46.756 W" , "45° 46.756' W" }, + { "45 46 W" , "45° 46' W" }, + { "45 .756 W" , "45° 0.756' W" }, + { "45 59.9999 W" , "45° 60' W" }, + + { "45°46′45.36″ " , "45° 46.756' E" }, + { "45°46′45.36″ E" , "45° 46.756' E" }, + { "45º46'47.36\" e" , "45° 46.789' E" }, + { "45º 46' 47.36\" e", "45° 46.789' E" }, + { "45°46’47.36” E" , "45° 46.789' E" }, + { "45 46 47.36 W" , "45° 46.789' W" }, + { "45° 46′ 47.36″ e" , "45° 46.789' E" }, + { "45° 46’ 47.36” w" , "45° 46.789' W" }, + { "45° 46’ 47” W" , "45° 46.783' W" }, + { "45° 46’ .36” w" , "45° 46.006' W" }, + }; + + static const std::vector> latVariants = + { + { "45.76260" , "45° 45.756' N" }, + { "-45.76260" , "45° 45.756' S" }, + { " 45.76260 " , "45° 45.756' N"}, + { "45.76260°" , "45° 45.756' N" }, + { "45.76260° N " , "45° 45.756' N" }, + { "45" , "45° 00' N" }, + { ".76260 S" , "00° 45.756' S" }, + + { "45° 46.1' " , "45° 46.1' N" }, + { " 45° 46.1' N " , "45° 46.1' N" }, + { "45°46.756′N" , "45° 46.756' N" }, + { "45° 46.756′ N" , "45° 46.756' N" }, + { "45 46.756 S" , "45° 46.756' S" }, + { "45 46 S" , "45° 46' S" }, + { "45 .756 S" , "45° 0.756' S" }, + { "45 59.9999 S" , "45° 60' S" }, + + { "45°46′45.36″ " , "45° 46.756' N" }, + { "45°46′45.36″ N" , "45° 46.756' N" }, + { "45º46'47.36\" S" , "45° 46.789' S" }, + { "45º 46' 47.36\" n", "45° 46.789' N" }, + { "45°46’47.36” N" , "45° 46.789' N" }, + { "45 46 47.36 S" , "45° 46.789' S" }, + { "45° 46′ 47.36″ n" , "45° 46.789' N" }, + { "45° 46’ 47.36” s" , "45° 46.789' S" }, + { "45° 46’ 47” S" , "45° 46.783' S" }, + { "45° 46’ .36” s" , "45° 46.006' S" }, + }; + + for (auto &&v : lonVariants) { + const auto lon = Longitude::fromString(v.first); + verifyString(v.second, lon.toString(Coordinate::Format::DDM, 3)); + } + + for (auto &&v : latVariants) { + const auto lat = Latitude::fromString(v.first); + verifyString(v.second, lat.toString(Coordinate::Format::DDM, 3)); + } + } + ////////////////////////////////////////////////////////////////////////////// // Ellipsoidal points