#pragma once #include #include #include #include #include #include #include #include "mcc_traits.h" namespace mcc::utils { static const std::regex decimalNumberRx{" *[-+]?([0-9]*[.])?[0-9]+([eE][-+]?[0-9]+)? *"}; static const std::regex sexagesimalReprRx{" *[-+]?[0-9]{1,2}:[0-9]{1,2}:([0-9]{0,2}[.])?[0-9]+ *"}; static bool isEqual(std::floating_point auto const& v1, std::floating_point auto const& v2) { constexpr auto eps = std::numeric_limits>::epsilon(); return std::fabs(v1 - v2) <= eps * std::fmax(std::fabs(v1), std::fabs(v2)); } enum class TrimType { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH }; template static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH) requires std::same_as>> { auto is_space = [](const auto& ch) { return ch == ' '; }; auto end = std::forward(r).end(); auto f1 = std::forward(r).begin(); if (type != TrimType::TRIM_RIGHT) { // look for the first non-space symbol f1 = std::ranges::find_if_not(std::forward(r), is_space); if (f1 == end) { // all are spaces! return std::string_view(); } } auto f2 = end; if (type != TrimType::TRIM_LEFT) { auto f3 = f1; do { f2 = std::ranges::find_if(++f3, end, is_space); if (f2 == end) break; f3 = std::ranges::find_if_not(f2 + 1, end, is_space); } while (f3 != end); } return std::string_view(f1, f2); } static std::string_view trimSpaces(const char* r, TrimType type = TrimType::TRIM_BOTH) { return trimSpaces(std::string_view(r), type); } template std::optional numFromStr(R&& r) requires((std::integral || std::floating_point) && std::same_as>>) { T val; const char* end_ptr = &*r.end(); if constexpr (std::integral) { auto cvt_res = std::from_chars(&*r.begin(), &*r.end(), val); if (cvt_res.ec != std::errc()) { return std::nullopt; } else if (cvt_res.ptr != end_ptr) { return std::nullopt; } } else { #ifdef _LIBCPP_VERSION // clang's libc++ does not have floating-point overloads for std::from_chars std::string s{str.begin(), str.end()}; size_t pos; try { if constexpr (std::same_as) { val = std::stof(s, &pos); } else if constexpr (std::same_as) { val = std::stod(s, &pos); } else { val = std::stold(s, &pos); } } catch (...) { return std::nullopt; } if (pos != s.size()) { return std::nullopt; } #else auto cvt_res = std::from_chars(&*r.begin(), &*r.end(), val); if (cvt_res.ec != std::errc()) { return std::nullopt; } else if (cvt_res.ptr != end_ptr) { return std::nullopt; } #endif } return val; } template static std::optional parsAngleString(R&& r, bool hms = false) requires std::same_as>> { auto str = trimSpaces(std::forward(r)); bool ok = std::regex_match(str.begin(), str.end(), decimalNumberRx); if (ok) { return numFromStr(str); } ok = std::regex_match(str.begin(), str.end(), sexagesimalReprRx); if (ok) { auto str = trimSpaces(std::forward(r)); auto parts = std::views::split(str, std::string_view(":")); double val; double p1 = numFromStr(*parts.begin()).value(); val = std::abs(p1); double dv = 60.0; for (auto const& v : parts | std::views::drop(1)) { val += numFromStr(v).value() / dv; dv *= 60.0; } // val += numFromStr(parts[1]) / 60.0; // val += numFromStr(parts[2]) / 3600.0; if (p1 < 0) { val = -val; } if (hms) { val *= 15.0; } return val; } return std::nullopt; } static std::optional parsAngleString(const char* s, bool hms = false) { return parsAngleString(std::span{s, std::strlen(s)}, hms); } static constexpr auto deg2radCoeff = std::numbers::pi / 180.0; // radians to degrees template static double rad2deg(double ang) { auto r = ang / deg2radCoeff; if constexpr (NORM) { return std::fmod(r, 360.0); } else { return r; } } template R, bool NORM = false> static R rad2deg_str(double ang, int prec = 6) { R res; std::string fmt = "{:0." + std::to_string(prec) + "f}"; auto degs = rad2deg(ang); std::vformat_to(std::back_inserter(res), {fmt.begin(), fmt.end()}, std::make_format_args(degs)); return res; } template static std::string rad2deg_str(double ang, int prec = 6) { return rad2deg_str(ang, prec); } template R, bool NORM = false> static R rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6) { R res = rad2deg_str(ang1, prec1); R r = rad2deg_str(ang2, prec2); std::ranges::copy(delim, std::back_inserter(res)); std::ranges::copy(r, std::back_inserter(res)); return res; } template static std::string rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6) { return rad2deg_str(ang1, ang2, delim, prec1, prec2); } // radians to sexagesimal template R, bool NORM = false> static R rad2sxg(double ang, bool hms = false, int prec = 2) { R res; if (!std::isfinite(ang)) { std::vformat_to(std::back_inserter(res), "{}", std::make_format_args(ang)); return res; } std::string fmt = "{:02.0f}:{:02.0f}:{:0" + std::to_string(prec + 3) + "." + std::to_string(prec) + "f}"; // std::string fmt = "{:02.0f}:{:02.0f}:{:02." + std::to_string(prec) + "f}"; auto degs = rad2deg(std::abs(ang)); if (hms) { degs /= 15.0; } auto term = 10.0; for (int i = 1; i < prec; ++i) { term *= 10.0; } auto d = std::trunc(degs); auto s = (degs - d) * 60.0; auto m = std::trunc(s); s = (s - m) * 60.0; // round to given precision s = std::round(s * term) / term; if (isEqual(s, 60.0)) { m += 1.0; s = 0.0; if (isEqual(m, 60.0)) { d += 1.0; m = 0.0; } } if (ang < 0) { std::ranges::copy(std::string_view("-"), std::back_inserter(res)); } std::vformat_to(std::back_inserter(res), std::string_view{fmt.begin(), fmt.end()}, std::make_format_args(d, m, s)); return res; } template static std::string rad2sxg(double ang, bool hms = false, int prec = 2) { return rad2sxg(ang, hms, prec); } template R, bool NORM = false> static R RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1) { R res = rad2sxg(ra, true, ra_prec); R r = rad2sxg(dec, false, dec_prec); std::ranges::copy(delim, std::back_inserter(res)); std::ranges::copy(r, std::back_inserter(res)); return res; } template static std::string RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1) { return RADEC_rad2sxg(ra, dec, delim, ra_prec, dec_prec); } template R, bool NORM = false> static R AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1) { R res = rad2sxg(az, false, az_prec); R r = rad2sxg(zd, false, zd_prec); std::ranges::copy(delim, std::back_inserter(res)); std::ranges::copy(r, std::back_inserter(res)); return res; } template static std::string AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1) { return AZZD_rad2sxg(az, zd, delim, az_prec, zd_prec); } // string to pair of angles // " 12.3453467,102.4345346" // "12:43:23.423, 102:43:12.124" // " 12.3453467, 102:43:12.124 " template std::pair parseAnglePair(R&& str, bool hms1 = false, bool hms2 = false, std::string_view delim = ",") { std::pair res{std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; auto found1 = std::ranges::search(std::forward(str), delim); if (found1.empty()) { return res; } std::string s1, s2; std::ranges::copy(str.begin(), found1.begin(), std::back_inserter(s1)); auto rem_len = std::distance(found1.end(), str.end()); if (!rem_len) { return res; } auto found2 = std::ranges::search(found1.end(), str.end(), delim.begin(), delim.end()); if (found2.empty()) { std::ranges::copy(found1.end(), str.end(), std::back_inserter(s2)); } else { std::ranges::copy(found1.end(), found2.end(), std::back_inserter(s2)); } auto vo1 = parsAngleString(s1, hms1); if (vo1) { auto vo2 = parsAngleString(s2, hms2); if (vo2) { res = {vo1.value(), vo2.value()}; } } return res; } } // namespace mcc::utils