Move the NaN guard from monitor reporting to the position solve

The previous commit filtered NaN azel out of tracked_satellites
entirely, which was the wrong place: NaN there is a legitimate,
informative result (ephemeris/almanac was corrupt enough to produce
one) that the monitor and its consumers should see and handle
explicitly, not have silently hidden.

What must never happen is a NaN satellite influencing the actual
position fix. Add that guard where it belongs: rescode() in
rtklib_pntpos.cc, right after satazel() and prange(), using
std::isfinite() (neither elaux < opt->elmin nor prange()'s P == 0.0
sentinel catches NaN, since ordinary relational comparisons are
vacuously false against it). This is the single gatekeeper for
vsat[]/nv, so the guard transitively protects everything downstream
that filters on vsat[] too: DOP computation, the chi-square residual
check, RAIM FDE, and the velocity/Doppler solve (resdop()/estvel()) --
not just the position solve directly.

tracked_satellites reporting goes back to reporting azel as computed,
NaN included.
This commit is contained in:
joebre
2026-09-10 08:54:18 +02:00
parent 9e4fd6b4c6
commit cc11435774
2 changed files with 25 additions and 13 deletions
+11 -11
View File
@@ -2850,20 +2850,20 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
// "not used" rather than as missing/no-az-el (which
// otherwise looks identical to a tracking problem).
//
// std::isfinite() guards against corrupt ephemeris/almanac
// (e.g. an out-of-range eccentricity) propagating NaN through
// eph2pos()/alm2pos()/satazel() -- none of RTKLIB's own
// internal sanity checks (A <= 0, Kepler iteration overflow,
// geodist()'s Earth-radius check, the elevation mask) catch a
// NaN, since ordinary numeric comparisons are vacuously false
// against it. Without this, a NaN azel would actually pass
// the old != 0.0 check (NaN != 0.0 is true) and get reported
// into the monitor as a literal NaN azimuth/elevation.
// Deliberately NOT filtered for NaN here: a corrupt ephemeris/
// almanac (e.g. an out-of-range eccentricity) can propagate NaN
// through eph2pos()/alm2pos()/satazel(), and that NaN is reported
// as-is rather than silently dropping the satellite from the
// monitor -- consumers (gnss-sdr-CtrlApp) are expected to handle
// a NaN azimuth_deg/elevation_deg explicitly (e.g. no sky plot
// entry, "NaN" printed in the signal table) rather than have it
// hidden here. What NaN must NOT do is influence the position fix
// itself -- see rescode()'s prange() isfinite guard in
// rtklib_pntpos.cc for where that's actually enforced.
d_monitor_pvt.tracked_satellites.clear();
for (int sat_idx = 0; sat_idx < MAXSAT; sat_idx++)
{
const bool has_azel = std::isfinite(pvt_ssat[sat_idx].azel[0]) && std::isfinite(pvt_ssat[sat_idx].azel[1]) &&
((pvt_ssat[sat_idx].azel[0] != 0.0) || (pvt_ssat[sat_idx].azel[1] != 0.0));
const bool has_azel = (pvt_ssat[sat_idx].azel[0] != 0.0) || (pvt_ssat[sat_idx].azel[1] != 0.0);
if (!has_azel)
{
continue;
+14 -2
View File
@@ -736,14 +736,26 @@ int rescode(int iter, const obsd_t *obs, int n, const double *rs,
continue;
}
double elaux = satazel(pos, e, azel + i * 2);
if (elaux < opt->elmin)
/* !isfinite(elaux): corrupt ephemeris/almanac (e.g. an out-of-range
* eccentricity) can propagate NaN through eph2pos()/alm2pos() into
* azel -- azel itself is intentionally left as computed (including
* NaN) for callers that report it (e.g. the monitor's per-satellite
* az/el), but a NaN elevation must not be allowed into the position
* solve below: elaux < opt->elmin is a no-op against NaN (every
* relational comparison against NaN is false), so it would
* otherwise fall through as if elmin had been satisfied. */
if (elaux < opt->elmin || !std::isfinite(elaux))
{
trace(4, "satazel error. el = %lf , elmin = %lf\n", elaux, opt->elmin);
continue;
}
/* psudorange with code bias correction */
double iono_scale = 1.0;
if ((P = prange(obs + i, nav, azel + i * 2, iter, opt, &vmeas, &iono_scale)) == 0.0)
P = prange(obs + i, nav, azel + i * 2, iter, opt, &vmeas, &iono_scale);
/* Same NaN concern as above, for prange()'s own corrections (iono/
* tropo/code bias) -- P == 0.0 is prange()'s own "no valid pseudorange"
* sentinel and doesn't catch NaN either. */
if (P == 0.0 || !std::isfinite(P))
{
trace(4, "prange error\n");
continue;