Changeset 11047 in ntrip for trunk/BNC/src/PPP/pppSatObs.cpp


Ignore:
Timestamp:
Sep 28, 2026, 11:17:34 AM (7 hours ago)
Author:
stuerze
Message:

updates regarding RTCM-SSR and some ovarall improvements

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/src/PPP/pppSatObs.cpp

    r11042 r11047  
    400400
    401401
    402 // A system's Satellite Antenna message is only trustworthy once the
    403 // provider has confirmed it via Metadata: SatelliteAntennaIOD (DF+010) must
    404 // be non-zero, and must match the Data IOD (DF+069) of a Metadata entry for
    405 // model-correction type 1 (satellite antenna PCV) or type 2 (GDV) - either
    406 // entry matching is sufficient. Metadata carries this IOD once globally, not
    407 // per system (DF+010 is GNSS-specific, DF+069 is not), so the same Metadata
    408 // entries are reused as the trust anchor for every system's check - the
    409 // provider is relied on to keep DF+010 synchronized with DF+069 across all
    410 // systems for this to work.
     402// Satellite Antenna IOD referenced by the SSR Metadata: the Data IOD (DF+069)
     403// of the model-correction entry of type 1 (satellite antenna PCV) or type 2
     404// (GDV). refIOD = 0 if Metadata references no antenna information (no
     405// Metadata, no such entry, no Data IOD, or a zero Data IOD). A provider's
     406// solution is relative to one Satellite Antenna information only, so two
     407// different non-zero IODs are inconsistent: returns false in that case.
     408//
     409// A non-zero reference means the provider's biases, and orbits/clocks, are
     410// relative to exactly that Satellite Antenna information, so the client
     411// must apply it and nothing else (see t_pppSatObs::cmpModel()).
     412////////////////////////////////////////////////////////////////////////////
     413static bool ssrSatAntennaRefIOD(const t_metaData* metaData, unsigned int& refIOD) {
     414  refIOD = 0;
     415  if (!metaData) {
     416    return true;
     417  }
     418  for (unsigned ii = 0; ii < metaData->_entries.size(); ii++) {
     419    const t_metaDataEntry& entry = metaData->_entries[ii];
     420    if ((entry._typeIndicator == 1 || entry._typeIndicator == 2) && // PCV or GDV
     421        entry._dataIODIndicator && entry._dataIOD != 0) {
     422      if (refIOD != 0 && refIOD != entry._dataIOD) {
     423        return false;
     424      }
     425      refIOD = entry._dataIOD;
     426    }
     427  }
     428  return true;
     429}
     430
     431// Whether cached Satellite Antenna data is the information referenced by
     432// Metadata (refIOD, from ssrSatAntennaRefIOD()). SatelliteAntennaIOD (DF+010)
     433// must be non-zero and equal refIOD. Metadata carries this IOD once globally,
     434// not per system (DF+010 is GNSS-specific, DF+069 is not), so the same
     435// Metadata entries are reused for every system's check - the provider is
     436// relied on to keep DF+010 synchronized with DF+069 across all systems.
     437//
     438// The IOD is also unique only per SSR Provider ID (DF414), so the Metadata
     439// must come from the same provider as the Antenna message - antenna data may
     440// be taken from the bias mountpoint while Metadata comes from the
     441// corrections mountpoint (see pppRun.cpp).
    411442//
    412443// Separately, providers only guarantee SatelliteAntennaIOD uniqueness within
    … …  
    414445// has been without a fresh Antenna message for that system for 64 days or
    415446// more must treat any cached data as unverifiable and stop using it, even
    416 // if the IOD/Metadata check above would otherwise pass - the same IOD value
    417 // could by then legitimately mean something else. epoTime is the current
    418 // processing epoch, compared against t_satAntenna::_time (when this data
    419 // was actually decoded, not a wire epoch - the message carries none).
    420 ////////////////////////////////////////////////////////////////////////////
    421 static bool ssrSatAntennaTrusted(const t_satAntenna* satAntenna, const bncTime& epoTime) {
     447// if the IOD check above would otherwise pass - the same IOD value could by
     448// then legitimately mean something else. epoTime is the current processing
     449// epoch, compared against t_satAntenna::_time (when this data was last
     450// received, to within the decoder's re-emit interval - not a wire epoch,
     451// the message carries none; see RTCM3coDecoder::sendResults()).
     452////////////////////////////////////////////////////////////////////////////
     453static bool ssrSatAntennaTrusted(const t_satAntenna* satAntenna, const t_metaData* metaData,
     454                                 unsigned int refIOD, const bncTime& epoTime) {
    422455  const double MAX_AGE_SEC = 64.0 * 86400.0;
    423   if (!satAntenna || satAntenna->_satelliteAntennaIOD == 0) {
     456  if (!satAntenna || !metaData || refIOD == 0 ||
     457      satAntenna->_satelliteAntennaIOD != refIOD ||
     458      satAntenna->_ssrProviderID != metaData->_providerID) {
    424459    return false;
    425460  }
    … …  
    428463    return false;
    429464  }
    430   const t_metaData* metaData = PPP_CLIENT->obsPool()->metaData();
    431   if (!metaData) {
    432     return false;
    433   }
    434   for (unsigned ii = 0; ii < metaData->_entries.size(); ii++) {
    435     const t_metaDataEntry& entry = metaData->_entries[ii];
    436     if ((entry._typeIndicator == 1 || entry._typeIndicator == 2) && // PCV or GDV
    437         entry._dataIODIndicator &&
    438         entry._dataIOD == satAntenna->_satelliteAntennaIOD) {
    439       return true;
    440     }
    441   }
    442   return false;
     465  return true;
    443466}
    444467
    445468// Satellite antenna correction (offset + nadir-angle-dependent PCV) from a
    446469// live SSR Satellite Antenna message, in the same additive sense as
    447 // bncAntex::satCorr(). Returns false (corr untouched) if no trusted SSR data
    448 // is available for this PRN/frequency (see ssrSatAntennaTrusted), so the
    449 // caller falls back to the static ANTEX file.
     470// bncAntex::satCorr(). Returns false (corr untouched) if the data contains
     471// no entry for this frequency.
    450472////////////////////////////////////////////////////////////////////////////
    451473static bool ssrSatAntennaCorr(const t_satAntenna* satAntenna, const string& frqStr,
    … …  
    472494      else if (idx >= (int)frq._nadirAngleCorrection.size()) {
    473495        idx = (int)frq._nadirAngleCorrection.size() - 1;
     496      }
     497      if (std::isnan(frq._nadirAngleCorrection[idx])) {
     498        return false; // invalid value (DF+021 = -2^(n-1)) at this nadir angle
    474499      }
    475500      corr += frq._nadirAngleCorrection[idx];
    … …  
    555580  // Antenna Phase Center Offsets and Variations
    556581  // -------------------------------------------
     582  // If SSR Metadata references Satellite Antenna information (non-zero IOD),
     583  // the provider's biases are relative to exactly that information, so it
     584  // is the only satellite antenna model applied - never mixed with ANTEX:
     585  // phaseranges get the SSR correction if the Phase Center indicator
     586  // (DF+011) is set, otherwise zero; pseudoranges likewise with the Group
     587  // Delay indicator (DF+012). Zero values are part of that information
     588  // (explicitly encoded, or DF+019/DF+013 not set); omitting a satellite or
     589  // frequency within a constellation is not a valid way to signal zero. So
     590  // if the referenced information is not (yet) available for this satellite,
     591  // or lacks a frequency the PPP linear combinations need, the satellite is
     592  // excluded rather than corrected with ANTEX. Without such a reference,
     593  // the static ANTEX file is used for both observables.
    557594  if (PPP_CLIENT->antex()) {
     595    const t_metaData*   metaData   = PPP_CLIENT->obsPool()->metaData();
    558596    const t_satAntenna* satAntenna = PPP_CLIENT->obsPool()->satAntenna(_prn);
    559     if (!ssrSatAntennaTrusted(satAntenna, _time)) {
    560       satAntenna = 0; // untrusted (IOD zero, or not confirmed via Metadata) - fall back to ANTEX
    561     }
     597    unsigned int refIOD;
     598    if (!ssrSatAntennaRefIOD(metaData, refIOD)) {
     599      LOG << "SSR Metadata references different Satellite Antenna IODs (PCV/GDV) - "
     600          << _prn.toString() << " excluded" << endl;
     601      return failure;
     602    }
     603    bool useSsr = (refIOD != 0);
     604    if (useSsr && !ssrSatAntennaTrusted(satAntenna, metaData, refIOD, _time)) {
     605      LOG << "SSR Satellite Antenna IOD " << refIOD << " referenced by Metadata not available for "
     606          << _prn.toString() << " - satellite excluded" << endl;
     607      return failure;
     608    }
     609
     610    bool phaseMissing[t_frequency::max] = {};
     611    bool codeMissing[t_frequency::max]  = {};
     612
    562613    for (unsigned ii = 0; ii < t_frequency::max; ii++) {
    563614      t_frequency::type frqType = static_cast<t_frequency::type>(ii);
    … …  
    568619      _model._antPCO[ii] = PPP_CLIENT->antex()->rcvCorr(station->antName(), frqType, _model._eleSat, _model._azSat, found);
    569620
    570       // Prefer a live, trusted SSR Satellite Antenna correction over the
    571       // static ANTEX file when available for this satellite/frequency,
    572       // mirroring how SSR orbit/clock corrections already take precedence
    573       // over broadcast ephemerides elsewhere in this codebase. Unlike
    574       // ANTEX's satCorr() (a single geometric PCO/PCV value shared by code
    575       // and phase alike), the SSR message's DF+011/DF+012 indicators say
    576       // explicitly which observable(s) the correction applies to, so it is
    577       // routed into the dedicated phase-only/code-only fields instead of
    578       // the shared _antPCO - both may fire for the same ssrCorr value if
    579       // both indicators are set.
    580       double ssrCorr;
    581       if (ssrSatAntennaCorr(satAntenna, frqStr, _model._elTx, ssrCorr)) {
    582         if (satAntenna->_phaseCenterInfoInd) {
    583           _model._antPCV[ii] += ssrCorr;
    584         }
    585         if (satAntenna->_groupDelayInfoInd) {
    586           _model._antGDV[ii] += ssrCorr;
     621      if (useSsr) {
     622        // Unlike ANTEX's satCorr() (a single geometric PCO/PCV value shared
     623        // by code and phase alike), DF+011/DF+012 say explicitly which
     624        // observable(s) the correction applies to, so it is routed into the
     625        // dedicated phase-only/code-only fields instead of the shared _antPCO.
     626        // BDS 1207.14 MHz: B2 (BDS-2, "C7") or B2b (BDS-3, "C7b") - a
     627        // satellite transmits only one of them. The entry of the satellite's
     628        // own generation is preferred, taken from the ANTEX block type valid
     629        // at this epoch (PRNs have been reassigned from BDS-2 to BDS-3).
     630        // Since antenna corrections depend on the carrier, not the signal,
     631        // the other entry is used as fallback if the own one is missing; if
     632        // the generation is unknown, an entry is used only if unambiguous
     633        // (just one present, or both equal).
     634        // PRELIMINARY - pending clarification by RTCM SC-104 whether B2 and
     635        // B2b entries of one satellite are meant to be identical; to revert
     636        // to the strict selection, drop the fallback branches below.
     637        double ssrCorr = 0.0;
     638        bool   ssrOK   = false;
     639        if (frqStr == "C7") {
     640          double corrB2 = 0.0, corrB2b = 0.0;
     641          bool   hasB2  = ssrSatAntennaCorr(satAntenna, "C7",  _model._elTx, corrB2);
     642          bool   hasB2b = ssrSatAntennaCorr(satAntenna, "C7b", _model._elTx, corrB2b);
     643          QString blockType = PPP_CLIENT->antex()->satBlockType(prn, _time);
     644          if      (blockType.startsWith("3")) {       // BDS-3: B2b, fallback B2
     645            ssrOK = hasB2b || hasB2;
     646            ssrCorr = hasB2b ? corrB2b : corrB2;
     647          }
     648          else if (blockType.startsWith("2")) {       // BDS-2: B2, fallback B2b
     649            ssrOK = hasB2 || hasB2b;
     650            ssrCorr = hasB2 ? corrB2 : corrB2b;
     651          }
     652          else if (hasB2 != hasB2b || (hasB2 && corrB2 == corrB2b)) { // generation unknown
     653            ssrOK = true;
     654            ssrCorr = hasB2 ? corrB2 : corrB2b;
     655          }
     656        }
     657        else {
     658          ssrOK = ssrSatAntennaCorr(satAntenna, frqStr, _model._elTx, ssrCorr);
     659        }
     660        if (ssrOK) {
     661          if (satAntenna->_phaseCenterInfoInd) {
     662            _model._antPCV[ii] += ssrCorr;
     663          }
     664          if (satAntenna->_groupDelayInfoInd) {
     665            _model._antGDV[ii] += ssrCorr;
     666          }
     667        }
     668        else {
     669          phaseMissing[ii] = satAntenna->_phaseCenterInfoInd;
     670          codeMissing[ii]  = satAntenna->_groupDelayInfoInd;
    587671        }
    588672      }
    … …  
    604688            _model._antPCO[ii] -= PPP_CLIENT->antex()->satCorr(prn, t_frequency::C2, _model._elTx, _model._azTx, found);
    605689          }
     690        }
     691      }
     692    }
     693
     694    // If the SSR antenna information applies to both observables (both
     695    // DF+011 and DF+012 set), the code and phase biases must come from the
     696    // same SSR solution (same SSR IOD and Provider ID), otherwise they are
     697    // not consistent with each other with respect to it. Only checked if
     698    // phase biases are used and both IODs are known.
     699    if (useSsr && satAntenna->_phaseCenterInfoInd && satAntenna->_groupDelayInfoInd &&
     700        OPT->arSystem(_prn.system())) {
     701      const t_satCodeBias*  satCodeBias  = PPP_CLIENT->obsPool()->satCodeBias(_prn);
     702      const t_satPhaseBias* satPhaseBias = PPP_CLIENT->obsPool()->satPhaseBias(_prn);
     703      if (satCodeBias && satPhaseBias &&
     704          satCodeBias->_ssrIOD >= 0 && satPhaseBias->_ssrIOD >= 0 &&
     705          (satCodeBias->_ssrIOD        != satPhaseBias->_ssrIOD ||
     706           satCodeBias->_ssrProviderID != satPhaseBias->_ssrProviderID)) {
     707        LOG << "SSR code bias (IOD " << satCodeBias->_ssrIOD << ", provider " << satCodeBias->_ssrProviderID
     708            << ") and phase bias (IOD " << satPhaseBias->_ssrIOD << ", provider " << satPhaseBias->_ssrProviderID
     709            << ") inconsistent for " << _prn.toString() << " with shared SSR Satellite Antenna information"
     710            << " - satellite excluded" << endl;
     711        return failure;
     712      }
     713    }
     714
     715    // Exclude the satellite if the referenced antenna information omits a
     716    // frequency actually used by the linear combinations of this system
     717    const vector<t_lc>& LCs = OPT->LCs(_prn.system());
     718    for (unsigned iLC = 0; iLC < LCs.size(); iLC++) {
     719      map<t_frequency::type, double> codeCoeff;
     720      map<t_frequency::type, double> phaseCoeff;
     721      map<t_frequency::type, double> ionoCoeff;
     722      lcCoeff(LCs[iLC], codeCoeff, phaseCoeff, ionoCoeff);
     723      map<t_frequency::type, double>::const_iterator it;
     724      for (it = phaseCoeff.begin(); it != phaseCoeff.end(); it++) {
     725        if (phaseMissing[it->first]) {
     726          LOG << "SSR Satellite Antenna IOD " << refIOD << " has no valid entry for "
     727              << _prn.toString() << ' ' << t_frequency::toString(it->first) << " (phase) - satellite excluded" << endl;
     728          return failure;
     729        }
     730      }
     731      for (it = codeCoeff.begin(); it != codeCoeff.end(); it++) {
     732        if (codeMissing[it->first]) {
     733          LOG << "SSR Satellite Antenna IOD " << refIOD << " has no valid entry for "
     734              << _prn.toString() << ' ' << t_frequency::toString(it->first) << " (code) - satellite excluded" << endl;
     735          return failure;
    606736        }
    607737      }
Note: See TracChangeset for help on using the changeset viewer.