mountcontrol/mcc/mcc_defaults.h
2025-11-19 12:02:43 +03:00

1226 lines
37 KiB
C++

#pragma once
/* MOUNT CONTROL COMPONENTS LIBRARY */
/* MCC LIBRARY DEFAULT IMPLEMENTATION OF SOME CLASSES */
// #include <compare>
#include "mcc_generics.h"
namespace mcc
{
/* DEFAULT TIME-RELATED CLASSES */
typedef std::chrono::system_clock::time_point MccTimePoint;
typedef std::chrono::duration<double> MccTimeDuration; // seconds as floating-point value
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()};
/* DEFAULT JULIAN DAY CLASS */
struct MccJulianDay {
static constexpr double MJD0 = 2400000.5;
MccJulianDay() = default;
MccJulianDay(double jd) : mjd(jd - MJD0) {}
constexpr operator double() const
{
return MccJulianDay::MJD0 + mjd;
}
MccJulianDay& operator=(double jd)
{
mjd = jd - MJD0;
return *this;
}
double MJD() const
{
return mjd;
}
constexpr auto operator<=>(const MccJulianDay&) const = default;
constexpr auto operator<=>(double v) const
{
return v <=> (MccJulianDay::MJD0 + mjd);
};
protected:
double mjd{51544.5}; // J2000.0
};
/* DEFAULT CELESTIAL COORDINATES EPOCH CLASS */
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) {}
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>, "!!!");
/* DEFAULT CELESTIAL POINT CLASS */
template <mcc_angle_c CoordT>
struct MccGenericCelestialPoint {
typedef CoordT coord_t;
MccCoordPairKind pair_kind{MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
// MccTimePoint time_point{std::chrono::sys_days(std::chrono::year_month_day{std::chrono::January / 1 / 2000}) +
// std::chrono::hours(12)}; // J2000.0
MccTimePoint time_point{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)}; // J2000.0 UTC
coord_t X{}, Y{};
};
typedef MccGenericCelestialPoint<double> MccCelestialPoint;
template <mcc_angle_c CoordT>
struct MccGenericEqtHrzCoords : MccGenericCelestialPoint<CoordT> {
using typename MccGenericCelestialPoint<CoordT>::coord_t;
using MccGenericCelestialPoint<CoordT>::time_point;
coord_t RA_APP{}, DEC_APP{}, HA{}, AZ{}, ZD{}, ALT{};
};
typedef MccGenericEqtHrzCoords<MccCelestialPoint::coord_t> MccEqtHrzCoords;
template <mcc_angle_c CoordT>
struct MccGenericPointingTarget : MccGenericEqtHrzCoords<CoordT> {
using typename MccGenericEqtHrzCoords<CoordT>::coord_t;
coord_t RA_ICRS{}, DEC_ICRS{};
};
typedef MccGenericPointingTarget<MccCelestialPoint::coord_t> MccPointingTarget;
template <mcc_angle_c CoordT>
struct MccGenericPCMResult {
CoordT pcmX, pcmY;
};
typedef MccGenericPCMResult<double> MccPCMResult;
/* DEFAULT TELEMETRY DATA CLASS */
template <mcc_angle_c CoordT>
struct MccGenericTelemetryData : MccGenericEqtHrzCoords<CoordT> {
using typename MccGenericEqtHrzCoords<CoordT>::coord_t;
MccJulianDay JD;
coord_t LST; // local apparent sideral time
MccGenericPointingTarget<coord_t> target{};
coord_t speedX, speedY;
coord_t pcmX, pcmY;
coord_t refCorr;
coord_t EO;
};
typedef MccGenericTelemetryData<MccCelestialPoint::coord_t> MccTelemetryData;
template <mcc_ccte_c CCTE_T, mcc_hardware_c HARDWARE_T, mcc_PCM_c PCM_T>
struct MccPositionControls : CCTE_T, HARDWARE_T, PCM_T {
virtual ~MccPositionControls() = default;
};
/* JUST CHECK FOR CONCEPT CONSISTENCY */
static_assert(mcc_julday_c<MccJulianDay>, "");
static_assert(mcc_celestial_point_c<MccCelestialPoint>, "");
static_assert(mcc_pointing_target_coord_c<MccGenericPointingTarget<double>>, "");
static_assert(mcc_telemetry_data_c<MccTelemetryData>, "");
/* DEFAULT SERIALIZER/DESERIALIZER FOR COORDINATE-RELATED CLASSES */
enum class MccCoordinateConvErrorCode : int {
ERROR_OK,
ERROR_ARG_LEN,
ERROR_NONNUM_ARG,
ERROR_NONSGM_ARG,
ERROR_INVALID_CPAIR,
ERROR_TIMEPOINT,
ERROR_INVALID_COORD_FMT,
ERROR_INVALID_COORD_PREC
};
class MccCoordinateConvErrorCategory : public std::error_category
{
public:
const char* name() const noexcept
{
return "MCC-COORD-CONV";
}
std::string message(int ec) const
{
MccCoordinateConvErrorCode err = static_cast<MccCoordinateConvErrorCode>(ec);
switch (err) {
case MccCoordinateConvErrorCode::ERROR_OK:
return "OK";
case MccCoordinateConvErrorCode::ERROR_ARG_LEN:
return "invalid argument length or number of argument elements";
case MccCoordinateConvErrorCode::ERROR_NONNUM_ARG:
return "non-numeric or out-of-range argument";
case MccCoordinateConvErrorCode::ERROR_NONSGM_ARG:
return "non-sexagesimal argument";
case MccCoordinateConvErrorCode::ERROR_INVALID_CPAIR:
return "invalid coordinate pair name";
case MccCoordinateConvErrorCode::ERROR_TIMEPOINT:
return "invalid time point";
case MccCoordinateConvErrorCode::ERROR_INVALID_COORD_FMT:
return "invalid coordinate formatter string";
case MccCoordinateConvErrorCode::ERROR_INVALID_COORD_PREC:
return "invalid coordinate formatter precision string";
defaut:
return "unknown";
};
return "unknown";
}
static const MccCoordinateConvErrorCategory& get()
{
static const MccCoordinateConvErrorCategory constInst;
return constInst;
}
};
} // namespace mcc
namespace std
{
template <>
class is_error_code_enum<mcc::MccCoordinateConvErrorCode> : public true_type
{
};
} // namespace std
namespace mcc
{
inline std::error_code make_error_code(mcc::MccCoordinateConvErrorCode ec)
{
return std::error_code(static_cast<int>(ec), mcc::MccCoordinateConvErrorCategory::get());
}
static constexpr std::string_view MccCoordinateDefaultDelimiter{","};
// base class
class MccCoordinateDeserializer
{
public:
MccCoordinateDeserializer() = default;
template <traits::mcc_input_char_range R>
MccCoordinateDeserializer(R&& delim) : MccCoordinateDeserializer()
{
setDelimiter(std::forward<R>(delim));
}
virtual ~MccCoordinateDeserializer() = default;
template <traits::mcc_input_char_range R>
void setDelimiter(R&& delim)
{
if constexpr (std::is_pointer_v<std::decay_t<R>>) {
setDelimiter(std::string_view(delim));
} else {
_delimiter.clear();
std::ranges::copy(std::forward<R>(delim), std::back_inserter(_delimiter));
}
}
template <std::derived_from<MccCoordinateDeserializer> SelfT, traits::mcc_input_char_range IR, typename VT>
std::error_code operator()(this SelfT&& self, IR&& bytes, VT& value)
{
if constexpr (std::same_as<std::remove_cvref_t<SelfT>, MccCoordinateDeserializer>) {
static_assert(false, "!!!!!");
} else {
return std::forward<SelfT>(self)(std::forward<IR>(bytes), value);
}
}
protected:
std::string _delimiter{MccCoordinateDefaultDelimiter};
template <traits::mcc_input_char_range IR>
std::vector<std::string_view> splitToElements(IR&& bytes)
{
std::vector<std::string_view> res;
auto els = std::views::split(std::forward<IR>(bytes), _delimiter);
for (auto const& el : els) {
std::back_inserter(res) = std::string_view(el.begin(), el.end());
}
return res;
}
template <traits::mcc_input_char_range IR, mcc_celestial_point_c T>
std::error_code parseTimePoint(const IR& bytes, T& cp)
{
MccCelestialCoordEpoch cep;
bool ok = cep.fromCharRange(bytes);
if (!ok) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_TIMEPOINT;
}
mcc_tp2tp(cep.UTC(), cp.time_point);
return {};
}
template <traits::mcc_input_char_range IR>
std::error_code parseHourRepr(const IR& bytes, double& ang_value)
{
std::optional<double> ang;
ang = mcc::utils::parsAngleString(bytes, true);
if (ang) {
ang_value = MccAngle(ang.value(), MccDegreeTag{});
} else {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_NONNUM_ARG;
}
return {};
}
template <traits::mcc_input_char_range IR>
std::error_code parseDegreeRepr(const IR& bytes, double& ang_value)
{
std::optional<double> ang;
ang = mcc::utils::parsAngleString(bytes);
if (ang) {
ang_value = MccAngle(ang.value(), MccDegreeTag{});
} else {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_NONNUM_ARG;
}
return {};
}
};
// base class
class MccCoordinateSerializer
{
public:
enum SerializedCoordFormat {
CFMT_DEGREES, // floating-point representation in degrees
CFMT_SGM // sexagesimal representation: HH:MM:SS.SSSSSS (hours:minutes:seconds) or 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
};
constexpr MccCoordinateSerializer() = default;
constexpr MccCoordinateSerializer(SerializedCoordFormat fmt, SexagesimalCoordPrec prec)
{
setFormat(fmt);
setPrecision(std::move(prec));
}
virtual ~MccCoordinateSerializer() = default;
void setFormat(SerializedCoordFormat fmt)
{
_currentFormat = fmt;
}
void setPrecision(SexagesimalCoordPrec prec)
{
_currentPrec = std::move(prec);
}
template <traits::mcc_input_char_range R>
void setDelimiter(R&& delim)
{
if constexpr (std::is_pointer_v<std::decay_t<R>>) {
setDelimiter(std::string_view(delim));
} else {
_delimiter.clear();
std::ranges::copy(std::forward<R>(delim), std::back_inserter(_delimiter));
}
}
template <std::derived_from<MccCoordinateSerializer> SelfT, typename T, traits::mcc_output_char_range OR>
void operator()(this SelfT&& self, const T& value, OR& bytes)
{
if constexpr (std::same_as<std::remove_cvref_t<SelfT>, MccCoordinateSerializer>) {
static_assert(false, "!!!!!");
} else {
std::forward<SelfT>(self)(value, bytes);
}
}
protected:
SerializedCoordFormat _currentFormat{CFMT_DEGREES};
SexagesimalCoordPrec _currentPrec{.hour_prec = 2, .deg_prec = 1};
std::string _delimiter{MccCoordinateDefaultDelimiter};
template <mcc_celestial_point_c T, traits::mcc_output_char_range OR>
void serializePairKindTimePoint(const T& value, OR& bytes)
{
std::format_to(std::back_inserter(bytes), "{0:}{1:}{2:%F}T{2:%T}", MccCoordPairKindToStr(value.pair_kind),
_delimiter, value.time_point);
}
template <traits::mcc_output_char_range OR, std::convertible_to<double>... Ts>
void toDegrees(OR& bytes, double angle, const Ts&... angles)
{
std::format_to(std::back_inserter(bytes), "{}", MccAngle(angle).degrees());
if constexpr (sizeof...(Ts)) {
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toDegrees(bytes, angles...);
}
}
template <traits::mcc_output_char_range OR, std::convertible_to<double>... Ts>
void toSexagesimalHour(OR& bytes, double angle, const Ts&... angles)
{
std::format_to(std::back_inserter(bytes), "{}", MccAngle(angle).sexagesimal(true, _currentPrec.hour_prec));
if constexpr (sizeof...(Ts)) {
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalHour(bytes, angles...);
}
}
template <traits::mcc_output_char_range OR, std::convertible_to<double>... Ts>
void toSexagesimalDeg(OR& bytes, double angle, const Ts&... angles)
{
std::format_to(std::back_inserter(bytes), "{}", MccAngle(angle).sexagesimal(false, _currentPrec.deg_prec));
if constexpr (sizeof...(Ts)) {
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, angles...);
}
}
};
class MccCelestialPointSerializer : public MccCoordinateSerializer
{
public:
using MccCoordinateSerializer::MccCoordinateSerializer;
template <mcc_celestial_point_c T, traits::mcc_output_char_range OR>
void operator()(const T& value, OR& bytes)
{
if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) {
// std::format_to(std::back_inserter(bytes), "{}{}{}", MccAngle(value.X).degrees(), _delimiter,
// MccAngle(value.Y).degrees());
toDegrees(bytes, value.X, value.Y);
} else {
switch (value.pair_kind) {
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
case MccCoordPairKind::COORDS_KIND_RADEC_APP:
case MccCoordPairKind::COORDS_KIND_HADEC_APP:
// std::format_to(std::back_inserter(bytes), "{}{}",
// MccAngle(value.X).sexagesimal(true, _currentPrec.hour_prec), _delimiter);
toSexagesimalHour(bytes, value.X);
break;
default:
// std::format_to(std::back_inserter(bytes), "{}{}",
// MccAngle(value.X).sexagesimal(false, _currentPrec.deg_prec), _delimiter);
toSexagesimalDeg(bytes, value.X);
}
// std::format_to(std::back_inserter(bytes), "{}",
// MccAngle(value.Y).sexagesimal(false, _currentPrec.deg_prec));
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.Y);
}
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
serializePairKindTimePoint(value, bytes);
}
};
class MccCelestialPointDeserializer : public MccCoordinateDeserializer
{
public:
using MccCoordinateDeserializer::MccCoordinateDeserializer;
template <traits::mcc_input_char_range IR, mcc_celestial_point_c T>
std::error_code operator()(IR&& bytes, T& value)
{
auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 2) { // at least coordinate pair must be given
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
}
MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
if (els.size() > 2) { // pair of coordinates and the pair kind
pt.pair_kind = MccCoordStrToPairKind(els[2]);
if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_INVALID_CPAIR;
}
}
if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
// J2000.0: 11:58:55.816 1 January 2000 UTC
auto tp = 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);
mcc_tp2tp(tp, pt.time_point);
} else {
if (els.size() > 3) { // coordinates epoch is given
auto err = parseTimePoint(els[3], pt);
if (err) {
return err;
}
} else { // no time point - use NOW
mcc_tp2tp(std::chrono::system_clock::now(), pt.time_point);
}
}
std::optional<double> ang1, ang2;
switch (pt.pair_kind) {
// if sexagesimal then hours:minutes:seconds form
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_APP:
case mcc::MccCoordPairKind::COORDS_KIND_HADEC_APP:
ang1 = mcc::utils::parsAngleString(els[0], true);
break;
default:
// if sexagesimal then degrees:arcminutes:arcseconds form
ang1 = mcc::utils::parsAngleString(els[0]);
}
if (!ang1) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_NONNUM_ARG;
}
ang2 = mcc::utils::parsAngleString(els[1]);
if (!ang2) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_NONNUM_ARG;
}
pt.X = MccAngle(ang1.value(), mcc::MccDegreeTag{});
pt.Y = MccAngle(ang2.value(), mcc::MccDegreeTag{});
mcc_copy_celestial_point(pt, &value);
return {};
}
};
class MccEqtHrzCoordsSerializer : public MccCoordinateSerializer
{
public:
using MccCoordinateSerializer::MccCoordinateSerializer;
template <mcc_eqt_hrz_coord_c T, traits::mcc_output_char_range OR>
void operator()(const T& value, OR& bytes)
{
// output format: RA, DEC, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal output X,Y coordinates will be interpretated
// according to value.pair_kind field
if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) {
toDegrees(bytes, value.RA_APP, value.DEC_APP, value.HA, value.AZ, value.ZD, value.ALT, value.X, value.Y);
} else if (_currentFormat == SerializedCoordFormat::CFMT_SGM) {
toSexagesimalHour(bytes, value.RA_APP);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.DEC_APP);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalHour(bytes, value.HA);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.AZ, value.ZD, value.ALT);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
switch (value.pair_kind) {
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
case MccCoordPairKind::COORDS_KIND_RADEC_APP:
case MccCoordPairKind::COORDS_KIND_HADEC_APP:
toSexagesimalHour(bytes, value.X);
break;
default:
toSexagesimalDeg(bytes, value.X);
}
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.Y);
} else {
// !!!!!
}
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
serializePairKindTimePoint(value, bytes);
}
};
class MccEqtHrzCoordsDeserializer : public MccCoordinateDeserializer
{
public:
using MccCoordinateDeserializer::MccCoordinateDeserializer;
template <traits::mcc_input_char_range IR, mcc_eqt_hrz_coord_c T>
std::error_code operator()(IR&& bytes, T& value)
{
// valid format: RA, DEC, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal input the X,Y coordinates will be interpretated
// according to value.pair_kind field
auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 10) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
}
MccEqtHrzCoords pt;
pt.pair_kind = MccCoordStrToPairKind(els[8]);
if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_INVALID_CPAIR;
}
auto err = parseTimePoint(els[9], pt);
if (err) {
return err;
}
err = parseHourRepr(els[0], pt.RA_APP);
if (err) {
return err;
}
err = parseDegreeRepr(els[1], pt.DEC_APP);
if (err) {
return err;
}
err = parseHourRepr(els[2], pt.HA);
if (err) {
return err;
}
err = parseDegreeRepr(els[4], pt.AZ);
if (err) {
return err;
}
err = parseDegreeRepr(els[5], pt.ZD);
if (err) {
return err;
}
err = parseDegreeRepr(els[6], pt.ALT);
if (err) {
return err;
}
switch (value.pair_kind) {
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
case MccCoordPairKind::COORDS_KIND_RADEC_APP:
case MccCoordPairKind::COORDS_KIND_HADEC_APP:
err = parseHourRepr(els[7], pt.X);
break;
default:
err = parseDegreeRepr(els[7], pt.X);
}
if (err) {
return err;
}
err = parseDegreeRepr(els[8], pt.Y);
if (err) {
return err;
}
mcc_copy_eqt_hrz_coord(pt, &value);
return {};
}
};
class MccPointingTargetSerializer : public MccCoordinateSerializer
{
public:
template <mcc_pointing_target_coord_c T, traits::mcc_output_char_range OR>
void operator()(const T& value, OR& bytes)
{
static MccEqtHrzCoordsSerializer eqhrz_ser;
// output format: RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal output X,Y coordinates will be interpretated
// according to value.pair_kind field
if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) {
toDegrees(bytes, value.RA_ICRS, value.DEC_ICRS);
} else if (_currentFormat == SerializedCoordFormat::CFMT_SGM) {
toSexagesimalHour(bytes, value.RA_ICRS);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.DEC_ICRS);
}
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
eqhrz_ser.setFormat(_currentFormat);
eqhrz_ser.setPrecision(_currentPrec);
eqhrz_ser(value, bytes);
// MccEqtHrzCoordsSerializer{}(value, bytes);
}
};
class MccPointingTargetDeserializer : public MccCoordinateDeserializer
{
public:
template <traits::mcc_input_char_range IR, mcc_pointing_target_coord_c T>
std::error_code operator()(IR&& bytes, T& value)
{
// valid format: RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal input the X,Y coordinates will be interpretated
// according to value.pair_kind field
auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 12) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
}
MccPointingTarget pt;
auto err = parseHourRepr(els[0], pt.RA_ICRS);
if (err) {
return err;
}
err = parseDegreeRepr(els[1], pt.DEC_ICRS);
if (err) {
return err;
}
err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[2].begin(), els[11].end()}, pt);
if (err) {
return err;
}
mcc_copy_pointing_target_coord(pt, &value);
return {};
}
};
class MccTelemetryDataSerializer : public MccCoordinateSerializer
{
public:
template <mcc_telemetry_data_c T, traits::mcc_output_char_range OR>
void operator()(const T& value, OR& bytes)
{
static MccEqtHrzCoordsSerializer eqhrz_ser;
static MccPointingTargetSerializer pt_ser;
// output format: <mount data>, speedX, speedY, pcmX, pcmY, refCorr (in arcsecs), <target data>
// RA-APP_mnt, DEC-APP_mnt, HA_mnt, AZ_mnt, ZD_mnt, ALT_mnt, X_mnt, Y_mnt, COO-PAIR_mnt, TIME-POINT_mnt,
// LST, EO, SPEED_X_mnt, SPEED_Y_mnt, PCM_X, PCM_Y, REFCORR,
// RA-ICRS_tag, DEC-ICRS_tag, RA-APP_tag, DEC-APP_tag, HA_tag, AZ_tag, ZD_tag, ALT_tag, X_tag, Y_tag,
// COO-PAIR_tag, TIME-POINT_tag
eqhrz_ser.setFormat(_currentFormat);
eqhrz_ser.setPrecision(_currentPrec);
eqhrz_ser(value, bytes);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalHour(bytes, value.LST);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalHour(bytes, value.EO);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
// '*3600.0' to express refraction correction in arcseconds!
// toDegrees(bytes, value.speedX, value.speedY, value.pcmX, value.pcmY, value.refCorr * 3600.0);
toSexagesimalDeg(bytes, value.speedX, value.speedY, value.pcmX, value.pcmY, value.refCorr);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
pt_ser.setFormat(_currentFormat);
pt_ser.setPrecision(_currentPrec);
pt_ser(value.target, bytes);
}
};
class MccTelemetryDataDeserializer : public MccCoordinateDeserializer
{
public:
using MccCoordinateDeserializer::MccCoordinateDeserializer;
template <traits::mcc_input_char_range IR, mcc_telemetry_data_c T>
std::error_code operator()(IR&& bytes, T& value)
{
// valid format: <mount data>, speedX, speedY, pcmX, pcmY, refCorr, <target data>
auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 29) {
// return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
}
MccTelemetryData tdata;
auto err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[0].begin(), els[9].end()}, tdata);
if (err) {
return err;
}
size_t idx = 10;
err = parseHourRepr(els[idx++], tdata.LST);
if (err) {
return err;
}
err = parseHourRepr(els[idx++], tdata.EO);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], tdata.speedX);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], tdata.speedY);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], tdata.pcmX);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], tdata.pcmY);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], tdata.refCorr);
if (err) {
return err;
}
err = MccPointingTargetDeserializer{}(std::string_view{els[idx].begin(), els.back().end()}, tdata.target);
if (err) {
return err;
}
mcc_copy_telemetry_data(tdata, &value);
return {};
}
};
} // namespace mcc