move headers to include/mcc to match duild and deploy configurations
move mcc_bsplines.h from fitpack to include/mcc rewrite CMakeLists.txt to incorporate these changes
This commit is contained in:
735
include/mcc/mcc_angle.h
Normal file
735
include/mcc/mcc_angle.h
Normal file
@@ -0,0 +1,735 @@
|
||||
#pragma once
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF GEOMETRICAL ANGLE *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "mcc_constants.h"
|
||||
#include "mcc_traits.h"
|
||||
#include "mcc_utils.h"
|
||||
|
||||
|
||||
/* HELPERS TO REPRESENT ANGLE VALUE */
|
||||
|
||||
constexpr double operator""_rads(long double val) // angle in radians (no conversion)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
constexpr double operator""_degs(long double val) // angle in degrees
|
||||
{
|
||||
return val * std::numbers::pi / 180.0;
|
||||
}
|
||||
|
||||
constexpr double operator""_arcmins(long double val) // angle in arc minutes
|
||||
{
|
||||
return val * std::numbers::pi / 180.0 / 60.0;
|
||||
}
|
||||
|
||||
constexpr double operator""_arcsecs(long double val) // angle in arc seconds
|
||||
{
|
||||
return val * std::numbers::pi / 180.0 / 3600.0;
|
||||
}
|
||||
|
||||
constexpr double operator""_hours(long double val) // angle in hours
|
||||
{
|
||||
return val * std::numbers::pi / 12.0;
|
||||
}
|
||||
|
||||
constexpr double operator""_mins(long double val) // angle in hour minutes
|
||||
{
|
||||
return val * std::numbers::pi / 12.0 / 60.0;
|
||||
}
|
||||
|
||||
constexpr double operator""_secs(long double val) // angle in hour seconds
|
||||
{
|
||||
return val * std::numbers::pi / 12.0 / 3600.0;
|
||||
}
|
||||
|
||||
constexpr double operator""_dms(const char* s, size_t size) // as a string "DEGREES:MINUTES:SECONDS"
|
||||
{
|
||||
auto res = mcc::utils::parsAngleString(std::span{s, size});
|
||||
if (res.has_value()) {
|
||||
return res.value() * std::numbers::pi / 180.0;
|
||||
} else {
|
||||
throw std::invalid_argument("invalid sexagesimal representation");
|
||||
}
|
||||
}
|
||||
|
||||
constexpr double operator""_hms(const char* s, size_t len) // as a string "HOURS:MINUTES:SECONDS"
|
||||
{
|
||||
auto res = mcc::utils::parsAngleString(std::span{s, len}, true);
|
||||
if (res.has_value()) {
|
||||
return res.value() * std::numbers::pi / 180.0;
|
||||
} else {
|
||||
throw std::invalid_argument("invalid sexagesimal representation");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
// tags for MccAngle class construction
|
||||
|
||||
struct MccRadianTag {
|
||||
};
|
||||
struct MccDegreeTag {
|
||||
};
|
||||
static constexpr MccDegreeTag mcc_degrees{};
|
||||
struct MccHMSTag {
|
||||
};
|
||||
static constexpr MccHMSTag mcc_hms{};
|
||||
|
||||
|
||||
class MccAngle
|
||||
{
|
||||
protected:
|
||||
double _angleInRads{0.0};
|
||||
int _precision{2};
|
||||
|
||||
public:
|
||||
enum norm_kind_t {
|
||||
NORM_KIND_0_360, // [0,360]
|
||||
NORM_KIND_0_180, // [0,180]
|
||||
NORM_KIND_180_180, // [-180,180]
|
||||
NORM_KIND_90_90, // [-90,90]
|
||||
};
|
||||
|
||||
MccAngle() = default;
|
||||
|
||||
// by default 'val' is in radians
|
||||
constexpr MccAngle(const double& val, const MccRadianTag = MccRadianTag{}) : _angleInRads(val) {}
|
||||
|
||||
// construct angle from 'val' in degrees, e.g.:
|
||||
// auto ang = MccAngle{180.0, mcc_degrees};
|
||||
constexpr MccAngle(const double& val, const MccDegreeTag) : _angleInRads(val * utils::deg2radCoeff) {}
|
||||
|
||||
|
||||
|
||||
// constuct angle from sexagesimal representation or floating-point number of degrees, e.g.:
|
||||
// auto ang = MccAngle{"-12:34:56.789"}; // from degrees:minutes:seconds
|
||||
// auto ang = MccAngle{"123.574698"}; // from degrees
|
||||
constexpr MccAngle(traits::mcc_input_char_range auto const& val)
|
||||
{
|
||||
auto res = utils::parsAngleString(val);
|
||||
if (res.has_value()) {
|
||||
_angleInRads = res.value() * utils::deg2radCoeff;
|
||||
} else {
|
||||
throw std::invalid_argument("invalid sexagesimal representation");
|
||||
}
|
||||
}
|
||||
|
||||
// construct angle from sexagesimal representation or floating-point number of degrees, e.g.:
|
||||
// auto ang = MccAngle{"01:23:45.6789", mcc_hms}; // from hours:minutes:seconds
|
||||
// auto ang = MccAngle{"123.574698"}; // from degrees
|
||||
constexpr MccAngle(traits::mcc_input_char_range auto const& val, const MccHMSTag)
|
||||
{
|
||||
auto res = utils::parsAngleString(val, true);
|
||||
if (res.has_value()) {
|
||||
_angleInRads = res.value() * utils::deg2radCoeff;
|
||||
} else {
|
||||
throw std::invalid_argument("invalid sexagesimal representation");
|
||||
}
|
||||
}
|
||||
|
||||
MccAngle(const MccAngle&) = default;
|
||||
MccAngle(MccAngle&&) = default;
|
||||
|
||||
MccAngle& operator=(const MccAngle&) = default;
|
||||
MccAngle& operator=(MccAngle&&) = default;
|
||||
|
||||
virtual ~MccAngle() = default;
|
||||
|
||||
template <std::derived_from<MccAngle> T>
|
||||
constexpr auto& operator=(this T&& self, const T& other)
|
||||
{
|
||||
std::forward<decltype(self)>(self)._angleInRads = other._angleInRads;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
template <std::derived_from<MccAngle> T>
|
||||
constexpr auto& operator=(this T&& self, T&& other)
|
||||
{
|
||||
// std::forward<decltype(self)>(self)._angleInRads = std::move(other._angleInRads);
|
||||
std::forward<decltype(self)>(self)._angleInRads = std::forward<T>(other)._angleInRads;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto& operator=(this auto&& self, const T& val)
|
||||
requires std::is_arithmetic_v<T>
|
||||
{
|
||||
std::forward<decltype(self)>(self)._angleInRads = val;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// normalize coordinate
|
||||
template <norm_kind_t KIND>
|
||||
MccAngle& normalize()
|
||||
{
|
||||
_angleInRads = std::fmod(_angleInRads, std::numbers::pi * 2.0);
|
||||
|
||||
if constexpr (KIND == NORM_KIND_0_360) {
|
||||
if (_angleInRads < 0.0) {
|
||||
_angleInRads += 2.0 * std::numbers::pi;
|
||||
}
|
||||
} else if constexpr (KIND == NORM_KIND_0_180) {
|
||||
if (_angleInRads < -std::numbers::pi) {
|
||||
// _angleInRads = 2.0 * std::numbers::pi + _angleInRads;
|
||||
_angleInRads = MCC_TWO_PI + _angleInRads;
|
||||
} else if (_angleInRads < 0.0) {
|
||||
_angleInRads = -_angleInRads;
|
||||
}
|
||||
} else if constexpr (KIND == NORM_KIND_180_180) {
|
||||
if (_angleInRads > std::numbers::pi) {
|
||||
// _angleInRads = 2.0 * std::numbers::pi - _angleInRads;
|
||||
_angleInRads = MCC_TWO_PI - _angleInRads;
|
||||
} else if (_angleInRads < -std::numbers::pi) {
|
||||
// _angleInRads += 2.0 * std::numbers::pi;
|
||||
_angleInRads += MCC_TWO_PI;
|
||||
}
|
||||
} else if constexpr (KIND == NORM_KIND_90_90) {
|
||||
if (_angleInRads >= 1.5 * std::numbers::pi) {
|
||||
_angleInRads = _angleInRads - 2.0 * std::numbers::pi;
|
||||
} else if (_angleInRads >= std::numbers::pi / 2.0) {
|
||||
_angleInRads = std::numbers::pi - _angleInRads;
|
||||
} else if (_angleInRads <= -1.5 * std::numbers::pi) {
|
||||
// _angleInRads += 2.0 * std::numbers::pi;
|
||||
_angleInRads += MCC_TWO_PI;
|
||||
} else if (_angleInRads <= -std::numbers::pi / 2.0) {
|
||||
_angleInRads = -(std::numbers::pi + _angleInRads);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
MccAngle& normalize()
|
||||
{
|
||||
return normalize<NORM_KIND_0_360>();
|
||||
}
|
||||
|
||||
|
||||
// template <typename T>
|
||||
// operator T() const
|
||||
// requires std::is_arithmetic_v<T>
|
||||
// {
|
||||
// return _angleInRads;
|
||||
// }
|
||||
|
||||
constexpr operator double() const
|
||||
{
|
||||
return _angleInRads;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
constexpr T degrees() const
|
||||
{
|
||||
return _angleInRads * 180.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
constexpr double degrees() const
|
||||
{
|
||||
return degrees<double>();
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T arcmins() const
|
||||
{
|
||||
return _angleInRads * 10800.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
double arcmins() const
|
||||
{
|
||||
return arcmins<double>();
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T arcsecs() const
|
||||
{
|
||||
return _angleInRads * 648000.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
double arcsecs() const
|
||||
{
|
||||
return arcsecs<double>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T hours() const
|
||||
{
|
||||
return _angleInRads * 12.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
double hours() const
|
||||
{
|
||||
return hours<double>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T minutes() const
|
||||
{
|
||||
return _angleInRads * 720.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
double minutes() const
|
||||
{
|
||||
return minutes<double>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T seconds() const
|
||||
{
|
||||
return _angleInRads * 43200.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
double seconds() const
|
||||
{
|
||||
return seconds<double>();
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_output_char_range T>
|
||||
T sexagesimal(bool hms = false, int prec = 2) const
|
||||
{
|
||||
return utils::rad2sxg(_angleInRads, hms, prec >= 0 ? prec : _precision);
|
||||
}
|
||||
|
||||
std::string sexagesimal(bool hms = false, int prec = 2) const
|
||||
{
|
||||
return sexagesimal<std::string>(hms, prec);
|
||||
}
|
||||
|
||||
|
||||
// arithmetics
|
||||
|
||||
template <typename SelfT, std::convertible_to<MccAngle> T>
|
||||
SelfT& operator+=(this SelfT& self, const T& v)
|
||||
{
|
||||
if constexpr (std::derived_from<T, MccAngle>) {
|
||||
static_assert(std::derived_from<SelfT, T>, "INCOMPATIBLE TYPES!");
|
||||
|
||||
self._angleInRads += v._angleInRads;
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
self._angleInRads += v;
|
||||
} else {
|
||||
self._angleInRads += MccAngle(v)._angleInRads;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
template <typename SelfT, std::convertible_to<MccAngle> T>
|
||||
SelfT& operator-=(this SelfT& self, const T& v)
|
||||
{
|
||||
if constexpr (std::derived_from<T, MccAngle>) {
|
||||
static_assert(std::derived_from<SelfT, T>, "INCOMPATIBLE TYPES!");
|
||||
|
||||
self._angleInRads -= v._angleInRads;
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
self._angleInRads -= v;
|
||||
} else {
|
||||
self._angleInRads -= MccAngle(v)._angleInRads;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
template <typename SelfT, typename T>
|
||||
SelfT& operator*=(this SelfT& self, const T& v)
|
||||
requires std::is_arithmetic_v<T>
|
||||
{
|
||||
self._angleInRads *= v;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
template <typename SelfT, typename T>
|
||||
SelfT& operator/=(this SelfT& self, const T& v)
|
||||
requires std::is_arithmetic_v<T>
|
||||
{
|
||||
self._angleInRads /= v;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
// unary '-' and '+'
|
||||
template <typename SelfT>
|
||||
SelfT operator-(this SelfT& self)
|
||||
{
|
||||
SelfT res = -self._angleInRads;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename SelfT>
|
||||
SelfT operator+(this SelfT& self)
|
||||
{
|
||||
return self;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// binary arithmetic operations
|
||||
|
||||
template <std::convertible_to<MccAngle> T1, std::convertible_to<MccAngle> T2>
|
||||
static auto operator+(const T1& v1, const T2& v2)
|
||||
{
|
||||
static_assert(std::convertible_to<T1, T2> || std::convertible_to<T2, T1>, "INCOMPATIBLE TYPES!");
|
||||
|
||||
using res_t = std::conditional_t<std::convertible_to<T1, T2> && std::derived_from<T1, MccAngle>, T1, T2>;
|
||||
|
||||
return res_t{(double)v1 + (double)v2};
|
||||
}
|
||||
|
||||
template <std::convertible_to<MccAngle> T1, std::convertible_to<MccAngle> T2>
|
||||
static auto operator-(const T1& v1, const T2& v2)
|
||||
{
|
||||
static_assert(std::convertible_to<T1, T2> || std::convertible_to<T2, T1>, "INCOMPATIBLE TYPES!");
|
||||
|
||||
using res_t = std::conditional_t<std::convertible_to<T1, T2> && std::derived_from<T1, MccAngle>, T1, T2>;
|
||||
|
||||
return res_t{(double)v1 - (double)v2};
|
||||
}
|
||||
|
||||
|
||||
template <std::convertible_to<MccAngle> T1, std::convertible_to<MccAngle> T2>
|
||||
static auto operator*(const T1& v1, const T2& v2)
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<T1>) {
|
||||
return T2{(double)v2 * v1};
|
||||
} else if constexpr (std::is_arithmetic_v<T2>) {
|
||||
return T1{(double)v1 * v2};
|
||||
} else {
|
||||
using res_t = std::conditional_t<std::convertible_to<T1, T2> && std::derived_from<T1, MccAngle>, T1, T2>;
|
||||
return res_t{(double)v1 * (double)v2};
|
||||
// static_assert(false, "INCOMPATIBLE TYPES!");
|
||||
}
|
||||
}
|
||||
|
||||
template <std::convertible_to<MccAngle> T1, typename T2>
|
||||
static T1 operator/(const T1& v1, const T2& v2)
|
||||
requires std::is_arithmetic_v<T2>
|
||||
{
|
||||
return (double)v1 / v2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string MccAngleFancyString(std::convertible_to<MccAngle> auto const& ang,
|
||||
std::format_string<double> val_fmt = "{}")
|
||||
{
|
||||
std::string s;
|
||||
|
||||
double abs_ang;
|
||||
if constexpr (std::is_arithmetic_v<std::decay_t<decltype(ang)>>) {
|
||||
abs_ang = std::abs(ang);
|
||||
} else {
|
||||
abs_ang = std::abs(MccAngle{ang});
|
||||
}
|
||||
|
||||
if (abs_ang < 1.0_arcmins) {
|
||||
std::format_to(std::back_inserter(s), val_fmt, MccAngle{ang}.arcsecs());
|
||||
s += " arcsecs";
|
||||
} else if (abs_ang < 1.0_degs) {
|
||||
std::format_to(std::back_inserter(s), val_fmt, MccAngle{ang}.arcmins());
|
||||
s += " arcmins";
|
||||
} else {
|
||||
std::format_to(std::back_inserter(s), val_fmt, MccAngle{ang}.degrees());
|
||||
s += " degs";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/* HELPER TYPES TO REPERESENT ANGLES ON THE CELESTIAL SPHERE */
|
||||
|
||||
class MccNamedAngle : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
|
||||
constexpr MccNamedAngle(MccAngle&& other)
|
||||
{
|
||||
_angleInRads = (double)std::forward<decltype(other)>(other);
|
||||
}
|
||||
|
||||
constexpr MccNamedAngle& operator=(MccAngle&& other)
|
||||
{
|
||||
_angleInRads = (double)std::forward<decltype(other)>(other);
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class MccAngleRA_ICRS : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleDEC_ICRS : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleRA_APP : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleDEC_APP : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleRA_OBS : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleDEC_OBS : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleHA : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleHA_APP : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleHA_OBS : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleAZ : public MccNamedAngle
|
||||
// class MccAngleAZ : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccNamedAngle::MccNamedAngle;
|
||||
// using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleZD : public MccNamedAngle
|
||||
// class MccAngleZD : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccNamedAngle::MccNamedAngle;
|
||||
// using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
static const MccAngleAZ az{MccAngle{}};
|
||||
|
||||
class MccAngleALT : public MccNamedAngle
|
||||
// class MccAngleALT : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccNamedAngle::MccNamedAngle;
|
||||
// using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
|
||||
class MccAngleX : public MccAngle // some co-longitude coordinate
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleY : public MccAngle // some co-latitude coordinate
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class MccAngleLAT : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleLON : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
class MccAngleUnknown : public MccAngle
|
||||
{
|
||||
public:
|
||||
using MccAngle::MccAngle;
|
||||
};
|
||||
|
||||
|
||||
enum class MccCoordKind : size_t {
|
||||
COORDS_KIND_GENERIC = traits::mcc_type_hash<MccAngle>,
|
||||
COORDS_KIND_RA_ICRS = traits::mcc_type_hash<MccAngleRA_ICRS>,
|
||||
COORDS_KIND_DEC_ICRS = traits::mcc_type_hash<MccAngleDEC_ICRS>,
|
||||
COORDS_KIND_RA_APP = traits::mcc_type_hash<MccAngleRA_APP>,
|
||||
COORDS_KIND_DEC_APP = traits::mcc_type_hash<MccAngleDEC_APP>,
|
||||
COORDS_KIND_RA_OBS = traits::mcc_type_hash<MccAngleRA_OBS>,
|
||||
COORDS_KIND_DEC_OBS = traits::mcc_type_hash<MccAngleDEC_OBS>,
|
||||
COORDS_KIND_HA_APP = traits::mcc_type_hash<MccAngleHA_APP>,
|
||||
COORDS_KIND_HA_OBS = traits::mcc_type_hash<MccAngleHA_OBS>,
|
||||
COORDS_KIND_AZ = traits::mcc_type_hash<MccAngleAZ>,
|
||||
COORDS_KIND_ZD = traits::mcc_type_hash<MccAngleZD>,
|
||||
COORDS_KIND_ALT = traits::mcc_type_hash<MccAngleALT>,
|
||||
COORDS_KIND_X = traits::mcc_type_hash<MccAngleX>,
|
||||
COORDS_KIND_Y = traits::mcc_type_hash<MccAngleY>,
|
||||
COORDS_KIND_LAT = traits::mcc_type_hash<MccAngleLAT>,
|
||||
COORDS_KIND_LON = traits::mcc_type_hash<MccAngleLON>,
|
||||
COORDS_KIND_UKNOWN = traits::mcc_type_hash<MccAngleUnknown>
|
||||
};
|
||||
|
||||
enum class MccCoordPairKind : size_t {
|
||||
COORDS_KIND_GENERIC = traits::mcc_type_pair_hash<MccAngle, MccAngle>(),
|
||||
COORDS_KIND_RADEC_ICRS = traits::mcc_type_pair_hash<MccAngleRA_ICRS, MccAngleDEC_ICRS>(),
|
||||
COORDS_KIND_RADEC_APP = traits::mcc_type_pair_hash<MccAngleRA_APP, MccAngleDEC_APP>(),
|
||||
COORDS_KIND_RADEC_OBS = traits::mcc_type_pair_hash<MccAngleRA_OBS, MccAngleDEC_OBS>(),
|
||||
COORDS_KIND_HADEC_APP = traits::mcc_type_pair_hash<MccAngleHA_APP, MccAngleDEC_APP>(),
|
||||
COORDS_KIND_HADEC_OBS = traits::mcc_type_pair_hash<MccAngleHA_OBS, MccAngleDEC_OBS>(),
|
||||
COORDS_KIND_AZZD = traits::mcc_type_pair_hash<MccAngleAZ, MccAngleZD>(),
|
||||
COORDS_KIND_AZALT = traits::mcc_type_pair_hash<MccAngleAZ, MccAngleALT>(),
|
||||
COORDS_KIND_XY = traits::mcc_type_pair_hash<MccAngleX, MccAngleY>(),
|
||||
COORDS_KIND_LONLAT = traits::mcc_type_pair_hash<MccAngleLON, MccAngleLAT>(),
|
||||
COORDS_KIND_UNKNOWN = traits::mcc_type_pair_hash<MccAngleUnknown, MccAngleUnknown>()
|
||||
};
|
||||
|
||||
template <MccCoordPairKind PK>
|
||||
static constexpr bool mccIsObsCoordPairKind =
|
||||
(PK == MccCoordPairKind::COORDS_KIND_RADEC_OBS || PK == MccCoordPairKind::COORDS_KIND_HADEC_OBS ||
|
||||
PK == MccCoordPairKind::COORDS_KIND_AZZD || PK == MccCoordPairKind::COORDS_KIND_AZALT);
|
||||
|
||||
static constexpr bool mcc_is_obs_coordpair(MccCoordPairKind kind)
|
||||
{
|
||||
return kind == MccCoordPairKind::COORDS_KIND_RADEC_OBS || kind == MccCoordPairKind::COORDS_KIND_HADEC_OBS ||
|
||||
kind == MccCoordPairKind::COORDS_KIND_AZZD || kind == MccCoordPairKind::COORDS_KIND_AZALT;
|
||||
};
|
||||
|
||||
template <MccCoordPairKind PK>
|
||||
static constexpr bool mccIsAppCoordPairKind =
|
||||
(PK == MccCoordPairKind::COORDS_KIND_RADEC_APP || PK == MccCoordPairKind::COORDS_KIND_HADEC_APP);
|
||||
|
||||
static constexpr bool mcc_is_app_coordpair(MccCoordPairKind kind)
|
||||
{
|
||||
return kind == MccCoordPairKind::COORDS_KIND_RADEC_APP || kind == MccCoordPairKind::COORDS_KIND_HADEC_APP;
|
||||
};
|
||||
|
||||
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_ICRS_STR = "RADEC-IRCS";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_APP_STR = "RADEC-APP";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_OBS_STR = "RADEC-OBS";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_HADEC_APP_STR = "HADEC-APP";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_HADEC_OBS_STR = "HADEC-OBS";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_AZALT_STR = "AZALT";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_AZZD_STR = "AZZD";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_XY_STR = "XY";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_LONLAT_STR = "LATLON";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_GENERIC_STR = "GENERIC";
|
||||
static constexpr std::string_view MCC_COORDPAIR_KIND_UNKNOWN_STR = "UNKNOWN";
|
||||
|
||||
template <MccCoordPairKind KIND>
|
||||
static constexpr std::string_view MccCoordPairKindStr =
|
||||
KIND == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COORDPAIR_KIND_RADEC_ICRS_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COORDPAIR_KIND_RADEC_APP_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_OBS ? MCC_COORDPAIR_KIND_RADEC_OBS_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COORDPAIR_KIND_HADEC_APP_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_OBS ? MCC_COORDPAIR_KIND_HADEC_OBS_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COORDPAIR_KIND_AZALT_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COORDPAIR_KIND_AZZD_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_XY ? MCC_COORDPAIR_KIND_XY_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_LONLAT ? MCC_COORDPAIR_KIND_LONLAT_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_GENERIC ? MCC_COORDPAIR_KIND_GENERIC_STR
|
||||
: MCC_COORDPAIR_KIND_UNKNOWN_STR;
|
||||
|
||||
|
||||
static constexpr std::string_view MccCoordPairKindToStr(MccCoordPairKind KIND)
|
||||
{
|
||||
return KIND == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COORDPAIR_KIND_RADEC_ICRS_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COORDPAIR_KIND_RADEC_APP_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_OBS ? MCC_COORDPAIR_KIND_RADEC_OBS_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COORDPAIR_KIND_HADEC_APP_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_OBS ? MCC_COORDPAIR_KIND_HADEC_OBS_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COORDPAIR_KIND_AZALT_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COORDPAIR_KIND_AZZD_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_XY ? MCC_COORDPAIR_KIND_XY_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_LONLAT ? MCC_COORDPAIR_KIND_LONLAT_STR
|
||||
: KIND == MccCoordPairKind::COORDS_KIND_GENERIC ? MCC_COORDPAIR_KIND_GENERIC_STR
|
||||
: MCC_COORDPAIR_KIND_UNKNOWN_STR;
|
||||
}
|
||||
|
||||
|
||||
template <mcc::traits::mcc_char_range R>
|
||||
static constexpr MccCoordPairKind MccCoordStrToPairKind(R&& spair)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<std::decay_t<R>>) {
|
||||
return MccCoordStrToPairKind(std::string_view{spair});
|
||||
}
|
||||
|
||||
const auto hash = mcc::utils::FNV1aHash(std::forward<R>(spair));
|
||||
|
||||
return hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_ICRS_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_APP_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_APP
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_OBS_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_OBS
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_HADEC_APP_STR) ? MccCoordPairKind::COORDS_KIND_HADEC_APP
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_HADEC_OBS_STR) ? MccCoordPairKind::COORDS_KIND_HADEC_OBS
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_AZALT_STR) ? MccCoordPairKind::COORDS_KIND_AZALT
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_AZZD_STR) ? MccCoordPairKind::COORDS_KIND_AZZD
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_XY_STR) ? MccCoordPairKind::COORDS_KIND_XY
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_LONLAT_STR) ? MccCoordPairKind::COORDS_KIND_LONLAT
|
||||
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_GENERIC_STR) ? MccCoordPairKind::COORDS_KIND_GENERIC
|
||||
: MccCoordPairKind::COORDS_KIND_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
// enum class MccCoordinatePairRep : int {
|
||||
// MCC_COORDPAIR_REP_DEGREES, // both angles are in decimal degrees
|
||||
// MCC_COORDPAIR_REP_SXGM_HOURDEG, // X is in hour and Y is in degree sexagesimal representation
|
||||
// MCC_COORDPAIR_REP_SXGM_DEGDEG // both angles are in sexagesimal degrees
|
||||
// };
|
||||
|
||||
|
||||
// // default wide-acceptable sexagesimal representation
|
||||
// static constexpr MccCoordinatePairRep MccCoordinatePairToSxgmRep(MccCoordPairKind kind)
|
||||
// {
|
||||
// return kind == MccCoordPairKind::COORDS_KIND_AZALT || kind == MccCoordPairKind::COORDS_KIND_AZZD ||
|
||||
// kind == MccCoordPairKind::COORDS_KIND_XY || kind == MccCoordPairKind::COORDS_KIND_LONLAT ||
|
||||
// kind == MccCoordPairKind::COORDS_KIND_GENERIC
|
||||
// ? MccCoordinatePairRep::MCC_COORDPAIR_REP_SXGM_DEGDEG
|
||||
// : MccCoordinatePairRep::MCC_COORDPAIR_REP_SXGM_HOURDEG; // RA-DEC or HA-DEC
|
||||
// }
|
||||
|
||||
} // namespace mcc::impl
|
||||
369
include/mcc/mcc_bsplines.h
Normal file
369
include/mcc/mcc_bsplines.h
Normal file
@@ -0,0 +1,369 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
#include <FortranCInterface.h>
|
||||
|
||||
extern "C" {
|
||||
void surfit(int* iopt,
|
||||
int* m,
|
||||
double* x,
|
||||
double* y,
|
||||
double* z,
|
||||
double* w,
|
||||
double* xb,
|
||||
double* xe,
|
||||
double* yb,
|
||||
double* ye,
|
||||
int* kx,
|
||||
int* ky,
|
||||
double* s,
|
||||
int* nxest,
|
||||
int* nyest,
|
||||
int* nmax,
|
||||
double* eps,
|
||||
int* nx,
|
||||
double* tx,
|
||||
int* ny,
|
||||
double* ty,
|
||||
double* c,
|
||||
double* fp,
|
||||
double* wrk1,
|
||||
int* lwrk1,
|
||||
double* wrk2,
|
||||
int* lwrk2,
|
||||
int* iwrk,
|
||||
int* kwrk,
|
||||
int* ier);
|
||||
|
||||
void bispev(double* tx,
|
||||
int* nx,
|
||||
double* ty,
|
||||
int* ny,
|
||||
double* c,
|
||||
int* kx,
|
||||
int* ky,
|
||||
double* x,
|
||||
int* mx,
|
||||
double* y,
|
||||
int* my,
|
||||
double* z,
|
||||
double* wrk,
|
||||
int* lwrk,
|
||||
int* iwrk,
|
||||
int* kwrk,
|
||||
int* ier);
|
||||
|
||||
void parder(double* tx,
|
||||
int* nx,
|
||||
double* ty,
|
||||
int* ny,
|
||||
double* c,
|
||||
int* kx,
|
||||
int* ky,
|
||||
int* nux,
|
||||
int* nuy,
|
||||
double* x,
|
||||
int* mx,
|
||||
double* y,
|
||||
int* my,
|
||||
double* z,
|
||||
double* wrk,
|
||||
int* lwrk,
|
||||
int* iwrk,
|
||||
int* kwrk,
|
||||
int* ier);
|
||||
|
||||
|
||||
void sphere(int* iopt,
|
||||
int* m,
|
||||
double* teta,
|
||||
double* phi,
|
||||
double* r,
|
||||
double* w,
|
||||
double* s,
|
||||
int* ntest,
|
||||
int* npest,
|
||||
double* eps,
|
||||
int* nt,
|
||||
double* tt,
|
||||
int* np,
|
||||
double* tp,
|
||||
double* c,
|
||||
double* fp,
|
||||
double* wrk1,
|
||||
int* lwrk1,
|
||||
double* wrk2,
|
||||
int* lwrk2,
|
||||
int* iwrk,
|
||||
int* kwrk,
|
||||
int* ier);
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace mcc::bsplines
|
||||
{
|
||||
|
||||
template <int IOPT = 0,
|
||||
std::ranges::contiguous_range TethaT,
|
||||
std::ranges::contiguous_range PhiT,
|
||||
std::ranges::contiguous_range FuncT,
|
||||
typename WeightT,
|
||||
std::ranges::contiguous_range TKnotT,
|
||||
std::ranges::contiguous_range PKnotT,
|
||||
std::ranges::contiguous_range CoeffT>
|
||||
int fitpack_sphere_smooth(const TethaT& tetha,
|
||||
const PhiT& phi,
|
||||
const FuncT& func,
|
||||
const WeightT& weight,
|
||||
double s_par,
|
||||
int& ntest,
|
||||
int& npest,
|
||||
TKnotT& tetha_knots,
|
||||
PKnotT& phi_knots,
|
||||
CoeffT& coeffs,
|
||||
double& resi2_sum,
|
||||
double eps = std::numeric_limits<double>::epsilon())
|
||||
requires((std::ranges::contiguous_range<WeightT> || std::convertible_to<WeightT, double>) &&
|
||||
std::ranges::output_range<CoeffT, double>)
|
||||
{
|
||||
static_assert(std::same_as<std::ranges::range_value_t<TethaT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<PhiT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<FuncT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<TKnotT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<PKnotT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<CoeffT>, double>,
|
||||
"Input ranges elements type must be double!");
|
||||
|
||||
if constexpr (std::ranges::contiguous_range<WeightT>) {
|
||||
static_assert(std::same_as<std::ranges::range_value_t<WeightT>, double>,
|
||||
"Input ranges elements type must be double!");
|
||||
}
|
||||
|
||||
|
||||
static_assert(IOPT != -1 || IOPT != 0 || IOPT != 1, "Invalid IOPT template parameter value!");
|
||||
|
||||
|
||||
int m = std::min(std::min(std::ranges::size(tetha), std::ranges::size(phi)), std::ranges::size(func));
|
||||
if (m < 2) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
|
||||
/* do checking here to avoid possibly unnecessary memory allocations */
|
||||
|
||||
// number of knots must be >= 8 for the qubic b-splines
|
||||
if (ntest < 8 || npest < 8) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
if constexpr (std::ranges::contiguous_range<WeightT>) {
|
||||
m = std::min(m, std::ranges::size(weight));
|
||||
if (m < 2) {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (s_par <= 0.0) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
if (eps <= 0.0 || eps >= 1.0) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
int nt = std::ranges::size(tetha_knots);
|
||||
int np = std::ranges::size(phi_knots);
|
||||
|
||||
if (nt < ntest) {
|
||||
std::ranges::fill_n(std::back_inserter(tetha_knots), ntest - nt, 0.0);
|
||||
}
|
||||
if (np < npest) {
|
||||
std::ranges::fill_n(std::back_inserter(phi_knots), npest - np, 0.0);
|
||||
}
|
||||
|
||||
|
||||
// compute working arrays sizes according to sphere.f
|
||||
const int u = ntest - 7;
|
||||
const int v = npest - 7;
|
||||
const int uv = u * v;
|
||||
const int v2 = v * v;
|
||||
|
||||
int lwrk1 = 185 + 52 * v + 10 * u + 14 * uv + 8 * (u - 1) * v2 + 8 * m;
|
||||
int lwrk2 = 48 + 21 * v + 7 * uv + 4 * (u - 1) * v2;
|
||||
int kwrk = m + uv;
|
||||
|
||||
std::vector<double> wrk1(lwrk1);
|
||||
std::vector<double> wrk2(lwrk2);
|
||||
std::vector<int> iwrk(kwrk);
|
||||
|
||||
const int n_coeffs = (ntest - 4) * (npest - 4);
|
||||
if (std::ranges::size(coeffs) < n_coeffs) { // resize
|
||||
std::ranges::fill_n(std::back_inserter(coeffs), n_coeffs - std::ranges::size(coeffs), 0.0);
|
||||
}
|
||||
|
||||
int iopt = IOPT;
|
||||
|
||||
auto tetha_ptr = const_cast<double*>(std::ranges::data(tetha));
|
||||
auto phi_ptr = const_cast<double*>(std::ranges::data(phi));
|
||||
auto func_ptr = const_cast<double*>(std::ranges::data(func));
|
||||
auto tetha_knots_ptr = const_cast<double*>(std::ranges::data(tetha_knots));
|
||||
auto phi_knots_ptr = const_cast<double*>(std::ranges::data(phi_knots));
|
||||
|
||||
if constexpr (std::ranges::contiguous_range<WeightT>) {
|
||||
auto weight_ptr = const_cast<double*>(std::ranges::data(weight));
|
||||
|
||||
sphere(&iopt, &m, tetha_ptr, phi_ptr, func_ptr, weight_ptr, &s_par, &nt, &np, &eps, &ntest, tetha_knots_ptr,
|
||||
&npest, phi_knots_ptr, std::ranges::data(coeffs), &resi2_sum, wrk1.data(), &lwrk1, wrk2.data(), &lwrk2,
|
||||
iwrk.data(), &kwrk, &res);
|
||||
// sphere(&iopt, &m, std::ranges::data(tetha), std::ranges::data(phi), std::ranges::data(func),
|
||||
// std::ranges::data(weight), &s_par, &ntest, &npest, &eps, &ntest, std::ranges::data(tetha_knots),
|
||||
// &npest, std::ranges::data(phi_knots), std::ranges::data(coeffs), &resi2_sum, wrk1.data(), &lwrk1,
|
||||
// wrk2.data(), &lwrk2, iwrk.data(), &kwrk, &res);
|
||||
} else {
|
||||
std::vector<double> weight_vec(m, weight);
|
||||
|
||||
sphere(&iopt, &m, tetha_ptr, phi_ptr, func_ptr, weight_vec.data(), &s_par, &nt, &np, &eps, &ntest,
|
||||
tetha_knots_ptr, &npest, phi_knots_ptr, std::ranges::data(coeffs), &resi2_sum, wrk1.data(), &lwrk1,
|
||||
wrk2.data(), &lwrk2, iwrk.data(), &kwrk, &res);
|
||||
// res = sphere(&iopt, &m, std::ranges::data(tetha), std::ranges::data(phi), std::ranges::data(func),
|
||||
// weight_vec.data(), &s_par, &ntest, &npest, &eps, &ntest, std::ranges::data(tetha_knots), &npest,
|
||||
// std::ranges::data(phi_knots), std::ranges::data(coeffs), &resi2_sum, wrk1.get(), &lwrk1,
|
||||
// wrk2.get(), &lwrk2, iwrk.get(), &kwrk, &res);
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
least-squares fitting (iopt=-1)
|
||||
*/
|
||||
template <std::ranges::contiguous_range TethaT,
|
||||
std::ranges::contiguous_range PhiT,
|
||||
std::ranges::contiguous_range FuncT,
|
||||
typename WeightT,
|
||||
std::ranges::contiguous_range TKnotT,
|
||||
std::ranges::contiguous_range PKnotT,
|
||||
std::ranges::contiguous_range CoeffT>
|
||||
int fitpack_sphere_fit(const TethaT& tetha,
|
||||
const PhiT& phi,
|
||||
const FuncT& func,
|
||||
const WeightT& weight,
|
||||
TKnotT& tetha_knots,
|
||||
PKnotT& phi_knots,
|
||||
CoeffT& coeffs,
|
||||
double& resi2_sum,
|
||||
double eps = std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
const double s = 100.0;
|
||||
int nt = std::ranges::size(tetha_knots);
|
||||
int np = std::ranges::size(phi_knots);
|
||||
|
||||
return fitpack_sphere_smooth<-1>(tetha, phi, func, weight, s, nt, np, tetha_knots, phi_knots, coeffs, resi2_sum,
|
||||
eps);
|
||||
}
|
||||
|
||||
|
||||
template <std::ranges::contiguous_range TXT,
|
||||
std::ranges::contiguous_range TYT,
|
||||
std::ranges::contiguous_range XT,
|
||||
std::ranges::contiguous_range YT,
|
||||
std::ranges::contiguous_range CoeffT,
|
||||
std::ranges::contiguous_range FuncT>
|
||||
int fitpack_eval_spl2d(const TXT& tx,
|
||||
const TYT& ty,
|
||||
const CoeffT& coeffs,
|
||||
const XT& x,
|
||||
const YT& y,
|
||||
FuncT& func,
|
||||
int kx = 3,
|
||||
int ky = 3)
|
||||
{
|
||||
static_assert(std::same_as<std::ranges::range_value_t<TXT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<TYT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<XT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<YT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<CoeffT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<FuncT>, double>,
|
||||
"Input ranges elements type must be double!");
|
||||
|
||||
if (kx < 0 || ky < 0) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
|
||||
int ier = 0;
|
||||
|
||||
int ntx = std::ranges::size(tx);
|
||||
int nty = std::ranges::size(ty);
|
||||
|
||||
auto n_coeffs = (ntx - kx - 1) * (nty - ky - 1);
|
||||
if (std::ranges::size(coeffs) < n_coeffs) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
int mx = std::ranges::size(x), my = std::ranges::size(y);
|
||||
int N = mx * my;
|
||||
|
||||
if (std::ranges::size(func) < N) {
|
||||
std::ranges::fill_n(std::back_inserter(func), N - std::ranges::size(func), 0.0);
|
||||
}
|
||||
|
||||
// compute sizes of working arrays according to bispev.f
|
||||
int lwrk = mx * (kx + 1) + my * (ky + 1);
|
||||
std::vector<double> wrk(lwrk);
|
||||
|
||||
int kwrk = mx + my;
|
||||
std::vector<int> iwrk(kwrk);
|
||||
|
||||
auto tx_ptr = const_cast<double*>(std::ranges::data(tx));
|
||||
auto ty_ptr = const_cast<double*>(std::ranges::data(ty));
|
||||
auto coeffs_ptr = const_cast<double*>(std::ranges::data(coeffs));
|
||||
auto x_ptr = const_cast<double*>(std::ranges::data(x));
|
||||
auto y_ptr = const_cast<double*>(std::ranges::data(y));
|
||||
|
||||
bispev(tx_ptr, &ntx, ty_ptr, &nty, coeffs_ptr, &kx, &ky, x_ptr, &mx, y_ptr, &my, std::ranges::data(func),
|
||||
wrk.data(), &lwrk, iwrk.data(), &kwrk, &ier);
|
||||
|
||||
return ier;
|
||||
}
|
||||
|
||||
|
||||
// scalar x,y version
|
||||
template <std::ranges::contiguous_range TXT,
|
||||
std::ranges::contiguous_range TYT,
|
||||
typename XT,
|
||||
typename YT,
|
||||
std::ranges::contiguous_range CoeffT,
|
||||
typename FuncT>
|
||||
int fitpack_eval_spl2d(const TXT& tx,
|
||||
const TYT& ty,
|
||||
const CoeffT& coeffs,
|
||||
const XT& x,
|
||||
const YT& y,
|
||||
FuncT& func,
|
||||
int kx = 3,
|
||||
int ky = 3)
|
||||
{
|
||||
static_assert(std::same_as<std::ranges::range_value_t<TXT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<TYT>, double> &&
|
||||
std::same_as<std::ranges::range_value_t<CoeffT>, double>,
|
||||
"Input ranges elements type must be double!");
|
||||
|
||||
static_assert(
|
||||
std::convertible_to<XT, double> && std::convertible_to<YT, double> && std::convertible_to<FuncT, double>,
|
||||
"XT, YT and FuncT types must be a scalar convertible to double!");
|
||||
|
||||
auto xv = std::vector<double>(1, x);
|
||||
auto yv = std::vector<double>(1, y);
|
||||
auto fv = std::vector<double>(1, func);
|
||||
|
||||
return fitpack_eval_spl2d(tx, ty, coeffs, xv, yv, fv, kx, ky);
|
||||
}
|
||||
|
||||
} // namespace mcc::fitpack
|
||||
899
include/mcc/mcc_ccte_erfa.h
Normal file
899
include/mcc/mcc_ccte_erfa.h
Normal file
@@ -0,0 +1,899 @@
|
||||
#pragma once
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF CELESTIAL COORDINATES TRANSFORMATION ENGINE *
|
||||
* (BASING ON THE ERFA LIBRARY) *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
#include <mutex>
|
||||
#include <numbers>
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
#include <erfa.h>
|
||||
#include <erfam.h>
|
||||
|
||||
// #include "build/Desktop-Debug/erfa_lib/erfa.h"
|
||||
// #include "build/Desktop-Debug/erfa_lib/erfam.h"
|
||||
|
||||
#include "mcc_ccte_iers.h"
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_error.h"
|
||||
|
||||
namespace mcc::ccte::erfa
|
||||
{
|
||||
|
||||
|
||||
enum class MccCCTE_ERFAErrorCode : int {
|
||||
ERROR_OK = 0,
|
||||
ERROR_NULLPTR,
|
||||
ERROR_INVALID_INPUT_ARG,
|
||||
ERROR_julday_INVALID_YEAR,
|
||||
ERROR_julday_INVALID_MONTH,
|
||||
ERROR_julday_INVALID_DAY,
|
||||
ERROR_UNSUPPORTED_COORD_PAIR,
|
||||
ERROR_BULLETINA_OUT_OF_RANGE,
|
||||
ERROR_LEAPSECONDS_OUT_OF_RANGE,
|
||||
ERROR_DUBIOUS_YEAR,
|
||||
ERROR_UNACCEPTABLE_DATE,
|
||||
ERROR_UPDATE_LEAPSECONDS,
|
||||
ERROR_UPDATE_BULLETINA,
|
||||
ERROR_UNEXPECTED
|
||||
};
|
||||
|
||||
} // namespace mcc::ccte::erfa
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::ccte::erfa::MccCCTE_ERFAErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
|
||||
namespace mcc::ccte::erfa
|
||||
{
|
||||
|
||||
/* error category definition */
|
||||
|
||||
// error category
|
||||
struct MccCCTE_ERFACategory : public std::error_category {
|
||||
MccCCTE_ERFACategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "CCTE-ERFA";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccCCTE_ERFAErrorCode err = static_cast<MccCCTE_ERFAErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccCCTE_ERFAErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_NULLPTR:
|
||||
return "input argument is the nullptr";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_INVALID_INPUT_ARG:
|
||||
return "invalid argument";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_YEAR:
|
||||
return "invalid year number";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_MONTH:
|
||||
return "invalid month number";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_DAY:
|
||||
return "invalid day number";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR:
|
||||
return "unsupported coordinate pair";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE:
|
||||
return "time point is out of range";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE:
|
||||
return "time point is out of range";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR:
|
||||
return "dubious year";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE:
|
||||
return "unacceptable date";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS:
|
||||
return "leap seconds update error";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA:
|
||||
return "bulletin A update error";
|
||||
case MccCCTE_ERFAErrorCode::ERROR_UNEXPECTED:
|
||||
return "unexpected error value";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccCCTE_ERFACategory& get()
|
||||
{
|
||||
static const MccCCTE_ERFACategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline mcc::impl::MccError make_error_code(MccCCTE_ERFAErrorCode ec)
|
||||
{
|
||||
static_assert(std::same_as<mcc::impl::MccError, std::error_code>,
|
||||
"MccError type must be an alias of std::error_code");
|
||||
|
||||
return mcc::impl::MccError(static_cast<int>(ec), MccCCTE_ERFACategory::get());
|
||||
}
|
||||
|
||||
|
||||
class MccCCTE_ERFA : public mcc_ccte_engine_interface_t<mcc::impl::MccError>
|
||||
{
|
||||
static constexpr double PI_2 = std::numbers::pi / 2.0;
|
||||
|
||||
public:
|
||||
static constexpr double DEFAULT_WAVELENGTH = 0.55; // default observed wavelength in mkm
|
||||
|
||||
typedef mcc::impl::MccError error_t;
|
||||
|
||||
static constexpr std::string_view ccteName = "ERFA-CCTE-ENGINE";
|
||||
|
||||
struct refract_model_t {
|
||||
static constexpr std::string_view name()
|
||||
{
|
||||
return "ERFA";
|
||||
}
|
||||
|
||||
double refa, refb;
|
||||
};
|
||||
|
||||
|
||||
// meteo parameters (to compute refraction)
|
||||
struct meteo_t {
|
||||
typedef double temp_t;
|
||||
typedef double humid_t;
|
||||
typedef double press_t;
|
||||
|
||||
temp_t temperature; // Temperature in C
|
||||
humid_t humidity; // humidity in % ([0.0, 1.0])
|
||||
press_t pressure; // atmospheric presure in hPa=mB
|
||||
};
|
||||
|
||||
// celestial object addition parameters
|
||||
struct obj_pars_t {
|
||||
double pm_RA = 0.0; // rads/year
|
||||
double pm_DEC = 0.0; // rads/year
|
||||
double parallax; // in arcsecs
|
||||
double radvel; // radial velocity (signed, km/s)
|
||||
};
|
||||
|
||||
struct engine_state_t {
|
||||
meteo_t meteo{.temperature = 0.0, .humidity = 0.5, .pressure = 1010.0};
|
||||
|
||||
double wavelength = DEFAULT_WAVELENGTH; // observed wavelength in mkm
|
||||
|
||||
double lat = 0.0; // site latitude
|
||||
double lon = 0.0; // site longitude
|
||||
double elev = 0.0; // site elevation (in meters)
|
||||
|
||||
mcc::ccte::iers::MccLeapSeconds _leapSeconds{};
|
||||
mcc::ccte::iers::MccIersBulletinA _bulletinA{};
|
||||
};
|
||||
|
||||
MccCCTE_ERFA() {}
|
||||
|
||||
MccCCTE_ERFA(engine_state_t state) : MccCCTE_ERFA()
|
||||
{
|
||||
_currentState = std::move(state);
|
||||
}
|
||||
|
||||
MccCCTE_ERFA(const MccCCTE_ERFA&) = delete;
|
||||
MccCCTE_ERFA& operator=(const MccCCTE_ERFA&) = delete;
|
||||
|
||||
MccCCTE_ERFA(MccCCTE_ERFA&&) = default;
|
||||
MccCCTE_ERFA& operator=(MccCCTE_ERFA&&) = default;
|
||||
|
||||
virtual ~MccCCTE_ERFA() = default;
|
||||
|
||||
|
||||
// engine state related methods
|
||||
|
||||
void setStateERFA(engine_state_t state)
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
_currentState = std::move(state);
|
||||
|
||||
// update refraction model coefficients
|
||||
eraRefco(_currentState.meteo.pressure, _currentState.meteo.temperature, _currentState.meteo.humidity,
|
||||
_currentState.wavelength, &_currentRefractModel.refa, &_currentRefractModel.refb);
|
||||
}
|
||||
|
||||
engine_state_t getStateERFA() const
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
return _currentState;
|
||||
}
|
||||
|
||||
void updateMeteoERFA(meteo_t meteo)
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
_currentState.meteo = std::move(meteo);
|
||||
|
||||
// update refraction model coefficients
|
||||
eraRefco(_currentState.meteo.pressure, _currentState.meteo.temperature, _currentState.meteo.humidity,
|
||||
_currentState.wavelength, &_currentRefractModel.refa, &_currentRefractModel.refb);
|
||||
}
|
||||
|
||||
error_t updateLeapSeconds(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '#')
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
if (!_currentState._leapSeconds.load(stream, comment_sym)) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS;
|
||||
}
|
||||
|
||||
return MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
error_t updateLeapSeconds(traits::mcc_input_char_range auto const& filename, char comment_sym = '#')
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
if (!_currentState._leapSeconds.load(filename, comment_sym)) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS;
|
||||
}
|
||||
|
||||
return MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
error_t updateBulletinA(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '*')
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
if (!_currentState._bulletinA.load(stream, comment_sym)) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA;
|
||||
}
|
||||
|
||||
return MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
error_t updateBulletinA(traits::mcc_input_char_range auto const& filename, char comment_sym = '*')
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
if (!_currentState._bulletinA.load(filename, comment_sym)) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA;
|
||||
}
|
||||
|
||||
return MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
// longitude and latitude
|
||||
template <mcc_angle_c LON_T, mcc_angle_c LAT_T>
|
||||
void geoPosition(std::pair<LON_T, LAT_T>* coords) const
|
||||
{
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
if (coords) {
|
||||
coords->first = _currentState.lon;
|
||||
coords->second = _currentState.lat;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// apparent sideral time (Greenwitch or local)
|
||||
error_t apparentSideralTime(mcc_coord_epoch_c auto const& epoch, mcc_angle_c auto* st, bool islocal = false)
|
||||
{
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
if (st == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
using real_days_t = std::chrono::duration<double, std::ratio<86400>>;
|
||||
|
||||
double ut1 = epoch.MJD();
|
||||
double tt = epoch.MJD();
|
||||
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
auto dut1 = _currentState._bulletinA.DUT1(epoch.MJD());
|
||||
|
||||
if (dut1.has_value()) {
|
||||
ut1 += std::chrono::duration_cast<real_days_t>(dut1.value()).count();
|
||||
} else { // out of range
|
||||
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
auto tai_utc = _currentState._leapSeconds[epoch.MJD()];
|
||||
if (tai_utc.has_value()) {
|
||||
tt += std::chrono::duration_cast<real_days_t>(tai_utc.value()).count();
|
||||
} else {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
|
||||
auto tt_tai = _currentState._bulletinA.TT_TAI();
|
||||
tt += std::chrono::duration_cast<real_days_t>(tt_tai).count();
|
||||
|
||||
*st = eraGst06a(ERFA_DJM0, ut1, ERFA_DJM0, tt);
|
||||
|
||||
if (islocal) {
|
||||
*st = eraAnp(*st + _currentState.lon);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// ICRS to observed
|
||||
// returned azimuth is counted from the South through the West
|
||||
error_t icrsToObs(mcc_angle_c auto const& ra_icrs,
|
||||
mcc_angle_c auto const& dec_icrs,
|
||||
mcc_coord_epoch_c auto const& epoch,
|
||||
mcc_angle_c auto* ra_obs,
|
||||
mcc_angle_c auto* dec_obs,
|
||||
mcc_angle_c auto* ha_obs,
|
||||
mcc_angle_c auto* az,
|
||||
mcc_angle_c auto* zd,
|
||||
obj_pars_t* obj_params = nullptr)
|
||||
{
|
||||
return icrsTo(true, ra_icrs, dec_icrs, epoch, ra_obs, dec_obs, ha_obs, az, zd, obj_params);
|
||||
}
|
||||
|
||||
// error_t icrsToObs(MccSkyRADEC_ICRS const& radec_icrs,
|
||||
// MccSkyRADEC_OBS* radec_obs,
|
||||
// MccSkyAZZD* azzd,
|
||||
// mcc_angle_c auto* ha_obs,
|
||||
// obj_pars_t* obj_params = nullptr)
|
||||
// {
|
||||
// double ra_obs, dec_obs, az, zd, ha;
|
||||
|
||||
// auto err =
|
||||
// icrsToObs(radec_icrs.x(), radec_icrs.y(), radec_icrs.epoch(), &ra_obs, &dec_obs, &ha, &az, &zd,
|
||||
// obj_params);
|
||||
|
||||
// if (!err) {
|
||||
// if (radec_obs) {
|
||||
// radec_obs->setX(ra_obs);
|
||||
// radec_obs->setY(dec_obs);
|
||||
// }
|
||||
|
||||
// if (azzd) {
|
||||
// azzd->setEpoch(radec_obs->epoch());
|
||||
// azzd->setX(az);
|
||||
// azzd->setY(zd);
|
||||
// }
|
||||
|
||||
// if (ha_obs) {
|
||||
// *ha_obs = ha;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return err;
|
||||
// };
|
||||
|
||||
|
||||
// ICRS to apparent (in vacuo)
|
||||
// returned azimuth is counted from the South through the West
|
||||
error_t icrsToApp(mcc_angle_c auto const& ra_icrs,
|
||||
mcc_angle_c auto const& dec_icrs,
|
||||
mcc_coord_epoch_c auto const& epoch,
|
||||
mcc_angle_c auto* ra_app,
|
||||
mcc_angle_c auto* dec_app,
|
||||
mcc_angle_c auto* ha_app,
|
||||
mcc_angle_c auto* az,
|
||||
mcc_angle_c auto* zd, // should be interpretated as zenithal distance corrected for refraction
|
||||
obj_pars_t* obj_params = nullptr)
|
||||
{
|
||||
return icrsTo(false, ra_icrs, dec_icrs, epoch, ra_app, dec_app, ha_app, az, zd, obj_params);
|
||||
}
|
||||
|
||||
|
||||
// error_t icrsToApp(MccSkyRADEC_ICRS const& radec_icrs,
|
||||
// MccSkyRADEC_OBS* radec_app,
|
||||
// MccSkyAZZD* azzd,
|
||||
// mcc_angle_c auto* ha_app,
|
||||
// obj_pars_t* obj_params = nullptr)
|
||||
// {
|
||||
// double ra_app, dec_app, az, zd, ha;
|
||||
|
||||
// auto err =
|
||||
// icrsToApp(radec_icrs.x(), radec_icrs.y(), radec_icrs.epoch(), &ra_app, &dec_app, &ha, &az, &zd,
|
||||
// obj_params);
|
||||
|
||||
// if (!err) {
|
||||
// if (radec_app) {
|
||||
// radec_app->setX(ra_app);
|
||||
// radec_app->setY(dec_app);
|
||||
// }
|
||||
|
||||
// if (azzd) {
|
||||
// azzd->setEpoch(radec_app->epoch());
|
||||
// azzd->setX(az);
|
||||
// azzd->setY(zd);
|
||||
// }
|
||||
|
||||
// if (ha_app) {
|
||||
// *ha_app = ha;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return err;
|
||||
// }
|
||||
|
||||
|
||||
error_t obsToICRS(impl::MccCoordPairKind obs_type,
|
||||
mcc_coord_epoch_c auto const& epoch,
|
||||
mcc_angle_c auto const& co_lon,
|
||||
mcc_angle_c auto const& co_lat,
|
||||
mcc_angle_c auto* ra_icrs,
|
||||
mcc_angle_c auto* dec_icrs)
|
||||
{
|
||||
return toICRS(true, obs_type, epoch, co_lon, co_lat, ra_icrs, dec_icrs);
|
||||
}
|
||||
|
||||
|
||||
// error_t obsToICRS(mcc_coord_pair_c auto const& xy_obs, MccSkyRADEC_ICRS* radec_icrs)
|
||||
// {
|
||||
// double ra, dec;
|
||||
|
||||
// auto err = obsToICRS(xy_obs.pair_kind, xy_obs.epoch(), xy_obs.x(), xy_obs.y(), &ra, &dec);
|
||||
// if (err) {
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// if (radec_icrs) {
|
||||
// radec_icrs->setX(ra);
|
||||
// radec_icrs->setY(dec);
|
||||
// }
|
||||
|
||||
// return err;
|
||||
// }
|
||||
|
||||
|
||||
error_t appToICRS(impl::MccCoordPairKind app_type,
|
||||
mcc_coord_epoch_c auto const& epoch,
|
||||
mcc_angle_c auto const& co_lon,
|
||||
mcc_angle_c auto const& co_lat,
|
||||
mcc_angle_c auto* ra_icrs,
|
||||
mcc_angle_c auto* dec_icrs)
|
||||
{
|
||||
return toICRS(false, app_type, epoch, co_lon, co_lat, ra_icrs, dec_icrs);
|
||||
}
|
||||
|
||||
|
||||
// error_t appToICRS(mcc_coord_pair_c auto const& xy_app, MccSkyRADEC_ICRS* radec_icrs)
|
||||
// {
|
||||
// double ra, dec;
|
||||
|
||||
// auto err = appToICRS(xy_app.pair_kind, xy_app.epoch(), xy_app.x(), xy_app.y(), &ra, &dec);
|
||||
// if (!err) {
|
||||
// if (radec_icrs) {
|
||||
// radec_icrs->setX(ra);
|
||||
// radec_icrs->setY(dec);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return err;
|
||||
// }
|
||||
|
||||
|
||||
error_t equationOrigins(mcc_coord_epoch_c auto const& epoch, mcc_angle_c auto* eo)
|
||||
{
|
||||
if (eo == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
using real_days_t = std::chrono::duration<double, std::ratio<86400>>;
|
||||
|
||||
double mjd = epoch.MJD();
|
||||
|
||||
auto tai_utc = _currentState._leapSeconds[mjd];
|
||||
if (tai_utc.has_value()) {
|
||||
double tt = mjd;
|
||||
tt += std::chrono::duration_cast<real_days_t>(tai_utc.value()).count();
|
||||
|
||||
auto tt_tai = _currentState._bulletinA.TT_TAI();
|
||||
tt += +std::chrono::duration_cast<real_days_t>(tt_tai).count();
|
||||
|
||||
*eo = eraEo06a(ERFA_DJM0, tt);
|
||||
} else {
|
||||
ret = MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// refraction
|
||||
|
||||
error_t refractionModel(refract_model_t* model)
|
||||
{
|
||||
if (model == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
// eraRefco(_currentState.meteo.pressure, _currentState.meteo.temperature, _currentState.meteo.humidity,
|
||||
// _currentState.wavelength, &model->refa, &model->refb);
|
||||
|
||||
*model = _currentRefractModel;
|
||||
|
||||
return MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
// Zobs must be observed zenithal distance (Zapp = Zobs + dZ -- corrected (in vacuo) zenithal distance)
|
||||
template <typename ZAPP_T = std::nullptr_t>
|
||||
error_t refractionCorrection(mcc_angle_c auto Zobs, mcc_angle_c auto* dZ, ZAPP_T Zapp = nullptr)
|
||||
requires(std::is_null_pointer_v<ZAPP_T> ||
|
||||
(std::is_pointer_v<ZAPP_T> && mcc_angle_c<std::remove_pointer_t<ZAPP_T>>))
|
||||
{
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
if (dZ == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
// refract_model_t rmodel;
|
||||
// ret = refractionModel(&rmodel);
|
||||
|
||||
// if (!ret) {
|
||||
// ret = refractionCorrection(rmodel, Zobs, dZ, Zapp);
|
||||
// }
|
||||
|
||||
{
|
||||
std::lock_guard lock(*_stateMutex);
|
||||
|
||||
ret = refractionCorrection(_currentRefractModel, Zobs, dZ, Zapp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Zobs must be observed zenithal distance (Zapp = Zobs + dZ -- corrected (in vacuo) zenithal distance)
|
||||
template <typename ZAPP_T = std::nullptr_t>
|
||||
error_t refractionCorrection(const refract_model_t& rmodel,
|
||||
mcc_angle_c auto Zobs,
|
||||
mcc_angle_c auto* dZ,
|
||||
ZAPP_T Zapp = nullptr)
|
||||
requires(std::is_null_pointer_v<ZAPP_T> ||
|
||||
(std::is_pointer_v<ZAPP_T> && mcc_angle_c<std::remove_pointer_t<ZAPP_T>>))
|
||||
{
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
if (dZ == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
|
||||
if (Zobs >= std::numbers::pi / 2.0) {
|
||||
*dZ = 35.4 / 60.0 * std::numbers::pi / 180.0; // 35.4 arcminutes
|
||||
} else {
|
||||
auto tanZ = tan(Zobs);
|
||||
*dZ = rmodel.refa * tanZ + rmodel.refb * tanZ * tanZ * tanZ;
|
||||
}
|
||||
|
||||
if constexpr (!std::is_null_pointer_v<ZAPP_T>) {
|
||||
*Zapp = Zobs + *dZ;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Zapp must be topocentric (in vacuo) zenithal distance (Zobs = Zapp - dZ -- observed, i.e. affected by refraction,
|
||||
// zenithal distance)
|
||||
template <typename ZOBS_T = std::nullptr_t>
|
||||
error_t refractionInverseCorrection(mcc_angle_c auto Zapp, mcc_angle_c auto* dZ, ZOBS_T Zobs = nullptr)
|
||||
requires(std::is_null_pointer_v<ZOBS_T> ||
|
||||
(std::is_pointer_v<ZOBS_T> && mcc_angle_c<std::remove_pointer_t<ZOBS_T>>))
|
||||
{
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
if (dZ == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
// refract_model_t rmodel;
|
||||
// ret = refractionModel(&rmodel);
|
||||
|
||||
// if (!ret) {
|
||||
// ret = refractionInverseCorrection(rmodel, Zapp, dZ, Zobs);
|
||||
// }
|
||||
|
||||
{
|
||||
std::lock_guard lock(*_stateMutex);
|
||||
|
||||
ret = refractionInverseCorrection(_currentRefractModel, Zapp, dZ, Zobs);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Zapp must be topocentric (in vacuo) zenithal distance (Zobs = Zapp - dZ -- observed, i.e. affected by refraction,
|
||||
// zenithal distance)
|
||||
template <typename ZOBS_T = std::nullptr_t>
|
||||
error_t refractionInverseCorrection(const refract_model_t& rmodel,
|
||||
mcc_angle_c auto Zapp,
|
||||
mcc_angle_c auto* dZ,
|
||||
ZOBS_T Zobs = nullptr)
|
||||
requires(std::is_null_pointer_v<ZOBS_T> ||
|
||||
(std::is_pointer_v<ZOBS_T> && mcc_angle_c<std::remove_pointer_t<ZOBS_T>>))
|
||||
{
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
if (dZ == nullptr) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
|
||||
if (Zapp >= std::numbers::pi / 2.0) {
|
||||
*dZ = 35.4 / 60.0 * std::numbers::pi / 180.0; // 35.4 arcminutes
|
||||
} else {
|
||||
auto tanZ = tan(Zapp);
|
||||
auto tanZ2 = tanZ * tanZ;
|
||||
auto b3 = 3.0 * rmodel.refb;
|
||||
|
||||
// with Newton-Raphson correction
|
||||
*dZ = (rmodel.refa * tanZ + rmodel.refb * tanZ * tanZ2) /
|
||||
(1.0 + rmodel.refa + tanZ2 * (rmodel.refa + b3) + b3 * tanZ2 * tanZ2);
|
||||
}
|
||||
|
||||
if constexpr (!std::is_null_pointer_v<ZOBS_T>) {
|
||||
*Zobs = Zapp - *dZ;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* helper mathods */
|
||||
|
||||
auto leapSecondsExpireDate() const
|
||||
{
|
||||
return _currentState._leapSeconds.expireDate();
|
||||
}
|
||||
|
||||
auto leapSecondsExpireMJD() const
|
||||
{
|
||||
return _currentState._leapSeconds.expireMJD();
|
||||
}
|
||||
|
||||
|
||||
auto bulletinADateRange() const
|
||||
{
|
||||
return _currentState._bulletinA.dateRange();
|
||||
}
|
||||
|
||||
auto bulletinADateRangeMJD() const
|
||||
{
|
||||
return _currentState._bulletinA.dateRangeMJD();
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
engine_state_t _currentState{};
|
||||
|
||||
refract_model_t _currentRefractModel{};
|
||||
|
||||
std::unique_ptr<std::mutex> _stateMutex{new std::mutex()};
|
||||
|
||||
|
||||
error_t icrsTo(bool observed, // true - observed, false - apparent
|
||||
mcc_angle_c auto const& ra_icrs,
|
||||
mcc_angle_c auto const& dec_icrs,
|
||||
mcc_coord_epoch_c auto const& epoch,
|
||||
mcc_angle_c auto* ra,
|
||||
mcc_angle_c auto* dec,
|
||||
mcc_angle_c auto* ha,
|
||||
mcc_angle_c auto* az,
|
||||
mcc_angle_c auto* zd,
|
||||
obj_pars_t* obj_params = nullptr)
|
||||
{
|
||||
int err;
|
||||
double r, d, h, a, z, eo;
|
||||
double pressure = 0.0; // 0 for apparent coordinates type (see ERFA's refco.c: if pressure is zero then
|
||||
// refraction is also zero)
|
||||
|
||||
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
if (observed) {
|
||||
pressure = _currentState.meteo.pressure;
|
||||
}
|
||||
|
||||
auto dut1 = _currentState._bulletinA.DUT1(epoch.MJD());
|
||||
|
||||
if (!dut1.has_value()) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
auto pol_pos = _currentState._bulletinA.polarCoords(epoch.MJD());
|
||||
if (!pol_pos.has_value()) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
// const auto arcsec2rad = std::numbers::pi / 180 / 3600;
|
||||
const auto arcsec2rad = 1.0_arcsecs;
|
||||
pol_pos->x *= arcsec2rad;
|
||||
pol_pos->y *= arcsec2rad;
|
||||
|
||||
if (obj_params) {
|
||||
err = eraAtco13(ra_icrs, dec_icrs, obj_params->pm_RA, obj_params->pm_DEC, obj_params->parallax,
|
||||
obj_params->radvel, ERFA_DJM0, epoch.MJD(), dut1->count(), _currentState.lon,
|
||||
_currentState.lat, _currentState.elev, pol_pos->x, pol_pos->y, pressure,
|
||||
_currentState.meteo.temperature, _currentState.meteo.humidity, _currentState.wavelength, &a,
|
||||
&z, &h, &d, &r, &eo);
|
||||
} else {
|
||||
err = eraAtco13(ra_icrs, dec_icrs, 0.0, 0.0, 0.0, 0.0, ERFA_DJM0, epoch.MJD(), dut1->count(),
|
||||
_currentState.lon, _currentState.lat, _currentState.elev, pol_pos->x, pol_pos->y, pressure,
|
||||
_currentState.meteo.temperature, _currentState.meteo.humidity, _currentState.wavelength, &a,
|
||||
&z, &h, &d, &r, &eo);
|
||||
}
|
||||
|
||||
if (err == 1) {
|
||||
ret = MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR;
|
||||
} else if (err == -1) {
|
||||
ret = MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE;
|
||||
}
|
||||
|
||||
|
||||
if (ra) {
|
||||
*ra = r;
|
||||
}
|
||||
|
||||
if (dec) {
|
||||
*dec = d;
|
||||
}
|
||||
|
||||
if (ha) {
|
||||
*ha = h;
|
||||
}
|
||||
|
||||
if (az) {
|
||||
// NOTE: according to definition of astronomical azimuth it is counted from the South through the West, but
|
||||
// in the ERFA the azimuth is counted from the North through the East!!!
|
||||
//
|
||||
// *az = impl::MccAngle(a - std::numbers::pi).normalize<impl::MccAngle::NORM_KIND_0_360>();
|
||||
*az = impl::MccAngle(a + std::numbers::pi).normalize<impl::MccAngle::NORM_KIND_0_360>();
|
||||
}
|
||||
|
||||
if (zd) {
|
||||
*zd = z;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
error_t toICRS(bool observed, // true - observed, false - apparent
|
||||
impl::MccCoordPairKind pair_type,
|
||||
mcc_coord_epoch_c auto const& epoch,
|
||||
mcc_angle_c auto const& co_lon,
|
||||
mcc_angle_c auto const& co_lat,
|
||||
mcc_angle_c auto* ra_icrs,
|
||||
mcc_angle_c auto* dec_icrs)
|
||||
{
|
||||
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
|
||||
|
||||
// check coordinate pair consistency
|
||||
if (mcc_is_app_coordpair(pair_type) && observed) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
|
||||
}
|
||||
if (mcc_is_obs_coordpair(pair_type) && !observed) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
|
||||
}
|
||||
|
||||
std::lock_guard lock{*_stateMutex};
|
||||
|
||||
auto dut1 = _currentState._bulletinA.DUT1(epoch.MJD());
|
||||
|
||||
if (!dut1.has_value()) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
auto pol_pos = _currentState._bulletinA.polarCoords(epoch.MJD());
|
||||
if (!pol_pos.has_value()) {
|
||||
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
// const auto arcsec2rad = std::numbers::pi / 180 / 3600;
|
||||
const auto arcsec2rad = 1.0_arcsecs;
|
||||
pol_pos->x *= arcsec2rad;
|
||||
pol_pos->y *= arcsec2rad;
|
||||
|
||||
std::string type;
|
||||
double x, y, ra, dec;
|
||||
double pressure = 0.0;
|
||||
|
||||
if (observed) {
|
||||
pressure = _currentState.meteo.pressure;
|
||||
}
|
||||
|
||||
switch (pair_type) {
|
||||
case impl::MccCoordPairKind::COORDS_KIND_AZZD:
|
||||
// NOTE: according to definition of astronomical azimuth it is counted from the South through the West,
|
||||
// but in the ERFA the azimuth is counted from the North through the East!!!
|
||||
//
|
||||
x = co_lon + std::numbers::pi;
|
||||
y = co_lat;
|
||||
type = "A";
|
||||
break;
|
||||
case impl::MccCoordPairKind::COORDS_KIND_AZALT:
|
||||
// NOTE: according to definition of astronomical azimuth it is counted from the South through the West,
|
||||
// but in the ERFA the azimuth is counted from the North through the East!!!
|
||||
//
|
||||
x = co_lon + std::numbers::pi;
|
||||
y = MccCCTE_ERFA::PI_2 - co_lat; // altitude to zenithal distance
|
||||
type = "A";
|
||||
break;
|
||||
case impl::MccCoordPairKind::COORDS_KIND_HADEC_OBS:
|
||||
type = "H";
|
||||
x = co_lon;
|
||||
y = co_lat;
|
||||
break;
|
||||
case impl::MccCoordPairKind::COORDS_KIND_RADEC_OBS:
|
||||
type = "R";
|
||||
x = co_lon;
|
||||
y = co_lat;
|
||||
break;
|
||||
case impl::MccCoordPairKind::COORDS_KIND_HADEC_APP:
|
||||
type = "H";
|
||||
x = co_lon;
|
||||
y = co_lat;
|
||||
break;
|
||||
case impl::MccCoordPairKind::COORDS_KIND_RADEC_APP:
|
||||
type = "R";
|
||||
x = co_lon;
|
||||
y = co_lat;
|
||||
break;
|
||||
default:
|
||||
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
|
||||
};
|
||||
|
||||
int err =
|
||||
eraAtoc13(type.c_str(), x, y, ERFA_DJM0, epoch.MJD(), dut1->count(), _currentState.lon, _currentState.lat,
|
||||
_currentState.elev, pol_pos->x, pol_pos->y, pressure, _currentState.meteo.temperature,
|
||||
_currentState.meteo.humidity, _currentState.wavelength, &ra, &dec);
|
||||
|
||||
if (err == 1) {
|
||||
ret = MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR;
|
||||
} else if (err == -1) {
|
||||
ret = MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE;
|
||||
}
|
||||
|
||||
if (ra) {
|
||||
*ra_icrs = ra;
|
||||
}
|
||||
|
||||
if (dec) {
|
||||
*dec_icrs = dec;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static_assert(mcc_ccte_c<MccCCTE_ERFA>, "");
|
||||
|
||||
} // namespace mcc::ccte::erfa
|
||||
501
include/mcc/mcc_ccte_iers.h
Normal file
501
include/mcc/mcc_ccte_iers.h
Normal file
@@ -0,0 +1,501 @@
|
||||
#pragma once
|
||||
|
||||
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
||||
|
||||
|
||||
/* Classes to represent IERS bulletins
|
||||
*
|
||||
* BULLETIN A: https://datacenter.iers.org/data/latestVersion/bulletinA.txt
|
||||
* leapseconds: https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat
|
||||
*
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
|
||||
#include "mcc_ccte_iers_default.h"
|
||||
#include "mcc_traits.h"
|
||||
#include "mcc_utils.h"
|
||||
|
||||
namespace mcc::ccte::iers
|
||||
{
|
||||
|
||||
class MccLeapSeconds final
|
||||
{
|
||||
public:
|
||||
typedef std::chrono::system_clock::time_point time_point_t;
|
||||
typedef std::chrono::duration<double> real_secs_t; // seconds duration in double
|
||||
|
||||
MccLeapSeconds()
|
||||
{
|
||||
// create default values
|
||||
std::istringstream ist(defaults::MCC_DEFAULT_LEAP_SECONDS_FILE);
|
||||
|
||||
load(ist);
|
||||
}
|
||||
|
||||
~MccLeapSeconds() = default;
|
||||
|
||||
|
||||
time_point_t expireDate() const
|
||||
{
|
||||
return _expireDate;
|
||||
}
|
||||
|
||||
auto expireMJD() const
|
||||
{
|
||||
return _expireMJD;
|
||||
}
|
||||
|
||||
// load from stream
|
||||
bool load(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '#')
|
||||
{
|
||||
std::istringstream is;
|
||||
double mjd;
|
||||
unsigned day, month;
|
||||
int year;
|
||||
double tai_utc;
|
||||
|
||||
decltype(_expireDate) edate;
|
||||
std::vector<leapsecond_db_elem_t> db;
|
||||
|
||||
for (std::string line; std::getline(stream, line);) {
|
||||
auto sv = utils::trimSpaces(line, utils::TrimType::TRIM_LEFT);
|
||||
|
||||
if (sv.size()) {
|
||||
if (sv[0] == comment_sym) { // comment string
|
||||
if (std::regex_match(line, expr_date_rx)) {
|
||||
auto pos = line.find("on");
|
||||
sv = utils::trimSpaces(std::string_view{line.begin() + pos + 2, line.end()},
|
||||
utils::TrimType::TRIM_LEFT);
|
||||
is.str({sv.begin(), sv.end()});
|
||||
is >> std::chrono::parse("%d %B %Y", edate);
|
||||
is.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::regex_match(line, data_rx)) {
|
||||
is.str(line);
|
||||
is >> mjd >> day >> month >> year >> tai_utc;
|
||||
db.emplace_back(mjd, std::chrono::year_month_day{std::chrono::year{year} / month / day}, tai_utc);
|
||||
// db.emplace_back(mjd,
|
||||
// std::chrono::year_month_day{std::chrono::year{year}, std::chrono::month{month},
|
||||
// std::chrono::day{day}},
|
||||
// tai_utc);
|
||||
is.clear();
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (db.empty()) { // keep previous data
|
||||
return false;
|
||||
}
|
||||
|
||||
_expireDate = std::move(edate);
|
||||
|
||||
// compute expire Julian Day
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
std::chrono::year_month_day ymd{std::chrono::floor<std::chrono::days>(_expireDate)};
|
||||
|
||||
static constexpr std::chrono::year MIN_YEAR = -4799y;
|
||||
|
||||
if (ymd.year() < MIN_YEAR) {
|
||||
return -1;
|
||||
}
|
||||
if (!ymd.month().ok()) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
int64_t im = (unsigned)ymd.month();
|
||||
int64_t id = (unsigned)ymd.day();
|
||||
int64_t iy = (int)ymd.year();
|
||||
|
||||
int64_t my = (im - 14LL) / 12LL;
|
||||
int64_t iypmy = iy + my;
|
||||
|
||||
// integer part of result MJD
|
||||
int64_t mjd_int = (1461LL * (iypmy + 4800LL)) / 4LL + (367LL * (im - 2LL - 12LL * my)) / 12LL -
|
||||
(3LL * ((iypmy + 4900LL) / 100LL)) / 4LL + id - 2432076LL;
|
||||
|
||||
_expireMJD = static_cast<double>(mjd_int);
|
||||
|
||||
|
||||
_db = std::move(db);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool load(traits::mcc_input_char_range auto const& filename, char comment_sym = '#')
|
||||
{
|
||||
std::ifstream fst;
|
||||
if constexpr (std::same_as<std::remove_cvref_t<decltype(filename)>, std::string>) {
|
||||
fst.open(filename);
|
||||
} else {
|
||||
fst.open(std::string{filename.begin(), filename.end()});
|
||||
}
|
||||
|
||||
bool ok = fst.is_open();
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = load(fst, comment_sym);
|
||||
|
||||
fst.close();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
// std::optional<double> operator[](const time_point_t& tp) const
|
||||
std::optional<real_secs_t> operator[](const time_point_t& tp) const
|
||||
{
|
||||
if (tp > _expireDate) { // ???????!!!!!!!!!!!
|
||||
return std::nullopt;
|
||||
// return _db.back().tai_utc;
|
||||
}
|
||||
|
||||
std::chrono::year_month_day ymd{std::chrono::floor<std::chrono::days>(tp)};
|
||||
|
||||
for (auto const& el : _db | std::views::reverse) {
|
||||
if (ymd >= el.ymd) {
|
||||
// return el.tai_utc;
|
||||
return real_secs_t{el.tai_utc};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// std::optional<double> operator[](const double& mjd) const
|
||||
std::optional<real_secs_t> operator[](const double& mjd) const
|
||||
{
|
||||
double e_mjd;
|
||||
|
||||
if (mjd > _expireMJD) { // ???????!!!!!!!!!!!
|
||||
return std::nullopt;
|
||||
// return _db.back().tai_utc;
|
||||
}
|
||||
|
||||
for (auto const& el : _db | std::views::reverse) {
|
||||
if (mjd >= el.mjd) {
|
||||
return real_secs_t{el.tai_utc};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
void dump(std::derived_from<std::basic_ostream<char>> auto& stream) const
|
||||
{
|
||||
stream << std::format("Leap seconds database expire date: {}", _expireDate) << '\n';
|
||||
for (auto const& el : _db) {
|
||||
stream << std::format("{} {} {}", el.mjd, el.ymd, el.tai_utc) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
inline static const std::regex expr_date_rx{
|
||||
"^ *# *File +expires +on +[0-8]{1,2} "
|
||||
"+(January|February|March|April|May|June|July|August|September|October|November|December) +[0-9]{4} *$"};
|
||||
|
||||
inline static const std::regex data_rx{"^ *[0-9]{5,}(\\.?[0-9]+) +[0-9]{1,2} +[0-9]{1,2} +[0-9]{4} +[0-9]{1,} *$"};
|
||||
|
||||
time_point_t _expireDate{};
|
||||
double _expireMJD{};
|
||||
|
||||
struct leapsecond_db_elem_t {
|
||||
double mjd;
|
||||
std::chrono::year_month_day ymd;
|
||||
double tai_utc; // TAI-UTC in seconds
|
||||
};
|
||||
|
||||
std::vector<leapsecond_db_elem_t> _db{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
class MccIersBulletinA final
|
||||
{
|
||||
public:
|
||||
typedef std::chrono::system_clock::time_point time_point_t;
|
||||
typedef std::chrono::duration<double> real_secs_t; // seconds duration in double
|
||||
|
||||
struct pole_pos_t {
|
||||
double x, y;
|
||||
};
|
||||
|
||||
struct date_range_t {
|
||||
std::chrono::year_month_day begin;
|
||||
std::chrono::year_month_day end;
|
||||
};
|
||||
|
||||
struct date_range_mjd_t {
|
||||
double begin;
|
||||
double end;
|
||||
};
|
||||
|
||||
MccIersBulletinA()
|
||||
{
|
||||
// create pre-defined (default-state) database
|
||||
std::istringstream ist(defaults::MCC_DEFAULT_IERS_BULLETIN_A_FILE);
|
||||
|
||||
load(ist);
|
||||
}
|
||||
|
||||
~MccIersBulletinA() = default;
|
||||
|
||||
std::chrono::system_clock::time_point bulletinDate() const
|
||||
{
|
||||
return _date;
|
||||
}
|
||||
|
||||
|
||||
date_range_t dateRange() const
|
||||
{
|
||||
return {_db.front().ymd, _db.back().ymd};
|
||||
}
|
||||
|
||||
date_range_mjd_t dateRangeMJD() const
|
||||
{
|
||||
return {_db.front().mjd, _db.back().mjd};
|
||||
}
|
||||
|
||||
|
||||
// double TT_TAI() const
|
||||
real_secs_t TT_TAI() const
|
||||
{
|
||||
return real_secs_t{_tt_tai};
|
||||
}
|
||||
|
||||
// DUT1 = UT1 - UTC
|
||||
// std::optional<double> DUT1(const time_point_t& tp) const
|
||||
std::optional<real_secs_t> DUT1(const time_point_t& tp) const
|
||||
{
|
||||
// use of the closest date
|
||||
std::chrono::year_month_day ymd{std::chrono::round<std::chrono::days>(tp)};
|
||||
|
||||
if (ymd < _db.front().ymd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (ymd > _db.back().ymd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (auto const& el : _db) {
|
||||
if (ymd <= el.ymd) {
|
||||
return real_secs_t{el.dut1};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// std::optional<double> DUT1(double mjd) const
|
||||
std::optional<real_secs_t> DUT1(double mjd) const
|
||||
{
|
||||
mjd = std::round(mjd); // round to closest integer MJD
|
||||
|
||||
if (mjd < _db.front().mjd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (mjd > _db.back().mjd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (auto const& el : _db) {
|
||||
if (mjd <= el.mjd) {
|
||||
return real_secs_t{el.dut1};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<pole_pos_t> polarCoords(const time_point_t& tp) const
|
||||
{
|
||||
std::chrono::year_month_day ymd{std::chrono::round<std::chrono::days>(tp)};
|
||||
|
||||
if (ymd < _db.front().ymd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (ymd > _db.back().ymd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (auto const& el : _db) {
|
||||
if (ymd <= el.ymd) {
|
||||
return pole_pos_t{el.x, el.y};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<pole_pos_t> polarCoords(double mjd) const
|
||||
{
|
||||
mjd = std::round(mjd); // round to closest integer MJD
|
||||
|
||||
if (mjd < _db.front().mjd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (mjd > _db.back().mjd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (auto const& el : _db) {
|
||||
if (mjd <= el.mjd) {
|
||||
return pole_pos_t{el.x, el.y};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool load(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '*')
|
||||
{
|
||||
std::vector<earth_orient_db_elem_t> db;
|
||||
enum { TAB_STATE_SEEK, TAB_STATE_START };
|
||||
int tab_state = TAB_STATE_SEEK;
|
||||
|
||||
int year;
|
||||
unsigned month, day;
|
||||
double mjd, x, y, dut1;
|
||||
std::istringstream is;
|
||||
decltype(_date) bdate;
|
||||
double tt_tai;
|
||||
|
||||
for (std::string line; std::getline(stream, line);) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sv = utils::trimSpaces(line, utils::TrimType::TRIM_LEFT);
|
||||
|
||||
if (sv.size()) {
|
||||
if (sv[0] == comment_sym) { // comment string
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tab_state == TAB_STATE_START) {
|
||||
if (std::regex_match(sv.begin(), sv.end(), bull_tab_vals_rx)) {
|
||||
// is.str({sv.begin(), sv.end()});
|
||||
is.str(line);
|
||||
is >> year >> month >> day >> mjd >> x >> y >> dut1;
|
||||
db.emplace_back(mjd, std::chrono::year_month_day{std::chrono::year{year} / month / day}, x, y,
|
||||
dut1);
|
||||
is.clear();
|
||||
} else { // end of the table - just stop parsing
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::regex_match(sv.begin(), sv.end(), bull_date_rx)) {
|
||||
is.str({sv.begin(), sv.end()});
|
||||
is >> std::chrono::parse("%d %B %Y", bdate);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::regex_match(sv.begin(), sv.end(), bull_tt_tai_rx)) {
|
||||
is.str({sv.begin(), sv.end()});
|
||||
std::string dummy;
|
||||
is >> dummy >> dummy >> dummy >> dummy >> tt_tai;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::regex_match(sv.begin(), sv.end(), bull_tab_title_rx)) {
|
||||
tab_state = TAB_STATE_START;
|
||||
continue;
|
||||
}
|
||||
|
||||
} else { // empty string (only spaces)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (db.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_date = std::move(bdate);
|
||||
_tt_tai = tt_tai;
|
||||
_db = std::move(db);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load(traits::mcc_input_char_range auto const& filename, char comment_sym = '*')
|
||||
{
|
||||
std::ifstream fst;
|
||||
if constexpr (std::same_as<std::remove_cvref_t<decltype(filename)>, std::string>) {
|
||||
fst.open(filename);
|
||||
} else {
|
||||
fst.open(std::string{filename.begin(), filename.end()});
|
||||
}
|
||||
|
||||
bool ok = fst.is_open();
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = load(fst, comment_sym);
|
||||
|
||||
fst.close();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
void dump(std::derived_from<std::basic_ostream<char>> auto& stream) const
|
||||
{
|
||||
stream << std::format("Bulletin A issue date: {}", _date) << '\n';
|
||||
stream << std::format("TT-TAI: {}", _tt_tai) << '\n';
|
||||
for (auto const& el : _db) {
|
||||
stream << std::format("{} {} {:6.4f} {:6.4f} {:7.5f}", el.mjd, el.ymd, el.x, el.y, el.dut1) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
inline static const std::regex bull_date_rx{
|
||||
"^ *[0-9]{1,2} +(January|February|March|April|May|June|July|August|September|October|November|December) "
|
||||
"+[0-9]{4,} +Vol\\. +[XMLCDVI]+ +No\\. +[0-9]+ *$"};
|
||||
|
||||
inline static const std::regex bull_tt_tai_rx{"^ *TT += +TAI +\\+ +[0-9]+\\.[0-9]+ +seconds *$"};
|
||||
|
||||
inline static const std::regex bull_tab_title_rx{"^ *MJD +x\\(arcsec\\) +y\\(arcsec\\) +UT1-UTC\\(sec\\) *$"};
|
||||
|
||||
inline static const std::regex bull_tab_vals_rx{
|
||||
"^ *[0-9]{4,} +[0-9]{1,2} +[0-9]{1,2} +[0-9]{5,} +[0-9]+\\.[0-9]+ +[0-9]+\\.[0-9]+ +[0-9]+\\.[0-9]+ *$"};
|
||||
|
||||
|
||||
time_point_t _date;
|
||||
double _tt_tai;
|
||||
|
||||
struct earth_orient_db_elem_t {
|
||||
double mjd;
|
||||
std::chrono::year_month_day ymd;
|
||||
double x, y; // Polar coordinates in arcsecs
|
||||
double dut1; // UT1-UTC in seconds
|
||||
};
|
||||
|
||||
std::vector<earth_orient_db_elem_t> _db;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace mcc::ccte::iers
|
||||
647
include/mcc/mcc_ccte_iers_default.h
Normal file
647
include/mcc/mcc_ccte_iers_default.h
Normal file
@@ -0,0 +1,647 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace mcc::ccte::iers::defaults
|
||||
|
||||
{
|
||||
|
||||
// https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat
|
||||
|
||||
static std::string MCC_DEFAULT_LEAP_SECONDS_FILE = R"--(
|
||||
|
||||
# Value of TAI-UTC in second valid beetween the initial value until
|
||||
# the epoch given on the next line. The last line reads that NO
|
||||
# leap second was introduced since the corresponding date
|
||||
# Updated through IERS Bulletin 71 issued in January 2026
|
||||
#
|
||||
#
|
||||
# File expires on 28 December 2026
|
||||
#
|
||||
#
|
||||
# MJD Date TAI-UTC (s)
|
||||
# day month year
|
||||
# --- -------------- ------
|
||||
#
|
||||
41317.0 1 1 1972 10
|
||||
41499.0 1 7 1972 11
|
||||
41683.0 1 1 1973 12
|
||||
42048.0 1 1 1974 13
|
||||
42413.0 1 1 1975 14
|
||||
42778.0 1 1 1976 15
|
||||
43144.0 1 1 1977 16
|
||||
43509.0 1 1 1978 17
|
||||
43874.0 1 1 1979 18
|
||||
44239.0 1 1 1980 19
|
||||
44786.0 1 7 1981 20
|
||||
45151.0 1 7 1982 21
|
||||
45516.0 1 7 1983 22
|
||||
46247.0 1 7 1985 23
|
||||
47161.0 1 1 1988 24
|
||||
47892.0 1 1 1990 25
|
||||
48257.0 1 1 1991 26
|
||||
48804.0 1 7 1992 27
|
||||
49169.0 1 7 1993 28
|
||||
49534.0 1 7 1994 29
|
||||
50083.0 1 1 1996 30
|
||||
50630.0 1 7 1997 31
|
||||
51179.0 1 1 1999 32
|
||||
53736.0 1 1 2006 33
|
||||
54832.0 1 1 2009 34
|
||||
56109.0 1 7 2012 35
|
||||
57204.0 1 7 2015 36
|
||||
57754.0 1 1 2017 37
|
||||
)--";
|
||||
|
||||
|
||||
// https://datacenter.iers.org/data/latestVersion/bulletinA.txt
|
||||
|
||||
static std::string MCC_DEFAULT_IERS_BULLETIN_A_FILE = R"--(
|
||||
|
||||
|
||||
**********************************************************************
|
||||
* *
|
||||
* I E R S B U L L E T I N - A *
|
||||
* *
|
||||
* Rapid Service/Prediction of Earth Orientation *
|
||||
**********************************************************************
|
||||
8 January 2026 Vol. XXXIX No. 002
|
||||
______________________________________________________________________
|
||||
GENERAL INFORMATION:
|
||||
MJD = Julian Date - 2 400 000.5 days
|
||||
UT2-UT1 = 0.022 sin(2*pi*T) - 0.012 cos(2*pi*T)
|
||||
- 0.006 sin(4*pi*T) + 0.007 cos(4*pi*T)
|
||||
where pi = 3.14159265... and T is the date in Besselian years.
|
||||
TT = TAI + 32.184 seconds
|
||||
DUT1= (UT1-UTC) transmitted with time signals
|
||||
= +0.1 seconds beginning 10 July 2025 at 0000 UTC
|
||||
Beginning 1 January 2017:
|
||||
TAI-UTC = 37.000 000 seconds
|
||||
***********************************************************************
|
||||
* ANNOUNCEMENTS: *
|
||||
* *
|
||||
* There will NOT be a leap second introduced in UTC *
|
||||
* at the end of June 2026. *
|
||||
* *
|
||||
* The primary source for IERS Rapid Service/Prediction Center (RS/PC) *
|
||||
* data products is the official IERS RS/PC website: *
|
||||
* https://maia.usno.navy.mil *
|
||||
* *
|
||||
* IERS RS/PC products are also available from: *
|
||||
* NASA CDDIS: https://cddis.nasa.gov/archive/products/iers/ *
|
||||
* NASA CDDIS: ftps://gdc.cddis.eosdis.nasa.gov/products/iers/ *
|
||||
* IERS Central Bureau: https://datacenter.iers.org/eop.php *
|
||||
* *
|
||||
* Questions about IERS RS/PC products can be emailed to: *
|
||||
* eopcp@us.navy.mil *
|
||||
* *
|
||||
* Distribution statement A: *
|
||||
* Approved for public release: distribution unlimited. *
|
||||
* *
|
||||
***********************************************************************
|
||||
________________________________________________________________________
|
||||
The contributed observations used in the preparation of this Bulletin
|
||||
are available at <http://www.usno.navy.mil/USNO/earth-orientation/
|
||||
eo-info/general/input-data>. The contributed analysis results are based
|
||||
on data from Very Long Baseline Interferometry (VLBI), Satellite Laser
|
||||
Ranging (SLR), the Global Positioning System (GPS) satellites, Lunar
|
||||
Laser Ranging (LLR), and meteorological predictions of variations in
|
||||
Atmospheric Angular Momentum (AAM).
|
||||
________________________________________________________________________
|
||||
|
||||
COMBINED EARTH ORIENTATION PARAMETERS:
|
||||
|
||||
IERS Rapid Service
|
||||
MJD x error y error UT1-UTC error
|
||||
" " " " s s
|
||||
26 1 2 61042 0.10962 .00009 0.33249 .00009 0.074152 0.000020
|
||||
26 1 3 61043 0.10827 .00009 0.33355 .00009 0.074367 0.000021
|
||||
26 1 4 61044 0.10690 .00009 0.33455 .00009 0.074487 0.000020
|
||||
26 1 5 61045 0.10554 .00009 0.33551 .00009 0.074361 0.000015
|
||||
26 1 6 61046 0.10404 .00009 0.33628 .00009 0.073991 0.000015
|
||||
26 1 7 61047 0.10253 .00009 0.33692 .00009 0.073470 0.000012
|
||||
26 1 8 61048 0.10121 .00009 0.33746 .00009 0.072861 0.000008
|
||||
|
||||
IERS Final Values
|
||||
MJD x y UT1-UTC
|
||||
" " s
|
||||
25 11 2 60981 0.1750 0.3194 0.09220
|
||||
25 11 3 60982 0.1735 0.3187 0.09112
|
||||
25 11 4 60983 0.1718 0.3187 0.09005
|
||||
25 11 5 60984 0.1699 0.3183 0.08916
|
||||
25 11 6 60985 0.1679 0.3182 0.08854
|
||||
25 11 7 60986 0.1659 0.3179 0.08822
|
||||
25 11 8 60987 0.1640 0.3171 0.08814
|
||||
25 11 9 60988 0.1630 0.3163 0.08819
|
||||
25 11 10 60989 0.1625 0.3158 0.08820
|
||||
25 11 11 60990 0.1615 0.3153 0.08801
|
||||
25 11 12 60991 0.1602 0.3151 0.08763
|
||||
25 11 13 60992 0.1583 0.3154 0.08711
|
||||
25 11 14 60993 0.1564 0.3157 0.08644
|
||||
25 11 15 60994 0.1545 0.3160 0.08566
|
||||
25 11 16 60995 0.1530 0.3163 0.08494
|
||||
25 11 17 60996 0.1511 0.3167 0.08433
|
||||
25 11 18 60997 0.1491 0.3165 0.08390
|
||||
25 11 19 60998 0.1474 0.3163 0.08366
|
||||
25 11 20 60999 0.1456 0.3161 0.08357
|
||||
25 11 21 61000 0.1437 0.3157 0.08363
|
||||
25 11 22 61001 0.1419 0.3149 0.08381
|
||||
25 11 23 61002 0.1398 0.3143 0.08403
|
||||
25 11 24 61003 0.1380 0.3136 0.08426
|
||||
25 11 25 61004 0.1373 0.3137 0.08431
|
||||
25 11 26 61005 0.1360 0.3146 0.08417
|
||||
25 11 27 61006 0.1351 0.3149 0.08377
|
||||
25 11 28 61007 0.1343 0.3153 0.08305
|
||||
25 11 29 61008 0.1334 0.3153 0.08209
|
||||
25 11 30 61009 0.1325 0.3157 0.08097
|
||||
25 12 1 61010 0.1316 0.3160 0.07984
|
||||
|
||||
_______________________________________________________________________
|
||||
|
||||
PREDICTIONS:
|
||||
The following formulas will not reproduce the predictions given below,
|
||||
but may be used to extend the predictions beyond the end of this table.
|
||||
|
||||
x = 0.1611 - 0.0626 cos A - 0.1221 sin A + 0.0025 cos C + 0.0585 sin C
|
||||
y = 0.3836 - 0.1091 cos A + 0.0516 sin A + 0.0585 cos C - 0.0025 sin C
|
||||
UT1-UTC = 0.0617 + 0.00005 (MJD - 61056) - (UT2-UT1)
|
||||
|
||||
where A = 2*pi*(MJD-61048)/365.25 and C = 2*pi*(MJD-61048)/435.
|
||||
|
||||
TAI-UTC(MJD 61049) = 37.0
|
||||
The accuracy may be estimated from the expressions:
|
||||
S x,y = 0.00068 (MJD-61048)**0.80 S t = 0.00025 (MJD-61048)**0.75
|
||||
Estimated accuracies are: Predictions 10 d 20 d 30 d 40 d
|
||||
Polar coord's 0.004 0.007 0.010 0.013
|
||||
UT1-UTC 0.0014 0.0024 0.0032 0.0040
|
||||
|
||||
MJD x(arcsec) y(arcsec) UT1-UTC(sec)
|
||||
2026 1 9 61049 0.1001 0.3380 0.07228
|
||||
2026 1 10 61050 0.0993 0.3385 0.07180
|
||||
2026 1 11 61051 0.0985 0.3390 0.07149
|
||||
2026 1 12 61052 0.0978 0.3396 0.07136
|
||||
2026 1 13 61053 0.0971 0.3403 0.07142
|
||||
2026 1 14 61054 0.0964 0.3411 0.07168
|
||||
2026 1 15 61055 0.0957 0.3418 0.07209
|
||||
2026 1 16 61056 0.0949 0.3426 0.07260
|
||||
2026 1 17 61057 0.0942 0.3434 0.07313
|
||||
2026 1 18 61058 0.0935 0.3442 0.07360
|
||||
2026 1 19 61059 0.0928 0.3450 0.07391
|
||||
2026 1 20 61060 0.0921 0.3458 0.07400
|
||||
2026 1 21 61061 0.0915 0.3466 0.07380
|
||||
2026 1 22 61062 0.0909 0.3474 0.07332
|
||||
2026 1 23 61063 0.0903 0.3482 0.07264
|
||||
2026 1 24 61064 0.0897 0.3490 0.07185
|
||||
2026 1 25 61065 0.0892 0.3499 0.07110
|
||||
2026 1 26 61066 0.0886 0.3507 0.07051
|
||||
2026 1 27 61067 0.0881 0.3516 0.07015
|
||||
2026 1 28 61068 0.0876 0.3524 0.07004
|
||||
2026 1 29 61069 0.0872 0.3533 0.07016
|
||||
2026 1 30 61070 0.0867 0.3542 0.07040
|
||||
2026 1 31 61071 0.0863 0.3551 0.07062
|
||||
2026 2 1 61072 0.0858 0.3560 0.07069
|
||||
2026 2 2 61073 0.0855 0.3569 0.07052
|
||||
2026 2 3 61074 0.0851 0.3578 0.07011
|
||||
2026 2 4 61075 0.0847 0.3587 0.06952
|
||||
2026 2 5 61076 0.0844 0.3596 0.06884
|
||||
2026 2 6 61077 0.0841 0.3605 0.06818
|
||||
2026 2 7 61078 0.0838 0.3615 0.06763
|
||||
2026 2 8 61079 0.0835 0.3624 0.06723
|
||||
2026 2 9 61080 0.0832 0.3634 0.06700
|
||||
2026 2 10 61081 0.0830 0.3643 0.06696
|
||||
2026 2 11 61082 0.0828 0.3653 0.06706
|
||||
2026 2 12 61083 0.0826 0.3662 0.06727
|
||||
2026 2 13 61084 0.0824 0.3672 0.06753
|
||||
2026 2 14 61085 0.0822 0.3682 0.06774
|
||||
2026 2 15 61086 0.0821 0.3692 0.06784
|
||||
2026 2 16 61087 0.0820 0.3702 0.06774
|
||||
2026 2 17 61088 0.0819 0.3711 0.06738
|
||||
2026 2 18 61089 0.0818 0.3721 0.06675
|
||||
2026 2 19 61090 0.0818 0.3731 0.06590
|
||||
2026 2 20 61091 0.0817 0.3741 0.06493
|
||||
2026 2 21 61092 0.0817 0.3751 0.06395
|
||||
2026 2 22 61093 0.0817 0.3761 0.06311
|
||||
2026 2 23 61094 0.0818 0.3771 0.06250
|
||||
2026 2 24 61095 0.0818 0.3781 0.06215
|
||||
2026 2 25 61096 0.0819 0.3792 0.06203
|
||||
2026 2 26 61097 0.0820 0.3802 0.06204
|
||||
2026 2 27 61098 0.0821 0.3812 0.06206
|
||||
2026 2 28 61099 0.0823 0.3822 0.06196
|
||||
2026 3 1 61100 0.0824 0.3832 0.06165
|
||||
2026 3 2 61101 0.0826 0.3842 0.06111
|
||||
2026 3 3 61102 0.0828 0.3852 0.06037
|
||||
2026 3 4 61103 0.0831 0.3862 0.05951
|
||||
2026 3 5 61104 0.0833 0.3872 0.05864
|
||||
2026 3 6 61105 0.0836 0.3882 0.05787
|
||||
2026 3 7 61106 0.0839 0.3893 0.05726
|
||||
2026 3 8 61107 0.0842 0.3903 0.05686
|
||||
2026 3 9 61108 0.0846 0.3913 0.05667
|
||||
2026 3 10 61109 0.0849 0.3922 0.05667
|
||||
2026 3 11 61110 0.0853 0.3932 0.05681
|
||||
2026 3 12 61111 0.0857 0.3942 0.05703
|
||||
2026 3 13 61112 0.0861 0.3952 0.05725
|
||||
2026 3 14 61113 0.0866 0.3962 0.05737
|
||||
2026 3 15 61114 0.0870 0.3972 0.05732
|
||||
2026 3 16 61115 0.0875 0.3981 0.05703
|
||||
2026 3 17 61116 0.0880 0.3991 0.05647
|
||||
2026 3 18 61117 0.0886 0.4000 0.05564
|
||||
2026 3 19 61118 0.0891 0.4010 0.05462
|
||||
2026 3 20 61119 0.0897 0.4019 0.05355
|
||||
2026 3 21 61120 0.0903 0.4029 0.05256
|
||||
2026 3 22 61121 0.0909 0.4038 0.05180
|
||||
2026 3 23 61122 0.0916 0.4047 0.05132
|
||||
2026 3 24 61123 0.0922 0.4056 0.05111
|
||||
2026 3 25 61124 0.0929 0.4065 0.05107
|
||||
2026 3 26 61125 0.0936 0.4074 0.05108
|
||||
2026 3 27 61126 0.0943 0.4083 0.05100
|
||||
2026 3 28 61127 0.0950 0.4092 0.05075
|
||||
2026 3 29 61128 0.0958 0.4101 0.05028
|
||||
2026 3 30 61129 0.0966 0.4109 0.04959
|
||||
2026 3 31 61130 0.0974 0.4118 0.04876
|
||||
2026 4 1 61131 0.0982 0.4126 0.04787
|
||||
2026 4 2 61132 0.0990 0.4134 0.04702
|
||||
2026 4 3 61133 0.0998 0.4142 0.04629
|
||||
2026 4 4 61134 0.1007 0.4150 0.04574
|
||||
2026 4 5 61135 0.1016 0.4158 0.04540
|
||||
2026 4 6 61136 0.1025 0.4166 0.04526
|
||||
2026 4 7 61137 0.1034 0.4174 0.04528
|
||||
2026 4 8 61138 0.1044 0.4181 0.04540
|
||||
2026 4 9 61139 0.1053 0.4188 0.04556
|
||||
2026 4 10 61140 0.1063 0.4196 0.04566
|
||||
2026 4 11 61141 0.1073 0.4203 0.04563
|
||||
2026 4 12 61142 0.1083 0.4210 0.04539
|
||||
2026 4 13 61143 0.1093 0.4217 0.04489
|
||||
2026 4 14 61144 0.1103 0.4223 0.04411
|
||||
2026 4 15 61145 0.1114 0.4230 0.04311
|
||||
2026 4 16 61146 0.1124 0.4236 0.04198
|
||||
2026 4 17 61147 0.1135 0.4242 0.04088
|
||||
2026 4 18 61148 0.1146 0.4248 0.03995
|
||||
2026 4 19 61149 0.1157 0.4254 0.03929
|
||||
2026 4 20 61150 0.1168 0.4260 0.03893
|
||||
2026 4 21 61151 0.1180 0.4265 0.03881
|
||||
2026 4 22 61152 0.1191 0.4271 0.03879
|
||||
2026 4 23 61153 0.1203 0.4276 0.03874
|
||||
2026 4 24 61154 0.1214 0.4281 0.03855
|
||||
2026 4 25 61155 0.1226 0.4286 0.03815
|
||||
2026 4 26 61156 0.1238 0.4291 0.03757
|
||||
2026 4 27 61157 0.1250 0.4295 0.03685
|
||||
2026 4 28 61158 0.1262 0.4299 0.03606
|
||||
2026 4 29 61159 0.1275 0.4304 0.03532
|
||||
2026 4 30 61160 0.1287 0.4308 0.03468
|
||||
2026 5 1 61161 0.1300 0.4311 0.03422
|
||||
2026 5 2 61162 0.1312 0.4315 0.03397
|
||||
2026 5 3 61163 0.1325 0.4318 0.03394
|
||||
2026 5 4 61164 0.1338 0.4322 0.03410
|
||||
2026 5 5 61165 0.1350 0.4325 0.03439
|
||||
2026 5 6 61166 0.1363 0.4327 0.03475
|
||||
2026 5 7 61167 0.1376 0.4330 0.03509
|
||||
2026 5 8 61168 0.1389 0.4332 0.03536
|
||||
2026 5 9 61169 0.1402 0.4335 0.03547
|
||||
2026 5 10 61170 0.1416 0.4337 0.03538
|
||||
2026 5 11 61171 0.1429 0.4339 0.03505
|
||||
2026 5 12 61172 0.1442 0.4340 0.03452
|
||||
2026 5 13 61173 0.1456 0.4342 0.03383
|
||||
2026 5 14 61174 0.1469 0.4343 0.03311
|
||||
2026 5 15 61175 0.1482 0.4344 0.03249
|
||||
2026 5 16 61176 0.1496 0.4345 0.03211
|
||||
2026 5 17 61177 0.1509 0.4345 0.03204
|
||||
2026 5 18 61178 0.1523 0.4345 0.03225
|
||||
2026 5 19 61179 0.1537 0.4346 0.03262
|
||||
2026 5 20 61180 0.1550 0.4346 0.03301
|
||||
2026 5 21 61181 0.1564 0.4345 0.03328
|
||||
2026 5 22 61182 0.1577 0.4345 0.03336
|
||||
2026 5 23 61183 0.1591 0.4344 0.03325
|
||||
2026 5 24 61184 0.1605 0.4343 0.03299
|
||||
2026 5 25 61185 0.1618 0.4342 0.03268
|
||||
2026 5 26 61186 0.1632 0.4341 0.03241
|
||||
2026 5 27 61187 0.1646 0.4339 0.03224
|
||||
2026 5 28 61188 0.1659 0.4338 0.03225
|
||||
2026 5 29 61189 0.1673 0.4336 0.03246
|
||||
2026 5 30 61190 0.1686 0.4333 0.03289
|
||||
2026 5 31 61191 0.1700 0.4331 0.03352
|
||||
2026 6 1 61192 0.1713 0.4328 0.03429
|
||||
2026 6 2 61193 0.1727 0.4326 0.03515
|
||||
2026 6 3 61194 0.1740 0.4323 0.03602
|
||||
2026 6 4 61195 0.1754 0.4319 0.03683
|
||||
2026 6 5 61196 0.1767 0.4316 0.03751
|
||||
2026 6 6 61197 0.1781 0.4312 0.03801
|
||||
2026 6 7 61198 0.1794 0.4308 0.03831
|
||||
2026 6 8 61199 0.1807 0.4304 0.03841
|
||||
2026 6 9 61200 0.1820 0.4300 0.03835
|
||||
2026 6 10 61201 0.1833 0.4295 0.03823
|
||||
2026 6 11 61202 0.1846 0.4291 0.03815
|
||||
2026 6 12 61203 0.1859 0.4286 0.03824
|
||||
2026 6 13 61204 0.1872 0.4280 0.03860
|
||||
2026 6 14 61205 0.1885 0.4275 0.03924
|
||||
2026 6 15 61206 0.1897 0.4270 0.04009
|
||||
2026 6 16 61207 0.1910 0.4264 0.04101
|
||||
2026 6 17 61208 0.1922 0.4258 0.04185
|
||||
2026 6 18 61209 0.1935 0.4252 0.04250
|
||||
2026 6 19 61210 0.1947 0.4245 0.04293
|
||||
2026 6 20 61211 0.1959 0.4239 0.04317
|
||||
2026 6 21 61212 0.1971 0.4232 0.04333
|
||||
2026 6 22 61213 0.1983 0.4225 0.04352
|
||||
2026 6 23 61214 0.1995 0.4218 0.04381
|
||||
2026 6 24 61215 0.2007 0.4211 0.04428
|
||||
2026 6 25 61216 0.2018 0.4203 0.04496
|
||||
2026 6 26 61217 0.2030 0.4195 0.04586
|
||||
2026 6 27 61218 0.2041 0.4187 0.04695
|
||||
2026 6 28 61219 0.2052 0.4179 0.04820
|
||||
2026 6 29 61220 0.2063 0.4171 0.04955
|
||||
2026 6 30 61221 0.2074 0.4162 0.05092
|
||||
2026 7 1 61222 0.2085 0.4154 0.05224
|
||||
2026 7 2 61223 0.2095 0.4145 0.05344
|
||||
2026 7 3 61224 0.2106 0.4136 0.05447
|
||||
2026 7 4 61225 0.2116 0.4127 0.05530
|
||||
2026 7 5 61226 0.2126 0.4118 0.05595
|
||||
2026 7 6 61227 0.2136 0.4108 0.05644
|
||||
2026 7 7 61228 0.2146 0.4098 0.05686
|
||||
2026 7 8 61229 0.2155 0.4088 0.05730
|
||||
2026 7 9 61230 0.2164 0.4078 0.05787
|
||||
2026 7 10 61231 0.2174 0.4068 0.05866
|
||||
2026 7 11 61232 0.2183 0.4058 0.05971
|
||||
2026 7 12 61233 0.2191 0.4048 0.06099
|
||||
2026 7 13 61234 0.2200 0.4037 0.06238
|
||||
2026 7 14 61235 0.2208 0.4026 0.06374
|
||||
2026 7 15 61236 0.2217 0.4015 0.06490
|
||||
2026 7 16 61237 0.2225 0.4004 0.06580
|
||||
2026 7 17 61238 0.2232 0.3993 0.06643
|
||||
2026 7 18 61239 0.2240 0.3982 0.06686
|
||||
2026 7 19 61240 0.2247 0.3970 0.06720
|
||||
2026 7 20 61241 0.2255 0.3959 0.06757
|
||||
2026 7 21 61242 0.2262 0.3947 0.06804
|
||||
2026 7 22 61243 0.2268 0.3935 0.06865
|
||||
2026 7 23 61244 0.2275 0.3923 0.06942
|
||||
2026 7 24 61245 0.2281 0.3911 0.07035
|
||||
2026 7 25 61246 0.2287 0.3899 0.07139
|
||||
2026 7 26 61247 0.2293 0.3887 0.07253
|
||||
2026 7 27 61248 0.2299 0.3875 0.07369
|
||||
2026 7 28 61249 0.2304 0.3862 0.07483
|
||||
2026 7 29 61250 0.2309 0.3849 0.07589
|
||||
2026 7 30 61251 0.2314 0.3837 0.07681
|
||||
2026 7 31 61252 0.2319 0.3824 0.07755
|
||||
2026 8 1 61253 0.2323 0.3811 0.07812
|
||||
2026 8 2 61254 0.2327 0.3798 0.07854
|
||||
2026 8 3 61255 0.2331 0.3785 0.07889
|
||||
2026 8 4 61256 0.2335 0.3772 0.07925
|
||||
2026 8 5 61257 0.2338 0.3759 0.07972
|
||||
2026 8 6 61258 0.2342 0.3746 0.08039
|
||||
2026 8 7 61259 0.2344 0.3733 0.08130
|
||||
2026 8 8 61260 0.2347 0.3719 0.08241
|
||||
2026 8 9 61261 0.2350 0.3706 0.08365
|
||||
2026 8 10 61262 0.2352 0.3692 0.08486
|
||||
2026 8 11 61263 0.2354 0.3679 0.08590
|
||||
2026 8 12 61264 0.2355 0.3665 0.08667
|
||||
2026 8 13 61265 0.2357 0.3652 0.08710
|
||||
2026 8 14 61266 0.2358 0.3638 0.08724
|
||||
2026 8 15 61267 0.2359 0.3624 0.08725
|
||||
2026 8 16 61268 0.2359 0.3611 0.08727
|
||||
2026 8 17 61269 0.2360 0.3597 0.08741
|
||||
2026 8 18 61270 0.2360 0.3583 0.08772
|
||||
2026 8 19 61271 0.2360 0.3570 0.08823
|
||||
2026 8 20 61272 0.2359 0.3556 0.08889
|
||||
2026 8 21 61273 0.2359 0.3542 0.08975
|
||||
2026 8 22 61274 0.2358 0.3528 0.09075
|
||||
2026 8 23 61275 0.2356 0.3515 0.09173
|
||||
2026 8 24 61276 0.2355 0.3501 0.09267
|
||||
2026 8 25 61277 0.2353 0.3487 0.09354
|
||||
2026 8 26 61278 0.2351 0.3473 0.09418
|
||||
2026 8 27 61279 0.2349 0.3460 0.09450
|
||||
2026 8 28 61280 0.2346 0.3446 0.09461
|
||||
2026 8 29 61281 0.2344 0.3432 0.09454
|
||||
2026 8 30 61282 0.2340 0.3419 0.09433
|
||||
2026 8 31 61283 0.2337 0.3405 0.09408
|
||||
2026 9 1 61284 0.2334 0.3391 0.09392
|
||||
2026 9 2 61285 0.2330 0.3378 0.09389
|
||||
2026 9 3 61286 0.2326 0.3364 0.09406
|
||||
2026 9 4 61287 0.2321 0.3351 0.09437
|
||||
2026 9 5 61288 0.2317 0.3337 0.09483
|
||||
2026 9 6 61289 0.2312 0.3324 0.09533
|
||||
2026 9 7 61290 0.2306 0.3311 0.09572
|
||||
2026 9 8 61291 0.2301 0.3298 0.09587
|
||||
2026 9 9 61292 0.2295 0.3285 0.09576
|
||||
2026 9 10 61293 0.2289 0.3272 0.09541
|
||||
2026 9 11 61294 0.2283 0.3259 0.09492
|
||||
2026 9 12 61295 0.2277 0.3246 0.09452
|
||||
2026 9 13 61296 0.2270 0.3233 0.09420
|
||||
2026 9 14 61297 0.2263 0.3220 0.09410
|
||||
2026 9 15 61298 0.2256 0.3208 0.09416
|
||||
2026 9 16 61299 0.2248 0.3195 0.09433
|
||||
2026 9 17 61300 0.2241 0.3183 0.09465
|
||||
2026 9 18 61301 0.2233 0.3170 0.09510
|
||||
2026 9 19 61302 0.2225 0.3158 0.09562
|
||||
2026 9 20 61303 0.2216 0.3146 0.09615
|
||||
2026 9 21 61304 0.2208 0.3134 0.09657
|
||||
2026 9 22 61305 0.2199 0.3122 0.09690
|
||||
2026 9 23 61306 0.2190 0.3110 0.09704
|
||||
2026 9 24 61307 0.2180 0.3099 0.09702
|
||||
2026 9 25 61308 0.2171 0.3087 0.09681
|
||||
2026 9 26 61309 0.2161 0.3076 0.09642
|
||||
2026 9 27 61310 0.2151 0.3065 0.09591
|
||||
2026 9 28 61311 0.2141 0.3054 0.09537
|
||||
2026 9 29 61312 0.2130 0.3043 0.09501
|
||||
2026 9 30 61313 0.2119 0.3032 0.09489
|
||||
2026 10 1 61314 0.2109 0.3022 0.09500
|
||||
2026 10 2 61315 0.2097 0.3011 0.09526
|
||||
2026 10 3 61316 0.2086 0.3001 0.09568
|
||||
2026 10 4 61317 0.2075 0.2991 0.09602
|
||||
2026 10 5 61318 0.2063 0.2981 0.09613
|
||||
2026 10 6 61319 0.2051 0.2971 0.09602
|
||||
2026 10 7 61320 0.2039 0.2962 0.09573
|
||||
2026 10 8 61321 0.2027 0.2952 0.09526
|
||||
2026 10 9 61322 0.2014 0.2943 0.09470
|
||||
2026 10 10 61323 0.2001 0.2934 0.09420
|
||||
2026 10 11 61324 0.1989 0.2925 0.09382
|
||||
2026 10 12 61325 0.1976 0.2917 0.09360
|
||||
2026 10 13 61326 0.1962 0.2908 0.09354
|
||||
2026 10 14 61327 0.1949 0.2900 0.09367
|
||||
2026 10 15 61328 0.1935 0.2892 0.09395
|
||||
2026 10 16 61329 0.1922 0.2884 0.09436
|
||||
2026 10 17 61330 0.1908 0.2876 0.09484
|
||||
2026 10 18 61331 0.1894 0.2869 0.09525
|
||||
2026 10 19 61332 0.1880 0.2862 0.09550
|
||||
2026 10 20 61333 0.1865 0.2855 0.09558
|
||||
2026 10 21 61334 0.1851 0.2848 0.09543
|
||||
2026 10 22 61335 0.1836 0.2841 0.09503
|
||||
2026 10 23 61336 0.1821 0.2835 0.09443
|
||||
2026 10 24 61337 0.1807 0.2829 0.09372
|
||||
2026 10 25 61338 0.1792 0.2823 0.09295
|
||||
2026 10 26 61339 0.1776 0.2817 0.09229
|
||||
2026 10 27 61340 0.1761 0.2812 0.09186
|
||||
2026 10 28 61341 0.1746 0.2807 0.09174
|
||||
2026 10 29 61342 0.1730 0.2802 0.09188
|
||||
2026 10 30 61343 0.1715 0.2797 0.09217
|
||||
2026 10 31 61344 0.1699 0.2792 0.09250
|
||||
2026 11 1 61345 0.1683 0.2788 0.09272
|
||||
2026 11 2 61346 0.1667 0.2784 0.09267
|
||||
2026 11 3 61347 0.1651 0.2780 0.09243
|
||||
2026 11 4 61348 0.1635 0.2777 0.09199
|
||||
2026 11 5 61349 0.1619 0.2773 0.09144
|
||||
2026 11 6 61350 0.1603 0.2770 0.09088
|
||||
2026 11 7 61351 0.1587 0.2767 0.09046
|
||||
2026 11 8 61352 0.1571 0.2765 0.09016
|
||||
2026 11 9 61353 0.1554 0.2763 0.09004
|
||||
2026 11 10 61354 0.1538 0.2760 0.09011
|
||||
2026 11 11 61355 0.1521 0.2759 0.09033
|
||||
2026 11 12 61356 0.1505 0.2757 0.09067
|
||||
2026 11 13 61357 0.1488 0.2756 0.09106
|
||||
2026 11 14 61358 0.1471 0.2755 0.09148
|
||||
2026 11 15 61359 0.1455 0.2754 0.09178
|
||||
2026 11 16 61360 0.1438 0.2753 0.09198
|
||||
2026 11 17 61361 0.1421 0.2753 0.09203
|
||||
2026 11 18 61362 0.1405 0.2753 0.09194
|
||||
2026 11 19 61363 0.1388 0.2753 0.09161
|
||||
2026 11 20 61364 0.1371 0.2754 0.09115
|
||||
2026 11 21 61365 0.1354 0.2755 0.09063
|
||||
2026 11 22 61366 0.1338 0.2756 0.09016
|
||||
2026 11 23 61367 0.1321 0.2757 0.08988
|
||||
2026 11 24 61368 0.1304 0.2759 0.08982
|
||||
2026 11 25 61369 0.1287 0.2760 0.09000
|
||||
2026 11 26 61370 0.1271 0.2762 0.09038
|
||||
2026 11 27 61371 0.1254 0.2765 0.09073
|
||||
2026 11 28 61372 0.1237 0.2767 0.09096
|
||||
2026 11 29 61373 0.1221 0.2770 0.09110
|
||||
2026 11 30 61374 0.1204 0.2773 0.09103
|
||||
2026 12 1 61375 0.1188 0.2777 0.09070
|
||||
2026 12 2 61376 0.1171 0.2780 0.09027
|
||||
2026 12 3 61377 0.1155 0.2784 0.08987
|
||||
2026 12 4 61378 0.1139 0.2788 0.08951
|
||||
2026 12 5 61379 0.1122 0.2793 0.08932
|
||||
2026 12 6 61380 0.1106 0.2797 0.08929
|
||||
2026 12 7 61381 0.1090 0.2802 0.08941
|
||||
2026 12 8 61382 0.1074 0.2807 0.08977
|
||||
2026 12 9 61383 0.1058 0.2813 0.09031
|
||||
2026 12 10 61384 0.1042 0.2819 0.09092
|
||||
2026 12 11 61385 0.1026 0.2824 0.09153
|
||||
2026 12 12 61386 0.1011 0.2831 0.09210
|
||||
2026 12 13 61387 0.0995 0.2837 0.09261
|
||||
2026 12 14 61388 0.0980 0.2844 0.09296
|
||||
2026 12 15 61389 0.0964 0.2851 0.09308
|
||||
2026 12 16 61390 0.0949 0.2858 0.09298
|
||||
2026 12 17 61391 0.0934 0.2865 0.09271
|
||||
2026 12 18 61392 0.0919 0.2873 0.09223
|
||||
2026 12 19 61393 0.0904 0.2881 0.09183
|
||||
2026 12 20 61394 0.0889 0.2889 0.09151
|
||||
2026 12 21 61395 0.0875 0.2897 0.09140
|
||||
2026 12 22 61396 0.0860 0.2906 0.09146
|
||||
2026 12 23 61397 0.0846 0.2914 0.09172
|
||||
2026 12 24 61398 0.0832 0.2923 0.09217
|
||||
2026 12 25 61399 0.0818 0.2933 0.09256
|
||||
2026 12 26 61400 0.0804 0.2942 0.09281
|
||||
2026 12 27 61401 0.0791 0.2952 0.09278
|
||||
2026 12 28 61402 0.0777 0.2962 0.09251
|
||||
2026 12 29 61403 0.0764 0.2972 0.09213
|
||||
2026 12 30 61404 0.0751 0.2982 0.09173
|
||||
2026 12 31 61405 0.0738 0.2993 0.09140
|
||||
2027 1 1 61406 0.0725 0.3004 0.09123
|
||||
2027 1 2 61407 0.0713 0.3015 0.09132
|
||||
2027 1 3 61408 0.0700 0.3026 0.09160
|
||||
2027 1 4 61409 0.0688 0.3037 0.09200
|
||||
2027 1 5 61410 0.0676 0.3049 0.09264
|
||||
2027 1 6 61411 0.0665 0.3061 0.09344
|
||||
2027 1 7 61412 0.0653 0.3072 0.09426
|
||||
2027 1 8 61413 0.0642 0.3085 0.09509
|
||||
These predictions are based on all announced leap seconds.
|
||||
|
||||
CELESTIAL POLE OFFSET SERIES:
|
||||
NEOS Celestial Pole Offset Series
|
||||
MJD dpsi error deps error
|
||||
(msec. of arc)
|
||||
61026 -114.45 1.33 -7.19 0.16
|
||||
61027 -114.43 1.33 -7.42 0.16
|
||||
61028 -114.48 1.33 -7.64 0.16
|
||||
61029 -114.51 1.19 -7.67 0.18
|
||||
61030 -114.39 1.19 -7.49 0.18
|
||||
61031 -114.09 1.24 -7.26 0.16
|
||||
61032 -113.74 1.35 -7.13 0.06
|
||||
61033 -113.52 1.35 -7.11 0.06
|
||||
|
||||
IERS Celestial Pole Offset Final Series
|
||||
MJD dpsi deps
|
||||
(msec. of arc)
|
||||
60981 -117.6 -9.0
|
||||
60982 -117.8 -8.8
|
||||
60983 -118.0 -8.6
|
||||
60984 -117.8 -8.6
|
||||
60985 -117.5 -8.8
|
||||
60986 -117.1 -8.9
|
||||
60987 -116.8 -8.7
|
||||
60988 -116.9 -8.5
|
||||
60989 -117.0 -8.6
|
||||
60990 -116.9 -8.8
|
||||
60991 -116.6 -8.8
|
||||
60992 -116.5 -8.5
|
||||
60993 -116.5 -8.0
|
||||
60994 -116.6 -7.7
|
||||
60995 -116.6 -7.6
|
||||
60996 -116.4 -7.5
|
||||
60997 -116.1 -7.5
|
||||
60998 -115.6 -7.8
|
||||
60999 -115.5 -8.3
|
||||
61000 -115.9 -8.6
|
||||
61001 -116.0 -8.6
|
||||
61002 -115.9 -8.5
|
||||
61003 -115.7 -8.3
|
||||
61004 -115.4 -8.2
|
||||
61005 -115.1 -8.2
|
||||
61006 -114.7 -8.1
|
||||
61007 -114.4 -7.9
|
||||
61008 -114.3 -7.9
|
||||
61009 -114.6 -7.8
|
||||
61010 -115.2 -7.6
|
||||
|
||||
IAU2000A Celestial Pole Offset Series
|
||||
MJD dX error dY error
|
||||
(msec. of arc)
|
||||
61026 0.512 0.531 -0.092 0.161
|
||||
61027 0.519 0.531 -0.101 0.161
|
||||
61028 0.523 0.531 -0.107 0.161
|
||||
61029 0.527 0.474 -0.112 0.176
|
||||
61030 0.530 0.474 -0.114 0.176
|
||||
61031 0.533 0.494 -0.114 0.156
|
||||
61032 0.536 0.536 -0.113 0.059
|
||||
61033 0.539 0.536 -0.111 0.059
|
||||
|
||||
|
||||
IAU2000A Celestial Pole Offset Final Series
|
||||
MJD dX dY
|
||||
(msec. of arc)
|
||||
60981 0.40 0.03
|
||||
60982 0.38 0.02
|
||||
60983 0.37 0.00
|
||||
60984 0.42 -0.05
|
||||
60985 0.41 -0.05
|
||||
60986 0.34 -0.00
|
||||
60987 0.33 0.01
|
||||
60988 0.36 -0.01
|
||||
60989 0.41 -0.04
|
||||
60990 0.46 -0.06
|
||||
60991 0.45 -0.05
|
||||
60992 0.42 -0.02
|
||||
60993 0.39 0.01
|
||||
60994 0.33 0.07
|
||||
60995 0.29 0.12
|
||||
60996 0.30 0.14
|
||||
60997 0.38 0.10
|
||||
60998 0.59 -0.02
|
||||
60999 0.62 -0.11
|
||||
61000 0.51 -0.14
|
||||
61001 0.41 -0.14
|
||||
61002 0.35 -0.10
|
||||
61003 0.32 -0.06
|
||||
61004 0.33 -0.03
|
||||
61005 0.38 -0.07
|
||||
61006 0.47 -0.10
|
||||
61007 0.57 -0.11
|
||||
61008 0.57 -0.10
|
||||
61009 0.48 -0.08
|
||||
61010 0.35 -0.06
|
||||
|
||||
)--";
|
||||
|
||||
|
||||
|
||||
} // namespace mcc::ccte::iers::defaults
|
||||
|
||||
995
include/mcc/mcc_concepts.h
Normal file
995
include/mcc/mcc_concepts.h
Normal file
@@ -0,0 +1,995 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* COMPONENT CLASSES CONCEPTS *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
// #include <expected>
|
||||
#include <string_view>
|
||||
|
||||
|
||||
#include "mcc_angle.h"
|
||||
#include "mcc_traits.h"
|
||||
|
||||
namespace mcc
|
||||
{
|
||||
|
||||
|
||||
/* LIBRARY-WIDE CONCEPT FOR LOGGER CLASS */
|
||||
|
||||
template <typename T>
|
||||
concept mcc_logger_c = requires(T t, const T t_const) {
|
||||
{ t.logError(std::declval<const std::string&>()) };
|
||||
{ t.logDebug(std::declval<const std::string&>()) };
|
||||
{ t.logWarn(std::declval<const std::string&>()) };
|
||||
{ t.logInfo(std::declval<const std::string&>()) };
|
||||
{ t.logTrace(std::declval<const std::string&>()) };
|
||||
};
|
||||
|
||||
|
||||
/* CLASS TO EMULATE NO LOGGING */
|
||||
struct MccNullLogger {
|
||||
void logError(const std::string&) {}
|
||||
void logDebug(const std::string&) {}
|
||||
void logWarn(const std::string&) {}
|
||||
void logInfo(const std::string&) {}
|
||||
void logTrace(const std::string&) {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* LIBRARY-WIDE TYPES DEFINITION OF OPERATIONAL ERROR */
|
||||
/* AND CLASS METHODS RETURNED VALUE */
|
||||
|
||||
template <typename T>
|
||||
concept mcc_error_c = std::default_initializable<T> && (std::convertible_to<T, bool> || requires(const T t) {
|
||||
// std::formattable<T, char> && std::default_initializable<T> && (std::convertible_to<T, bool>
|
||||
// || requires(const T t) {
|
||||
(bool)T() == false; // default constucted value must be a "non-error"!
|
||||
});
|
||||
|
||||
|
||||
template <mcc_error_c ErrT, mcc_error_c DefErrT>
|
||||
auto mcc_deduced_err(ErrT const& err, DefErrT const& default_err)
|
||||
// DefErrT mcc_deduced_err(ErrT const& err, DefErrT const& default_err)
|
||||
{
|
||||
if constexpr (std::same_as<ErrT, DefErrT>) {
|
||||
return err;
|
||||
} else if constexpr (std::is_error_code_enum_v<DefErrT>) {
|
||||
if constexpr (std::same_as<ErrT, std::error_code>) {
|
||||
return err;
|
||||
} else {
|
||||
return default_err;
|
||||
}
|
||||
} else if constexpr (std::is_error_condition_enum_v<DefErrT>) {
|
||||
if constexpr (std::same_as<ErrT, std::error_condition>) {
|
||||
return err;
|
||||
} else {
|
||||
return default_err;
|
||||
}
|
||||
} else {
|
||||
return default_err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// template <typename T, typename VT>
|
||||
// concept mcc_retval_c = requires(T t) {
|
||||
// //
|
||||
// []<mcc_error_c ErrT>(std::expected<VT, ErrT>) {}(t);
|
||||
// };
|
||||
|
||||
|
||||
// // deduce an error from mcc_retval_c and default error value
|
||||
// template <typename VT, mcc_retval_c<VT> RetT, mcc_error_c DefErrT>
|
||||
// DefErrT mcc_deduced_err(RetT const& ret, DefErrT const& default_err)
|
||||
// {
|
||||
// if (ret) {
|
||||
// return DefErrT{}; // no error
|
||||
// }
|
||||
|
||||
// if constexpr (std::same_as<typename RetT::error_type, DefErrT>) {
|
||||
// return ret.error();
|
||||
// } else {
|
||||
// return default_err;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/* MOUNT CONSTRUCTION-RELATED STUFF */
|
||||
|
||||
// mount construction type (only the most common ones)
|
||||
enum class MccMountType : uint8_t { GERMAN_TYPE, FORK_TYPE, CROSSAXIS_TYPE, ALTAZ_TYPE };
|
||||
|
||||
template <MccMountType TYPE>
|
||||
static constexpr std::string_view MccMountTypeStr = TYPE == MccMountType::GERMAN_TYPE ? "GERMAN"
|
||||
: TYPE == MccMountType::FORK_TYPE ? "FORK"
|
||||
: TYPE == MccMountType::CROSSAXIS_TYPE ? "CROSSAXIS"
|
||||
: TYPE == MccMountType::ALTAZ_TYPE ? "ALTAZ"
|
||||
: "UNKNOWN";
|
||||
|
||||
template <MccMountType TYPE>
|
||||
static constexpr bool mcc_is_equatorial_mount = TYPE == MccMountType::GERMAN_TYPE ? true
|
||||
: TYPE == MccMountType::FORK_TYPE ? true
|
||||
: TYPE == MccMountType::CROSSAXIS_TYPE ? true
|
||||
: TYPE == MccMountType::ALTAZ_TYPE ? false
|
||||
: false;
|
||||
template <MccMountType TYPE>
|
||||
static constexpr bool mcc_is_altaz_mount = TYPE == MccMountType::GERMAN_TYPE ? false
|
||||
: TYPE == MccMountType::FORK_TYPE ? false
|
||||
: TYPE == MccMountType::CROSSAXIS_TYPE ? false
|
||||
: TYPE == MccMountType::ALTAZ_TYPE ? true
|
||||
: false;
|
||||
|
||||
static consteval bool mccIsEquatorialMount(const MccMountType type)
|
||||
{
|
||||
return type == MccMountType::GERMAN_TYPE ? true
|
||||
: type == MccMountType::FORK_TYPE ? true
|
||||
: type == MccMountType::CROSSAXIS_TYPE ? true
|
||||
: type == MccMountType::ALTAZ_TYPE ? false
|
||||
: false;
|
||||
};
|
||||
|
||||
static consteval bool mccIsAltAzMount(const MccMountType type)
|
||||
{
|
||||
return type == MccMountType::GERMAN_TYPE ? false
|
||||
: type == MccMountType::FORK_TYPE ? false
|
||||
: type == MccMountType::CROSSAXIS_TYPE ? false
|
||||
: type == MccMountType::ALTAZ_TYPE ? true
|
||||
: false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* FLOATING-POINT LIKE CLASS CONCEPT */
|
||||
|
||||
template <typename T>
|
||||
concept mcc_fp_type_like_c =
|
||||
std::floating_point<T> ||
|
||||
(std::convertible_to<T, double> && std::constructible_from<T, double> && std::default_initializable<T>);
|
||||
|
||||
|
||||
|
||||
/* GEOMETRICAL ANGLE REPRESENTATION CLASS CONCEPT */
|
||||
|
||||
/* REQUIREMENT: in the MCC-library it is assumed that an arithmetic representation of angles are measured in the
|
||||
radians!!! This means that possible conversion operator 'SOME_USER_ANGLE_CLASS::operator double()'
|
||||
must return an angle in radians!
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
concept mcc_angle_c = mcc_fp_type_like_c<T> && requires(T v, double vd) {
|
||||
// mandatory arithmetic operations
|
||||
{ v + v } -> std::same_as<T>;
|
||||
{ v - v } -> std::same_as<T>;
|
||||
{ v += v } -> std::same_as<T&>;
|
||||
{ v -= v } -> std::same_as<T&>;
|
||||
|
||||
{ vd + v } -> std::same_as<T>;
|
||||
{ vd - v } -> std::same_as<T>;
|
||||
{ v + vd } -> std::same_as<T>;
|
||||
{ v - vd } -> std::same_as<T>;
|
||||
{ v += vd } -> std::same_as<T&>;
|
||||
{ v -= vd } -> std::same_as<T&>;
|
||||
|
||||
{ v * vd } -> std::same_as<T>;
|
||||
{ v / vd } -> std::same_as<T>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* CELESTIAL COORDINATES EPOCH CLASS CONCEPT */
|
||||
|
||||
struct mcc_coord_epoch_interface_t {
|
||||
virtual ~mcc_coord_epoch_interface_t() = default;
|
||||
|
||||
static constexpr double MJD0 = 2400000.5;
|
||||
|
||||
template <std::derived_from<mcc_coord_epoch_interface_t> SelfT, traits::mcc_input_char_range IR>
|
||||
bool fromCharRange(this SelfT&& self, IR&& str)
|
||||
{
|
||||
return std::forward<decltype(self)>(self).fromCharRange(std::forward<IR>(str));
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_coord_epoch_interface_t> SelfT, typename ClockT, typename DurT>
|
||||
bool fromTimePoint(this SelfT&& self, std::chrono::time_point<ClockT, DurT>&& tp)
|
||||
{
|
||||
return std::forward<decltype(self)>(self).fromTimePoint(std::forward<decltype(tp)>(tp));
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_coord_epoch_interface_t> SelfT, typename VT>
|
||||
bool fromMJD(this SelfT&& self, VT&& mjd)
|
||||
requires std::is_arithmetic_v<VT>
|
||||
{
|
||||
return std::forward<decltype(self)>(self).fromMJD(std::forward<VT>(mjd));
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_coord_epoch_interface_t> SelfT, traits::mcc_time_duration_c DT>
|
||||
SelfT& operator+=(this SelfT& self, DT&& dt)
|
||||
{
|
||||
return std::forward<decltype(self)>(self).operator+=(std::forward<DT>(dt));
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_coord_epoch_interface_t> SelfT, traits::mcc_time_duration_c DT>
|
||||
SelfT& operator-=(this SelfT& self, DT&& dt)
|
||||
{
|
||||
return std::forward<decltype(self)>(self).operator-=(std::forward<DT>(dt));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept mcc_coord_epoch_c = std::derived_from<T, mcc_coord_epoch_interface_t> && requires(T t1, T t2, const T t_const) {
|
||||
{ t_const.MJD() } -> std::convertible_to<double>;
|
||||
{ t_const.UTC() } -> traits::mcc_systime_c;
|
||||
{ t_const.JEpoch() } -> traits::mcc_output_char_range;
|
||||
|
||||
{ t1 <=> t2 };
|
||||
|
||||
{ T::now() } -> std::same_as<T>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* CELESTIAL COORDINATE TRANSFORMATION ENGINE CLASS CONCEPT */
|
||||
|
||||
template <typename RetT>
|
||||
struct mcc_ccte_engine_interface_t {
|
||||
virtual ~mcc_ccte_engine_interface_t() = default;
|
||||
|
||||
// returns geographical site coordinates for underlying transformation calculations
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT, mcc_angle_c LAT_T, mcc_angle_c LON_T>
|
||||
void geoPosition(this SelfT&& self, std::pair<LAT_T, LON_T>* pos)
|
||||
{
|
||||
std::forward<SelfT>(self).geoPosition(pos);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT, mcc_angle_c StT, mcc_coord_epoch_c EpT>
|
||||
auto apparentSideralTime(this SelfT&& self, EpT const& ep, StT* st, bool isLocal)
|
||||
{
|
||||
return std::forward<SelfT>(self).apparentSideralTime(ep, st, isLocal);
|
||||
}
|
||||
|
||||
// from ICRS to observed (taking into account atmospheric refraction) coordinate transformation
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT,
|
||||
mcc_angle_c RA_ICRS_T,
|
||||
mcc_angle_c DEC_ICRS_T,
|
||||
mcc_coord_epoch_c EpT,
|
||||
mcc_angle_c RA_OBS_T,
|
||||
mcc_angle_c DEC_OBS_T,
|
||||
mcc_angle_c HA_OBS_T,
|
||||
mcc_angle_c AZ_T,
|
||||
mcc_angle_c ZD_T>
|
||||
RetT icrsToObs(this SelfT&& self,
|
||||
RA_ICRS_T const& ra_icrs,
|
||||
DEC_ICRS_T const& dec_icrs,
|
||||
EpT const& ep,
|
||||
RA_OBS_T* ra,
|
||||
DEC_OBS_T* dec,
|
||||
HA_OBS_T* ha,
|
||||
AZ_T* az,
|
||||
ZD_T* zd)
|
||||
{
|
||||
return std::forward<SelfT>(self).icrsToObs(ra_icrs, dec_icrs, ep, ra, dec, ha, az, zd);
|
||||
}
|
||||
|
||||
|
||||
// from ICRS to apparent (in vacuo) coordinate transformation
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT,
|
||||
mcc_angle_c RA_ICRS_T,
|
||||
mcc_angle_c DEC_ICRS_T,
|
||||
mcc_coord_epoch_c EpT,
|
||||
mcc_angle_c RA_APP_T,
|
||||
mcc_angle_c DEC_APP_T,
|
||||
mcc_angle_c HA_APP_T,
|
||||
mcc_angle_c AZ_T,
|
||||
mcc_angle_c ZD_T>
|
||||
RetT icrsToApp(this SelfT&& self,
|
||||
RA_ICRS_T const& ra_icrs,
|
||||
DEC_ICRS_T const& dec_icrs,
|
||||
EpT const& ep,
|
||||
RA_APP_T* ra,
|
||||
DEC_APP_T* dec,
|
||||
HA_APP_T* ha,
|
||||
AZ_T* az,
|
||||
ZD_T* zd)
|
||||
{
|
||||
return std::forward<SelfT>(self).icrsToApp(ra_icrs, dec_icrs, ep, ra, dec, ha, az, zd);
|
||||
}
|
||||
|
||||
|
||||
// from observed (taking into account atmospheric refraction) to ICRS coordinate transformation
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT,
|
||||
mcc_coord_epoch_c EpT,
|
||||
mcc_angle_c CO_LON_T,
|
||||
mcc_angle_c CO_LAT_T,
|
||||
mcc_angle_c RA_ICRS_T,
|
||||
mcc_angle_c DEC_ICRS_T>
|
||||
RetT obsToICRS(this SelfT&& self,
|
||||
impl::MccCoordPairKind obs_type,
|
||||
EpT const& epoch,
|
||||
CO_LON_T const& co_lon,
|
||||
CO_LAT_T const& co_lat,
|
||||
RA_ICRS_T* ra_icrs,
|
||||
DEC_ICRS_T* dec_icrs)
|
||||
{
|
||||
return std::forward<SelfT>(self).obsToICRS(epoch, obs_type, co_lon, co_lat, ra_icrs, dec_icrs);
|
||||
}
|
||||
|
||||
|
||||
// from apparent (in vacuo) to ICRS coordinate transformation
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT,
|
||||
mcc_coord_epoch_c EpT,
|
||||
mcc_angle_c CO_LON_T,
|
||||
mcc_angle_c CO_LAT_T,
|
||||
mcc_angle_c RA_ICRS_T,
|
||||
mcc_angle_c DEC_ICRS_T>
|
||||
RetT appToICRS(this SelfT&& self,
|
||||
impl::MccCoordPairKind app_type,
|
||||
EpT const& epoch,
|
||||
CO_LON_T const& co_lon,
|
||||
CO_LAT_T const& co_lat,
|
||||
RA_ICRS_T* ra_icrs,
|
||||
DEC_ICRS_T* dec_icrs)
|
||||
{
|
||||
return std::forward<SelfT>(self).appToICRS(epoch, app_type, co_lon, co_lat, ra_icrs, dec_icrs);
|
||||
}
|
||||
|
||||
// compute equation of origins EO = ERA-GAST
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT, mcc_coord_epoch_c EpT, mcc_angle_c EO_T>
|
||||
RetT equationOrigins(this SelfT&& self, EpT const& epoch, EO_T* eo)
|
||||
{
|
||||
return std::forward<SelfT>(self).equationOrigins(epoch, eo);
|
||||
}
|
||||
|
||||
|
||||
// compute refraction correction from observed zenithal distance
|
||||
// (Zapp = Zobs + dZ)
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT, mcc_angle_c ZD_OBS_T, mcc_angle_c DZ_T>
|
||||
RetT refractionCorrection(this SelfT&& self, ZD_OBS_T const& zd_obs, DZ_T* dZ)
|
||||
{
|
||||
return std::forward<SelfT>(self).refractionCorrection(zd_obs, dZ);
|
||||
}
|
||||
|
||||
|
||||
// compute refraction correction from apparent (in vacuo) zenithal distance
|
||||
// (Zobs = Zapp - dZ)
|
||||
template <std::derived_from<mcc_ccte_engine_interface_t> SelfT, mcc_angle_c ZD_APP_T, mcc_angle_c DZ_T>
|
||||
RetT refractionInverseCorrection(this SelfT&& self, ZD_APP_T const& zd_app, DZ_T* dZ)
|
||||
{
|
||||
return std::forward<SelfT>(self).refractionInverseCorrection(zd_app, dZ);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_ccte_c = std::derived_from<T, mcc_ccte_engine_interface_t<typename T::error_t>> && requires {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
// static const variable with name of CCTE
|
||||
requires std::formattable<decltype(T::ccteName), char> && std::is_const_v<decltype(T::ccteName)>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* COORDINATES PAIR CLASS CONCEPT */
|
||||
|
||||
struct mcc_coord_pair_interface_t {
|
||||
virtual ~mcc_coord_pair_interface_t() = default;
|
||||
|
||||
template <std::derived_from<mcc_coord_pair_interface_t> SelfT, mcc_coord_epoch_c EpT>
|
||||
auto setEpoch(this SelfT&& self, EpT const& ep)
|
||||
{
|
||||
return std::forward<SelfT>(self).setEpoch(ep);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept mcc_coord_pair_c = std::derived_from<T, mcc_coord_pair_interface_t> && requires(T t, const T t_const) {
|
||||
// the 'T' class must contain static constexpr member of 'pairKind' of some type
|
||||
// (usually just a enum: see mcc_coordinate.h for an example of the implementation)
|
||||
[]() {
|
||||
[[maybe_unused]] static constexpr auto val = T::pairKind;
|
||||
}(); // to ensure 'pairKind' can be used in compile-time context
|
||||
|
||||
requires mcc_angle_c<typename T::x_t>; // co-longitude coordinate type
|
||||
requires mcc_angle_c<typename T::y_t>; // co-latitude coordinate type
|
||||
|
||||
// std::constructible_from<T, typename T::x_t const&, typename T::y_t const&>;
|
||||
|
||||
{ t_const.x() } -> std::same_as<typename T::x_t>;
|
||||
{ t_const.y() } -> std::same_as<typename T::y_t>;
|
||||
|
||||
{ t_const.epoch() } -> mcc_coord_epoch_c;
|
||||
|
||||
{ t.setX(std::declval<typename T::x_t const&>()) };
|
||||
{ t.setY(std::declval<typename T::y_t const&>()) };
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* SKY POINT CLASS CONCEPT */
|
||||
|
||||
|
||||
struct mcc_skypoint_interface_t {
|
||||
virtual ~mcc_skypoint_interface_t() = default;
|
||||
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
|
||||
auto from(this SelfT&& self, PT&& cpair)
|
||||
{
|
||||
return std::forward<SelfT>(self).from(std::forward<PT>(cpair));
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
|
||||
auto operator=(this SelfT&& self, PT&& cpair)
|
||||
{
|
||||
return std::forward<SelfT>(self).operator=(std::forward<PT>(cpair));
|
||||
}
|
||||
|
||||
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT, mcc_coord_pair_c... PTs>
|
||||
auto to(this SelfT&& self, PT& cpair, PTs&... cpairs)
|
||||
{
|
||||
return std::forward<SelfT>(self).to(cpair, cpairs...);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
|
||||
operator PT(this SelfT&& self)
|
||||
{
|
||||
return std::forward<SelfT>(self).operator PT();
|
||||
}
|
||||
|
||||
// mandatory specialization conversional operator to
|
||||
// get geographic coordinates used in underlying transformation calculations
|
||||
// (it are geographic site coordinates mandatory used in celestial coordinate transformation engine
|
||||
// of any implementation of skypoint class)
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
|
||||
requires(PT::pairKind == impl::MccCoordPairKind::COORDS_KIND_LONLAT)
|
||||
operator PT(this SelfT&& self)
|
||||
{
|
||||
return std::forward<SelfT>(self).operator PT();
|
||||
}
|
||||
|
||||
|
||||
// return coordinates pair at same epoch as 'this' sky point
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT, mcc_coord_pair_c... PTs>
|
||||
auto toAtSameEpoch(this SelfT&& self, PT& cpair, PTs&... cpairs)
|
||||
{
|
||||
return std::forward<SelfT>(self).to(cpair, cpairs...);
|
||||
}
|
||||
|
||||
|
||||
// Refraction correction for given celestial point.
|
||||
// It is assumed that for the apparent (in vacuo) and ICRS kinds of coordinates this correction is 0!
|
||||
// The returned refraction correction must be calculated as the correction applied to observed (affected by
|
||||
// refraction) zenithal distance to compute apparent (in vacuo) one, i.e., Z_app = Z_obs + refr_corr
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT>
|
||||
auto refractCorrection(this SelfT&& self, mcc_angle_c auto* dZ)
|
||||
{
|
||||
return std::forward<SelfT>(self).refractCorrection(dZ);
|
||||
}
|
||||
|
||||
// As above but the returned correction must be calculated as the correction applied to apparent (in vacuo)
|
||||
// zenithal distance to compute observed (affected by refraction) one, i.e., Z_obs = Z_app - refr_corr.
|
||||
// It is assumed that for the observed and ICRS kinds of coordinates this correction is 0!
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT>
|
||||
auto refractInverseCorrection(this SelfT&& self, mcc_angle_c auto* dZ)
|
||||
{
|
||||
return std::forward<SelfT>(self).refractInverseCorrection(dZ);
|
||||
}
|
||||
|
||||
// returns apparent sideral time (Greenwich or local) for the epoch of the celestial point
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT>
|
||||
auto appSideralTime(this SelfT&& self, mcc_angle_c auto* st, bool is_local)
|
||||
{
|
||||
return std::forward<SelfT>(self).appSideralTime(st, is_local);
|
||||
}
|
||||
|
||||
// returns equation of origins for the epoch of the celestial point
|
||||
template <std::derived_from<mcc_skypoint_interface_t> SelfT>
|
||||
auto EO(this SelfT&& self, mcc_angle_c auto* eo)
|
||||
{
|
||||
return std::forward<SelfT>(self).EO(eo);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept mcc_skypoint_c = std::derived_from<T, mcc_skypoint_interface_t> && requires(const T t_const) {
|
||||
{ t_const.epoch() } -> mcc_coord_epoch_c;
|
||||
|
||||
// currently stored coordinates pair
|
||||
{ t_const.pairKind() } -> std::same_as<impl::MccCoordPairKind>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* POINTING CORRECTION MODEL CLASS CONCEPT */
|
||||
|
||||
|
||||
|
||||
// The result of PCM calculations must be at least corrections along both mount axes
|
||||
template <typename T>
|
||||
concept mcc_pcm_result_c = requires(T t) {
|
||||
requires mcc_angle_c<decltype(t.pcmX)>;
|
||||
requires mcc_angle_c<decltype(t.pcmY)>;
|
||||
};
|
||||
|
||||
|
||||
template <typename RetT>
|
||||
struct mcc_pcm_interface_t {
|
||||
virtual ~mcc_pcm_interface_t() = default;
|
||||
|
||||
template <std::derived_from<mcc_pcm_interface_t> SelfT, mcc_coord_pair_c HW_COORD_T>
|
||||
RetT computePCM(this SelfT&& self,
|
||||
HW_COORD_T const& hw_coord,
|
||||
mcc_pcm_result_c auto* result,
|
||||
mcc_skypoint_c auto* obs_pt)
|
||||
{
|
||||
return std::forward<SelfT>(self).computePCM(hw_coord, result, obs_pt);
|
||||
}
|
||||
|
||||
|
||||
template <std::derived_from<mcc_pcm_interface_t> SelfT, mcc_coord_pair_c HW_COORD_T>
|
||||
RetT computeInversePCM(this SelfT&& self,
|
||||
mcc_skypoint_c auto const& obs_pt,
|
||||
mcc_pcm_result_c auto* inv_result,
|
||||
HW_COORD_T* hw_coord)
|
||||
{
|
||||
return std::forward<SelfT>(self).computeInversePCM(obs_pt, inv_result, hw_coord);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_pcm_c = std::derived_from<T, mcc_pcm_interface_t<typename T::error_t>> && requires {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
// the 'T' class must contain static constexpr member of 'MccMountType' type
|
||||
requires std::same_as<decltype(T::mountType), const MccMountType>;
|
||||
[]() {
|
||||
[[maybe_unused]] static constexpr MccMountType val = T::mountType;
|
||||
}(); // to ensure 'mountType' can be used in compile-time context
|
||||
|
||||
// static const variable with name of PCM
|
||||
requires std::formattable<decltype(T::pcmName), char> && std::is_const_v<decltype(T::pcmName)>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* MOUNT HARDWARE ABSTRACTION CLASS CONCEPT */
|
||||
|
||||
|
||||
// a type that defines at least HW_MOVE_ERROR, HW_MOVE_STOPPED, HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, HW_MOVE_TRACKING
|
||||
// and HW_MOVE_GUIDING compile-time constants.
|
||||
//
|
||||
// e.g. an implementations can be as follows:
|
||||
// enum class hardware_movement_state_t: int {HW_MOVE_ERROR = -1, HW_MOVE_STOPPED = 0, HW_MOVE_SLEWING,
|
||||
// HW_MOVE_ADJUSTING, HW_MOVE_TRACKING, HW_MOVE_GUIDING}
|
||||
//
|
||||
// struct hardware_movement_state_t {
|
||||
// static constexpr uint16_t HW_MOVE_STOPPED = 0;
|
||||
// static constexpr uint16_t HW_MOVE_SLEWING = 111;
|
||||
// static constexpr uint16_t HW_MOVE_ADJUSTING = 222;
|
||||
// static constexpr uint16_t HW_MOVE_TRACKING = 333;
|
||||
// static constexpr uint16_t HW_MOVE_GUIDING = 444;
|
||||
// static constexpr uint16_t HW_MOVE_ERROR = 555;
|
||||
// }
|
||||
template <typename T>
|
||||
concept mcc_hardware_movement_state_c = std::formattable<T, char> && requires {
|
||||
[]() {
|
||||
// // mount axes were stopped
|
||||
// [[maybe_unused]] static constexpr auto v0 = T::HW_MOVE_STOPPED;
|
||||
|
||||
// // hardware was asked for slewing (move to given celestial point)
|
||||
// [[maybe_unused]] static constexpr auto v1 = T::HW_MOVE_SLEWING;
|
||||
|
||||
// // hardware was asked for adjusting after slewing
|
||||
// // (adjusting actual mount position to align with target celestial point at the end of slewing process)
|
||||
// [[maybe_unused]] static constexpr auto v2 = T::HW_MOVE_ADJUSTING;
|
||||
|
||||
// // hardware was asked for tracking (track target celestial point)
|
||||
// [[maybe_unused]] static constexpr auto v3 = T::HW_MOVE_TRACKING;
|
||||
|
||||
// // hardware was asked for guiding
|
||||
// // (small corrections to align actual mount position with target celestial point)
|
||||
// [[maybe_unused]] static constexpr auto v4 = T::HW_MOVE_GUIDING;
|
||||
|
||||
// // to detect possible hardware error
|
||||
// [[maybe_unused]] static constexpr auto v5 = T::HW_MOVE_ERROR;
|
||||
|
||||
[[maybe_unused]] static constexpr std::array arr{
|
||||
// mount hardware was asked to stop
|
||||
T::HW_MOVE_STOPPING,
|
||||
|
||||
// mount axes were stopped
|
||||
T::HW_MOVE_STOPPED,
|
||||
|
||||
// move to given celestial point
|
||||
T::HW_MOVE_SLEWING,
|
||||
|
||||
// adjusting after slewing
|
||||
T::HW_MOVE_ADJUSTING,
|
||||
|
||||
// tracking (track target celestial point)
|
||||
T::HW_MOVE_TRACKING,
|
||||
|
||||
// guiding (small corrections to align actual mount position with target celestial point)
|
||||
T::HW_MOVE_GUIDING,
|
||||
|
||||
// to detect possible hardware error
|
||||
T::HW_MOVE_ERROR};
|
||||
}();
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_hardware_state_c = requires(T state) {
|
||||
// encoder co-longitude and co-latiitude positions, as well as its measurement time point
|
||||
requires mcc_coord_pair_c<decltype(state.XY)> &&
|
||||
(decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_XY);
|
||||
|
||||
// co-longitude and co-latiitude axis angular speeds, as well as its measurement/computation time point
|
||||
requires mcc_coord_pair_c<decltype(state.speedXY)> &&
|
||||
(decltype(state.speedXY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
decltype(state.speedXY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_XY);
|
||||
|
||||
|
||||
requires mcc_hardware_movement_state_c<decltype(state.movementState)>;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_hardware_c = requires(T t) {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
// static const variable with name of hardware
|
||||
requires std::formattable<decltype(T::hardwareName), char> && std::is_const_v<decltype(T::hardwareName)>;
|
||||
|
||||
// a type that defines at least HW_MOVE_ERROR, HW_MOVE_STOPPING, HW_MOVE_STOPPED, HW_MOVE_SLEWING,
|
||||
// HW_MOVE_ADJUSTING, HW_MOVE_TRACKING and HW_MOVE_GUIDING compile-time constants. The main purpose of this type is
|
||||
// a possible tunning of hardware hardwareSetState-related commands and detect the stop and error states from
|
||||
// hardware
|
||||
//
|
||||
// e.g. an implementations can be as follows:
|
||||
// enum class hardware_movement_state_t: int {HW_MOVE_ERROR = -1, HW_MOVE_STOPPED = 0, HW_MOVE_STOPPING,
|
||||
// HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, HW_MOVE_TRACKING, HW_MOVE_GUIDING}
|
||||
//
|
||||
// struct hardware_movement_state_t {
|
||||
// static constexpr uint16_t HW_MOVE_STOPPED = 0;
|
||||
// static constexpr uint16_t HW_MOVE_SLEWING = 111;
|
||||
// static constexpr uint16_t HW_MOVE_ADJUSTING = 222;
|
||||
// static constexpr uint16_t HW_MOVE_TRACKING = 333;
|
||||
// static constexpr uint16_t HW_MOVE_GUIDING = 444;
|
||||
// static constexpr uint16_t HW_MOVE_ERROR = 555;
|
||||
// static constexpr uint16_t HW_MOVE_STOPPING = 666;
|
||||
// }
|
||||
|
||||
requires mcc_hardware_movement_state_c<typename T::hardware_movement_state_t>;
|
||||
|
||||
// requires requires(typename T::hardware_movement_state_t type) {
|
||||
// []() {
|
||||
// // mount axes were stopped
|
||||
// static constexpr auto v0 = T::hardware_movement_state_t::HW_MOVE_STOPPED;
|
||||
|
||||
// // hardware was asked for slewing (move to given celestial point)
|
||||
// static constexpr auto v1 = T::hardware_movement_state_t::HW_MOVE_SLEWING;
|
||||
|
||||
// // hardware was asked for adjusting after slewing
|
||||
// // (adjusting actual mount position to align with target celestial point at the end of slewing process)
|
||||
// static constexpr auto v2 = T::hardware_movement_state_t::HW_MOVE_ADJUSTING;
|
||||
|
||||
// // hardware was asked for tracking (track target celestial point)
|
||||
// static constexpr auto v3 = T::hardware_movement_state_t::HW_MOVE_TRACKING;
|
||||
|
||||
// // hardware was asked for guiding
|
||||
// // (small corrections to align actual mount position with target celestial point)
|
||||
// static constexpr auto v4 = T::hardware_movement_state_t::HW_MOVE_GUIDING;
|
||||
|
||||
// // to detect possible hardware error
|
||||
// static constexpr auto v5 = T::hardware_movement_state_t::HW_MOVE_ERROR;
|
||||
// }();
|
||||
// };
|
||||
|
||||
requires mcc_hardware_state_c<typename T::hardware_state_t> && requires(typename T::hardware_state_t state) {
|
||||
requires std::same_as<decltype(state.movementState), typename T::hardware_movement_state_t>;
|
||||
};
|
||||
|
||||
// requires requires(typename T::hardware_state_t state) {
|
||||
// // encoder co-longitude and co-latiitude positions, as well as its measurement time point
|
||||
// // the given constrains on coordinate pair kind can be used to deduce mount type
|
||||
// requires mcc_coord_pair_c<decltype(state.XY)> &&
|
||||
// ( // for equathorial mount:
|
||||
// decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_HADEC_OBS ||
|
||||
// // for alt-azimuthal mount:
|
||||
// decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_AZALT ||
|
||||
// decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_AZZD);
|
||||
|
||||
// // co-longitude and co-latiitude axis angular speeds, as well as its measurement/computation time point
|
||||
// requires mcc_coord_pair_c<decltype(state.speedXY)> &&
|
||||
// (decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
// decltype(state.XY)::pairKind == impl::MccCoordPairKind::COORDS_KIND_XY);
|
||||
|
||||
|
||||
// requires std::same_as<typename T::hardware_movement_state_t, decltype(state.movementState)>;
|
||||
// };
|
||||
|
||||
|
||||
// set hardware state:
|
||||
{ t.hardwareSetState(std::declval<typename T::hardware_state_t const&>()) } -> std::same_as<typename T::error_t>;
|
||||
|
||||
// get current state
|
||||
{ t.hardwareGetState(std::declval<typename T::hardware_state_t*>()) } -> std::same_as<typename T::error_t>;
|
||||
|
||||
// { t.hardwareStop() } -> std::same_as<typename T::error_t>; // stop any moving
|
||||
{ t.hardwareInit() } -> std::same_as<typename T::error_t>; // initialize hardware
|
||||
};
|
||||
|
||||
|
||||
/* MOUNT TELEMETRY DATA CLASS CONCEPT */
|
||||
|
||||
template <typename T>
|
||||
concept mcc_telemetry_data_c = requires(T t) {
|
||||
// target celestial point (position on sky where mount must be slewed)
|
||||
requires mcc_skypoint_c<decltype(t.targetPos)>;
|
||||
|
||||
// mount current celestial position
|
||||
requires mcc_skypoint_c<decltype(t.mountPos)>;
|
||||
|
||||
// hardware state
|
||||
requires mcc_hardware_state_c<decltype(t.hwState)>;
|
||||
|
||||
// corrections to transform hardware encoder coordinates to observed celestial ones
|
||||
requires mcc_pcm_result_c<decltype(t.pcmCorrection)>;
|
||||
|
||||
// requires mcc_angle_c<decltype(t.pcmX)>; // PCM correction along X-axis
|
||||
// requires mcc_angle_c<decltype(t.pcmY)>; // PCM correction along Y-axis
|
||||
|
||||
// // atmospheric refraction correction for current zenithal distance
|
||||
// requires mcc_angle_c<decltype(t.refCorr)>;
|
||||
|
||||
|
||||
// // current local apparent sideral time
|
||||
// requires mcc_angle_c<decltype(t.LST)>;
|
||||
|
||||
// // equation of the origins (ERA-GST)
|
||||
// requires mcc_angle_c<decltype(t.EO)>;
|
||||
|
||||
// // target celestial point
|
||||
// { t.targetPos() } -> mcc_skypoint_c;
|
||||
|
||||
// // mount current celestial position
|
||||
// { t.mountPos() } -> mcc_skypoint_c;
|
||||
|
||||
// // hardware state
|
||||
// { t.hwState() } -> mcc_hardware_c;
|
||||
|
||||
// // corrections to transform hardware encoder coordinates to observed celestial ones
|
||||
// { t.pcmData() } -> mcc_pcm_result_c;
|
||||
|
||||
// // // atmospheric refraction correction for current mount zenithal distance
|
||||
// // { t.refractionCorr() } -> mcc_angle_c;
|
||||
|
||||
// // current local apparent sideral time
|
||||
// { t.LST() } -> mcc_angle_c;
|
||||
|
||||
// // equation of the origins (ERA-GST)
|
||||
// { t.EO() } -> mcc_angle_c;
|
||||
};
|
||||
|
||||
|
||||
/* MOUNT TELEMETRY CLASS CONCEPT */
|
||||
|
||||
template <typename RetT>
|
||||
struct mcc_telemetry_interface_t {
|
||||
virtual ~mcc_telemetry_interface_t() = default;
|
||||
|
||||
// set target position
|
||||
template <std::derived_from<mcc_telemetry_interface_t> SelfT>
|
||||
RetT setTarget(this SelfT&& self, mcc_skypoint_c auto const& pt)
|
||||
{
|
||||
return std::forward<SelfT>(self).setTarget(pt);
|
||||
}
|
||||
|
||||
// get entered target position
|
||||
template <std::derived_from<mcc_telemetry_interface_t> SelfT>
|
||||
RetT setTarget(this SelfT&& self, mcc_skypoint_c auto* pt)
|
||||
{
|
||||
return std::forward<SelfT>(self).getTarget(pt);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept mcc_telemetry_c = std::derived_from<T, mcc_telemetry_interface_t<typename T::error_t>> && requires(T t) {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
// telemetry data type definition
|
||||
requires mcc_telemetry_data_c<typename T::telemetry_data_t>;
|
||||
|
||||
// get telemetry data
|
||||
{ t.telemetryData(std::declval<typename T::telemetry_data_t*>()) } -> std::same_as<typename T::error_t>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* PROHIBITED ZONE CLASS CONCEPT */
|
||||
|
||||
|
||||
enum class MccProhibitedZonePolicy : int {
|
||||
PZ_POLICY_STOP, // stop mount near the zone
|
||||
PZ_POLICY_FLIP // flip mount, e.g., near the meridian, near HA-axis encoder limit switch
|
||||
};
|
||||
|
||||
|
||||
template <typename RetT>
|
||||
struct mcc_pzone_interface_t {
|
||||
virtual ~mcc_pzone_interface_t() = default;
|
||||
|
||||
template <std::derived_from<mcc_pzone_interface_t> SelfT>
|
||||
RetT inPZone(this SelfT&& self, mcc_skypoint_c auto const& coords, bool* result)
|
||||
{
|
||||
return std::forward<SelfT>(self).inPZone(coords, result);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_pzone_interface_t> SelfT>
|
||||
RetT timeToPZone(this SelfT&& self, mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
|
||||
{
|
||||
return std::forward<SelfT>(self).timeToPZone(coords, res_time);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_pzone_interface_t> SelfT>
|
||||
RetT timeFromPZone(this SelfT&& self, mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
|
||||
{
|
||||
return std::forward<SelfT>(self).timeFromPZone(coords, res_time);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_pzone_c = std::derived_from<T, mcc_pzone_interface_t<typename T::error_t>> && requires(T t) {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
// static constant member with prohibitted zone name
|
||||
requires std::formattable<decltype(T::pzoneName), char> && std::is_const_v<decltype(T::pzoneName)>;
|
||||
|
||||
// the 'T' class must contain static constexpr member of 'MccProhibitedZonePolicy' type
|
||||
requires std::same_as<decltype(T::pzPolicy), const MccProhibitedZonePolicy>;
|
||||
[]() {
|
||||
[[maybe_unused]] static constexpr MccProhibitedZonePolicy val = T::pzPolicy;
|
||||
}(); // to ensure 'pzPolicy' can be used in compile-time context
|
||||
};
|
||||
|
||||
|
||||
/* PROHIBITED ZONES CONTAINER CLASS CONCEPT */
|
||||
|
||||
template <mcc_error_c RetT>
|
||||
struct mcc_pzone_container_interface_t {
|
||||
virtual ~mcc_pzone_container_interface_t() = default;
|
||||
|
||||
|
||||
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
|
||||
size_t addPZone(this SelfT&& self, mcc_pzone_c auto zone)
|
||||
{
|
||||
return std::forward<SelfT>(self).addPZone(std::move(zone));
|
||||
}
|
||||
|
||||
|
||||
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
|
||||
void clearPZones(this SelfT&& self)
|
||||
{
|
||||
return std::forward<SelfT>(self).clearPZones();
|
||||
}
|
||||
|
||||
|
||||
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
|
||||
size_t sizePZones(this SelfT&& self)
|
||||
{
|
||||
return std::forward<SelfT>(self).sizePZones();
|
||||
}
|
||||
|
||||
|
||||
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
|
||||
RetT inPZone(this SelfT&& self,
|
||||
mcc_skypoint_c auto const& coords,
|
||||
bool* at_least_one,
|
||||
std::ranges::output_range<bool> auto* result)
|
||||
{
|
||||
return std::forward<SelfT>(self).inPZone(coords, at_least_one, result);
|
||||
}
|
||||
|
||||
|
||||
template <std::derived_from<mcc_pzone_container_interface_t> SelfT, traits::mcc_time_duration_c DT>
|
||||
RetT timeToPZone(this SelfT&& self, mcc_skypoint_c auto const& coords, std::ranges::output_range<DT> auto* res_time)
|
||||
{
|
||||
return std::forward<SelfT>(self).timeToPZone(coords, res_time);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_pzone_container_interface_t> SelfT, traits::mcc_time_duration_c DT>
|
||||
RetT timeFromPZone(this SelfT&& self,
|
||||
mcc_skypoint_c auto const& coords,
|
||||
std::ranges::output_range<DT> auto* res_time)
|
||||
{
|
||||
return std::forward<SelfT>(self).timeFromPZone(coords, res_time);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_pzone_container_c = std::derived_from<T, mcc_pzone_container_interface_t<typename T::error_t>> && requires {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* A CONCEPT FOR MOUNT MOVEMENT CONTROLS CLASS */
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_movement_controls_c = requires(T t) {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
// movement parameters holder type
|
||||
typename T::movement_params_t;
|
||||
|
||||
// argument of the method:
|
||||
// true - slew and stop
|
||||
// false - slew and track
|
||||
{ t.slewToTarget(std::declval<bool>()) } -> std::same_as<typename T::error_t>;
|
||||
|
||||
{ t.trackTarget() } -> std::same_as<typename T::error_t>;
|
||||
|
||||
{ t.stopMount() } -> std::same_as<typename T::error_t>;
|
||||
|
||||
{ t.setMovementParams(std::declval<typename T::movement_params_t const&>()) } -> std::same_as<typename T::error_t>;
|
||||
|
||||
{ t.getMovementParams() } -> std::same_as<typename T::movement_params_t>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* GENERIC MOUNT CLASS CONCEPT */
|
||||
|
||||
|
||||
// minimal set of the mount status constants
|
||||
template <typename T>
|
||||
concept mcc_mount_status_c = requires {
|
||||
[]() {
|
||||
[[maybe_unused]]
|
||||
static constexpr std::array arr = {T::MOUNT_STATUS_ERROR, T::MOUNT_STATUS_IDLE,
|
||||
T::MOUNT_STATUS_INITIALIZATION, T::MOUNT_STATUS_STOPPED,
|
||||
T::MOUNT_STATUS_SLEWING, T::MOUNT_STATUS_ADJUSTING,
|
||||
T::MOUNT_STATUS_GUIDING, T::MOUNT_STATUS_TRACKING};
|
||||
}; // to ensure mount status is compile-time constants
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_generic_mount_c = mcc_logger_c<T> && mcc_pzone_container_c<T> && mcc_telemetry_c<T> &&
|
||||
mcc_movement_controls_c<T> && requires(T t, const T t_const) {
|
||||
// error type
|
||||
requires mcc_error_c<typename T::error_t>;
|
||||
|
||||
requires mcc_mount_status_c<typename T::mount_status_t>;
|
||||
|
||||
{ t.initMount() } -> std::same_as<typename T::error_t>;
|
||||
|
||||
{ t_const.mountStatus() } -> std::same_as<typename T::mount_status_t>;
|
||||
};
|
||||
|
||||
|
||||
} // namespace mcc
|
||||
48
include/mcc/mcc_constants.h
Normal file
48
include/mcc/mcc_constants.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* LIBRARY-WIDE CONSTANTS *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
#include <numbers>
|
||||
|
||||
#include "mcc_traits.h"
|
||||
|
||||
namespace mcc
|
||||
{
|
||||
|
||||
static constexpr double MCC_DEGRESS_TO_RADS = std::numbers::pi / 180.0;
|
||||
static constexpr double MCC_RADS_TO_DEGRESS = 1.0 / MCC_DEGRESS_TO_RADS;
|
||||
|
||||
static constexpr double MCC_HALF_PI = std::numbers::pi / 2.0;
|
||||
static constexpr double MCC_TWO_PI = std::numbers::pi * 2.0;
|
||||
|
||||
static constexpr double MCC_SIDERAL_TO_UT1_RATIO = 1.002737909350795; // sideral/UT1
|
||||
|
||||
static constexpr double MCC_J2000_MJD = 51544.5;
|
||||
static constexpr double MCC_MJD_ZERO = 2400000.5;
|
||||
|
||||
|
||||
// a value to represent of infinite time duration according to type of duration representation
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
static constexpr DT MCC_INFINITE_DURATION_V =
|
||||
std::floating_point<typename DT::rep> ? DT{std::numeric_limits<typename DT::rep>::infinity()}
|
||||
: DT{std::numeric_limits<typename DT::rep>::max()};
|
||||
|
||||
|
||||
// serializer/deserializer delimiters
|
||||
|
||||
// delimiter between items of serializing values sequence
|
||||
static constexpr std::string_view MCC_DEFAULT_SEQ_DELIMITER{";"};
|
||||
|
||||
// delimiter between items of aggregative (multi-element) serializing value
|
||||
static constexpr std::string_view MCC_DEFAULT_ELEM_DELIMITER{","};
|
||||
|
||||
} // namespace mcc
|
||||
996
include/mcc/mcc_coordinate.h
Normal file
996
include/mcc/mcc_coordinate.h
Normal file
@@ -0,0 +1,996 @@
|
||||
#pragma once
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF CELESTIAL COORDINATES *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
// #include "mcc_angle.h"
|
||||
#include "mcc_ccte_erfa.h"
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_epoch.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
|
||||
/* CLASSES TO REPRESENT COORDINATES PAIR */
|
||||
|
||||
template <mcc_angle_c CO_LON_T, mcc_angle_c CO_LAT_T>
|
||||
class MccCoordPair : public mcc_coord_pair_interface_t
|
||||
{
|
||||
public:
|
||||
typedef CO_LON_T x_t;
|
||||
typedef CO_LAT_T y_t;
|
||||
|
||||
static constexpr MccCoordPairKind pairKind =
|
||||
!(std::derived_from<CO_LON_T, MccAngle> ||
|
||||
std::derived_from<CO_LAT_T, MccAngle>) // unknown type (possibly just double or float)
|
||||
? MccCoordPairKind::COORDS_KIND_GENERIC
|
||||
: (std::same_as<CO_LON_T, MccAngle> || std::same_as<CO_LAT_T, MccAngle>) // one of the types is MccAngle
|
||||
? MccCoordPairKind::COORDS_KIND_GENERIC
|
||||
// ICRS RA and DEC
|
||||
: (std::same_as<CO_LON_T, MccAngleRA_ICRS> && std::same_as<CO_LAT_T, MccAngleDEC_ICRS>)
|
||||
? MccCoordPairKind::COORDS_KIND_RADEC_ICRS
|
||||
// apparent RA and DEC
|
||||
: (std::same_as<CO_LON_T, MccAngleRA_APP> && std::same_as<CO_LAT_T, MccAngleDEC_APP>)
|
||||
? MccCoordPairKind::COORDS_KIND_RADEC_APP
|
||||
// observed RA and DEC
|
||||
: (std::same_as<CO_LON_T, MccAngleRA_OBS> && std::same_as<CO_LAT_T, MccAngleDEC_OBS>)
|
||||
? MccCoordPairKind::COORDS_KIND_RADEC_OBS
|
||||
// apparent HA and DEC
|
||||
: (std::same_as<CO_LON_T, MccAngleHA_APP> && std::same_as<CO_LAT_T, MccAngleDEC_APP>)
|
||||
? MccCoordPairKind::COORDS_KIND_HADEC_APP
|
||||
// observed HA and DEC
|
||||
: (std::same_as<CO_LON_T, MccAngleHA_OBS> && std::same_as<CO_LAT_T, MccAngleDEC_OBS>)
|
||||
? MccCoordPairKind::COORDS_KIND_HADEC_OBS
|
||||
// apparent AZ and ZD
|
||||
: (std::same_as<CO_LON_T, MccAngleAZ> && std::same_as<CO_LAT_T, MccAngleZD>)
|
||||
? MccCoordPairKind::COORDS_KIND_AZZD
|
||||
// apparent AZ and ALT
|
||||
: (std::same_as<CO_LON_T, MccAngleAZ> && std::same_as<CO_LAT_T, MccAngleALT>)
|
||||
? MccCoordPairKind::COORDS_KIND_AZALT
|
||||
// general purpose X and Y
|
||||
: (std::same_as<CO_LON_T, MccAngleX> && std::same_as<CO_LAT_T, MccAngleY>)
|
||||
? MccCoordPairKind::COORDS_KIND_XY
|
||||
// geographical longitude and latitude
|
||||
: (std::same_as<CO_LON_T, MccAngleLON> && std::same_as<CO_LAT_T, MccAngleLAT>)
|
||||
? MccCoordPairKind::COORDS_KIND_LONLAT
|
||||
: MccCoordPairKind::COORDS_KIND_UNKNOWN;
|
||||
|
||||
MccCoordPair() : _x(0.0), _y(0.0), _epoch(MccCelestialCoordEpoch::now()) {}
|
||||
|
||||
template <mcc_coord_epoch_c EpT = MccCelestialCoordEpoch>
|
||||
MccCoordPair(CO_LON_T const& x, CO_LAT_T const& y, EpT const& epoch = EpT::now()) : _x(x), _y(y), _epoch(epoch)
|
||||
{
|
||||
normalize();
|
||||
}
|
||||
|
||||
MccCoordPair(const MccCoordPair&) = default;
|
||||
MccCoordPair(MccCoordPair&&) = default;
|
||||
|
||||
MccCoordPair& operator=(const MccCoordPair&) = default;
|
||||
MccCoordPair& operator=(MccCoordPair&&) = default;
|
||||
|
||||
template <mcc_coord_pair_c T>
|
||||
requires(T::pairKind == pairKind || T::pairKind == MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
T::pairKind == MccCoordPairKind::COORDS_KIND_XY)
|
||||
MccCoordPair(const T& other)
|
||||
{
|
||||
setX((double)other.x());
|
||||
setY((double)other.y());
|
||||
setEpoch(other.epoch());
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
|
||||
template <mcc_coord_pair_c T>
|
||||
requires(T::pairKind == pairKind || T::pairKind == MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
T::pairKind == MccCoordPairKind::COORDS_KIND_XY)
|
||||
MccCoordPair(T&& other)
|
||||
{
|
||||
setX((double)other.x());
|
||||
setY((double)other.y());
|
||||
setEpoch(other.epoch());
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
|
||||
template <mcc_coord_pair_c T>
|
||||
requires(T::pairKind == pairKind || T::pairKind == MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
T::pairKind == MccCoordPairKind::COORDS_KIND_XY)
|
||||
MccCoordPair& operator=(const T& other)
|
||||
{
|
||||
setX((double)other.x());
|
||||
setY((double)other.y());
|
||||
setEpoch(other.epoch());
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
|
||||
template <mcc_coord_pair_c T>
|
||||
requires(T::pairKind == pairKind || T::pairKind == MccCoordPairKind::COORDS_KIND_GENERIC ||
|
||||
T::pairKind == MccCoordPairKind::COORDS_KIND_XY)
|
||||
MccCoordPair& operator=(T&& other)
|
||||
{
|
||||
setX((double)other.x());
|
||||
setY((double)other.y());
|
||||
setEpoch(other.epoch());
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
|
||||
virtual ~MccCoordPair() = default;
|
||||
|
||||
CO_LON_T x() const
|
||||
{
|
||||
return _x;
|
||||
}
|
||||
|
||||
CO_LAT_T y() const
|
||||
{
|
||||
return _y;
|
||||
}
|
||||
|
||||
|
||||
MccCelestialCoordEpoch epoch() const
|
||||
{
|
||||
return _epoch;
|
||||
}
|
||||
|
||||
template <mcc_coord_epoch_c EpT>
|
||||
EpT epoch() const
|
||||
{
|
||||
return _epoch;
|
||||
}
|
||||
|
||||
|
||||
double MJD() const
|
||||
{
|
||||
return _epoch.MJD();
|
||||
}
|
||||
|
||||
// for something like:
|
||||
// auto [ra, dec, epoch] = coord_pair;
|
||||
operator std::tuple<CO_LON_T, CO_LAT_T, MccCelestialCoordEpoch>() const
|
||||
{
|
||||
return {_x, _y, _epoch};
|
||||
}
|
||||
|
||||
void setX(const CO_LON_T& x)
|
||||
{
|
||||
_x = x;
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
void setY(const CO_LAT_T& y)
|
||||
{
|
||||
_y = y;
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
void setEpoch(mcc_coord_epoch_c auto const& ep)
|
||||
{
|
||||
_epoch = ep;
|
||||
}
|
||||
|
||||
protected:
|
||||
CO_LON_T _x;
|
||||
CO_LAT_T _y;
|
||||
|
||||
MccCelestialCoordEpoch _epoch;
|
||||
|
||||
void normalize()
|
||||
{
|
||||
if constexpr (pairKind != MccCoordPairKind::COORDS_KIND_GENERIC &&
|
||||
pairKind != MccCoordPairKind::COORDS_KIND_XY) {
|
||||
if constexpr (pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP ||
|
||||
pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
|
||||
_x = (double)MccAngle(_x).normalize<MccAngle::NORM_KIND_180_180>();
|
||||
} else { // RA, AZ
|
||||
_x = (double)MccAngle(_x).normalize<MccAngle::NORM_KIND_0_360>();
|
||||
}
|
||||
|
||||
// DEC and ALT is [-90,90] degrees
|
||||
if constexpr (pairKind != MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
_y = (double)MccAngle(_y).normalize<MccAngle::NORM_KIND_90_90>();
|
||||
} else { // ZD id [0, 180] degrees
|
||||
_y = (double)MccAngle(_y).normalize<MccAngle::NORM_KIND_0_180>();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static_assert(mcc_coord_pair_c<MccCoordPair<MccAngleRA_ICRS, MccAngleDEC_ICRS>>, "");
|
||||
|
||||
|
||||
/* PREDEFINED COORDINATES PAIR TYPES */
|
||||
|
||||
|
||||
|
||||
struct MccSkyRADEC_ICRS : MccCoordPair<MccAngleRA_ICRS, MccAngleDEC_ICRS> {
|
||||
// re-implement constructors to keep the epoch equal to J2000.0
|
||||
|
||||
MccSkyRADEC_ICRS() : MccCoordPair<MccAngleRA_ICRS, MccAngleDEC_ICRS>(0.0, 0.0, MccCelestialCoordEpoch{}) {}
|
||||
|
||||
MccSkyRADEC_ICRS(MccAngleRA_ICRS const& x, MccAngleDEC_ICRS const& y)
|
||||
: MccCoordPair<MccAngleRA_ICRS, MccAngleDEC_ICRS>((double)x, (double)y, MccCelestialCoordEpoch{})
|
||||
{
|
||||
}
|
||||
|
||||
// ignore epoch setting (it is always J2000.0)
|
||||
void setEpoch(mcc_coord_epoch_c auto const&)
|
||||
{
|
||||
static_assert(false, "CANNOT SET EPOCH FOR ICRS-KIND COORDINATE PAIR!!!");
|
||||
}
|
||||
};
|
||||
|
||||
using MccSkyRADEC_APP = MccCoordPair<MccAngleRA_APP, MccAngleDEC_APP>;
|
||||
|
||||
using MccSkyRADEC_OBS = MccCoordPair<MccAngleRA_OBS, MccAngleDEC_OBS>;
|
||||
|
||||
using MccSkyHADEC_APP = MccCoordPair<MccAngleHA_APP, MccAngleDEC_APP>;
|
||||
|
||||
using MccSkyHADEC_OBS = MccCoordPair<MccAngleHA_OBS, MccAngleDEC_OBS>;
|
||||
|
||||
// using MccSkyAZZD = MccCoordPair<MccAngleAZ, MccAngleZD>;
|
||||
struct MccSkyAZZD : MccCoordPair<MccAngleAZ, MccAngleZD> {
|
||||
using MccCoordPair<MccAngleAZ, MccAngleZD>::MccCoordPair;
|
||||
|
||||
template <mcc_coord_pair_c AZALT_PAIR_T>
|
||||
requires(AZALT_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZALT)
|
||||
MccSkyAZZD(AZALT_PAIR_T const& azalt) : MccSkyAZZD()
|
||||
{
|
||||
setX((double)azalt.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azalt.x());
|
||||
setY(MCC_HALF_PI - (double)azalt.x());
|
||||
setEpoch(azalt.epoch());
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c AZALT_PAIR_T>
|
||||
requires(AZALT_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZALT)
|
||||
MccSkyAZZD(AZALT_PAIR_T&& azalt) : MccSkyAZZD()
|
||||
{
|
||||
setX((double)azalt.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azalt.x());
|
||||
setY(MCC_HALF_PI - (double)azalt.x());
|
||||
setEpoch(azalt.epoch());
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c AZALT_PAIR_T>
|
||||
requires(AZALT_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZALT)
|
||||
MccSkyAZZD& operator=(AZALT_PAIR_T const& azalt)
|
||||
{
|
||||
setX((double)azalt.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azalt.x());
|
||||
setY(MCC_HALF_PI - (double)azalt.x());
|
||||
setEpoch(azalt.epoch());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c AZALT_PAIR_T>
|
||||
requires(AZALT_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZALT)
|
||||
MccSkyAZZD& operator=(AZALT_PAIR_T&& azalt)
|
||||
{
|
||||
setX((double)azalt.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azalt.x());
|
||||
setY(MCC_HALF_PI - (double)azalt.x());
|
||||
setEpoch(azalt.epoch());
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// using MccSkyAZALT = MccCoordPair<MccAngleAZ, MccAngleALT>;
|
||||
struct MccSkyAZALT : MccCoordPair<MccAngleAZ, MccAngleALT> {
|
||||
using MccCoordPair<MccAngleAZ, MccAngleALT>::MccCoordPair;
|
||||
|
||||
template <mcc_coord_pair_c AZZD_PAIR_T>
|
||||
requires(AZZD_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZZD)
|
||||
MccSkyAZALT(AZZD_PAIR_T const& azzd) : MccSkyAZALT()
|
||||
{
|
||||
setX((double)azzd.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azzd.x());
|
||||
setY(MCC_HALF_PI - (double)azzd.x());
|
||||
setEpoch(azzd.epoch());
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c AZZD_PAIR_T>
|
||||
requires(AZZD_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZZD)
|
||||
MccSkyAZALT(AZZD_PAIR_T&& azzd) : MccSkyAZALT()
|
||||
{
|
||||
setX((double)azzd.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azzd.x());
|
||||
setY(MCC_HALF_PI - (double)azzd.x());
|
||||
setEpoch(azzd.epoch());
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c AZZD_PAIR_T>
|
||||
requires(AZZD_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZZD)
|
||||
MccSkyAZALT& operator=(AZZD_PAIR_T const& azzd)
|
||||
{
|
||||
setX((double)azzd.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azzd.x());
|
||||
setY(MCC_HALF_PI - (double)azzd.x());
|
||||
setEpoch(azzd.epoch());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c AZZD_PAIR_T>
|
||||
requires(AZZD_PAIR_T::pairKind == MccCoordPairKind::COORDS_KIND_AZZD)
|
||||
MccSkyAZALT& operator=(AZZD_PAIR_T&& azzd)
|
||||
{
|
||||
setX((double)azzd.x());
|
||||
// setY(std::numbers::pi / 2.0 - (double)azzd.x());
|
||||
setY(MCC_HALF_PI - (double)azzd.x());
|
||||
setEpoch(azzd.epoch());
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
using MccGenXY = MccCoordPair<MccAngleX, MccAngleY>;
|
||||
|
||||
using MccGeoLONLAT = MccCoordPair<MccAngleLON, MccAngleLAT>;
|
||||
|
||||
|
||||
static MccSkyHADEC_APP hadec = MccGenXY{};
|
||||
static MccSkyAZALT azalt{MccSkyAZZD{1.0, 1.1}};
|
||||
|
||||
|
||||
/* MCC-LIBRARY DEFAULT GENERIC SKY POINT CLASS IMPLEMENTATION */
|
||||
|
||||
|
||||
|
||||
template <mcc_ccte_c CCTE_T>
|
||||
class MccGenericSkyPoint : public mcc_skypoint_interface_t
|
||||
{
|
||||
public:
|
||||
typedef CCTE_T ccte_t;
|
||||
|
||||
static constexpr double MJD0 = 2400000.5;
|
||||
|
||||
inline static CCTE_T cctEngine{}; // celestial coordinates transformation engine
|
||||
|
||||
using error_t = typename CCTE_T::error_t;
|
||||
|
||||
MccGenericSkyPoint() {}
|
||||
|
||||
template <mcc_coord_pair_c PT>
|
||||
MccGenericSkyPoint(const PT& coord_pair) : MccGenericSkyPoint()
|
||||
{
|
||||
auto self = from(coord_pair);
|
||||
}
|
||||
|
||||
MccGenericSkyPoint(const MccGenericSkyPoint&) = default;
|
||||
MccGenericSkyPoint(MccGenericSkyPoint&&) = default;
|
||||
|
||||
MccGenericSkyPoint& operator=(const MccGenericSkyPoint&) = default;
|
||||
MccGenericSkyPoint& operator=(MccGenericSkyPoint&&) = default;
|
||||
|
||||
|
||||
MccGenericSkyPoint(mcc_skypoint_c auto const& other)
|
||||
{
|
||||
fromOtherSkyPoint(other);
|
||||
}
|
||||
|
||||
MccGenericSkyPoint(mcc_skypoint_c auto&& other)
|
||||
{
|
||||
fromOtherSkyPoint(other);
|
||||
}
|
||||
|
||||
|
||||
MccGenericSkyPoint& operator=(mcc_skypoint_c auto const& other)
|
||||
{
|
||||
fromOtherSkyPoint(other);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
MccGenericSkyPoint& operator=(mcc_skypoint_c auto&& other)
|
||||
{
|
||||
fromOtherSkyPoint(other);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
virtual ~MccGenericSkyPoint() = default;
|
||||
|
||||
MccCoordPairKind pairKind() const
|
||||
{
|
||||
return _pairKind;
|
||||
}
|
||||
|
||||
MccCelestialCoordEpoch epoch() const
|
||||
{
|
||||
return _epoch;
|
||||
}
|
||||
|
||||
template <mcc_coord_pair_c PT>
|
||||
MccGenericSkyPoint& from(const PT& coord_pair)
|
||||
{
|
||||
_x = coord_pair.x();
|
||||
_y = coord_pair.y();
|
||||
|
||||
_pairKind = PT::pairKind;
|
||||
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
_epoch = MccCelestialCoordEpoch(); // J2000.0
|
||||
} else {
|
||||
_epoch.fromMJD(coord_pair.MJD());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
MccGenericSkyPoint& operator=(mcc_coord_pair_c auto const& coord_pair)
|
||||
{
|
||||
return from(coord_pair);
|
||||
}
|
||||
|
||||
|
||||
template <mcc_coord_pair_c PT, mcc_coord_pair_c... PTs>
|
||||
error_t to(PT& cpair, PTs&... cpairs) const
|
||||
{
|
||||
auto err = toHelper(cpair);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(PTs)) {
|
||||
err = to(cpairs...);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// according to mcc_error_c concept (see mcc_concepts.h)
|
||||
// default-constructed mcc_error_c-like class must be assumed as
|
||||
// non-error state
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
|
||||
template <mcc_coord_pair_c PT>
|
||||
operator PT() const
|
||||
{
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_LONLAT) { // returns geographic site coordinates
|
||||
std::pair<double, double> pos;
|
||||
cctEngine.geoPosition(&pos);
|
||||
|
||||
return MccGeoLONLAT(pos.first, pos.second);
|
||||
}
|
||||
|
||||
|
||||
PT res;
|
||||
toAtSameEpoch(res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
template <mcc_coord_pair_c PT, mcc_coord_pair_c... PTs>
|
||||
error_t toAtSameEpoch(PT& cpair, PTs&... cpairs) const
|
||||
{
|
||||
if constexpr (PT::pairKind != MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
if (_pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { // from ICRS: set current epoch for result
|
||||
cpair.setEpoch(MccCelestialCoordEpoch::now());
|
||||
} else {
|
||||
cpair.setEpoch(_epoch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto err = toHelper(cpair);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(PTs)) {
|
||||
err = toAtSameEpoch(cpairs...);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// according to mcc_error_c concept (see mcc_concepts.h)
|
||||
// default-constructed mcc_error_c-like class must be assumed as
|
||||
// non-error state
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
|
||||
error_t refractCorrection(mcc_angle_c auto* dZ) const
|
||||
{
|
||||
if (mcc_is_obs_coordpair(_pairKind)) {
|
||||
if (_pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
return cctEngine.refractionCorrection(_y, dZ);
|
||||
} else if (_pairKind == MccCoordPairKind::COORDS_KIND_AZALT) {
|
||||
return cctEngine.refractionCorrection(MCC_HALF_PI - _y, dZ);
|
||||
} else {
|
||||
MccSkyAZZD azzd;
|
||||
auto err = toAtSameEpoch(azzd);
|
||||
if (!err) {
|
||||
err = cctEngine.refractionCorrection(azzd.y(), dZ);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
if (dZ) {
|
||||
*dZ = 0.0;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
error_t refractInverseCorrection(mcc_angle_c auto* dZ) const
|
||||
{
|
||||
double phi = cctEngine.getStateERFA().lat;
|
||||
double ha = _x, dec = _y;
|
||||
|
||||
if (mcc_is_app_coordpair(_pairKind)) {
|
||||
double az, alt;
|
||||
|
||||
if (_pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
|
||||
double eo, lst;
|
||||
|
||||
auto ccte_err = cctEngine.equationOrigins(_epoch, &eo);
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
|
||||
ccte_err = cctEngine.apparentSideralTime(_epoch, &lst, true);
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
|
||||
// from RA to HA
|
||||
ha = lst + eo - _x;
|
||||
}
|
||||
|
||||
hadec2azalt(ha, dec, phi, &az, &alt);
|
||||
|
||||
return cctEngine.refractionInverseCorrection(MCC_HALF_PI - alt, dZ);
|
||||
} else {
|
||||
if (dZ) {
|
||||
*dZ = 0.0;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
error_t appSideralTime(mcc_angle_c auto* st, bool is_local = false) const
|
||||
{
|
||||
return cctEngine.apparentSideralTime(_epoch, st, is_local);
|
||||
}
|
||||
|
||||
|
||||
error_t EO(mcc_angle_c auto* eo) const
|
||||
{
|
||||
return cctEngine.equationOrigins(_epoch, eo);
|
||||
}
|
||||
|
||||
protected:
|
||||
double _x{0.0}, _y{0.0};
|
||||
MccCoordPairKind _pairKind{MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
|
||||
MccCelestialCoordEpoch _epoch{}; // J2000.0
|
||||
|
||||
template <mcc_skypoint_c T>
|
||||
void fromOtherSkyPoint(T&& other)
|
||||
{
|
||||
switch (other.pairKind()) {
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS: {
|
||||
MccSkyRADEC_ICRS pt;
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_OBS: {
|
||||
MccSkyRADEC_OBS pt;
|
||||
pt.setEpoch(other.epoch());
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_APP: {
|
||||
MccSkyRADEC_APP pt;
|
||||
pt.setEpoch(other.epoch());
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_OBS: {
|
||||
MccSkyHADEC_OBS pt;
|
||||
pt.setEpoch(other.epoch());
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_APP: {
|
||||
MccSkyHADEC_APP pt;
|
||||
pt.setEpoch(other.epoch());
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
case MccCoordPairKind::COORDS_KIND_AZALT: {
|
||||
MccSkyAZALT pt;
|
||||
pt.setEpoch(other.epoch());
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
case MccCoordPairKind::COORDS_KIND_AZZD: {
|
||||
MccSkyAZZD pt;
|
||||
pt.setEpoch(other.epoch());
|
||||
|
||||
other.to(pt);
|
||||
from(pt);
|
||||
} break;
|
||||
default:
|
||||
// error!!!
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// HA, DEC to AZ, ALT (AZ from the South through the West)
|
||||
void hadec2azalt(double ha, double dec, double phi, double& az, double& alt) const
|
||||
{
|
||||
// eraHd2ae(ha, dec, phi, &az, &alt);
|
||||
// // from ERFA "from N" to "from S"
|
||||
// if (az > std::numbers::pi) {
|
||||
// az -= std::numbers::pi;
|
||||
// } else {
|
||||
// az += std::numbers::pi;
|
||||
// }
|
||||
|
||||
// return;
|
||||
|
||||
const auto cos_phi = std::cos(phi), sin_phi = std::sin(phi);
|
||||
const auto cos_dec = std::cos(dec), sin_dec = std::sin(dec);
|
||||
const auto cos_ha = std::cos(ha), sin_ha = std::sin(ha);
|
||||
|
||||
auto x = sin_phi * cos_dec * cos_ha - cos_phi * sin_dec;
|
||||
auto y = cos_dec * sin_ha;
|
||||
auto z = cos_phi * cos_dec * cos_ha + sin_phi * sin_dec;
|
||||
|
||||
auto xx = x * x, yy = y * y;
|
||||
decltype(x) r;
|
||||
if (xx < yy) {
|
||||
r = std::abs(y) * sqrt(1.0 + xx / yy);
|
||||
} else {
|
||||
r = std::abs(x) * sqrt(1.0 + yy / xx);
|
||||
}
|
||||
|
||||
az = utils::isEqual(r, 0.0) ? 0.0 : std::atan2(y, x);
|
||||
if (az < 0.0) {
|
||||
az += MCC_TWO_PI; // to range of [0, 2*PI]
|
||||
}
|
||||
|
||||
alt = std::atan2(z, r);
|
||||
};
|
||||
|
||||
|
||||
// AZ, ALT to HA, DEC (AZ from the South through the West)
|
||||
void azalt2hadec(double az, double alt, double phi, double& ha, double& dec) const
|
||||
{
|
||||
// az += std::numbers::pi;
|
||||
// eraAe2hd(az, alt, phi, &ha, &dec);
|
||||
|
||||
// return;
|
||||
|
||||
const auto cos_phi = std::cos(phi), sin_phi = std::sin(phi);
|
||||
const auto cos_az = std::cos(az), sin_az = std::sin(az);
|
||||
const auto cos_alt = std::cos(alt), sin_alt = std::sin(alt);
|
||||
|
||||
auto x = sin_phi * cos_alt * cos_az + cos_phi * sin_alt;
|
||||
auto y = cos_alt * sin_az;
|
||||
auto z = -cos_phi * cos_alt * cos_az + sin_phi * sin_alt;
|
||||
|
||||
auto xx = x * x, yy = y * y;
|
||||
decltype(x) r;
|
||||
if (xx < yy) {
|
||||
r = std::abs(y) * sqrt(1.0 + xx / yy);
|
||||
} else {
|
||||
r = std::abs(x) * sqrt(1.0 + yy / xx);
|
||||
}
|
||||
|
||||
ha = utils::isEqual(r, 0.0) ? 0.0 : std::atan2(y, x);
|
||||
dec = std::atan2(z, r);
|
||||
};
|
||||
|
||||
|
||||
template <mcc_coord_pair_c PT>
|
||||
error_t toHelper(PT& cpair) const
|
||||
{
|
||||
typename CCTE_T::error_t ccte_err;
|
||||
|
||||
double phi = cctEngine.getStateERFA().lat;
|
||||
double ra_icrs, dec_icrs, ra, dec, ha, az, zd, alt, lst, eo;
|
||||
|
||||
static_assert(PT::pairKind != MccCoordPairKind::COORDS_KIND_GENERIC, "UNSUPPORTED SKY POINT TRANSFORMATION!");
|
||||
static_assert(PT::pairKind != MccCoordPairKind::COORDS_KIND_UNKNOWN, "UNSUPPORTED SKY POINT TRANSFORMATION!");
|
||||
|
||||
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_LONLAT) { // returns geographic site coordinates
|
||||
std::pair<double, double> pos;
|
||||
cctEngine.geoPosition(&pos);
|
||||
|
||||
cpair.setX(pos.first);
|
||||
cpair.setY(pos.second);
|
||||
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
if (_pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS &&
|
||||
PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { // from ICRS to ICRS - just copy and exit
|
||||
cpair = PT(typename PT::x_t(_x), typename PT::y_t(_y));
|
||||
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
// just copy coordinates and exit
|
||||
if (_pairKind == PT::pairKind && utils::isEqual(_epoch.MJD(), cpair.MJD())) {
|
||||
// cpair = PT(typename PT::x_t(_x), typename PT::y_t(_y), _epoch);
|
||||
cpair.setX(_x);
|
||||
cpair.setY(_y);
|
||||
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
|
||||
// if epochs are not the same then
|
||||
// 1) convert stored coordinates to ICRS ones
|
||||
// 2) convert from the computed ICRS coordinates to required ones
|
||||
MccCoordPairKind pkind = _pairKind;
|
||||
if (!utils::isEqual(_epoch.MJD(), cpair.MJD())) { // convert stored pair to ICRS one (ra_icrs, dec_icrs)
|
||||
if (_pairKind != MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
pkind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS;
|
||||
|
||||
if (mcc_is_obs_coordpair(_pairKind)) {
|
||||
ccte_err = cctEngine.obsToICRS(_pairKind, _epoch, _x, _y, &ra_icrs, &dec_icrs);
|
||||
} else if (mcc_is_app_coordpair(_pairKind)) {
|
||||
ccte_err = cctEngine.appToICRS(_pairKind, _epoch, _x, _y, &ra_icrs, &dec_icrs);
|
||||
} else { // unsupported transformation!!! silently ignore!
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
} else {
|
||||
ra_icrs = _x;
|
||||
dec_icrs = _y;
|
||||
}
|
||||
}
|
||||
|
||||
if (utils::isEqual(ra_icrs, MCC_TWO_PI)) {
|
||||
ra_icrs = 0.0;
|
||||
}
|
||||
|
||||
|
||||
// here, from APP or OBS to ICRS and exit
|
||||
if (pkind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS &&
|
||||
PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
cpair = PT(typename PT::x_t(ra_icrs), typename PT::y_t(dec_icrs));
|
||||
|
||||
return error_t{};
|
||||
}
|
||||
|
||||
// here, the input coordinates and stored one are at the same epoch
|
||||
|
||||
ccte_err = cctEngine.equationOrigins(cpair.epoch(), &eo);
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
|
||||
ccte_err = cctEngine.apparentSideralTime(cpair.epoch(), &lst, true);
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
|
||||
|
||||
if (pkind == MccCoordPairKind::COORDS_KIND_RADEC_APP || pkind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
|
||||
ra = _x;
|
||||
dec = _y;
|
||||
} else if (pkind == MccCoordPairKind::COORDS_KIND_HADEC_APP ||
|
||||
pkind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
|
||||
ha = _x;
|
||||
dec = _y;
|
||||
} else if (pkind == MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
az = _x;
|
||||
zd = _y;
|
||||
} else if (pkind == MccCoordPairKind::COORDS_KIND_AZALT) {
|
||||
az = _x;
|
||||
alt = _y;
|
||||
}
|
||||
// else if (pkind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
// ra_icrs = _x;
|
||||
// dec_icrs = _y;
|
||||
// } else { // unsupported transformation!!!
|
||||
// return;
|
||||
// }
|
||||
|
||||
// coordinate transformation lambda (possibly recursive!!!)
|
||||
// "obj = this" to fix GCC compilation crash!!!
|
||||
auto comp_func = [&, obj = this](this auto&& self, MccCoordPairKind cp_kind) -> error_t {
|
||||
if (cp_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
if constexpr (mccIsAppCoordPairKind<PT::pairKind>) {
|
||||
ccte_err = cctEngine.icrsToApp(ra_icrs, dec_icrs, cpair.epoch(), &ra, &dec, &ha, &az, &zd);
|
||||
} else if constexpr (mccIsObsCoordPairKind<PT::pairKind>) {
|
||||
ccte_err = cctEngine.icrsToObs(ra_icrs, dec_icrs, cpair.epoch(), &ra, &dec, &ha, &az, &zd);
|
||||
} else {
|
||||
static_assert(true, "UNSUPPORTED SKY POINT TRANSFORMATION!");
|
||||
}
|
||||
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP ||
|
||||
PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
|
||||
cpair.setX(ra);
|
||||
cpair.setY(dec);
|
||||
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP ||
|
||||
PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
|
||||
cpair.setX(ha);
|
||||
cpair.setY(dec);
|
||||
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
cpair.setX(az);
|
||||
cpair.setY(zd);
|
||||
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZALT) {
|
||||
cpair.setX(az);
|
||||
cpair.setY(MCC_HALF_PI - zd);
|
||||
} else {
|
||||
static_assert(true, "UNSUPPORTED SKY POINT TRANSFORMATION!");
|
||||
}
|
||||
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_AZALT) {
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
zd = MCC_HALF_PI - alt;
|
||||
cpair.setX(az);
|
||||
cpair.setY(zd);
|
||||
} else {
|
||||
if constexpr (mccIsAppCoordPairKind<PT::pairKind>) {
|
||||
// correct for refraction: alt -= dz_refr
|
||||
double dZ;
|
||||
ccte_err = cctEngine.refractionCorrection(MCC_HALF_PI - alt, &dZ);
|
||||
if (ccte_err) {
|
||||
return ccte_err;
|
||||
}
|
||||
|
||||
alt -= dZ;
|
||||
}
|
||||
|
||||
obj->azalt2hadec(az, alt, phi, ha, dec);
|
||||
cpair.setY(dec);
|
||||
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
|
||||
ra = lst + eo - ha;
|
||||
cpair.setX(ra);
|
||||
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
|
||||
ra = lst + eo - ha;
|
||||
cpair.setX(ra);
|
||||
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
|
||||
cpair.setX(ha);
|
||||
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
|
||||
cpair.setX(ha);
|
||||
} else { // unsupported transformation!!! silently ignore!!!
|
||||
return error_t{};
|
||||
}
|
||||
}
|
||||
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
alt = MCC_HALF_PI - zd;
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZALT) {
|
||||
cpair.setX(az);
|
||||
cpair.setY(alt);
|
||||
} else {
|
||||
return self(MccCoordPairKind::COORDS_KIND_AZALT);
|
||||
}
|
||||
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
|
||||
ra = lst + eo - ha;
|
||||
cpair.setX(ra);
|
||||
cpair.setY(dec);
|
||||
} else {
|
||||
obj->hadec2azalt(ha, dec, phi, az, alt);
|
||||
if constexpr (mccIsAppCoordPairKind<PT::pairKind>) { // RADEC_APP, HADEC_APP
|
||||
return self(MccCoordPairKind::COORDS_KIND_AZALT);
|
||||
} else { // AZALT, AZZD
|
||||
cpair.setX(az);
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
|
||||
zd = MCC_HALF_PI - alt;
|
||||
cpair.setY(zd);
|
||||
} else {
|
||||
cpair.setY(alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
|
||||
ra = lst + eo - ha;
|
||||
cpair.setX(ra);
|
||||
cpair.setY(dec);
|
||||
} else {
|
||||
obj->hadec2azalt(ha, dec, phi, az, alt);
|
||||
if constexpr (mccIsObsCoordPairKind<PT::pairKind>) { // RADEC_OBS, HADEC_OBS, AZALT, AZZD
|
||||
// correct for refraction: alt += dz_refr
|
||||
double dZ;
|
||||
ccte_err = cctEngine.refractionInverseCorrection(MCC_HALF_PI - alt, &dZ);
|
||||
alt += dZ;
|
||||
return self(MccCoordPairKind::COORDS_KIND_AZALT);
|
||||
}
|
||||
}
|
||||
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
|
||||
ha = lst + eo - ra;
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
|
||||
cpair.setX(ha);
|
||||
cpair.setY(dec);
|
||||
} else {
|
||||
return self(MccCoordPairKind::COORDS_KIND_HADEC_OBS);
|
||||
}
|
||||
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
|
||||
ha = lst + eo - ra;
|
||||
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
|
||||
cpair.setX(ha);
|
||||
cpair.setY(dec);
|
||||
} else {
|
||||
return self(MccCoordPairKind::COORDS_KIND_HADEC_APP);
|
||||
}
|
||||
}
|
||||
|
||||
return error_t{};
|
||||
};
|
||||
|
||||
auto err = comp_func(pkind); // ran transformation
|
||||
|
||||
if (ra < 0.0) {
|
||||
ra += MCC_TWO_PI;
|
||||
cpair.setX(ra);
|
||||
}
|
||||
if (az < 0.0) {
|
||||
az += MCC_TWO_PI;
|
||||
cpair.setX(az);
|
||||
}
|
||||
|
||||
// if (utils::isEqual(ra, MCC_TWO_PI)) {
|
||||
// cpair.setX(0.0);
|
||||
// }
|
||||
|
||||
// if (utils::isEqual(az, MCC_TWO_PI)) {
|
||||
// cpair.setX(0.0);
|
||||
// }
|
||||
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* MCC-LIBRARY DEFAULT SKY POINT CLASS IMPLEMENTATION BASED ON THE ERFA LIBRARY */
|
||||
|
||||
typedef MccGenericSkyPoint<mcc::ccte::erfa::MccCCTE_ERFA> MccSkyPoint;
|
||||
|
||||
static_assert(mcc_skypoint_c<MccSkyPoint>, "!!!!");
|
||||
|
||||
} // namespace mcc::impl
|
||||
451
include/mcc/mcc_deserializer.h
Normal file
451
include/mcc/mcc_deserializer.h
Normal file
@@ -0,0 +1,451 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "mcc_coordinate.h"
|
||||
#include "mcc_epoch.h"
|
||||
#include "mcc_serialization_common.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccDeserializerErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_UNDERLYING_DESERIALIZER,
|
||||
ERROR_INVALID_SERIALIZED_VALUE,
|
||||
ERROR_COORD_TRANSFORM
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccDeserializerErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
|
||||
// error category
|
||||
struct MccDeserializerCategory : public std::error_category {
|
||||
MccDeserializerCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "MCC-DESERIALIZER-ERR-CATEGORY";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccDeserializerErrorCode err = static_cast<MccDeserializerErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccDeserializerErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER:
|
||||
return "error returned by underlying deserializer";
|
||||
case MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE:
|
||||
return "invalid serialized value";
|
||||
case MccDeserializerErrorCode::ERROR_COORD_TRANSFORM:
|
||||
return "coordinates transformation error";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccDeserializerCategory& get()
|
||||
{
|
||||
static const MccDeserializerCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccDeserializerErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccDeserializerCategory::get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* BASE DESERIALIZER CLASS (FOR IMPLEMENTATIONS BELOW) */
|
||||
|
||||
struct MccDeserializerBase : mcc_deserializer_interface_t<MccError> {
|
||||
using typename mcc_deserializer_interface_t<MccError>::error_t;
|
||||
|
||||
virtual ~MccDeserializerBase() = default;
|
||||
|
||||
|
||||
protected:
|
||||
MccDeserializerBase() = default;
|
||||
|
||||
//
|
||||
// empty == true, if the 'input' is empty or if all elements consist of only spaces
|
||||
//
|
||||
static std::vector<std::string_view> splitValueIntoElements(traits::mcc_input_char_range auto const& input,
|
||||
mcc_serialization_params_c auto const& params,
|
||||
bool& empty)
|
||||
{
|
||||
static_assert(std::ranges::contiguous_range<decltype(input)>, "NOT IMPLEMENTED FOR NON-CONTIGUIUS RANGES!!!");
|
||||
|
||||
std::vector<std::string_view> res;
|
||||
|
||||
if (std::ranges::size(input)) {
|
||||
empty = true;
|
||||
|
||||
std::ranges::for_each(std::views::split(input, params.elem_delim), [&res, &empty](auto const& el) {
|
||||
std::back_inserter(res) = utils::trimSpaces(std::string_view{el.begin(), el.end()});
|
||||
if (empty && res.back().size()) {
|
||||
empty = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
empty = true;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename VT, std::ranges::output_range<VT> R, typename... DeserParamsT>
|
||||
static error_t deserializingRange(mcc_deserializer_c auto& dsr,
|
||||
traits::mcc_input_char_range auto const& input,
|
||||
R& r,
|
||||
mcc_serialization_params_c auto const& params)
|
||||
{
|
||||
if (std::ranges::size(input) == 0) { // ignore an empty input, just return empty range?!!
|
||||
r = R{};
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
auto r_str = std::views::split(input, params.seq_delim);
|
||||
VT val;
|
||||
|
||||
auto it = r.begin();
|
||||
for (auto const& el : r_str) {
|
||||
auto err = dsr(el, val, std::forward<DeserParamsT>(params)...);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
if (it == r.end()) {
|
||||
std::back_inserter(r) = val;
|
||||
it = r.end();
|
||||
} else {
|
||||
*it = val;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* MAIN (FALLBACK) TEMPLATED IMPLEMENTATION */
|
||||
|
||||
|
||||
template <typename VT>
|
||||
struct MccDeserializer : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-FALLBACK-DESERIALIZER"};
|
||||
|
||||
virtual ~MccDeserializer() = default;
|
||||
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input,
|
||||
VT& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<VT>) {
|
||||
auto v = mcc::utils::numFromStr<VT>(utils::trimSpaces(input));
|
||||
if (!v.has_value()) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
value = v.value();
|
||||
} else if constexpr (mcc::traits::mcc_output_char_range<VT>) {
|
||||
VT r;
|
||||
if constexpr (traits::mcc_array_c<VT>) {
|
||||
size_t N =
|
||||
std::ranges::size(r) <= std::ranges::size(input) ? std::ranges::size(r) : std::ranges::size(input);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
r[i] = input[i];
|
||||
}
|
||||
if (std::ranges::size(r) > N) {
|
||||
for (size_t i = N; i < std::ranges::size(r); ++i) {
|
||||
r[i] = '\0';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::ranges::copy(input, std::back_inserter(r));
|
||||
}
|
||||
|
||||
value = r;
|
||||
} else if constexpr (std::ranges::range<VT>) {
|
||||
using el_t = std::ranges::range_value_t<VT>;
|
||||
|
||||
static_assert(std::ranges::output_range<VT, el_t>, "INVALID RANGE TYPE!!!");
|
||||
|
||||
// no reference or constants allowed
|
||||
static_assert(!(std::is_reference_v<el_t> || std::is_const_v<el_t>), "INVALID RANGE ELEMENT TYPE!!!");
|
||||
|
||||
MccDeserializer<el_t> dsr;
|
||||
return deserializingRange<el_t>(dsr, input, value, params);
|
||||
} else if constexpr (traits::mcc_time_duration_c<VT>) {
|
||||
typename VT::rep vd;
|
||||
|
||||
MccDeserializer<typename VT::rep> dsr;
|
||||
|
||||
auto err = dsr(utils::trimSpaces(input), vd, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
value = VT{vd};
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED VALUE TYPE!!!");
|
||||
}
|
||||
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* SPECIALIZATION FOR THE SOME CONCEPTS */
|
||||
|
||||
template <mcc_coord_epoch_c VT>
|
||||
struct MccDeserializer<VT> : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-COORD-EPOCH-DESERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input,
|
||||
VT& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
bool ok = value.fromCharRange(input);
|
||||
if (!ok) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(!std::is_arithmetic_v<VT> && mcc_angle_c<VT>)
|
||||
struct MccDeserializer<VT> : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-ANGLE-DESERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input,
|
||||
VT& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
bool hms = params.angle_format == MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS;
|
||||
|
||||
auto res = utils::parsAngleString(input, hms);
|
||||
if (res) { // returned angle is in degrees!!!
|
||||
value = res.value() * MCC_DEGRESS_TO_RADS;
|
||||
} else {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <mcc_skypoint_c VT>
|
||||
struct MccDeserializer<VT> : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-SKYPOINT-DESERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input,
|
||||
VT& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
auto pars = params;
|
||||
|
||||
// valid format: X<elem-delim>Y[<elem-delim>TIME-POINT<elem-delim>PAIR-KIND]
|
||||
// X<elem-delim>Y (assumed RADEC_ICRS and J2000.0 epoch)
|
||||
|
||||
bool empty;
|
||||
|
||||
auto elems = MccDeserializerBase::splitValueIntoElements(input, params, empty);
|
||||
|
||||
if (empty || (elems.size() < 2) || (elems.size() == 3)) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
MccCoordPairKind pair_kind{MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
|
||||
MccCelestialCoordEpoch epoch; // J2000.0
|
||||
|
||||
MccAngle x, y;
|
||||
MccDeserializer<MccAngle> dsr_ang;
|
||||
|
||||
typename MccDeserializer<MccAngle>::error_t dsr_err;
|
||||
|
||||
if (elems.size() > 3) { // full format
|
||||
// deserialize pair kind string
|
||||
pair_kind = MccCoordStrToPairKind(elems[3]);
|
||||
if (pair_kind == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
// epoch
|
||||
bool ok = epoch.fromCharRange(elems[2]);
|
||||
if (!ok) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// deserialize X and Y
|
||||
if (params.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG) {
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS;
|
||||
} else {
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS;
|
||||
}
|
||||
|
||||
dsr_err = dsr_ang(elems[0], x, pars);
|
||||
if (dsr_err) {
|
||||
return mcc_deduced_err(dsr_err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS;
|
||||
dsr_err = dsr_ang(elems[1], y, pars);
|
||||
if (dsr_err) {
|
||||
return mcc_deduced_err(dsr_err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
switch (pair_kind) {
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
|
||||
value.from(MccSkyRADEC_ICRS{(double)x, (double)y});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_OBS:
|
||||
value.from(MccSkyRADEC_OBS{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_APP:
|
||||
value.from(MccSkyRADEC_APP{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_OBS:
|
||||
value.from(MccSkyHADEC_OBS{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_APP:
|
||||
value.from(MccSkyHADEC_APP{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_AZZD:
|
||||
value.from(MccSkyAZZD{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_AZALT:
|
||||
value.from(MccSkyAZALT{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_XY:
|
||||
value.from(MccGenXY{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_GENERIC:
|
||||
value.from(MccCoordPair{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_LONLAT:
|
||||
value.from(MccGeoLONLAT{(double)x, (double)y});
|
||||
break;
|
||||
default:
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct MccDeserializer<MccCoordPairKind> : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-COORDPAIR-DESERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator ()(
|
||||
traits::mcc_input_char_range auto const& input,
|
||||
MccCoordPairKind& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
value = MccCoordStrToPairKind(input);
|
||||
|
||||
if (value == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct MccDeserializer<MccSerializedAngleFormatPrec> : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-ANGLE-FORMAT-PREC-DESERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator ()(
|
||||
traits::mcc_input_char_range auto const& input,
|
||||
MccSerializedAngleFormatPrec& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
// valid format: hour_prec[<params.elem_delim>deg_prec<params.elem_delim>decimals]
|
||||
|
||||
std::vector<uint8_t> v;
|
||||
|
||||
auto err = MccDeserializer<decltype(v)>{}(input, v);
|
||||
if (err) {
|
||||
return MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER;
|
||||
}
|
||||
|
||||
if (v.size() > 0) {
|
||||
value.hour_prec = v[0];
|
||||
}
|
||||
|
||||
if (v.size() > 1) {
|
||||
value.deg_prec = v[1];
|
||||
}
|
||||
|
||||
if (v.size() > 2) {
|
||||
value.decimals = v[2];
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
struct MccDeserializer<MccSerializedCoordPairFormat> : MccDeserializerBase {
|
||||
static constexpr std::string_view deserializerName{"MCC-COORDPAIR-FORMAT-DESERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator ()(
|
||||
traits::mcc_input_char_range auto const& input,
|
||||
MccSerializedCoordPairFormat& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
value = MccSerializedCoordPairFormatStrToValue(input);
|
||||
|
||||
if (value != MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_UNKNOWN) {
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
} else {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
445
include/mcc/mcc_deserializer.h.old
Normal file
445
include/mcc/mcc_deserializer.h.old
Normal file
@@ -0,0 +1,445 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF DESERIALIZER CLASSES *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <string_view>
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_coordinate.h"
|
||||
#include "mcc_epoch.h"
|
||||
#include "mcc_error.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccDeserializerErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_UNDERLYING_DESERIALIZER,
|
||||
ERROR_INVALID_SERIALIZED_VALUE,
|
||||
ERROR_COORD_TRANSFORM
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccDeserializerErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
|
||||
// error category
|
||||
struct MccDeserializerCategory : public std::error_category {
|
||||
MccDeserializerCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "MCC-DESERIALIZER-ERR-CATEGORY";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccDeserializerErrorCode err = static_cast<MccDeserializerErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccDeserializerErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER:
|
||||
return "error returned by underlying deserializer";
|
||||
case MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE:
|
||||
return "invalid serialized value";
|
||||
case MccDeserializerErrorCode::ERROR_COORD_TRANSFORM:
|
||||
return "coordinates transformation error";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccDeserializerCategory& get()
|
||||
{
|
||||
static const MccDeserializerCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccDeserializerErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccDeserializerCategory::get());
|
||||
}
|
||||
|
||||
|
||||
template <mcc_error_c RetT>
|
||||
struct mcc_deserializer_interface_t {
|
||||
virtual ~mcc_deserializer_interface_t() = default;
|
||||
|
||||
typedef RetT error_t;
|
||||
|
||||
template <std::derived_from<mcc_deserializer_interface_t> SelfT,
|
||||
traits::mcc_input_char_range R,
|
||||
typename ValueT,
|
||||
typename... DeserParamsT>
|
||||
RetT operator()(this SelfT&& self, R const& input, ValueT& value, DeserParamsT&&... params)
|
||||
{
|
||||
return std::forward<SelfT>(self)(input, value);
|
||||
}
|
||||
|
||||
protected:
|
||||
mcc_deserializer_interface_t() = default;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept mcc_deserializer_c = std::derived_from<T, mcc_deserializer_interface_t<typename T::error_t>>;
|
||||
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
struct MccDeserializerBase : mcc_deserializer_interface_t<impl::MccError>, utils::mcc_elem_sequence_with_delim_t {
|
||||
using typename mcc_deserializer_interface_t<impl::MccError>::error_t;
|
||||
|
||||
virtual ~MccDeserializerBase() = default;
|
||||
|
||||
|
||||
protected:
|
||||
MccDeserializerBase() = default;
|
||||
|
||||
//
|
||||
// empty == true, if the 'input' is empty or if all elements consist of only spaces
|
||||
//
|
||||
std::vector<std::string_view> splitAggregateValue(traits::mcc_input_char_range auto const& input, bool& empty) const
|
||||
{
|
||||
std::vector<std::string_view> res;
|
||||
|
||||
if (std::ranges::size(input)) {
|
||||
empty = true;
|
||||
|
||||
std::ranges::for_each(std::views::split(input, this->_elementDelimiter), [&res, &empty](auto const& el) {
|
||||
std::back_inserter(res) = utils::trimSpaces(std::string_view{el.begin(), el.end()});
|
||||
if (empty && res.back().size()) {
|
||||
empty = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
empty = true;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename VT, std::ranges::output_range<VT> R, typename... DeserParamsT>
|
||||
error_t deserializingRange(mcc_deserializer_c auto& dsr,
|
||||
traits::mcc_input_char_range auto const& input,
|
||||
R& r,
|
||||
DeserParamsT&&... params) const
|
||||
{
|
||||
if (std::ranges::size(input) == 0) { // ignore an empty input, just return empty range?!!
|
||||
r = R{};
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
auto r_str = std::views::split(input, _seqDelimiter);
|
||||
VT val;
|
||||
|
||||
auto it = r.begin();
|
||||
for (auto const& el : r_str) {
|
||||
auto err = dsr(el, val, std::forward<DeserParamsT>(params)...);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
if (it == r.end()) {
|
||||
std::back_inserter(r) = val;
|
||||
it = r.end();
|
||||
} else {
|
||||
*it = val;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
|
||||
/* fallback template: basic deserializer */
|
||||
|
||||
template <typename VT>
|
||||
class MccDeserializer : public details::MccDeserializerBase
|
||||
{
|
||||
public:
|
||||
using typename details::MccDeserializerBase::error_t;
|
||||
|
||||
virtual ~MccDeserializer() = default;
|
||||
|
||||
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input, VT& value)
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<VT>) {
|
||||
auto v = mcc::utils::numFromStr<VT>(utils::trimSpaces(input));
|
||||
if (!v.has_value()) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
value = v.value();
|
||||
} else if constexpr (mcc::traits::mcc_output_char_range<VT>) {
|
||||
VT r;
|
||||
if constexpr (traits::mcc_array_c<VT>) {
|
||||
size_t N =
|
||||
std::ranges::size(r) <= std::ranges::size(input) ? std::ranges::size(r) : std::ranges::size(input);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
r[i] = input[i];
|
||||
}
|
||||
if (std::ranges::size(r) > N) {
|
||||
for (size_t i = N; i < std::ranges::size(r); ++i) {
|
||||
r[i] = '\0';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::ranges::copy(input, std::back_inserter(r));
|
||||
}
|
||||
|
||||
value = r;
|
||||
} else if constexpr (std::ranges::range<VT>) {
|
||||
using el_t = std::ranges::range_value_t<VT>;
|
||||
|
||||
static_assert(std::ranges::output_range<VT, el_t>, "INVALID RANGE TYPE!!!");
|
||||
|
||||
// no reference or constants allowed
|
||||
static_assert(std::is_reference_v<el_t> || std::is_const_v<el_t>, "INVALID RANGE ELEMENT TYPE!!!");
|
||||
|
||||
MccDeserializer<el_t> dsr;
|
||||
return deserializingRange<el_t>(dsr, input, value);
|
||||
} else if constexpr (traits::mcc_time_duration_c<VT>) {
|
||||
typename VT::rep vd;
|
||||
|
||||
MccDeserializer<typename VT::rep> dsr;
|
||||
|
||||
auto err = dsr(trimSpaces(input), vd);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
value = VT{vd};
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED VALUE TYPE!!!");
|
||||
}
|
||||
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* SPECIALIZATION FOR THE SOME CONCEPTS */
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(mcc_coord_epoch_c<VT> || (std::ranges::output_range<VT, std::ranges::range_value_t<VT>> &&
|
||||
mcc_coord_epoch_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>))
|
||||
class MccDeserializer<VT> : public details::MccDeserializerBase
|
||||
{
|
||||
public:
|
||||
using typename details::MccDeserializerBase::error_t;
|
||||
|
||||
virtual ~MccDeserializer() = default;
|
||||
|
||||
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input, VT& value)
|
||||
{
|
||||
if constexpr (mcc_coord_epoch_c<VT>) { // scalar
|
||||
bool ok = value.fromCharRange(input);
|
||||
if (!ok) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
} else { // range
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccDeserializer<value_t> dsr;
|
||||
|
||||
return deserializingRange(dsr, input, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename VT>
|
||||
requires(!std::is_arithmetic_v<VT> &&
|
||||
(mcc_angle_c<VT> || (std::ranges::output_range<VT, std::ranges::range_value_t<VT>> &&
|
||||
mcc_angle_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>)))
|
||||
class MccDeserializer<VT> : public details::MccDeserializerBase
|
||||
{
|
||||
public:
|
||||
using typename details::MccDeserializerBase::error_t;
|
||||
|
||||
virtual ~MccDeserializer() = default;
|
||||
|
||||
// if hms == true and the input sequence is a sexagesimal string then it interpretates angle as hours:mins:secs
|
||||
// otherwise the input sequence is interpretated as decimal or sexagesimal degrees
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input, VT& value, bool hms = false)
|
||||
{
|
||||
if constexpr (mcc_angle_c<VT>) { // scalar
|
||||
auto res = utils::parsAngleString(input, hms);
|
||||
if (res) { // returned angle is in degrees!!!
|
||||
value = res.value() * MCC_DEGRESS_TO_RADS;
|
||||
} else {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
} else { // range
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccDeserializer<value_t> dsr;
|
||||
|
||||
if constexpr (std::invocable<MccDeserializer<value_t>, decltype(input), VT&, bool>) {
|
||||
// it is assumed here, the angle deserializer has the same behavior as the one implemented above
|
||||
// (that is, it can interpret the third argument as a flag "false/true = only
|
||||
// degrees/hour-like")
|
||||
return deserializingRange<value_t>(dsr, input, value, hms);
|
||||
} else {
|
||||
return deserializingRange<value_t>(dsr, input, value);
|
||||
}
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(mcc_skypoint_c<VT> || (std::ranges::output_range<VT, std::ranges::range_value_t<VT>> &&
|
||||
mcc_skypoint_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>))
|
||||
class MccDeserializer<VT> : public details::MccDeserializerBase
|
||||
{
|
||||
public:
|
||||
using typename details::MccDeserializerBase::error_t;
|
||||
|
||||
virtual ~MccDeserializer() = default;
|
||||
|
||||
error_t operator()(traits::mcc_input_char_range auto const& input, VT& value)
|
||||
{
|
||||
if constexpr (mcc_skypoint_c<VT>) { // scalar: X[elem-delim]Y{[elem-delim]TIME-POINT[elem-delim]PAIR-KIND}
|
||||
bool empty; // exactly 2 or >3 elements
|
||||
auto elems = splitAggregateValue(input, empty);
|
||||
|
||||
if (empty || (elems.size() < 2) || (elems.size() == 3)) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
MccCoordPairKind pair_kind{MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
|
||||
MccCelestialCoordEpoch epoch; // J2000.0
|
||||
|
||||
MccAngle x, y;
|
||||
MccDeserializer<MccAngle> dsr_ang;
|
||||
|
||||
typename MccDeserializer<MccAngle>::error_t dsr_err;
|
||||
|
||||
if (elems.size() >= 4) { // X, Y, TIME-POINT, PAIR-KIND
|
||||
// first, get pair-kind
|
||||
pair_kind = MccCoordStrToPairKind(elems[3]);
|
||||
if (pair_kind == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
// epoch
|
||||
bool ok = epoch.fromCharRange(elems[2]);
|
||||
if (!ok) {
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (elems.size() >= 2) { // if == 2: just X and Y (if so it is interpretated as RADEC_ICRS)
|
||||
if (MccCoordinatePairToSxgmRep(pair_kind) == MccCoordinatePairRep::MCC_COORDPAIR_REP_SXGM_HOURDEG) {
|
||||
// if input X-angle is in sexagesimal from then interpretate it in hours::mins::secs format
|
||||
if constexpr (std::invocable<MccDeserializer<MccAngle>, decltype(elems[0]), MccAngle&, bool>) {
|
||||
// it is assumed here, the angle deserializer has the same behavior as the one implemented above
|
||||
// (that is, it can interpret the third argument as a flag "false/true = only
|
||||
// degrees/hour-like")
|
||||
dsr_err = dsr_ang(elems[0], x, true);
|
||||
} else {
|
||||
dsr_err = dsr_ang(elems[0], x);
|
||||
}
|
||||
} else {
|
||||
dsr_err = dsr_ang(elems[0], x);
|
||||
}
|
||||
|
||||
if (dsr_err) {
|
||||
return mcc_deduced_err(dsr_err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
|
||||
dsr_err = dsr_ang(elems[1], y);
|
||||
|
||||
if (dsr_err) {
|
||||
return mcc_deduced_err(dsr_err, MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (pair_kind) {
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
|
||||
value.from(MccSkyRADEC_ICRS{(double)x, (double)y});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_OBS:
|
||||
value.from(MccSkyRADEC_OBS{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_APP:
|
||||
value.from(MccSkyRADEC_APP{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_OBS:
|
||||
value.from(MccSkyHADEC_OBS{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_APP:
|
||||
value.from(MccSkyHADEC_APP{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_AZZD:
|
||||
value.from(MccSkyAZZD{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_AZALT:
|
||||
value.from(MccSkyAZALT{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_XY:
|
||||
value.from(MccGenXY{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_GENERIC:
|
||||
value.from(MccCoordPair{(double)x, (double)y, epoch});
|
||||
break;
|
||||
case MccCoordPairKind::COORDS_KIND_LONLAT:
|
||||
value.from(MccGeoLONLAT{(double)x, (double)y});
|
||||
break;
|
||||
default:
|
||||
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
|
||||
}
|
||||
|
||||
} else { // range
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccDeserializer<value_t> dsr;
|
||||
|
||||
return deserializingRange<value_t>(dsr, input, value);
|
||||
}
|
||||
|
||||
return MccDeserializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
412
include/mcc/mcc_epoch.h
Normal file
412
include/mcc/mcc_epoch.h
Normal file
@@ -0,0 +1,412 @@
|
||||
#pragma once
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF CELESTIAL COORDINATES EPOCH *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
#include <regex>
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_utils.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
class MccCelestialCoordEpoch : public mcc_coord_epoch_interface_t
|
||||
{
|
||||
inline static const std::regex dateTimeISO8601Rx{
|
||||
" *[0-9]{4}-[0,1][0-9]-[0-2][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](\\.[0-9]+)? *"};
|
||||
|
||||
inline static const std::regex dateTimeJEpochRx{" *J[0-9]{4}(\\.[0-9]{1,})+ *"};
|
||||
inline static const std::regex dateTimeBEpochRx{" *B[0-9]{4}(\\.[0-9]{1,})+ *"};
|
||||
inline static const std::regex dateTimeMJDRx{" *([0-9]*[.])?[0-9]+([eE][-+]?[0-9]+)? *"};
|
||||
|
||||
|
||||
typedef std::chrono::duration<double, std::ratio<31556952>> year_fp_t;
|
||||
typedef std::chrono::duration<double, std::ratio<86400>> day_fp_t;
|
||||
|
||||
public:
|
||||
static constexpr auto J2000_UTC =
|
||||
std::chrono::sys_days(
|
||||
std::chrono::year_month_day(std::chrono::January / std::chrono::day(1) / std::chrono::year(2000))) +
|
||||
std::chrono::hours(11) + std::chrono::minutes(58) + std::chrono::milliseconds(55816);
|
||||
|
||||
static constexpr double J2000_MJD = 51544.5;
|
||||
|
||||
|
||||
static MccCelestialCoordEpoch now()
|
||||
{
|
||||
MccCelestialCoordEpoch ep;
|
||||
ep.fromTimePoint(std::chrono::system_clock::now());
|
||||
|
||||
return ep;
|
||||
}
|
||||
|
||||
MccCelestialCoordEpoch() : _UTC(J2000_UTC), _MJD(J2000_MJD), _JEpoch(2000.0) {}
|
||||
|
||||
MccCelestialCoordEpoch(const MccCelestialCoordEpoch&) = default;
|
||||
MccCelestialCoordEpoch(MccCelestialCoordEpoch&&) = default;
|
||||
|
||||
MccCelestialCoordEpoch& operator=(const MccCelestialCoordEpoch&) = default;
|
||||
MccCelestialCoordEpoch& operator=(MccCelestialCoordEpoch&&) = default;
|
||||
|
||||
MccCelestialCoordEpoch(mcc_coord_epoch_c auto&& other) : MccCelestialCoordEpoch()
|
||||
{
|
||||
fromTimePoint(std::forward<decltype(other)>(other).UTC());
|
||||
}
|
||||
|
||||
MccCelestialCoordEpoch& operator=(mcc_coord_epoch_c auto&& other)
|
||||
{
|
||||
fromTimePoint(std::forward<decltype(other)>(other).UTC());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
MccCelestialCoordEpoch& operator=(traits::mcc_input_char_range auto&& str)
|
||||
{
|
||||
// ignore possible errors!!!
|
||||
auto ok = fromCharRange(std::forward<decltype(str)>(str));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template <typename ClockT, typename DurT>
|
||||
MccCelestialCoordEpoch& operator=(std::chrono::time_point<ClockT, DurT>&& tp)
|
||||
{
|
||||
// ignore possible errors!!!
|
||||
auto ok = fromTimePoint(std::forward<decltype(tp)>(tp));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename VT>
|
||||
MccCelestialCoordEpoch& operator=(VT&& mjd)
|
||||
requires std::is_arithmetic_v<VT>
|
||||
{
|
||||
// ignore possible errors!!!
|
||||
auto ok = fromMJD(std::forward<decltype(mjd)>(mjd));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range IR>
|
||||
bool fromCharRange(IR&& str)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<std::decay_t<IR>>) {
|
||||
return fromCharRange(std::string_view{str});
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
||||
std::string_view sv = utils::trimSpaces(std::forward<IR>(str));
|
||||
std::istringstream ist{std::string(sv)};
|
||||
|
||||
// try ISO8601 date ...
|
||||
std::chrono::from_stream(ist, "%FT%T", _UTC);
|
||||
if (ist.fail()) { // not ISO8601 date
|
||||
// try MJD (floating-point number) ...
|
||||
std::optional<double> mjd = utils::numFromStr<double>(sv);
|
||||
if (mjd) {
|
||||
_MJD = mjd.value();
|
||||
ret = fromMJD();
|
||||
} else { // not MJD
|
||||
// try epoch (e.g. J2010.32)
|
||||
if (sv[0] == 'J') {
|
||||
auto jep = utils::numFromStr<double>(sv.substr(1));
|
||||
if (jep) {
|
||||
_JEpoch = jep.value();
|
||||
ret = fromJEpoch();
|
||||
} else { // ERROR!!!
|
||||
ret = false;
|
||||
}
|
||||
} else { // ERROR!!!
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = fromUTC();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename ClockT, typename DurT>
|
||||
bool fromTimePoint(std::chrono::time_point<ClockT, DurT>&& tp)
|
||||
{
|
||||
if constexpr (std::same_as<ClockT, std::chrono::system_clock>) {
|
||||
_UTC = std::chrono::time_point_cast<decltype(_UTC)::duration>(std::forward<decltype(tp)>(tp));
|
||||
} else if constexpr (std::same_as<ClockT, std::chrono::utc_clock>) {
|
||||
auto stp = std::chrono::utc_clock::to_sys(std::forward<decltype(tp)>(tp));
|
||||
_UTC = std::chrono::time_point_cast<decltype(_UTC)::duration>(std::forward<decltype(tp)>(stp));
|
||||
} else if constexpr (std::same_as<ClockT, std::chrono::tai_clock>) {
|
||||
return fromTimePoint(ClockT::to_utc(std::forward<decltype(tp)>(tp)));
|
||||
} else if constexpr (std::same_as<ClockT, std::chrono::gps_clock>) {
|
||||
return fromTimePoint(ClockT::to_utc(std::forward<decltype(tp)>(tp)));
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED CLOCK!!!");
|
||||
}
|
||||
|
||||
|
||||
return fromUTC();
|
||||
}
|
||||
|
||||
template <typename VT>
|
||||
bool fromMJD(VT&& mjd)
|
||||
requires std::is_arithmetic_v<VT>
|
||||
{
|
||||
_MJD = static_cast<double>(std::forward<VT>(mjd));
|
||||
|
||||
return fromMJD();
|
||||
}
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
MccCelestialCoordEpoch& operator+=(DT&& dt)
|
||||
{
|
||||
_UTC += std::chrono::duration_cast<decltype(_UTC)::duration>(std::forward<DT>(dt));
|
||||
|
||||
_MJD += std::chrono::duration_cast<day_fp_t>(std::forward<DT>(dt)).count();
|
||||
|
||||
_JEpoch += std::chrono::duration_cast<year_fp_t>(std::forward<DT>(dt)).count();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
MccCelestialCoordEpoch& operator-=(DT&& dt)
|
||||
{
|
||||
_UTC -= std::chrono::duration_cast<decltype(_UTC)::duration>(std::forward<DT>(dt));
|
||||
|
||||
_MJD -= std::chrono::duration_cast<day_fp_t>(std::forward<DT>(dt)).count();
|
||||
|
||||
_JEpoch -= std::chrono::duration_cast<year_fp_t>(std::forward<DT>(dt)).count();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
friend MccCelestialCoordEpoch operator+(const MccCelestialCoordEpoch& lhs, const DT& dt)
|
||||
{
|
||||
MccCelestialCoordEpoch ep = lhs;
|
||||
ep += dt;
|
||||
|
||||
return ep;
|
||||
}
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
friend MccCelestialCoordEpoch operator+(const DT& dt, const MccCelestialCoordEpoch& rhs)
|
||||
{
|
||||
return rhs + dt;
|
||||
}
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
friend MccCelestialCoordEpoch operator-(const MccCelestialCoordEpoch& lhs, const DT& dt)
|
||||
{
|
||||
MccCelestialCoordEpoch ep = lhs;
|
||||
ep -= dt;
|
||||
|
||||
return ep;
|
||||
}
|
||||
|
||||
friend auto operator-(const MccCelestialCoordEpoch& lhs, const MccCelestialCoordEpoch& rhs)
|
||||
{
|
||||
return lhs._UTC - rhs._UTC;
|
||||
}
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires std::is_arithmetic_v<VT>
|
||||
VT MJD() const
|
||||
{
|
||||
return _MJD;
|
||||
}
|
||||
|
||||
double MJD() const
|
||||
{
|
||||
return _MJD;
|
||||
}
|
||||
|
||||
template <typename DT>
|
||||
std::chrono::sys_time<DT> UTC() const
|
||||
{
|
||||
return std::chrono::time_point_cast<DT>(_UTC);
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point UTC() const
|
||||
{
|
||||
return _UTC;
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_output_char_range R>
|
||||
R JEpoch(uint8_t prec = 0) const
|
||||
{
|
||||
std::string prec_str{"J{:"};
|
||||
if (prec > 0) {
|
||||
prec_str += ".";
|
||||
prec_str += std::to_string(prec);
|
||||
}
|
||||
prec_str += "f}";
|
||||
|
||||
std::string res = std::vformat(std::string_view{prec_str}, std::make_format_args(_JEpoch));
|
||||
|
||||
if constexpr (std::same_as<R, std::string>) {
|
||||
return res;
|
||||
}
|
||||
|
||||
R r;
|
||||
std::ranges::copy(res, std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string JEpoch(uint8_t prec = 0) const
|
||||
{
|
||||
return JEpoch<std::string>(prec);
|
||||
}
|
||||
|
||||
auto operator<=>(const MccCelestialCoordEpoch& rhs) const
|
||||
{
|
||||
return _UTC <=> rhs._UTC;
|
||||
}
|
||||
|
||||
auto operator==(const MccCelestialCoordEpoch& rhs) const
|
||||
{
|
||||
return _UTC == rhs._UTC;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::chrono::system_clock::time_point _UTC;
|
||||
double _MJD;
|
||||
double _JEpoch;
|
||||
|
||||
bool fromUTC()
|
||||
{
|
||||
// modified Julian date (based on ERFA eraCal2jd)
|
||||
auto dd = std::chrono::floor<std::chrono::days>(_UTC);
|
||||
std::chrono::year_month_day ymd{dd};
|
||||
|
||||
static constexpr std::chrono::year MIN_YEAR{-4799};
|
||||
|
||||
if (ymd.year() < MIN_YEAR) {
|
||||
return false;
|
||||
}
|
||||
if (!ymd.month().ok()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t im = (unsigned)ymd.month();
|
||||
int64_t id = (unsigned)ymd.day();
|
||||
int64_t iy = (int)ymd.year();
|
||||
|
||||
int64_t my = (im - 14LL) / 12LL;
|
||||
int64_t iypmy = iy + my;
|
||||
|
||||
// integer part of result MJD
|
||||
int64_t mjd_int = (1461LL * (iypmy + 4800LL)) / 4LL + (367LL * (im - 2LL - 12LL * my)) / 12LL -
|
||||
(3LL * ((iypmy + 4900LL) / 100LL)) / 4LL + id - 2432076LL;
|
||||
|
||||
_MJD = static_cast<double>(mjd_int) + std::chrono::duration_cast<day_fp_t>(_UTC - dd).count();
|
||||
|
||||
_JEpoch = std::chrono::duration_cast<year_fp_t>(_UTC - J2000_UTC).count() + 2000.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fromMJD(bool only_time_point = false)
|
||||
{
|
||||
// Gregorian date from modified Julian date (based on ERFS eraJd2cal)
|
||||
double f1 = -0.5;
|
||||
long jd = std::round(_MJD);
|
||||
double f2 = _MJD - jd;
|
||||
jd += 2400001.0;
|
||||
|
||||
double s = 0.5, cs = 0.0;
|
||||
// double v[] = {f1, f2};
|
||||
|
||||
auto lmd = [&](double v) {
|
||||
// double x = v;
|
||||
// double t = s + x;
|
||||
// cs += std::fabs(s) >= std::fabs(x) ? (s - t) + x : (x - t) + s;
|
||||
double t = s + v;
|
||||
cs += std::fabs(s) >= std::fabs(v) ? (s - t) + v : (v - t) + s;
|
||||
s = t;
|
||||
if (s >= 1.0) {
|
||||
jd++;
|
||||
s -= 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
lmd(f1);
|
||||
lmd(f2);
|
||||
|
||||
double f = s + cs;
|
||||
cs = f - s;
|
||||
|
||||
if (f < 0.0) {
|
||||
f = s + 1.0;
|
||||
cs += (1.0 - f) + s;
|
||||
s = f;
|
||||
f = s + cs;
|
||||
cs = f - s;
|
||||
jd--;
|
||||
}
|
||||
|
||||
|
||||
if ((f - 1.0) >= -std::numeric_limits<double>::epsilon() / 4.0) {
|
||||
/* Compensated summation: assume that |s| <= 1.0. */
|
||||
double t = s - 1.0;
|
||||
cs += (s - t) - 1.0;
|
||||
s = t;
|
||||
f = s + cs;
|
||||
if (-std::numeric_limits<double>::epsilon() / 2.0 < f) {
|
||||
jd++;
|
||||
f = std::max(f, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
long l = jd + 68569L;
|
||||
long n = (4L * l) / 146097L;
|
||||
l -= (146097L * n + 3L) / 4L;
|
||||
long i = (4000L * (l + 1L)) / 1461001L;
|
||||
l -= (1461L * i) / 4L - 31L;
|
||||
long k = (80L * l) / 2447L;
|
||||
|
||||
auto day = std::chrono::day(l - (2447L * k) / 80L);
|
||||
l = k / 11L;
|
||||
auto month = std::chrono::month(k + 2L - 12L * l);
|
||||
auto year = std::chrono::year(100L * (n - 49L) + i + l);
|
||||
|
||||
auto day_frac = day_fp_t(f);
|
||||
|
||||
// _UTC = years + month + days + day_frac;
|
||||
_UTC = std::chrono::sys_days(std::chrono::year_month_day(month / day / year));
|
||||
_UTC += std::chrono::duration_cast<decltype(_UTC)::duration>(day_frac);
|
||||
|
||||
if (!only_time_point) {
|
||||
_JEpoch = 2000.0 + (_MJD - J2000_MJD) / 365.25;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fromJEpoch()
|
||||
{
|
||||
_MJD = J2000_MJD + (_JEpoch - 2000.0) * 365.25;
|
||||
|
||||
return fromMJD(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static_assert(mcc_coord_epoch_c<MccCelestialCoordEpoch>, "!!!");
|
||||
|
||||
|
||||
} // namespace mcc::impl
|
||||
124
include/mcc/mcc_error.h
Normal file
124
include/mcc/mcc_error.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF OPERATIONAL ERROR *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
#include <format>
|
||||
#include <sstream>
|
||||
#include <system_error>
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
typedef std::error_code MccError; // library-wide definition of operational error
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
template <>
|
||||
struct std::formatter<mcc::impl::MccError, char> {
|
||||
template <class ParseContext>
|
||||
constexpr ParseContext::iterator parse(ParseContext& ctx)
|
||||
{
|
||||
auto it = ctx.begin();
|
||||
if (it == ctx.end()) {
|
||||
return it;
|
||||
}
|
||||
|
||||
_currFmt.clear();
|
||||
_delim = " ";
|
||||
|
||||
for (; it != ctx.end(); ++it) {
|
||||
switch (*it) {
|
||||
case '#': // numerical error value
|
||||
_currFmt.push_back(*it);
|
||||
break;
|
||||
case '@': // error category name
|
||||
_currFmt.push_back(*it);
|
||||
break;
|
||||
case '^': // error message
|
||||
_currFmt.push_back(*it);
|
||||
break;
|
||||
case '_':
|
||||
// while (it++ != ctx.end() || it++ != '}' || it++ != '_') {
|
||||
// _currFmt.push_back(*(++it));
|
||||
// }
|
||||
|
||||
if (it++ != ctx.end() || it++ != '}') {
|
||||
_delim = *(++it);
|
||||
}
|
||||
case '}':
|
||||
if (_currFmt.empty()) {
|
||||
_currFmt = "#@^";
|
||||
}
|
||||
return it;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
template <class FmtContext>
|
||||
FmtContext::iterator format(mcc::impl::MccError const& err, FmtContext& ctx) const
|
||||
{
|
||||
std::ostringstream out;
|
||||
|
||||
size_t idx = 0;
|
||||
for (auto const& fmt_sym : _currFmt) {
|
||||
switch (fmt_sym) {
|
||||
case '#':
|
||||
out << err.value();
|
||||
++idx;
|
||||
break;
|
||||
case '@':
|
||||
out << err.category().name();
|
||||
++idx;
|
||||
break;
|
||||
case '^':
|
||||
out << err.message();
|
||||
++idx;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (idx < _currFmt.size()) {
|
||||
out << _delim;
|
||||
}
|
||||
}
|
||||
|
||||
return std::ranges::copy(std::move(out).str(), ctx.out()).out;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _currFmt{}, _delim{" "};
|
||||
};
|
||||
|
||||
|
||||
static_assert(mcc::mcc_error_c<mcc::impl::MccError>, "");
|
||||
|
||||
|
||||
// // a helper formatter impelementation
|
||||
|
||||
// template <typename T>
|
||||
// requires std::is_enum_v<T>
|
||||
// struct std::formatter<T, char> : std::formatter<std::underlying_type_t<T>, char> {
|
||||
// auto format(T e, auto& ctx) const
|
||||
// {
|
||||
// return formatter<std::underlying_type_t<T>>::format(std::underlying_type_t<T>(e), ctx);
|
||||
// }
|
||||
// };
|
||||
|
||||
// enum class EE : int { A, B, C };
|
||||
|
||||
// static_assert(mcc::mcc_error_c<EE>, "");
|
||||
226
include/mcc/mcc_generic_mount.h
Normal file
226
include/mcc/mcc_generic_mount.h
Normal file
@@ -0,0 +1,226 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF A GENERIC MOUNT CLASS *
|
||||
* (A SOME OF POSSIBLE GENERIC IMPLEMENTATIONS) *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
#include <thread>
|
||||
#include "mcc_error.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccGenericMountErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_HW_INIT,
|
||||
ERROR_HW_STOP,
|
||||
ERROR_HW_GETSTATE,
|
||||
ERROR_SET_TARGET,
|
||||
ERROR_MOUNT_SLEW,
|
||||
ERROR_MOUNT_TRACK,
|
||||
ERROR_GET_TELEMETRY,
|
||||
ERROR_UNSUPPORTED_TARGET_COORDPAIR,
|
||||
ERROR_PZONE_COMP,
|
||||
ERROR_TARGET_IN_ZONE
|
||||
};
|
||||
|
||||
enum class MccGenericFsmMountErrorCode : int { ERROR_OK, ERROR_INVALID_OPERATION, ERROR_UNKNOWN_EVENT };
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccGenericMountErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
// error category
|
||||
struct MccGenericMountCategory : public std::error_category {
|
||||
MccGenericMountCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "MCC-GENERIC-MOUNT";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccGenericMountErrorCode err = static_cast<MccGenericMountErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccGenericMountErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccGenericMountErrorCode::ERROR_HW_INIT:
|
||||
return "an error occured while initializing mount";
|
||||
case MccGenericMountErrorCode::ERROR_HW_STOP:
|
||||
return "an error occured while stopping mount";
|
||||
case MccGenericMountErrorCode::ERROR_HW_GETSTATE:
|
||||
return "cannot get state of hardware";
|
||||
case MccGenericMountErrorCode::ERROR_SET_TARGET:
|
||||
return "cannot set target coordinates";
|
||||
case MccGenericMountErrorCode::ERROR_MOUNT_SLEW:
|
||||
return "slewing error";
|
||||
case MccGenericMountErrorCode::ERROR_MOUNT_TRACK:
|
||||
return "tracking error";
|
||||
case MccGenericMountErrorCode::ERROR_GET_TELEMETRY:
|
||||
return "cannot get telemetry data";
|
||||
case MccGenericMountErrorCode::ERROR_UNSUPPORTED_TARGET_COORDPAIR:
|
||||
return "unsupported coordinate pair of target";
|
||||
case MccGenericMountErrorCode::ERROR_PZONE_COMP:
|
||||
return "an error occured while computing prohibited zone";
|
||||
case MccGenericMountErrorCode::ERROR_TARGET_IN_ZONE:
|
||||
return "target coordinates are in prohibitted zone";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccGenericMountCategory& get()
|
||||
{
|
||||
static const MccGenericMountCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccGenericMountErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccGenericMountCategory::get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <mcc_hardware_c HARDWARE_T,
|
||||
mcc_telemetry_c TELEMETRY_T,
|
||||
mcc_pzone_container_c PZONE_CONT_T,
|
||||
mcc_movement_controls_c MOVE_CNTRL_T,
|
||||
mcc_logger_c LOGGER_T>
|
||||
class MccGenericMount : protected HARDWARE_T,
|
||||
public TELEMETRY_T,
|
||||
public PZONE_CONT_T,
|
||||
public MOVE_CNTRL_T,
|
||||
public LOGGER_T
|
||||
{
|
||||
public:
|
||||
using LOGGER_T::logDebug;
|
||||
using LOGGER_T::logError;
|
||||
using LOGGER_T::logInfo;
|
||||
using LOGGER_T::logTrace;
|
||||
using LOGGER_T::logWarn;
|
||||
|
||||
typedef MccError error_t;
|
||||
|
||||
using typename TELEMETRY_T::telemetry_data_t;
|
||||
|
||||
enum class mount_status_t : int {
|
||||
MOUNT_STATUS_ERROR,
|
||||
MOUNT_STATUS_IDLE,
|
||||
MOUNT_STATUS_UNINITIALIZED,
|
||||
MOUNT_STATUS_INITIALIZATION,
|
||||
MOUNT_STATUS_STOPPED,
|
||||
MOUNT_STATUS_STOPPING,
|
||||
MOUNT_STATUS_SLEWING,
|
||||
MOUNT_STATUS_ADJUSTING,
|
||||
MOUNT_STATUS_GUIDING,
|
||||
MOUNT_STATUS_TRACKING
|
||||
};
|
||||
|
||||
template <typename... HardwareCtorTs,
|
||||
typename... TelemetryCtorTs,
|
||||
typename... PzoneContCtorTs,
|
||||
typename... MoveCntrCtorTs,
|
||||
typename... LoggerCtorTs>
|
||||
MccGenericMount(std::tuple<HardwareCtorTs...> hw_ctor_args,
|
||||
std::tuple<TelemetryCtorTs...> telemetry_ctor_args,
|
||||
std::tuple<PzoneContCtorTs...> pzone_cont_ctor_ars,
|
||||
std::tuple<MoveCntrCtorTs...> move_cntrl_ctor_ars,
|
||||
std::tuple<LoggerCtorTs...> logger_ctor_args)
|
||||
: HARDWARE_T(std::make_from_tuple<HARDWARE_T>(std::move(hw_ctor_args))),
|
||||
TELEMETRY_T(std::make_from_tuple<TELEMETRY_T>(std::move(telemetry_ctor_args))),
|
||||
PZONE_CONT_T(std::make_from_tuple<PZONE_CONT_T>(pzone_cont_ctor_ars)),
|
||||
MOVE_CNTRL_T(std::make_from_tuple<MOVE_CNTRL_T>(move_cntrl_ctor_ars)),
|
||||
LOGGER_T(std::make_from_tuple<LOGGER_T>(logger_ctor_args))
|
||||
{
|
||||
logDebug(std::format("Create MccGenericMount class instance (thread: {})", std::this_thread::get_id()));
|
||||
}
|
||||
|
||||
MccGenericMount(const MccGenericMount&) = delete;
|
||||
MccGenericMount(MccGenericMount&&) = default;
|
||||
|
||||
MccGenericMount& operator=(const MccGenericMount&) = delete;
|
||||
MccGenericMount& operator=(MccGenericMount&&) = default;
|
||||
|
||||
virtual ~MccGenericMount()
|
||||
{
|
||||
logDebug(std::format("Delete MccGenericMount class instance (thread: {})", std::this_thread::get_id()));
|
||||
|
||||
auto err = MOVE_CNTRL_T::stopMount();
|
||||
if (err) {
|
||||
logError(formatError(err));
|
||||
}
|
||||
}
|
||||
|
||||
error_t initMount()
|
||||
{
|
||||
logInfo(std::format("Start MccGenericMount class initialization (thread: {}) ...", std::this_thread::get_id()));
|
||||
|
||||
*_lastMountError = MccGenericMountErrorCode::ERROR_OK;
|
||||
|
||||
*_mountStatus = mount_status_t::MOUNT_STATUS_INITIALIZATION;
|
||||
|
||||
auto hw_err = this->hardwareInit();
|
||||
if (hw_err) {
|
||||
*_mountStatus = mount_status_t::MOUNT_STATUS_ERROR;
|
||||
|
||||
*_lastMountError = mcc_deduce_err(hw_err, MccGenericMountErrorCode::ERROR_HW_INIT);
|
||||
} else {
|
||||
logInfo("Generic mount initialization was performed");
|
||||
|
||||
*_mountStatus = mount_status_t::IDLE;
|
||||
}
|
||||
|
||||
return *_lastMountError;
|
||||
}
|
||||
|
||||
mount_status_t mountStatus() const
|
||||
{
|
||||
return _mountStatus->load();
|
||||
}
|
||||
|
||||
error_t mountLastError() const
|
||||
{
|
||||
return _lastMountError->load();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<std::atomic<mount_status_t>> _mountStatus{
|
||||
new std::atomic<mount_status_t>{mount_status_t::MOUNT_STATUS_UNINITIALIZED}};
|
||||
|
||||
std::unique_ptr<std::atomic<error_t>> _lastMountError{new std::atomic<error_t>{MccGenericMountErrorCode::ERROR_OK}};
|
||||
|
||||
std::string formatError(error_t const& err, std::string_view prefix = "") const
|
||||
{
|
||||
return std::format("{}{} (category: {}, code: {})", prefix, err.message(), err.value(), err.category().name());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
1262
include/mcc/mcc_netserver.h
Normal file
1262
include/mcc/mcc_netserver.h
Normal file
File diff suppressed because it is too large
Load Diff
512
include/mcc/mcc_netserver_endpoint.h
Normal file
512
include/mcc/mcc_netserver_endpoint.h
Normal file
@@ -0,0 +1,512 @@
|
||||
#pragma once
|
||||
|
||||
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
||||
|
||||
|
||||
|
||||
/* NETWORK SERVER ENDPOINT CLASS IMPLEMENTATION */
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
|
||||
#include "mcc_traits.h"
|
||||
|
||||
namespace mcc::network
|
||||
{
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
static constexpr bool mcc_char_range_compare(const traits::mcc_char_view auto& what,
|
||||
const traits::mcc_char_view auto& where,
|
||||
bool case_insensitive = false)
|
||||
{
|
||||
if (std::ranges::size(what) == std::ranges::size(where)) {
|
||||
if (case_insensitive) {
|
||||
auto f = std::ranges::search(where,
|
||||
std::views::transform(what, [](const char& ch) { return std::tolower(ch); }));
|
||||
return !f.empty();
|
||||
} else {
|
||||
auto f = std::ranges::search(where, what);
|
||||
return !f.empty();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
||||
/*
|
||||
* Very simple various protocols endpoint parser and holder class
|
||||
*
|
||||
* endpoint: proto_mark://host_name:port_num/path
|
||||
* where "path" is optional for all non-local protocol kinds;
|
||||
*
|
||||
* for local kind of protocols the endpoint must be given as:
|
||||
* local://stream/PATH
|
||||
* local://seqpacket/PATH
|
||||
* local://serial/PATH
|
||||
* where 'stream' and 'seqpacket' "host_name"-field marks the
|
||||
* stream-type and seqpacket-type UNIX domain sockets protocols;
|
||||
* 'serial' marks a serial (RS232/485) protocol.
|
||||
* here, possible "port_num" field is allowed but ignored.
|
||||
*
|
||||
* NOTE: "proto_mark" and "host_name" (for local kind) fields are parsed in case-insensitive manner!
|
||||
*
|
||||
* EXAMPLES: tcp://192.168.70.130:3131
|
||||
* local://serial/dev/ttyS1
|
||||
* local://seqpacket/tmp/BM70_SERVER_SOCK
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
class MccNetServerEndpoint
|
||||
{
|
||||
public:
|
||||
static constexpr std::string_view protoHostDelim = "://";
|
||||
static constexpr std::string_view hostPortDelim = ":";
|
||||
static constexpr std::string_view portPathDelim = "/";
|
||||
|
||||
enum proto_id_t : uint8_t {
|
||||
PROTO_ID_LOCAL,
|
||||
PROTO_ID_SEQLOCAL,
|
||||
PROTO_ID_SERLOCAL,
|
||||
PROTO_ID_TCP,
|
||||
PROTO_ID_TLS,
|
||||
PROTO_ID_UNKNOWN
|
||||
};
|
||||
|
||||
static constexpr std::string_view protoMarkLocal{"local"}; // UNIX domain
|
||||
static constexpr std::string_view protoMarkTCP{"tcp"}; // TCP
|
||||
static constexpr std::string_view protoMarkTLS{"tls"}; // TLS
|
||||
|
||||
static constexpr std::array validProtoMarks{protoMarkLocal, protoMarkTCP, protoMarkTLS};
|
||||
|
||||
|
||||
static constexpr std::string_view localProtoTypeStream{"stream"}; // UNIX domain stream
|
||||
static constexpr std::string_view localProtoTypeSeqpacket{"seqpacket"}; // UNIX domain seqpacket
|
||||
static constexpr std::string_view localProtoTypeSerial{"serial"}; // serial (RS232/485)
|
||||
|
||||
static constexpr std::array validLocalProtoTypes{localProtoTypeStream, localProtoTypeSeqpacket,
|
||||
localProtoTypeSerial};
|
||||
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
MccNetServerEndpoint(const R& ept)
|
||||
{
|
||||
fromRange(ept);
|
||||
}
|
||||
|
||||
MccNetServerEndpoint(const MccNetServerEndpoint& other)
|
||||
{
|
||||
copyInst(other);
|
||||
}
|
||||
|
||||
MccNetServerEndpoint(MccNetServerEndpoint&& other)
|
||||
{
|
||||
moveInst(std::move(other));
|
||||
}
|
||||
|
||||
virtual ~MccNetServerEndpoint() = default;
|
||||
|
||||
|
||||
MccNetServerEndpoint& operator=(const MccNetServerEndpoint& other)
|
||||
{
|
||||
copyInst(other);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
MccNetServerEndpoint& operator=(MccNetServerEndpoint&& other)
|
||||
{
|
||||
moveInst(std::move(other));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
requires std::ranges::contiguous_range<R>
|
||||
bool fromRange(const R& ept)
|
||||
{
|
||||
_isValid = false;
|
||||
|
||||
// at least 'ws://a' (proto, proto-host delimiter and at least a single character of hostname)
|
||||
if (std::ranges::size(ept) < 6) {
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
if constexpr (std::is_array_v<std::remove_cvref_t<R>>) {
|
||||
_endpoint = ept;
|
||||
} else {
|
||||
_endpoint.clear();
|
||||
std::ranges::copy(ept, std::back_inserter(_endpoint));
|
||||
}
|
||||
|
||||
auto found = std::ranges::search(_endpoint, protoHostDelim);
|
||||
if (found.empty()) {
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
|
||||
ssize_t idx;
|
||||
if ((idx = checkProtoMark(std::string_view{_endpoint.begin(), found.begin()})) < 0) {
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
_proto = validProtoMarks[idx];
|
||||
|
||||
_host = std::string_view{found.end(), _endpoint.end()};
|
||||
|
||||
auto f1 = std::ranges::search(_host, portPathDelim);
|
||||
// std::string_view port_sv;
|
||||
if (f1.empty() && isLocal()) { // no path, but it is mandatory for 'local'!
|
||||
return _isValid;
|
||||
} else {
|
||||
_host = std::string_view(_host.begin(), f1.begin());
|
||||
|
||||
_path = std::string_view(f1.end(), &*_endpoint.end());
|
||||
|
||||
f1 = std::ranges::search(_host, hostPortDelim);
|
||||
if (f1.empty() && !isLocal()) { // no port, but it is mandatory for non-local!
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
_portView = std::string_view(f1.end(), _host.end());
|
||||
if (_portView.size()) {
|
||||
_host = std::string_view(_host.begin(), f1.begin());
|
||||
|
||||
if (!isLocal()) {
|
||||
// convert port string to int
|
||||
auto end_ptr = _portView.data() + _portView.size();
|
||||
|
||||
auto [ptr, ec] = std::from_chars(_portView.data(), end_ptr, _port);
|
||||
if (ec != std::errc() || ptr != end_ptr) {
|
||||
return _isValid;
|
||||
}
|
||||
} else { // ignore for local
|
||||
_port = -1;
|
||||
}
|
||||
} else {
|
||||
_port = -1;
|
||||
}
|
||||
|
||||
if (isLocal()) { // check for special values
|
||||
idx = 0;
|
||||
if (std::ranges::any_of(validLocalProtoTypes, [&idx, this](const auto& el) {
|
||||
bool ok = utils::mcc_char_range_compare(_host, el, true);
|
||||
if (!ok) {
|
||||
++idx;
|
||||
}
|
||||
return ok;
|
||||
})) {
|
||||
_host = validLocalProtoTypes[idx];
|
||||
} else {
|
||||
return _isValid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isValid = true;
|
||||
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
|
||||
auto endpoint() const
|
||||
{
|
||||
return _endpoint;
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R proto() const
|
||||
{
|
||||
return part<R>(PROTO_PART);
|
||||
}
|
||||
|
||||
std::string_view proto() const
|
||||
{
|
||||
return proto<std::string_view>();
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R host() const
|
||||
{
|
||||
return part<R>(HOST_PART);
|
||||
}
|
||||
|
||||
std::string_view host() const
|
||||
{
|
||||
return host<std::string_view>();
|
||||
}
|
||||
|
||||
int port() const
|
||||
{
|
||||
return _port;
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R portView() const
|
||||
{
|
||||
return part<R>(PORT_PART);
|
||||
}
|
||||
|
||||
std::string_view portView() const
|
||||
{
|
||||
return portView<std::string_view>();
|
||||
}
|
||||
|
||||
template <traits::mcc_output_char_range R, traits::mcc_input_char_range RR = std::string_view>
|
||||
R path(RR&& root_path) const
|
||||
{
|
||||
if (_path.empty()) {
|
||||
if constexpr (traits::mcc_output_char_range<R>) {
|
||||
R res;
|
||||
std::ranges::copy(std::forward<RR>(root_path), std::back_inserter(res));
|
||||
|
||||
return res;
|
||||
} else { // can't add root path!!!
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
}
|
||||
|
||||
auto N = std::ranges::distance(root_path.begin(), root_path.end());
|
||||
|
||||
if (N) {
|
||||
R res;
|
||||
std::filesystem::path pt(root_path.begin(), root_path.end());
|
||||
|
||||
if (isLocal() && _path[0] == '\0') {
|
||||
std::ranges::copy(std::string_view(" "), std::back_inserter(res));
|
||||
pt /= _path.substr(1);
|
||||
std::ranges::copy(pt.string(), std::back_inserter(res));
|
||||
*res.begin() = '\0';
|
||||
} else {
|
||||
pt /= _path;
|
||||
std::ranges::copy(pt.string(), std::back_inserter(res));
|
||||
}
|
||||
|
||||
return res;
|
||||
} else {
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range RR = std::string_view>
|
||||
std::string path(RR&& root_path) const
|
||||
{
|
||||
return path<std::string, RR>(std::forward<RR>(root_path));
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R path() const
|
||||
{
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
|
||||
std::string_view path() const
|
||||
{
|
||||
return path<std::string_view>();
|
||||
}
|
||||
|
||||
|
||||
bool isLocal() const
|
||||
{
|
||||
return proto() == protoMarkLocal;
|
||||
}
|
||||
|
||||
bool isLocalStream() const
|
||||
{
|
||||
return host() == localProtoTypeStream;
|
||||
}
|
||||
|
||||
bool isLocalSerial() const
|
||||
{
|
||||
return host() == localProtoTypeSerial;
|
||||
}
|
||||
|
||||
bool isLocalSeqpacket() const
|
||||
{
|
||||
return host() == localProtoTypeSeqpacket;
|
||||
}
|
||||
|
||||
|
||||
bool isTCP() const
|
||||
{
|
||||
return proto() == protoMarkTCP;
|
||||
}
|
||||
|
||||
bool isTLS() const
|
||||
{
|
||||
return proto() == protoMarkTLS;
|
||||
}
|
||||
|
||||
|
||||
// add '\0' char (or replace special-meaning char/char-sequence) to construct UNIX abstract namespace
|
||||
// endpoint path
|
||||
template <typename T = std::nullptr_t>
|
||||
MccNetServerEndpoint& makeAbstract(const T& mark = nullptr)
|
||||
requires(traits::mcc_input_char_range<T> || std::same_as<std::remove_cv_t<T>, char> ||
|
||||
std::is_null_pointer_v<std::remove_cv_t<T>>)
|
||||
{
|
||||
if (!(isLocalStream() || isLocalSeqpacket())) { // only local proto is valid!
|
||||
return *this;
|
||||
}
|
||||
|
||||
if constexpr (std::is_null_pointer_v<T>) { // just insert '\0'
|
||||
auto it = _endpoint.insert(std::string::const_iterator(_path.begin()), '\0');
|
||||
_path = std::string_view(it, _endpoint.end());
|
||||
} else if constexpr (std::same_as<std::remove_cv_t<T>, char>) { // replace a character (mark)
|
||||
auto pos = std::distance(_endpoint.cbegin(), std::string::const_iterator(_path.begin()));
|
||||
if (_endpoint[pos] == mark) {
|
||||
_endpoint[pos] = '\0';
|
||||
}
|
||||
} else { // replace a character range (mark)
|
||||
if (std::ranges::equal(_path | std::views::take(std::ranges::size(mark), mark))) {
|
||||
auto pos = std::distance(_endpoint.cbegin(), std::string::const_iterator(_path.begin()));
|
||||
_endpoint.replace(pos, std::ranges::size(mark), 1, '\0');
|
||||
_path = std::string_view(_endpoint.begin() + pos, _endpoint.end());
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string _endpoint;
|
||||
std::string_view _proto, _host, _path, _portView;
|
||||
int _port;
|
||||
bool _isValid;
|
||||
|
||||
|
||||
virtual ssize_t checkProtoMark(std::string_view proto_mark)
|
||||
{
|
||||
ssize_t idx = 0;
|
||||
|
||||
// case-insensitive look-up
|
||||
bool found = std::ranges::any_of(MccNetServerEndpoint::validProtoMarks, [&idx, &proto_mark](const auto& el) {
|
||||
bool ok = utils::mcc_char_range_compare(proto_mark, el, true);
|
||||
|
||||
if (!ok) {
|
||||
++idx;
|
||||
}
|
||||
|
||||
return ok;
|
||||
});
|
||||
|
||||
return found ? idx : -1;
|
||||
}
|
||||
|
||||
enum EndpointPart { PROTO_PART, HOST_PART, PATH_PART, PORT_PART };
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R part(EndpointPart what) const
|
||||
{
|
||||
R res;
|
||||
|
||||
// if (!_isValid) {
|
||||
// return res;
|
||||
// }
|
||||
|
||||
auto part = _proto;
|
||||
|
||||
switch (what) {
|
||||
case PROTO_PART:
|
||||
part = _proto;
|
||||
break;
|
||||
case HOST_PART:
|
||||
part = _host;
|
||||
break;
|
||||
case PATH_PART:
|
||||
part = _path;
|
||||
break;
|
||||
case PORT_PART:
|
||||
part = _portView;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if constexpr (std::ranges::view<R>) {
|
||||
return {part.begin(), part.end()};
|
||||
} else {
|
||||
std::ranges::copy(part, std::back_inserter(res));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void copyInst(const MccNetServerEndpoint& other)
|
||||
{
|
||||
if (&other != this) {
|
||||
if (other._isValid) {
|
||||
_isValid = other._isValid;
|
||||
_endpoint = other._endpoint;
|
||||
_proto = other._proto;
|
||||
|
||||
std::iterator_traits<const char*>::difference_type idx;
|
||||
if (other.isLocal()) { // for 'local' host is one of static class constants
|
||||
_host = other._host;
|
||||
} else {
|
||||
idx = std::distance(other._endpoint.c_str(), other._host.data());
|
||||
_host = std::string_view(_endpoint.c_str() + idx, other._host.size());
|
||||
}
|
||||
|
||||
idx = std::distance(other._endpoint.c_str(), other._path.data());
|
||||
_path = std::string_view(_endpoint.c_str() + idx, other._path.size());
|
||||
|
||||
idx = std::distance(other._endpoint.c_str(), other._portView.data());
|
||||
_portView = std::string_view(_endpoint.c_str() + idx, other._portView.size());
|
||||
|
||||
_port = other._port;
|
||||
} else {
|
||||
_isValid = false;
|
||||
_endpoint = std::string();
|
||||
_proto = std::string_view();
|
||||
_host = std::string_view();
|
||||
_path = std::string_view();
|
||||
_portView = std::string_view();
|
||||
_port = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void moveInst(MccNetServerEndpoint&& other)
|
||||
{
|
||||
if (&other != this) {
|
||||
if (other._isValid) {
|
||||
_isValid = std::move(other._isValid);
|
||||
_endpoint = std::move(other._endpoint);
|
||||
_proto = other._proto;
|
||||
_host = std::move(other._host);
|
||||
_path = std::move(other._path);
|
||||
_port = std::move(other._port);
|
||||
_portView = std::move(other._portView);
|
||||
} else {
|
||||
_isValid = false;
|
||||
_endpoint = std::string();
|
||||
_proto = std::string_view();
|
||||
_host = std::string_view();
|
||||
_path = std::string_view();
|
||||
_portView = std::string_view();
|
||||
_port = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcc::network
|
||||
601
include/mcc/mcc_netserver_proto.h
Normal file
601
include/mcc/mcc_netserver_proto.h
Normal file
@@ -0,0 +1,601 @@
|
||||
#pragma once
|
||||
|
||||
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
||||
|
||||
/* BASIC NETWORK PROTOCOL DEFINITIONS */
|
||||
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
|
||||
#include "mcc_deserializer.h"
|
||||
#include "mcc_serializer.h"
|
||||
#include "mcc_utils.h"
|
||||
|
||||
namespace mcc::network
|
||||
{
|
||||
|
||||
/*
|
||||
* The network protocol is the ASCII-based, case-sensitive textual protocol.
|
||||
* The "client-server" communication is performed through messages.
|
||||
* The message is a minimal unit of this communication.
|
||||
* The model of network communication is a simple "client-server" one, i.e.,
|
||||
* client asks - server responds.
|
||||
*
|
||||
* network communication message format:
|
||||
* <keyword>[[<key-param-delim>]<param1>[<param-param-delim>][<param2>]...]<stop-seq>
|
||||
*
|
||||
* where
|
||||
* <keyword> - mandatory message keyword (one or more ASCII symbols)
|
||||
* <key-param-delim>
|
||||
*
|
||||
* e.g.
|
||||
* "TARGET 12:23:45.56 00:32:21.978\n"
|
||||
*/
|
||||
|
||||
|
||||
/* low-level network message format definitions */
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_STOP_SEQ = "\n";
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYPARAM_DELIM_SEQ = " ";
|
||||
static constexpr std::string_view MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ = ";";
|
||||
static constexpr std::string_view MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ = ",";
|
||||
|
||||
|
||||
/* server special keywords */
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR = "ACK"; // ACK
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR = "ERROR"; // mount operational error
|
||||
// pre-defined errors
|
||||
static constexpr std::string_view MCC_COMMPROTO_SERVER_ERROR_INVKEY_STR = "INVKEY"; // invalid keyword
|
||||
static constexpr std::string_view MCC_COMMPROTO_SERVER_ERROR_INVPAR_STR = "INVPAR"; // invalid parameter
|
||||
|
||||
|
||||
/* server control keywords */
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_RESTART_SERVER_STR = "RESTART"; // restart server
|
||||
|
||||
|
||||
/* BELOW IS ONE OF THE PROTOCOL OPTIONS CORRESPONDING MCC_GENERIC_MOUNT_C CONCEPT */
|
||||
|
||||
|
||||
|
||||
/* keywords */
|
||||
|
||||
// NOTE: THE COORDINATES AND TIME-RELATED QUANTITIES CAN BE EXPRESSED IN THE TWO FORMATS:
|
||||
// 1) fixed-point real number, e.g. 123.43987537359 or -0.09775
|
||||
// 2) sexagesimal number, e.g. 10:43:43.12 or -123:54:12.435
|
||||
//
|
||||
// IN THE FIRST CASE ALL NUMBERS MUST BE INTERPRETATED AS DEGREES,
|
||||
// IN THE SECOND CASE NUMBERS MUST BE INTERPRETATED ACCORDING TO ITS TYPE:
|
||||
// ALL TIME-RELATED QUANTITIES AND RA/HA COORDINATES MUST BE EXPRESSED
|
||||
// IN FORMAT 'HOURS:MINUTES:SECONDS', WHILE DEC/ALT/AZ/ZD COORDINATES MUST
|
||||
// BE EXPRESSED AS '+/-DEGREES:ARCMINUTES:ARCSECONDS'
|
||||
//
|
||||
// USER-ENTERED (FROM NETWORK CLIENTS) COORDINATE PAIR CAN BE PROVIDED IN A MIXED FORM, I.E.,
|
||||
// 12.34436658678 10:32:11.432 or
|
||||
// 10:32:11.432 12.34436658678
|
||||
//
|
||||
// SERVER-RESPONDED COORDINATES ARE ALWAYS IN THE SAME FORMAT, SEXAGESIMAL OR FIXED-POINT
|
||||
//
|
||||
|
||||
// format of output coordinates:
|
||||
// "COORDFMT FMT-type\n"
|
||||
// e.g.:
|
||||
// "COORDFMT SGM\n"
|
||||
// "COORDFMT\n"
|
||||
//
|
||||
// server must return "ACK" or "ERROR INVPAR" in the case of 'set'-operation and
|
||||
// "ACK COORDFMT FMT-type" in the case of 'get'-operation
|
||||
// e.g.:
|
||||
// "COORDFMT FIX\n" -> "ACK\n"
|
||||
// "COORDFMT SXT\n" -> "ERROR INVPAR\n" (invalid parameter of format type)
|
||||
// "COORDFMT\n" -> "ACK COORDFMT FIX\n"
|
||||
//
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_STR = "COORDFMT";
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_SEXGM_STR = "SGM"; // sexagesimal
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_FIXED_STR = "FIX"; // fixed point
|
||||
|
||||
|
||||
// precision (number of decimal places) of returned coordinates:
|
||||
// "COORDPREC seconds-prec arcseconds-prec\n"
|
||||
// seconds-prec - precision of hour-based coordinates (RA and HA) or time-related quantities
|
||||
// arcseconds-prec - precision of degree-based coordinates (DEC, AZ, ZD, ALT)
|
||||
// precision must be given as non-negative integer number
|
||||
// e.g.
|
||||
// "COORDPREC 2,1\n" (output sexagesimal RA=12:34:56.67, DEC=32:54:21.9)
|
||||
//
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDPREC_STR = "COORDPREC";
|
||||
|
||||
|
||||
// set/get target coordinates
|
||||
// "TARGET X-coord Y-coord XY-kind\n", if 'XY-kind' is omitted then one should assume RADEC_ICRS
|
||||
// e.g.:
|
||||
// "TARGET 12.7683487 10:23:09.75 AZZD\n"
|
||||
// "TARGET HADEC\n"
|
||||
// "TARGET\n"
|
||||
//
|
||||
// server must return "ACK" or "ERROR INVPAR" in the case of 'set'-operation and
|
||||
// "ACK TARGET X-coord Y-coord XY-kind" in the case of 'get'-operation
|
||||
// e.g.:
|
||||
// "TARGET 12.7683487 10:23:09.75 AZZD\n" -> "ACK\n"
|
||||
// "TARGET 12.7683487 10:23:09.75 AZZE\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind)
|
||||
//
|
||||
// "TARGET HADEC\n" -> "ACK TARGET 20:21:56.32 -01:32:34.2 HADEC\n"
|
||||
// "TARGET\n" -> "ACK TARGET 20:21:56.32 -01:32:34.2 RADEC_ICRS\n"
|
||||
//
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TARGET_STR = "TARGET";
|
||||
|
||||
// get mount coordinates:
|
||||
// "MOUNT coord-kind", if 'coord-kind' is omitted then coordinates are according to mount type,
|
||||
// i.e., HADEC for equathorial-type mount and AZZD for alt-azimuthal one
|
||||
// e.g.:
|
||||
// "MOUNT RADEC\n" (get current apparent RA and DEC mount coordinates)
|
||||
//
|
||||
// server must return "ACK MOUNT X-coord Y-coord XY-kind" or "ERROR INVPAR"
|
||||
// e.g.
|
||||
// "MOUNT AZALT\n" -> "ACK MOUNT 1.2332325 54.23321312 AZALT\n"
|
||||
// "MOUNT AZAL\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind)
|
||||
// "MOUNT\n" -> "ACK MOUNT 1.2332325 54.23321312 AZZD\n" for alt-azimuthal mount
|
||||
// "MOUNT\n" -> "ACK MOUNT 1.2332325 54.23321312 HADEC\n" for equathorial mount
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_MOUNT_STR = "MOUNT";
|
||||
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TELEMETRY_STR = "TELEMETRY";
|
||||
|
||||
// init mount
|
||||
// "INIT\n"
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_INIT_STR = "INIT";
|
||||
|
||||
// stop any movements
|
||||
// "STOP\n"
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_STOP_STR = "STOP";
|
||||
|
||||
// slew mount and track target:
|
||||
// "SLEW\n"
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SLEW_STR = "SLEW";
|
||||
|
||||
// slew mount and stop:
|
||||
// "MOVE\n"
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_MOVE_STR = "MOVE";
|
||||
|
||||
// track target
|
||||
// "TRACK\n"
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TRACK_STR = "TRACK";
|
||||
|
||||
// get mount status
|
||||
// "STATUS\n"
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_STATUS_STR = "STATUS";
|
||||
|
||||
// valid keywords
|
||||
static constexpr std::array MCC_COMMPROTO_VALID_KEYS = {
|
||||
MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR, MCC_COMMPROTO_KEYWORD_COORDFMT_STR,
|
||||
MCC_COMMPROTO_KEYWORD_COORDPREC_STR, MCC_COMMPROTO_KEYWORD_TARGET_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR,
|
||||
MCC_COMMPROTO_KEYWORD_TELEMETRY_STR, MCC_COMMPROTO_KEYWORD_INIT_STR, MCC_COMMPROTO_KEYWORD_STOP_STR,
|
||||
MCC_COMMPROTO_KEYWORD_SLEW_STR, MCC_COMMPROTO_KEYWORD_MOVE_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR,
|
||||
MCC_COMMPROTO_KEYWORD_STATUS_STR};
|
||||
|
||||
|
||||
// hashes of valid keywords
|
||||
static constexpr std::array MCC_COMMPROTO_VALID_KEYS_HASH = []<size_t... Is>(std::index_sequence<Is...>) {
|
||||
return std::array{mcc::utils::FNV1aHash(MCC_COMMPROTO_VALID_KEYS[Is])...};
|
||||
}(std::make_index_sequence<MCC_COMMPROTO_VALID_KEYS.size()>());
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_netmsg_valid_keys_c = requires(T t) {
|
||||
// std::array of valid message keywords
|
||||
[]<size_t N>(std::array<std::string_view, N>) {
|
||||
// to ensure T::NETMSG_VALID_KEYWORDS can be used as compile-time constant
|
||||
static constexpr auto v0 = T::NETMSG_VALID_KEYWORDS[0];
|
||||
return v0;
|
||||
}(T::NETMSG_VALID_KEYWORDS);
|
||||
|
||||
// std::array of valid message keywords hashes
|
||||
[]<size_t N>(std::array<size_t, N>) {
|
||||
// to ensure T::NETMSG_VALID_KEYWORD_HASHES can be used as compile-time constant
|
||||
static constexpr auto v0 = T::NETMSG_VALID_KEYWORD_HASHES[0];
|
||||
return v0;
|
||||
}(T::NETMSG_VALID_KEYWORD_HASHES);
|
||||
|
||||
requires T::NETMSG_VALID_KEYWORDS.size() == T::NETMSG_VALID_KEYWORD_HASHES.size();
|
||||
};
|
||||
|
||||
|
||||
struct MccNetMessageValidKeywords {
|
||||
static constexpr std::array NETMSG_VALID_KEYWORDS = {
|
||||
MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR,
|
||||
MCC_COMMPROTO_KEYWORD_COORDFMT_STR, MCC_COMMPROTO_KEYWORD_COORDPREC_STR,
|
||||
MCC_COMMPROTO_KEYWORD_TARGET_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR,
|
||||
MCC_COMMPROTO_KEYWORD_TELEMETRY_STR, MCC_COMMPROTO_KEYWORD_INIT_STR,
|
||||
MCC_COMMPROTO_KEYWORD_STOP_STR, MCC_COMMPROTO_KEYWORD_SLEW_STR,
|
||||
MCC_COMMPROTO_KEYWORD_MOVE_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR,
|
||||
MCC_COMMPROTO_KEYWORD_STATUS_STR};
|
||||
|
||||
|
||||
// hashes of valid keywords
|
||||
static constexpr std::array NETMSG_VALID_KEYWORD_HASHES = []<size_t... Is>(std::index_sequence<Is...>) {
|
||||
return std::array{mcc::utils::FNV1aHash(NETMSG_VALID_KEYWORDS[Is])...};
|
||||
}(std::make_index_sequence<NETMSG_VALID_KEYWORDS.size()>());
|
||||
|
||||
constexpr static const size_t* isKeywordValid(std::string_view key)
|
||||
{
|
||||
const auto hash = mcc::utils::FNV1aHash(key);
|
||||
|
||||
for (auto const& h : NETMSG_VALID_KEYWORD_HASHES) {
|
||||
if (h == hash) {
|
||||
return &h;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(mcc_netmsg_valid_keys_c<MccNetMessageValidKeywords>, "");
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_netmessage_c = requires(T t) { T(); };
|
||||
|
||||
|
||||
|
||||
template <mcc::traits::mcc_char_range BYTEREPR_T = std::string_view,
|
||||
mcc_netmsg_valid_keys_c BASE_T = MccNetMessageValidKeywords>
|
||||
class MccNetMessage
|
||||
{
|
||||
protected:
|
||||
// just a wrapper class to hold MccSerializer<T> specializations
|
||||
struct DefaultSerializer {
|
||||
template <traits::mcc_output_char_range OR, typename T>
|
||||
auto operator()(OR& bytes, const T& value, mcc_serialization_params_c auto const& pars) const
|
||||
{
|
||||
return impl::MccSerializer<T>{}(bytes, value, pars);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
typedef BASE_T valid_keys_t;
|
||||
typedef BYTEREPR_T byte_repr_t;
|
||||
|
||||
enum MccNetMessageError { ERROR_OK, ERROR_EMPTY_MESSAGE, ERROR_INVALID_KEYWORD, ERROR_EMPTY_KEYWORD };
|
||||
|
||||
MccNetMessage() = default;
|
||||
|
||||
template <traits::mcc_input_char_range KT, typename... PTs>
|
||||
MccNetMessage(KT&& key, PTs&&... params)
|
||||
requires traits::mcc_output_char_range<BYTEREPR_T>
|
||||
{
|
||||
construct(_defaultSerializer, std::forward<KT>(key), std::forward<PTs>(params)...);
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
constexpr MccNetMessage(const R& msg)
|
||||
requires traits::mcc_input_char_range<BYTEREPR_T>
|
||||
{
|
||||
fromCharRange(msg);
|
||||
}
|
||||
|
||||
// constexpr MccNetMessage(const BYTEREPR_T& msg)
|
||||
// requires traits::mcc_input_char_range<BYTEREPR_T>
|
||||
// {
|
||||
// fromCharRange(msg);
|
||||
// }
|
||||
|
||||
virtual ~MccNetMessage() = default;
|
||||
|
||||
template <traits::mcc_input_char_range KT>
|
||||
constexpr bool withKey(const KT& key) const
|
||||
{
|
||||
if constexpr (std::is_pointer_v<std::decay_t<KT>>) {
|
||||
return withKey(std::string_view{key});
|
||||
}
|
||||
|
||||
return mcc::utils::FNV1aHash(key) == _keywordHash;
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R keyword() const
|
||||
{
|
||||
if constexpr (traits::mcc_char_view<R>) {
|
||||
return R{_keyword.begin(), _keyword.end()};
|
||||
} else {
|
||||
R r;
|
||||
std::ranges::copy(_keyword, std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view keyword() const
|
||||
{
|
||||
return _keyword;
|
||||
}
|
||||
|
||||
|
||||
size_t paramSize() const
|
||||
{
|
||||
return _params.size();
|
||||
}
|
||||
|
||||
template <std::ranges::range R>
|
||||
R params(size_t start_idx = 0, size_t Nelemes = std::numeric_limits<size_t>::max()) const
|
||||
requires(traits::mcc_view_or_output_char_range<R> || traits::mcc_range_of_char_range<R>)
|
||||
{
|
||||
if (start_idx >= _params.size()) {
|
||||
return R{};
|
||||
}
|
||||
|
||||
auto stop_idx = start_idx + Nelemes - 1;
|
||||
if (stop_idx >= _params.size()) {
|
||||
stop_idx = _params.size() - 1;
|
||||
}
|
||||
|
||||
if constexpr (traits::mcc_range_of_char_range<R>) { // returm parameters as array
|
||||
using el_t = std::ranges::range_value_t<R>;
|
||||
|
||||
R r;
|
||||
if constexpr (traits::mcc_char_view<el_t> || traits::mcc_output_char_range<el_t>) {
|
||||
for (size_t i = start_idx; i <= stop_idx; ++i) {
|
||||
auto& el = _params[i];
|
||||
std::back_inserter(r) = el_t{el.begin(), el.end()};
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED RANGE TYPE!!!");
|
||||
}
|
||||
|
||||
return r;
|
||||
} else {
|
||||
if constexpr (traits::mcc_char_view<R>) { // return joined parameters as a single char-range
|
||||
return R{_params[start_idx].begin(), _params[stop_idx].end()};
|
||||
} else {
|
||||
R r;
|
||||
std::ranges::copy(std::string_view{_params[start_idx].begin(), _params[stop_idx].end()},
|
||||
std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view params(size_t start_idx = 0, size_t Nelemes = std::numeric_limits<size_t>::max()) const
|
||||
{
|
||||
return params<std::string_view>(start_idx, Nelemes);
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R param(size_t idx) const
|
||||
{
|
||||
if (idx >= _params.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if constexpr (traits::mcc_char_view<R>) {
|
||||
return R{_params[idx].begin(), _params[idx].end()};
|
||||
} else {
|
||||
R r;
|
||||
std::ranges::copy(_params[idx], std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view param(size_t idx) const
|
||||
{
|
||||
if (idx >= _params.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _params[idx];
|
||||
}
|
||||
|
||||
|
||||
template <typename T, typename DeserFuncT>
|
||||
std::expected<T, std::error_code> paramValue(size_t idx, DeserFuncT&& deser_func) const
|
||||
{
|
||||
static_assert(std::invocable<DeserFuncT, typename decltype(_params)::value_type const&, T&,
|
||||
impl::mcc_serialization_params_t const&>,
|
||||
"INVALID DESERIALIZATION FUNCTION!");
|
||||
static_assert(std::same_as<std::invoke_result_t<DeserFuncT, typename decltype(_params)::value_type const&, T&,
|
||||
impl::mcc_serialization_params_t const&>,
|
||||
std::error_code>,
|
||||
"INVALID DESERIALIZATION FUNCTION!");
|
||||
|
||||
if (idx >= _params.size()) {
|
||||
return std::unexpected{std::make_error_code(std::errc::value_too_large)};
|
||||
}
|
||||
|
||||
T val;
|
||||
|
||||
auto ec = std::forward<DeserFuncT>(deser_func)(_params[idx], val, _serializationParams);
|
||||
if (ec) {
|
||||
return std::unexpected(ec);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::expected<T, std::error_code> paramValue(size_t idx) const
|
||||
{
|
||||
return paramValue<T>(
|
||||
idx, impl::MccDeserializer<T>{}); // use one of specialization of MccDeserializer templated class
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R byteRepr() const
|
||||
{
|
||||
if constexpr (traits::mcc_char_view<R>) {
|
||||
return R{_msgBuffer.begin(), _msgBuffer.end()};
|
||||
} else {
|
||||
R r;
|
||||
std::ranges::copy(_msgBuffer, std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view byteRepr() const
|
||||
{
|
||||
return byteRepr<std::string_view>();
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range KT, typename... PTs>
|
||||
std::error_code construct(KT&& key, PTs&&... params)
|
||||
requires traits::mcc_output_char_range<BYTEREPR_T>
|
||||
{
|
||||
return construct(_defaultSerializer, std::forward<KT>(key), std::forward<PTs>(params)...);
|
||||
}
|
||||
|
||||
//
|
||||
// serializing function SerFuncT - a callable with the signature:
|
||||
// template<typename T, mcc_output_char_range R>
|
||||
// void ser_func(R&& buffer, const T& val, mcc_serialization_params_t const& pars)
|
||||
//
|
||||
template <typename SerFuncT, traits::mcc_input_char_range KT, typename... PTs>
|
||||
std::error_code construct(SerFuncT&& ser_func, KT&& key, PTs&&... params)
|
||||
requires(traits::mcc_output_char_range<BYTEREPR_T> &&
|
||||
!traits::mcc_input_char_range<std::remove_cvref_t<SerFuncT>>)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<std::decay_t<KT>>) {
|
||||
return construct(std::forward<SerFuncT>(ser_func), std::string_view(key), std::forward<PTs>(params)...);
|
||||
}
|
||||
|
||||
|
||||
if (!std::ranges::size(key)) {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
auto r = valid_keys_t::isKeywordValid(key);
|
||||
if (!r) {
|
||||
return std::make_error_code(std::errc::not_supported);
|
||||
}
|
||||
|
||||
_keywordHash = *r;
|
||||
|
||||
_msgBuffer = BYTEREPR_T{};
|
||||
|
||||
std::ranges::copy(std::forward<KT>(key), std::back_inserter(_msgBuffer));
|
||||
// _keyword = {_msgBuffer.begin(), _msgBuffer.end()};
|
||||
size_t key_idx = std::distance(_msgBuffer.begin(), _msgBuffer.end());
|
||||
std::vector<size_t> par_idx;
|
||||
|
||||
_params.clear();
|
||||
|
||||
if constexpr (sizeof...(PTs)) {
|
||||
std::ranges::copy(MCC_COMMPROTO_KEYPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer));
|
||||
|
||||
convertFunc(std::forward<SerFuncT>(ser_func), par_idx, std::forward<PTs>(params)...);
|
||||
|
||||
for (size_t i = 0; i < par_idx.size(); i += 2) {
|
||||
_params.emplace_back(_msgBuffer.begin() + par_idx[i], _msgBuffer.begin() + par_idx[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
_keyword = std::string_view{_msgBuffer.begin(), _msgBuffer.begin() + key_idx};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
constexpr MccNetMessageError fromCharRange(const R& r)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<std::decay_t<R>>) {
|
||||
return fromCharRange(std::string_view(r));
|
||||
}
|
||||
|
||||
|
||||
if (std::ranges::size(r) == 0) {
|
||||
return ERROR_EMPTY_MESSAGE;
|
||||
}
|
||||
|
||||
std::string_view key;
|
||||
|
||||
// auto prev_msg_buff = _msgBuffer;
|
||||
|
||||
if constexpr (traits::mcc_output_char_range<BYTEREPR_T>) {
|
||||
_msgBuffer = BYTEREPR_T{};
|
||||
std::ranges::copy(r, std::back_inserter(_msgBuffer));
|
||||
} else {
|
||||
_msgBuffer = {std::begin(r), std::end(r)};
|
||||
}
|
||||
|
||||
auto found = std::ranges::search(_msgBuffer, MCC_COMMPROTO_KEYPARAM_DELIM_SEQ);
|
||||
|
||||
if (found.empty()) { // only keyword
|
||||
key = mcc::utils::trimSpaces(std::string_view{_msgBuffer.begin(), _msgBuffer.end()});
|
||||
} else {
|
||||
key = mcc::utils::trimSpaces(std::string_view{_msgBuffer.begin(), found.begin()});
|
||||
}
|
||||
|
||||
auto kv = valid_keys_t::isKeywordValid(key);
|
||||
if (!kv) {
|
||||
// _msgBuffer = prev_msg_buff; // restore previous netmessage state
|
||||
return ERROR_INVALID_KEYWORD;
|
||||
}
|
||||
|
||||
_keywordHash = *kv;
|
||||
_keyword = key;
|
||||
|
||||
if (!found.empty()) { // params ...
|
||||
_params.clear();
|
||||
auto pr =
|
||||
std::views::split(std::string_view{found.end(), _msgBuffer.end()}, MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ);
|
||||
for (auto const& p : pr) {
|
||||
_params.emplace_back(p.begin(), p.end());
|
||||
}
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _keywordHash{};
|
||||
std::string_view _keyword{};
|
||||
std::vector<std::string_view> _params{};
|
||||
|
||||
BYTEREPR_T _msgBuffer{};
|
||||
|
||||
DefaultSerializer _defaultSerializer{};
|
||||
|
||||
impl::mcc_serialization_params_t _serializationParams{.seq_delim{MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ},
|
||||
.elem_delim{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ}};
|
||||
|
||||
template <typename SerFuncT, typename T, typename... Ts>
|
||||
void convertFunc(SerFuncT&& ser_func, std::vector<size_t>& idx, const T& par, const Ts&... pars)
|
||||
{
|
||||
if constexpr (std::same_as<T, MccSerializedCoordPairFormat>) {
|
||||
_serializationParams.coordpair_format = par;
|
||||
} else if constexpr (std::same_as<T, MccSerializedAngleFormatPrec>) {
|
||||
_serializationParams.angle_prec = par;
|
||||
} else {
|
||||
idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
||||
|
||||
std::forward<SerFuncT>(ser_func)(_msgBuffer, par, _serializationParams);
|
||||
|
||||
idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
||||
|
||||
if constexpr (sizeof...(Ts)) {
|
||||
std::ranges::copy(_serializationParams.seq_delim, std::back_inserter(_msgBuffer));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(Ts)) {
|
||||
convertFunc(std::forward<SerFuncT>(ser_func), idx, pars...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(MccNetMessage<std::string, MccNetMessageValidKeywords>{"ACK"}.withKey("ACK"));
|
||||
static_assert(MccNetMessage{"ACK"}.withKey("ACK"));
|
||||
|
||||
} // namespace mcc::network
|
||||
470
include/mcc/mcc_pcm.h
Normal file
470
include/mcc/mcc_pcm.h
Normal file
@@ -0,0 +1,470 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
||||
|
||||
/* A REFERENCE "POINTING-CORRECTION-MODEL" CLASS IMPLEMENTATION */
|
||||
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
#include "mcc_bsplines.h"
|
||||
#endif
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_coordinate.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccDefaultPCMErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_CCTE,
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
ERROR_INVALID_INPUTS_BISPLEV,
|
||||
#endif
|
||||
ERROR_EXCEED_MAX_ITERS,
|
||||
ERROR_NULLPTR
|
||||
};
|
||||
|
||||
/* error category definition */
|
||||
|
||||
// error category
|
||||
struct MccDefaultPCMCategory : public std::error_category {
|
||||
MccDefaultPCMCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "ADC_GENERIC_DEVICE";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccDefaultPCMErrorCode err = static_cast<MccDefaultPCMErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccDefaultPCMErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccDefaultPCMErrorCode::ERROR_CCTE:
|
||||
return "CCTE calculation error";
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
case MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV:
|
||||
return "invalid input arguments for bispev";
|
||||
#endif
|
||||
case MccDefaultPCMErrorCode::ERROR_EXCEED_MAX_ITERS:
|
||||
return "exceed maximum of iterations number";
|
||||
case MccDefaultPCMErrorCode::ERROR_NULLPTR:
|
||||
return "nullptr input argument";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccDefaultPCMCategory& get()
|
||||
{
|
||||
static const MccDefaultPCMCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccDefaultPCMErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccDefaultPCMCategory::get());
|
||||
}
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccDefaultPCMErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
|
||||
// type of PCM corrections (algorithm used):
|
||||
// PCM_TYPE_GEOMETRY - "classic" geometry-based correction coefficients
|
||||
// PCM_TYPE_GEOMETRY_BSPLINE - previous one and additional 2D B-spline corrections
|
||||
// PCM_TYPE_BSPLINE - pure 2D B-spline corrections
|
||||
enum class MccDefaultPCMType { PCM_TYPE_GEOMETRY, PCM_TYPE_GEOMETRY_BSPLINE, PCM_TYPE_BSPLINE };
|
||||
|
||||
template <MccDefaultPCMType TYPE>
|
||||
static constexpr std::string_view MccDefaultPCMTypeString =
|
||||
TYPE == MccDefaultPCMType::PCM_TYPE_GEOMETRY ? "GEOMETRY"
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
: TYPE == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE ? "GEOMETRY-BSPLINE"
|
||||
: TYPE == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE ? "BSPLINE"
|
||||
#endif
|
||||
: "UNKNOWN";
|
||||
|
||||
template <MccMountType MOUNT_TYPE>
|
||||
class MccDefaultPCM : public mcc_pcm_interface_t<std::error_code>
|
||||
{
|
||||
public:
|
||||
static constexpr MccMountType mountType = MOUNT_TYPE;
|
||||
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
static constexpr std::string_view pcmName{"MCC-GEOMETRY-BSPLINES-PCM"};
|
||||
#else
|
||||
static constexpr std::string_view pcmName{"MCC-GEOMETRY-ONLY-PCM"};
|
||||
#endif
|
||||
|
||||
typedef std::error_code error_t;
|
||||
typedef double coord_t;
|
||||
|
||||
|
||||
// "classic" geometric PCM coefficients
|
||||
struct pcm_geom_coeffs_t {
|
||||
typedef double coeff_t;
|
||||
|
||||
coeff_t zeroPointX;
|
||||
coeff_t zeroPointY;
|
||||
coeff_t collimationErr; // tube collimation error
|
||||
coeff_t nonperpendErr; // X-Y axes nonperpendicularity
|
||||
coeff_t misalignErr1; // misalignment of hour-angle/azimuth axis: left-right for equatorial, East-West for
|
||||
// alt-azimuthal
|
||||
coeff_t misalignErr2; // misalignment of hour-angle/azimuth axis: vertical for equatorial, North-South for
|
||||
// alt-azimuthal
|
||||
|
||||
coeff_t tubeFlexure;
|
||||
coeff_t forkFlexure;
|
||||
coeff_t DECaxisFlexure; // declination axis flexure
|
||||
};
|
||||
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
// B-splines related data structure (coefficients, knots ...)
|
||||
struct pcm_bspline_t {
|
||||
typedef double knot_t;
|
||||
typedef double coeff_t;
|
||||
|
||||
size_t bsplDegreeX = 3;
|
||||
size_t bsplDegreeY = 3;
|
||||
|
||||
std::vector<knot_t> knotsX{};
|
||||
std::vector<knot_t> knotsY{};
|
||||
|
||||
std::vector<coeff_t> coeffsX{};
|
||||
std::vector<coeff_t> coeffsY{};
|
||||
};
|
||||
#endif
|
||||
|
||||
struct pcm_data_t {
|
||||
MccDefaultPCMType type{MccDefaultPCMType::PCM_TYPE_GEOMETRY};
|
||||
double siteLatitude{0.0}; // in radians
|
||||
|
||||
// from encoder to observed celestial
|
||||
pcm_geom_coeffs_t geomCoefficients{};
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
pcm_bspline_t bspline{};
|
||||
#endif
|
||||
|
||||
// from observed celestial to encoder
|
||||
pcm_geom_coeffs_t inverseGeomCoefficients{};
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
pcm_bspline_t inverseBspline{};
|
||||
#endif
|
||||
};
|
||||
|
||||
// constructors
|
||||
|
||||
MccDefaultPCM() = default;
|
||||
MccDefaultPCM(pcm_data_t pdata) : MccDefaultPCM()
|
||||
{
|
||||
_pcmData = std::move(pdata);
|
||||
}
|
||||
|
||||
MccDefaultPCM(MccDefaultPCM&& other) = default;
|
||||
MccDefaultPCM& operator=(MccDefaultPCM&& other) = default;
|
||||
|
||||
MccDefaultPCM(const MccDefaultPCM&) = delete;
|
||||
MccDefaultPCM& operator=(const MccDefaultPCM&) = delete;
|
||||
|
||||
virtual ~MccDefaultPCM() = default;
|
||||
|
||||
// virtual ~MccDefaultPCM() {
|
||||
// // _pcmData.geomCoefficients.DECaxisFlexure = 0.0;
|
||||
// };
|
||||
|
||||
void setPCMData(pcm_data_t pdata)
|
||||
{
|
||||
std::lock_guard lock(*_pcmDataMutex);
|
||||
|
||||
_pcmData = std::move(pdata);
|
||||
|
||||
_cosPhi = std::cos(_pcmData.siteLatitude);
|
||||
_sinPhi = std::sin(_pcmData.siteLatitude);
|
||||
}
|
||||
|
||||
|
||||
pcm_data_t getPCMData() const
|
||||
{
|
||||
std::lock_guard lock(*_pcmDataMutex);
|
||||
|
||||
return _pcmData;
|
||||
}
|
||||
|
||||
void setPCMType(MccDefaultPCMType type)
|
||||
{
|
||||
std::lock_guard lock(*_pcmDataMutex);
|
||||
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
_pcmData.type = type;
|
||||
#else
|
||||
_pcmData.type = MccDefaultPCMType::PCM_TYPE_GEOMETRY;
|
||||
#endif
|
||||
}
|
||||
|
||||
MccDefaultPCMType getPCMType() const
|
||||
{
|
||||
std::lock_guard lock(*_pcmDataMutex);
|
||||
|
||||
return _pcmData.type;
|
||||
}
|
||||
|
||||
// The computed PCM quantities must be interpretated as:
|
||||
// observed_X = encoder_X + res.pcmX
|
||||
// observed_Y = encoder_Y + res.pcmY
|
||||
// so, input hw_coord is assumed to be coordinates pair of mount axis encoder readouts
|
||||
template <typename T = std::nullptr_t>
|
||||
error_t computePCM(mcc_coord_pair_c auto const& hw_coord, mcc_pcm_result_c auto* res, T* obs_skycoord = nullptr)
|
||||
requires(mcc_skypoint_c<T> || std::is_null_pointer_v<T>)
|
||||
{
|
||||
if (res == nullptr) {
|
||||
return MccDefaultPCMErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
std::lock_guard lock(*_pcmDataMutex);
|
||||
|
||||
res->pcmX = 0.0;
|
||||
res->pcmY = 0.0;
|
||||
|
||||
auto ret = _compResult(hw_coord.x(), hw_coord.y(), res, false);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if constexpr (mcc_is_equatorial_mount<MOUNT_TYPE>) { // equatorial (HA and DEC axes)
|
||||
if constexpr (mcc_skypoint_c<T>) {
|
||||
obs_skycoord->from(MccSkyHADEC_OBS{double(hw_coord.x() + res->pcmX), double(hw_coord.y() + res->pcmY),
|
||||
hw_coord.epoch()});
|
||||
}
|
||||
|
||||
} else if constexpr (mcc_is_altaz_mount<MOUNT_TYPE>) {
|
||||
if constexpr (mcc_skypoint_c<T>) {
|
||||
obs_skycoord->from(
|
||||
MccSkyAZZD{double(hw_coord.x() + res->pcmX), double(hw_coord.y() + res->pcmY), hw_coord.epoch()});
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED MOUNT TYPE");
|
||||
}
|
||||
|
||||
|
||||
return MccDefaultPCMErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
//
|
||||
// computed inverse PCM corrections must be interpretated as:
|
||||
// X_encoder = X_observed + res->pcmX
|
||||
// Y_encoder = Y_observed + res->pcmY
|
||||
//
|
||||
template <typename T>
|
||||
error_t computeInversePCM(mcc_skypoint_c auto const& obs_skycoord, mcc_pcm_result_c auto* res, T* hw_pt = nullptr)
|
||||
requires(mcc_coord_pair_c<T> || std::is_null_pointer_v<T>)
|
||||
{
|
||||
error_t ret = MccDefaultPCMErrorCode::ERROR_OK;
|
||||
|
||||
if constexpr (mcc_coord_pair_c<T>) {
|
||||
static_assert(
|
||||
T::pairKind == MccCoordPairKind::COORDS_KIND_GENERIC || T::pairKind == MccCoordPairKind::COORDS_KIND_XY,
|
||||
"Invalid output coordinate pair type!");
|
||||
}
|
||||
|
||||
// to be computed as observed celestial X and Y cordinate according to mount type (HA-DEC or AZ-ZD)
|
||||
double x, y;
|
||||
|
||||
auto getXY = [&, this](auto& cp) {
|
||||
auto err = obs_skycoord.toAtSameEpoch(cp);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccDefaultPCMErrorCode::ERROR_CCTE);
|
||||
}
|
||||
|
||||
x = cp.x();
|
||||
y = cp.y();
|
||||
|
||||
return MccDefaultPCMErrorCode::ERROR_OK;
|
||||
};
|
||||
|
||||
if constexpr (mcc_is_equatorial_mount<MOUNT_TYPE>) {
|
||||
MccSkyHADEC_OBS hadec;
|
||||
ret = getXY(hadec);
|
||||
} else if constexpr (mcc_is_altaz_mount<MOUNT_TYPE>) {
|
||||
MccSkyAZZD azzd;
|
||||
ret = getXY(azzd);
|
||||
}
|
||||
|
||||
std::lock_guard lock(*_pcmDataMutex);
|
||||
|
||||
ret = _compResult(x, y, res, true);
|
||||
if (!ret) {
|
||||
if constexpr (mcc_coord_pair_c<T>) {
|
||||
*hw_pt = MccCoordPair<typename T::x_t, typename T::y_t>{x + res->pcmX, y + res->pcmY};
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
template <typename T>
|
||||
error_t computeInversePCM(mcc_skypoint_c auto const& obs_skycoord,
|
||||
mcc_pcm_result_c auto* result,
|
||||
T* hw_pt = nullptr)
|
||||
requires(mcc_coord_pair_c<T> || std::is_null_pointer_v<T>)
|
||||
{
|
||||
error_t ret = MccDefaultPCMErrorCode::ERROR_OK;
|
||||
|
||||
if constexpr (mcc_coord_pair_c<T>) {
|
||||
static_assert(
|
||||
T::pairKind == MccCoordPairKind::COORDS_KIND_GENERIC || T::pairKind == MccCoordPairKind::COORDS_KIND_XY,
|
||||
"Invalid output coordinate pair type!");
|
||||
}
|
||||
|
||||
double x, y;
|
||||
|
||||
auto comp_func = [&, this](auto& cp) {
|
||||
auto err = obs_skycoord.toAtSameEpoch(cp);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccDefaultPCMErrorCode::ERROR_CCTE);
|
||||
}
|
||||
|
||||
x = cp.x();
|
||||
y = cp.y();
|
||||
|
||||
return computePCM(cp, result);
|
||||
};
|
||||
|
||||
|
||||
// for small corrections only!!!
|
||||
if constexpr (mcc_is_equatorial_mount<MOUNT_TYPE>) {
|
||||
MccSkyHADEC_OBS hadec;
|
||||
ret = comp_func(hadec);
|
||||
} else if constexpr (mcc_is_altaz_mount<MOUNT_TYPE>) {
|
||||
MccSkyAZZD azzd;
|
||||
ret = comp_func(azzd);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if constexpr (mcc_coord_pair_c<T>) {
|
||||
*hw_pt = MccCoordPair<typename T::x_t, typename T::y_t>{x - result->pcmX, y - result->pcmY};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
*/
|
||||
|
||||
private:
|
||||
pcm_data_t _pcmData{};
|
||||
double _cosPhi{}, _sinPhi{};
|
||||
|
||||
std::unique_ptr<std::mutex> _pcmDataMutex{new std::mutex};
|
||||
|
||||
error_t _compResult(double x, double y, mcc_pcm_result_c auto* res, bool inverse)
|
||||
{
|
||||
pcm_geom_coeffs_t* geom_coeffs;
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
pcm_bspline_t* bspline;
|
||||
#endif
|
||||
|
||||
geom_coeffs = inverse ? &_pcmData.inverseGeomCoefficients : &_pcmData.geomCoefficients;
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
bspline = inverse ? &_pcmData.inverseBspline : &_pcmData.bspline;
|
||||
#endif
|
||||
|
||||
if (_pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
|| _pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE
|
||||
#endif
|
||||
) {
|
||||
const auto tanY = std::tan(y);
|
||||
const auto sinX = std::sin(x);
|
||||
const auto cosX = std::cos(x);
|
||||
const auto cosY = std::cos(y);
|
||||
|
||||
|
||||
if (utils::isEqual(cosY, 0.0)) {
|
||||
res->pcmX = _pcmData.geomCoefficients.zeroPointX;
|
||||
} else {
|
||||
res->pcmX = geom_coeffs->zeroPointX + geom_coeffs->collimationErr / cosY +
|
||||
geom_coeffs->nonperpendErr * tanY - geom_coeffs->misalignErr1 * cosX * tanY +
|
||||
geom_coeffs->misalignErr2 * sinX * tanY + geom_coeffs->tubeFlexure * _cosPhi * sinX / cosY -
|
||||
geom_coeffs->DECaxisFlexure * (_cosPhi * cosX + _sinPhi * tanY);
|
||||
}
|
||||
|
||||
res->pcmY = geom_coeffs->zeroPointY + geom_coeffs->misalignErr1 * sinX + geom_coeffs->misalignErr2 * cosX +
|
||||
geom_coeffs->tubeFlexure * (_cosPhi * cosX * std::sin(y) - _sinPhi * cosY);
|
||||
|
||||
if constexpr (mountType == MccMountType::FORK_TYPE) {
|
||||
if (!utils::isEqual(cosX, 0.0)) {
|
||||
res->pcmY += geom_coeffs->forkFlexure / cosX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef USE_BSPLINE_PCM
|
||||
if (_pcmData.type == MccDefaultPCMType::PCM_TYPE_BSPLINE ||
|
||||
_pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE) {
|
||||
double spl_valX, spl_valY;
|
||||
|
||||
int ret = bsplines::fitpack_eval_spl2d(bspline->knotsX, bspline->knotsY, bspline->coeffsX, x, y, spl_valX,
|
||||
bspline->bsplDegreeX, bspline->bsplDegreeY);
|
||||
|
||||
if (ret) {
|
||||
res->pcmX = std::numeric_limits<double>::quiet_NaN();
|
||||
res->pcmY = std::numeric_limits<double>::quiet_NaN();
|
||||
return MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV;
|
||||
}
|
||||
|
||||
|
||||
ret = bsplines::fitpack_eval_spl2d(bspline->knotsX, bspline->knotsY, bspline->coeffsY, x, y, spl_valY,
|
||||
bspline->bsplDegreeX, bspline->bsplDegreeY);
|
||||
|
||||
if (ret) {
|
||||
res->pcmX = std::numeric_limits<double>::quiet_NaN();
|
||||
res->pcmY = std::numeric_limits<double>::quiet_NaN();
|
||||
return MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV;
|
||||
}
|
||||
|
||||
|
||||
res->pcmX += spl_valX;
|
||||
res->pcmY += spl_valY;
|
||||
}
|
||||
#endif
|
||||
|
||||
return MccDefaultPCMErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef MccDefaultPCM<MccMountType::ALTAZ_TYPE> MccMountDefaultAltAzPec;
|
||||
typedef MccDefaultPCM<MccMountType::FORK_TYPE> MccMountDefaultForkPec;
|
||||
|
||||
static_assert(mcc_pcm_c<MccMountDefaultForkPec>, "");
|
||||
static_assert(std::movable<MccMountDefaultForkPec>);
|
||||
|
||||
|
||||
} // namespace mcc::impl
|
||||
455
include/mcc/mcc_pzone.h
Normal file
455
include/mcc/mcc_pzone.h
Normal file
@@ -0,0 +1,455 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF SOME SIMPLE PROHIBITED ZONES *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_constants.h"
|
||||
#include "mcc_coordinate.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
|
||||
enum class MccPZoneErrorCode : int { ERROR_OK, ERROR_NULLPTR, ERROR_COORD_TRANSFROM, ERROR_PCM_COMP };
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccPZoneErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
// error category
|
||||
struct MccPZoneCategory : public std::error_category {
|
||||
MccPZoneCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "ALTITUDE-LIMIT-PZ";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccPZoneErrorCode err = static_cast<MccPZoneErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccPZoneErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccPZoneErrorCode::ERROR_NULLPTR:
|
||||
return "input argument os nullptr";
|
||||
case MccPZoneErrorCode::ERROR_COORD_TRANSFROM:
|
||||
return "coordinate transformation error";
|
||||
case MccPZoneErrorCode::ERROR_PCM_COMP:
|
||||
return "PCM computation error";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccPZoneCategory& get()
|
||||
{
|
||||
static const MccPZoneCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccPZoneErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccPZoneCategory::get());
|
||||
}
|
||||
|
||||
|
||||
enum class MccAltLimitKind { MIN_ALT_LIMIT, MAX_ALT_LIMIT };
|
||||
|
||||
template <MccAltLimitKind KIND = MccAltLimitKind::MIN_ALT_LIMIT>
|
||||
class MccAltLimitPZ : public mcc_pzone_interface_t<std::error_code>
|
||||
{
|
||||
public:
|
||||
static constexpr MccProhibitedZonePolicy pzPolicy = MccProhibitedZonePolicy::PZ_POLICY_STOP;
|
||||
|
||||
typedef std::error_code error_t;
|
||||
|
||||
MccAltLimitPZ(mcc_angle_c auto const& alt_limit, mcc_angle_c auto const& latitude)
|
||||
: _altLimit(MccAngle(alt_limit).normalize<MccAngle::NORM_KIND_90_90>()),
|
||||
_cosALim(cos(_altLimit)),
|
||||
_sinAlim(sin(_altLimit)),
|
||||
_cosLat(cos(latitude)),
|
||||
_sinLat(sin(latitude)),
|
||||
_absLat(abs(latitude)),
|
||||
_latLim(MCC_TWO_PI - _altLimit)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
MccAltLimitPZ(MccAltLimitPZ&&) = default;
|
||||
MccAltLimitPZ(const MccAltLimitPZ&) = default;
|
||||
|
||||
static constexpr std::string_view pzoneName = KIND == MccAltLimitKind::MIN_ALT_LIMIT ? "MINALT-ZONE"
|
||||
: KIND == MccAltLimitKind::MAX_ALT_LIMIT ? "MAXALT-ZONE"
|
||||
: "ALTLIMIT-UNKNOWN";
|
||||
|
||||
|
||||
error_t inPZone(mcc_skypoint_c auto const& coords, bool* result)
|
||||
{
|
||||
if (result == nullptr) {
|
||||
return MccPZoneErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
error_t ret = MccPZoneErrorCode::ERROR_OK;
|
||||
|
||||
|
||||
MccSkyAZALT azalt;
|
||||
auto ccte_err = coords.to(azalt);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccPZoneErrorCode::ERROR_COORD_TRANSFROM);
|
||||
}
|
||||
|
||||
|
||||
if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) {
|
||||
*result = azalt.y() <= _altLimit;
|
||||
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) {
|
||||
*result = azalt.y() >= _altLimit;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
error_t timeToPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
|
||||
{
|
||||
using res_t = std::remove_cvref_t<decltype(*res_time)>;
|
||||
|
||||
if (res_time == nullptr) {
|
||||
return MccPZoneErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
error_t ret = MccPZoneErrorCode::ERROR_OK;
|
||||
|
||||
bool inzone;
|
||||
ret = inPZone(coords, &inzone);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (inzone) {
|
||||
*res_time = res_t{0};
|
||||
return ret;
|
||||
}
|
||||
|
||||
MccSkyHADEC_OBS hadec;
|
||||
|
||||
auto ccte_err = coords.to(hadec);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccPZoneErrorCode::ERROR_COORD_TRANSFROM);
|
||||
}
|
||||
|
||||
|
||||
if (!doesObjectReachZone(hadec.y())) {
|
||||
*res_time = MCC_INFINITE_DURATION_V<res_t>;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if constexpr (KIND ==
|
||||
MccAltLimitKind::MIN_ALT_LIMIT) { // the closest time point is one after upper culmination
|
||||
compute(hadec.x(), hadec.y(), false, res_time);
|
||||
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one before upper
|
||||
// culmination
|
||||
compute(hadec.x(), hadec.y(), true, res_time);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
error_t timeFromPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
|
||||
{
|
||||
using res_t = std::remove_cvref_t<decltype(*res_time)>;
|
||||
|
||||
if (res_time == nullptr) {
|
||||
return MccPZoneErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
error_t ret = MccPZoneErrorCode::ERROR_OK;
|
||||
|
||||
bool inzone;
|
||||
ret = inPZone(coords, &inzone);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!inzone) {
|
||||
*res_time = res_t{0};
|
||||
return ret;
|
||||
}
|
||||
|
||||
MccSkyHADEC_OBS hadec;
|
||||
|
||||
auto ccte_err = coords.to(hadec);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccPZoneErrorCode::ERROR_COORD_TRANSFROM);
|
||||
}
|
||||
|
||||
if (!doesObjectExitFromZone(hadec.y())) {
|
||||
*res_time = MCC_INFINITE_DURATION_V<res_t>;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!doesObjectReachZone(hadec.y())) {
|
||||
*res_time = res_t{0};
|
||||
return ret;
|
||||
}
|
||||
|
||||
if constexpr (KIND ==
|
||||
MccAltLimitKind::MIN_ALT_LIMIT) { // the closest time point is one before upper culmination
|
||||
compute(hadec.x(), hadec.y(), true, res_time);
|
||||
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one after upper
|
||||
// culmination
|
||||
compute(hadec.x(), hadec.y(), false, res_time);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
double _altLimit, _cosALim, _sinAlim;
|
||||
double _cosLat, _sinLat, _absLat, _latLim;
|
||||
|
||||
bool doesObjectReachZone(const double& dec_app)
|
||||
{
|
||||
// check for limit conditions
|
||||
auto dd = std::abs(dec_app);
|
||||
|
||||
if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) {
|
||||
dd += _altLimit;
|
||||
|
||||
if (dd > _latLim) { // never fall below altitude limit
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) {
|
||||
auto z = std::numbers::pi / 2.0 - _altLimit;
|
||||
if ((dd < (_absLat - z)) || (dd > (_absLat + z))) { // never rise above altitude limit
|
||||
return false;
|
||||
}
|
||||
// if ((dd < (_absLat - _altLimit)) || (dd > (_absLat + _altLimit))) { // never rise above altitude limit
|
||||
// return false;
|
||||
// }
|
||||
} else {
|
||||
static_assert(false, "UNKNOWN ALTITUDE LIMIT TYPE!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool doesObjectExitFromZone(const double& dec_app)
|
||||
{
|
||||
// check for limit conditions
|
||||
auto dd = std::abs(dec_app);
|
||||
|
||||
if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) {
|
||||
dd -= _altLimit;
|
||||
|
||||
if (-dd <= -_latLim) { // always below altitude limit
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) {
|
||||
if ((dd >= (_absLat - _altLimit)) || (dd <= (_absLat + _altLimit))) { // always above altitude limit
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "UNKNOWN ALTITUDE LIMIT TYPE!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void compute(const double& ha_app,
|
||||
const double& dec_app,
|
||||
bool before_upper_culm,
|
||||
traits::mcc_time_duration_c auto* result)
|
||||
{
|
||||
using res_t = std::remove_cvref_t<decltype(*result)>;
|
||||
using period_t = typename res_t::period;
|
||||
|
||||
double cos_ha = (_sinAlim - std::sin(dec_app) * _sinLat) / std::cos(dec_app) / _cosLat;
|
||||
|
||||
if (cos_ha > 1.0) { // should not be!
|
||||
*result = MCC_INFINITE_DURATION_V<res_t>;
|
||||
return;
|
||||
}
|
||||
|
||||
double ha;
|
||||
// WARNING: what about south hemisphere?!!!
|
||||
if (before_upper_culm) {
|
||||
ha = -std::acos(cos_ha); // HA before upper culmination
|
||||
} else {
|
||||
ha = std::acos(cos_ha); // HA after upper culmination!!
|
||||
}
|
||||
|
||||
auto time_ang = ha - ha_app; // in sideral time scale
|
||||
|
||||
if (time_ang < 0.0) { // next day
|
||||
time_ang += MCC_TWO_PI;
|
||||
}
|
||||
|
||||
time_ang /= MCC_SIDERAL_TO_UT1_RATIO; // to UT1 time scale
|
||||
|
||||
std::chrono::nanoseconds ns{
|
||||
static_cast<std::chrono::nanoseconds::rep>(time_ang * 43200.0 / std::numbers::pi * 1.0E9)};
|
||||
|
||||
period_t rat;
|
||||
*result = res_t{static_cast<typename res_t::rep>(time_ang * 43200.0 / std::numbers::pi * rat.den / rat.num)};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* co-longitude axis (HA or AZ) limit switch prohibited zone */
|
||||
|
||||
template <MccCoordKind AXIS_KIND>
|
||||
class MccAxisLimitSwitchPZ : public mcc_pzone_interface_t<std::error_code>
|
||||
{
|
||||
public:
|
||||
static_assert(AXIS_KIND == MccCoordKind::COORDS_KIND_AZ || AXIS_KIND == MccCoordKind::COORDS_KIND_HA_OBS,
|
||||
"UNSUPPORTED AXIS TYPE!");
|
||||
|
||||
typedef std::error_code error_t;
|
||||
|
||||
static constexpr MccCoordKind axisKind = AXIS_KIND;
|
||||
|
||||
static constexpr MccProhibitedZonePolicy pzPolicy = MccProhibitedZonePolicy::PZ_POLICY_FLIP;
|
||||
|
||||
//
|
||||
// min_limit_val and max_limit_val are hardware encoder angles in radians!
|
||||
//
|
||||
MccAxisLimitSwitchPZ(mcc_angle_c auto const& min_limit_val,
|
||||
mcc_angle_c auto const& max_limit_val,
|
||||
mcc_pcm_c auto* pcm)
|
||||
: _minLimit(min_limit_val), _maxLimit(max_limit_val)
|
||||
{
|
||||
_correctForPCM = [pcm](MccSkyPoint const& skypt, MccGenXY* hw_coords) {
|
||||
struct pcm_res_t {
|
||||
double pcmX, pcmY;
|
||||
};
|
||||
|
||||
pcm_res_t res;
|
||||
|
||||
auto err = pcm->computeInversePCM(skypt, &res, hw_coords);
|
||||
|
||||
return mcc_deduced_err(err, MccPZoneErrorCode::ERROR_PCM_COMP);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static constexpr std::string_view pzoneName = axisKind == MccCoordKind::COORDS_KIND_AZ ? "AZ_AXIS-LIMITSWITCH_ZONE"
|
||||
: axisKind == MccCoordKind::COORDS_KIND_HA_OBS
|
||||
? "HA_AXIS-LIMITSWITCH_ZONE"
|
||||
: "UKNOWN";
|
||||
|
||||
|
||||
|
||||
error_t inPZone(mcc_skypoint_c auto const& coords, bool* result)
|
||||
{
|
||||
if (result == nullptr) {
|
||||
return MccPZoneErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
MccGenXY xy;
|
||||
|
||||
if (coords.pairKind() != MccCoordPairKind::COORDS_KIND_XY) {
|
||||
auto err = _correctForPCM(coords, &xy);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else { // 'coords' is interpretated as hardware (encoder readout) coordinates
|
||||
coords.to(xy);
|
||||
}
|
||||
|
||||
double x = xy.x();
|
||||
*result = (x > _maxLimit) || (x < _minLimit);
|
||||
|
||||
return MccPZoneErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
// time to reach maximal limit
|
||||
error_t timeToPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
|
||||
{
|
||||
return _computeTime(coords, res_time, false);
|
||||
}
|
||||
|
||||
// time to reach minimal limit
|
||||
error_t timeFromPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
|
||||
{
|
||||
return _computeTime(coords, res_time, true);
|
||||
}
|
||||
|
||||
protected:
|
||||
double _minLimit, _maxLimit;
|
||||
|
||||
std::function<error_t(MccSkyPoint const&, MccGenXY*)> _correctForPCM{};
|
||||
|
||||
error_t _computeTime(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time, bool from_time)
|
||||
{
|
||||
using res_t = std::remove_cvref_t<decltype(*res_time)>;
|
||||
using period_t = typename res_t::period;
|
||||
|
||||
double time_ang;
|
||||
|
||||
if (res_time == nullptr) {
|
||||
return MccPZoneErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
|
||||
MccGenXY xy;
|
||||
|
||||
if (coords.pairKind() != MccCoordPairKind::COORDS_KIND_XY) {
|
||||
auto err = _correctForPCM(coords, &xy);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else { // 'coords' is interpretated as hardware (encoder readout) coordinates
|
||||
coords.to(xy);
|
||||
}
|
||||
|
||||
double x = xy.x();
|
||||
|
||||
if constexpr (AXIS_KIND == MccCoordKind::COORDS_KIND_HA_OBS) {
|
||||
if (from_time) { // timeFromPZone
|
||||
time_ang = (_minLimit - x) / MCC_SIDERAL_TO_UT1_RATIO; // to UT1 scale
|
||||
} else { // timeToPZone
|
||||
time_ang = (_maxLimit - x) / MCC_SIDERAL_TO_UT1_RATIO; // to UT1 scale
|
||||
}
|
||||
} else if constexpr (AXIS_KIND == MccCoordKind::COORDS_KIND_AZ) {
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds ns{
|
||||
static_cast<std::chrono::nanoseconds::rep>(time_ang * 43200.0 / std::numbers::pi * 1.0E9)};
|
||||
|
||||
period_t rat;
|
||||
*res_time = res_t{static_cast<typename res_t::rep>(time_ang * 43200.0 / std::numbers::pi * rat.den / rat.num)};
|
||||
|
||||
return MccPZoneErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace mcc::impl
|
||||
231
include/mcc/mcc_pzone_container.h
Normal file
231
include/mcc/mcc_pzone_container.h
Normal file
@@ -0,0 +1,231 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF PROHIBITED ZONES CONTAINER *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_coordinate.h"
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccPZoneContainerErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_NULLPTR,
|
||||
ERROR_INVALID_SIZE,
|
||||
ERROR_INZONE_FUNC,
|
||||
ERROR_TIMETO_FUNC,
|
||||
ERROR_TIMEFROM_FUNC,
|
||||
ERROR_INTERSECT_FUNC
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccPZoneContainerErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
// error category
|
||||
struct MccPZoneContainerCategory : public std::error_category {
|
||||
MccPZoneContainerCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "ALTITUDE-LIMIT-PZ";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccPZoneContainerErrorCode err = static_cast<MccPZoneContainerErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccPZoneContainerErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccPZoneContainerErrorCode::ERROR_NULLPTR:
|
||||
return "nullptr argument";
|
||||
case MccPZoneContainerErrorCode::ERROR_INVALID_SIZE:
|
||||
return "invalid range size of input argument";
|
||||
case MccPZoneContainerErrorCode::ERROR_INZONE_FUNC:
|
||||
return "inPZone method error";
|
||||
case MccPZoneContainerErrorCode::ERROR_TIMETO_FUNC:
|
||||
return "timeToPZone method error";
|
||||
case MccPZoneContainerErrorCode::ERROR_TIMEFROM_FUNC:
|
||||
return "timeFromPZone method error";
|
||||
case MccPZoneContainerErrorCode::ERROR_INTERSECT_FUNC:
|
||||
return "intersectPZone method error";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccPZoneContainerCategory& get()
|
||||
{
|
||||
static const MccPZoneContainerCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccPZoneContainerErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccPZoneContainerCategory::get());
|
||||
}
|
||||
|
||||
|
||||
// template <traits::mcc_time_duration_c DurT>
|
||||
class MccPZoneContainer : public mcc_pzone_container_interface_t<std::error_code>
|
||||
{
|
||||
public:
|
||||
typedef std::error_code error_t;
|
||||
|
||||
MccPZoneContainer() = default;
|
||||
|
||||
MccPZoneContainer(MccPZoneContainer&&) = default;
|
||||
MccPZoneContainer(const MccPZoneContainer&) = default;
|
||||
|
||||
MccPZoneContainer& operator=(MccPZoneContainer&&) = default;
|
||||
MccPZoneContainer& operator=(const MccPZoneContainer&) = default;
|
||||
|
||||
|
||||
virtual ~MccPZoneContainer() = default;
|
||||
|
||||
|
||||
size_t addPZone(mcc_pzone_c auto zone)
|
||||
{
|
||||
auto sptr = std::make_shared<decltype(zone)>(std::move(zone));
|
||||
|
||||
_inZoneFunc.emplace_back([sptr](MccSkyPoint const& pt, bool* res) -> error_t {
|
||||
auto err = sptr->inPZone(pt, res);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccPZoneContainerErrorCode::ERROR_INZONE_FUNC);
|
||||
}
|
||||
|
||||
return MccPZoneContainerErrorCode::ERROR_OK;
|
||||
});
|
||||
|
||||
|
||||
_timeToZoneFunc.emplace_back([sptr](MccSkyPoint const& pt, duration_t* res) -> error_t {
|
||||
auto err = sptr->timeToPZone(pt, res);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccPZoneContainerErrorCode::ERROR_TIMETO_FUNC);
|
||||
}
|
||||
|
||||
return MccPZoneContainerErrorCode::ERROR_OK;
|
||||
});
|
||||
|
||||
|
||||
_timeFromZoneFunc.emplace_back([sptr](MccSkyPoint const& pt, duration_t* res) -> error_t {
|
||||
auto err = sptr->timeFromPZone(pt, res);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccPZoneContainerErrorCode::ERROR_TIMEFROM_FUNC);
|
||||
}
|
||||
|
||||
return MccPZoneContainerErrorCode::ERROR_OK;
|
||||
});
|
||||
|
||||
return _inZoneFunc.size();
|
||||
}
|
||||
|
||||
void clearZones()
|
||||
{
|
||||
_inZoneFunc.clear();
|
||||
_timeToZoneFunc.clear();
|
||||
_timeFromZoneFunc.clear();
|
||||
}
|
||||
|
||||
|
||||
error_t inPZone(mcc_skypoint_c auto const& coords, bool* at_least_one, std::ranges::output_range<bool> auto* result)
|
||||
{
|
||||
auto err = forEach(_inZoneFunc, coords, result);
|
||||
if (!err) {
|
||||
*at_least_one = false;
|
||||
for (auto const& r : *result) {
|
||||
if (r) {
|
||||
*at_least_one = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
error_t timeToPZone(mcc_skypoint_c auto const& coords, std::ranges::output_range<DT> auto* res_time)
|
||||
{
|
||||
return forEach(_timeToZoneFunc, coords, res_time);
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
error_t timeFromPZone(mcc_skypoint_c auto const& coords, std::ranges::output_range<DT> auto* res_time)
|
||||
{
|
||||
return forEach(_timeFromZoneFunc, coords, res_time);
|
||||
}
|
||||
|
||||
protected:
|
||||
typedef std::chrono::nanoseconds duration_t;
|
||||
|
||||
std::vector<std::function<error_t(MccSkyPoint const& pt, bool*)>> _inZoneFunc;
|
||||
std::vector<std::function<error_t(MccSkyPoint const& pt, duration_t*)>> _timeToZoneFunc;
|
||||
std::vector<std::function<error_t(MccSkyPoint const& pt, duration_t*)>> _timeFromZoneFunc;
|
||||
|
||||
error_t forEach(auto& func_cont, MccSkyPoint const& pt, auto* result)
|
||||
{
|
||||
if (result == nullptr) {
|
||||
return MccPZoneContainerErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
using res_t = std::decay_t<decltype(*result)>;
|
||||
using res_elem_t = std::ranges::range_value_t<res_t>;
|
||||
|
||||
error_t err;
|
||||
|
||||
res_elem_t res_elem;
|
||||
|
||||
size_t res_sz = std::ranges::size(*result);
|
||||
auto it = result->begin();
|
||||
|
||||
for (auto& func : func_cont) {
|
||||
err = func(pt, &res_elem);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (it == result->end()) {
|
||||
std::back_inserter(*result) = res_elem;
|
||||
it = result->end();
|
||||
} else {
|
||||
*it = std::move(res_elem);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return MccPZoneContainerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace mcc::impl
|
||||
390
include/mcc/mcc_serialization_common.h
Normal file
390
include/mcc/mcc_serialization_common.h
Normal file
@@ -0,0 +1,390 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* COMMON DEFINITIONS FOR DATA SERIALIZATION/DESERIALIZATION *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_error.h"
|
||||
#include "mcc_traits.h"
|
||||
|
||||
namespace mcc
|
||||
{
|
||||
|
||||
|
||||
/* celestial angle serialization */
|
||||
|
||||
enum class MccSerializedAngleFormat {
|
||||
MCC_SERIALIZED_FORMAT_DEGREES, // degrees as floating-point number
|
||||
MCC_SERIALIZED_FORMAT_SXGM_HOURS, // sexagesimal representation: hours:mins:secs
|
||||
MCC_SERIALIZED_FORMAT_SXGM_DEGS, // sexagesimal representation: degs:arcmins:arcsecs
|
||||
MCC_SERIALIZED_FORMAT_UNKNOWN
|
||||
};
|
||||
|
||||
static constexpr std::string_view MCC_SERIALIZED_ANG_FORMAT_DEGREES_STR = "SRANG-FORMAT-DEGREES";
|
||||
static constexpr std::string_view MCC_SERIALIZED_ANG_FORMAT_SXGM_HOURS_STR = "SRANG-FORMAT-SXGM_HOURDEG";
|
||||
static constexpr std::string_view MCC_SERIALIZED_ANG_FORMAT_SXGM_DEGS_STR = "SRANG-FORMAT-SXGM_DEGDEG";
|
||||
|
||||
|
||||
static constexpr std::string_view MccSerializedAngleFormatToStr(MccSerializedAngleFormat fmt)
|
||||
{
|
||||
return fmt == MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES ? MCC_SERIALIZED_ANG_FORMAT_DEGREES_STR
|
||||
: fmt == MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS
|
||||
? MCC_SERIALIZED_ANG_FORMAT_SXGM_HOURS_STR
|
||||
: fmt == MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS ? MCC_SERIALIZED_ANG_FORMAT_SXGM_DEGS_STR
|
||||
: MCC_SERIALIZED_ANG_FORMAT_DEGREES_STR;
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
static constexpr MccSerializedAngleFormat MccSerializedAngleFormatStrToValue(R&& str)
|
||||
{
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) {
|
||||
return MccSerializedAngleFormatStrToValue(std::string_view{str});
|
||||
}
|
||||
|
||||
const auto hash = mcc::utils::FNV1aHash(std::forward<R>(str));
|
||||
|
||||
return hash == mcc::utils::FNV1aHash(MCC_SERIALIZED_ANG_FORMAT_DEGREES_STR)
|
||||
? MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES
|
||||
: hash == mcc::utils::FNV1aHash(MCC_SERIALIZED_ANG_FORMAT_SXGM_HOURS_STR)
|
||||
? MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS
|
||||
: hash == mcc::utils::FNV1aHash(MCC_SERIALIZED_ANG_FORMAT_SXGM_DEGS_STR)
|
||||
? MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS
|
||||
: MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
/* coordinates pair serialization */
|
||||
|
||||
enum class MccSerializedCoordPairFormat {
|
||||
MCC_SERIALIZED_FORMAT_DEGREES, // both angles are in degrees as floating-point number
|
||||
MCC_SERIALIZED_FORMAT_SXGM_HOURDEG, // X is in hour (if RA or HA) and Y is in degree sexagesimal representation
|
||||
MCC_SERIALIZED_FORMAT_SXGM_DEGDEG, // both angles are in sexagesimal degrees
|
||||
MCC_SERIALIZED_FORMAT_UNKNOWN
|
||||
};
|
||||
|
||||
static constexpr std::string_view MCC_SERIALIZED_CP_FORMAT_DEGREES_STR = "SRCP-FORMAT-DEGREES";
|
||||
static constexpr std::string_view MCC_SERIALIZED_CP_FORMAT_SXGM_HOURDEG_STR = "SRCP-FORMAT-SXGM_HOURDEG";
|
||||
static constexpr std::string_view MCC_SERIALIZED_CP_FORMAT_SXGM_DEGDEG_STR = "SRCP-FORMAT-SXGM_DEGDEG";
|
||||
|
||||
|
||||
static constexpr std::string_view MccSerializedCoordPairFormatToStr(MccSerializedCoordPairFormat fmt)
|
||||
{
|
||||
return fmt == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_DEGREES ? MCC_SERIALIZED_CP_FORMAT_DEGREES_STR
|
||||
: fmt == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG
|
||||
? MCC_SERIALIZED_CP_FORMAT_SXGM_HOURDEG_STR
|
||||
: fmt == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGDEG
|
||||
? MCC_SERIALIZED_CP_FORMAT_SXGM_DEGDEG_STR
|
||||
: MCC_SERIALIZED_CP_FORMAT_DEGREES_STR;
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
static constexpr MccSerializedCoordPairFormat MccSerializedCoordPairFormatStrToValue(R&& str)
|
||||
{
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) {
|
||||
return MccSerializedCoordPairFormatStrToValue(std::string_view{str});
|
||||
}
|
||||
|
||||
const auto hash = mcc::utils::FNV1aHash(std::forward<R>(str));
|
||||
|
||||
return hash == mcc::utils::FNV1aHash(MCC_SERIALIZED_CP_FORMAT_DEGREES_STR)
|
||||
? MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_DEGREES
|
||||
: hash == mcc::utils::FNV1aHash(MCC_SERIALIZED_CP_FORMAT_SXGM_HOURDEG_STR)
|
||||
? MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG
|
||||
: hash == mcc::utils::FNV1aHash(MCC_SERIALIZED_CP_FORMAT_SXGM_DEGDEG_STR)
|
||||
? MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGDEG
|
||||
: MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
/* precision of representation of serialized celestial angle/coordinates pair */
|
||||
|
||||
struct MccSerializedAngleFormatPrec {
|
||||
uint8_t hour_prec = 2; // number of decimal places in hour seconds (sexagesimal format)
|
||||
uint8_t deg_prec = 1; // number of decimal places in arcseconds (sexagesimal format)
|
||||
// slightly better than 0.1 arcsecond precision
|
||||
uint8_t decimals = 6; // number of decimal places in degrees (floating-point number format)
|
||||
// if 0, then number of decimal places is according to formating rules of 'double' type
|
||||
};
|
||||
|
||||
enum class MccTimePointFormat {
|
||||
MCC_TIMEPOINT_FORMAT_DATE, // UTC date
|
||||
MCC_TIMEPOINT_FORMAT_MJD, // MJD
|
||||
MCC_TIMEPOINT_FORMAT_JD, // JD
|
||||
MCC_TIMEPOINT_FORMAT_JEPOCH, // Julian epoch
|
||||
MCC_TIMEPOINT_FORMAT_UNKNOWN
|
||||
};
|
||||
|
||||
static constexpr std::string_view MCC_TIMEPOINT_FORMAT_DATE_STR = "TP-FORMAT-DATE";
|
||||
static constexpr std::string_view MCC_TIMEPOINT_FORMAT_MJD_STR = "TP-FORMAT-MJD";
|
||||
static constexpr std::string_view MCC_TIMEPOINT_FORMAT_JD_STR = "TP-FORMAT-JD";
|
||||
static constexpr std::string_view MCC_TIMEPOINT_FORMAT_JEPOCH_STR = "TP-FORMAT-JEPOCH";
|
||||
|
||||
static constexpr std::string_view MccTimePointFormatToStr(MccTimePointFormat fmt)
|
||||
{
|
||||
return fmt == MccTimePointFormat::MCC_TIMEPOINT_FORMAT_DATE ? MCC_TIMEPOINT_FORMAT_DATE_STR
|
||||
: fmt == MccTimePointFormat::MCC_TIMEPOINT_FORMAT_MJD ? MCC_TIMEPOINT_FORMAT_MJD_STR
|
||||
: fmt == MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JD ? MCC_TIMEPOINT_FORMAT_JD_STR
|
||||
: fmt == MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JEPOCH ? MCC_TIMEPOINT_FORMAT_JEPOCH_STR
|
||||
: MCC_TIMEPOINT_FORMAT_MJD_STR;
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
static constexpr MccTimePointFormat MccTimePointFormatStrToValue(R&& str)
|
||||
{
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) {
|
||||
return MccTimePointFormatStrToValue(std::string_view{str});
|
||||
}
|
||||
|
||||
const auto hash = mcc::utils::FNV1aHash(std::forward<R>(str));
|
||||
|
||||
return hash == mcc::utils::FNV1aHash(MCC_TIMEPOINT_FORMAT_DATE_STR) ? MccTimePointFormat::MCC_TIMEPOINT_FORMAT_DATE
|
||||
: hash == mcc::utils::FNV1aHash(MCC_TIMEPOINT_FORMAT_MJD_STR) ? MccTimePointFormat::MCC_TIMEPOINT_FORMAT_MJD
|
||||
: hash == mcc::utils::FNV1aHash(MCC_TIMEPOINT_FORMAT_JD_STR) ? MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JD
|
||||
: hash == mcc::utils::FNV1aHash(MCC_TIMEPOINT_FORMAT_JEPOCH_STR)
|
||||
? MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JEPOCH
|
||||
: MccTimePointFormat::MCC_TIMEPOINT_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
/* SERIALIZATION/DESERIALIZATION PROCESS TUNING PARAMETERS */
|
||||
|
||||
// delimiter between items of serializing values sequence
|
||||
static constexpr std::string_view MCC_SERIALIZING_DEFAULT_SEQ_DELIMITER{";"};
|
||||
|
||||
// delimiter between items of aggregative (multi-element) serializing value
|
||||
static constexpr std::string_view MCC_SERIALIZING_DEFAULT_ELEM_DELIMITER{","};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_serialization_params_c = std::copyable<T> && requires(T t) {
|
||||
requires traits::mcc_output_char_range<decltype(t.seq_delim)>;
|
||||
requires traits::mcc_output_char_range<decltype(t.elem_delim)>;
|
||||
|
||||
requires std::same_as<decltype(t.angle_format), MccSerializedAngleFormat>;
|
||||
requires std::same_as<decltype(t.angle_prec), MccSerializedAngleFormatPrec>;
|
||||
requires std::same_as<decltype(t.coordpair_format), MccSerializedCoordPairFormat>;
|
||||
requires std::same_as<decltype(t.timepoint_format), MccTimePointFormat>;
|
||||
|
||||
// a format string for mcc_systime_c types (std;:chrono::sys_time)
|
||||
requires std::same_as<decltype(t.systime_format), std::string_view>;
|
||||
|
||||
// if true - normalize angle in sexagesimal format (to control rounding)
|
||||
// (to avoid something like "24:00:00.0" for sexagesimal 'hours:minutes:seconds' format)
|
||||
requires std::convertible_to<decltype(t.norm_sxgm), bool>;
|
||||
|
||||
// if true - interpretate serialized angle in sexagesimal format as 'hours:minutes:seconds'
|
||||
// otherwise as 'degrees:arcmins:arcsecs'
|
||||
requires std::convertible_to<decltype(t.sxgm_hms), bool>;
|
||||
};
|
||||
|
||||
|
||||
/* SERIALIZER/DESERIALIZER CONCEPTS */
|
||||
|
||||
template <mcc_error_c RetT>
|
||||
struct mcc_serializer_interface_t {
|
||||
virtual ~mcc_serializer_interface_t() = default;
|
||||
|
||||
typedef RetT error_t;
|
||||
|
||||
template <std::derived_from<mcc_serializer_interface_t> SelfT, traits::mcc_output_char_range R, typename ValueT>
|
||||
RetT operator()(this SelfT&& self, R& output, ValueT const& value)
|
||||
{
|
||||
return std::forward<SelfT>(self)(output, value);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_serializer_interface_t> SelfT, traits::mcc_output_char_range R, typename ValueT>
|
||||
RetT operator()(this SelfT&& self, R& output, ValueT const& value, mcc_serialization_params_c auto const& params)
|
||||
{
|
||||
return std::forward<SelfT>(self)(output, value, params);
|
||||
}
|
||||
|
||||
protected:
|
||||
mcc_serializer_interface_t() = default;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_serializer_c =
|
||||
std::derived_from<T, mcc_serializer_interface_t<typename T::error_t>> && requires(T t, const T t_const) {
|
||||
// static const variable with name of the serializer
|
||||
requires std::formattable<decltype(T::serializerName), char> && std::is_const_v<decltype(T::serializerName)>;
|
||||
|
||||
// // must define a type "params_t"
|
||||
// requires mcc_serialization_params_c<typename T::params_t>;
|
||||
|
||||
// { t.setParams(std::declval<typename T::params_t const&>()) };
|
||||
|
||||
// { t_const.getParams() } -> std::same_as<typename T::params_t>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <mcc_error_c RetT>
|
||||
struct mcc_deserializer_interface_t {
|
||||
virtual ~mcc_deserializer_interface_t() = default;
|
||||
|
||||
typedef RetT error_t;
|
||||
|
||||
template <std::derived_from<mcc_deserializer_interface_t> SelfT, traits::mcc_input_char_range R, typename ValueT>
|
||||
RetT operator()(this SelfT&& self, R const& input, ValueT& value)
|
||||
{
|
||||
return std::forward<SelfT>(self)(input, value);
|
||||
}
|
||||
|
||||
template <std::derived_from<mcc_deserializer_interface_t> SelfT, traits::mcc_input_char_range R, typename ValueT>
|
||||
RetT operator()(this SelfT&& self, R const& input, ValueT& value, mcc_serialization_params_c auto& params)
|
||||
{
|
||||
return std::forward<SelfT>(self)(input, value, params);
|
||||
}
|
||||
|
||||
protected:
|
||||
mcc_deserializer_interface_t() = default;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_deserializer_c =
|
||||
std::derived_from<T, mcc_deserializer_interface_t<typename T::error_t>> && requires(T t, const T t_const) {
|
||||
// static const variable with name of the deserializer
|
||||
requires std::formattable<decltype(T::deserializerName), char> &&
|
||||
std::is_const_v<decltype(T::deserializerName)>;
|
||||
|
||||
// // must define a type "params_t"
|
||||
// requires mcc_serialization_params_c<typename T::params_t>;
|
||||
|
||||
// { t.setParams(std::declval<typename T::params_t const&>()) };
|
||||
|
||||
// { t_const.getParams() } -> std::same_as<typename T::params_t>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* BASE CLASS IMPLEMENTATION FOR SERIALIZER/DESERIALIZER */
|
||||
|
||||
namespace impl
|
||||
{
|
||||
|
||||
// default definition of serialization/deserialization process parameters structure
|
||||
struct mcc_serialization_params_t {
|
||||
std::string seq_delim{MCC_SERIALIZING_DEFAULT_SEQ_DELIMITER};
|
||||
std::string elem_delim{MCC_SERIALIZING_DEFAULT_ELEM_DELIMITER};
|
||||
|
||||
MccSerializedAngleFormat angle_format{MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES};
|
||||
|
||||
MccSerializedAngleFormatPrec angle_prec{MccSerializedAngleFormatPrec{.hour_prec = 2, .deg_prec = 1, .decimals = 6}};
|
||||
|
||||
MccSerializedCoordPairFormat coordpair_format{MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG};
|
||||
|
||||
MccTimePointFormat timepoint_format{MccTimePointFormat::MCC_TIMEPOINT_FORMAT_DATE};
|
||||
|
||||
std::string_view systime_format{"{:%FT%T}"};
|
||||
|
||||
bool norm_sxgm{false};
|
||||
|
||||
bool sxgm_hms{false};
|
||||
};
|
||||
|
||||
|
||||
static_assert(mcc_serialization_params_c<mcc_serialization_params_t>, "!!!");
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
template <mcc_serialization_params_c ParamsT>
|
||||
struct _params_manipulator_t {
|
||||
virtual ~_params_manipulator_t() = default;
|
||||
|
||||
typedef ParamsT params_t;
|
||||
|
||||
template <mcc_serialization_params_c p_t>
|
||||
void setParams(p_t const& pars)
|
||||
{
|
||||
if constexpr (std::same_as<p_t, params_t>) {
|
||||
_params = pars;
|
||||
} else {
|
||||
_params.seq_delim = pars.seq_delim;
|
||||
_params.elem_delim = pars.elem_delim;
|
||||
_params.coordpair_format = pars.coordpair_format;
|
||||
_params.angle_prec = pars.angle_prec;
|
||||
_params.timepoint_format = pars.timepoint_format;
|
||||
_params.norm_sxgm = pars.norm_sxgm;
|
||||
_params.sxgm_hms = pars.sxgm_hms;
|
||||
}
|
||||
}
|
||||
|
||||
template <mcc_serialization_params_c p_t>
|
||||
requires(!std::same_as<p_t, params_t>)
|
||||
p_t getParams() const
|
||||
{
|
||||
p_t pars;
|
||||
|
||||
pars.seq_delim = _params.seq_delim;
|
||||
pars.elem_delim = _params.elem_delim;
|
||||
pars.coordpair_format = _params.coordpair_format;
|
||||
pars.angle_prec = _params.angle_prec;
|
||||
pars.timepoint_format = _params.timepoint_format;
|
||||
pars.norm_sxgm = _params.norm_sxgm;
|
||||
pars.sxgm_hms = _params.sxgm_hms;
|
||||
|
||||
return pars;
|
||||
}
|
||||
|
||||
|
||||
params_t getParams() const
|
||||
{
|
||||
return _params;
|
||||
}
|
||||
|
||||
protected:
|
||||
_params_manipulator_t() = default;
|
||||
|
||||
params_t _params;
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
|
||||
|
||||
// struct MccDeserializerBase : mcc_deserializer_interface_t<MccError> {
|
||||
// virtual ~MccDeserializerBase() = default;
|
||||
|
||||
// using typename mcc_deserializer_interface_t<MccError>::error_t;
|
||||
|
||||
// protected:
|
||||
// MccDeserializerBase() = default;
|
||||
// };
|
||||
|
||||
// template <mcc_serialization_params_c ParamsT>
|
||||
// struct MccSerializerBase : mcc_serializer_interface_t<MccError>, details::_params_manipulator_t<ParamsT> {
|
||||
// virtual ~MccSerializerBase() = default;
|
||||
|
||||
// using typename mcc_serializer_interface_t<MccError>::error_t;
|
||||
|
||||
// using typename details::_params_manipulator_t<ParamsT>::params_t;
|
||||
|
||||
// protected:
|
||||
// MccSerializerBase() = default;
|
||||
// };
|
||||
|
||||
// template <mcc_serialization_params_c ParamsT>
|
||||
// struct MccDeserializerBase : mcc_deserializer_interface_t<MccError>, details::_params_manipulator_t<ParamsT> {
|
||||
// virtual ~MccDeserializerBase() = default;
|
||||
|
||||
// using typename mcc_deserializer_interface_t<MccError>::error_t;
|
||||
|
||||
// using typename details::_params_manipulator_t<ParamsT>::params_t;
|
||||
|
||||
// protected:
|
||||
// MccDeserializerBase() = default;
|
||||
// };
|
||||
|
||||
} // namespace impl
|
||||
|
||||
} // namespace mcc
|
||||
775
include/mcc/mcc_serializer.h
Normal file
775
include/mcc/mcc_serializer.h
Normal file
@@ -0,0 +1,775 @@
|
||||
#pragma once
|
||||
|
||||
#include "mcc_coordinate.h"
|
||||
#include "mcc_serialization_common.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccSerializerErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_UNDERLYING_SERIALIZER,
|
||||
ERROR_INVALID_EPOCH,
|
||||
ERROR_COORD_TRANSFORM
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccSerializerErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
// error category
|
||||
struct MccSerializerCategory : public std::error_category {
|
||||
MccSerializerCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "MCC-SERIALIZER-ERR-CATEGORY";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccSerializerErrorCode err = static_cast<MccSerializerErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccSerializerErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER:
|
||||
return "error returned by underlying serializer";
|
||||
case MccSerializerErrorCode::ERROR_INVALID_EPOCH:
|
||||
return "invalid coordinate epoch";
|
||||
case MccSerializerErrorCode::ERROR_COORD_TRANSFORM:
|
||||
return "coordinates transformation error";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccSerializerCategory& get()
|
||||
{
|
||||
static const MccSerializerCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccSerializerErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccSerializerCategory::get());
|
||||
}
|
||||
|
||||
|
||||
/* BASE SERIALIZER CLASS (FOR IMPLEMENTATIONS BELOW) */
|
||||
|
||||
struct MccSerializerBase : mcc_serializer_interface_t<MccError> {
|
||||
virtual ~MccSerializerBase() = default;
|
||||
|
||||
using typename mcc_serializer_interface_t<MccError>::error_t;
|
||||
|
||||
protected:
|
||||
MccSerializerBase() = default;
|
||||
|
||||
enum CoordType { CO_LON, CO_LAT };
|
||||
|
||||
static void addElemDelimiter(traits::mcc_output_char_range auto& output,
|
||||
mcc_serialization_params_c auto const& params)
|
||||
{
|
||||
std::format_to(std::back_inserter(output), "{}", params.elem_delim);
|
||||
}
|
||||
|
||||
|
||||
static void addSeqDelimiter(traits::mcc_output_char_range auto& output,
|
||||
mcc_serialization_params_c auto const& params)
|
||||
{
|
||||
std::format_to(std::back_inserter(output), "{}", params.seq_delim);
|
||||
}
|
||||
|
||||
|
||||
// set serialized angle format according to coordinates pair format and type of
|
||||
// serializing mcc_coord_pair_c::pairKind
|
||||
template <MccCoordPairKind PAIRKIND, CoordType TYPE = MccSerializerBase::CO_LON>
|
||||
static void angleFormatFromCoordPairType(mcc_serialization_params_c auto& pars)
|
||||
{
|
||||
if (pars.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_DEGREES) {
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES;
|
||||
} else { // format to sexagesimal form according to celestial coordinate type
|
||||
if constexpr (TYPE == MccSerializerBase::CO_LON) {
|
||||
if (pars.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGDEG) {
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS;
|
||||
} else if (pars.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG) {
|
||||
if constexpr (PAIRKIND == MccCoordPairKind::COORDS_KIND_AZZD ||
|
||||
PAIRKIND == MccCoordPairKind::COORDS_KIND_AZALT ||
|
||||
PAIRKIND == MccCoordPairKind::COORDS_KIND_XY ||
|
||||
PAIRKIND == MccCoordPairKind::COORDS_KIND_GENERIC) { // azimuth is in degrees
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS;
|
||||
} else { // RA or HA
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS;
|
||||
}
|
||||
} else {
|
||||
// !!!!!!!!!!!!!!!!!!
|
||||
}
|
||||
} else { // Y-coordinates (co-latitude one, DEC, ALT, ZD, generic X) is always in degrees for celestial
|
||||
// point
|
||||
pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VT, typename R>
|
||||
requires(std::ranges::input_range<R> && std::same_as<VT, std::ranges::range_value_t<R>>)
|
||||
static error_t serializeRange(mcc_serializer_c auto& sr,
|
||||
R const& r,
|
||||
traits::mcc_output_char_range auto& output,
|
||||
mcc_serialization_params_c auto const& params)
|
||||
{
|
||||
size_t i = 0, N = std::ranges::size(r);
|
||||
|
||||
for (auto const& el : r) {
|
||||
auto err = sr(output, el, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
if (++i < N) {
|
||||
// MccSerializerBase::addSeqDelimiter(output, params);
|
||||
MccSerializerBase::addElemDelimiter(output, params);
|
||||
}
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* MAIN (FALLBACK) TEMPLATED IMPLEMENTATION */
|
||||
|
||||
template <typename VT>
|
||||
struct MccSerializer : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-FALLBACK-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
if constexpr (std::convertible_to<VT, std::string>) {
|
||||
std::string s = value;
|
||||
std::ranges::copy(s, std::back_inserter(output));
|
||||
// auto err = MccSerializer<std::string>{}(output, static_cast<std::string>(value), params);
|
||||
// if (err) {
|
||||
// return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
// }
|
||||
} else if constexpr (std::ranges::range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
|
||||
// special range (character sequence)
|
||||
if constexpr (std::same_as<value_t, char>) {
|
||||
std::ranges::copy(value, std::back_inserter(output));
|
||||
} else {
|
||||
MccSerializer<value_t> sr;
|
||||
|
||||
return MccSerializerBase::serializeRange<value_t>(sr, value, output, params);
|
||||
}
|
||||
} else if constexpr (std::formattable<VT, char>) {
|
||||
std::format_to(std::back_inserter(output), "{}", value);
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED TYPE!!!");
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* SPECIALIZATION FOR THE SOME CONCEPTS */
|
||||
|
||||
|
||||
template <traits::mcc_time_duration_c VT>
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
virtual ~MccSerializer() = default;
|
||||
|
||||
constexpr static std::string_view serializerName{"MCC-TIME-DURATION-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
std::format_to(std::back_inserter(output), "{}", value.count());
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* std::chrono::sys_time variants and its sequence */
|
||||
|
||||
template <traits::mcc_systime_c VT>
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
virtual ~MccSerializer() = default;
|
||||
|
||||
constexpr static std::string_view serializerName{"MCC-SYSTIME-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
// std::vformat_to(std::back_inserter(output), params.systime_format, std::make_format_args(value));
|
||||
auto tp = std::chrono::round<std::chrono::milliseconds>(value);
|
||||
std::vformat_to(std::back_inserter(output), params.systime_format, std::make_format_args(tp));
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(!std::is_arithmetic_v<VT> && mcc_angle_c<VT>)
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-ANGLE-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
double v = (double)value; // radians (see mcc_angle_c concept)
|
||||
std::string sgm;
|
||||
|
||||
switch (params.angle_format) {
|
||||
case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES: {
|
||||
v *= MCC_RADS_TO_DEGRESS;
|
||||
std::string_view fmt = "{:." + std::to_string(params.angle_prec.decimals) + "f}";
|
||||
std::vformat_to(std::back_inserter(output), fmt, std::make_format_args(v));
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
// return MccSerializer<double>{}(output, v, params);
|
||||
case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS:
|
||||
if (params.norm_sxgm) {
|
||||
sgm = utils::rad2sxg<true>(v, false, params.angle_prec.deg_prec);
|
||||
} else {
|
||||
sgm = utils::rad2sxg<false>(v, false, params.angle_prec.deg_prec);
|
||||
}
|
||||
|
||||
break;
|
||||
case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS:
|
||||
if (params.norm_sxgm) {
|
||||
sgm = utils::rad2sxg<true>(v, true, params.angle_prec.hour_prec);
|
||||
} else {
|
||||
sgm = utils::rad2sxg<false>(v, true, params.angle_prec.hour_prec);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto err = MccSerializer<std::string>{}(output, sgm, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
template <mcc_coord_epoch_c VT>
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-COORD-EPOCH-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
double jd;
|
||||
|
||||
switch (params.timepoint_format) {
|
||||
case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_DATE: {
|
||||
auto tp = value.UTC();
|
||||
auto err = MccSerializer<decltype(tp)>{}(output, tp, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
};
|
||||
case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JEPOCH: {
|
||||
auto ep = value.JEpoch();
|
||||
auto err = MccSerializer<decltype(ep)>{}(output, ep, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
} break;
|
||||
case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JD:
|
||||
jd = value.MJD() + MCC_J2000_MJD;
|
||||
break;
|
||||
case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_MJD:
|
||||
jd = value.MJD();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
auto err = MccSerializer<double>{}(output, jd, params);
|
||||
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <mcc_coord_pair_c VT>
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-COORD-PAIR-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
auto pars = params;
|
||||
|
||||
// pars.norm_sxgm = true;
|
||||
// pars.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG;
|
||||
|
||||
// X-coordinate
|
||||
MccSerializerBase::angleFormatFromCoordPairType<VT::pairKind, MccSerializerBase::CO_LON>(pars);
|
||||
|
||||
auto err = MccSerializer<MccAngle>{}(output, value.x(), pars);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, params);
|
||||
|
||||
pars.norm_sxgm = false; // do not normalize co-latitude angle
|
||||
|
||||
// Y-coordinate
|
||||
MccSerializerBase::angleFormatFromCoordPairType<VT::pairKind, MccSerializerBase::CO_LAT>(pars);
|
||||
|
||||
err = MccSerializer<MccAngle>{}(output, value.y(), pars);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, params);
|
||||
|
||||
// epoch
|
||||
auto ep = value.epoch();
|
||||
auto ep_err = MccSerializer<decltype(ep)>{}(output, ep, params);
|
||||
if (ep_err) {
|
||||
return mcc_deduced_err(ep_err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, params);
|
||||
|
||||
// pair kind
|
||||
auto pk_err = MccSerializer<std::string_view>{}(output, MccCoordPairKindToStr(VT::pairKind), params);
|
||||
if (pk_err) {
|
||||
return mcc_deduced_err(pk_err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <mcc_skypoint_c VT>
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-SKYPOINT-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
auto serialize_cpair = [&]<typename T>(T& cp) -> error_t {
|
||||
auto ccte_err = value.to(cp);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
auto err = MccSerializer<T>{}(output, cp, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
};
|
||||
|
||||
switch (value.pairKind()) {
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS: {
|
||||
MccSkyRADEC_ICRS cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_OBS: {
|
||||
MccSkyRADEC_OBS cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_APP: {
|
||||
MccSkyRADEC_APP cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_OBS: {
|
||||
MccSkyHADEC_OBS cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_APP: {
|
||||
MccSkyHADEC_APP cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_AZZD: {
|
||||
MccSkyAZZD cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_AZALT: {
|
||||
MccSkyAZALT cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_XY: {
|
||||
MccGenXY cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
case MccCoordPairKind::COORDS_KIND_GENERIC: {
|
||||
MccGenXY cp;
|
||||
return serialize_cpair(cp);
|
||||
}
|
||||
default:
|
||||
return MccSerializerErrorCode::ERROR_COORD_TRANSFORM;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <mcc_telemetry_data_c VT>
|
||||
struct MccSerializer<VT> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-TELEMETRY-DATA-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
VT const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
// FORMAT: RA_OBS_MOUNT, DEC_OBS_MOUNT, RA_APP_MOUNT, DEC_APP_MOUNT, HA_APP_MOUNT, AZ_MOUNT, ZD_MOUNT,
|
||||
// REFR_CORR_MOUNT, ENC_X, ENC_Y, PCM_X, PCM_Y, RA_APP_TAG, DEC_APP_TAG, AZ_TAG, ZD_TAG, LAST, EO, TIMEPOINT,
|
||||
// STATUS
|
||||
|
||||
// NOTE: One must assume that the returned RA coordinates are in format of underlying celestial coordinate
|
||||
// transformation engine used in the mcc_skypoint_c class implementation. E.g. ERFA-library uses the
|
||||
// CIO-based representation of RA
|
||||
|
||||
auto pars_h = params;
|
||||
|
||||
auto pars_d = params;
|
||||
|
||||
|
||||
pars_h.norm_sxgm = true;
|
||||
pars_h.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG;
|
||||
pars_d.norm_sxgm = true;
|
||||
pars_d.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG;
|
||||
|
||||
MccSkyRADEC_OBS rd_obs;
|
||||
MccSkyRADEC_APP rd_app;
|
||||
MccSkyHADEC_APP hd_app;
|
||||
MccSkyAZZD azzd;
|
||||
|
||||
// quantities in hour representation
|
||||
MccSerializerBase::angleFormatFromCoordPairType<MccCoordPairKind::COORDS_KIND_RADEC_ICRS,
|
||||
MccSerializerBase::CO_LON>(pars_h);
|
||||
|
||||
// quantities in degree representation
|
||||
MccSerializerBase::angleFormatFromCoordPairType<MccCoordPairKind::COORDS_KIND_RADEC_ICRS,
|
||||
MccSerializerBase::CO_LON>(pars_d);
|
||||
|
||||
MccSerializer<MccAngle> ang_sr;
|
||||
|
||||
// RA_OBS_MOUNT, DEC_OBS_MOUNT
|
||||
auto ccte_err = value.mountPos.toAtSameEpoch(rd_obs);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
auto err = ang_sr(output, rd_obs.x(), pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, rd_obs.y(), pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
// RA_APP_MOUNT, DEC_APP_MOUNT
|
||||
ccte_err = value.mountPos.toAtSameEpoch(rd_app);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, rd_app.x(), pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, rd_app.y(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
// HA_APP_MOUNT
|
||||
ccte_err = value.mountPos.toAtSameEpoch(hd_app);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, hd_app.x(), pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
// AZ_MOUNT, ZD_MOUNT
|
||||
ccte_err = value.mountPos.toAtSameEpoch(azzd);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, azzd.x(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, azzd.y(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
// refraction correction
|
||||
|
||||
MccAngle ang;
|
||||
ccte_err = value.mountPos.refractCorrection(&ang);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, ang, pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
|
||||
// encoder X and Y
|
||||
|
||||
err = ang_sr(output, value.hwState.XY.x(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, value.hwState.XY.y(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
|
||||
// PCM X and Y
|
||||
|
||||
err = ang_sr(output, value.pcmCorrection.pcmX, pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, value.pcmCorrection.pcmY, pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
|
||||
// RA_APP_TAG, DEC_APP_TAG
|
||||
ccte_err = value.targetPos.toAtSameEpoch(rd_app);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, rd_app.x(), pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, rd_app.y(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
// AZ_TAG, ZD_TAG
|
||||
ccte_err = value.targetPos.toAtSameEpoch(azzd);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, azzd.x(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
err = ang_sr(output, azzd.y(), pars_d);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
|
||||
// LAST, local apparent sideral time
|
||||
|
||||
ccte_err = value.mountPos.appSideralTime(&ang, true);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, ang, pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
|
||||
// EO, equation of origins
|
||||
|
||||
ccte_err = value.mountPos.EO(&ang);
|
||||
if (ccte_err) {
|
||||
return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
err = ang_sr(output, ang, pars_h);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
// coordinates epoch
|
||||
|
||||
auto ep_err = MccSerializer<MccCelestialCoordEpoch>{}(output, value.mountPos.epoch(), pars_d);
|
||||
if (ep_err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
MccSerializerBase::addElemDelimiter(output, pars_h);
|
||||
|
||||
|
||||
// status (it must be formattable, see mcc_concepts.h)
|
||||
|
||||
auto st_err =
|
||||
MccSerializer<decltype(value.hwState.movementState)>{}(output, value.hwState.movementState, pars_d);
|
||||
if (st_err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
struct MccSerializer<MccSerializedCoordPairFormat> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-COORDPAIR-FORMAT-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
MccSerializedCoordPairFormat const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
std::ranges::copy(MccSerializedCoordPairFormatToStr(value), std::back_inserter(output));
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct MccSerializer<MccSerializedAngleFormatPrec> : MccSerializerBase {
|
||||
constexpr static std::string_view serializerName{"MCC-ANGLE-FORMAT-PREC-SERIALIZER"};
|
||||
|
||||
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output,
|
||||
MccSerializedAngleFormatPrec const& value,
|
||||
ParamsT const& params = mcc_serialization_params_t{})
|
||||
{
|
||||
auto err = MccSerializer<decltype(value.hour_prec)>{}(output, value.hour_prec, params);
|
||||
if (!err) {
|
||||
auto err = MccSerializer<decltype(value.deg_prec)>{}(output, value.deg_prec, params);
|
||||
if (!err) {
|
||||
auto err = MccSerializer<decltype(value.decimals)>{}(output, value.decimals, params);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
} else {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
} else {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
static_assert(mcc_serializer_c<MccSerializer<MccAngle>>, "!!!");
|
||||
|
||||
} // namespace mcc::impl
|
||||
910
include/mcc/mcc_serializer.h.old
Normal file
910
include/mcc/mcc_serializer.h.old
Normal file
@@ -0,0 +1,910 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF SERIALIZER CLASSES *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_constants.h"
|
||||
#include "mcc_coordinate.h"
|
||||
#include "mcc_epoch.h"
|
||||
#include "mcc_error.h"
|
||||
#include "mcc_traits.h"
|
||||
#include "mcc_utils.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccSerializerErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_UNDERLYING_SERIALIZER,
|
||||
ERROR_INVALID_EPOCH,
|
||||
ERROR_COORD_TRANSFORM
|
||||
};
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccSerializerErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
|
||||
// error category
|
||||
struct MccSerializerCategory : public std::error_category {
|
||||
MccSerializerCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "MCC-SERIALIZER-ERR-CATEGORY";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccSerializerErrorCode err = static_cast<MccSerializerErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccSerializerErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER:
|
||||
return "error returned by underlying serializer";
|
||||
case MccSerializerErrorCode::ERROR_INVALID_EPOCH:
|
||||
return "invalid coordinate epoch";
|
||||
case MccSerializerErrorCode::ERROR_COORD_TRANSFORM:
|
||||
return "coordinates transformation error";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccSerializerCategory& get()
|
||||
{
|
||||
static const MccSerializerCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccSerializerErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccSerializerCategory::get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <mcc_error_c RetT>
|
||||
struct mcc_serializer_interface_t {
|
||||
virtual ~mcc_serializer_interface_t() = default;
|
||||
|
||||
typedef RetT error_t;
|
||||
|
||||
template <std::derived_from<mcc_serializer_interface_t> SelfT,
|
||||
traits::mcc_output_char_range R,
|
||||
typename ValueT,
|
||||
typename FmtT>
|
||||
requires(std::same_as<std::format_string<ValueT>, FmtT> || std::same_as<std::string_view, FmtT>)
|
||||
RetT operator()(this SelfT&& self, R& output, ValueT const& value, FmtT fmt)
|
||||
{
|
||||
return std::forward<SelfT>(self)(output, value, std::move(fmt));
|
||||
}
|
||||
|
||||
protected:
|
||||
mcc_serializer_interface_t() = default;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept mcc_serializer_c = std::derived_from<T, mcc_serializer_interface_t<typename T::error_t>>;
|
||||
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
struct MccSerializerBase : mcc_serializer_interface_t<impl::MccError>, utils::mcc_elem_sequence_with_delim_t {
|
||||
using typename mcc_serializer_interface_t<impl::MccError>::error_t;
|
||||
|
||||
virtual ~MccSerializerBase() = default;
|
||||
|
||||
protected:
|
||||
MccSerializerBase() = default;
|
||||
|
||||
template <typename VT, typename R>
|
||||
requires(std::ranges::input_range<R> && std::same_as<VT, std::ranges::range_value_t<R>>)
|
||||
error_t serializingRange(mcc_serializer_c auto& sr,
|
||||
R const& r,
|
||||
traits::mcc_output_char_range auto& output,
|
||||
auto fmt) const
|
||||
{
|
||||
size_t i = 0, N = std::ranges::size(r);
|
||||
|
||||
for (auto const& el : r) {
|
||||
auto err = sr(output, el, std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
if (++i < N) {
|
||||
std::format_to(std::back_inserter(output), "{}", _seqDelimiter);
|
||||
}
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
|
||||
/* fallback template */
|
||||
template <typename VT>
|
||||
class MccSerializer : public details::MccSerializerBase
|
||||
// class MccSerializer : public mcc_serializer_interface_t<impl::MccError>
|
||||
{
|
||||
public:
|
||||
// default delimiter
|
||||
static constexpr std::string_view defaultRangeDelimiter{","};
|
||||
|
||||
// typedef impl::MccError error_t;
|
||||
|
||||
MccSerializer() = default;
|
||||
|
||||
~MccSerializer() = default;
|
||||
|
||||
template <typename FmtT = std::nullptr_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr)
|
||||
{
|
||||
if constexpr (std::formattable<VT, char>) {
|
||||
if constexpr (std::is_null_pointer_v<FmtT>) {
|
||||
std::format_to(std::back_inserter(output), "{}", value);
|
||||
} else if constexpr (traits::mcc_input_char_range<FmtT>) {
|
||||
std::string_view sfmt{fmt.begin(), fmt.end()};
|
||||
std::vformat_to(std::back_inserter(output), sfmt, std::make_format_args(value));
|
||||
} else if constexpr (std::same_as<std::format_string<VT>, FmtT>) {
|
||||
std::format_to(std::back_inserter(output), std::move(fmt), value);
|
||||
} else {
|
||||
static_assert(false, "INVALID FORMAT STRING TYPE!!!");
|
||||
}
|
||||
} else if constexpr (std::convertible_to<VT, std::string>) {
|
||||
auto err = MccSerializer<std::string>{}(output, static_cast<std::string>(value), std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
} else if constexpr (std::ranges::range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
|
||||
// special range (character sequence)
|
||||
if constexpr (std::same_as<value_t, char>) {
|
||||
std::string str;
|
||||
std::ranges::copy(value, std::back_inserter(str));
|
||||
|
||||
auto err = MccSerializer<std::string>{}(output, str, std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
} else {
|
||||
MccSerializer<value_t> sr;
|
||||
|
||||
return serializingRange<value_t>(sr, value, output, std::move(fmt));
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "UNSUPPORTED TYPE!!!");
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* SPECIALIZATION FOR THE SOME CONCEPTS */
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(traits::mcc_systime_c<VT> ||
|
||||
(std::ranges::input_range<VT> && traits::mcc_systime_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>))
|
||||
class MccSerializer<VT> : public details::MccSerializerBase
|
||||
{
|
||||
public:
|
||||
virtual ~MccSerializer() = default;
|
||||
|
||||
template <typename FmtT = std::nullptr_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr)
|
||||
{
|
||||
if constexpr (std::ranges::input_range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccSerializer<value_t> sr;
|
||||
|
||||
return serializingRange<value_t>(sr, value, output, std::move(fmt));
|
||||
} else { // scalar
|
||||
if constexpr (std::is_null_pointer_v<FmtT>) {
|
||||
std::format_to(std::back_inserter(output), "{:%FT%T}", value);
|
||||
} else if constexpr (traits::mcc_input_char_range<FmtT>) {
|
||||
std::string_view sfmt{fmt.begin(), fmt.end()};
|
||||
if (sfmt == "{}") {
|
||||
sfmt = "{:%FT%T}";
|
||||
}
|
||||
std::vformat_to(std::back_inserter(output), sfmt, std::make_format_args(value));
|
||||
} else if constexpr (std::same_as<std::format_string<VT>, FmtT>) {
|
||||
std::string_view sfmt{fmt.get().begin(), fmt.get().end()};
|
||||
if (sfmt == "{}") {
|
||||
std::format_to(std::back_inserter(output), "{:%FT%T}", value);
|
||||
} else {
|
||||
std::format_to(std::back_inserter(output), std::move(fmt), value);
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "INVALID FORMAT STRING TYPE!!!");
|
||||
}
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(!std::is_arithmetic_v<VT> &&
|
||||
(mcc_angle_c<VT> ||
|
||||
(std::ranges::input_range<VT> && mcc_angle_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>)))
|
||||
class MccSerializer<VT> : public virtual details::MccSerializerBase
|
||||
// class MccSerializer<VT> : public mcc_serializer_interface_t<impl::MccError>
|
||||
{
|
||||
public:
|
||||
enum SerializedCoordFormat {
|
||||
CFMT_DEGREES, // floating-point representation in degrees
|
||||
CFMT_SGM_HOURS, // sexagesimal representation: HH:MM:SS.SSSSSS (hours:minutes:seconds)
|
||||
CFMT_SGM_DEGS // sexagesimal representation: DD:MM:SS.SSSSS (degrees:arcmins::arcsecs)
|
||||
};
|
||||
|
||||
struct SexagesimalCoordPrec {
|
||||
uint8_t hour_prec = 2; // number of decimal places in hour seconds
|
||||
uint8_t deg_prec = 1; // number of decimal places in arcseconds
|
||||
};
|
||||
|
||||
// typedef impl::MccError error_t;
|
||||
|
||||
virtual ~MccSerializer() = default;
|
||||
|
||||
void setCoordFormat(SerializedCoordFormat format)
|
||||
{
|
||||
_coordFormat = format;
|
||||
}
|
||||
|
||||
SerializedCoordFormat getCoordFormat() const
|
||||
{
|
||||
return _coordFormat;
|
||||
}
|
||||
|
||||
|
||||
void setCoordPrec(SexagesimalCoordPrec prec)
|
||||
{
|
||||
_coordPrec = std::move(prec);
|
||||
}
|
||||
|
||||
SexagesimalCoordPrec getCoordPrec() const
|
||||
{
|
||||
return _coordPrec;
|
||||
}
|
||||
|
||||
template <typename FmtT = std::nullptr_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr)
|
||||
{
|
||||
if constexpr (std::ranges::input_range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccSerializer<value_t> sr;
|
||||
sr.setCoordFormat(_coordFormat);
|
||||
sr.setCoordPrec(_coordPrec);
|
||||
|
||||
return serializingRange<value_t>(sr, value, output, std::move(fmt));
|
||||
} else { // scalar
|
||||
double v = (double)value; // radians
|
||||
std::string sgm;
|
||||
|
||||
switch (_coordFormat) {
|
||||
case MccSerializer::CFMT_DEGREES:
|
||||
v *= 180.0 / std::numbers::pi;
|
||||
return MccSerializer<double>{}(output, v, std::move(fmt));
|
||||
case MccSerializer::CFMT_SGM_HOURS:
|
||||
sgm = utils::rad2sxg(v, true, _coordPrec.load().hour_prec);
|
||||
break;
|
||||
case MccSerializer::CFMT_SGM_DEGS:
|
||||
sgm = utils::rad2sxg(v, false, _coordPrec.load().deg_prec);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
auto err = MccSerializer<std::string>{}(output, sgm, std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
// std::atomic<SerializedCoordFormat> _coordFormat{MccSerializer::CFMT_DEGREES};
|
||||
std::atomic<SerializedCoordFormat> _coordFormat{MccSerializer::CFMT_SGM_DEGS};
|
||||
std::atomic<SexagesimalCoordPrec> _coordPrec{SexagesimalCoordPrec{.hour_prec = 2, .deg_prec = 1}};
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(mcc_coord_epoch_c<VT> ||
|
||||
(std::ranges::input_range<VT> && mcc_coord_epoch_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>))
|
||||
class MccSerializer<VT> : public virtual details::MccSerializerBase
|
||||
{
|
||||
public:
|
||||
enum TimePointFormat { TP_FORMAT_DATE, TP_FORMAT_MJD, TP_FORMAT_JEPOCH };
|
||||
|
||||
virtual ~MccSerializer() = default;
|
||||
|
||||
void setTimePointFormat(TimePointFormat format)
|
||||
{
|
||||
_tpFormat = format;
|
||||
}
|
||||
|
||||
TimePointFormat getTimePointFormat() const
|
||||
{
|
||||
return _tpFormat;
|
||||
}
|
||||
|
||||
|
||||
template <typename FmtT = std::nullptr_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr)
|
||||
{
|
||||
if constexpr (std::ranges::input_range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccSerializer<value_t> sr;
|
||||
sr.setTimePointFormat(_tpFormat);
|
||||
|
||||
return serializingRange<value_t>(sr, value, output, std::move(fmt));
|
||||
} else {
|
||||
switch (_tpFormat) {
|
||||
case TP_FORMAT_DATE: {
|
||||
auto utc = value.UTC();
|
||||
auto err = MccSerializer<std::remove_cvref_t<decltype(utc)>>{}(output, utc, std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
} break;
|
||||
case TP_FORMAT_MJD: {
|
||||
double mjd = value.MJD();
|
||||
auto err = MccSerializer<double>{}(output, mjd, std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
} break;
|
||||
case TP_FORMAT_JEPOCH: {
|
||||
auto jepoch = value.JEpoch();
|
||||
auto err = MccSerializer<std::remove_cvref_t<decltype(jepoch)>>{}(output, jepoch, std::move(fmt));
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::atomic<TimePointFormat> _tpFormat{TP_FORMAT_DATE};
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(mcc_coord_pair_c<VT> ||
|
||||
(std::ranges::input_range<VT> && mcc_coord_pair_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>))
|
||||
class MccSerializer<VT> : public std::conditional_t<std::ranges::input_range<VT>,
|
||||
MccSerializer<std::vector<MccCelestialCoordEpoch>>,
|
||||
MccSerializer<MccCelestialCoordEpoch>>,
|
||||
public std::conditional_t<std::ranges::input_range<VT>,
|
||||
MccSerializer<std::vector<MccAngle>>,
|
||||
MccSerializer<MccAngle>>
|
||||
// class MccSerializer<VT> : public mcc_serializer_interface_t<MccError>
|
||||
{
|
||||
protected:
|
||||
typedef std::conditional_t<std::ranges::input_range<VT>,
|
||||
MccSerializer<std::vector<MccCelestialCoordEpoch>>,
|
||||
MccSerializer<MccCelestialCoordEpoch>>
|
||||
base_ser_epoch_t;
|
||||
|
||||
typedef std::
|
||||
conditional_t<std::ranges::input_range<VT>, MccSerializer<std::vector<MccAngle>>, MccSerializer<MccAngle>>
|
||||
base_ser_angle_t;
|
||||
|
||||
public:
|
||||
using typename details::MccSerializerBase::error_t;
|
||||
|
||||
template <typename FmtT = std::nullptr_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr)
|
||||
{
|
||||
if constexpr (std::ranges::input_range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccSerializer<value_t> sr;
|
||||
|
||||
sr.setTimePointFormat(this->_tpFormat);
|
||||
sr.setCoordFormat(this->_coordFormat);
|
||||
sr.setCoordPrec(this->_coordPrec);
|
||||
|
||||
return serializingRange<value_t>(sr, value, output, std::move(fmt));
|
||||
} else { // scalar: format X[elem-delimiter]Y[elem-delimiter]epoch[elem-delimiter]pair-kind
|
||||
|
||||
// WARNING: still ignore format string!!!
|
||||
|
||||
error_t err;
|
||||
|
||||
// format to sexagesimal form according to celestial coordinate type (general XY-type is skipped)
|
||||
if (this->_coordFormat != base_ser_angle_t::CFMT_DEGREES) {
|
||||
auto prev_format = this->_coordFormat.load();
|
||||
|
||||
if constexpr (VT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD ||
|
||||
VT::pairKind == MccCoordPairKind::COORDS_KIND_AZALT) { // azimuth is in degrees
|
||||
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_DEGS);
|
||||
err = base_ser_angle_t::operator()(output, MccAngle{(double)value.x()});
|
||||
} else { // RA or HA are in hours
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
err = base_ser_angle_t::operator()(output, MccAngle{(double)value.x()});
|
||||
}
|
||||
|
||||
this->setCoordFormat(prev_format); // restore previous value
|
||||
} else { // here all angles in deciamal degrees
|
||||
err = base_ser_angle_t::operator()(output, MccAngle{(double)value.x()});
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
// format to sexagesimal form according to celestial coordinate type (general XY-type is skipped)
|
||||
// Y-coordinate of the celestial point is always in degrees
|
||||
if (this->_coordFormat != base_ser_angle_t::CFMT_DEGREES) {
|
||||
auto prev_format = this->_coordFormat.load();
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_DEGS);
|
||||
|
||||
err = base_ser_angle_t::operator()(output, MccAngle{(double)value.y()});
|
||||
|
||||
this->setCoordFormat(prev_format); // restore previous value
|
||||
} else { // here all angles in deciamal degrees
|
||||
err = base_ser_angle_t::operator()(output, MccAngle{(double)value.y()});
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
auto ep = value.epoch();
|
||||
if constexpr (std::convertible_to<decltype(ep), MccCelestialCoordEpoch>) {
|
||||
auto err_e = base_ser_epoch_t::operator()(output, MccCelestialCoordEpoch{ep});
|
||||
if (err_e) {
|
||||
return err_e;
|
||||
}
|
||||
} else {
|
||||
MccCelestialCoordEpoch ep1;
|
||||
bool ok = ep1.fromMJD(ep.MJD());
|
||||
if (!ok) {
|
||||
return MccSerializerErrorCode::ERROR_INVALID_EPOCH;
|
||||
}
|
||||
|
||||
auto err_e = (*this)(output, ep1);
|
||||
if (err_e) {
|
||||
return err_e;
|
||||
}
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
auto err_s = MccSerializer<std::string_view>{}(output, MccCoordPairKindToStr(VT::pairKind));
|
||||
if (err_s) {
|
||||
return mcc_deduced_err(err_s, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename VT>
|
||||
requires(mcc_skypoint_c<VT> ||
|
||||
(std::ranges::input_range<VT> && mcc_skypoint_c<std::remove_cv_t<std::ranges::range_value_t<VT>>>))
|
||||
class MccSerializer<VT> : public std::conditional_t<std::ranges::input_range<VT>,
|
||||
MccSerializer<std::vector<MccCelestialCoordEpoch>>,
|
||||
MccSerializer<MccCelestialCoordEpoch>>,
|
||||
public std::conditional_t<std::ranges::input_range<VT>,
|
||||
MccSerializer<std::vector<MccAngle>>,
|
||||
MccSerializer<MccAngle>>
|
||||
{
|
||||
protected:
|
||||
typedef std::conditional_t<std::ranges::input_range<VT>,
|
||||
MccSerializer<std::vector<MccCelestialCoordEpoch>>,
|
||||
MccSerializer<MccCelestialCoordEpoch>>
|
||||
base_ser_epoch_t;
|
||||
|
||||
typedef std::
|
||||
conditional_t<std::ranges::input_range<VT>, MccSerializer<std::vector<MccAngle>>, MccSerializer<MccAngle>>
|
||||
base_ser_angle_t;
|
||||
|
||||
public:
|
||||
using typename details::MccSerializerBase::error_t;
|
||||
|
||||
enum SkyPointFormat {
|
||||
SKYPOINT_FORMAT_FULL, // return RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, RA_OBS, DEC_OBS, AZ, ZD, HA_APP,
|
||||
// TIME-POINT
|
||||
SKYPOINT_FORMAT_OBS, // return observed coordinates: RA_OBS, DEC_OBS, AZ, ZD, HA_OBS, TIME-POINT
|
||||
SKYPOINT_FORMAT_APP, // return apparent (in vacuo) coordinates: RA_APP, DEC_APP, HA_APP, TIME-POINT
|
||||
SKYPOINT_FORMAT_PAIR // return current stored coordinate pair: X, Y, TIME-POINT, PAIR-KIND
|
||||
};
|
||||
|
||||
virtual ~MccSerializer() = default;
|
||||
|
||||
void setSkyPointFormat(SkyPointFormat format)
|
||||
{
|
||||
_skptFormat = format;
|
||||
}
|
||||
|
||||
SkyPointFormat getSkyPointFormat() const
|
||||
{
|
||||
return _skptFormat;
|
||||
}
|
||||
|
||||
template <typename FmtT = std::nullptr_t>
|
||||
error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr)
|
||||
{
|
||||
if constexpr (std::ranges::input_range<VT>) {
|
||||
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
|
||||
MccSerializer<value_t> sr;
|
||||
|
||||
sr.setTimePointFormat(this->_tpFormat);
|
||||
sr.setCoordFormat(this->_coordFormat);
|
||||
sr.setCoordPrec(this->_coordPrec);
|
||||
|
||||
return serializingRange<value_t>(sr, value, output, std::move(fmt));
|
||||
} else { // scalar
|
||||
|
||||
auto serialize_pair_func = [&output, this]<typename PT>(PT const& pt) -> error_t {
|
||||
MccSerializer<PT> pt_sr;
|
||||
|
||||
pt_sr.setTimePointFormat(this->_tpFormat);
|
||||
pt_sr.setCoordFormat(this->_coordFormat);
|
||||
pt_sr.setCoordPrec(this->_coordPrec);
|
||||
|
||||
auto err = pt_sr(output, pt);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
};
|
||||
|
||||
// WARNING: still ignore format string!!!
|
||||
|
||||
auto skpt_fmt = _skptFormat.load();
|
||||
|
||||
if (skpt_fmt == SKYPOINT_FORMAT_PAIR) {
|
||||
switch (value.pairKind()) {
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
|
||||
return serialize_pair_func(value.operator MccSkyRADEC_ICRS());
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_APP:
|
||||
return serialize_pair_func(value.operator MccSkyRADEC_APP());
|
||||
case MccCoordPairKind::COORDS_KIND_RADEC_OBS:
|
||||
return serialize_pair_func(value.operator MccSkyRADEC_OBS());
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_APP:
|
||||
return serialize_pair_func(value.operator MccSkyHADEC_APP());
|
||||
case MccCoordPairKind::COORDS_KIND_HADEC_OBS:
|
||||
return serialize_pair_func(value.operator MccSkyHADEC_OBS());
|
||||
case MccCoordPairKind::COORDS_KIND_AZZD:
|
||||
return serialize_pair_func(value.operator MccSkyAZZD());
|
||||
case MccCoordPairKind::COORDS_KIND_AZALT:
|
||||
return serialize_pair_func(value.operator MccSkyAZALT());
|
||||
case MccCoordPairKind::COORDS_KIND_XY:
|
||||
return serialize_pair_func(value.operator MccGenXY());
|
||||
default:
|
||||
return serialize_pair_func(value.operator MccGenXY()); // ???????!!!!!!!!!!!
|
||||
}
|
||||
} else {
|
||||
MccSkyRADEC_ICRS radec_icrs;
|
||||
MccSkyRADEC_OBS radec_obs;
|
||||
MccSkyHADEC_OBS hadec_obs;
|
||||
MccSkyRADEC_APP radec_app;
|
||||
MccSkyHADEC_APP hadec_app;
|
||||
MccSkyAZZD azzd;
|
||||
// MccGenXY xy;
|
||||
|
||||
if (value.pairKind() == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
|
||||
auto ep_now = MccCelestialCoordEpoch::now();
|
||||
radec_obs.setEpoch(ep_now);
|
||||
radec_app.setEpoch(ep_now);
|
||||
hadec_obs.setEpoch(ep_now);
|
||||
hadec_app.setEpoch(ep_now);
|
||||
azzd.setEpoch(ep_now);
|
||||
} else {
|
||||
auto ep = value.epoch();
|
||||
radec_obs.setEpoch(ep);
|
||||
radec_app.setEpoch(ep);
|
||||
hadec_obs.setEpoch(ep);
|
||||
hadec_app.setEpoch(ep);
|
||||
azzd.setEpoch(ep);
|
||||
}
|
||||
|
||||
auto prev_format = this->_coordFormat.load();
|
||||
|
||||
if (skpt_fmt == SKYPOINT_FORMAT_FULL) { // RA_ICRS, DEC_ICRS, RA_APP, DEC_APP
|
||||
auto err = value.to(radec_icrs, radec_app);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
// RA_ICRS is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
auto err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_icrs.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
|
||||
// DEC_ICRS is in sexagesimal degrees
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_DEGS);
|
||||
}
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_icrs.y()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
// RA_APP is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_app.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
|
||||
// DEC_APP is in sexagesimal degrees
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_DEGS);
|
||||
}
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_app.y()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
}
|
||||
|
||||
if (skpt_fmt == SKYPOINT_FORMAT_OBS || skpt_fmt == SKYPOINT_FORMAT_FULL) { // RA_OBS, DEC_OBS, AZ, ZD
|
||||
auto err = value.to(radec_obs, azzd);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
// RA_OBS is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
auto err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_obs.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
|
||||
// DEC_OBS, AZ and ZD are in sexagesimal degrees
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_DEGS);
|
||||
}
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_obs.y()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)azzd.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)azzd.y()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
}
|
||||
|
||||
if (skpt_fmt == SKYPOINT_FORMAT_FULL) { // HA_APP
|
||||
auto err = value.to(hadec_app);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
// HA_APP is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
auto err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)hadec_app.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
}
|
||||
|
||||
if (skpt_fmt == SKYPOINT_FORMAT_OBS) { // HA_OBS
|
||||
auto err = value.to(hadec_obs);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
// HA_OBS is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
auto err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)hadec_obs.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
}
|
||||
|
||||
|
||||
if (skpt_fmt == SKYPOINT_FORMAT_APP) { // RA_APP, DEC_APP, HA_APP
|
||||
auto err = value.to(radec_app, hadec_app);
|
||||
if (err) {
|
||||
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
|
||||
// RA_APP is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
auto err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_app.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
|
||||
// DEC_APP is in sexagesimal degrees
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_DEGS);
|
||||
}
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)radec_app.y()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
|
||||
// HA_APP is in sexagesimal hours
|
||||
if (prev_format != base_ser_angle_t::CFMT_DEGREES) {
|
||||
this->setCoordFormat(base_ser_angle_t::CFMT_SGM_HOURS);
|
||||
}
|
||||
|
||||
err_ang = base_ser_angle_t::operator()(output, MccAngle{(double)hadec_app.x()});
|
||||
if (err_ang) {
|
||||
return mcc_deduced_err(err_ang, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
|
||||
}
|
||||
|
||||
std::format_to(std::back_inserter(output), "{}", this->_elementDelimiter);
|
||||
}
|
||||
|
||||
this->setCoordFormat(prev_format); // restore previous value
|
||||
|
||||
|
||||
// epoch of app/obs coordinates
|
||||
auto ep = value.epoch();
|
||||
if constexpr (std::convertible_to<decltype(ep), MccCelestialCoordEpoch>) {
|
||||
auto err_e = base_ser_epoch_t::operator()(output, MccCelestialCoordEpoch{ep});
|
||||
if (err_e) {
|
||||
return err_e;
|
||||
}
|
||||
} else {
|
||||
MccCelestialCoordEpoch ep1;
|
||||
bool ok = ep1.fromMJD(ep.MJD());
|
||||
if (!ok) {
|
||||
return MccSerializerErrorCode::ERROR_INVALID_EPOCH;
|
||||
}
|
||||
|
||||
auto err_e = (*this)(output, ep1);
|
||||
if (err_e) {
|
||||
return err_e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MccSerializerErrorCode::ERROR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::atomic<SkyPointFormat> _skptFormat{SKYPOINT_FORMAT_FULL};
|
||||
};
|
||||
|
||||
|
||||
|
||||
static_assert(mcc_serializer_c<MccSerializer<float>>, "!!!");
|
||||
|
||||
static_assert(mcc_serializer_c<MccSerializer<impl::MccAngle>>, "!!!");
|
||||
|
||||
static_assert(std::atomic<MccSerializer<impl::MccAngle>::SerializedCoordFormat>::is_always_lock_free, "!!!");
|
||||
static_assert(std::atomic<MccSerializer<impl::MccAngle>::SexagesimalCoordPrec>::is_always_lock_free, "!!!");
|
||||
|
||||
static_assert(std::formattable<impl::MccAngle, char>, "!!!");
|
||||
|
||||
void f()
|
||||
{
|
||||
MccSerializer<impl::MccAngle> s;
|
||||
|
||||
std::string str;
|
||||
s(str, impl::MccAngleALT{1.1});
|
||||
}
|
||||
|
||||
} // namespace mcc::impl
|
||||
198
include/mcc/mcc_spdlog.h
Normal file
198
include/mcc/mcc_spdlog.h
Normal file
@@ -0,0 +1,198 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF LOGGER CLASS *
|
||||
* (BASING ON THE SPDLOG LIBRARY) *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
|
||||
#include <spdlog/logger.h>
|
||||
#include <list>
|
||||
|
||||
#include "mcc_traits.h"
|
||||
|
||||
|
||||
namespace mcc::utils
|
||||
{
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
/* SPDLOG-library based advanced single/multithreaded logger */
|
||||
|
||||
class MccSpdlogLogger
|
||||
{
|
||||
public:
|
||||
// [year-month-day time.millisecs][log-level]: log-message
|
||||
constexpr static std::array LOGGER_DEFAULT_FORMAT = {"[%Y-%m-%d %T.%e]"sv, "[%l]"sv, ": "sv, "%v"sv};
|
||||
|
||||
typedef spdlog::level::level_enum loglevel_t;
|
||||
|
||||
template <traits::mcc_range_of_input_char_range R = decltype(LOGGER_DEFAULT_FORMAT)>
|
||||
MccSpdlogLogger(std::shared_ptr<spdlog::logger> logger, const R& pattern_range = LOGGER_DEFAULT_FORMAT)
|
||||
: _currentLogPatternRange(), _currentLogPattern(), _loggerSPtr(logger)
|
||||
{
|
||||
if (std::distance(pattern_range.begin(), pattern_range.end())) {
|
||||
std::ranges::copy(
|
||||
pattern_range | std::views::transform([](const auto& el) { return std::string(el.begin(), el.end()); }),
|
||||
std::back_inserter(_currentLogPatternRange));
|
||||
} else {
|
||||
std::ranges::copy(LOGGER_DEFAULT_FORMAT | std::views::transform([](const auto& el) {
|
||||
return std::string(el.begin(), el.end());
|
||||
}),
|
||||
std::back_inserter(_currentLogPatternRange));
|
||||
}
|
||||
|
||||
std::ranges::copy(std::views::join(_currentLogPatternRange), std::back_inserter(_currentLogPattern));
|
||||
|
||||
_loggerSPtr->set_pattern(_currentLogPattern);
|
||||
}
|
||||
|
||||
|
||||
virtual ~MccSpdlogLogger() = default;
|
||||
|
||||
|
||||
void setLogLevel(loglevel_t log_level)
|
||||
{
|
||||
_loggerSPtr->set_level(log_level);
|
||||
}
|
||||
|
||||
loglevel_t getLogLevel() const
|
||||
{
|
||||
return _loggerSPtr->level();
|
||||
}
|
||||
|
||||
void logMessage(loglevel_t level, const std::string& msg)
|
||||
{
|
||||
_loggerSPtr->log(level, msg);
|
||||
}
|
||||
|
||||
// specialized for given level methods
|
||||
|
||||
void logCritical(const std::string& msg)
|
||||
{
|
||||
logMessage(spdlog::level::critical, msg);
|
||||
}
|
||||
|
||||
void logError(const std::string& msg)
|
||||
{
|
||||
logMessage(spdlog::level::err, msg);
|
||||
}
|
||||
|
||||
void logWarn(const std::string& msg)
|
||||
{
|
||||
logMessage(spdlog::level::warn, msg);
|
||||
}
|
||||
|
||||
void logInfo(const std::string& msg)
|
||||
{
|
||||
logMessage(spdlog::level::info, msg);
|
||||
}
|
||||
|
||||
void logDebug(const std::string& msg)
|
||||
{
|
||||
logMessage(spdlog::level::debug, msg);
|
||||
}
|
||||
|
||||
void logTrace(const std::string& msg)
|
||||
{
|
||||
logMessage(spdlog::level::trace, msg);
|
||||
}
|
||||
|
||||
template <traits::mcc_formattable... ArgTs>
|
||||
void logMessage(spdlog::level::level_enum level, spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(level, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
template <traits::mcc_formattable... ArgTs>
|
||||
void logCritical(spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(spdlog::level::critical, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
template <typename... ArgTs>
|
||||
void logError(spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(spdlog::level::err, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
template <traits::mcc_formattable... ArgTs>
|
||||
void logWarn(spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(spdlog::level::warn, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
template <traits::mcc_formattable... ArgTs>
|
||||
void logInfo(spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(spdlog::level::info, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
template <traits::mcc_formattable... ArgTs>
|
||||
void logDebug(spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(spdlog::level::debug, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
template <traits::mcc_formattable... ArgTs>
|
||||
void logTrace(spdlog::format_string_t<ArgTs...> fmt, ArgTs&&... args)
|
||||
{
|
||||
_loggerSPtr->log(spdlog::level::trace, fmt, std::forward<ArgTs>(args)...);
|
||||
}
|
||||
|
||||
|
||||
// helper methods
|
||||
|
||||
auto getThreadId() const
|
||||
{
|
||||
std::ostringstream st;
|
||||
st << std::this_thread::get_id();
|
||||
return st.str();
|
||||
}
|
||||
|
||||
// 'after_idx' is 0-based index!
|
||||
void addMarkToPatternIdx(const traits::mcc_input_char_range auto& mark, size_t after_idx = 1)
|
||||
requires(!std::is_pointer_v<std::decay_t<decltype(mark)>>)
|
||||
{
|
||||
if (!std::distance(mark.begin(), mark.end())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = _currentLogPatternRange.begin();
|
||||
size_t idx = 0;
|
||||
while (it != _currentLogPatternRange.end()) {
|
||||
++it;
|
||||
if (idx == after_idx)
|
||||
break;
|
||||
|
||||
++idx;
|
||||
}
|
||||
|
||||
_currentLogPatternRange.emplace(it, mark.begin(), mark.end());
|
||||
|
||||
_currentLogPattern.clear();
|
||||
std::ranges::copy(std::views::join(_currentLogPatternRange), std::back_inserter(_currentLogPattern));
|
||||
|
||||
_loggerSPtr->set_pattern(_currentLogPattern);
|
||||
}
|
||||
|
||||
void addMarkToPatternIdx(const char* mark, size_t after_idx = 1)
|
||||
{
|
||||
addMarkToPatternIdx(std::string_view{mark}, after_idx);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
std::list<std::string> _currentLogPatternRange;
|
||||
std::string _currentLogPattern;
|
||||
std::shared_ptr<spdlog::logger> _loggerSPtr;
|
||||
};
|
||||
|
||||
} // namespace mcc::utils
|
||||
377
include/mcc/mcc_telemetry.h
Normal file
377
include/mcc/mcc_telemetry.h
Normal file
@@ -0,0 +1,377 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* MOUNT CONTROL COMPONENTS LIBRARY *
|
||||
* *
|
||||
* *
|
||||
* IMPLEMENTATION OF MOUNT TELEMETRY CLASS *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
|
||||
#include <future>
|
||||
#include "mcc_concepts.h"
|
||||
#include "mcc_coordinate.h"
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
enum class MccTelemetryErrorCode : int {
|
||||
ERROR_OK,
|
||||
ERROR_NULLPTR,
|
||||
ERROR_COORD_TRANSFORM,
|
||||
ERROR_PCM_COMP,
|
||||
ERROR_HARDWARE_GETSTATE,
|
||||
ERROR_UPDATE_STOPPED,
|
||||
ERROR_DATA_TIMEOUT,
|
||||
ERROR_UNSUPPORTED_COORD_PAIR,
|
||||
ERROR_UPDATE_LOOP_WAIT
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace mcc::impl
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <>
|
||||
class is_error_code_enum<mcc::impl::MccTelemetryErrorCode> : public true_type
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
|
||||
|
||||
namespace mcc::impl
|
||||
{
|
||||
|
||||
// error category
|
||||
struct MccTelemetryCategory : public std::error_category {
|
||||
MccTelemetryCategory() : std::error_category() {}
|
||||
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "MCC-TELEMETRY";
|
||||
}
|
||||
|
||||
std::string message(int ec) const
|
||||
{
|
||||
MccTelemetryErrorCode err = static_cast<MccTelemetryErrorCode>(ec);
|
||||
|
||||
switch (err) {
|
||||
case MccTelemetryErrorCode::ERROR_OK:
|
||||
return "OK";
|
||||
case MccTelemetryErrorCode::ERROR_NULLPTR:
|
||||
return "nullptr input argument";
|
||||
case MccTelemetryErrorCode::ERROR_COORD_TRANSFORM:
|
||||
return "coordinate transformation error";
|
||||
case MccTelemetryErrorCode::ERROR_PCM_COMP:
|
||||
return "PCM computation error";
|
||||
case MccTelemetryErrorCode::ERROR_HARDWARE_GETSTATE:
|
||||
return "cannot get hardware state";
|
||||
case MccTelemetryErrorCode::ERROR_UPDATE_STOPPED:
|
||||
return "telemetry update was stopped";
|
||||
case MccTelemetryErrorCode::ERROR_DATA_TIMEOUT:
|
||||
return "a timeout occured while waiting for new data";
|
||||
case MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR:
|
||||
return "unsupported coordinate pair";
|
||||
case MccTelemetryErrorCode::ERROR_UPDATE_LOOP_WAIT:
|
||||
return "a timeout occured while waiting to exit the update loop";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const MccTelemetryCategory& get()
|
||||
{
|
||||
static const MccTelemetryCategory constInst;
|
||||
return constInst;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline std::error_code make_error_code(MccTelemetryErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccTelemetryCategory::get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <mcc_hardware_c HARDWARE_T>
|
||||
class MccTelemetry
|
||||
{
|
||||
public:
|
||||
typedef HARDWARE_T hardware_t;
|
||||
|
||||
typedef std::error_code error_t;
|
||||
|
||||
|
||||
struct telemetry_data_t {
|
||||
MccSkyPoint targetPos{};
|
||||
|
||||
MccSkyPoint mountPos{};
|
||||
|
||||
typename HARDWARE_T::hardware_state_t hwState{};
|
||||
|
||||
struct {
|
||||
double pcmX{}, pcmY{};
|
||||
} pcmCorrection{};
|
||||
};
|
||||
|
||||
static constexpr std::chrono::milliseconds _defaultTelemetryDataTimeout{500};
|
||||
|
||||
/*
|
||||
MccTelemetry(HARDWARE_T* hardware, mcc_pcm_c auto* pcm) : _hardware(hardware)
|
||||
{
|
||||
_updateFunc = [pcm, this](telemetry_data_t* tdata) {
|
||||
if (tdata == nullptr) {
|
||||
return MccTelemetryErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
// get hardware state (coordinates, speeds and so on)
|
||||
auto hw_err = _hardware->hardwareGetState(&(tdata->hwState));
|
||||
if (hw_err) {
|
||||
return mcc_deduced_err(hw_err, MccTelemetryErrorCode::ERROR_HARDWARE_GETSTATE);
|
||||
}
|
||||
|
||||
// compute PCM corrections and observed (corrected for PCM) mount coordinates
|
||||
auto pcm_err = pcm->computePCM(tdata->hwState.XY, &(tdata->pcmCorrection), &(tdata->mountPos));
|
||||
|
||||
if (pcm_err) {
|
||||
return mcc_deduced_err(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
|
||||
}
|
||||
|
||||
return MccTelemetryErrorCode::ERROR_OK;
|
||||
};
|
||||
|
||||
|
||||
_updatingStopSource = std::stop_source{};
|
||||
|
||||
_dataUpdatingRequested->clear();
|
||||
_dataUpdatingStart->clear();
|
||||
|
||||
// start thread for data updating
|
||||
_updatingFuture = std::async(
|
||||
std::launch::async,
|
||||
[this](std::stop_token stoken, telemetry_data_t* tdata) {
|
||||
while (!stoken.stop_requested()) {
|
||||
_dataUpdatingRequested->wait(false);
|
||||
|
||||
if (!stoken.stop_requested()) {
|
||||
std::lock_guard lock{*_timeoutMutex};
|
||||
|
||||
_dataUpdatingStart->test_and_set();
|
||||
_dataUpdatingStart->notify_all();
|
||||
|
||||
_lastUpdateError = _updateFunc(tdata);
|
||||
|
||||
_dataUpdatingStart->clear();
|
||||
_dataUpdatingRequested->clear();
|
||||
}
|
||||
}
|
||||
},
|
||||
_updatingStopSource.get_token(), _tdataPtr);
|
||||
}
|
||||
*/
|
||||
|
||||
MccTelemetry(HARDWARE_T* hardware, mcc_pcm_c auto* pcm)
|
||||
{
|
||||
_updatingStopSource = std::stop_source{};
|
||||
|
||||
_dataUpdatingRequested->clear();
|
||||
_dataUpdatingStart->clear();
|
||||
|
||||
// start thread for data updating
|
||||
_updatingFuture = std::async(
|
||||
std::launch::async,
|
||||
[this](std::stop_token stoken, HARDWARE_T* hw, auto* pcm_ptr) {
|
||||
while (!stoken.stop_requested()) {
|
||||
_dataUpdatingRequested->wait(false);
|
||||
|
||||
if (!stoken.stop_requested()) {
|
||||
std::lock_guard lock{*_timeoutMutex};
|
||||
|
||||
_dataUpdatingStart->test_and_set();
|
||||
_dataUpdatingStart->notify_all();
|
||||
|
||||
if (_tdataPtr != nullptr && hw && pcm_ptr) {
|
||||
_lastUpdateError = MccTelemetryErrorCode::ERROR_OK;
|
||||
|
||||
// get hardware state (coordinates, speeds and so on)
|
||||
auto hw_err = hw->hardwareGetState(&_tdataPtr->hwState);
|
||||
if (hw_err) {
|
||||
_lastUpdateError =
|
||||
mcc_deduced_err(hw_err, MccTelemetryErrorCode::ERROR_HARDWARE_GETSTATE);
|
||||
} else {
|
||||
// compute PCM corrections and observed (corrected for PCM) mount coordinates
|
||||
auto pcm_err = pcm_ptr->computePCM(_tdataPtr->hwState.XY, &_tdataPtr->pcmCorrection,
|
||||
&_tdataPtr->mountPos);
|
||||
|
||||
if (!pcm_err) {
|
||||
// set target coordinates
|
||||
if (_enteredTargetPos.pairKind() == MccCoordPairKind::COORDS_KIND_XY) {
|
||||
// hardware coordinates
|
||||
MccGenXY xy;
|
||||
auto ccte_err = _enteredTargetPos.toAtSameEpoch(xy);
|
||||
|
||||
if (!ccte_err) {
|
||||
decltype(_tdataPtr->pcmCorrection) pcm_corr;
|
||||
|
||||
pcm_err = pcm_ptr->computePCM(xy, &pcm_corr, &_tdataPtr->targetPos);
|
||||
if (pcm_err) {
|
||||
_lastUpdateError =
|
||||
mcc_deduced_err(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
|
||||
}
|
||||
|
||||
} else {
|
||||
_lastUpdateError =
|
||||
mcc_deduced_err(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
|
||||
}
|
||||
} else { // observed, apparent or ICRS
|
||||
_tdataPtr->targetPos = _enteredTargetPos;
|
||||
}
|
||||
|
||||
} else {
|
||||
_lastUpdateError = mcc_deduced_err(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
_lastUpdateError = MccTelemetryErrorCode::ERROR_NULLPTR;
|
||||
}
|
||||
|
||||
_dataUpdatingStart->clear();
|
||||
_dataUpdatingRequested->clear();
|
||||
}
|
||||
}
|
||||
},
|
||||
_updatingStopSource.get_token(), hardware, pcm);
|
||||
}
|
||||
|
||||
MccTelemetry(MccTelemetry&&) = default;
|
||||
MccTelemetry& operator=(MccTelemetry&&) = default;
|
||||
|
||||
MccTelemetry(const MccTelemetry&) = delete;
|
||||
MccTelemetry& operator=(const MccTelemetry&) = delete;
|
||||
|
||||
|
||||
~MccTelemetry()
|
||||
{
|
||||
_tdataPtr = nullptr;
|
||||
|
||||
_updatingStopSource.request_stop();
|
||||
|
||||
// trigger updating and exit from the inner loop of the hardware polling thread (see constructor)
|
||||
_dataUpdatingRequested->test_and_set();
|
||||
// _dataUpdatingRequested->notify_one();
|
||||
_dataUpdatingRequested->notify_all();
|
||||
|
||||
if (_updatingFuture.valid()) {
|
||||
// try to exit correctly
|
||||
_updatingFuture.wait_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
error_t setTarget(mcc_skypoint_c auto const& sp)
|
||||
{
|
||||
_enteredTargetPos = sp;
|
||||
|
||||
return MccTelemetryErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
error_t getTarget(mcc_skypoint_c auto* sp)
|
||||
{
|
||||
sp = _enteredTargetPos;
|
||||
|
||||
return MccTelemetryErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// blocks the current thread until telemetry data is received.
|
||||
// the maximum blocking time is equal to the set timeout (see setTelemetryDataTimeout method)
|
||||
//
|
||||
error_t telemetryData(telemetry_data_t* tdata)
|
||||
{
|
||||
std::lock_guard lock{*_updateMutex};
|
||||
|
||||
_lastUpdateError = MccTelemetryErrorCode::ERROR_OK;
|
||||
|
||||
_tdataPtr = tdata;
|
||||
|
||||
// trigger updating
|
||||
_dataUpdatingRequested->test_and_set();
|
||||
_dataUpdatingRequested->notify_one();
|
||||
|
||||
// wait for updating start
|
||||
_dataUpdatingStart->wait(false);
|
||||
if (_timeoutMutex->try_lock_for(_telemetryDataTimeout->load())) {
|
||||
_timeoutMutex->unlock();
|
||||
} else {
|
||||
_lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
|
||||
}
|
||||
|
||||
return _lastUpdateError;
|
||||
}
|
||||
|
||||
//
|
||||
// Set a timeout for the telemetry receiving process
|
||||
//
|
||||
void setTelemetryDataTimeout(traits::mcc_time_duration_c auto const& timeout)
|
||||
{
|
||||
if constexpr (std::floating_point<typename decltype(timeout)::rep>) {
|
||||
if (utils::isEqual(timeout.count(), 0.0) || timeout.count() < 0.0) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (timeout.count() <= 0.0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*_telemetryDataTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(timeout);
|
||||
}
|
||||
|
||||
template <traits::mcc_time_duration_c DT>
|
||||
DT getTelemetryDataTimeout() const
|
||||
{
|
||||
return std::chrono::duration_cast<DT>(_telemetryDataTimeout->load());
|
||||
}
|
||||
|
||||
std::chrono::milliseconds getTelemetryDataTimeout() const
|
||||
{
|
||||
return getTelemetryDataTimeout<std::chrono::milliseconds>();
|
||||
}
|
||||
|
||||
private:
|
||||
// HARDWARE_T* _hardware{nullptr};
|
||||
MccSkyPoint _enteredTargetPos{};
|
||||
|
||||
// std::function<error_t(telemetry_data_t*)> _updateFunc{};
|
||||
telemetry_data_t* _tdataPtr;
|
||||
|
||||
std::unique_ptr<std::atomic_flag> _dataUpdatingRequested{new std::atomic_flag{}};
|
||||
std::unique_ptr<std::atomic_flag> _dataUpdatingStart{new std::atomic_flag{}};
|
||||
std::unique_ptr<std::timed_mutex> _timeoutMutex{new std::timed_mutex()};
|
||||
|
||||
std::stop_source _updatingStopSource{};
|
||||
|
||||
error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK};
|
||||
|
||||
std::unique_ptr<std::atomic<std::chrono::nanoseconds>> _telemetryDataTimeout{
|
||||
new std::atomic<std::chrono::nanoseconds>{_defaultTelemetryDataTimeout}};
|
||||
|
||||
std::unique_ptr<std::mutex> _updateMutex{new std::mutex{}};
|
||||
|
||||
std::future<void> _updatingFuture{};
|
||||
};
|
||||
|
||||
|
||||
} // namespace mcc::impl
|
||||
249
include/mcc/mcc_traits.h
Normal file
249
include/mcc/mcc_traits.h
Normal file
@@ -0,0 +1,249 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
|
||||
namespace mcc::traits
|
||||
{
|
||||
|
||||
|
||||
template <std::ranges::range R>
|
||||
static constexpr size_t mcc_range_size(const R& r)
|
||||
{
|
||||
if constexpr (std::ranges::sized_range<R>) {
|
||||
return r.size();
|
||||
} else {
|
||||
return std::ranges::distance(r.begin(), r.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename R>
|
||||
concept mcc_char_view = std::ranges::view<R> && std::same_as<std::ranges::range_value_t<R>, char>;
|
||||
|
||||
|
||||
// range of char/const char
|
||||
template <typename R, typename CharT = char>
|
||||
concept mcc_char_range = std::ranges::range<R> && std::same_as<std::remove_cv_t<std::ranges::range_value_t<R>>, CharT>;
|
||||
|
||||
|
||||
// input range of char/const char
|
||||
template <typename R, typename CharT = char>
|
||||
concept mcc_input_char_range =
|
||||
std::ranges::input_range<R> && std::is_same_v<std::remove_cv_t<std::ranges::range_value_t<R>>, CharT>;
|
||||
|
||||
|
||||
// output range of char/const char
|
||||
template <typename R, typename CharT = char>
|
||||
concept mcc_output_char_range =
|
||||
std::ranges::output_range<R, CharT> && std::same_as<std::remove_cv_t<std::ranges::range_value_t<R>>, CharT>;
|
||||
|
||||
|
||||
template <typename R>
|
||||
concept mcc_view_or_output_char_range = mcc_char_view<R> || mcc_output_char_range<R>;
|
||||
|
||||
|
||||
template <typename R>
|
||||
concept mcc_range_of_char_range = std::ranges::range<R> && mcc_char_range<std::ranges::range_value_t<R>>;
|
||||
|
||||
template <typename R>
|
||||
concept mcc_range_of_input_char_range =
|
||||
std::ranges::range<R> && traits::mcc_input_char_range<std::ranges::range_value_t<R>>;
|
||||
|
||||
|
||||
template <typename R>
|
||||
concept mcc_range_of_output_char_range =
|
||||
std::ranges::range<R> && traits::mcc_output_char_range<std::ranges::range_value_t<R>>;
|
||||
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/72430369/how-to-check-that-a-type-is-formattable-using-type-traits-concepts)
|
||||
template <typename T>
|
||||
concept mcc_formattable =
|
||||
requires(T v, std::format_context ctx) { std::formatter<std::remove_cvref_t<T>>().format(v, ctx); };
|
||||
// concept mcc_formattable = requires(T v) { [](T vv) { return std::format("{}", vv); }(v); };
|
||||
|
||||
|
||||
// from https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
|
||||
template <typename T>
|
||||
concept mcc_time_duration_c = requires {
|
||||
[]<class Rep, class Period>(std::type_identity<std::chrono::duration<Rep, Period>>) {
|
||||
|
||||
}(std::type_identity<std::remove_cvref_t<T>>());
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_systime_c = requires {
|
||||
[]<class DT>(std::type_identity<std::chrono::sys_time<DT>>) {}(std::type_identity<std::remove_cvref_t<T>>());
|
||||
};
|
||||
|
||||
|
||||
template <typename R>
|
||||
concept mcc_time_duration_output_range_c =
|
||||
std::ranges::output_range<R, std::ranges::range_value_t<R>> && mcc_time_duration_c<std::ranges::range_value_t<R>>;
|
||||
|
||||
/* a callable concept and its signature traits */
|
||||
|
||||
template <typename T>
|
||||
concept mcc_is_callable = std::is_function_v<T> || (std::is_object_v<T> && requires(T) { &T::operator(); });
|
||||
|
||||
template <typename T>
|
||||
concept mcc_callable_c = std::is_function_v<T> || (std::is_object_v<T> && requires(T) { &T::operator(); });
|
||||
|
||||
|
||||
// helper classes for callable signature deducing
|
||||
template <typename... Ts>
|
||||
struct mcc_func_traits_helper_t;
|
||||
|
||||
template <typename R>
|
||||
struct mcc_func_traits_helper_t<R> {
|
||||
using ret_t = R;
|
||||
using args_t = std::tuple<>;
|
||||
using arg1_t = void;
|
||||
static constexpr size_t arity = 0;
|
||||
};
|
||||
|
||||
template <typename R, typename Arg, typename... Args>
|
||||
struct mcc_func_traits_helper_t<R, Arg, Args...> {
|
||||
using ret_t = R;
|
||||
using args_t = std::tuple<Arg, Args...>;
|
||||
using arg1_t = Arg;
|
||||
static constexpr size_t arity = sizeof...(Args) + 1;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct mcc_func_traits {
|
||||
// use of an empty struct here to match std::invoke_result behaivior (at least of GCC)
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct mcc_func_traits<R (*)(Args...)> : mcc_func_traits_helper_t<R, Args...> {
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct mcc_func_traits<R(Args...)> : mcc_func_traits_helper_t<R, Args...> {
|
||||
};
|
||||
|
||||
template <typename C, typename R, typename... Args>
|
||||
struct mcc_func_traits<R (C::*)(Args...)> : mcc_func_traits_helper_t<R, Args...> {
|
||||
};
|
||||
|
||||
template <typename C, typename R, typename... Args>
|
||||
struct mcc_func_traits<R (C::*)(Args...) const> : mcc_func_traits_helper_t<R, Args...> {
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
requires mcc_is_callable<F>
|
||||
struct mcc_func_traits<F> : mcc_func_traits<decltype(&F::operator())> {
|
||||
};
|
||||
|
||||
// reference/const ref and rvalue helpers
|
||||
template <typename F>
|
||||
struct mcc_func_traits<F&> : mcc_func_traits<F> {
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct mcc_func_traits<const F&> : mcc_func_traits<F> {
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct mcc_func_traits<F&&> : mcc_func_traits<F> {
|
||||
};
|
||||
|
||||
// type of the returned value
|
||||
template <typename T>
|
||||
using mcc_retval_t = typename mcc_func_traits<T>::ret_t;
|
||||
|
||||
// type of the first argument of callable
|
||||
template <typename T>
|
||||
using mcc_func_arg1_t = typename mcc_func_traits<T>::arg1_t;
|
||||
|
||||
// type of the N-th argument of callable
|
||||
// NOTE: starts from 1 not from 0!!!
|
||||
template <typename T, size_t N = 1>
|
||||
using mcc_func_argN_t = std::conditional_t<N >= mcc_func_traits<T>::arity,
|
||||
std::tuple_element_t<N - 1, typename mcc_func_traits<T>::args_t>,
|
||||
void>;
|
||||
|
||||
|
||||
// std::array
|
||||
template <typename T>
|
||||
concept mcc_array_c = requires(T t) {
|
||||
[]<typename VT, size_t N>(std::array<VT, N>) {
|
||||
|
||||
}(t);
|
||||
};
|
||||
|
||||
|
||||
// non-const lvalue reference, constructible from CtorArgTs (an output argument of function)
|
||||
template <typename T, typename... CtorArgTs>
|
||||
concept mcc_output_arg_c = !std::is_const_v<std::remove_reference_t<T>> && std::is_lvalue_reference_v<T> &&
|
||||
std::constructible_from<std::remove_reference_t<T>, CtorArgTs...>;
|
||||
|
||||
|
||||
|
||||
// std::tuple or std::pair
|
||||
template <typename T>
|
||||
concept mcc_tuple_c = requires {
|
||||
requires requires {
|
||||
[]<typename... Ts>(std::type_identity<std::tuple<Ts...>>) {}(std::type_identity<std::remove_cvref_t<T>>());
|
||||
} || requires {
|
||||
[]<typename T1, typename T2>(std::type_identity<std::pair<T1, T2>>) {
|
||||
}(std::type_identity<std::remove_cvref_t<T>>());
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_nonconst_lvref = std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>;
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept mcc_error_c = std::convertible_to<T, bool> || requires(const T t) {
|
||||
{ t.operator bool() };
|
||||
};
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
// compile-time hash for type
|
||||
// (from https://stackoverflow.com/questions/56292104/hashing-types-at-compile-time-in-c17-c2a)
|
||||
// WARNING: it does not work for unnamed struct!!!
|
||||
template <typename T>
|
||||
static consteval size_t Hash()
|
||||
{
|
||||
size_t result{};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
for (const auto& c : __FUNCSIG__)
|
||||
#else // GCC and clang
|
||||
for (const auto& c : __PRETTY_FUNCTION__)
|
||||
#endif
|
||||
(result ^= c) <<= 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
template <typename T>
|
||||
static constexpr size_t mcc_type_hash = details::Hash<T>();
|
||||
|
||||
static constexpr size_t mcc_hash_combine(size_t lhs, size_t rhs)
|
||||
{
|
||||
constexpr size_t v_const = sizeof(size_t) >= 8 ? 0x517cc1b727220a95 : 0x9e3779b9;
|
||||
|
||||
lhs ^= rhs + v_const + (lhs << 6) + (lhs >> 2);
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
static constexpr size_t mcc_type_pair_hash()
|
||||
{
|
||||
return mcc_hash_combine(mcc_type_hash<T1>, mcc_type_hash<T2>);
|
||||
}
|
||||
|
||||
} // namespace mcc::traits
|
||||
843
include/mcc/mcc_utils.h
Normal file
843
include/mcc/mcc_utils.h
Normal file
@@ -0,0 +1,843 @@
|
||||
#pragma once
|
||||
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <expected>
|
||||
#include <format>
|
||||
#include <numbers>
|
||||
#include <ranges>
|
||||
#include <regex>
|
||||
|
||||
#include "mcc_constants.h"
|
||||
#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]+ *"};
|
||||
|
||||
|
||||
constexpr 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>
|
||||
constexpr 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);
|
||||
}
|
||||
|
||||
constexpr 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;
|
||||
}
|
||||
|
||||
[[maybe_unused]] 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;
|
||||
}
|
||||
|
||||
// round to given precision of arcseconds/seconds
|
||||
degs = std::round(degs * 3600.0 * term) / term / 3600.0;
|
||||
|
||||
if constexpr (NORM) {
|
||||
if (hms) {
|
||||
if (isEqual(degs, 24.0)) {
|
||||
degs = 0.0;
|
||||
}
|
||||
} else {
|
||||
if (isEqual(degs, 360.0)) {
|
||||
degs = 0.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) {
|
||||
if (!isEqual(d, 0.0) || !isEqual(m, 0.0) || !isEqual(s, 0.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;
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
static constexpr size_t FNV1aHash(const R& r)
|
||||
{
|
||||
static_assert(sizeof(size_t) == 8 || sizeof(size_t) == 4, "ONLY FOR 32 or 64-bit size_t!!!");
|
||||
|
||||
size_t hash = 0, prime = 0;
|
||||
if constexpr (sizeof(size_t) == 8) { // 64-bit
|
||||
prime = 1099511628211UL;
|
||||
hash = 14695981039346656037UL;
|
||||
} else if constexpr (sizeof(size_t) == 4) { // 32-bit
|
||||
prime = 16777619;
|
||||
hash = 2166136261;
|
||||
}
|
||||
|
||||
for (const char& ch : r) {
|
||||
hash ^= ch;
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static constexpr size_t FNV1aHash(std::forward_iterator auto begin, std::sentinel_for<decltype(begin)> auto end)
|
||||
requires std::same_as<std::remove_cv_t<std::iter_value_t<decltype(begin)>>, char>
|
||||
{
|
||||
static_assert(sizeof(size_t) == 8 || sizeof(size_t) == 4, "ONLY FOR 32 or 64-bit size_t!!!");
|
||||
|
||||
size_t hash = 0, prime = 0;
|
||||
if constexpr (sizeof(size_t) == 8) { // 64-bit
|
||||
prime = 1099511628211UL;
|
||||
hash = 14695981039346656037UL;
|
||||
} else if constexpr (sizeof(size_t) == 4) { // 32-bit
|
||||
prime = 16777619;
|
||||
hash = 2166136261;
|
||||
}
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
hash ^= *it;
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
class MccSimpleDeserializer
|
||||
{
|
||||
public:
|
||||
static constexpr std::string_view RANGE_DELIM_SEQ = ",";
|
||||
|
||||
MccSimpleDeserializer() : _rangeDelim(RANGE_DELIM_SEQ) {}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
MccSimpleDeserializer(R&& r) : MccSimpleDeserializer()
|
||||
{
|
||||
setRangeDelim(std::forward<R>(r));
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
MccSimpleDeserializer& setRangeDelim(R&& r)
|
||||
{
|
||||
if (std::ranges::size(std::forward<R>(r))) {
|
||||
_rangeDelim.clear();
|
||||
std::ranges::copy(std::forward<R>(r), std::back_inserter(_rangeDelim));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <traits::mcc_output_char_range R>
|
||||
R getRangeDelim() const
|
||||
{
|
||||
R r;
|
||||
|
||||
std::ranges::copy(_rangeDelim, std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string getRangeDelim() const
|
||||
{
|
||||
return getRangeDelim<std::string>();
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range IR, typename VT>
|
||||
std::error_code operator()(IR&& bytes, VT& value) const
|
||||
{
|
||||
std::error_code ret{};
|
||||
|
||||
if constexpr (std::is_arithmetic_v<VT>) {
|
||||
auto v = mcc::utils::numFromStr<VT>(trimSpaces(bytes));
|
||||
if (!v.has_value()) {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
value = v.value();
|
||||
} else if constexpr (mcc::traits::mcc_output_char_range<VT>) {
|
||||
VT r;
|
||||
if constexpr (traits::mcc_array_c<VT>) {
|
||||
size_t N =
|
||||
std::ranges::size(r) <= std::ranges::size(bytes) ? std::ranges::size(r) : std::ranges::size(bytes);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
r[i] = bytes[i];
|
||||
}
|
||||
if (std::ranges::size(r) > N) {
|
||||
for (size_t i = N; i < std::ranges::size(r); ++i) {
|
||||
r[i] = '\0';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::ranges::copy(bytes, std::back_inserter(r));
|
||||
}
|
||||
value = r;
|
||||
} else if constexpr (std::ranges::range<VT>) {
|
||||
using el_t = std::ranges::range_value_t<VT>;
|
||||
|
||||
if constexpr (std::is_reference_v<el_t> || std::is_const_v<el_t>) { // no reference or constants allowed
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
VT r;
|
||||
el_t elem;
|
||||
size_t i = 0;
|
||||
|
||||
if (trimSpaces(bytes).size()) {
|
||||
auto els = std::views::split(bytes, _rangeDelim);
|
||||
|
||||
for (auto const& el : els) {
|
||||
ret = (*this)(std::string_view(el), elem);
|
||||
if (!ret) {
|
||||
if constexpr (traits::mcc_array_c<VT>) {
|
||||
if (i < std::ranges::size(r)) {
|
||||
r[i] = elem;
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
std::back_inserter(r) = elem;
|
||||
}
|
||||
} else {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = r;
|
||||
} else if constexpr (mcc::traits::mcc_time_duration_c<VT>) {
|
||||
typename VT::rep vd;
|
||||
|
||||
ret = (*this)(trimSpaces(bytes), vd);
|
||||
if (!ret) {
|
||||
value = VT{vd};
|
||||
}
|
||||
} else {
|
||||
ret = std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string _rangeDelim;
|
||||
};
|
||||
|
||||
|
||||
/* key-value pair holder */
|
||||
|
||||
// to follow std::variant requirements (not references, not array, not void)
|
||||
template <typename T>
|
||||
concept variant_valid_type_c = requires { !std::is_array_v<T> && !std::is_void_v<T> && !std::is_reference_v<T>; };
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept keyvalue_record_c = requires(T t) {
|
||||
requires std::same_as<decltype(t.key), std::string_view>;
|
||||
requires variant_valid_type_c<decltype(t.value)>;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
concept keyvalue_desc_c = requires(T t) { []<keyvalue_record_c... Ts>(std::tuple<Ts...>) {}(t); };
|
||||
|
||||
|
||||
template <keyvalue_desc_c DESCR_T>
|
||||
class KeyValueHolder
|
||||
{
|
||||
public:
|
||||
static constexpr std::string_view COMMENT_SEQ{"#"};
|
||||
static constexpr std::string_view KEY_VALUE_DELIM{"="};
|
||||
static constexpr std::string_view VALUE_ARRAY_DELIM{","};
|
||||
|
||||
inline static auto defaultDeserializeFunc = []<typename VT>(this auto&& self, std::string_view str, VT& value) {
|
||||
std::error_code ret{};
|
||||
|
||||
if constexpr (std::is_arithmetic_v<VT>) {
|
||||
auto v = mcc::utils::numFromStr<VT>(trimSpaces(str));
|
||||
if (!v.has_value()) {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
value = v.value();
|
||||
} else if constexpr (mcc::traits::mcc_output_char_range<VT>) {
|
||||
VT r;
|
||||
std::ranges::copy(str, std::back_inserter(r));
|
||||
value = r;
|
||||
} else if constexpr (std::ranges::range<VT>) {
|
||||
using el_t = std::ranges::range_value_t<VT>;
|
||||
|
||||
if constexpr (std::is_reference_v<el_t> || std::is_const_v<el_t>) { // no reference or constants allowed
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
VT r;
|
||||
el_t elem;
|
||||
|
||||
auto els = std::views::split(str, VALUE_ARRAY_DELIM);
|
||||
|
||||
for (auto const& el : els) {
|
||||
ret = std::forward<decltype(self)>(self)(std::string_view(el), elem);
|
||||
if (!ret) {
|
||||
std::back_inserter(r) = elem;
|
||||
} else {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
}
|
||||
|
||||
value = r;
|
||||
} else if constexpr (mcc::traits::mcc_time_duration_c<VT>) {
|
||||
typename VT::rep vd;
|
||||
|
||||
ret = std::forward<decltype(self)>(self)(trimSpaces(str), vd);
|
||||
if (!ret) {
|
||||
value = VT{vd};
|
||||
}
|
||||
} else {
|
||||
ret = std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
KeyValueHolder(DESCR_T desc) : _keyValue(desc)
|
||||
{
|
||||
[this]<size_t... I>(std::index_sequence<I...>) {
|
||||
((_hashes[I] = FNV1aHash(std::get<I>(_keyValue).key)), ...);
|
||||
}(std::make_index_sequence<std::tuple_size_v<DESCR_T>>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::expected<T, std::error_code> getValue(std::string_view key) const
|
||||
{
|
||||
T v;
|
||||
auto err = forKey(key, [&v]<typename VT>(const VT& val) {
|
||||
if constexpr (std::convertible_to<VT, T>) {
|
||||
v = val;
|
||||
return std::error_code();
|
||||
} else {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
});
|
||||
|
||||
if (err) {
|
||||
return std::unexpected(err);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::error_code setValue(std::string_view key, const T& value)
|
||||
{
|
||||
return forKey(key, [value]<typename VT>(VT& val) {
|
||||
if constexpr (std::convertible_to<T, VT>) {
|
||||
val = value;
|
||||
return std::error_code();
|
||||
} else if constexpr (std::constructible_from<VT, T>) {
|
||||
val = VT(value);
|
||||
return std::error_code();
|
||||
} else {
|
||||
return std::make_error_code(std::errc::invalid_argument);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
template <std::ranges::contiguous_range R, std::ranges::input_range RecDelimT = std::string_view>
|
||||
std::error_code fromCharRange(const R& buffer, RecDelimT rec_delim = std::string_view("\n"))
|
||||
{
|
||||
// return fromCharRange(buffer, KeyValueHolder::defaultDeserializeFunc, std::move(rec_delim));
|
||||
return fromCharRange(buffer, MccSimpleDeserializer{}.setRangeDelim(VALUE_ARRAY_DELIM), std::move(rec_delim));
|
||||
}
|
||||
|
||||
template <std::ranges::contiguous_range R,
|
||||
typename DeserFuncT,
|
||||
std::ranges::input_range RecDelimT = std::string_view>
|
||||
std::error_code fromCharRange(const R& buffer,
|
||||
DeserFuncT&& deser_func,
|
||||
RecDelimT rec_delim = std::string_view("\n"))
|
||||
{
|
||||
// static_assert(mcc::traits::mcc_callable_c<std::decay_t<DeserFuncT>>, "!!!!!!!");
|
||||
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) { // char*, const char*
|
||||
if constexpr (std::is_array_v<std::decay_t<RecDelimT>>) {
|
||||
return fromCharRange(std::string_view{buffer}, std::forward<DeserFuncT>(deser_func),
|
||||
std::string_view(rec_delim));
|
||||
} else {
|
||||
return fromCharRange(std::string_view{buffer}, std::forward<DeserFuncT>(deser_func),
|
||||
std::move(rec_delim));
|
||||
}
|
||||
} else {
|
||||
if constexpr (std::is_array_v<std::decay_t<RecDelimT>>) {
|
||||
return fromCharRange(buffer, std::forward<DeserFuncT>(deser_func), std::string_view(rec_delim));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::error_code ec{};
|
||||
std::string_view rec, key, value;
|
||||
|
||||
|
||||
auto recs = std::views::split(buffer, std::move(rec_delim));
|
||||
for (auto const& el : recs) {
|
||||
rec = mcc::utils::trimSpaces(el, TrimType::TRIM_LEFT);
|
||||
|
||||
if (rec.size()) {
|
||||
auto found = std::ranges::search(rec, COMMENT_SEQ);
|
||||
if (found.begin() != rec.end()) { // there was the comment sequence in record
|
||||
rec = std::string_view(rec.begin(), found.begin());
|
||||
}
|
||||
|
||||
if (rec.size()) {
|
||||
found = std::ranges::search(rec, KEY_VALUE_DELIM);
|
||||
if (found.begin() != rec.begin()) { // ignore an empty key
|
||||
key = trimSpaces(std::string_view(rec.begin(), found.begin()), TrimType::TRIM_RIGHT);
|
||||
value = std::string_view(found.end(), rec.end());
|
||||
|
||||
ec = forKey(key, [value, &deser_func]<typename VT>(VT& v) { return deser_func(value, v); });
|
||||
}
|
||||
} // just comment string starting from the beginning, just skip it (no error)
|
||||
|
||||
} // empty record, just skip it (no error)
|
||||
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
protected:
|
||||
DESCR_T _keyValue;
|
||||
|
||||
std::array<size_t, std::tuple_size_v<DESCR_T>> _hashes;
|
||||
|
||||
|
||||
//
|
||||
// NOTE: deduced this is needed here to use "forKey" method in getter and setter (const and non-const contexts)!!!
|
||||
//
|
||||
|
||||
std::error_code forKey(this auto&& self, std::string_view key, auto&& func)
|
||||
{
|
||||
return std::forward<decltype(self)>(self).template forHash<0>(FNV1aHash(key),
|
||||
std::forward<decltype(func)>(func));
|
||||
}
|
||||
|
||||
|
||||
template <size_t I = 0>
|
||||
std::error_code forHash(this auto&& self, size_t hash, auto&& func)
|
||||
{
|
||||
if constexpr (I < std::tuple_size_v<DESCR_T>) {
|
||||
if (hash == std::forward<decltype(self)>(self)._hashes[I]) {
|
||||
return std::forward<decltype(func)>(func)(
|
||||
std::get<I>(std::forward<decltype(self)>(self)._keyValue).value);
|
||||
} else {
|
||||
return std::forward<decltype(self)>(self).template forHash<I + 1>(hash,
|
||||
std::forward<decltype(func)>(func));
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_error_code(std::errc::argument_out_of_domain);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* a base class for some sequence of elements with delimiter */
|
||||
|
||||
struct mcc_elem_sequence_with_delim_t {
|
||||
virtual ~mcc_elem_sequence_with_delim_t() = default;
|
||||
|
||||
// set delimiter for elements of the range-type serialized value
|
||||
template <traits::mcc_input_char_range R>
|
||||
void setSeqDelimiter(R const& delim)
|
||||
{
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) {
|
||||
setSeqDelimiter(std::string_view{delim});
|
||||
} else {
|
||||
_seqDelimiter.clear();
|
||||
std::ranges::copy(delim, std::back_inserter(_seqDelimiter));
|
||||
}
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R getSeqDelimiter() const
|
||||
{
|
||||
if constexpr (std::same_as<R, decltype(_seqDelimiter)>) {
|
||||
return _seqDelimiter;
|
||||
} else if constexpr (std::ranges::view<R>) {
|
||||
return R{_seqDelimiter.begin(), _seqDelimiter.end()};
|
||||
} else {
|
||||
R r;
|
||||
std::ranges::copy(_seqDelimiter, std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getSeqDelimiter() const
|
||||
{
|
||||
return getSeqDelimiter<std::string>();
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_input_char_range R>
|
||||
void setElemDelimiter(R const& delim)
|
||||
{
|
||||
if constexpr (std::is_array_v<std::decay_t<R>>) {
|
||||
setElemDelimiter(std::string_view{delim});
|
||||
} else {
|
||||
_elementDelimiter.clear();
|
||||
std::ranges::copy(delim, std::back_inserter(_seqDelimiter));
|
||||
}
|
||||
}
|
||||
|
||||
template <traits::mcc_view_or_output_char_range R>
|
||||
R getElemDelimiter() const
|
||||
{
|
||||
if constexpr (std::same_as<R, decltype(_seqDelimiter)>) {
|
||||
return _elementDelimiter;
|
||||
} else if constexpr (std::ranges::view<R>) {
|
||||
return R{_elementDelimiter.begin(), _elementDelimiter.end()};
|
||||
} else {
|
||||
R r;
|
||||
std::ranges::copy(_elementDelimiter, std::back_inserter(r));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getElemDelimiter() const
|
||||
{
|
||||
return getElemDelimiter<std::string>();
|
||||
}
|
||||
|
||||
protected:
|
||||
mcc_elem_sequence_with_delim_t() = default;
|
||||
|
||||
// delimiter for sequence of serializing values
|
||||
std::string _seqDelimiter{MCC_DEFAULT_SEQ_DELIMITER};
|
||||
|
||||
// delimiter for aggregative (multi-element) serializing value
|
||||
std::string _elementDelimiter{MCC_DEFAULT_ELEM_DELIMITER};
|
||||
};
|
||||
|
||||
} // namespace mcc::utils
|
||||
Reference in New Issue
Block a user