360 lines
12 KiB
C++
360 lines
12 KiB
C++
|
|
#pragma once
|
|
|
|
|
|
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
|
|
|
/* A REFERENCE "POINTING-CORRECTION-MODEL" CLASS IMPLEMENTATION */
|
|
|
|
|
|
#include <mutex>
|
|
#include "bsplines/mcc_bsplines.h"
|
|
#include "mcc_defaults.h"
|
|
#include "mcc_generics.h"
|
|
|
|
namespace mcc
|
|
{
|
|
|
|
enum class MccDefaultPCMErrorCode : int {
|
|
ERROR_OK,
|
|
ERROR_INVALID_INPUTS_BISPLEV,
|
|
ERROR_EXCEED_MAX_ITERS,
|
|
ERROR_NULLPTR
|
|
};
|
|
|
|
/* error category definition */
|
|
|
|
// error category
|
|
struct MccDefaultPCMCategory : public std::error_category {
|
|
MccDefaultPCMCategory() : std::error_category() {}
|
|
|
|
const char* name() const noexcept
|
|
{
|
|
return "ADC_GENERIC_DEVICE";
|
|
}
|
|
|
|
std::string message(int ec) const
|
|
{
|
|
MccDefaultPCMErrorCode err = static_cast<MccDefaultPCMErrorCode>(ec);
|
|
|
|
switch (err) {
|
|
case MccDefaultPCMErrorCode::ERROR_OK:
|
|
return "OK";
|
|
case MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV:
|
|
return "invalid input arguments for bispev";
|
|
case MccDefaultPCMErrorCode::ERROR_EXCEED_MAX_ITERS:
|
|
return "exceed maximum of iterations number";
|
|
case MccDefaultPCMErrorCode::ERROR_NULLPTR:
|
|
return "nullptr input argument";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static const MccDefaultPCMCategory& get()
|
|
{
|
|
static const MccDefaultPCMCategory constInst;
|
|
return constInst;
|
|
}
|
|
};
|
|
|
|
|
|
inline std::error_code make_error_code(MccDefaultPCMErrorCode ec)
|
|
{
|
|
return std::error_code(static_cast<int>(ec), MccDefaultPCMCategory::get());
|
|
}
|
|
} // namespace mcc
|
|
|
|
|
|
namespace std
|
|
{
|
|
|
|
template <>
|
|
class is_error_code_enum<mcc::MccDefaultPCMErrorCode> : public true_type
|
|
{
|
|
};
|
|
|
|
} // namespace std
|
|
|
|
|
|
|
|
namespace mcc
|
|
{
|
|
|
|
|
|
// type of PCM corrections (algorithm used):
|
|
// PCM_TYPE_GEOMETRY - "classic" geometry-based correction coefficients
|
|
// PCM_TYPE_GEOMETRY_BSPLINE - previous one and additional 2D B-spline corrections
|
|
// PCM_TYPE_BSPLINE - pure 2D B-spline corrections
|
|
enum class MccDefaultPCMType { PCM_TYPE_GEOMETRY, PCM_TYPE_GEOMETRY_BSPLINE, PCM_TYPE_BSPLINE };
|
|
|
|
template <MccDefaultPCMType TYPE>
|
|
static constexpr std::string_view MccDefaultPCMTypeString =
|
|
TYPE == MccDefaultPCMType::PCM_TYPE_GEOMETRY ? "GEOMETRY"
|
|
: TYPE == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE ? "GEOMETRY-BSPLINE"
|
|
: TYPE == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE ? "BSPLINE"
|
|
: "UNKNOWN";
|
|
|
|
template <MccMountType MOUNT_TYPE>
|
|
class MccDefaultPCM : public mcc_PCM_interface_t<std::error_code>
|
|
{
|
|
public:
|
|
static constexpr MccMountType mountType = MOUNT_TYPE;
|
|
|
|
typedef std::error_code error_t;
|
|
typedef double coord_t;
|
|
|
|
|
|
// "classic" geometric PEC coefficients
|
|
struct pcm_geom_coeffs_t {
|
|
typedef double coeff_t;
|
|
|
|
coeff_t zeroPointX;
|
|
coeff_t zeroPointY;
|
|
coeff_t collimationErr; // tube collimation error
|
|
coeff_t nonperpendErr; // X-Y axes nonperpendicularity
|
|
coeff_t misalignErr1; // misalignment of hour-angle/azimuth axis: left-right for equatorial, East-West for
|
|
// alt-azimuthal
|
|
coeff_t misalignErr2; // misalignment of hour-angle/azimuth axis: vertical for equatorial, North-South for
|
|
// alt-azimuthal
|
|
|
|
coeff_t tubeFlexure;
|
|
coeff_t forkFlexure;
|
|
coeff_t DECaxisFlexure; // declination axis flexure
|
|
};
|
|
|
|
// B-splines related data structure (coefficients, knots ...)
|
|
struct pcm_bspline_t {
|
|
typedef double knot_t;
|
|
typedef double coeff_t;
|
|
|
|
size_t bsplDegreeX = 3;
|
|
size_t bsplDegreeY = 3;
|
|
|
|
std::vector<knot_t> knotsX{};
|
|
std::vector<knot_t> knotsY{};
|
|
|
|
std::vector<coeff_t> coeffsX{};
|
|
std::vector<coeff_t> coeffsY{};
|
|
};
|
|
|
|
struct pcm_data_t {
|
|
MccDefaultPCMType type{MccDefaultPCMType::PCM_TYPE_GEOMETRY};
|
|
double siteLatitude{0.0}; // in radians
|
|
pcm_geom_coeffs_t geomCoefficients{};
|
|
pcm_bspline_t bspline{};
|
|
};
|
|
|
|
// constructors
|
|
|
|
MccDefaultPCM() : _pcmDataMutex(new std::mutex) {}
|
|
MccDefaultPCM(pcm_data_t pdata) : MccDefaultPCM()
|
|
{
|
|
_pcmData = std::move(pdata);
|
|
}
|
|
|
|
MccDefaultPCM(MccDefaultPCM&& other) = default;
|
|
MccDefaultPCM& operator=(MccDefaultPCM&& other) = default;
|
|
|
|
MccDefaultPCM(const MccDefaultPCM&) = delete;
|
|
MccDefaultPCM& operator=(const MccDefaultPCM&) = delete;
|
|
|
|
virtual ~MccDefaultPCM() = default;
|
|
|
|
void setPCMData(pcm_data_t pdata)
|
|
{
|
|
std::lock_guard lock(*_pcmDataMutex);
|
|
|
|
_pcmData = std::move(pdata);
|
|
}
|
|
|
|
|
|
pcm_data_t getPCMData() const
|
|
{
|
|
std::lock_guard lock(*_pcmDataMutex);
|
|
|
|
return _pcmData;
|
|
}
|
|
|
|
void setPCMType(MccDefaultPCMType type)
|
|
{
|
|
std::lock_guard lock(*_pcmDataMutex);
|
|
|
|
_pcmData.type = type;
|
|
}
|
|
|
|
MccDefaultPCMType getPCMType() const
|
|
{
|
|
std::lock_guard lock(*_pcmDataMutex);
|
|
|
|
return _pcmData.type;
|
|
}
|
|
|
|
// The computed PCM quantities must be interpretated as:
|
|
// apparent_X = encoder_X + res.pcmX
|
|
// apparent_Y = encoder_Y + res.pcmY
|
|
// so, input x and y are assumed to be mount axis encoder coordinates
|
|
template <typename T>
|
|
error_t computePCM(mcc_celestial_point_c auto pt, mcc_PCM_result_c auto* res, T* app_pt = nullptr)
|
|
requires(mcc_celestial_point_c<T> || mcc_eqt_hrz_coord_c<T>)
|
|
{
|
|
if (res == nullptr) {
|
|
return MccDefaultPCMErrorCode::ERROR_NULLPTR;
|
|
}
|
|
|
|
std::lock_guard lock(*_pcmDataMutex);
|
|
|
|
res->pcmX = 0.0;
|
|
res->pcmY = 0.0;
|
|
|
|
if constexpr (mcc_is_equatorial_mount<MOUNT_TYPE>) { // equatorial
|
|
if (_pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY ||
|
|
_pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE) {
|
|
const auto cosPhi = std::cos(_pcmData.siteLatitude);
|
|
const auto sinPhi = std::sin(_pcmData.siteLatitude);
|
|
const auto tanY = std::tan(pt.Y);
|
|
const auto sinX = std::sin(pt.X);
|
|
const auto cosX = std::cos(pt.X);
|
|
const auto cosY = std::cos(pt.Y);
|
|
|
|
|
|
if (utils::isEqual(cosY, 0.0)) {
|
|
res->pcmX = _pcmData.geomCoefficients.zeroPointX;
|
|
} else {
|
|
res->pcmX = _pcmData.geomCoefficients.zeroPointX + _pcmData.geomCoefficients.collimationErr / cosY +
|
|
_pcmData.geomCoefficients.nonperpendErr * tanY -
|
|
_pcmData.geomCoefficients.misalignErr1 * cosX * tanY +
|
|
_pcmData.geomCoefficients.misalignErr2 * sinX * tanY +
|
|
_pcmData.geomCoefficients.tubeFlexure * cosPhi * sinX / cosY -
|
|
_pcmData.geomCoefficients.DECaxisFlexure * (cosPhi * cosX + sinPhi * tanY);
|
|
}
|
|
|
|
res->pcmY = _pcmData.geomCoefficients.zeroPointY + _pcmData.geomCoefficients.misalignErr1 * sinX +
|
|
_pcmData.geomCoefficients.misalignErr2 * cosX +
|
|
_pcmData.geomCoefficients.tubeFlexure * (cosPhi * cosX * std::sin(pt.Y) - sinPhi * cosY);
|
|
|
|
if constexpr (mountType == MccMountType::FORK_TYPE) {
|
|
if (!utils::isEqual(cosX, 0.0)) {
|
|
res->pcmY += _pcmData.geomCoefficients.forkFlexure / cosX;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_pcmData.type == MccDefaultPCMType::PCM_TYPE_BSPLINE ||
|
|
_pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE) {
|
|
double spl_valX, spl_valY;
|
|
|
|
int ret = bsplines::fitpack_eval_spl2d(_pcmData.bspline.knotsX, _pcmData.bspline.knotsY,
|
|
_pcmData.bspline.coeffsX, pt.X, pt.Y, spl_valX,
|
|
_pcmData.bspline.bsplDegreeX, _pcmData.bspline.bsplDegreeY);
|
|
|
|
if (ret) {
|
|
res->pcmX = std::numeric_limits<double>::quiet_NaN();
|
|
res->pcmY = std::numeric_limits<double>::quiet_NaN();
|
|
return MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV;
|
|
}
|
|
|
|
|
|
ret = bsplines::fitpack_eval_spl2d(_pcmData.bspline.knotsX, _pcmData.bspline.knotsY,
|
|
_pcmData.bspline.coeffsY, pt.X, pt.Y, spl_valY,
|
|
_pcmData.bspline.bsplDegreeX, _pcmData.bspline.bsplDegreeY);
|
|
|
|
if (ret) {
|
|
res->pcmX = std::numeric_limits<double>::quiet_NaN();
|
|
res->pcmY = std::numeric_limits<double>::quiet_NaN();
|
|
return MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV;
|
|
}
|
|
|
|
|
|
res->pcmX += spl_valX;
|
|
res->pcmY += spl_valY;
|
|
}
|
|
} else if constexpr (mcc_is_altaz_mount<MOUNT_TYPE>) {
|
|
static_assert(false, "NOT IMPLEMENTED!");
|
|
} else {
|
|
static_assert(false, "UNSUPPORTED");
|
|
}
|
|
|
|
if (app_pt != nullptr) {
|
|
if constexpr (mcc_eqt_hrz_coord_c<T>) {
|
|
if constexpr (mccIsEquatorialMount(mountType)) {
|
|
app_pt->HA = pt.X + res->pcmX;
|
|
app_pt->DEC_APP = pt.Y + res->pcmY;
|
|
} else if constexpr (mccIsAltAzMount(mountType)) {
|
|
app_pt->AZ = pt.X + res->pcmX;
|
|
app_pt->ZD = pt.Y + res->pcmY;
|
|
} else {
|
|
static_assert(false, "UNKNOW MOUNT TYPE!");
|
|
}
|
|
} else {
|
|
app_pt->X = pt.X + res->pcmX;
|
|
app_pt->Y = pt.Y + res->pcmY;
|
|
}
|
|
}
|
|
|
|
|
|
return MccDefaultPCMErrorCode::ERROR_OK;
|
|
}
|
|
|
|
template <typename T>
|
|
error_t computeInversePCM(T app_pt, mcc_PCM_result_c auto* result, mcc_celestial_point_c auto* hw_pt = nullptr)
|
|
requires(mcc_celestial_point_c<T> || mcc_eqt_hrz_coord_c<T>)
|
|
{
|
|
// for small corrections only!!!
|
|
if constexpr (mcc_eqt_hrz_coord_c<T>) {
|
|
if constexpr (mcc_is_equatorial_mount<MOUNT_TYPE>) {
|
|
app_pt.X = app_pt.HA;
|
|
app_pt.Y = app_pt.DEC_APP;
|
|
} else if constexpr (mcc_is_altaz_mount<MOUNT_TYPE>) {
|
|
app_pt.X = app_pt.AZ;
|
|
app_pt.Y = app_pt.ZD;
|
|
}
|
|
}
|
|
|
|
MccCelestialPoint cp;
|
|
auto ret = computePCM(app_pt, result, &cp);
|
|
// auto ret = computePCM(std::move(app_pt), result, hw_pt);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
result->pcmX = -result->pcmX;
|
|
result->pcmY = -result->pcmY;
|
|
|
|
if (hw_pt != nullptr) {
|
|
if constexpr (mcc_eqt_hrz_coord_c<T>) {
|
|
if constexpr (mccIsEquatorialMount(mountType)) {
|
|
hw_pt->X = app_pt.HA + result->pcmX;
|
|
hw_pt->Y = app_pt.DEC_APP + result->pcmY;
|
|
} else if constexpr (mccIsAltAzMount(mountType)) {
|
|
hw_pt->X = app_pt.AZ + result->pcmX;
|
|
hw_pt->Y = app_pt.ZD + result->pcmY;
|
|
} else {
|
|
static_assert(false, "UNKNOW MOUNT TYPE!");
|
|
}
|
|
} else {
|
|
hw_pt->X = app_pt.X + result->pcmX;
|
|
hw_pt->Y = app_pt.Y + result->pcmY;
|
|
}
|
|
}
|
|
|
|
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
pcm_data_t _pcmData;
|
|
|
|
std::unique_ptr<std::mutex> _pcmDataMutex;
|
|
};
|
|
|
|
|
|
typedef MccDefaultPCM<MccMountType::ALTAZ_TYPE> MccMountDefaultAltAzPec;
|
|
typedef MccDefaultPCM<MccMountType::FORK_TYPE> MccMountDefaultForkPec;
|
|
|
|
static_assert(mcc_PCM_c<MccMountDefaultForkPec>, "");
|
|
static_assert(std::movable<MccMountDefaultForkPec>);
|
|
|
|
|
|
} // namespace mcc
|