add mcc directory

This commit is contained in:
Timur A. Fatkhullin 2025-08-18 02:10:43 +03:00
parent 61e41b1a1d
commit 6c10c6b6ff
10 changed files with 2832 additions and 10 deletions

View File

@ -13,3 +13,4 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
# ******* C++ PART OF THE PROJECT ******* # ******* C++ PART OF THE PROJECT *******
add_subdirectory(cxx) add_subdirectory(cxx)
add_subdirectory(mcc)

View File

@ -58,7 +58,10 @@ namespace mcc::astrom::erfa
struct MccMountAstromEngineERFACategory : public std::error_category { struct MccMountAstromEngineERFACategory : public std::error_category {
MccMountAstromEngineERFACategory() : std::error_category() {} MccMountAstromEngineERFACategory() : std::error_category() {}
const char* name() const noexcept { return "ADC_GENERIC_DEVICE"; } const char* name() const noexcept
{
return "ADC_GENERIC_DEVICE";
}
std::string message(int ec) const std::string message(int ec) const
{ {
@ -254,7 +257,10 @@ public:
/* time-related methods */ /* time-related methods */
static time_point_t timePointNow() { return time_point_t::clock::now(); } static time_point_t timePointNow()
{
return time_point_t::clock::now();
}
// templated generic version // templated generic version
template <mcc::traits::mcc_systime_c TpT> template <mcc::traits::mcc_systime_c TpT>
@ -587,10 +593,10 @@ public:
// special case: to ICRS from apparent // special case: to ICRS from apparent
if (coord_to_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { if (coord_to_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
if (coord_to_kind == MccCoordPairKind::COORDS_KIND_AZALT || if (coord_from_kind == MccCoordPairKind::COORDS_KIND_AZALT ||
coord_to_kind == MccCoordPairKind::COORDS_KIND_AZZD || coord_from_kind == MccCoordPairKind::COORDS_KIND_AZZD ||
coord_to_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP || coord_from_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP ||
coord_to_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) { coord_from_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
// //
ret = greg2jul(time_point_from, jd); ret = greg2jul(time_point_from, jd);
if (!ret) { if (!ret) {
@ -718,14 +724,26 @@ public:
/* helper mathods */ /* helper mathods */
auto leapSecondsExpireDate() const { return _currentState._leapSeconds.expireDate(); } auto leapSecondsExpireDate() const
{
return _currentState._leapSeconds.expireDate();
}
auto leapSecondsExpireMJD() const { return _currentState._leapSeconds.expireMJD(); } auto leapSecondsExpireMJD() const
{
return _currentState._leapSeconds.expireMJD();
}
auto bulletinADateRange() const { return _currentState._bulletinA.dateRange(); } auto bulletinADateRange() const
{
return _currentState._bulletinA.dateRange();
}
auto bulletinADateRangeMJD() const { return _currentState._bulletinA.dateRangeMJD(); } auto bulletinADateRangeMJD() const
{
return _currentState._bulletinA.dateRangeMJD();
}
protected: protected:
engine_state_t _currentState{}; engine_state_t _currentState{};

73
mcc/CMakeLists.txt Normal file
View File

@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.14)
# set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
# ******* SPDLOG LIBRARY *******
include(FetchContent)
include(ExternalProject)
set(SPDLOG_USE_STD_FORMAT ON CACHE INTERNAL "Use of C++20 std::format")
set(SPDLOG_FMT_EXTERNAL OFF CACHE INTERNAL "Turn off external fmt library")
FetchContent_Declare(spdlog
# ExternalProject_Add(spdlog
# SOURCE_DIR ${CMAKE_BINARY_DIR}/spdlog_lib
# BINARY_DIR ${CMAKE_BINARY_DIR}/spdlog_lib/build
GIT_REPOSITORY "https://github.com/gabime/spdlog.git"
GIT_TAG "v1.15.1"
GIT_SHALLOW TRUE
GIT_SUBMODULES ""
GIT_PROGRESS TRUE
CMAKE_ARGS "-DSPDLOG_USE_STD_FORMAT=ON -DSPDLOG_FMT_EXTERNAL=OFF"
# CONFIGURE_COMMAND ""
# BUILD_COMMAND ""
# INSTALL_COMMAND ""
# UPDATE_COMMAND ""
# SOURCE_SUBDIR cmake # turn off building
OVERRIDE_FIND_PACKAGE
)
find_package(spdlog CONFIG)
# ******* ERFA LIBRARY *******
ExternalProject_Add(erfalib1
PREFIX ${CMAKE_BINARY_DIR}/erfa_lib1
GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
GIT_TAG "v2.0.1"
UPDATE_COMMAND ""
PATCH_COMMAND ""
# BINARY_DIR erfa_build
# SOURCE_DIR erfa
# INSTALL_DIR
LOG_CONFIGURE 1
CONFIGURE_COMMAND meson setup --reconfigure -Ddefault_library=static -Dbuildtype=release
-Dprefix=${CMAKE_BINARY_DIR}/erfa_lib -Dlibdir= -Dincludedir= -Ddatadir= <SOURCE_DIR>
# CONFIGURE_COMMAND meson setup --reconfigure -Ddefault_library=static -Dbuildtype=release -Dc_args='-march=native' -Doptimization=3
# -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib -Dlibdir= -Dincludedir= -Ddatadir= <SOURCE_DIR>
BUILD_COMMAND ninja -C <BINARY_DIR>
INSTALL_COMMAND meson install -C <BINARY_DIR>
BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/erfa_lib1/liberfa.a
)
add_library(ERFA_LIB STATIC IMPORTED)
set_target_properties(ERFA_LIB PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/erfa_lib1/liberfa.a)
add_dependencies(ERFA_LIB erfalib)
set(ERFA_INCLUDE_DIR ${CMAKE_BINARY_DIR}/erfa_lib1)
include_directories(${ERFA_INCLUDE_DIR})
set(MCC_LIBRARY_SRC1 mcc_generics.h mcc_defaults.h mcc_traits.h mcc_utils.h mcc_ccte_iers.h mcc_ccte_iers_default.h)
set(MCC_LIBRARY1 mcc1)
add_library(${MCC_LIBRARY1} INTERFACE ${MCC_LIBRARY_SRC1})
target_compile_features(${MCC_LIBRARY1} INTERFACE cxx_std_23)
target_include_directories(${MCC_LIBRARY1} INTERFACE ${ERFA_INCLUDE_DIR})

541
mcc/mcc_ccte_erfa.h Normal file
View File

@ -0,0 +1,541 @@
#pragma once
/* MOUNT CONTROL COMPONENTS LIBRARY */
/* CELESTIAL COORDINATES TRANSFORMATION ENGINE IMPLEMENTATION BASED ON ERFA LIBRARY */
#include <erfa.h>
#include <erfam.h>
#include <mutex>
#include "mcc_ccte_iers.h"
#include "mcc_defaults.h"
namespace mcc::ccte::erfa
{
enum class MccCCTE_ERFAErrorCode : int {
ERROR_OK = 0,
ERROR_INVALID_INPUT_ARG,
ERROR_julday_INVALID_YEAR,
ERROR_julday_INVALID_MONTH,
ERROR_julday_INVALID_DAY,
ERROR_UNSUPPORTED_COORD_PAIR,
ERROR_BULLETINA_OUT_OF_RANGE,
ERROR_LEAPSECONDS_OUT_OF_RANGE,
ERROR_DUBIOUS_YEAR,
ERROR_UNACCEPTABLE_DATE,
ERROR_UPDATE_LEAPSECONDS,
ERROR_UPDATE_BULLETINA,
ERROR_UNEXPECTED
};
} // namespace mcc::ccte::erfa
namespace std
{
template <>
class is_error_code_enum<mcc::ccte::erfa::MccCCTE_ERFAErrorCode> : public true_type
{
};
} // namespace std
namespace mcc::ccte::erfa
{
/* error category definition */
// error category
struct MccCCTE_ERFACategory : public std::error_category {
MccCCTE_ERFACategory() : std::error_category() {}
const char* name() const noexcept
{
return "CCTE-ERFA";
}
std::string message(int ec) const
{
MccCCTE_ERFAErrorCode err = static_cast<MccCCTE_ERFAErrorCode>(ec);
switch (err) {
case MccCCTE_ERFAErrorCode::ERROR_OK:
return "OK";
case MccCCTE_ERFAErrorCode::ERROR_INVALID_INPUT_ARG:
return "invalid argument";
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_YEAR:
return "invalid year number";
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_MONTH:
return "invalid month number";
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_DAY:
return "invalid day number";
case MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR:
return "unsupported coordinate pair";
case MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE:
return "time point is out of range";
case MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE:
return "time point is out of range";
case MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR:
return "dubious year";
case MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE:
return "unacceptable date";
case MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS:
return "leap seconds update error";
case MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA:
return "bulletin A update error";
case MccCCTE_ERFAErrorCode::ERROR_UNEXPECTED:
return "unexpected error value";
default:
return "UNKNOWN";
}
}
static const MccCCTE_ERFACategory& get()
{
static const MccCCTE_ERFACategory constInst;
return constInst;
}
};
inline std::error_code make_error_code(MccCCTE_ERFAErrorCode ec)
{
return std::error_code(static_cast<int>(ec), MccCCTE_ERFACategory::get());
}
class MccCCTE_ERFA
{
public:
static constexpr double DEFAULT_WAVELENGTH = 0.55; // default observed wavelength in mkm
typedef std::error_code error_t;
struct refract_model_t {
static constexpr std::string_view name()
{
return "ERFA";
}
double refa, refb;
};
/* use of the same type for representation of celestial and geodetic coordinates, celestial angles (e.g. P.A.),
* and sideral time */
typedef MccCelestialPoint::coord_t coord_t;
// meteo parameters (to compute refraction)
struct meteo_t {
typedef double temp_t;
typedef double humid_t;
typedef double press_t;
temp_t temperature; // Temperature in C
humid_t humidity; // humidity in % ([0.0, 1.0])
press_t pressure; // atmospheric presure in hPa=mB
};
struct engine_state_t {
meteo_t meteo{.temperature = 0.0, .humidity = 0.5, .pressure = 1010.0};
double wavelength = DEFAULT_WAVELENGTH; // observed wavelength in mkm
coord_t lat = 0.0; // site latitude
coord_t lon = 0.0; // site longitude
double elev = 0.0; // site elevation (in meters)
mcc::ccte::iers::MccLeapSeconds _leapSeconds{};
mcc::ccte::iers::MccIersBulletinA _bulletinA{};
};
MccCCTE_ERFA() : _stateMutex(new std::mutex) {}
virtual ~MccCCTE_ERFA() = default;
// engine state related methods
void setState(engine_state_t state)
{
std::lock_guard lock{*_stateMutex};
_currentState = std::move(state);
}
engine_state_t getState() const
{
std::lock_guard lock{*_stateMutex};
return _currentState;
}
void updateMeteo(meteo_t meteo)
{
std::lock_guard lock{*_stateMutex};
_currentState.meteo = std::move(meteo);
}
error_t updateLeapSeconds(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '#')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._leapSeconds.load(stream, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t updateLeapSeconds(traits::mcc_input_char_range auto const& filename, char comment_sym = '#')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._leapSeconds.load(filename, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t updateBulletinA(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '*')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._bulletinA.load(stream, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t updateBulletinA(traits::mcc_input_char_range auto const& filename, char comment_sym = '*')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._bulletinA.load(filename, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
// time-related methods
error_t timepointToJulday(mcc_time_point_c auto tp, mcc_julday_c auto* julday)
{
auto ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (julday == nullptr) {
return ret;
}
auto days = std::chrono::floor<std::chrono::days>(tp);
std::chrono::year_month_day ymd{days};
double mjd0;
int err = eraCal2jd(ymd.year(), (unsigned)ymd.month(), (unsigned)ymd.day(), &mjd0, &julday->mjd);
if (err != 0) {
ret = err == -1 ? MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_YEAR
: err == -2 ? MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_MONTH
: err == -3 ? MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_DAY
: MccCCTE_ERFAErrorCode::ERROR_UNEXPECTED;
} else { // partial part of day
julday->mjd +=
std::chrono::duration_cast<std::chrono::duration<double, std::ratio<86400>>>(tp - days).count();
}
return ret;
}
error_t timepointToAppSideral(mcc_time_point_c auto tp, mcc_angle_c auto* st, bool islocal = false)
{
auto ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (st == nullptr) {
return ret;
}
using real_days_t = std::chrono::duration<double, std::ratio<86400>>;
MccJulianDay julday;
ret = timepointToJulday(tp, &julday);
if (ret != MccCCTE_ERFAErrorCode::ERROR_OK) {
return ret;
}
double ut1 = julday.mjd;
double tt = julday.mjd;
std::lock_guard lock{*_stateMutex};
auto dut1 = _currentState._bulletinA.DUT1(julday.mjd);
if (dut1.has_value()) {
ut1 += std::chrono::duration_cast<real_days_t>(dut1.value()).count();
} else { // out of range
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
auto tai_utc = _currentState._leapSeconds[julday.mjd];
if (tai_utc.has_value()) {
tt += std::chrono::duration_cast<real_days_t>(tai_utc.value()).count();
} else {
return MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE;
}
auto tt_tai = _currentState._bulletinA.TT_TAI();
tt += std::chrono::duration_cast<real_days_t>(tt_tai).count();
*st = eraGst06a(julday.MJD0, ut1, julday.MJD0, tt);
if (islocal) {
*st += _currentState.lon;
}
return ret;
}
// coordinates transformations
error_t transformCoordinates(mcc_celestial_point_c auto from_pt, mcc_celestial_point_c auto* to_pt)
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
MccJulianDay jd;
if (to_pt == nullptr) {
return ret;
}
// no transformations
if (from_pt.time_point == to_pt->time_point && from_pt.pair_kind == to_pt->pair_kind) {
to_pt->X = from_pt.X;
to_pt->Y = from_pt.Y;
return ret;
}
// special case: to ICRS from apparent
if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
if (from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_AZALT ||
from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD ||
from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP ||
from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
//
ret = timepointToJulday(from_pt.time_point, &jd);
if (ret) {
return ret;
}
std::lock_guard lock{*_stateMutex};
auto dut1 = _currentState._bulletinA.DUT1(jd.mjd);
if (!dut1.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
auto pol_pos = _currentState._bulletinA.polarCoords(jd.mjd);
if (!pol_pos.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
const auto arcsec2rad = std::numbers::pi / 180 / 3600;
pol_pos->x *= arcsec2rad;
pol_pos->y *= arcsec2rad;
std::string type;
switch (from_pt.pair_kind) {
case mcc::MccCoordPairKind::COORDS_KIND_AZZD:
type = "A";
break;
case mcc::MccCoordPairKind::COORDS_KIND_AZALT:
from_pt.Y = std::numbers::pi / 2.0 - from_pt.Y; // altitude to zenithal distance
type = "A";
break;
case mcc::MccCoordPairKind::COORDS_KIND_HADEC_APP:
type = "H";
break;
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_APP:
type = "R";
break;
default:
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
}
int err = eraAtoc13(type.c_str(), from_pt.X, from_pt.Y, jd.MJD0, jd.mjd, dut1->count(),
_currentState.lon, _currentState.lat, _currentState.elev, pol_pos->x, pol_pos->y,
_currentState.meteo.pressure, _currentState.meteo.temperature,
_currentState.meteo.humidity, _currentState.wavelength, &to_pt->X, &to_pt->Y);
if (err == 1) {
return MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR;
} else if (err == -1) {
return MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE;
}
} else {
ret = MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
}
return ret;
}
// special case: from ICRS to apparent
if (from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_AZALT ||
to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_AZZD ||
to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP ||
to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
//
ret = timepointToJulday(to_pt->time_point, &jd);
if (ret) {
return ret;
}
std::lock_guard lock{*_stateMutex};
auto dut1 = _currentState._bulletinA.DUT1(jd.mjd);
if (!dut1.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
auto pol_pos = _currentState._bulletinA.polarCoords(jd.mjd);
if (!pol_pos.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
const auto arcsec2rad = std::numbers::pi / 180 / 3600;
pol_pos->x *= arcsec2rad;
pol_pos->y *= arcsec2rad;
double oaz, ozd, oha, odec, ora, eo_;
int err = eraAtco13(from_pt.X, from_pt.Y, 0.0, 0.0, 0.0, 0.0, jd.MJD0, jd.mjd, dut1->count(),
_currentState.lon, _currentState.lat, _currentState.elev, pol_pos->x, pol_pos->y,
_currentState.meteo.pressure, _currentState.meteo.temperature,
_currentState.meteo.humidity, _currentState.wavelength, &oaz, &ozd, &oha, &odec,
&ora, &eo_);
if (err == 1) {
return MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR;
} else if (err == -1) {
return MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE;
}
if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_AZALT) {
to_pt->X = oaz;
to_pt->Y = std::numbers::pi / 2.0 - ozd;
} else if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
to_pt->X = oaz;
to_pt->Y = ozd;
} else if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
to_pt->X = ora;
to_pt->Y = odec;
} else if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
to_pt->X = oha;
to_pt->Y = odec;
}
} else {
ret = MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
}
return ret;
}
}
// refraction
error_t refractionModel(refract_model_t* model)
{
std::lock_guard lock{*_stateMutex};
eraRefco(_currentState.meteo.pressure, _currentState.meteo.temperature, _currentState.meteo.humidity,
_currentState.wavelength, &model->refa, &model->refb);
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t refractionCorrection(mcc_celestial_point_c auto pt, mcc_angle_c auto* dZ)
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (dZ == nullptr) {
return ret;
}
refract_model_t rmodel;
refractionModel(&rmodel);
if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
if (pt.Y >= std::numbers::pi / 2.0) {
*dZ = 35.4 / 60.0 * std::numbers::pi / 180.0; // 35.4 arcminutes
} else {
auto tanZ = tan(pt.Y);
*dZ = rmodel.refa * tanZ + rmodel.refb * tanZ * tanZ * tanZ;
}
} else {
MccCelestialPoint tr_pt{.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD, .time_point = pt.time_point};
ret = transformCoordinates(std::move(pt), &tr_pt);
if (!ret) {
ret = refractionCorrection(std::move(tr_pt), dZ);
}
}
return ret;
}
/* helper mathods */
auto leapSecondsExpireDate() const
{
return _currentState._leapSeconds.expireDate();
}
auto leapSecondsExpireMJD() const
{
return _currentState._leapSeconds.expireMJD();
}
auto bulletinADateRange() const
{
return _currentState._bulletinA.dateRange();
}
auto bulletinADateRangeMJD() const
{
return _currentState._bulletinA.dateRangeMJD();
}
protected:
engine_state_t _currentState{};
std::unique_ptr<std::mutex> _stateMutex;
};
} // namespace mcc::ccte::erfa

491
mcc/mcc_ccte_iers.h Normal file
View File

@ -0,0 +1,491 @@
#pragma once
/* MOUNT CONTROL COMPONENTS LIBRARY */
/* Classes to represent IERS bulletins
*
* BULLETIN A: https://datacenter.iers.org/data/latestVersion/bulletinA.txt
* leapseconds: https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat
*
*/
#include <chrono>
#include <fstream>
#include "mcc_ccte_iers_default.h"
#include "mcc_traits.h"
#include "mcc_utils.h"
namespace mcc::ccte::iers
{
class MccLeapSeconds final
{
public:
typedef std::chrono::system_clock::time_point time_point_t;
typedef std::chrono::duration<double> real_secs_t; // seconds duration in double
MccLeapSeconds()
{
// create default values
std::istringstream ist(defaults::MCC_DEFAULT_LEAP_SECONDS_FILE);
load(ist);
}
~MccLeapSeconds() = default;
time_point_t expireDate() const
{
return _expireDate;
}
auto expireMJD() const
{
return _expireMJD;
}
// load from stream
bool load(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '#')
{
std::istringstream is;
double mjd;
unsigned day, month;
int year;
double tai_utc;
decltype(_expireDate) edate;
std::vector<leapsecond_db_elem_t> db;
for (std::string line; std::getline(stream, line);) {
auto sv = utils::trimSpaces(line, utils::TrimType::TRIM_LEFT);
if (sv.size()) {
if (sv[0] == comment_sym) { // comment string
if (std::regex_match(line, expr_date_rx)) {
auto pos = line.find("on");
sv = utils::trimSpaces(std::string_view{line.begin() + pos + 2, line.end()},
utils::TrimType::TRIM_LEFT);
is.str({sv.begin(), sv.end()});
is >> std::chrono::parse("%d %B %Y", edate);
is.clear();
}
continue;
}
} else {
continue;
}
if (std::regex_match(line, data_rx)) {
is.str(line);
is >> mjd >> day >> month >> year >> tai_utc;
db.emplace_back(mjd, std::chrono::year_month_day{std::chrono::year{year} / month / day}, tai_utc);
// db.emplace_back(mjd,
// std::chrono::year_month_day{std::chrono::year{year}, std::chrono::month{month},
// std::chrono::day{day}},
// tai_utc);
is.clear();
continue;
}
}
if (db.empty()) { // keep previous data
return false;
}
_expireDate = std::move(edate);
// compute expire Julian Day
using namespace std::literals::chrono_literals;
std::chrono::year_month_day ymd{std::chrono::floor<std::chrono::days>(_expireDate)};
static constexpr std::chrono::year MIN_YEAR = -4799y;
if (ymd.year() < MIN_YEAR) {
return -1;
}
if (!ymd.month().ok()) {
return -2;
}
int64_t im = (unsigned)ymd.month();
int64_t id = (unsigned)ymd.day();
int64_t iy = (int)ymd.year();
int64_t my = (im - 14LL) / 12LL;
int64_t iypmy = iy + my;
// integer part of result MJD
int64_t mjd_int = (1461LL * (iypmy + 4800LL)) / 4LL + (367LL * (im - 2LL - 12LL * my)) / 12LL -
(3LL * ((iypmy + 4900LL) / 100LL)) / 4LL + id - 2432076LL;
_expireMJD = static_cast<double>(mjd_int);
_db = std::move(db);
return true;
}
bool load(traits::mcc_input_char_range auto const& filename, char comment_sym = '#')
{
std::ifstream fst(filename);
bool ok = fst.is_open();
if (!ok) {
return false;
}
ok = load(fst, comment_sym);
fst.close();
return ok;
}
// std::optional<double> operator[](const time_point_t& tp) const
std::optional<real_secs_t> operator[](const time_point_t& tp) const
{
if (tp > _expireDate) { // ???????!!!!!!!!!!!
return std::nullopt;
// return _db.back().tai_utc;
}
std::chrono::year_month_day ymd{std::chrono::floor<std::chrono::days>(tp)};
for (auto const& el : _db | std::views::reverse) {
if (ymd >= el.ymd) {
// return el.tai_utc;
return real_secs_t{el.tai_utc};
}
}
return std::nullopt;
}
// std::optional<double> operator[](const double& mjd) const
std::optional<real_secs_t> operator[](const double& mjd) const
{
double e_mjd;
if (mjd > _expireMJD) { // ???????!!!!!!!!!!!
return std::nullopt;
// return _db.back().tai_utc;
}
for (auto const& el : _db | std::views::reverse) {
if (mjd >= el.mjd) {
return real_secs_t{el.tai_utc};
}
}
return std::nullopt;
}
void dump(std::derived_from<std::basic_ostream<char>> auto& stream) const
{
stream << std::format("Leap seconds database expire date: {}", _expireDate) << '\n';
for (auto const& el : _db) {
stream << std::format("{} {} {}", el.mjd, el.ymd, el.tai_utc) << '\n';
}
}
private:
inline static const std::regex expr_date_rx{
"^ *# *File +expires +on +[0-8]{1,2} "
"+(January|February|March|April|May|June|July|August|September|October|November|December) +[0-9]{4} *$"};
inline static const std::regex data_rx{"^ *[0-9]{5,}(\\.?[0-9]+) +[0-9]{1,2} +[0-9]{1,2} +[0-9]{4} +[0-9]{1,} *$"};
time_point_t _expireDate{};
double _expireMJD{};
struct leapsecond_db_elem_t {
double mjd;
std::chrono::year_month_day ymd;
double tai_utc; // TAI-UTC in seconds
};
std::vector<leapsecond_db_elem_t> _db{};
};
class MccIersBulletinA final
{
public:
typedef std::chrono::system_clock::time_point time_point_t;
typedef std::chrono::duration<double> real_secs_t; // seconds duration in double
struct pole_pos_t {
double x, y;
};
struct date_range_t {
std::chrono::year_month_day begin;
std::chrono::year_month_day end;
};
struct date_range_mjd_t {
double begin;
double end;
};
MccIersBulletinA()
{
// create pre-defined (default-state) database
std::istringstream ist(defaults::MCC_DEFAULT_IERS_BULLETIN_A_FILE);
load(ist);
}
~MccIersBulletinA() = default;
std::chrono::system_clock::time_point bulletinDate() const
{
return _date;
}
date_range_t dateRange() const
{
return {_db.front().ymd, _db.back().ymd};
}
date_range_mjd_t dateRangeMJD() const
{
return {_db.front().mjd, _db.back().mjd};
}
// double TT_TAI() const
real_secs_t TT_TAI() const
{
return real_secs_t{_tt_tai};
}
// DUT1 = UT1 - UTC
// std::optional<double> DUT1(const time_point_t& tp) const
std::optional<real_secs_t> DUT1(const time_point_t& tp) const
{
// use of the closest date
std::chrono::year_month_day ymd{std::chrono::round<std::chrono::days>(tp)};
if (ymd < _db.front().ymd) {
return std::nullopt;
}
if (ymd > _db.back().ymd) {
return std::nullopt;
}
for (auto const& el : _db) {
if (ymd <= el.ymd) {
return real_secs_t{el.dut1};
}
}
return std::nullopt;
}
// std::optional<double> DUT1(double mjd) const
std::optional<real_secs_t> DUT1(double mjd) const
{
mjd = std::round(mjd); // round to closest integer MJD
if (mjd < _db.front().mjd) {
return std::nullopt;
}
if (mjd > _db.back().mjd) {
return std::nullopt;
}
for (auto const& el : _db) {
if (mjd <= el.mjd) {
return real_secs_t{el.dut1};
}
}
return std::nullopt;
}
std::optional<pole_pos_t> polarCoords(const time_point_t& tp) const
{
std::chrono::year_month_day ymd{std::chrono::round<std::chrono::days>(tp)};
if (ymd < _db.front().ymd) {
return std::nullopt;
}
if (ymd > _db.back().ymd) {
return std::nullopt;
}
for (auto const& el : _db) {
if (ymd <= el.ymd) {
return pole_pos_t{el.x, el.y};
}
}
return std::nullopt;
}
std::optional<pole_pos_t> polarCoords(double mjd) const
{
mjd = std::round(mjd); // round to closest integer MJD
if (mjd < _db.front().mjd) {
return std::nullopt;
}
if (mjd > _db.back().mjd) {
return std::nullopt;
}
for (auto const& el : _db) {
if (mjd <= el.mjd) {
return pole_pos_t{el.x, el.y};
}
}
return std::nullopt;
}
bool load(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '*')
{
std::vector<earth_orient_db_elem_t> db;
enum { TAB_STATE_SEEK, TAB_STATE_START };
int tab_state = TAB_STATE_SEEK;
int year;
unsigned month, day;
double mjd, x, y, dut1;
std::istringstream is;
decltype(_date) bdate;
double tt_tai;
for (std::string line; std::getline(stream, line);) {
if (line.empty()) {
continue;
}
auto sv = utils::trimSpaces(line, utils::TrimType::TRIM_LEFT);
if (sv.size()) {
if (sv[0] == comment_sym) { // comment string
continue;
}
if (tab_state == TAB_STATE_START) {
if (std::regex_match(sv.begin(), sv.end(), bull_tab_vals_rx)) {
// is.str({sv.begin(), sv.end()});
is.str(line);
is >> year >> month >> day >> mjd >> x >> y >> dut1;
db.emplace_back(mjd, std::chrono::year_month_day{std::chrono::year{year} / month / day}, x, y,
dut1);
is.clear();
} else { // end of the table - just stop parsing
break;
}
continue;
}
if (std::regex_match(sv.begin(), sv.end(), bull_date_rx)) {
is.str({sv.begin(), sv.end()});
is >> std::chrono::parse("%d %B %Y", bdate);
continue;
}
if (std::regex_match(sv.begin(), sv.end(), bull_tt_tai_rx)) {
is.str({sv.begin(), sv.end()});
std::string dummy;
is >> dummy >> dummy >> dummy >> dummy >> tt_tai;
continue;
}
if (std::regex_match(sv.begin(), sv.end(), bull_tab_title_rx)) {
tab_state = TAB_STATE_START;
continue;
}
} else { // empty string (only spaces)
continue;
}
}
if (db.empty()) {
return false;
}
_date = std::move(bdate);
_tt_tai = tt_tai;
_db = std::move(db);
return true;
}
bool load(traits::mcc_input_char_range auto const& filename, char comment_sym = '*')
{
std::ifstream fst(filename);
bool ok = fst.is_open();
if (!ok) {
return false;
}
ok = load(fst, comment_sym);
fst.close();
return ok;
}
void dump(std::derived_from<std::basic_ostream<char>> auto& stream) const
{
stream << std::format("Bulletin A issue date: {}", _date) << '\n';
stream << std::format("TT-TAI: {}", _tt_tai) << '\n';
for (auto const& el : _db) {
stream << std::format("{} {} {:6.4f} {:6.4f} {:7.5f}", el.mjd, el.ymd, el.x, el.y, el.dut1) << '\n';
}
}
private:
inline static const std::regex bull_date_rx{
"^ *[0-9]{1,2} +(January|February|March|April|May|June|July|August|September|October|November|December) "
"+[0-9]{4,} +Vol\\. +[XMLCDVI]+ +No\\. +[0-9]+ *$"};
inline static const std::regex bull_tt_tai_rx{"^ *TT += +TAI +\\+ +[0-9]+\\.[0-9]+ +seconds *$"};
inline static const std::regex bull_tab_title_rx{"^ *MJD +x\\(arcsec\\) +y\\(arcsec\\) +UT1-UTC\\(sec\\) *$"};
inline static const std::regex bull_tab_vals_rx{
"^ *[0-9]{4,} +[0-9]{1,2} +[0-9]{1,2} +[0-9]{5,} +[0-9]+\\.[0-9]+ +[0-9]+\\.[0-9]+ +[0-9]+\\.[0-9]+ *$"};
time_point_t _date;
double _tt_tai;
struct earth_orient_db_elem_t {
double mjd;
std::chrono::year_month_day ymd;
double x, y; // Polar coordinates in arcsecs
double dut1; // UT1-UTC in seconds
};
std::vector<earth_orient_db_elem_t> _db;
};
} // namespace mcc::ccte::iers

658
mcc/mcc_ccte_iers_default.h Normal file
View File

@ -0,0 +1,658 @@
#pragma once
#include <string>
namespace mcc::ccte::iers::defaults
{
// https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat
static std::string MCC_DEFAULT_LEAP_SECONDS_FILE = R"--(
# Value of TAI-UTC in second valid beetween the initial value until
# the epoch given on the next line. The last line reads that NO
# leap second was introduced since the corresponding date
# Updated through IERS Bulletin 70 issued in July 2025
#
#
# File expires on 28 June 2026
#
#
# MJD Date TAI-UTC (s)
# day month year
# --- -------------- ------
#
41317.0 1 1 1972 10
41499.0 1 7 1972 11
41683.0 1 1 1973 12
42048.0 1 1 1974 13
42413.0 1 1 1975 14
42778.0 1 1 1976 15
43144.0 1 1 1977 16
43509.0 1 1 1978 17
43874.0 1 1 1979 18
44239.0 1 1 1980 19
44786.0 1 7 1981 20
45151.0 1 7 1982 21
45516.0 1 7 1983 22
46247.0 1 7 1985 23
47161.0 1 1 1988 24
47892.0 1 1 1990 25
48257.0 1 1 1991 26
48804.0 1 7 1992 27
49169.0 1 7 1993 28
49534.0 1 7 1994 29
50083.0 1 1 1996 30
50630.0 1 7 1997 31
51179.0 1 1 1999 32
53736.0 1 1 2006 33
54832.0 1 1 2009 34
56109.0 1 7 2012 35
57204.0 1 7 2015 36
57754.0 1 1 2017 37
)--";
// https://datacenter.iers.org/data/latestVersion/bulletinA.txt
static std::string MCC_DEFAULT_IERS_BULLETIN_A_FILE = R"--(
**********************************************************************
* *
* I E R S B U L L E T I N - A *
* *
* Rapid Service/Prediction of Earth Orientation *
**********************************************************************
7 August 2025 Vol. XXXVIII No. 032
______________________________________________________________________
GENERAL INFORMATION:
MJD = Julian Date - 2 400 000.5 days
UT2-UT1 = 0.022 sin(2*pi*T) - 0.012 cos(2*pi*T)
- 0.006 sin(4*pi*T) + 0.007 cos(4*pi*T)
where pi = 3.14159265... and T is the date in Besselian years.
TT = TAI + 32.184 seconds
DUT1= (UT1-UTC) transmitted with time signals
= +0.1 seconds beginning 10 July 2025 at 0000 UTC
Beginning 1 January 2017:
TAI-UTC = 37.000 000 seconds
***********************************************************************
* ANNOUNCEMENTS: *
* *
* There will NOT be a leap second introduced in UTC *
* at the end of December 2025. *
* *
* The primary source for IERS Rapid Service/Prediction Center (RS/PC) *
* data products is the official IERS RS/PC website: *
* https://maia.usno.navy.mil *
* *
* IERS RS/PC products are also available from: *
* NASA CDDIS: https://cddis.nasa.gov/archive/products/iers/ *
* NASA CDDIS: ftps://gdc.cddis.eosdis.nasa.gov/products/iers/ *
* IERS Central Bureau: https://datacenter.iers.org/eop.php *
* *
* Questions about IERS RS/PC products can be emailed to: *
* eopcp@us.navy.mil *
* *
* Distribution statement A: *
* Approved for public release: distribution unlimited. *
* *
***********************************************************************
________________________________________________________________________
The contributed observations used in the preparation of this Bulletin
are available at <http://www.usno.navy.mil/USNO/earth-orientation/
eo-info/general/input-data>. The contributed analysis results are based
on data from Very Long Baseline Interferometry (VLBI), Satellite Laser
Ranging (SLR), the Global Positioning System (GPS) satellites, Lunar
Laser Ranging (LLR), and meteorological predictions of variations in
Atmospheric Angular Momentum (AAM).
________________________________________________________________________
COMBINED EARTH ORIENTATION PARAMETERS:
IERS Rapid Service
MJD x error y error UT1-UTC error
" " " " s s
25 8 1 60888 0.21017 .00009 0.42717 .00009 0.062125 0.000021
25 8 2 60889 0.21181 .00009 0.42627 .00009 0.062752 0.000019
25 8 3 60890 0.21302 .00009 0.42504 .00009 0.063645 0.000016
25 8 4 60891 0.21368 .00009 0.42383 .00009 0.064781 0.000010
25 8 5 60892 0.21398 .00009 0.42271 .00009 0.066047 0.000009
25 8 6 60893 0.21437 .00009 0.42152 .00009 0.067348 0.000008
25 8 7 60894 0.21485 .00009 0.42038 .00009 0.068615 0.000055
IERS Final Values
MJD x y UT1-UTC
" " s
25 6 2 60828 0.1141 0.4380 0.02903
25 6 3 60829 0.1154 0.4384 0.02896
25 6 4 60830 0.1172 0.4390 0.02885
25 6 5 60831 0.1187 0.4399 0.02874
25 6 6 60832 0.1202 0.4403 0.02868
25 6 7 60833 0.1217 0.4408 0.02871
25 6 8 60834 0.1232 0.4410 0.02891
25 6 9 60835 0.1248 0.4415 0.02936
25 6 10 60836 0.1262 0.4420 0.03004
25 6 11 60837 0.1276 0.4425 0.03086
25 6 12 60838 0.1291 0.4428 0.03178
25 6 13 60839 0.1307 0.4428 0.03273
25 6 14 60840 0.1325 0.4426 0.03360
25 6 15 60841 0.1347 0.4424 0.03430
25 6 16 60842 0.1370 0.4426 0.03479
25 6 17 60843 0.1389 0.4427 0.03506
25 6 18 60844 0.1406 0.4429 0.03512
25 6 19 60845 0.1420 0.4431 0.03504
25 6 20 60846 0.1436 0.4427 0.03492
25 6 21 60847 0.1452 0.4426 0.03489
25 6 22 60848 0.1468 0.4423 0.03515
25 6 23 60849 0.1486 0.4420 0.03583
25 6 24 60850 0.1502 0.4416 0.03682
25 6 25 60851 0.1518 0.4411 0.03797
25 6 26 60852 0.1533 0.4407 0.03919
25 6 27 60853 0.1548 0.4404 0.04037
25 6 28 60854 0.1564 0.4401 0.04139
25 6 29 60855 0.1585 0.4400 0.04222
25 6 30 60856 0.1603 0.4401 0.04287
25 7 1 60857 0.1621 0.4398 0.04342
_______________________________________________________________________
PREDICTIONS:
The following formulas will not reproduce the predictions given below,
but may be used to extend the predictions beyond the end of this table.
x = 0.1420 + 0.1046 cos A + 0.1043 sin A - 0.0336 cos C - 0.0648 sin C
y = 0.3838 + 0.1044 cos A - 0.0915 sin A - 0.0648 cos C + 0.0336 sin C
UT1-UTC = 0.0474 + 0.00010 (MJD - 60902) - (UT2-UT1)
where A = 2*pi*(MJD-60894)/365.25 and C = 2*pi*(MJD-60894)/435.
TAI-UTC(MJD 60895) = 37.0
The accuracy may be estimated from the expressions:
S x,y = 0.00068 (MJD-60894)**0.80 S t = 0.00025 (MJD-60894)**0.75
Estimated accuracies are: Predictions 10 d 20 d 30 d 40 d
Polar coord's 0.004 0.007 0.010 0.013
UT1-UTC 0.0014 0.0024 0.0032 0.0040
MJD x(arcsec) y(arcsec) UT1-UTC(sec)
2025 8 8 60895 0.2155 0.4191 0.06979
2025 8 9 60896 0.2161 0.4179 0.07073
2025 8 10 60897 0.2169 0.4167 0.07137
2025 8 11 60898 0.2176 0.4154 0.07173
2025 8 12 60899 0.2184 0.4142 0.07190
2025 8 13 60900 0.2191 0.4130 0.07200
2025 8 14 60901 0.2198 0.4117 0.07218
2025 8 15 60902 0.2204 0.4105 0.07254
2025 8 16 60903 0.2210 0.4094 0.07316
2025 8 17 60904 0.2216 0.4082 0.07403
2025 8 18 60905 0.2222 0.4070 0.07507
2025 8 19 60906 0.2227 0.4058 0.07619
2025 8 20 60907 0.2232 0.4046 0.07725
2025 8 21 60908 0.2237 0.4033 0.07814
2025 8 22 60909 0.2242 0.4021 0.07878
2025 8 23 60910 0.2247 0.4008 0.07912
2025 8 24 60911 0.2251 0.3995 0.07921
2025 8 25 60912 0.2255 0.3983 0.07914
2025 8 26 60913 0.2259 0.3970 0.07899
2025 8 27 60914 0.2263 0.3957 0.07887
2025 8 28 60915 0.2266 0.3944 0.07884
2025 8 29 60916 0.2269 0.3931 0.07898
2025 8 30 60917 0.2272 0.3918 0.07930
2025 8 31 60918 0.2275 0.3905 0.07979
2025 9 1 60919 0.2278 0.3892 0.08042
2025 9 2 60920 0.2280 0.3879 0.08112
2025 9 3 60921 0.2282 0.3865 0.08182
2025 9 4 60922 0.2283 0.3852 0.08242
2025 9 5 60923 0.2285 0.3839 0.08283
2025 9 6 60924 0.2286 0.3825 0.08295
2025 9 7 60925 0.2287 0.3812 0.08277
2025 9 8 60926 0.2287 0.3799 0.08232
2025 9 9 60927 0.2287 0.3785 0.08173
2025 9 10 60928 0.2288 0.3772 0.08114
2025 9 11 60929 0.2287 0.3758 0.08072
2025 9 12 60930 0.2287 0.3745 0.08056
2025 9 13 60931 0.2286 0.3731 0.08070
2025 9 14 60932 0.2285 0.3718 0.08108
2025 9 15 60933 0.2284 0.3705 0.08159
2025 9 16 60934 0.2282 0.3691 0.08209
2025 9 17 60935 0.2281 0.3678 0.08247
2025 9 18 60936 0.2279 0.3664 0.08265
2025 9 19 60937 0.2276 0.3651 0.08260
2025 9 20 60938 0.2274 0.3638 0.08234
2025 9 21 60939 0.2271 0.3624 0.08194
2025 9 22 60940 0.2268 0.3611 0.08146
2025 9 23 60941 0.2264 0.3598 0.08101
2025 9 24 60942 0.2261 0.3585 0.08064
2025 9 25 60943 0.2257 0.3571 0.08042
2025 9 26 60944 0.2253 0.3558 0.08037
2025 9 27 60945 0.2248 0.3545 0.08049
2025 9 28 60946 0.2244 0.3532 0.08076
2025 9 29 60947 0.2239 0.3520 0.08112
2025 9 30 60948 0.2234 0.3507 0.08148
2025 10 1 60949 0.2228 0.3494 0.08177
2025 10 2 60950 0.2223 0.3481 0.08189
2025 10 3 60951 0.2217 0.3469 0.08176
2025 10 4 60952 0.2211 0.3456 0.08133
2025 10 5 60953 0.2204 0.3444 0.08059
2025 10 6 60954 0.2198 0.3431 0.07962
2025 10 7 60955 0.2191 0.3419 0.07856
2025 10 8 60956 0.2184 0.3407 0.07759
2025 10 9 60957 0.2177 0.3395 0.07687
2025 10 10 60958 0.2169 0.3383 0.07647
2025 10 11 60959 0.2161 0.3371 0.07637
2025 10 12 60960 0.2153 0.3360 0.07648
2025 10 13 60961 0.2145 0.3348 0.07665
2025 10 14 60962 0.2137 0.3337 0.07674
2025 10 15 60963 0.2128 0.3325 0.07667
2025 10 16 60964 0.2119 0.3314 0.07640
2025 10 17 60965 0.2110 0.3303 0.07594
2025 10 18 60966 0.2101 0.3292 0.07535
2025 10 19 60967 0.2092 0.3281 0.07470
2025 10 20 60968 0.2082 0.3271 0.07405
2025 10 21 60969 0.2072 0.3260 0.07350
2025 10 22 60970 0.2062 0.3250 0.07310
2025 10 23 60971 0.2052 0.3240 0.07289
2025 10 24 60972 0.2041 0.3230 0.07287
2025 10 25 60973 0.2031 0.3220 0.07302
2025 10 26 60974 0.2020 0.3210 0.07329
2025 10 27 60975 0.2009 0.3200 0.07360
2025 10 28 60976 0.1998 0.3191 0.07388
2025 10 29 60977 0.1986 0.3182 0.07405
2025 10 30 60978 0.1975 0.3173 0.07402
2025 10 31 60979 0.1963 0.3164 0.07372
2025 11 1 60980 0.1951 0.3155 0.07315
2025 11 2 60981 0.1939 0.3147 0.07232
2025 11 3 60982 0.1927 0.3139 0.07133
2025 11 4 60983 0.1915 0.3130 0.07034
2025 11 5 60984 0.1902 0.3122 0.06953
2025 11 6 60985 0.1890 0.3115 0.06901
2025 11 7 60986 0.1877 0.3107 0.06882
2025 11 8 60987 0.1864 0.3100 0.06889
2025 11 9 60988 0.1851 0.3093 0.06908
2025 11 10 60989 0.1838 0.3086 0.06924
2025 11 11 60990 0.1825 0.3079 0.06927
2025 11 12 60991 0.1812 0.3072 0.06910
2025 11 13 60992 0.1798 0.3066 0.06876
2025 11 14 60993 0.1784 0.3060 0.06828
2025 11 15 60994 0.1771 0.3054 0.06774
2025 11 16 60995 0.1757 0.3048 0.06722
2025 11 17 60996 0.1743 0.3043 0.06677
2025 11 18 60997 0.1729 0.3038 0.06647
2025 11 19 60998 0.1715 0.3033 0.06635
2025 11 20 60999 0.1701 0.3028 0.06642
2025 11 21 61000 0.1687 0.3023 0.06667
2025 11 22 61001 0.1672 0.3019 0.06705
2025 11 23 61002 0.1658 0.3015 0.06751
2025 11 24 61003 0.1643 0.3011 0.06788
2025 11 25 61004 0.1629 0.3007 0.06817
2025 11 26 61005 0.1614 0.3003 0.06829
2025 11 27 61006 0.1600 0.3000 0.06820
2025 11 28 61007 0.1585 0.2997 0.06785
2025 11 29 61008 0.1570 0.2994 0.06727
2025 11 30 61009 0.1556 0.2992 0.06650
2025 12 1 61010 0.1541 0.2989 0.06567
2025 12 2 61011 0.1526 0.2987 0.06494
2025 12 3 61012 0.1511 0.2985 0.06442
2025 12 4 61013 0.1496 0.2984 0.06421
2025 12 5 61014 0.1481 0.2982 0.06429
2025 12 6 61015 0.1466 0.2981 0.06454
2025 12 7 61016 0.1451 0.2980 0.06483
2025 12 8 61017 0.1437 0.2980 0.06500
2025 12 9 61018 0.1422 0.2979 0.06498
2025 12 10 61019 0.1407 0.2979 0.06476
2025 12 11 61020 0.1392 0.2979 0.06439
2025 12 12 61021 0.1377 0.2979 0.06395
2025 12 13 61022 0.1362 0.2980 0.06353
2025 12 14 61023 0.1347 0.2980 0.06319
2025 12 15 61024 0.1332 0.2981 0.06299
2025 12 16 61025 0.1318 0.2982 0.06297
2025 12 17 61026 0.1303 0.2984 0.06315
2025 12 18 61027 0.1288 0.2985 0.06351
2025 12 19 61028 0.1274 0.2987 0.06401
2025 12 20 61029 0.1259 0.2989 0.06461
2025 12 21 61030 0.1245 0.2992 0.06522
2025 12 22 61031 0.1230 0.2994 0.06575
2025 12 23 61032 0.1216 0.2997 0.06614
2025 12 24 61033 0.1201 0.3000 0.06632
2025 12 25 61034 0.1187 0.3003 0.06627
2025 12 26 61035 0.1173 0.3007 0.06599
2025 12 27 61036 0.1159 0.3011 0.06554
2025 12 28 61037 0.1145 0.3015 0.06500
2025 12 29 61038 0.1131 0.3019 0.06451
2025 12 30 61039 0.1117 0.3023 0.06418
2025 12 31 61040 0.1103 0.3028 0.06410
2026 1 1 61041 0.1090 0.3033 0.06429
2026 1 2 61042 0.1076 0.3038 0.06468
2026 1 3 61043 0.1063 0.3043 0.06514
2026 1 4 61044 0.1050 0.3048 0.06553
2026 1 5 61045 0.1036 0.3054 0.06573
2026 1 6 61046 0.1023 0.3060 0.06569
2026 1 7 61047 0.1010 0.3066 0.06545
2026 1 8 61048 0.0998 0.3072 0.06509
2026 1 9 61049 0.0985 0.3079 0.06472
2026 1 10 61050 0.0973 0.3086 0.06441
2026 1 11 61051 0.0960 0.3093 0.06423
2026 1 12 61052 0.0948 0.3100 0.06423
2026 1 13 61053 0.0936 0.3107 0.06442
2026 1 14 61054 0.0924 0.3115 0.06480
2026 1 15 61055 0.0912 0.3122 0.06532
2026 1 16 61056 0.0901 0.3130 0.06595
2026 1 17 61057 0.0889 0.3138 0.06660
2026 1 18 61058 0.0878 0.3146 0.06719
2026 1 19 61059 0.0867 0.3155 0.06763
2026 1 20 61060 0.0856 0.3164 0.06787
2026 1 21 61061 0.0846 0.3172 0.06785
2026 1 22 61062 0.0835 0.3181 0.06761
2026 1 23 61063 0.0825 0.3191 0.06717
2026 1 24 61064 0.0815 0.3200 0.06664
2026 1 25 61065 0.0805 0.3209 0.06615
2026 1 26 61066 0.0795 0.3219 0.06579
2026 1 27 61067 0.0786 0.3229 0.06566
2026 1 28 61068 0.0776 0.3239 0.06578
2026 1 29 61069 0.0767 0.3249 0.06612
2026 1 30 61070 0.0758 0.3259 0.06656
2026 1 31 61071 0.0749 0.3270 0.06697
2026 2 1 61072 0.0741 0.3280 0.06723
2026 2 2 61073 0.0733 0.3291 0.06725
2026 2 3 61074 0.0725 0.3302 0.06704
2026 2 4 61075 0.0717 0.3313 0.06666
2026 2 5 61076 0.0709 0.3324 0.06622
2026 2 6 61077 0.0702 0.3335 0.06583
2026 2 7 61078 0.0695 0.3346 0.06557
2026 2 8 61079 0.0688 0.3358 0.06552
2026 2 9 61080 0.0681 0.3369 0.06570
2026 2 10 61081 0.0675 0.3381 0.06610
2026 2 11 61082 0.0668 0.3393 0.06671
2026 2 12 61083 0.0662 0.3405 0.06747
2026 2 13 61084 0.0657 0.3417 0.06829
2026 2 14 61085 0.0651 0.3429 0.06910
2026 2 15 61086 0.0646 0.3441 0.06977
2026 2 16 61087 0.0641 0.3453 0.07023
2026 2 17 61088 0.0636 0.3466 0.07040
2026 2 18 61089 0.0632 0.3478 0.07026
2026 2 19 61090 0.0627 0.3491 0.06986
2026 2 20 61091 0.0623 0.3503 0.06930
2026 2 21 61092 0.0620 0.3516 0.06869
2026 2 22 61093 0.0616 0.3529 0.06818
2026 2 23 61094 0.0613 0.3541 0.06787
2026 2 24 61095 0.0610 0.3554 0.06779
2026 2 25 61096 0.0607 0.3567 0.06793
2026 2 26 61097 0.0605 0.3580 0.06818
2026 2 27 61098 0.0602 0.3593 0.06844
2026 2 28 61099 0.0600 0.3606 0.06858
2026 3 1 61100 0.0599 0.3619 0.06850
2026 3 2 61101 0.0597 0.3632 0.06818
2026 3 3 61102 0.0596 0.3646 0.06765
2026 3 4 61103 0.0595 0.3659 0.06699
2026 3 5 61104 0.0595 0.3672 0.06631
2026 3 6 61105 0.0594 0.3685 0.06572
2026 3 7 61106 0.0594 0.3698 0.06529
2026 3 8 61107 0.0594 0.3712 0.06505
2026 3 9 61108 0.0595 0.3725 0.06500
2026 3 10 61109 0.0595 0.3738 0.06511
2026 3 11 61110 0.0596 0.3751 0.06533
2026 3 12 61111 0.0597 0.3765 0.06562
2026 3 13 61112 0.0599 0.3778 0.06589
2026 3 14 61113 0.0600 0.3791 0.06606
2026 3 15 61114 0.0602 0.3804 0.06604
2026 3 16 61115 0.0604 0.3818 0.06576
2026 3 17 61116 0.0607 0.3831 0.06516
2026 3 18 61117 0.0610 0.3844 0.06423
2026 3 19 61118 0.0613 0.3857 0.06304
2026 3 20 61119 0.0616 0.3870 0.06180
2026 3 21 61120 0.0619 0.3883 0.06065
2026 3 22 61121 0.0623 0.3896 0.05966
2026 3 23 61122 0.0627 0.3909 0.05895
2026 3 24 61123 0.0631 0.3922 0.05844
2026 3 25 61124 0.0636 0.3934 0.05812
2026 3 26 61125 0.0640 0.3947 0.05791
2026 3 27 61126 0.0645 0.3960 0.05759
2026 3 28 61127 0.0651 0.3972 0.05717
2026 3 29 61128 0.0656 0.3985 0.05646
2026 3 30 61129 0.0662 0.3997 0.05548
2026 3 31 61130 0.0668 0.4010 0.05432
2026 4 1 61131 0.0674 0.4022 0.05306
2026 4 2 61132 0.0680 0.4034 0.05187
2026 4 3 61133 0.0687 0.4046 0.05076
2026 4 4 61134 0.0694 0.4058 0.04986
2026 4 5 61135 0.0701 0.4070 0.04920
2026 4 6 61136 0.0708 0.4082 0.04872
2026 4 7 61137 0.0716 0.4093 0.04838
2026 4 8 61138 0.0724 0.4105 0.04815
2026 4 9 61139 0.0732 0.4116 0.04795
2026 4 10 61140 0.0740 0.4128 0.04768
2026 4 11 61141 0.0748 0.4139 0.04735
2026 4 12 61142 0.0757 0.4150 0.04689
2026 4 13 61143 0.0766 0.4161 0.04620
2026 4 14 61144 0.0775 0.4172 0.04531
2026 4 15 61145 0.0784 0.4182 0.04419
2026 4 16 61146 0.0794 0.4193 0.04299
2026 4 17 61147 0.0803 0.4203 0.04183
2026 4 18 61148 0.0813 0.4213 0.04095
2026 4 19 61149 0.0823 0.4223 0.04036
2026 4 20 61150 0.0833 0.4233 0.04007
2026 4 21 61151 0.0844 0.4243 0.04013
2026 4 22 61152 0.0854 0.4253 0.04026
2026 4 23 61153 0.0865 0.4262 0.04040
2026 4 24 61154 0.0876 0.4271 0.04039
2026 4 25 61155 0.0887 0.4280 0.04013
2026 4 26 61156 0.0899 0.4289 0.03962
2026 4 27 61157 0.0910 0.4298 0.03894
2026 4 28 61158 0.0922 0.4307 0.03819
2026 4 29 61159 0.0933 0.4315 0.03743
2026 4 30 61160 0.0945 0.4323 0.03681
2026 5 1 61161 0.0957 0.4331 0.03639
2026 5 2 61162 0.0970 0.4339 0.03619
2026 5 3 61163 0.0982 0.4347 0.03616
2026 5 4 61164 0.0994 0.4354 0.03629
2026 5 5 61165 0.1007 0.4362 0.03660
2026 5 6 61166 0.1020 0.4369 0.03691
2026 5 7 61167 0.1033 0.4376 0.03721
2026 5 8 61168 0.1046 0.4382 0.03749
2026 5 9 61169 0.1059 0.4389 0.03763
2026 5 10 61170 0.1072 0.4395 0.03762
2026 5 11 61171 0.1086 0.4401 0.03744
2026 5 12 61172 0.1099 0.4407 0.03706
2026 5 13 61173 0.1113 0.4413 0.03652
2026 5 14 61174 0.1126 0.4418 0.03595
2026 5 15 61175 0.1140 0.4423 0.03555
2026 5 16 61176 0.1154 0.4428 0.03545
2026 5 17 61177 0.1168 0.4433 0.03561
2026 5 18 61178 0.1182 0.4438 0.03605
2026 5 19 61179 0.1196 0.4442 0.03672
2026 5 20 61180 0.1210 0.4446 0.03732
2026 5 21 61181 0.1224 0.4450 0.03779
2026 5 22 61182 0.1239 0.4454 0.03807
2026 5 23 61183 0.1253 0.4457 0.03818
2026 5 24 61184 0.1267 0.4461 0.03811
2026 5 25 61185 0.1282 0.4464 0.03802
2026 5 26 61186 0.1296 0.4466 0.03795
2026 5 27 61187 0.1311 0.4469 0.03798
2026 5 28 61188 0.1326 0.4471 0.03820
2026 5 29 61189 0.1340 0.4473 0.03862
2026 5 30 61190 0.1355 0.4475 0.03926
2026 5 31 61191 0.1369 0.4477 0.04005
2026 6 1 61192 0.1384 0.4478 0.04090
2026 6 2 61193 0.1399 0.4480 0.04191
2026 6 3 61194 0.1414 0.4481 0.04298
2026 6 4 61195 0.1428 0.4481 0.04399
2026 6 5 61196 0.1443 0.4482 0.04491
2026 6 6 61197 0.1458 0.4482 0.04568
2026 6 7 61198 0.1472 0.4482 0.04614
2026 6 8 61199 0.1487 0.4482 0.04638
2026 6 9 61200 0.1502 0.4481 0.04649
2026 6 10 61201 0.1516 0.4481 0.04648
2026 6 11 61202 0.1531 0.4480 0.04649
2026 6 12 61203 0.1545 0.4479 0.04661
2026 6 13 61204 0.1560 0.4477 0.04699
2026 6 14 61205 0.1574 0.4476 0.04756
2026 6 15 61206 0.1589 0.4474 0.04835
2026 6 16 61207 0.1603 0.4472 0.04924
2026 6 17 61208 0.1618 0.4470 0.05017
2026 6 18 61209 0.1632 0.4467 0.05089
2026 6 19 61210 0.1646 0.4464 0.05142
2026 6 20 61211 0.1660 0.4461 0.05178
2026 6 21 61212 0.1674 0.4458 0.05200
2026 6 22 61213 0.1688 0.4455 0.05221
2026 6 23 61214 0.1702 0.4451 0.05260
2026 6 24 61215 0.1716 0.4447 0.05320
2026 6 25 61216 0.1730 0.4443 0.05401
2026 6 26 61217 0.1743 0.4439 0.05511
2026 6 27 61218 0.1757 0.4434 0.05647
2026 6 28 61219 0.1770 0.4430 0.05798
2026 6 29 61220 0.1783 0.4425 0.05967
2026 6 30 61221 0.1797 0.4420 0.06128
2026 7 1 61222 0.1810 0.4414 0.06275
2026 7 2 61223 0.1823 0.4409 0.06403
2026 7 3 61224 0.1836 0.4403 0.06505
2026 7 4 61225 0.1848 0.4397 0.06585
2026 7 5 61226 0.1861 0.4391 0.06645
2026 7 6 61227 0.1873 0.4384 0.06686
2026 7 7 61228 0.1886 0.4378 0.06722
2026 7 8 61229 0.1898 0.4371 0.06760
2026 7 9 61230 0.1910 0.4364 0.06810
2026 7 10 61231 0.1921 0.4357 0.06889
2026 7 11 61232 0.1933 0.4349 0.06992
2026 7 12 61233 0.1945 0.4342 0.07122
2026 7 13 61234 0.1956 0.4334 0.07254
2026 7 14 61235 0.1967 0.4326 0.07391
2026 7 15 61236 0.1978 0.4318 0.07504
2026 7 16 61237 0.1989 0.4310 0.07591
2026 7 17 61238 0.2000 0.4301 0.07652
2026 7 18 61239 0.2010 0.4293 0.07702
2026 7 19 61240 0.2021 0.4284 0.07743
2026 7 20 61241 0.2031 0.4275 0.07799
2026 7 21 61242 0.2041 0.4266 0.07873
2026 7 22 61243 0.2050 0.4256 0.07969
2026 7 23 61244 0.2060 0.4247 0.08084
2026 7 24 61245 0.2069 0.4237 0.08212
2026 7 25 61246 0.2078 0.4227 0.08367
2026 7 26 61247 0.2087 0.4218 0.08531
2026 7 27 61248 0.2096 0.4207 0.08702
2026 7 28 61249 0.2105 0.4197 0.08862
2026 7 29 61250 0.2113 0.4187 0.09004
2026 7 30 61251 0.2121 0.4176 0.09130
2026 7 31 61252 0.2129 0.4166 0.09234
2026 8 1 61253 0.2136 0.4155 0.09321
2026 8 2 61254 0.2144 0.4144 0.09386
2026 8 3 61255 0.2151 0.4133 0.09435
2026 8 4 61256 0.2158 0.4122 0.09493
2026 8 5 61257 0.2165 0.4111 0.09554
2026 8 6 61258 0.2171 0.4099 0.09635
2026 8 7 61259 0.2177 0.4088 0.09737
These predictions are based on all announced leap seconds.
CELESTIAL POLE OFFSET SERIES:
NEOS Celestial Pole Offset Series
MJD dpsi error deps error
(msec. of arc)
60868 -116.99 0.86 -10.78 0.21
60869 -117.02 0.86 -10.71 0.30
60870 -117.16 0.86 -10.75 0.30
60871 -117.35 0.86 -10.82 0.34
60872 -117.56 0.83 -10.83 0.29
60873 -117.79 0.83 -10.77 0.29
60874 -118.05 0.83 -10.70 0.29
60875 -118.31 0.78 -10.66 0.17
60876 -118.49 0.83 -10.70 0.16
60877 -118.55 0.83 -10.81 0.16
60878 -118.54 1.09 -10.92 0.16
60879 -118.54 0.94 -10.97 0.15
60880 -118.53 0.94 -10.93 0.15
60881 -118.44 0.94 -10.81 0.15
IERS Celestial Pole Offset Final Series
MJD dpsi deps
(msec. of arc)
60828 -111.0 -11.3
60829 -111.7 -10.9
60830 -112.1 -10.8
60831 -112.0 -10.8
60832 -111.7 -10.8
60833 -111.7 -10.8
60834 -111.9 -10.9
60835 -112.2 -11.2
60836 -112.5 -11.5
60837 -112.7 -11.7
60838 -112.8 -11.7
60839 -112.7 -11.5
60840 -112.6 -11.3
60841 -112.5 -11.3
60842 -112.4 -11.3
60843 -112.3 -11.3
60844 -112.4 -11.2
60845 -112.6 -11.1
60846 -113.0 -10.9
60847 -113.6 -10.7
60848 -114.2 -10.7
60849 -114.6 -10.9
60850 -114.7 -11.2
60851 -114.6 -11.3
60852 -114.5 -11.4
60853 -114.3 -11.3
60854 -114.0 -11.2
60855 -114.1 -11.0
60856 -114.6 -10.6
60857 -115.3 -10.4
IAU2000A Celestial Pole Offset Series
MJD dX error dY error
(msec. of arc)
60868 0.366 0.342 -0.270 0.206
60869 0.360 0.343 -0.284 0.298
60870 0.356 0.343 -0.292 0.298
60871 0.352 0.343 -0.292 0.342
60872 0.347 0.330 -0.282 0.288
60873 0.343 0.330 -0.265 0.288
60874 0.341 0.330 -0.243 0.288
60875 0.342 0.308 -0.222 0.166
60876 0.347 0.331 -0.202 0.164
60877 0.352 0.331 -0.183 0.164
60878 0.357 0.433 -0.166 0.157
60879 0.359 0.375 -0.150 0.149
60880 0.359 0.375 -0.135 0.149
60881 0.358 0.375 -0.120 0.149
IAU2000A Celestial Pole Offset Final Series
MJD dX dY
(msec. of arc)
60828 0.30 -0.23
60829 0.28 -0.26
60830 0.28 -0.28
60831 0.35 -0.27
60832 0.43 -0.24
60833 0.45 -0.20
60834 0.43 -0.18
60835 0.39 -0.16
60836 0.34 -0.15
60837 0.34 -0.19
60838 0.35 -0.25
60839 0.37 -0.30
60840 0.41 -0.34
60841 0.45 -0.35
60842 0.50 -0.35
60843 0.53 -0.34
60844 0.52 -0.32
60845 0.49 -0.29
60846 0.45 -0.27
60847 0.42 -0.25
60848 0.41 -0.25
60849 0.40 -0.26
60850 0.39 -0.27
60851 0.39 -0.29
60852 0.39 -0.32
60853 0.39 -0.33
60854 0.38 -0.28
60855 0.36 -0.19
60856 0.35 -0.10
60857 0.34 -0.03
)--";
} // namespace mcc::ccte::iers::defaults

111
mcc/mcc_defaults.h Normal file
View File

@ -0,0 +1,111 @@
#pragma once
/* MOUNT CONTROL COMPONENTS LIBRARY */
/* MCC LIBRARY DEFAULT IMPLEMENTATION OF SOME CLASSES */
// #include <compare>
#include "mcc_generics.h"
namespace mcc
{
/* DEFAULT TIME POINT CLASS */
typedef std::chrono::system_clock::time_point MccTimePoint;
/* DEFAULT JULIAN DAY CLASS */
struct MccJulianDay {
static constexpr double MJD0 = 2400000.5;
double mjd{51544.5}; // J2000.0
constexpr operator double() const
{
return MccJulianDay::MJD0 + mjd;
}
constexpr auto operator<=>(const MccJulianDay&) const = default;
constexpr auto operator<=>(double v) const
{
return v <=> (MccJulianDay::MJD0 + mjd);
};
};
/* DEFAULT CELESTIAL POINT CLASS */
template <mcc_angle_c CoordT>
struct MccGenericCelestialPoint {
typedef CoordT coord_t;
MccCoordPairKind pair_kind{MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
MccTimePoint time_point{std::chrono::sys_days(std::chrono::year_month_day{std::chrono::January / 1 / 2000}) +
std::chrono::hours(12)}; // J2000.0
coord_t X{}, Y{};
};
typedef MccGenericCelestialPoint<double> MccCelestialPoint;
template <mcc_angle_c CoordT>
struct MccGenericEqtHrzCoords : MccGenericCelestialPoint<CoordT> {
using typename MccGenericCelestialPoint<CoordT>::coord_t;
coord_t RA_APP{}, DEC_APP{}, HA{}, AZ{}, ZD{}, ALT{};
};
typedef MccGenericEqtHrzCoords<MccCelestialPoint::coord_t> MccEqtHrzCoords;
template <mcc_angle_c CoordT>
struct MccGenericPointingTarget : MccGenericEqtHrzCoords<CoordT> {
using typename MccGenericEqtHrzCoords<CoordT>::coord_t;
coord_t RA_ICRS{}, DEC_ICRS{};
};
typedef MccGenericPointingTarget<MccCelestialPoint::coord_t> MccPointingTarget;
/* DEFAULT TELEMETRY DATA CLASS */
template <mcc_angle_c CoordT>
struct MccGenericTelemetryData : MccGenericEqtHrzCoords<CoordT> {
using typename MccGenericEqtHrzCoords<CoordT>::coord_t;
MccJulianDay JD;
coord_t LST; // local apparent sideral time
MccGenericPointingTarget<coord_t> target{};
coord_t speedX, speedY;
coord_t pcmX, pcmY;
coord_t refCorr;
};
typedef MccGenericTelemetryData<MccCelestialPoint::coord_t> MccTelemetryData;
/* JUST CHECK FOR CONCEPT CONSISTENCY */
static_assert(mcc_julday_c<MccJulianDay>, "");
static_assert(mcc_celestial_point_c<MccCelestialPoint>, "");
static_assert(mcc_telemetry_data_c<MccTelemetryData>, "");
} // namespace mcc

406
mcc/mcc_generics.h Normal file
View File

@ -0,0 +1,406 @@
#pragma once
/* MOUNT CONTROL COMPONENTS LIBRARY */
/* SOME LIBRARY-WIDE DECLARATIONS */
#include <chrono>
#include <concepts>
#include "mcc_traits.h"
namespace mcc
{
// mount construction type (only the most common ones)
enum class MccMountType : uint8_t { GERMAN_TYPE, FORK_TYPE, CROSSAXIS_TYPE, ALTAZ_TYPE };
enum MccCoordPairKind : size_t {
COORDS_KIND_GENERIC,
COORDS_KIND_RADEC_ICRS,
COORDS_KIND_RADEC_APP,
COORDS_KIND_HADEC_APP,
COORDS_KIND_AZZD,
COORDS_KIND_AZALT,
COORDS_KIND_XY,
COORDS_KIND_LATLON
};
/* FLOATING-POINT LIKE CLASS CONCEPT */
template <typename T>
concept mcc_fp_type_like_c =
std::floating_point<T> ||
(std::convertible_to<T, double> && std::constructible_from<T, double> && std::default_initializable<T>);
/* ANGLE REPRESENTATION CLASS CONCEPT */
template <typename T>
concept mcc_angle_c = mcc_fp_type_like_c<T> && requires(T v, double vd) {
{ v + v } -> std::same_as<T>;
{ v - v } -> std::same_as<T>;
{ v += v } -> std::same_as<T&>;
{ v -= v } -> std::same_as<T&>;
{ v * vd } -> std::same_as<T>;
{ v / vd } -> std::same_as<T>;
};
/* TIME POINT CLASS CONCEPT */
/*
* USE OF STL std::chrono::time_point
*/
template <typename T>
concept mcc_time_point_c = requires(T t) { []<typename CT, typename DT>(std::chrono::time_point<CT, DT>) {}(t); };
/* JULIAN DAY CLASS CONCEPT */
template <typename T>
concept mcc_julday_c = mcc_fp_type_like_c<T> && requires(const T v) {
// comparison operators
v <=> v;
};
/* ERROR CLASS CONCEPT */
template <typename T>
concept mcc_error_c = std::convertible_to<T, bool> || requires(const T t) {
{ t.operator bool() };
};
/* ATMOSPHERIC REFRACTION MODEL CLASS CONCEPT */
template <typename T>
concept mcc_refract_model_c = requires(const T t_const) {
{ t_const.name() } -> std::formattable<char>;
};
/* CELESTIAL POINT WITH A PAIR OF COORDINATES CLASS CONCEPT */
template <typename T>
concept mcc_celestial_point_c = requires(T t) {
requires std::same_as<decltype(t.pair_kind), MccCoordPairKind>; // type of given coordinate pair
requires mcc_time_point_c<decltype(t.time_point)>; // time point for given coordinates
requires mcc_angle_c<decltype(t.X)>; // co-longitude (X-axis)
requires mcc_angle_c<decltype(t.Y)>; // co-latitude (Y-axis)
};
/* CELESTIAL POINT WITH APPARENT EQUATORIAL AND HORIZONTAL CLASS CONCEPT */
template <typename T>
concept mcc_eqt_hrz_coord_c = mcc_celestial_point_c<T> && requires(T t) {
requires mcc_angle_c<decltype(t.RA_APP)>; // right ascension
requires mcc_angle_c<decltype(t.DEC_APP)>; // declination
requires mcc_angle_c<decltype(t.HA)>; // hour angle
requires mcc_angle_c<decltype(t.AZ)>; // azimuth
requires mcc_angle_c<decltype(t.ZD)>; // zenithal distance
requires mcc_angle_c<decltype(t.ALT)>; // altitude
};
/* CELESTIAL COORDINATES TRANSFORMATION ENGINE */
template <mcc_error_c RetT>
struct mcc_CCTE_interface_t {
virtual ~mcc_CCTE_interface_t() = default;
template <std::derived_from<mcc_CCTE_interface_t> SelfT>
RetT timepointToJulday(this SelfT&& self, mcc_time_point_c auto tp, mcc_julday_c auto* julday)
{
return std::forward<SelfT>(self).timepointToJulday(std::move(tp), julday);
}
// APPARENT SIDERAL TIME
template <std::derived_from<mcc_CCTE_interface_t> SelfT>
RetT timepointToAppSideral(this SelfT&& self, mcc_time_point_c auto tp, mcc_angle_c auto* st, bool islocal = false)
{
return std::forward<SelfT>(self).timepointToAppSideral(std::move(tp), st, islocal);
}
template <std::derived_from<mcc_CCTE_interface_t> SelfT>
RetT transformCoordinates(this SelfT&& self, mcc_celestial_point_c auto from_pt, mcc_celestial_point_c auto* to_pt)
{
return std::forward<SelfT>(self).transformCoordinates(std::move(from_pt), to_pt);
}
template <std::derived_from<mcc_CCTE_interface_t> SelfT>
RetT transformCoordinates(this SelfT&& self, mcc_celestial_point_c auto from_pt, mcc_eqt_hrz_coord_c auto* to_pt)
{
return std::forward<SelfT>(self).transformCoordinates(std::move(from_pt), to_pt);
}
template <std::derived_from<mcc_CCTE_interface_t> SelfT>
RetT parallacticAngle(this SelfT&& self, mcc_celestial_point_c auto pt, mcc_angle_c auto* pa)
{
return std::forward<SelfT>(self).parallacticAngle(std::move(pt), pa);
}
template <std::derived_from<mcc_CCTE_interface_t> SelfT>
RetT refractionCorrection(this SelfT&& self, mcc_celestial_point_c auto pt, mcc_angle_c auto* dZ)
{
return std::forward<SelfT>(self).refractionCoeff(std::move(pt), dZ);
}
protected:
mcc_CCTE_interface_t() = default;
};
template <typename T>
concept mcc_ast_engine_c =
std::derived_from<T, mcc_CCTE_interface_t<typename T::error_t>> && requires(const T t_const, T t) {
{ t_const.name() } -> std::formattable<char>;
requires mcc_refract_model_c<typename T::refract_model_t>;
{ t.refractionModel(std::declval<typename T::refract_model_t*>()) } -> std::same_as<typename T::error_t>;
};
/* MOUNT TELEMETRY DATA CLASS CONCEPT */
template <typename T>
concept mcc_pointing_target_coord_c = mcc_eqt_hrz_coord_c<T> && requires(T t) {
requires mcc_angle_c<decltype(t.RA_ICRS)>; // ICRS right ascention
requires mcc_angle_c<decltype(t.DEC_ICRS)>; // ICRS declination
};
template <typename T>
concept mcc_telemetry_data_c = mcc_eqt_hrz_coord_c<T> && requires(T t) {
// target target coordinates
requires mcc_pointing_target_coord_c<decltype(t.target)>;
// t.X and t.Y (from mcc_celestial_point_c) are encoder coordinates
// t.* from mcc_eqt_hrz_coord_c are apparent mount pointing coordinates
requires mcc_angle_c<decltype(t.speedX)>; // speed along X from hardware encoder
requires mcc_angle_c<decltype(t.speedY)>; // speed along Y from hardware encoder
// corrections to transform hardware encoder coordinates to apparent celestial ones
requires mcc_angle_c<decltype(t.pcmX)>; // PCM correction along X-axis
requires mcc_angle_c<decltype(t.pcmY)>; // PCM correction along Y-axis
// atmospheric refraction correction for current zenithal distance
requires mcc_angle_c<decltype(t.refCorr)>; // for current .ZD
};
/* MOUNT TELEMETRY MANAGER CLASS CONCEPT */
template <mcc_error_c RetT>
struct mcc_telemetry_interface_t {
virtual ~mcc_telemetry_interface_t() = default;
template <std::derived_from<mcc_telemetry_interface_t> SelfT>
RetT telemetryData(this SelfT&& self, mcc_telemetry_data_c auto* data)
{
return std::forward<SelfT>(self).telemetryData(data);
}
template <std::derived_from<mcc_telemetry_interface_t> SelfT>
RetT setPointingTarget(this SelfT&& self, mcc_celestial_point_c auto pt)
{
return std::forward<SelfT>(self).telemetryData(std::move(pt));
}
protected:
mcc_telemetry_interface_t() = default;
};
template <typename T>
concept mcc_telemetry_c = std::derived_from<T, mcc_telemetry_interface_t<typename T::error_t>>;
/* POINTING CORRECTION MODEL CLASS CONCEPT */
template <typename T>
concept mcc_PCM_result_c = requires(T t) {
requires mcc_angle_c<decltype(t.dx)>;
requires mcc_angle_c<decltype(t.dy)>;
};
template <mcc_error_c RetT, mcc_PCM_result_c ResT>
struct mcc_PCM_interface_t {
template <std::derived_from<mcc_PCM_interface_t> SelfT>
RetT computePCM(this SelfT&& self, mcc_celestial_point_c auto pt, ResT* result)
{
return std::forward<SelfT>(self).computePCM(std::move(pt), result);
}
};
template <typename T>
concept mcc_PCM_c =
std::derived_from<T, mcc_PCM_interface_t<typename T::error_t, typename T::pcm_result_t>> && requires {
// the 'T' class must contain static constexpr member of 'MccMountType' type
requires std::same_as<decltype(T::mountType), const MccMountType>;
[]() {
static constexpr MccMountType val = T::mountType;
return val;
}(); // to ensure 'mountType' can be used in compile-time context
};
/* MOUNT HARDWARE ABSTRACTION CLASS CONCEPT */
template <typename T>
concept mcc_hardware_c = requires(T t, const T t_const) {
typename T::error_t;
{ t_const.name() } -> std::formattable<char>;
// a class that contains at least time point of measurement, coordinates for x,y axes and its moving rates
requires requires(typename T::axes_pos_t pos) {
requires mcc_time_point_c<typename T::time_point_t>; // time point
requires mcc_angle_c<decltype(pos.X)>; // target or current co-longitude coordinate
requires mcc_angle_c<decltype(pos.Y)>; // target or current co-latitude coordinate
requires mcc_angle_c<decltype(pos.speedX)>;
requires mcc_angle_c<decltype(pos.speedY)>;
};
// set positions (angles) of mount axes with given speeds
// NOTE: exact interpretation (or even ignoring) of the given moving speeds is subject of a hardware-class
// implementation.
// e.g. it can be maximal speeds at slewing ramp
{ t.setPos(std::declval<typename T::axes_pos_t>()) } -> std::same_as<typename T::error_t>;
// get current positions and speeds (angles) of mount axes
{ t.getPos(std::declval<typename T::axes_pos_t*>()) } -> std::same_as<typename T::error_t>;
{ t.stop() } -> std::same_as<typename T::error_t>; // stop any moving
{ t.init() } -> std::same_as<typename T::error_t>; // initialize hardware
};
/* PROHIBITED ZONE CLASS CONCEPT */
template <mcc_error_c RetT>
struct mcc_pzone_interface_t {
virtual ~mcc_pzone_interface_t() = default;
template <std::derived_from<mcc_pzone_interface_t> SelfT, typename InputT>
RetT inPZone(this SelfT&& self, InputT coords, bool* result)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
return std::forward<SelfT>(self).InPZone(std::move(coords), result);
}
template <std::derived_from<mcc_pzone_interface_t> SelfT, typename InputT>
RetT timeToPZone(this SelfT&& self, InputT coords, traits::mcc_time_duration_c auto* res_time)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
return std::forward<SelfT>(self).timeToPZone(std::move(coords), res_time);
}
template <std::derived_from<mcc_pzone_interface_t> SelfT, typename InputT>
RetT timeFromPZone(this SelfT&& self, InputT coords, traits::mcc_time_duration_c auto* res_time)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
return std::forward<SelfT>(self).timeFromPZone(std::move(coords), res_time);
}
template <std::derived_from<mcc_pzone_interface_t> SelfT, typename InputT>
RetT intersectPZone(this SelfT&& self, InputT coords, mcc_celestial_point_c auto* point)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>) &&
requires { self.intersectPZone(coords, point); }
{
return std::forward<SelfT>(self).intersectPZone(std::move(coords), point);
}
protected:
mcc_pzone_interface_t() = default;
};
template <typename T>
concept mcc_prohibited_zone_c =
std::derived_from<T, mcc_pzone_interface_t<typename T::error_t>> && requires(const T t_const) {
{ t_const.name() } -> std::formattable<char>;
};
/* PROHIBITED ZONES CONTAINER CLASS CONCEPT */
template <mcc_error_c RetT>
struct mcc_pzone_container_interface_t {
virtual ~mcc_pzone_container_interface_t() = default;
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
size_t addPZone(this SelfT&& self, mcc_prohibited_zone_c auto zone)
{
return std::forward<SelfT>(self).addPZone(std::move(zone));
}
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
void clearPZones(this SelfT&& self)
{
return std::forward<SelfT>(self).clearPZones();
}
template <std::derived_from<mcc_pzone_container_interface_t> SelfT>
size_t sizePZones(this SelfT&& self)
{
return std::forward<SelfT>(self).sizePZones();
}
template <std::derived_from<mcc_pzone_container_interface_t> SelfT, typename InputT>
RetT inPZone(this SelfT&& self, InputT coords, std::ranges::output_range<bool> auto* result)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
return std::forward<SelfT>(self).InPZone(std::move(coords), result);
}
template <std::derived_from<mcc_pzone_container_interface_t> SelfT, typename InputT, traits::mcc_time_duration_c DT>
RetT timeToPZone(this SelfT&& self, InputT coords, std::ranges::output_range<DT> auto* res_time)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
return std::forward<SelfT>(self).timeToPZone(std::move(coords), res_time);
}
template <std::derived_from<mcc_pzone_container_interface_t> SelfT, typename InputT, traits::mcc_time_duration_c DT>
RetT timeFromPZone(this SelfT&& self, InputT coords, std::ranges::output_range<DT> auto* res_time)
requires(mcc_eqt_hrz_coord_c<InputT> || mcc_celestial_point_c<InputT>)
{
return std::forward<SelfT>(self).timeFromPZone(std::move(coords), res_time);
}
protected:
mcc_pzone_container_interface_t() = default;
};
template <typename T>
concept mcc_pzone_container_c = std::derived_from<T, mcc_pzone_container_interface_t<typename T::error_t>>;
} // namespace mcc

212
mcc/mcc_traits.h Normal file
View File

@ -0,0 +1,212 @@
#pragma once
#include <chrono>
#include <format>
#include <ranges>
namespace mcc::traits
{
template <typename R>
concept mcc_char_view = std::ranges::view<R> && std::same_as<std::ranges::range_value_t<R>, char>;
// input range of char/const char
template <typename R, typename CharT = char>
concept mcc_input_char_range =
std::ranges::input_range<R> && std::is_same_v<std::remove_cv_t<std::ranges::range_value_t<R>>, CharT>;
// output range of char/const char
template <typename R, typename CharT = char>
concept mcc_output_char_range =
std::ranges::output_range<R, CharT> && std::same_as<std::remove_cv_t<std::ranges::range_value_t<R>>, CharT>;
template <typename R>
concept mcc_view_or_output_char_range = mcc_char_view<R> || mcc_output_char_range<R>;
template <typename R>
concept mcc_range_of_input_char_range =
std::ranges::range<R> && traits::mcc_input_char_range<std::ranges::range_value_t<R>>;
template <typename R>
concept mcc_range_of_output_char_range =
std::ranges::range<R> && traits::mcc_output_char_range<std::ranges::range_value_t<R>>;
// https://stackoverflow.com/questions/72430369/how-to-check-that-a-type-is-formattable-using-type-traits-concepts)
template <typename T>
concept mcc_formattable =
requires(T v, std::format_context ctx) { std::formatter<std::remove_cvref_t<T>>().format(v, ctx); };
// from https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
template <typename T>
concept mcc_time_duration_c = requires {
[]<class Rep, class Period>(std::type_identity<std::chrono::duration<Rep, Period>>) {
}(std::type_identity<std::remove_cvref_t<T>>());
};
template <typename T>
concept mcc_systime_c = requires {
[]<class DT>(std::type_identity<std::chrono::sys_time<DT>>) {}(std::type_identity<std::remove_cvref_t<T>>());
};
/* a callable concept and its signature traits */
template <typename T>
concept mcc_is_callable = std::is_function_v<T> || (std::is_object_v<T> && requires(T) { &T::operator(); });
// helper classes for callable signature deducing
template <typename... Ts>
struct mcc_func_traits_helper_t;
template <typename R>
struct mcc_func_traits_helper_t<R> {
using ret_t = R;
using args_t = std::tuple<>;
using arg1_t = void;
static constexpr size_t arity = 0;
};
template <typename R, typename Arg, typename... Args>
struct mcc_func_traits_helper_t<R, Arg, Args...> {
using ret_t = R;
using args_t = std::tuple<Arg, Args...>;
using arg1_t = Arg;
static constexpr size_t arity = sizeof...(Args) + 1;
};
template <typename F>
struct mcc_func_traits {
// use of an empty struct here to match std::invoke_result behaivior (at least of GCC)
};
template <typename R, typename... Args>
struct mcc_func_traits<R (*)(Args...)> : mcc_func_traits_helper_t<R, Args...> {
};
template <typename R, typename... Args>
struct mcc_func_traits<R(Args...)> : mcc_func_traits_helper_t<R, Args...> {
};
template <typename C, typename R, typename... Args>
struct mcc_func_traits<R (C::*)(Args...)> : mcc_func_traits_helper_t<R, Args...> {
};
template <typename C, typename R, typename... Args>
struct mcc_func_traits<R (C::*)(Args...) const> : mcc_func_traits_helper_t<R, Args...> {
};
template <typename F>
requires mcc_is_callable<F>
struct mcc_func_traits<F> : mcc_func_traits<decltype(&F::operator())> {
};
// reference/const ref and rvalue helpers
template <typename F>
struct mcc_func_traits<F&> : mcc_func_traits<F> {
};
template <typename F>
struct mcc_func_traits<const F&> : mcc_func_traits<F> {
};
template <typename F>
struct mcc_func_traits<F&&> : mcc_func_traits<F> {
};
// type of the returned value
template <typename T>
using mcc_retval_t = typename mcc_func_traits<T>::ret_t;
// type of the first argument of callable
template <typename T>
using mcc_func_arg1_t = typename mcc_func_traits<T>::arg1_t;
// type of the N-th argument of callable
// NOTE: starts from 1 not from 0!!!
template <typename T, size_t N = 1>
using mcc_func_argN_t = std::conditional_t<N >= mcc_func_traits<T>::arity,
std::tuple_element_t<N - 1, typename mcc_func_traits<T>::args_t>,
void>;
// non-const lvalue reference, constructible from CtorArgTs (an output argument of function)
template <typename T, typename... CtorArgTs>
concept mcc_output_arg_c = !std::is_const_v<std::remove_reference_t<T>> && std::is_lvalue_reference_v<T> &&
std::constructible_from<std::remove_reference_t<T>, CtorArgTs...>;
// std::tuple or std::pair
template <typename T>
concept mcc_tuple_c = requires {
requires requires {
[]<typename... Ts>(std::type_identity<std::tuple<Ts...>>) {}(std::type_identity<std::remove_cvref_t<T>>());
} || requires {
[]<typename T1, typename T2>(std::type_identity<std::pair<T1, T2>>) {
}(std::type_identity<std::remove_cvref_t<T>>());
};
};
template <typename T>
concept mcc_nonconst_lvref = std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>;
template <typename T>
concept mcc_error_c = std::convertible_to<T, bool> || requires(const T t) {
{ t.operator bool() };
};
namespace details
{
// compile-time hash for type
// (from https://stackoverflow.com/questions/56292104/hashing-types-at-compile-time-in-c17-c2a)
// WARNING: it does not work for unnamed struct!!!
template <typename T>
static consteval size_t Hash()
{
size_t result{};
#ifdef _MSC_VER
for (const auto& c : __FUNCSIG__)
#else // GCC and clang
for (const auto& c : __PRETTY_FUNCTION__)
#endif
(result ^= c) <<= 1;
return result;
}
} // namespace details
template <typename T>
static constexpr size_t mcc_type_hash = details::Hash<T>();
static constexpr size_t mcc_hash_combine(size_t lhs, size_t rhs)
{
constexpr size_t v_const = sizeof(size_t) >= 8 ? 0x517cc1b727220a95 : 0x9e3779b9;
lhs ^= rhs + v_const + (lhs << 6) + (lhs >> 2);
return lhs;
}
template <typename T1, typename T2>
static constexpr size_t mcc_type_pair_hash()
{
return mcc_hash_combine(mcc_type_hash<T1>, mcc_type_hash<T2>);
}
} // namespace mcc::traits

311
mcc/mcc_utils.h Normal file
View File

@ -0,0 +1,311 @@
#pragma once
#include <charconv>
#include <cmath>
#include <cstring>
#include <format>
#include <numbers>
#include <ranges>
#include <regex>
namespace mcc::utils
{
static const std::regex decimalNumberRx{" *[-+]?([0-9]*[.])?[0-9]+([eE][-+]?[0-9]+)? *"};
static const std::regex sexagesimalReprRx{" *[-+]?[0-9]{1,2}:[0-9]{1,2}:([0-9]{0,2}[.])?[0-9]+ *"};
static bool isEqual(std::floating_point auto const& v1, std::floating_point auto const& v2)
{
constexpr auto eps = std::numeric_limits<std::common_type_t<decltype(v1), decltype(v2)>>::epsilon();
return std::fabs(v1 - v2) <= eps * std::fmax(std::fabs(v1), std::fabs(v2));
}
enum class TrimType { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH };
template <std::ranges::contiguous_range R>
static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH)
requires std::same_as<char, std::remove_cvref_t<std::ranges::range_value_t<R>>>
{
auto is_space = [](const auto& ch) { return ch == ' '; };
auto end = std::forward<R>(r).end();
auto f1 = std::forward<R>(r).begin();
if (type != TrimType::TRIM_RIGHT) {
// look for the first non-space symbol
f1 = std::ranges::find_if_not(std::forward<R>(r), is_space);
if (f1 == end) { // all are spaces!
return std::string_view();
}
}
auto f2 = end;
if (type != TrimType::TRIM_LEFT) {
auto f3 = f1;
do {
f2 = std::ranges::find_if(++f3, end, is_space);
if (f2 == end)
break;
f3 = std::ranges::find_if_not(f2 + 1, end, is_space);
} while (f3 != end);
}
return std::string_view(f1, f2);
}
static std::string_view trimSpaces(const char* r, TrimType type = TrimType::TRIM_BOTH)
{
return trimSpaces(std::string_view(r), type);
}
template <typename T, std::ranges::contiguous_range R>
std::optional<T> numFromStr(R&& r)
requires((std::integral<T> || std::floating_point<T>) &&
std::same_as<char, std::remove_cvref_t<std::ranges::range_value_t<R>>>)
{
T val;
const char* end_ptr = &*r.end();
if constexpr (std::integral<T>) {
auto cvt_res = std::from_chars(&*r.begin(), &*r.end(), val);
if (cvt_res.ec != std::errc()) {
return std::nullopt;
} else if (cvt_res.ptr != end_ptr) {
return std::nullopt;
}
} else {
#ifdef _LIBCPP_VERSION // clang's libc++ does not have floating-point overloads for std::from_chars
std::string s{str.begin(), str.end()};
size_t pos;
try {
if constexpr (std::same_as<T, float>) {
val = std::stof(s, &pos);
} else if constexpr (std::same_as<T, double>) {
val = std::stod(s, &pos);
} else {
val = std::stold(s, &pos);
}
} catch (...) {
return std::nullopt;
}
if (pos != s.size()) {
return std::nullopt;
}
#else
auto cvt_res = std::from_chars(&*r.begin(), &*r.end(), val);
if (cvt_res.ec != std::errc()) {
return std::nullopt;
} else if (cvt_res.ptr != end_ptr) {
return std::nullopt;
}
#endif
}
return val;
}
template <std::ranges::contiguous_range R>
static std::optional<double> parsAngleString(R&& r, bool hms = false)
requires std::same_as<char, std::remove_cvref_t<std::ranges::range_value_t<R>>>
{
auto str = trimSpaces(std::forward<R>(r));
bool ok = std::regex_match(str.begin(), str.end(), decimalNumberRx);
if (ok) {
return numFromStr<double>(str);
}
ok = std::regex_match(str.begin(), str.end(), sexagesimalReprRx);
if (ok) {
auto str = trimSpaces(std::forward<R>(r));
auto parts = std::views::split(str, std::string_view(":"));
double val;
double p1 = numFromStr<double>(*parts.begin()).value();
val = std::abs(p1);
double dv = 60.0;
for (auto const& v : parts | std::views::drop(1)) {
val += numFromStr<double>(v).value() / dv;
dv *= 60.0;
}
// val += numFromStr<double>(parts[1]) / 60.0;
// val += numFromStr<double>(parts[2]) / 3600.0;
if (p1 < 0) {
val = -val;
}
if (hms) {
val *= 15.0;
}
return val;
}
return std::nullopt;
}
static std::optional<double> parsAngleString(const char* s, bool hms = false)
{
return parsAngleString(std::span{s, std::strlen(s)}, hms);
}
static constexpr auto deg2radCoeff = std::numbers::pi / 180.0;
// radians to degrees
template <bool NORM = false>
static double rad2deg(double ang)
{
auto r = ang / deg2radCoeff;
if constexpr (NORM) {
return std::fmod(r, 360.0);
} else {
return r;
}
}
template <std::ranges::output_range<char> R, bool NORM = false>
static R rad2deg_str(double ang, int prec = 6)
{
R res;
std::string fmt = "{:0." + std::to_string(prec) + "f}";
auto degs = rad2deg<NORM>(ang);
std::vformat_to(std::back_inserter(res), {fmt.begin(), fmt.end()}, std::make_format_args(degs));
return res;
}
template <bool NORM = false>
static std::string rad2deg_str(double ang, int prec = 6)
{
return rad2deg_str<std::string, NORM>(ang, prec);
}
template <std::ranges::output_range<char> R, bool NORM = false>
static R rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6)
{
R res = rad2deg_str<R, NORM>(ang1, prec1);
R r = rad2deg_str<R, NORM>(ang2, prec2);
std::ranges::copy(delim, std::back_inserter(res));
std::ranges::copy(r, std::back_inserter(res));
return res;
}
template <bool NORM = false>
static std::string rad2deg_str(double ang1, double ang2, std::string_view delim = ",", int prec1 = 6, int prec2 = 6)
{
return rad2deg_str<std::string, NORM>(ang1, ang2, delim, prec1, prec2);
}
// radians to sexagesimal
template <std::ranges::output_range<char> R, bool NORM = false>
static R rad2sxg(double ang, bool hms = false, int prec = 2)
{
R res;
std::string fmt = "{:02.0f}:{:02.0f}:{:0" + std::to_string(prec + 3) + "." + std::to_string(prec) + "f}";
// std::string fmt = "{:02.0f}:{:02.0f}:{:02." + std::to_string(prec) + "f}";
auto degs = rad2deg<NORM>(std::abs(ang));
if (hms) {
degs /= 15.0;
}
auto term = 10.0;
for (int i = 1; i < prec; ++i) {
term *= 10.0;
}
auto d = std::trunc(degs);
auto s = (degs - d) * 60.0;
auto m = std::trunc(s);
s = (s - m) * 60.0;
// round to given precision
s = std::round(s * term) / term;
if (isEqual(s, 60.0)) {
m += 1.0;
s = 0.0;
if (isEqual(m, 60.0)) {
d += 1.0;
m = 0.0;
}
}
if (ang < 0) {
std::ranges::copy(std::string_view("-"), std::back_inserter(res));
}
std::vformat_to(std::back_inserter(res), std::string_view{fmt.begin(), fmt.end()}, std::make_format_args(d, m, s));
return res;
}
template <bool NORM = false>
static std::string rad2sxg(double ang, bool hms = false, int prec = 2)
{
return rad2sxg<std::string, NORM>(ang, hms, prec);
}
template <std::ranges::output_range<char> R, bool NORM = false>
static R RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1)
{
R res = rad2sxg<R, NORM>(ra, true, ra_prec);
R r = rad2sxg(dec, false, dec_prec);
std::ranges::copy(delim, std::back_inserter(res));
std::ranges::copy(r, std::back_inserter(res));
return res;
}
template <bool NORM = false>
static std::string RADEC_rad2sxg(double ra, double dec, std::string_view delim = ",", int ra_prec = 2, int dec_prec = 1)
{
return RADEC_rad2sxg<std::string, NORM>(ra, dec, delim, ra_prec, dec_prec);
}
template <std::ranges::output_range<char> R, bool NORM = false>
static R AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1)
{
R res = rad2sxg<R, NORM>(az, false, az_prec);
R r = rad2sxg(zd, false, zd_prec);
std::ranges::copy(delim, std::back_inserter(res));
std::ranges::copy(r, std::back_inserter(res));
return res;
}
template <bool NORM = false>
static std::string AZZD_rad2sxg(double az, double zd, std::string_view delim = ",", int az_prec = 2, int zd_prec = 1)
{
return AZZD_rad2sxg<std::string, NORM>(az, zd, delim, az_prec, zd_prec);
}
} // namespace mcc::utils