#pragma once #include "mcc_coordinate.h" #include "mcc_serialization_common.h" namespace mcc::impl { enum class MccSerializerErrorCode : int { ERROR_OK, ERROR_UNDERLYING_SERIALIZER, ERROR_INVALID_EPOCH, ERROR_COORD_TRANSFORM }; } // namespace mcc::impl namespace std { template <> class is_error_code_enum : 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()); } /* BASE SERIALIZER CLASS (FOR IMPLEMENTATIONS BELOW) */ struct MccSerializerBase : mcc_serializer_interface_t { virtual ~MccSerializerBase() = default; using typename mcc_serializer_interface_t::error_t; protected: MccSerializerBase() = default; enum CoordType { CO_LON, CO_LAT }; static void addElemDelimiter(traits::mcc_output_char_range auto& output, mcc_serialization_params_c auto const& params) { std::format_to(std::back_inserter(output), "{}", params.elem_delim); } static void addSeqDelimiter(traits::mcc_output_char_range auto& output, mcc_serialization_params_c auto const& params) { std::format_to(std::back_inserter(output), "{}", params.seq_delim); } // set serialized angle format according to coordinates pair format and type of // serializing mcc_coord_pair_c::pairKind template static void angleFormatFromCoordPairType(mcc_serialization_params_c auto& pars) { if (pars.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_DEGREES) { pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES; } else { // format to sexagesimal form according to celestial coordinate type if constexpr (TYPE == MccSerializerBase::CO_LON) { if (pars.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGDEG) { pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS; } else if (pars.coordpair_format == MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG) { if constexpr (PAIRKIND == MccCoordPairKind::COORDS_KIND_AZZD || PAIRKIND == MccCoordPairKind::COORDS_KIND_AZALT || PAIRKIND == MccCoordPairKind::COORDS_KIND_XY || PAIRKIND == MccCoordPairKind::COORDS_KIND_GENERIC) { // azimuth is in degrees pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS; } else { // RA or HA pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS; } } else { // !!!!!!!!!!!!!!!!!! } } else { // Y-coordinates (co-latitude one, DEC, ALT, ZD, generic X) is always in degrees for celestial // point pars.angle_format = MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS; } } } template requires(std::ranges::input_range && std::same_as>) static error_t serializeRange(mcc_serializer_c auto& sr, R const& r, traits::mcc_output_char_range auto& output, mcc_serialization_params_c auto const& params) { size_t i = 0, N = std::ranges::size(r); for (auto const& el : r) { auto err = sr(output, el, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } if (++i < N) { MccSerializerBase::addSeqDelimiter(output, params); } } return MccSerializerErrorCode::ERROR_OK; } }; /* MAIN (FALLBACK) TEMPLATED IMPLEMENTATION */ template struct MccSerializer : MccSerializerBase { constexpr static std::string_view serializerName{"MCC-FALLBACK-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { if constexpr (std::formattable) { } else if constexpr (std::convertible_to) { auto err = MccSerializer{}(output, static_cast(value), params); 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, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } } else { MccSerializer sr; return MccSerializerBase::serializeRange(sr, value, output, params); } } else { static_assert(false, "UNSUPPORTED TYPE!!!"); } return MccSerializerErrorCode::ERROR_OK; } }; /* SPECIALIZATION FOR THE SOME CONCEPTS */ /* std::chrono::sys_time variants and its sequence */ template requires traits::mcc_systime_c struct MccSerializer : MccSerializerBase { virtual ~MccSerializer() = default; constexpr static std::string_view serializerName{"MCC-SYSTIME-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { std::vformat_to(std::back_inserter(output), params.systime_format, std::make_format_args(value)); return MccSerializerErrorCode::ERROR_OK; } }; template requires(!std::is_arithmetic_v && mcc_angle_c) struct MccSerializer : MccSerializerBase { constexpr static std::string_view serializerName{"MCC-ANGLE-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { double v = (double)value; // radians (see mcc_angle_c concept) std::string sgm; switch (params.angle_format) { case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES: v *= MCC_RADS_TO_DEGRESS; return MccSerializer{}(output, v, params); case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_DEGS: if (params.norm_sxgm) { sgm = utils::rad2sxg(v, false, params.angle_prec.deg_prec); } else { sgm = utils::rad2sxg(v, false, params.angle_prec.deg_prec); } break; case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURS: if (params.norm_sxgm) { sgm = utils::rad2sxg(v, true, params.angle_prec.hour_prec); } else { sgm = utils::rad2sxg(v, true, params.angle_prec.hour_prec); } break; default: break; } auto err = MccSerializer{}(output, sgm, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } return MccSerializerErrorCode::ERROR_OK; }; }; template struct MccSerializer : MccSerializerBase { constexpr static std::string_view serializerName{"MCC-COORD-EPOCH-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { double jd; switch (params.timepoint_format) { case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_DATE: { auto tp = value.UTC(); auto err = MccSerializer{}(output, tp, params); return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JEPOCH: { auto ep = value.JEpoch(); auto err = MccSerializer{}(output, ep, params); return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JD: jd = value.MJD() + MCC_J2000_MJD; break; case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_MJD: jd = value.MJD(); break; } auto err = MccSerializer{}(output, jd, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } return MccSerializerErrorCode::ERROR_OK; } }; template struct MccSerializer : MccSerializerBase { constexpr static std::string_view serializerName{"MCC-COORD-PAIR-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { std::conditional_t, std::nullptr_t, mcc_serialization_params_t> pars = std::is_null_pointer_v ? nullptr : params; pars.norm_sxgm = true; pars.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG; // X-coordinate MccSerializerBase::angleFormatFromCoordPairType(pars); auto err = MccSerializer{}(output, value.x(), params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, params); pars.norm_sxgm = false; // do not normalize co-latitude angle // Y-coordinate MccSerializerBase::angleFormatFromCoordPairType(pars); err = MccSerializer{}(output, value.y(), params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, params); // epoch auto ep = value.epoch(); auto ep_err = MccSerializer{}(output, ep, params); if (ep_err) { return mcc_deduced_err(ep_err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, params); // pair kind return mcc_deduced_err(MccSerializer{}(output, MccCoordPairKindToStr(VT::pairKind), params), MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); return MccSerializerErrorCode::ERROR_OK; } }; template struct MccSerializer : MccSerializerBase { constexpr static std::string_view serializerName{"MCC-SKYPOINT-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { auto serialize_cpair = [&](T& cp) { auto ccte_err = value.to(cp); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } auto err = MccSerializer{}(output, cp, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } return MccSerializerErrorCode::ERROR_OK; }; switch (value.pairKind()) { case MccCoordPairKind::COORDS_KIND_RADEC_ICRS: { MccSkyRADEC_ICRS cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_RADEC_OBS: { MccSkyRADEC_OBS cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_RADEC_APP: { MccSkyRADEC_APP cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_HADEC_OBS: { MccSkyHADEC_OBS cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_HADEC_APP: { MccSkyHADEC_APP cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_AZZD: { MccSkyAZZD cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_AZALT: { MccSkyAZALT cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_XY: { MccGenXY cp; return serialize_cpair(cp); } case MccCoordPairKind::COORDS_KIND_GENERIC: { MccGenXY cp; return serialize_cpair(cp); } default: // !!!!!!!!!!!! } } }; template struct MccSerializer : MccSerializerBase { constexpr static std::string_view serializerName{"MCC-TELEMETRY-DATA-SERIALIZER"}; template error_t operator()(traits::mcc_output_char_range auto& output, VT const& value, ParamsT const& params = nullptr) { // FORMAT: RA_OBS_MOUNT, DEC_OBS_MOUNT, RA_APP_MOUNT, DEC_APP_MOUNT, HA_APP_MOUNT, AZ_MOUNT, ZD_MOUNT, // REFR_CORR_MOUNT, LAST, ENC_X, ENC_Y, PCM_X, PCM_Y, RA_APP_TAG, DEC_APP_TAG, AZ_TAG, ZD_TAG, TIMEPOINT // NOTE: One must assume that the returned RA coordinates are in format of underlying celestial coordinate // transformation engine used in the mcc_skypoint_c class implementation. E.g. ERFA-library uses the // CIO-based representation of RA std::conditional_t, std::nullptr_t, mcc_serialization_params_t> pars = std::is_null_pointer_v ? nullptr : params; pars.norm_sxgm = true; pars.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG; std::string ra_obs, ra_app, ha, last, ra_app_tg; MccSkyRADEC_OBS r_obs; MccSkyRADEC_APP r_app; // quantities in hour representation MccSerializerBase::angleFormatFromCoordPairType(pars); MccSerializer ang_sr; // RA_OBS_MOUNT auto ccte_err = value.mountPos.toAtSameEpoch(r_obs); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } auto err = ang_sr(ra_obs, r_obs, pars); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } // RA_APP_MOUNT ccte_err = value.mountPos.toAtSameEpoch(r_app); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(ra_app, r_app, pars); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } } }; } // namespace mcc::impl