#pragma once /********************************* * MOUNT CONTROL COMPONENTS * * * * astrometry functions * *********************************/ #include #include #include #include "utils.h" namespace mcc::traits { template concept mcc_scalar_or_simd_c = xsimd::is_batch::value || std::is_arithmetic_v; template concept mcc_real_or_char_range = std::floating_point || (std::ranges::contiguous_range && std::same_as>>); } // namespace mcc::traits namespace mcc::astro { // modified Julian date (based on ERFA eraCal2jd) // template static int mcc_julday(const std::chrono::system_clock::time_point& start_time, const std::chrono::system_clock::duration& step, std::vector>& mjd) { if (mjd.empty()) { return -100; } using namespace std::literals::chrono_literals; auto dd = std::chrono::floor(start_time); std::chrono::year_month_day ymd{dd}; static constexpr std::chrono::year MIN_YEAR = -4799y; if (ymd.year() < MIN_YEAR) { return -1; } if (!ymd.month().ok()) { return -2; } // my = (im - 14) / 12; // iypmy = (long) (iy + my); int64_t my = -(14 - (unsigned)ymd.month()) / 12; int64_t iypmy = (int)ymd.year() + my; // (1461L * (iypmy + 4800L)) / 4L // + (367L * (long) (im - 2 - 12 * my)) / 12L // - (3L * ((iypmy + 4900L) / 100L)) / 4L // + (long) id - 2432076L // integer part of result MJD int64_t mjd_int = 1461LL * (iypmy + 480LL) / 4LL + (367LL * ((int64_t)(unsigned)ymd.month() - 2LL - 12LL * my)) / 12LL - (3LL * (iypmy + 4900LL) / 100LL) / 4LL + (int64_t)(unsigned)ymd.day() - 2432076LL; constexpr double nanosec = 1.0 / 24.0 / 3600.0 / 1.0E-9; // 1 nanosecond in days double mjd_float = static_cast(mjd_int) + std::chrono::duration_cast(start_time - dd).count() * nanosec; double d_step = std::chrono::duration_cast(step).count() * nanosec; constexpr size_t reg_size = xsimd::batch::size; size_t vec_size = mjd.size() - mjd.size() % reg_size; xsimd::batch res_reg{mjd_float}; xsimd::batch step_reg = [d_step](std::index_sequence) { return xsimd::batch{(Is * d_step)...}; }(std::make_index_sequence{}); // vectorized part size_t i = 0; for (; i < vec_size; i += vec_size) { res_reg += step_reg; res_reg.store_aligned(mjd.data() + i); } // scalar part for (size_t j = i; j < mjd.size(); ++j) { mjd[j] = mjd_float + j * d_step; } return 0; } /* * angles are in degrees or sexagimal string form */ template double mcc_time_to_alt_limit(const AT& alt_limit, const RT& RA, const DT& DEC, const LT& LAT, const std::chrono::system_clock::time_point& now) { // sin(alt) = sin(DEC)*sin(phi) + cos(DEC)*cos(phi)*cos(HA) // HA = LST - RA double ra, dec, lat, alt; if constexpr (std::floating_point) { alt = alt_limit * utils::deg2radCoeff; } else { alt = utils::parsAngleString(alt_limit); alt *= utils::deg2radCoeff; } if constexpr (std::floating_point) { ra = RA * utils::deg2radCoeff; } else { ra = utils::parsAngleString(RA, true); ra *= utils::deg2radCoeff; } if constexpr (std::floating_point
) { dec = DEC * utils::deg2radCoeff; } else { dec = utils::parsAngleString(DEC); dec *= utils::deg2radCoeff; } if constexpr (std::floating_point) { lat = LAT * utils::deg2radCoeff; } else { lat = utils::parsAngleString(LAT); lat *= utils::deg2radCoeff; } double cos_ha = (std::sin(alt) - std::sin(dec) * std::sin(lat)) / std::cos(dec) / std::cos(lat); if (std::abs(cos_ha) > 1.0) { // circumpolar (it never sets below horizon) return std::numeric_limits::infinity(); } double lst = std::acos(cos_ha) + ra; return 0.0; } /* IERS bulletins * * BULLETIN A: https://datacenter.iers.org/data/latestVersion/bulletinA.txt * leapseconds: https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat * */ struct leapsecond_db_elem_t { double mjd; unsigned day, month; int year; // std::chrono::year_month_day ymd; double tai_utc; // TAI-UTC in seconds }; typedef std::vector leapsecond_db_t; struct earth_orient_db_elem_t { int year; unsigned month, day; double mjd; double x, y; // Polar coordinates in arcsecs double dut1; // UT1-UTC in seconds }; // typedef std::vector earth_orient_db_t; struct earth_orient_db_t { std::chrono::system_clock::time_point bulletinDate{}; std::vector db{}; }; // init to some known state static leapsecond_db_t CURRENT_LEAPSECONDS_DB = { {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} }; static earth_orient_db_t CURRENT_EARTH_ORIENT_DB; static bool mcc_parse_bulletinA(std::derived_from> auto& stream, char comment_sym = '*') { 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]+ *$"}; const std::regex bull_tab_title_rx{"^ *MJD +x\\(arcsec\\) +y\\(arcsec\\) +UT1-UTC\\(sec\\) *$"}; // 2025 3 7 60741 0.0663 0.3341 0.04348 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]+ *$"}; earth_orient_db_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; 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.db.emplace_back(year, month, day, mjd, 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", db.bulletinDate); 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.db.empty()) { return false; } CURRENT_EARTH_ORIENT_DB = std::move(db); return true; } static bool mcc_parse_leapsecs(std::derived_from> auto& stream, char comment_sym = '#') { // # File expires on 28 December 2025 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} *$"}; const std::regex data_rx{"^ *[0-9]{5,}(\\.?[0-9]+) +[0-9]{1,2} +[0-9]{1,2} +[0-9]{4} +[0-9]{1,} *$"}; std::istringstream is; double mjd; unsigned day, month; int year; double tai_utc; leapsecond_db_t db; // leapsecond_db_elem_t db_elem; 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 continue; } } else { continue; } if (std::regex_match(line, data_rx)) { is.str(std::move(line)); is >> mjd >> day >> month >> year >> tai_utc; db.emplace_back(mjd, day, month, year, tai_utc); is.clear(); } } if (db.empty()) { return false; } CURRENT_LEAPSECONDS_DB = std::move(db); return true; } } // namespace mcc::astro