#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::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) RetT operator()(this SelfT&& self, R& output, ValueT const& value, FmtT fmt) { return std::forward(self)(output, value, std::move(fmt)); } protected: mcc_serializer_interface_t() = default; }; template concept mcc_serializer_c = std::derived_from>; namespace details { struct MccSerializerBase : mcc_serializer_interface_t, utils::mcc_elem_sequence_with_delim_t { using typename mcc_serializer_interface_t::error_t; virtual ~MccSerializerBase() = default; protected: MccSerializerBase() = default; 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 details::MccSerializerBase // class MccSerializer : public mcc_serializer_interface_t { public: // default delimiter static constexpr std::string_view defaultRangeDelimiter{","}; // typedef impl::MccError error_t; MccSerializer() = default; ~MccSerializer() = default; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, FmtT fmt = nullptr) { if constexpr (std::formattable) { if constexpr (std::is_null_pointer_v) { std::format_to(std::back_inserter(output), "{}", value); } else if constexpr (traits::mcc_input_char_range) { 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) { 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>; // special range (character sequence) if constexpr (std::same_as) { std::string str; std::ranges::copy(value, std::back_inserter(str)); auto err = MccSerializer{}(output, str, std::move(fmt)); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } } else { MccSerializer sr; return serializingRange(sr, value, output, std::move(fmt)); } } else { static_assert(false, "UNSUPPORTED TYPE!!!"); } return MccSerializerErrorCode::ERROR_OK; } }; /* SPECIALIZATION FOR THE SOME CONCEPTS */ template 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 { 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 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.setCoordFormat(_coordFormat); sr.setCoordPrec(_coordPrec); return serializingRange(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{}(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{}(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_SGM_DEGS}; std::atomic _coordPrec{SexagesimalCoordPrec{.hour_prec = 2, .deg_prec = 1}}; }; 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}; }; static_assert(mcc_serializer_c>, "!!!"); 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(std::formattable, "!!!"); void f() { MccSerializer s; std::string str; s(str, impl::MccAngleALT{1.1}); } } // namespace mcc::impl