359 lines
9.7 KiB
C++
359 lines
9.7 KiB
C++
#pragma once
|
|
|
|
#include <charconv>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <format>
|
|
#include <numbers>
|
|
#include <ranges>
|
|
#include <regex>
|
|
#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<std::common_type_t<decltype(v1), decltype(v2)>>::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 <std::ranges::contiguous_range R>
|
|
static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH)
|
|
requires std::same_as<char, std::remove_cvref_t<std::ranges::range_value_t<R>>>
|
|
{
|
|
auto is_space = [](const auto& ch) { return ch == ' '; };
|
|
|
|
auto end = std::forward<R>(r).end();
|
|
|
|
auto f1 = std::forward<R>(r).begin();
|
|
|
|
if (type != TrimType::TRIM_RIGHT) {
|
|
// look for the first non-space symbol
|
|
f1 = std::ranges::find_if_not(std::forward<R>(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 <typename T, std::ranges::contiguous_range R>
|
|
std::optional<T> numFromStr(R&& r)
|
|
requires((std::integral<T> || std::floating_point<T>) &&
|
|
std::same_as<char, std::remove_cvref_t<std::ranges::range_value_t<R>>>)
|
|
{
|
|
T val;
|
|
const char* end_ptr = &*r.end();
|
|
|
|
if constexpr (std::integral<T>) {
|
|
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<T, float>) {
|
|
val = std::stof(s, &pos);
|
|
} else if constexpr (std::same_as<T, double>) {
|
|
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 <std::ranges::contiguous_range R>
|
|
static std::optional<double> parsAngleString(R&& r, bool hms = false)
|
|
requires std::same_as<char, std::remove_cvref_t<std::ranges::range_value_t<R>>>
|
|
{
|
|
auto str = trimSpaces(std::forward<R>(r));
|
|
|
|
bool ok = std::regex_match(str.begin(), str.end(), decimalNumberRx);
|
|
|
|
if (ok) {
|
|
return numFromStr<double>(str);
|
|
}
|
|
|
|
ok = std::regex_match(str.begin(), str.end(), sexagesimalReprRx);
|
|
|
|
if (ok) {
|
|
auto str = trimSpaces(std::forward<R>(r));
|
|
auto parts = std::views::split(str, std::string_view(":"));
|
|
|
|
double val;
|
|
|
|
double p1 = numFromStr<double>(*parts.begin()).value();
|
|
val = std::abs(p1);
|
|
double dv = 60.0;
|
|
for (auto const& v : parts | std::views::drop(1)) {
|
|
val += numFromStr<double>(v).value() / dv;
|
|
dv *= 60.0;
|
|
}
|
|
// val += numFromStr<double>(parts[1]) / 60.0;
|
|
// val += numFromStr<double>(parts[2]) / 3600.0;
|
|
if (p1 < 0) {
|
|
val = -val;
|
|
}
|
|
|
|
if (hms) {
|
|
val *= 15.0;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
static std::optional<double> 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 <bool NORM = false>
|
|
static double rad2deg(double ang)
|
|
{
|
|
auto r = ang / deg2radCoeff;
|
|
|
|
if constexpr (NORM) {
|
|
return std::fmod(r, 360.0);
|
|
} else {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
|
|
template <std::ranges::output_range<char> 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<NORM>(ang);
|
|
|
|
std::vformat_to(std::back_inserter(res), {fmt.begin(), fmt.end()}, std::make_format_args(degs));
|
|
|
|
return res;
|
|
}
|
|
|
|
template <bool NORM = false>
|
|
static std::string rad2deg_str(double ang, int prec = 6)
|
|
{
|
|
return rad2deg_str<std::string, NORM>(ang, prec);
|
|
}
|
|
|
|
|
|
template <std::ranges::output_range<char> 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<R, NORM>(ang1, prec1);
|
|
|
|
R r = rad2deg_str<R, NORM>(ang2, prec2);
|
|
|
|
std::ranges::copy(delim, std::back_inserter(res));
|
|
std::ranges::copy(r, std::back_inserter(res));
|
|
|
|
return res;
|
|
}
|
|
|
|
template <bool NORM = false>
|
|
static std::string rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6)
|
|
{
|
|
return rad2deg_str<std::string, NORM>(ang1, ang2, delim, prec1, prec2);
|
|
}
|
|
|
|
// radians to sexagesimal
|
|
template <std::ranges::output_range<char> 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<NORM>(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 <bool NORM = false>
|
|
static std::string rad2sxg(double ang, bool hms = false, int prec = 2)
|
|
{
|
|
return rad2sxg<std::string, NORM>(ang, hms, prec);
|
|
}
|
|
|
|
|
|
template <std::ranges::output_range<char> 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<R, NORM>(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 <bool NORM = false>
|
|
static std::string RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1)
|
|
{
|
|
return RADEC_rad2sxg<std::string, NORM>(ra, dec, delim, ra_prec, dec_prec);
|
|
}
|
|
|
|
|
|
template <std::ranges::output_range<char> 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<R, NORM>(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 <bool NORM = false>
|
|
static std::string AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1)
|
|
{
|
|
return AZZD_rad2sxg<std::string, NORM>(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 <mcc::traits::mcc_input_char_range R>
|
|
std::pair<double, double> parseAnglePair(R&& str, bool hms1 = false, bool hms2 = false, std::string_view delim = ",")
|
|
{
|
|
std::pair<double, double> res{std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()};
|
|
auto found1 = std::ranges::search(std::forward<R>(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
|