diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 19b8d9554..bdd89e35d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -387,6 +387,9 @@ All notable changes to GNSS-SDR will be documented in this file. epochs now fall on the same integer-millisecond grid as the observables, as they already did in configurations including GPS. RINEX files were not affected. +- The PVT Monitor now reports per-signal details for satellites used in the + position solution, including PRN, constellation, signal, azimuth, elevation, + and whether multiple signals were combined. Contributed by @joebre. See the definitions of concepts and metrics at https://gnss-sdr.org/design-forces/ diff --git a/docs/protobuf/monitor_pvt.proto b/docs/protobuf/monitor_pvt.proto index 2ef1a6d2c..4461dd3f9 100644 --- a/docs/protobuf/monitor_pvt.proto +++ b/docs/protobuf/monitor_pvt.proto @@ -52,4 +52,19 @@ message MonitorPvt { uint32 galhas_status = 35; // Galileo HAS status: 1- HAS messages decoded and applied, 0 - HAS not available string geohash = 36; // Encoded geographic location. See https://en.wikipedia.org/wiki/Geohash + + // One satellite/signal used in this fix, with its azimuth/elevation and + // whether it was combined with another signal of the same satellite (e.g. + // Galileo E1+E5a iono-free combination). Signals are listed individually, + // not merged: a combined satellite appears as two entries, both with + // combined = true. + message UsedSatellite { + uint32 prn = 1; + string system = 2; // "G" GPS, "E" Galileo, "R" GLONASS, "C" BeiDou, "S" SBAS, "J" QZSS + string signal = 3; + double azimuth_deg = 4; + double elevation_deg = 5; + bool combined = 6; + } + repeated UsedSatellite used_satellites = 37; } diff --git a/src/algorithms/PVT/libs/monitor_pvt.h b/src/algorithms/PVT/libs/monitor_pvt.h index 863d8c004..6a3b278e4 100644 --- a/src/algorithms/PVT/libs/monitor_pvt.h +++ b/src/algorithms/PVT/libs/monitor_pvt.h @@ -18,8 +18,10 @@ #define GNSS_SDR_MONITOR_PVT_H #include +#include #include #include +#include /** \addtogroup PVT * \{ */ @@ -33,6 +35,42 @@ class Monitor_Pvt { public: + /*! + * \brief One satellite/signal that contributed to this fix, with its + * azimuth/elevation and whether it was combined with another signal of + * the same satellite (e.g. Galileo E1+E5a iono-free combination -- see + * the "dual-frequency" branch of prange() in rtklib_pntpos.cc). Signals + * are listed individually (one entry per satellite per signal), not + * merged, so a combined satellite appears as two entries both flagged + * combined = true. + */ + class UsedSatelliteInfo + { + public: + uint32_t prn{}; + char system{}; // 'G' GPS, 'E' Galileo, 'R' GLONASS, 'C' BeiDou, 'S' SBAS, 'J' QZSS + std::string signal; + double azimuth_deg{}; + double elevation_deg{}; + bool combined{}; + + template + void serialize(Archive& ar, const unsigned int version) + { + if (version) + { + }; + ar& BOOST_SERIALIZATION_NVP(prn); + ar& BOOST_SERIALIZATION_NVP(system); + ar& BOOST_SERIALIZATION_NVP(signal); + ar& BOOST_SERIALIZATION_NVP(azimuth_deg); + ar& BOOST_SERIALIZATION_NVP(elevation_deg); + ar& BOOST_SERIALIZATION_NVP(combined); + } + }; + + std::vector used_satellites; + // TOW uint32_t TOW_at_current_symbol_ms; // WEEK @@ -155,6 +193,7 @@ public: ar& BOOST_SERIALIZATION_NVP(cog); ar& BOOST_SERIALIZATION_NVP(geohash); + ar& BOOST_SERIALIZATION_NVP(used_satellites); } }; diff --git a/src/algorithms/PVT/libs/rtklib_solver.cc b/src/algorithms/PVT/libs/rtklib_solver.cc index eb6f6b1c5..a91e788e7 100644 --- a/src/algorithms/PVT/libs/rtklib_solver.cc +++ b/src/algorithms/PVT/libs/rtklib_solver.cc @@ -2830,6 +2830,76 @@ bool Rtklib_Solver::get_PVT(const std::map &gnss_observables_ d_monitor_pvt.hdop = d_dop[2]; d_monitor_pvt.vdop = d_dop[3]; + // USED SATELLITES: per-satellite azimuth/elevation and which + // signal(s) contributed to this fix. Signals are listed + // individually (one entry per satellite per signal); a + // satellite whose signals were combined (e.g. Galileo E1+E5a + // iono-free combination -- see the "dual-frequency" branch of + // prange() in rtklib_pntpos.cc) gets one entry per signal, all + // flagged combined = true. + d_monitor_pvt.used_satellites.clear(); + for (int sat_idx = 0; sat_idx < MAXSAT; sat_idx++) + { + if (!pvt_ssat[sat_idx].vs) + { + continue; + } + int prn = 0; + char sys_char = '?'; + switch (satsys(sat_idx + 1, &prn)) + { + case SYS_GPS: + sys_char = 'G'; + break; + case SYS_GAL: + sys_char = 'E'; + break; + case SYS_GLO: + sys_char = 'R'; + break; + case SYS_BDS: + sys_char = 'C'; + break; + case SYS_SBS: + sys_char = 'S'; + break; + case SYS_QZS: + sys_char = 'J'; + break; + default: + break; + } + + std::vector contributing_signals; + for (const auto &observable_pair : gnss_observables_map) + { + const Gnss_Synchro &synchro = observable_pair.second; + if (synchro.System == sys_char && static_cast(synchro.PRN) == prn) + { + contributing_signals.push_back(&synchro); + } + } + const bool combined = contributing_signals.size() > 1; + // satazel() (rtklib_rtkcmn.cc) returns azimuth in [0, 2*pi); wrap to + // (-180, 180] deg, the convention expected downstream (monitor sky plot). + double az_deg = pvt_ssat[sat_idx].azel[0] * R2D; + if (az_deg > 180.0) + { + az_deg -= 360.0; + } + for (const Gnss_Synchro *synchro : contributing_signals) + { + Monitor_Pvt::UsedSatelliteInfo info; + info.prn = static_cast(prn); + info.system = sys_char; + info.signal = std::string(synchro->Signal, 2); + info.azimuth_deg = az_deg; + info.elevation_deg = pvt_ssat[sat_idx].azel[1] * R2D; + info.combined = combined; + d_monitor_pvt.used_satellites.push_back(info); + } + } + this->set_rx_vel({enuv[0], enuv[1], enuv[2]}); // ENU vel [m/s] diff --git a/src/algorithms/PVT/libs/serdes_monitor_pvt.h b/src/algorithms/PVT/libs/serdes_monitor_pvt.h index 905c4deeb..f6d0b15e4 100644 --- a/src/algorithms/PVT/libs/serdes_monitor_pvt.h +++ b/src/algorithms/PVT/libs/serdes_monitor_pvt.h @@ -120,6 +120,17 @@ public: monitor_.set_galhas_status(monitor->galhas_status); monitor_.set_geohash(monitor->geohash); + for (const auto& sat : monitor->used_satellites) + { + gnss_sdr::MonitorPvt::UsedSatellite* pb_sat = monitor_.add_used_satellites(); + pb_sat->set_prn(sat.prn); + pb_sat->set_system(std::string(1, sat.system)); + pb_sat->set_signal(sat.signal); + pb_sat->set_azimuth_deg(sat.azimuth_deg); + pb_sat->set_elevation_deg(sat.elevation_deg); + pb_sat->set_combined(sat.combined); + } + if (!monitor_.SerializeToString(&data)) { return {}; @@ -168,6 +179,18 @@ public: monitor.galhas_status = mon.galhas_status(); monitor.geohash = mon.geohash(); + for (const auto& pb_sat : mon.used_satellites()) + { + Monitor_Pvt::UsedSatelliteInfo sat; + sat.prn = pb_sat.prn(); + sat.system = pb_sat.system().empty() ? '\0' : pb_sat.system()[0]; + sat.signal = pb_sat.signal(); + sat.azimuth_deg = pb_sat.azimuth_deg(); + sat.elevation_deg = pb_sat.elevation_deg(); + sat.combined = pb_sat.combined(); + monitor.used_satellites.push_back(sat); + } + return monitor; }