#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 = mcc_serialization_params_t{}) { if constexpr (std::formattable) { std::format_to(std::back_inserter(output), "{}", value); } 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 = mcc_serialization_params_t{}) { 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 = mcc_serialization_params_t{}) { double v = (double)value; // radians (see mcc_angle_c concept) std::string sgm; switch (params.angle_format) { case MccSerializedAngleFormat::MCC_SERIALIZED_FORMAT_DEGREES: v *= MCC_RADS_TO_DEGRESS; 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 = mcc_serialization_params_t{}) { double jd; switch (params.timepoint_format) { case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_DATE: { auto tp = value.UTC(); auto err = MccSerializer{}(output, tp, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } return MccSerializerErrorCode::ERROR_OK; }; case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JEPOCH: { auto ep = value.JEpoch(); auto err = MccSerializer{}(output, ep, params); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } return MccSerializerErrorCode::ERROR_OK; } break; case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_JD: jd = value.MJD() + MCC_J2000_MJD; break; case MccTimePointFormat::MCC_TIMEPOINT_FORMAT_MJD: jd = value.MJD(); break; } auto err = MccSerializer{}(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 = mcc_serialization_params_t{}) { auto pars = 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(), pars); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, params); pars.norm_sxgm = false; // do not normalize co-latitude angle // Y-coordinate MccSerializerBase::angleFormatFromCoordPairType(pars); err = MccSerializer{}(output, value.y(), pars); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, params); // epoch auto ep = value.epoch(); auto ep_err = MccSerializer{}(output, ep, params); if (ep_err) { return mcc_deduced_err(ep_err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, params); // pair kind auto pk_err = MccSerializer{}(output, MccCoordPairKindToStr(VT::pairKind), params); if (pk_err) { return mcc_deduced_err(pk_err, 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 = mcc_serialization_params_t{}) { 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: return MccSerializerErrorCode::ERROR_COORD_TRANSFORM; } } }; 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 = mcc_serialization_params_t{}) { // FORMAT: RA_OBS_MOUNT, DEC_OBS_MOUNT, RA_APP_MOUNT, DEC_APP_MOUNT, HA_APP_MOUNT, AZ_MOUNT, ZD_MOUNT, // REFR_CORR_MOUNT, ENC_X, ENC_Y, PCM_X, PCM_Y, RA_APP_TAG, DEC_APP_TAG, AZ_TAG, ZD_TAG, LAST, EO, TIMEPOINT, // STATUS // NOTE: One must assume that the returned RA coordinates are in format of underlying celestial coordinate // transformation engine used in the mcc_skypoint_c class implementation. E.g. ERFA-library uses the // CIO-based representation of RA auto pars_h = params; auto pars_d = params; pars_h.norm_sxgm = true; pars_h.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG; pars_d.norm_sxgm = true; pars_d.coordpair_format = MccSerializedCoordPairFormat::MCC_SERIALIZED_FORMAT_SXGM_HOURDEG; MccSkyRADEC_OBS rd_obs; MccSkyRADEC_APP rd_app; MccSkyHADEC_APP hd_app; MccSkyAZZD azzd; // quantities in hour representation MccSerializerBase::angleFormatFromCoordPairType(pars_h); // quantities in degree representation MccSerializerBase::angleFormatFromCoordPairType(pars_d); MccSerializer ang_sr; // RA_OBS_MOUNT, DEC_OBS_MOUNT auto ccte_err = value.mountPos.toAtSameEpoch(rd_obs); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } auto err = ang_sr(output, rd_obs.x(), pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, rd_obs.y(), pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // RA_APP_MOUNT, DEC_APP_MOUNT ccte_err = value.mountPos.toAtSameEpoch(rd_app); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, rd_app.x(), pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, rd_app.y(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // HA_APP_MOUNT ccte_err = value.mountPos.toAtSameEpoch(hd_app); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, hd_app.x(), pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // AZ_MOUNT, ZD_MOUNT ccte_err = value.mountPos.toAtSameEpoch(azzd); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, azzd.x(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, azzd.y(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // refraction correction MccAngle ang; ccte_err = value.mountPos.refractCorrection(&ang); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, ang, pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // encoder X and Y err = ang_sr(output, value.hwState.XY.x(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, value.hwState.XY.y(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // PCM X and Y err = ang_sr(output, value.pcmCorrection.pcmX(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, value.pcmCorrection.pcmY(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // RA_APP_TAG, DEC_APP_TAG ccte_err = value.targetPos.toAtSameEpoch(rd_app); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, rd_app.x(), pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, rd_app.y(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // AZ_TAG, ZD_TAG ccte_err = value.targetPos.toAtSameEpoch(azzd); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, azzd.x(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); err = ang_sr(output, azzd.y(), pars_d); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // LAST, local apparent sideral time ccte_err = value.mountPos.appSideralTime(&ang, true); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, ang, pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // EO, equation of origins ccte_err = value.mountPos.EO(&ang); if (ccte_err) { return mcc_deduced_err(ccte_err, MccSerializerErrorCode::ERROR_COORD_TRANSFORM); } err = ang_sr(output, ang, pars_h); if (err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // coordinates epoch auto ep_err = MccSerializer{}(output, value.mountPos.epoch(), pars_d); if (ep_err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } MccSerializerBase::addElemDelimiter(output, pars_h); // status (it must be formattable, see mcc_concepts.h) auto st_err = MccSerializer{}(output, value.hwState.movementState, pars_d); if (st_err) { return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); } return MccSerializerErrorCode::ERROR_OK; } }; static_assert(mcc_serializer_c>, "!!!"); } // namespace mcc::impl