mountcontrol/mcc/mcc_pzone.h
2025-08-21 19:02:11 +03:00

426 lines
13 KiB
C++

#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<mcc::MccAltLimitPZErrorCode> : 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<MccAltLimitPZErrorCode>(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<int>(ec), MccAltLimitPZCategory::get());
}
enum class MccAltLimitKind { MIN_ALT_LIMIT, MAX_ALT_LIMIT };
template <MccAltLimitKind KIND = MccAltLimitKind::MIN_ALT_LIMIT>
class MccAltLimitPZ : public mcc_pzone_interface_t<std::error_code>
{
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<MccAngle::NORM_KIND_90_90>()),
_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<decltype(err), error_t>) {
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 <typename InputT>
error_t inPZone(InputT coords, bool* result)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
double alt;
error_t ret = MccAltLimitPZErrorCode::ERROR_OK;
if constexpr (mcc_eqt_hrz_coord_c<InputT>) {
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 <typename InputT>
error_t timeToPZone(InputT coords, traits::mcc_time_duration_c auto* res_time)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
using res_t = std::remove_cvref_t<decltype(*res_time)>;
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<InputT>) {
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<res_t>;
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 <typename InputT>
error_t timeFromPZone(InputT coords, traits::mcc_time_duration_c auto* res_time)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
using res_t = std::remove_cvref_t<decltype(*res_time)>;
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<InputT>) {
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<res_t>;
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 <typename InputT>
error_t intersectPZone(InputT coords, mcc_celestial_point_c auto* point)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
double ha, dec, az;
error_t ret = MccAltLimitPZErrorCode::ERROR_OK;
if constexpr (mcc_eqt_hrz_coord_c<InputT>) {
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<double>::quiet_NaN();
point->Y = std::numeric_limits<double>::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<error_t(MccCelestialPoint, MccCelestialPoint*)> _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<decltype(*result)>;
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<res_t>;
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<std::chrono::nanoseconds::rep>(time_ang * 43200.0 / std::numbers::pi * 1.0E9)};
period_t rat;
*result = res_t{static_cast<typename res_t::rep>(time_ang * 43200.0 / std::numbers::pi * rat.den / rat.num)};
}
};
} // namespace mcc