#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ /* IMPLEMENTATION OF SOME SIMPLE PROHIBITED ZONES */ #include "mcc_defaults.h" #include "mcc_generics.h" namespace mcc { static constexpr double mcc_sideral_to_UT1_ratio = 1.002737909350795; // sideral/UT1 enum MccAltLimitPZErrorCode : int { ERROR_OK, ERROR_COORD_TRANSFROM }; } // namespace mcc namespace std { template <> class is_error_code_enum : public true_type { }; } // namespace std namespace mcc { /* MINIMAL OR MAXIMAL ALTITUDE PROHIBITED ZONES */ /* error category definition */ // error category struct MccAltLimitPZCategory : public std::error_category { MccAltLimitPZCategory() : std::error_category() {} const char* name() const noexcept { return "ALTITUDE-LIMIT-PZ"; } std::string message(int ec) const { MccAltLimitPZErrorCode err = static_cast(ec); switch (err) { case MccAltLimitPZErrorCode::ERROR_OK: return "OK"; case MccAltLimitPZErrorCode::ERROR_COORD_TRANSFROM: return "coordinate transformation error"; default: return "UNKNOWN"; } } static const MccAltLimitPZCategory& get() { static const MccAltLimitPZCategory constInst; return constInst; } }; inline std::error_code make_error_code(MccAltLimitPZErrorCode ec) { return std::error_code(static_cast(ec), MccAltLimitPZCategory::get()); } enum class MccAltLimitKind { MIN_ALT_LIMIT, MAX_ALT_LIMIT }; template class MccAltLimitPZ : public mcc_pzone_interface_t { protected: static constexpr auto pi2 = std::numbers::pi * 2.0; public: typedef std::error_code error_t; MccAltLimitPZ(mcc_angle_c auto const& alt_limit, mcc_angle_c auto const& latitude, mcc_ccte_c auto* ccte_engine) : _altLimit(MccAngle(alt_limit).normalize()), _cosALim(cos(_altLimit)), _sinAlim(sin(_altLimit)), _cosLat(cos(latitude)), _sinLat(sin(latitude)), _absLat(abs(latitude)), _latLim(MccAltLimitPZ::pi2 - _altLimit) { _transformCoordinates = [ccte_engine](MccCelestialPoint from_pt, MccCelestialPoint* to_pt) -> error_t { auto err = ccte_engine->transformCoordinates(from_pt, to_pt); if (!err) { return MccAltLimitPZErrorCode::ERROR_OK; } if (std::same_as) { return err; } else { return MccAltLimitPZErrorCode::ERROR_COORD_TRANSFROM; } }; } MccAltLimitPZ(MccAltLimitPZ&&) = default; MccAltLimitPZ(const MccAltLimitPZ&) = default; consteval std::string_view name() const { return KIND == MccAltLimitKind::MIN_ALT_LIMIT ? "MINALT-ZONE" : KIND == MccAltLimitKind::MAX_ALT_LIMIT ? "MAXALT-ZONE" : "ALTLIMIT-UNKNOWN"; } template error_t inPZone(InputT coords, bool* result) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { double alt; error_t ret = MccAltLimitPZErrorCode::ERROR_OK; if constexpr (mcc_eqt_hrz_coord_c) { alt = coords.ALT; } else { MccCelestialPoint to_pt{.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT, .time_point = coords.time_point}; ret = getCoord(coords, &to_pt); if (ret) { return ret; } alt = to_pt.Y; } if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) { *result = alt <= _altLimit; } else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { *result = alt >= _altLimit; } return ret; } template error_t timeToPZone(InputT coords, traits::mcc_time_duration_c auto* res_time) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { using res_t = std::remove_cvref_t; double ha, dec; error_t ret = MccAltLimitPZErrorCode::ERROR_OK; bool inzone; ret = inPZone(coords, &inzone); if (ret) { return ret; } if (inzone) { *res_time = res_t{0}; return ret; } if constexpr (mcc_eqt_hrz_coord_c) { ha = coords.HA; dec = coords.DEC_APP; } else { MccCelestialPoint to_pt{.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP, .time_point = coords.time_point}; ret = getCoord(coords, &to_pt); if (ret) { return ret; } ha = to_pt.X; dec = to_pt.Y; } if (!doesObjectReachZone(dec)) { *res_time = mcc_infinite_duration_v; return ret; } if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) { // the closest time point is one after upper culmination compute(ha, dec, false, res_time); } else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one before upper // culmination compute(ha, dec, true, res_time); } return ret; } template error_t timeFromPZone(InputT coords, traits::mcc_time_duration_c auto* res_time) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { using res_t = std::remove_cvref_t; double ha, dec; error_t ret = MccAltLimitPZErrorCode::ERROR_OK; bool inzone; ret = inPZone(coords, &inzone); if (ret) { return ret; } if (!inzone) { *res_time = res_t{0}; return ret; } if constexpr (mcc_eqt_hrz_coord_c) { ha = coords.HA; dec = coords.DEC_APP; } else { MccCelestialPoint to_pt{.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP, .time_point = coords.time_point}; ret = getCoord(coords, &to_pt); if (ret) { return ret; } ha = to_pt.X; dec = to_pt.Y; } if (!doesObjectExitFromZone(dec)) { *res_time = mcc_infinite_duration_v; return ret; } if (!doesObjectReachZone(dec)) { *res_time = res_t{0}; return ret; } if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) { // the closest time point is one before upper culmination compute(ha, dec, true, res_time); } else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one after upper // culmination compute(ha, dec, false, res_time); } return ret; } template error_t intersectPZone(InputT coords, mcc_celestial_point_c auto* point) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { double ha, dec, az; error_t ret = MccAltLimitPZErrorCode::ERROR_OK; if constexpr (mcc_eqt_hrz_coord_c) { ha = coords.HA; dec = coords.DEC_APP; } else { MccCelestialPoint to_pt{.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP, .time_point = coords.time_point}; ret = getCoord(coords, &to_pt); if (ret) { return ret; } ha = to_pt.X; dec = to_pt.Y; } double sinDec = sin(dec), cosDec = cos(dec); auto cos_ha = (_sinAlim - sinDec * _sinLat) / cosDec / _cosLat; if (cos_ha > 1.0) { // no intersection point->pair_kind = MccCoordPairKind::COORDS_KIND_GENERIC; point->X = std::numeric_limits::quiet_NaN(); point->Y = std::numeric_limits::quiet_NaN(); return ret; } // WARNNIG: THE EXPRESSION ASSUMES THAT AZIMUTH IS COUNTED FROM THE SOUTH THROUGH THE WEST!!! double cosA = (-sinDec * _cosLat + cosDec * _sinLat * cos_ha) / _cosALim; if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) { // the closest time point is one after upper culmination az = std::acos(cosA); } else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one before upper // culmination az = -std::acos(cosA); } MccCelestialPoint pt{ .pair_kind = MccCoordPairKind::COORDS_KIND_AZALT, .time_point = coords.time_point, .X = az, .Y = _altLimit}, to_pt{.pair_kind = point->pair_kind, .time_point = point->time_point}; ret = _transformCoordinates(pt, &to_pt); if (!ret) { point->X = to_pt.X; point->Y = to_pt.Y; } return ret; } protected: double _altLimit, _cosALim, _sinAlim; double _cosLat, _sinLat, _absLat, _latLim; std::function _transformCoordinates{}; error_t getCoord(mcc_celestial_point_c auto const& from_pt, MccCelestialPoint* to_pt) { MccCelestialPoint pt{ .pair_kind = from_pt.pair_kind, .time_point = from_pt.time_point, .X = from_pt.X, .Y = from_pt.Y}; return _transformCoordinates(pt, to_pt); } bool doesObjectReachZone(const double& dec_app) { // check for limit conditions auto dd = std::abs(dec_app); if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) { dd += _altLimit; if (dd > _latLim) { // never fall below altitude limit return false; } } else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { auto z = std::numbers::pi / 2.0 - _altLimit; if ((dd < (_absLat - z)) || (dd > (_absLat + z))) { // never rise above altitude limit return false; } // if ((dd < (_absLat - _altLimit)) || (dd > (_absLat + _altLimit))) { // never rise above altitude limit // return false; // } } else { static_assert(false, "UNKNOWN ALTITUDE LIMIT TYPE!"); } return true; } bool doesObjectExitFromZone(const double& dec_app) { // check for limit conditions auto dd = std::abs(dec_app); if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) { dd -= _altLimit; if (-dd <= -_latLim) { // always below altitude limit return false; } } else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { if ((dd >= (_absLat - _altLimit)) || (dd <= (_absLat + _altLimit))) { // always above altitude limit return false; } } else { static_assert(false, "UNKNOWN ALTITUDE LIMIT TYPE!"); } return true; } void compute(const double& ha_app, const double& dec_app, bool before_upper_culm, traits::mcc_time_duration_c auto* result) { using res_t = std::remove_cvref_t; using period_t = typename res_t::period; double cos_ha = (_sinAlim - std::sin(dec_app) * _sinLat) / std::cos(dec_app) / _cosLat; if (cos_ha > 1.0) { // should not be! *result = mcc_infinite_duration_v; return; } double ha; // WARNING: what about south hemisphere?!!! if (before_upper_culm) { ha = -std::acos(cos_ha); // HA before upper culmination } else { ha = std::acos(cos_ha); // HA after upper culmination!! } auto time_ang = ha - ha_app; // in sideral time scale if (time_ang < 0.0) { // next day time_ang += MccAltLimitPZ::pi2; } time_ang /= mcc_sideral_to_UT1_ratio; // to UT1 time scale std::chrono::nanoseconds ns{ static_cast(time_ang * 43200.0 / std::numbers::pi * 1.0E9)}; period_t rat; *result = res_t{static_cast(time_ang * 43200.0 / std::numbers::pi * rat.den / rat.num)}; } }; } // namespace mcc