.. _program_listing_file_interfaces_antex_loader.cc: Program Listing for File antex_loader.cc ======================================== |exhale_lsh| :ref:`Return to documentation for file ` (``interfaces/antex_loader.cc``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #include "lupnt/interfaces/antex_loader.h" #include #include #include #include #include #include #include "lupnt/conversions/time_conversions.h" #include "lupnt/core/error.h" #include "lupnt/core/file.h" #include "lupnt/data/kernels.h" namespace lupnt { namespace { constexpr int kLabelCol = 60; // ANTEX label starts at column 61 (1-based) std::string GetLabel(const std::string& line) { if (line.size() <= kLabelCol) return ""; std::string label = line.substr(kLabelCol); // rstrip label.erase(label.find_last_not_of(" \t\r\n") + 1); return label; } std::string GetData(const std::string& line) { return line.substr(0, std::min(line.size(), static_cast(kLabelCol))); } std::string ToUpper(const std::string& s) { std::string out = s; std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return std::toupper(c); }); return out; } std::string Trim(const std::string& s) { size_t b = s.find_first_not_of(" \t\r\n"); if (b == std::string::npos) return ""; size_t e = s.find_last_not_of(" \t\r\n"); return s.substr(b, e - b + 1); } bool ParseAntexDatetimeTai(const std::string& data, double& t_tai) { std::istringstream ss(data); int y, mo, d; int hh = 0, mm = 0; double ssec = 0.0; if (!(ss >> y >> mo >> d)) return false; ss >> hh >> mm >> ssec; // optional fields default to 0 Real t = ConvertTime(GregorianToTime(y, mo, d, hh, mm, ssec), Time::GPS, Time::TAI); t_tai = t.val(); return true; } bool LooksLikePrnToken(const std::string& tok) { static const std::regex re("^[A-Z][0-9]{2}$"); return std::regex_match(tok, re); } inline Vec3d ToVec3d(const Vec3& v) { return Vec3d(v(0).val(), v(1).val(), v(2).val()); } } // namespace // Constructors *************************************************************** AntexLoader::AntexLoader(const std::filesystem::path& filepath) { LoadFile(filepath); } // Loading ******************************************************************** void AntexLoader::LoadFile(const std::filesystem::path& filepath) { ParseFile(filepath); } void AntexLoader::ParseFile(const std::filesystem::path& filepath) { LUPNT_CHECK(std::filesystem::exists(filepath), "ANTEX file not found: " + filepath.string(), "AntexLoader"); std::ifstream file = OpenFile(filepath); SatAntennaEntry cur_entry; bool have_entry = false; std::string cur_freq; bool have_freq = false; std::string raw; while (std::getline(file, raw)) { // strip trailing CR (Windows line endings) if (!raw.empty() && raw.back() == '\r') raw.pop_back(); std::string label = GetLabel(raw); std::string data = GetData(raw); if (label == "START OF ANTENNA") { cur_entry = SatAntennaEntry{}; have_entry = true; cur_freq.clear(); have_freq = false; continue; } if (label == "END OF ANTENNA") { if (have_entry && !cur_entry.sat_id.empty()) { sat_index_[cur_entry.sat_id].push_back(cur_entry); } have_entry = false; cur_freq.clear(); have_freq = false; continue; } if (!have_entry) continue; if (label == "TYPE / SERIAL NO") { std::istringstream ss(data); std::string tok; std::string prn_token; while (ss >> tok) { if (LooksLikePrnToken(tok)) { prn_token = tok; break; } } cur_entry.sat_id = prn_token; // empty -> receiver block, not indexed continue; } if (label == "VALID FROM") { double t; if (ParseAntexDatetimeTai(data, t)) { cur_entry.has_valid_from = true; cur_entry.valid_from_tai = t; } continue; } if (label == "VALID UNTIL") { double t; if (ParseAntexDatetimeTai(data, t)) { cur_entry.has_valid_until = true; cur_entry.valid_until_tai = t; } continue; } if (label == "START OF FREQUENCY") { if (cur_entry.sat_id.empty()) { have_freq = false; continue; } std::istringstream ss(data); std::string tok; if (!(ss >> tok)) continue; cur_freq = Trim(tok); have_freq = true; cur_entry.freqs.emplace(cur_freq, FreqPattern{cur_freq}); continue; } if (label == "END OF FREQUENCY") { cur_freq.clear(); have_freq = false; continue; } if (cur_entry.sat_id.empty() || !have_freq) continue; if (label == "NORTH / EAST / UP") { std::istringstream ss(data); double n, e, u; if (ss >> n >> e >> u) { FreqPattern& pat = cur_entry.freqs.at(cur_freq); pat.has_pco = true; // ANTEX stores PCO in millimeters pat.pco_neu_m = Vec3d(n, e, u) / 1000.0; } continue; } } } // Queries ******************************************************************** std::string AntexLoader::GnssLetter(GnssConst gnss_const) { switch (gnss_const) { case GnssConst::GPS: return "G"; case GnssConst::GLONASS: return "R"; case GnssConst::GALILEO: return "E"; case GnssConst::BEIDOU: return "C"; case GnssConst::QZSS: return "J"; default: return "G"; } } std::string AntexLoader::SatId(GnssConst gnss_const, int prn) { std::ostringstream ss; ss << GnssLetter(gnss_const) << std::setfill('0') << std::setw(2) << prn; return ss.str(); } std::string AntexLoader::FreqToAntexCode(const std::string& gnss_letter, const std::string& freq_name) { std::string c = ToUpper(gnss_letter); std::string f = ToUpper(freq_name); if (c == "G") { if (f == "L1") return "G01"; if (f == "L2") return "G02"; if (f == "L5") return "G05"; } else if (c == "E") { if (f == "E1") return "E01"; if (f == "E5A" || f == "E5a") return "E05"; if (f == "E5B") return "E07"; if (f == "E5") return "E08"; if (f == "E6") return "E06"; } else if (c == "R") { if (f == "G1") return "R01"; if (f == "G2") return "R02"; if (f == "G3") return "R03"; } else if (c == "J") { if (f == "L1") return "J01"; if (f == "L2") return "J02"; if (f == "L5") return "J05"; if (f == "L6") return "J06"; } else if (c == "C") { if (f == "B1I") return "C02"; if (f == "B1C") return "C01"; if (f == "B2A") return "C05"; if (f == "B2I") return "C07"; if (f == "B3I") return "C06"; } return f; // fallback: assume `freq_name` is already an ANTEX code } bool AntexLoader::HasSatellite(GnssConst gnss_const, int prn) const { return sat_index_.find(SatId(gnss_const, prn)) != sat_index_.end(); } const AntexLoader::SatAntennaEntry& AntexLoader::SelectEntry(const std::string& sat_id, double t_tai) const { auto it = sat_index_.find(sat_id); LUPNT_CHECK(it != sat_index_.end(), "Satellite '" + sat_id + "' not found in ANTEX file", "AntexLoader"); const SatAntennaEntry* best = nullptr; for (const auto& e : it->second) { bool ok_from = !e.has_valid_from || t_tai >= e.valid_from_tai; bool ok_until = !e.has_valid_until || t_tai <= e.valid_until_tai; if (ok_from && ok_until) { // Prefer the entry with the latest `valid_from` (mirrors `_select_entry`'s // descending sort by `valid_from`). if (best == nullptr || (e.has_valid_from && (!best->has_valid_from || e.valid_from_tai > best->valid_from_tai))) { best = &e; } } } LUPNT_CHECK(best != nullptr, "No valid ANTEX entry for satellite '" + sat_id + "' at the requested epoch", "AntexLoader"); return *best; } std::vector AntexLoader::GetAvailableFreqCodes(GnssConst gnss_const, int prn, Real t_tai) const { const SatAntennaEntry& entry = SelectEntry(SatId(gnss_const, prn), t_tai.val()); std::vector codes; codes.reserve(entry.freqs.size()); for (const auto& [code, _] : entry.freqs) codes.push_back(code); std::sort(codes.begin(), codes.end()); return codes; } Vec3d AntexLoader::GetPco(const std::string& gnss_letter, int prn, const std::string& antex_freq_code, Real t_tai) const { std::ostringstream ss; ss << ToUpper(gnss_letter) << std::setfill('0') << std::setw(2) << prn; std::string sat_id = ss.str(); const SatAntennaEntry& entry = SelectEntry(sat_id, t_tai.val()); std::string code = ToUpper(antex_freq_code); auto it = entry.freqs.find(code); LUPNT_CHECK(it != entry.freqs.end() && it->second.has_pco, "PCO not found for satellite '" + sat_id + "', frequency code '" + code + "'", "AntexLoader"); return it->second.pco_neu_m; } Vec3d AntexLoader::GetPco(GnssConst gnss_const, int prn, GnssFreq freq, Real t_tai) const { std::string gnss_letter = GnssLetter(gnss_const); std::string freq_name = ToUpper(std::string(enum_name(freq))); std::string code = FreqToAntexCode(gnss_letter, freq_name); return GetPco(gnss_letter, prn, code, t_tai); } // PCO -> ECEF correction ***************************************************** Mat3d AntexLoader::ComputeIjkToEcefRotation(Real t_tai, const Vec3d& r_sat_ecef) { constexpr double kEps = 1e-12; Vec3d kvec = -r_sat_ecef.normalized(); LUPNT_CHECK(r_sat_ecef.norm() > kEps, "Cannot normalize near-zero satellite position", "AntexLoader"); Real t_tdb = ConvertTime(t_tai, Time::TAI, Time::TDB); Vec3d r_sun_ecef = ToVec3d(GetBodyPos(t_tdb, BodyId::EARTH, BodyId::SUN, Frame::ECEF)); Vec3d to_sun = r_sun_ecef - r_sat_ecef; LUPNT_CHECK(to_sun.norm() > kEps, "Cannot normalize near-zero Sun direction", "AntexLoader"); Vec3d jvec = to_sun.normalized(); Vec3d ivec = jvec.cross(kvec); Mat3d Cijk; Cijk.col(0) = ivec; Cijk.col(1) = jvec; Cijk.col(2) = kvec; return Cijk; } Vec3d AntexLoader::ApplyPcoCorrectionEcef(Real t_tai, const Vec3d& pos_sp3_ecef, const Vec3d& pco_neu_m) { Mat3d Cijk = ComputeIjkToEcefRotation(t_tai, pos_sp3_ecef); Vec3d pco_ecef = Cijk * pco_neu_m; return pos_sp3_ecef + pco_ecef; } } // namespace lupnt