Files
mcc/include/mcc/mcc_pzone.h
Timur A. Fatkhullin a1f17f0d76 mcc_keyvalue.h: fix MccKeyValueHolder class coping and moving
mcc_pzone.h: add copy and move constructors and operator=
2026-06-02 10:53:40 +03:00

713 lines
23 KiB
C++

#pragma once
/****************************************************************************************
* *
* MOUNT CONTROL COMPONENTS LIBRARY *
* *
* *
* IMPLEMENTATION OF SOME SIMPLE PROHIBITED ZONES *
* *
****************************************************************************************/
#include "mcc_concepts.h"
#include "mcc_constants.h"
#include "mcc_coordinate.h"
#include "mcc_deserializer.h"
#include "mcc_serializer.h"
namespace mcc::impl
{
enum class MccPZoneErrorCode : int { ERROR_OK, ERROR_NULLPTR, ERROR_COORD_TRANSFROM, ERROR_NO_PCM, ERROR_PCM_COMP };
} // namespace mcc::impl
namespace std
{
template <>
class is_error_code_enum<mcc::impl::MccPZoneErrorCode> : public true_type
{
};
} // namespace std
namespace mcc::impl
{
// error category
struct MccPZoneCategory : public std::error_category {
MccPZoneCategory() : std::error_category() {}
const char* name() const noexcept
{
return "ALTITUDE-LIMIT-PZ";
}
std::string message(int ec) const
{
MccPZoneErrorCode err = static_cast<MccPZoneErrorCode>(ec);
switch (err) {
case MccPZoneErrorCode::ERROR_OK:
return "OK";
case MccPZoneErrorCode::ERROR_NULLPTR:
return "input argument os nullptr";
case MccPZoneErrorCode::ERROR_COORD_TRANSFROM:
return "coordinate transformation error";
case MccPZoneErrorCode::ERROR_NO_PCM:
return "PCM was not set";
case MccPZoneErrorCode::ERROR_PCM_COMP:
return "PCM computation error";
default:
return "UNKNOWN";
}
}
static const MccPZoneCategory& get()
{
static const MccPZoneCategory constInst;
return constInst;
}
};
inline std::error_code make_error_code(MccPZoneErrorCode ec)
{
return std::error_code(static_cast<int>(ec), MccPZoneCategory::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>
{
public:
static constexpr MccProhibitedZonePolicy pzPolicy = MccProhibitedZonePolicy::PZ_POLICY_STOP;
typedef std::error_code error_t;
MccAltLimitPZ(mcc_angle_c auto const& alt_limit, mcc_angle_c auto const& latitude)
: _altLimit(MccAngle(alt_limit).normalize<MccAngle::NORM_KIND_90_90>()),
_latitude(MccAngle::normalizeAngle<MccAngle::NORM_KIND_90_90>(latitude)),
_cosALim(cos(_altLimit)),
_sinAlim(sin(_altLimit)),
_cosLat(cos(latitude)),
_sinLat(sin(latitude)),
_absLat(abs(latitude)),
_latLim(MCC_TWO_PI - _altLimit)
{
}
MccAltLimitPZ(MccAltLimitPZ&&) = default;
MccAltLimitPZ(const MccAltLimitPZ&) = default;
static constexpr std::string_view pzoneName = KIND == MccAltLimitKind::MIN_ALT_LIMIT ? "MINALT-ZONE"
: KIND == MccAltLimitKind::MAX_ALT_LIMIT ? "MAXALT-ZONE"
: "ALTLIMIT-UNKNOWN";
MccAltLimitPZ& operator=(MccAltLimitPZ&&) = default;
MccAltLimitPZ& operator=(const MccAltLimitPZ&) = default;
template <mcc_angle_c T>
T altLimit() const
requires(!std::derived_from<T, MccAngle>)
{
return _altLimit;
}
MccAngle altLimit() const
{
return _altLimit;
}
template <mcc_angle_c T>
T latitude() const
requires(!std::derived_from<T, MccAngle>)
{
return _latitude;
}
MccAngle latitude() const
{
return _latitude;
}
error_t inPZone(mcc_skypoint_c auto const& coords, bool* result)
{
if (result == nullptr) {
return MccPZoneErrorCode::ERROR_NULLPTR;
}
error_t ret = MccPZoneErrorCode::ERROR_OK;
MccSkyAZALT azalt;
auto ccte_err = coords.to(azalt);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccPZoneErrorCode::ERROR_COORD_TRANSFROM);
}
if constexpr (KIND == MccAltLimitKind::MIN_ALT_LIMIT) {
*result = azalt.y() <= _altLimit;
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) {
*result = azalt.y() >= _altLimit;
}
return ret;
}
error_t timeToPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
{
using res_t = std::remove_cvref_t<decltype(*res_time)>;
if (res_time == nullptr) {
return MccPZoneErrorCode::ERROR_NULLPTR;
}
error_t ret = MccPZoneErrorCode::ERROR_OK;
bool inzone;
ret = inPZone(coords, &inzone);
if (ret) {
return ret;
}
if (inzone) {
*res_time = res_t{0};
return ret;
}
MccSkyHADEC_OBS hadec;
auto ccte_err = coords.to(hadec);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccPZoneErrorCode::ERROR_COORD_TRANSFROM);
}
if (!doesObjectReachZone(hadec.y())) {
*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(hadec.x(), hadec.y(), false, res_time);
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one before upper
// culmination
compute(hadec.x(), hadec.y(), true, res_time);
}
return ret;
}
error_t timeFromPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
{
using res_t = std::remove_cvref_t<decltype(*res_time)>;
if (res_time == nullptr) {
return MccPZoneErrorCode::ERROR_NULLPTR;
}
error_t ret = MccPZoneErrorCode::ERROR_OK;
bool inzone;
ret = inPZone(coords, &inzone);
if (ret) {
return ret;
}
if (!inzone) {
*res_time = res_t{0};
return ret;
}
MccSkyHADEC_OBS hadec;
auto ccte_err = coords.to(hadec);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccPZoneErrorCode::ERROR_COORD_TRANSFROM);
}
if (!doesObjectExitFromZone(hadec.y())) {
*res_time = MCC_INFINITE_DURATION_V<res_t>;
return ret;
}
if (!doesObjectReachZone(hadec.y())) {
*res_time = res_t{0};
return ret;
}
if constexpr (KIND ==
MccAltLimitKind::MIN_ALT_LIMIT) { // the closest time point is one before upper culmination
compute(hadec.x(), hadec.y(), true, res_time);
} else if constexpr (KIND == MccAltLimitKind::MAX_ALT_LIMIT) { // the closest time point is one after upper
// culmination
compute(hadec.x(), hadec.y(), false, res_time);
}
return ret;
}
protected:
double _altLimit, _cosALim, _sinAlim;
double _latitude, _cosLat, _sinLat, _absLat, _latLim;
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 += MCC_TWO_PI;
}
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)};
}
};
static_assert(std::is_copy_assignable_v<MccAltLimitPZ<MccAltLimitKind::MIN_ALT_LIMIT>>);
/*
to be serialized in format:
ALT-LIMIT<seq-delim>LATITUDE
*/
template <MccAltLimitKind KIND>
struct MccSerializer<MccAltLimitPZ<KIND>> : MccSerializerBase {
constexpr static std::string_view serializerName{"MCC-ALTITUDE-PZONE-SERIALIZER"};
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
error_t operator()(traits::mcc_output_char_range auto& output,
MccAltLimitPZ<KIND> const& value,
ParamsT const& params = mcc_serialization_params_t{})
{
MccSerializer<MccAngle> aser;
auto err = aser(output, value.altLimit(), params);
if (err) {
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
}
std::format_to(std::back_inserter(output), "{}", params.seq_delim);
err = aser(output, value.latitude(), params);
if (err) {
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
}
return MccSerializerErrorCode::ERROR_OK;
}
};
template <MccAltLimitKind KIND>
struct MccDeserializer<MccAltLimitPZ<KIND>> : MccDeserializerBase {
static constexpr std::string_view deserializerName{"MCC-ALTITUDE-PZONE-DESERIALIZER"};
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
error_t operator()(traits::mcc_input_char_range auto const& input,
MccAltLimitPZ<KIND>& value,
ParamsT const& params = mcc_serialization_params_t{})
{
std::vector<std::string_view> s;
MccDeserializer<MccAngle> ades;
auto seq = std::views::split(input, params.seq_delim);
if (std::ranges::distance(seq.begin(), seq.end()) > 1) {
for (auto const& seq_el : seq) {
s.push_back(utils::trimSpaces(seq_el, utils::TrimType::TRIM_BOTH));
}
MccAngle alt, lat;
auto err = ades(s[0], alt, params);
if (err) {
return MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER;
}
err = ades(s[1], lat, params);
if (err) {
return MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER;
}
value = MccAltLimitPZ<KIND>(alt, lat);
} else {
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
}
return MccDeserializerErrorCode::ERROR_OK;
}
};
/* co-longitude axis (HA or AZ) and co-latitude (DEC, ZD) limit switch prohibited zone */
template <MccCoordKind AXIS_KIND>
class MccAxisLimitSwitchPZ : public mcc_pzone_interface_t<std::error_code>
{
public:
static_assert(AXIS_KIND == MccCoordKind::COORDS_KIND_AZ || AXIS_KIND == MccCoordKind::COORDS_KIND_HA_OBS ||
AXIS_KIND == MccCoordKind::COORDS_KIND_ZD || AXIS_KIND == MccCoordKind::COORDS_KIND_DEC_OBS,
"UNSUPPORTED AXIS TYPE!");
typedef std::error_code error_t;
static constexpr MccCoordKind axisKind = AXIS_KIND;
static constexpr MccProhibitedZonePolicy pzPolicy = MccProhibitedZonePolicy::PZ_POLICY_FLIP;
//
// min_limit_val and max_limit_val are hardware encoder angles!
// pcm is nullptr or pointer to mcc_pcm_c
//
template <typename PCM_T>
MccAxisLimitSwitchPZ(mcc_angle_c auto const& min_limit_val, mcc_angle_c auto const& max_limit_val, PCM_T pcm)
requires(std::is_null_pointer_v<PCM_T> || mcc_pcm_c<std::remove_pointer_t<PCM_T>>)
: _minLimit(min_limit_val), _maxLimit(max_limit_val)
{
if constexpr (std::is_null_pointer_v<PCM_T>) {
_correctForPCM = [](MccSkyPoint const&, MccGenXY*) -> error_t { return MccPZoneErrorCode::ERROR_NO_PCM; };
} else {
_correctForPCM = [pcm](MccSkyPoint const& skypt, MccGenXY* hw_coords) {
struct pcm_res_t {
double pcmX, pcmY;
};
pcm_res_t res;
auto err = pcm->computeInversePCM(skypt, &res, hw_coords);
return mcc_deduced_err(err, MccPZoneErrorCode::ERROR_PCM_COMP);
};
}
}
static constexpr std::string_view pzoneName =
axisKind == MccCoordKind::COORDS_KIND_AZ ? "AZ-AXIS-LIMITSWITCH-ZONE"
: axisKind == MccCoordKind::COORDS_KIND_HA_OBS ? "HA-AXIS-LIMITSWITCH-ZONE"
: axisKind == MccCoordKind::COORDS_KIND_ZD ? "ZD-AXIS-LIMITSWITCH-ZONE"
: axisKind == MccCoordKind::COORDS_KIND_DEC_OBS ? "DEC-AXIS-LIMITSWITCH-ZONE"
: "UKNOWN";
MccAxisLimitSwitchPZ(const MccAxisLimitSwitchPZ&) = default;
MccAxisLimitSwitchPZ(MccAxisLimitSwitchPZ&&) = default;
MccAxisLimitSwitchPZ& operator=(const MccAxisLimitSwitchPZ&) = default;
MccAxisLimitSwitchPZ& operator=(MccAxisLimitSwitchPZ&&) = default;
template <mcc_angle_c T>
T minLimit() const
requires(!std::derived_from<T, MccAngle>)
{
return _minLimit;
}
MccAngle minLimit() const
{
return _minLimit;
}
template <mcc_angle_c T>
T maxLimit() const
requires(!std::derived_from<T, MccAngle>)
{
return _maxLimit;
}
MccAngle maxLimit() const
{
return _maxLimit;
}
template <typename PCM_T>
void setPCM(PCM_T pcm)
requires(std::is_null_pointer_v<PCM_T> || mcc_pcm_c<std::remove_pointer_t<PCM_T>>)
{
if constexpr (std::is_null_pointer_v<PCM_T>) {
_correctForPCM = [](MccSkyPoint const&, MccGenXY*) -> error_t { return MccPZoneErrorCode::ERROR_NO_PCM; };
} else {
_correctForPCM = [pcm](MccSkyPoint const& skypt, MccGenXY* hw_coords) {
struct pcm_res_t {
double pcmX, pcmY;
};
pcm_res_t res;
auto err = pcm->computeInversePCM(skypt, &res, hw_coords);
return mcc_deduced_err(err, MccPZoneErrorCode::ERROR_PCM_COMP);
};
}
}
error_t inPZone(mcc_skypoint_c auto const& coords, bool* result)
{
if (result == nullptr) {
return MccPZoneErrorCode::ERROR_NULLPTR;
}
MccGenXY xy;
if (coords.pairKind() != MccCoordPairKind::COORDS_KIND_XY) {
auto err = _correctForPCM(coords, &xy);
if (err) {
return err;
}
} else { // 'coords' is interpretated as hardware (encoder readout) coordinates
coords.to(xy);
}
double x = xy.x();
*result = (x > _maxLimit) || (x < _minLimit);
return MccPZoneErrorCode::ERROR_OK;
}
// time to reach maximal limit
error_t timeToPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
{
return _computeTime(coords, res_time, false);
}
// time to reach minimal limit
error_t timeFromPZone(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time)
{
return _computeTime(coords, res_time, true);
}
protected:
double _minLimit, _maxLimit;
std::function<error_t(MccSkyPoint const&, MccGenXY*)> _correctForPCM{};
error_t _computeTime(mcc_skypoint_c auto const& coords, traits::mcc_time_duration_c auto* res_time, bool from_time)
{
using res_t = std::remove_cvref_t<decltype(*res_time)>;
using period_t = typename res_t::period;
double time_ang;
if (res_time == nullptr) {
return MccPZoneErrorCode::ERROR_NULLPTR;
}
MccGenXY xy;
if (coords.pairKind() != MccCoordPairKind::COORDS_KIND_XY) {
auto err = _correctForPCM(coords, &xy);
if (err) {
return err;
}
} else { // 'coords' is interpretated as hardware (encoder readout) coordinates
coords.to(xy);
}
if constexpr (AXIS_KIND == MccCoordKind::COORDS_KIND_HA_OBS) {
double x = xy.x();
if (from_time) { // timeFromPZone
time_ang = (_minLimit - x) / MCC_SIDERAL_TO_UT1_RATIO; // to UT1 scale
} else { // timeToPZone
time_ang = (_maxLimit - x) / MCC_SIDERAL_TO_UT1_RATIO; // to UT1 scale
}
} else if constexpr (AXIS_KIND == MccCoordKind::COORDS_KIND_DEC_OBS) {
double y = xy.y();
// TODO: !!!!!!!!!!!!!!!!!!!!!!!!
// assume here sideral speed moving! so, observed DEC is near constant
if (from_time) { // timeFromPZone
if (y < _minLimit || y > _maxLimit) {
time_ang = std::numeric_limits<double>::max();
} else {
time_ang = 0;
}
} else { // timeToPZone
if (y < _minLimit || y > _maxLimit) { // already in the zone
time_ang = 0;
} else {
time_ang = std::numeric_limits<double>::max();
}
}
} else if constexpr (AXIS_KIND == MccCoordKind::COORDS_KIND_AZ) {
static_assert(false, "NOT IMPLEMENTED YET!!!");
} else {
static_assert(false, "NOT IMPLEMENTED YET!!!");
}
std::chrono::nanoseconds ns{
static_cast<std::chrono::nanoseconds::rep>(time_ang * 43200.0 / std::numbers::pi * 1.0E9)};
period_t rat;
*res_time = res_t{static_cast<typename res_t::rep>(time_ang * 43200.0 / std::numbers::pi * rat.den / rat.num)};
return MccPZoneErrorCode::ERROR_OK;
}
};
static_assert(std::is_copy_assignable_v<MccAxisLimitSwitchPZ<MccCoordKind::COORDS_KIND_HA_OBS>>);
static_assert(std::is_copy_assignable_v<MccAxisLimitSwitchPZ<MccCoordKind::COORDS_KIND_DEC_OBS>>);
/*
to be serialized in format:
MIN_LIMIT<seq-delim>MAX_LIMIT
*/
template <MccCoordKind AXIS_KIND>
struct MccSerializer<MccAxisLimitSwitchPZ<AXIS_KIND>> : MccSerializerBase {
constexpr static std::string_view serializerName{"MCC-AXIS-LIMIT-SWITCH-SERIALIZER"};
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
error_t operator()(traits::mcc_output_char_range auto& output,
MccAxisLimitSwitchPZ<AXIS_KIND> const& value,
ParamsT const& params = mcc_serialization_params_t{})
{
MccSerializer<MccAngle> aser;
auto err = aser(output, value.minLimit(), params);
if (err) {
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
}
std::format_to(std::back_inserter(output), "{}", params.seq_delim);
err = aser(output, value.maxLimit(), params);
if (err) {
return mcc_deduced_err(err, MccSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER);
}
return MccSerializerErrorCode::ERROR_OK;
}
};
template <MccCoordKind AXIS_KIND>
struct MccDeserializer<MccAxisLimitSwitchPZ<AXIS_KIND>> : MccDeserializerBase {
static constexpr std::string_view deserializerName{"MCC-AXIS-LIMIT-SWITCH-DESERIALIZER"};
template <mcc_serialization_params_c ParamsT = mcc_serialization_params_t>
error_t operator()(traits::mcc_input_char_range auto const& input,
MccAxisLimitSwitchPZ<AXIS_KIND>& value,
ParamsT const& params = mcc_serialization_params_t{})
{
std::vector<std::string_view> s;
MccDeserializer<MccAngle> ades;
auto seq = std::views::split(input, params.seq_delim);
// if (std::ranges::size(seq) < 2) {
if (std::ranges::distance(seq.begin(), seq.end()) > 1) {
for (auto const& seq_el : seq) {
s.push_back(utils::trimSpaces(seq_el, utils::TrimType::TRIM_BOTH));
}
MccAngle minLim, maxLim;
auto err = ades(s[0], minLim, params);
if (err) {
return MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER;
}
err = ades(s[1], maxLim, params);
if (err) {
return MccDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER;
}
// WARNING: the class below is created without the PCM-class pointer!!!
value = MccAxisLimitSwitchPZ<AXIS_KIND>(minLim, maxLim, nullptr);
} else {
return MccDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE;
}
return MccDeserializerErrorCode::ERROR_OK;
}
};
} // namespace mcc::impl