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:
Timur A. Fatkhullin
2026-02-15 20:43:51 +03:00
parent 6ffc8e3582
commit 66e5f37c74
33 changed files with 50 additions and 40 deletions

735
include/mcc/mcc_angle.h Normal file
View 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
View 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
View 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
View 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

View 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
View 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

View 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

View 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

View 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

View 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
View 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
View 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>, "");

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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
View 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
View 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

View 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

View 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

View 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

View 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
View 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
View 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
View 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
View 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