Report az/el for satellites excluded from the PVT fix, not just used ones

rescode() (rtklib_pntpos.cc) computes each satellite's azimuth/elevation via
satazel() before checking it against PVT.elevation_mask, and pntpos() copies
that azel into ssat[] unconditionally -- only ssat[].vs is gated by the mask
(the same applies to a satellite dropped by RAIM FDE). The used_satellites
loop in rtklib_solver.cc was keying its entire per-satellite entry on vs, so
a tracked satellite excluded from the fix (below the elevation mask, or by
RAIM) disappeared from MonitorPvt entirely -- indistinguishable from a
genuine tracking problem.

Key entries on whether azel was actually computed this epoch instead, and
add a separate `used` flag (default true) reflecting vs. A satellite that
is tracked but excluded from the fix now still reports its real az/el, with
used = false.
This commit is contained in:
joebre
2026-09-07 18:11:12 +02:00
parent 09a42eb5dc
commit 7c8dfbbb79
4 changed files with 34 additions and 6 deletions
+9 -5
View File
@@ -53,11 +53,14 @@ 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.
// One tracked satellite/signal with its azimuth/elevation, whether it was
// combined with another signal of the same satellite (e.g. Galileo E1+E5a
// iono-free combination), and whether it was actually used in this fix.
// Signals are listed individually, not merged: a combined satellite
// appears as two entries, both with combined = true. A satellite can have
// azimuth_deg/elevation_deg populated with used = false when it was
// tracked and had az/el computed but excluded from the fix itself (e.g.
// below PVT.elevation_mask, or by RAIM FDE).
message UsedSatellite {
uint32 prn = 1;
string system = 2; // "G" GPS, "E" Galileo, "R" GLONASS, "C" BeiDou, "S" SBAS, "J" QZSS
@@ -65,6 +68,7 @@ message MonitorPvt {
double azimuth_deg = 4;
double elevation_deg = 5;
bool combined = 6;
bool used = 7;
}
repeated UsedSatellite used_satellites = 37;
}
+7
View File
@@ -53,6 +53,12 @@ public:
double azimuth_deg{};
double elevation_deg{};
bool combined{};
// false when this satellite/signal was tracked and had azimuth/elevation
// computed but was excluded from the fix itself (e.g. below
// PVT.elevation_mask, or by RAIM FDE) -- azimuth_deg/elevation_deg are
// still valid in that case, only the position solve ignored this
// observation.
bool used{true};
template <class Archive>
void serialize(Archive& ar, const unsigned int version)
@@ -66,6 +72,7 @@ public:
ar& BOOST_SERIALIZATION_NVP(azimuth_deg);
ar& BOOST_SERIALIZATION_NVP(elevation_deg);
ar& BOOST_SERIALIZATION_NVP(combined);
ar& BOOST_SERIALIZATION_NVP(used);
}
};
+16 -1
View File
@@ -2837,13 +2837,27 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
// iono-free combination -- see the "dual-frequency" branch of
// prange() in rtklib_pntpos.cc) gets one entry per signal, all
// flagged combined = true.
//
// rescode() (rtklib_pntpos.cc) computes azel via satazel()
// *before* checking it against PVT.elevation_mask, and
// pntpos() copies that azel into ssat[] unconditionally --
// only ssat[].vs is gated by the mask. So a satellite that
// is tracked and has a live observation this epoch, but
// falls below PVT.elevation_mask (or was excluded by RAIM
// FDE), still has a valid azel here; only vs is false.
// Report it anyway with used = false instead of dropping
// it, so an excluded satellite shows up in the monitor as
// "not used" rather than as missing/no-az-el (which
// otherwise looks identical to a tracking problem).
d_monitor_pvt.used_satellites.clear();
for (int sat_idx = 0; sat_idx < MAXSAT; sat_idx++)
{
if (!pvt_ssat[sat_idx].vs)
const bool has_azel = (pvt_ssat[sat_idx].azel[0] != 0.0) || (pvt_ssat[sat_idx].azel[1] != 0.0);
if (!has_azel)
{
continue;
}
const bool used = pvt_ssat[sat_idx].vs != 0;
int prn = 0;
char sys_char = '?';
switch (satsys(sat_idx + 1, &prn))
@@ -2896,6 +2910,7 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
info.azimuth_deg = az_deg;
info.elevation_deg = pvt_ssat[sat_idx].azel[1] * R2D;
info.combined = combined;
info.used = used;
d_monitor_pvt.used_satellites.push_back(info);
}
}
@@ -129,6 +129,7 @@ public:
pb_sat->set_azimuth_deg(sat.azimuth_deg);
pb_sat->set_elevation_deg(sat.elevation_deg);
pb_sat->set_combined(sat.combined);
pb_sat->set_used(sat.used);
}
if (!monitor_.SerializeToString(&data))
@@ -188,6 +189,7 @@ public:
sat.azimuth_deg = pb_sat.azimuth_deg();
sat.elevation_deg = pb_sat.elevation_deg();
sat.combined = pb_sat.combined();
sat.used = pb_sat.used();
monitor.used_satellites.push_back(sat);
}