From ae91e7320c7b8beb9a0cf68559684c0f5812e889 Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Mon, 2 Feb 2026 02:36:23 +0300 Subject: [PATCH] ... --- CMakeLists.txt | 4 +- mcc_ccte_erfa.h | 8 +- mcc_concepts.h | 55 +-- mcc_constants.h | 7 + mcc_coordinate.h | 87 +++- mcc_deserializer.h | 279 +++++++++++++ mcc_error.h | 22 +- mcc_serializer.h | 872 ++++++++++++++++++++++++++++++++++++--- mcc_traits.h | 6 +- mcc_utils.h | 11 + tests/mcc_coord_test.cpp | 63 ++- 11 files changed, 1311 insertions(+), 103 deletions(-) create mode 100644 mcc_deserializer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34f9df5..3d70f90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,8 +135,8 @@ if (USE_BSPLINE_PCM) endif() set(MCC_SRC mcc_concepts.h mcc_constants.h mcc_epoch.h mcc_angle.h mcc_coordinate.h mcc_error.h - mcc_traits.h mcc_utils.h - mcc_ccte_iers.h mcc_ccte_iers_default.h mcc_ccte_erfa.h mcc_pzone.h mcc_pzone_container.h mcc_pcm.h mcc_telemetry.h) + mcc_traits.h mcc_utils.h mcc_ccte_iers.h mcc_ccte_iers_default.h mcc_ccte_erfa.h mcc_pzone.h + mcc_pzone_container.h mcc_pcm.h mcc_telemetry.h mcc_serializer.h) if (USE_SPDLOG) list(APPEND MCC_SRC mcc_spdlog.h) diff --git a/mcc_ccte_erfa.h b/mcc_ccte_erfa.h index 4d89d95..4d18fd0 100644 --- a/mcc_ccte_erfa.h +++ b/mcc_ccte_erfa.h @@ -208,6 +208,10 @@ public: 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 @@ -770,8 +774,8 @@ protected: // 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(); - // *az = MccAngle(a + std::numbers::pi).normalize(); + // *az = impl::MccAngle(a - std::numbers::pi).normalize(); + *az = impl::MccAngle(a + std::numbers::pi).normalize(); } if (zd) { diff --git a/mcc_concepts.h b/mcc_concepts.h index 2f9df36..fbbc2b2 100644 --- a/mcc_concepts.h +++ b/mcc_concepts.h @@ -12,7 +12,7 @@ #include -#include +// #include #include @@ -50,10 +50,11 @@ struct MccNullLogger { /* AND CLASS METHODS RETURNED VALUE */ template -concept mcc_error_c = - std::formattable && std::default_initializable && (std::convertible_to || requires(const T t) { - (bool)T() == false; // default constucted value must be a "non-error"! - }); +concept mcc_error_c = std::default_initializable && (std::convertible_to || requires(const T t) { + // std::formattable && std::default_initializable && (std::convertible_to + // || requires(const T t) { + (bool)T() == false; // default constucted value must be a "non-error"! + }); template @@ -67,27 +68,27 @@ DefErrT mcc_deduced_err(ErrT const& err, DefErrT const& default_err) } -template -concept mcc_retval_c = requires(T t) { - // - [](std::expected) {}(t); -}; +// template +// concept mcc_retval_c = requires(T t) { +// // +// [](std::expected) {}(t); +// }; -// deduce an error from mcc_retval_c and default error value -template RetT, mcc_error_c DefErrT> -DefErrT mcc_deduced_err(RetT const& ret, DefErrT const& default_err) -{ - if (ret) { - return DefErrT{}; // no error - } +// // deduce an error from mcc_retval_c and default error value +// template 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) { - return ret.error(); - } else { - return default_err; - } -} +// if constexpr (std::same_as) { +// return ret.error(); +// } else { +// return default_err; +// } +// } /* MOUNT CONSTRUCTION-RELATED STUFF */ @@ -374,7 +375,7 @@ struct mcc_coord_pair_interface_t { }; template -concept mcc_coord_pair_c = std::derived_from && requires(T t) { +concept mcc_coord_pair_c = std::derived_from && 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) []() { @@ -386,8 +387,10 @@ concept mcc_coord_pair_c = std::derived_from && r // std::constructible_from; - { t.x() } -> std::same_as; - { t.y() } -> std::same_as; + { t_const.x() } -> std::same_as; + { t_const.y() } -> std::same_as; + + { t_const.epoch() } -> mcc_coord_epoch_c; { t.setX(std::declval()) }; { t.setY(std::declval()) }; diff --git a/mcc_constants.h b/mcc_constants.h index 192aaf2..8645771 100644 --- a/mcc_constants.h +++ b/mcc_constants.h @@ -31,5 +31,12 @@ static constexpr DT MCC_INFINITE_DURATION_V = : DT{std::numeric_limits::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 diff --git a/mcc_coordinate.h b/mcc_coordinate.h index bed101d..b0ea315 100644 --- a/mcc_coordinate.h +++ b/mcc_coordinate.h @@ -69,6 +69,7 @@ public: template 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; @@ -85,6 +86,8 @@ public: setX((double)other.x()); setY((double)other.y()); setEpoch(other.epoch()); + + normalize(); } @@ -96,6 +99,8 @@ public: setX((double)other.x()); setY((double)other.y()); setEpoch(other.epoch()); + + normalize(); } @@ -107,6 +112,8 @@ public: setX((double)other.x()); setY((double)other.y()); setEpoch(other.epoch()); + + normalize(); } @@ -118,6 +125,8 @@ public: setX((double)other.x()); setY((double)other.y()); setEpoch(other.epoch()); + + normalize(); } @@ -161,11 +170,15 @@ public: 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) @@ -178,6 +191,26 @@ protected: 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(); + } else { // RA, AZ + _x = (double)MccAngle(_x).normalize(); + } + + // DEC and ALT is [-90,90] degrees + if constexpr (pairKind != MccCoordPairKind::COORDS_KIND_AZZD) { + _y = (double)MccAngle(_y).normalize(); + } else { // ZD id [0, 180] degrees + _y = (double)MccAngle(_y).normalize(); + } + } + } }; @@ -436,7 +469,7 @@ public: template - operator PT() + operator PT() const { if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_LONLAT) { // returns geographic site coordinates std::pair pos; @@ -447,7 +480,7 @@ public: PT res; - to(res); + toAtSameEpoch(res); return res; } @@ -457,8 +490,14 @@ public: error_t toAtSameEpoch(PT& cpair, PTs&... cpairs) const { if constexpr (PT::pairKind != MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { - cpair.setEpoch(_epoch); + 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; @@ -620,6 +659,16 @@ protected: // 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); @@ -638,7 +687,6 @@ protected: az = utils::isEqual(r, 0.0) ? 0.0 : std::atan2(y, x); if (az < 0.0) { - // az += std::numbers::pi * 2.0; // to range of [0, 2*PI] az += MCC_TWO_PI; // to range of [0, 2*PI] } @@ -649,6 +697,11 @@ protected: // 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); @@ -724,6 +777,11 @@ protected: } } + 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) { @@ -896,7 +954,26 @@ protected: return error_t{}; }; - return comp_func(pkind); // ran transformation + 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; } }; diff --git a/mcc_deserializer.h b/mcc_deserializer.h new file mode 100644 index 0000000..50722ed --- /dev/null +++ b/mcc_deserializer.h @@ -0,0 +1,279 @@ +#pragma once + + +/**************************************************************************************** + * * + * MOUNT CONTROL COMPONENTS LIBRARY * + * * + * * + * IMPLEMENTATION OF DESERIALIZER CLASSES * + * * + ****************************************************************************************/ + +#include "mcc_concepts.h" +#include "mcc_constants.h" +#include "mcc_error.h" + +namespace mcc::impl +{ + +enum class MccDeserializerErrorCode : int { + ERROR_OK, + ERROR_UNDERLYING_DESERIALIZER, + ERROR_INVALID_SERIAL_VALUE, + ERROR_COORD_TRANSFORM +}; + +} // namespace mcc::impl + + + +namespace std +{ + +template <> +class is_error_code_enum : 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(ec); + + switch (err) { + case MccDeserializerErrorCode::ERROR_OK: + return "OK"; + case MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER: + return "error returned by underlying deserializer"; + case MccDeserializerErrorCode::ERROR_INVALID_SERIAL_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(ec), MccDeserializerCategory::get()); +} + + +template +struct mcc_deserializer_interface_t { + virtual ~mcc_deserializer_interface_t() = default; + + typedef RetT error_t; + + template SelfT, traits::mcc_input_char_range R, typename ValueT> + RetT operator()(this SelfT&& self, R const& input, ValueT& value) + { + return std::forward(self)(input, value); + } + +protected: + mcc_deserializer_interface_t() = default; +}; + +template +concept mcc_deserializer_c = std::derived_from>; + + +namespace details +{ + +struct MccDeserializerBase : mcc_deserializer_interface_t { + using typename mcc_deserializer_interface_t::error_t; + + virtual ~MccDeserializerBase() = default; + + // set delimiter for elements of the range-type serialized value + template + void setSeqDelimiter(R const& delim) + { + if constexpr (std::is_array_v>) { + setSeqDelimiter(std::string_view{delim}); + } else { + _seqDelimiter.clear(); + std::ranges::copy(delim, std::back_inserter(_seqDelimiter)); + } + } + + template + R getSeqDelimiter() const + { + if constexpr (std::same_as) { + return _seqDelimiter; + } else if constexpr (std::ranges::view) { + 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(); + } + + + template + void setElemDelimiter(R const& delim) + { + if constexpr (std::is_array_v>) { + setElemDelimiter(std::string_view{delim}); + } else { + _elementDelimiter.clear(); + std::ranges::copy(delim, std::back_inserter(_seqDelimiter)); + } + } + + template + R getElemDelimiter() const + { + if constexpr (std::same_as) { + return _elementDelimiter; + } else if constexpr (std::ranges::view) { + 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(); + } + +protected: + MccDeserializerBase() = default; + + // delimiter for sequence of serialized values + std::string _seqDelimiter{MCC_DEFAULT_SEQ_DELIMITER}; + + // delimiter for aggregative (multi-element) serialized value + std::string _elementDelimiter{MCC_DEFAULT_ELEM_DELIMITER}; + + template + requires(std::ranges::input_range && std::same_as>) + error_t deserializingRange(mcc_deserializer_c auto& dsr, traits::mcc_input_char_range auto const& input, R& r) const + { + if (std::ranges::size(input) == 0) { // ignore empy input + 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); + 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 + + +// +template +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) { + auto v = mcc::utils::numFromStr(trimSpaces(input)); + if (!v.has_value()) { + return MccDeserializerErrorCode::ERROR_INVALID_SERIAL_VALUE; + } + + value = v.value(); + } else if constexpr (mcc::traits::mcc_output_char_range) { + VT r; + if constexpr (traits::mcc_array_c) { + 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) { + using el_t = std::ranges::range_value_t; + + static_assert(std::ranges::output_range, "INVALID RANGE TYPE!!!"); + + // no reference or constants allowed + static_assert(std::is_reference_v || std::is_const_v, "INVALID RANGE ELEMENT TYPE!!!"); + + MccDeserializer dsr; + return deserializingRange(dsr, input, value); + } else { + static_assert(false, "UNSUPPORTED VALUE TYPE!!!"); + } + + + return MccDeserializerErrorCode::ERROR_OK; + } +}; + +} // namespace mcc::impl diff --git a/mcc_error.h b/mcc_error.h index f9defbf..ff9e28e 100644 --- a/mcc_error.h +++ b/mcc_error.h @@ -108,17 +108,17 @@ private: static_assert(mcc::mcc_error_c, ""); -// a helper formatter impelementation +// // a helper formatter impelementation -template - requires std::is_enum_v -struct std::formatter : std::formatter, char> { - auto format(T e, auto& ctx) const - { - return formatter>::format(std::underlying_type_t(e), ctx); - } -}; +// template +// requires std::is_enum_v +// struct std::formatter : std::formatter, char> { +// auto format(T e, auto& ctx) const +// { +// return formatter>::format(std::underlying_type_t(e), ctx); +// } +// }; -enum class EE : int { A, B, C }; +// enum class EE : int { A, B, C }; -static_assert(mcc::mcc_error_c, ""); +// static_assert(mcc::mcc_error_c, ""); diff --git a/mcc_serializer.h b/mcc_serializer.h index 1bacfc7..9d50024 100644 --- a/mcc_serializer.h +++ b/mcc_serializer.h @@ -1,45 +1,241 @@ #pragma once + + +/**************************************************************************************** + * * + * MOUNT CONTROL COMPONENTS LIBRARY * + * * + * * + * IMPLEMENTATION OF SERIALIZER CLASSES * + * * + ****************************************************************************************/ + + + #include #include #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 +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 : 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(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(ec), MccSerializerCategory::get()); +} + + + +template struct mcc_serializer_interface_t { virtual ~mcc_serializer_interface_t() = default; + typedef RetT error_t; + template SelfT, traits::mcc_output_char_range R, typename ValueT, typename FmtT> requires(std::same_as, FmtT> || std::same_as) - mcc_error_c auto operator()(this SelfT&& self, R& output, ValueT const& value, FmtT fmt) + RetT operator()(this SelfT&& self, R& output, ValueT const& value, FmtT fmt) { - return std::forward(self).operator=(output, value, std::move(fmt)); + return std::forward(self)(output, value, std::move(fmt)); } + +protected: + mcc_serializer_interface_t() = default; }; template -concept mcc_serializer_c = std::derived_from; +concept mcc_serializer_c = std::derived_from>; +namespace details +{ + +struct MccSerializerBase : mcc_serializer_interface_t { + using typename mcc_serializer_interface_t::error_t; + + virtual ~MccSerializerBase() = default; + + // set delimiter for elements of the range-type serialized value + template + void setSeqDelimiter(R const& delim) + { + if constexpr (std::is_array_v>) { + setSeqDelimiter(std::string_view{delim}); + } else { + _seqDelimiter.clear(); + std::ranges::copy(delim, std::back_inserter(_seqDelimiter)); + } + } + + template + R getSeqDelimiter() const + { + if constexpr (std::same_as) { + return _seqDelimiter; + } else if constexpr (std::ranges::view) { + 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(); + } + + + template + void setElemDelimiter(R const& delim) + { + if constexpr (std::is_array_v>) { + setElemDelimiter(std::string_view{delim}); + } else { + _elementDelimiter.clear(); + std::ranges::copy(delim, std::back_inserter(_seqDelimiter)); + } + } + + template + R getElemDelimiter() const + { + if constexpr (std::same_as) { + return _elementDelimiter; + } else if constexpr (std::ranges::view) { + 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(); + } + +protected: + MccSerializerBase() = 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}; + + template + requires(std::ranges::input_range && std::same_as>) + 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 -class MccSerializer : public mcc_serializer_interface_t +class MccSerializer : public details::MccSerializerBase +// class MccSerializer : public mcc_serializer_interface_t { public: // default delimiter - static constexpr std::string_view defaultDelimiter{","}; + static constexpr std::string_view defaultRangeDelimiter{","}; - typedef impl::MccError error_t; + // typedef impl::MccError error_t; MccSerializer() = default; @@ -52,15 +248,19 @@ public: if constexpr (std::is_null_pointer_v) { std::format_to(std::back_inserter(output), "{}", value); } else if constexpr (traits::mcc_input_char_range) { - std::format_to(std::back_inserter(output), fmt, value); - } else if constexpr (std::same_as, 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, 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) { - return MccSerializer{}(output, static_cast(value), std::move(fmt)); + auto err = MccSerializer{}(output, static_cast(value), std::move(fmt)); + if (err) { + return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); + } + } else if constexpr (std::ranges::range) { using value_t = std::remove_cv_t>; @@ -69,37 +269,77 @@ public: std::string str; std::ranges::copy(value, std::back_inserter(str)); - return MccSerializer{}(output, str, std::move(fmt)); + auto err = MccSerializer{}(output, str, std::move(fmt)); + if (err) { + return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); + } + } else { MccSerializer sr; - size_t i = 0, N = std::ranges::size(value); - for (auto const& el : value) { - error_t err = sr(output, el, std::move(fmt)); - if (err) { - return err; - } - if (++i < N) { - std::format_to(std::back_inserter(output), "{}", _delimiter); - } - } + return serializingRange(sr, value, output, std::move(fmt)); } + } else { + static_assert(false, "UNSUPPORTED TYPE!!!"); } - return {}; + return MccSerializerErrorCode::ERROR_OK; } - -protected: - std::string _delimiter{defaultDelimiter}; }; + /* SPECIALIZATION FOR THE SOME CONCEPTS */ + template - requires(mcc_angle_c || - (std::ranges::input_range && mcc_angle_c>>)) -class MccSerializer : public mcc_serializer_interface_t + requires(traits::mcc_systime_c || + (std::ranges::input_range && traits::mcc_systime_c>>)) +class MccSerializer : public details::MccSerializerBase +{ +public: + virtual ~MccSerializer() = default; + + template + error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr) + { + if constexpr (std::ranges::input_range) { + using value_t = std::remove_cv_t>; + MccSerializer sr; + + return serializingRange(sr, value, output, std::move(fmt)); + } else { // scalar + if constexpr (std::is_null_pointer_v) { + std::format_to(std::back_inserter(output), "{:%FT%T}", value); + } else if constexpr (traits::mcc_input_char_range) { + 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, 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 + requires(!std::is_arithmetic_v && + (mcc_angle_c || + (std::ranges::input_range && mcc_angle_c>>))) +class MccSerializer : public virtual details::MccSerializerBase +// class MccSerializer : public mcc_serializer_interface_t { public: enum SerializedCoordFormat { @@ -113,7 +353,9 @@ public: uint8_t deg_prec = 1; // number of decimal places in arcseconds }; - typedef impl::MccError error_t; + // typedef impl::MccError error_t; + + virtual ~MccSerializer() = default; void setCoordFormat(SerializedCoordFormat format) { @@ -140,24 +382,12 @@ public: error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr) { if constexpr (std::ranges::input_range) { - MccSerializer>> sr; + using value_t = std::remove_cv_t>; + MccSerializer sr; sr.setCoordFormat(_coordFormat); sr.setCoordPrec(_coordPrec); - error_t err; - size_t i = 0, N = std::ranges::size(value); - for (auto const& el : value) { - err = sr(output, el, std::move(fmt)); - if (err) { - return err; - } - - if (++i < N) { - std::format_to(std::back_inserter(output), " "); - } - } - - return error_t{}; + return serializingRange(sr, value, output, std::move(fmt)); } else { // scalar double v = (double)value; // radians std::string sgm; @@ -167,27 +397,565 @@ public: v *= 180.0 / std::numbers::pi; return MccSerializer{}(output, v, std::move(fmt)); case MccSerializer::CFMT_SGM_HOURS: - sgm = utils::rad2sxg(value, true, _coordPrec.load().hour_prec); + sgm = utils::rad2sxg(v, true, _coordPrec.load().hour_prec); break; case MccSerializer::CFMT_SGM_DEGS: - sgm = utils::rad2sxg(value, false, _coordPrec.load().deg_prec); + sgm = utils::rad2sxg(v, false, _coordPrec.load().deg_prec); break; default: break; } - return MccSerializer{}(output, sgm, std::move(fmt)); + auto err = MccSerializer{}(output, sgm, std::move(fmt)); + if (err) { + return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); + } } + + return MccSerializerErrorCode::ERROR_OK; } protected: - std::atomic _coordFormat{MccSerializer::CFMT_DEGREES}; + // std::atomic _coordFormat{MccSerializer::CFMT_DEGREES}; + std::atomic _coordFormat{MccSerializer::CFMT_SGM_DEGS}; std::atomic _coordPrec{SexagesimalCoordPrec{.hour_prec = 2, .deg_prec = 1}}; }; -template -class MccSerializer : public mcc_serializer_interface_t + +template + requires(mcc_coord_epoch_c || + (std::ranges::input_range && mcc_coord_epoch_c>>)) +class MccSerializer : 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 + error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr) + { + if constexpr (std::ranges::input_range) { + using value_t = std::remove_cv_t>; + MccSerializer sr; + sr.setTimePointFormat(_tpFormat); + + return serializingRange(sr, value, output, std::move(fmt)); + } else { + switch (_tpFormat) { + case TP_FORMAT_DATE: { + auto utc = value.UTC(); + auto err = MccSerializer>{}(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{}(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>{}(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 _tpFormat{TP_FORMAT_DATE}; +}; + + + +template + requires(mcc_coord_pair_c || + (std::ranges::input_range && mcc_coord_pair_c>>)) +class MccSerializer : public std::conditional_t, + MccSerializer>, + MccSerializer>, + public std::conditional_t, + MccSerializer>, + MccSerializer> +// class MccSerializer : public mcc_serializer_interface_t +{ +protected: + typedef std::conditional_t, + MccSerializer>, + MccSerializer> + base_ser_epoch_t; + + typedef std:: + conditional_t, MccSerializer>, MccSerializer> + base_ser_angle_t; + +public: + using typename details::MccSerializerBase::error_t; + + template + error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr) + { + if constexpr (std::ranges::input_range) { + using value_t = std::remove_cv_t>; + MccSerializer sr; + + sr.setTimePointFormat(this->_tpFormat); + sr.setCoordFormat(this->_coordFormat); + sr.setCoordPrec(this->_coordPrec); + + return serializingRange(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) { + 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{}(output, MccCoordPairKindToStr(VT::pairKind)); + if (err_s) { + return mcc_deduced_err(err_s, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); + } + } + + return MccSerializerErrorCode::ERROR_OK; + } +}; + + +template + requires(mcc_skypoint_c || + (std::ranges::input_range && mcc_skypoint_c>>)) +class MccSerializer : public std::conditional_t, + MccSerializer>, + MccSerializer>, + public std::conditional_t, + MccSerializer>, + MccSerializer> +{ +protected: + typedef std::conditional_t, + MccSerializer>, + MccSerializer> + base_ser_epoch_t; + + typedef std:: + conditional_t, MccSerializer>, MccSerializer> + 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 + error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr) + { + if constexpr (std::ranges::input_range) { + using value_t = std::remove_cv_t>; + MccSerializer sr; + + sr.setTimePointFormat(this->_tpFormat); + sr.setCoordFormat(this->_coordFormat); + sr.setCoordPrec(this->_coordPrec); + + return serializingRange(sr, value, output, std::move(fmt)); + } else { // scalar + + auto serialize_pair_func = [&output, this](PT const& pt) -> error_t { + MccSerializer 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) { + 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 _skptFormat{SKYPOINT_FORMAT_FULL}; }; @@ -199,7 +967,7 @@ static_assert(mcc_serializer_c>, "!!!"); static_assert(std::atomic::SerializedCoordFormat>::is_always_lock_free, "!!!"); static_assert(std::atomic::SexagesimalCoordPrec>::is_always_lock_free, "!!!"); -static_assert(traits::mcc_formattable, "!!!"); +static_assert(std::formattable, "!!!"); void f() { @@ -209,4 +977,4 @@ void f() s(str, impl::MccAngleALT{1.1}); } -} // namespace mcc +} // namespace mcc::impl diff --git a/mcc_traits.h b/mcc_traits.h index 6883a38..3f02282 100644 --- a/mcc_traits.h +++ b/mcc_traits.h @@ -60,9 +60,9 @@ concept mcc_range_of_output_char_range = // https://stackoverflow.com/questions/72430369/how-to-check-that-a-type-is-formattable-using-type-traits-concepts) template -concept mcc_formattable = requires(T v) { [](T vv) { return std::format("{}", vv); }(v); }; -// concept mcc_formattable = -// requires(T v, std::format_context ctx) { std::formatter>().format(v, ctx); }; +concept mcc_formattable = + requires(T v, std::format_context ctx) { std::formatter>().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 diff --git a/mcc_utils.h b/mcc_utils.h index 8c7cdde..765ee2c 100644 --- a/mcc_utils.h +++ b/mcc_utils.h @@ -247,6 +247,17 @@ static R rad2sxg(double ang, bool hms = false, int prec = 2) // 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; diff --git a/tests/mcc_coord_test.cpp b/tests/mcc_coord_test.cpp index c1b33f3..ce8f82c 100644 --- a/tests/mcc_coord_test.cpp +++ b/tests/mcc_coord_test.cpp @@ -2,7 +2,7 @@ // #include #include - +#include using namespace mcc::impl; @@ -19,6 +19,21 @@ static skypt_t::ccte_t::engine_state_t saoras{.meteo{.temperature = 0.0, .humidi static_assert(mcc::mcc_angle_c, "!!!!!!!!!!!!"); + +template +void serialize(VT const& value) +{ + MccSerializer ser; + std::string s; + + auto err = ser(s, value); + if (err) { + std::cout << "SERIALIZing ERR: " << err << "\n"; + } else { + std::cout << "SERIALIZED: " << s << "\n"; + } +} + int main() { skypt_t::cctEngine.setStateERFA(saoras); @@ -60,9 +75,12 @@ int main() azzd.setX(111.0_degs), azzd.setY(111.0_degs); azalt.setX(111.0_degs), azalt.setY(111.0_degs); hadec_obs.setX(111.0_degs), hadec_obs.setY(111.0_degs); + radec_app.setX(111.0_degs), radec_app.setY(111.0_degs); pt = radec_obs; - pt.to(icrs, azzd, hadec_obs); + // pt.to(icrs, azzd, hadec_obs, radec_app); + pt.to(icrs, radec_app); + pt.to(radec_obs, hadec_obs, azzd); std::cout << "\n\nFROM OBSERVED TO ICRS:\n"; std::cout << "OBS COORD EPOCH: " << radec_obs.epoch().UTC() << "\n"; @@ -78,9 +96,50 @@ int main() std::cout << "HA_OBS = " << hadec_obs.x().sexagesimal(true) << "\n"; std::cout << "AZ = " << azzd.x().sexagesimal() << "\n"; std::cout << "ZD = " << azzd.y().sexagesimal() << "\n"; + std::cout << "RA_APP = " << radec_app.x().sexagesimal(true) << "\n"; + std::cout << "DEC_APP = " << radec_app.y().sexagesimal() << "\n"; azalt = pt; std::cout << "ALT = " << azalt.y().sexagesimal() << "\n"; + std::cout << "\nIN DEGREES:\n"; + std::cout << "RA_ICRS = " << icrs.x().degrees() << "\n"; + std::cout << "DEC_ICRS = " << icrs.y().degrees() << "\n"; + std::cout << "RA_OBS = " << radec_obs.x().degrees() << "\n"; + std::cout << "DEC_OBS = " << radec_obs.y().degrees() << "\n"; + std::cout << "HA_OBS = " << hadec_obs.x().degrees() << "\n"; + std::cout << "AZ = " << azzd.x().degrees() << "\n"; + std::cout << "ZD = " << azzd.y().degrees() << "\n"; + std::cout << "RA_APP = " << radec_app.x().degrees() << "\n"; + std::cout << "DEC_APP = " << radec_app.y().degrees() << "\n"; + + + std::cout << "\n\nSERIALIZATION TEST:\n"; + + + double v = 7.7; + + std::cout << "\tfor 'double' type: "; + serialize(v); + + std::cout << "\tfor 'coord pair' type: "; + serialize(radec_obs); + + radec_app = pt; + // pt.to(radec_app); + std::cout << "\tfor 'coord pair' type: "; + serialize(radec_app); + + azzd = pt; + // pt.to(azzd); + std::cout << "\tfor 'coord pair' type: "; + serialize(azzd); + + // pt.from(icrs); + pt = radec_obs; + + std::cout << "\tfor 'sky point' type: "; + serialize(pt); + return 0; }