Changeset 11047 in ntrip


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

updates regarding RTCM-SSR and some ovarall improvements

Location:
trunk/BNC/src
Files:
19 edited

Legend:

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

    r10953 r11047  
    2020#include "pppInclude.h"
    2121#include "pppClient.h"
     22#include "bncephuser.h"
    2223
    2324using namespace BNC_PPP;
    … …  
    2627//
    2728/////////////////////////////////////////////////////////////////////////////
     29// Bad, outdated and unhealthy ephemerides are never used. Ephemerides from
     30// RINEX navigation files (post-processing) never pass bncEphUser's checks and
     31// stay "unchecked", so the health flag and the plausibility of the satellite
     32// distance are checked here directly as well.
     33/////////////////////////////////////////////////////////////////////////////
    2834void t_pppEphPool::putEphemeris(t_eph* eph) {
    29   if (eph && (eph->checkState() != t_eph::bad ||
    30               eph->checkState() != t_eph::unhealthy ||
    31               eph->checkState() != t_eph::outdated))  {
    32     _satEphPool[eph->prn().toInt()].putEphemeris(_maxQueueSize, eph);
     35  if (!eph) {
     36    return;
     37  }
     38  int iPrn = eph->prn().toInt();
     39  if (iPrn == 0) { // satellite number out of range
     40    delete eph;
     41  }
     42  else if (eph->checkState() == t_eph::unhealthy || eph->isUnhealthy()) {
     43    _satEphPool[iPrn].setUnhealthy(eph);
     44  }
     45  else if (eph->checkState() == t_eph::unchecked && !bncEphUser::radialDistanceOk(eph)) {
     46    LOG << "bad ephemeris " << eph->prn().toString() << ' ' << string(eph->TOC())
     47        << " - implausible satellite distance, ignored" << endl;
     48    delete eph;
     49  }
     50  else if (eph->checkState() != t_eph::bad &&
     51           eph->checkState() != t_eph::outdated) {
     52    _satEphPool[iPrn].putEphemeris(_maxQueueSize, eph);
    3353  }
    3454  else {
    … …  
    87107    delete eph;
    88108  }
     109}
     110
     111// A newer ephemeris declaring the satellite unhealthy: rejecting it alone
     112// would leave the older, healthy ephemerides in the pool, and the satellite
     113// would still be used with them - so they are all discarded, and the
     114// satellite stays unused until a newer healthy ephemeris arrives.
     115/////////////////////////////////////////////////////////////////////////////
     116void t_pppEphPool::t_satEphPool::setUnhealthy(t_eph* eph) {
     117  if (_ephs.empty() || eph->isNewerThan(_ephs.front())) {
     118    if (!_ephs.empty()) {
     119      LOG << "unhealthy ephemeris " << eph->prn().toString() << ' ' << string(eph->TOC())
     120          << " - older ephemerides of this satellite discarded" << endl;
     121    }
     122    for (unsigned ii = 0; ii < _ephs.size(); ii++) {
     123      delete _ephs[ii];
     124    }
     125    _ephs.clear();
     126  }
     127  delete eph;
    89128}
    90129
  • trunk/BNC/src/PPP/pppEphPool.h

    r8401 r11047  
    4141    }
    4242    void putEphemeris(unsigned maxQueueSize, t_eph* eph);
     43    void setUnhealthy(t_eph* eph);
    4344    void putOrbCorrection(t_orbCorr* corr);
    4445    void putClkCorrection(t_clkCorr* corr);
  • trunk/BNC/src/PPP/pppObsPool.cpp

    r11041 r11047  
    8282void t_pppObsPool::putCodeBias(t_satCodeBias* satCodeBias) {
    8383  int iPrn = satCodeBias->_prn.toInt();
     84  if (iPrn == 0) { // satellite number out of range
     85    delete satCodeBias;
     86    return;
     87  }
    8488  delete _satCodeBiases[iPrn];
    8589  _satCodeBiases[iPrn] = satCodeBias;
    … …  
    97101void t_pppObsPool::putPhaseBias(t_satPhaseBias* satPhaseBias) {
    98102  int iPrn = satPhaseBias->_prn.toInt();
     103  if (iPrn == 0) { // satellite number out of range
     104    delete satPhaseBias;
     105    return;
     106  }
    99107  delete _satPhaseBiases[iPrn];
    100108  _satPhaseBiases[iPrn] = satPhaseBias;
    … …  
    122130void t_pppObsPool::putSatAntenna(t_satAntenna* satAntenna) {
    123131  int iPrn = satAntenna->_prn.toInt();
     132  if (iPrn == 0) { // satellite number out of range
     133    delete satAntenna;
     134    return;
     135  }
    124136  delete _satAntennas[iPrn];
    125137  _satAntennas[iPrn] = satAntenna;
  • 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      }
  • trunk/BNC/src/RTCM3/RTCM3Decoder.cpp

    r11043 r11047  
    141141    flag = t_corrSSR::getSsrNavTypeFlag(sys, num);
    142142    CurrentObs._prn.set(sys, num, flag);
     143    checkPrnRange(CurrentObs._prn);
    143144
    144145    t_frqObs *frqObs = new t_frqObs;
    … …  
    723724          else
    724725            CurrentObs._prn.set(sys, RTCM3_MSM_NUMSAT - i);
     726          checkPrnRange(CurrentObs._prn);
    725727          j = RTCM3_MSM_NUMSIG;
    726728          while (!(sigmask & (1 << --j)))
    … …  
    954956    int flag = t_corrSSR::getSsrNavTypeFlag(sys, sv);
    955957    CurrentObs._prn.set(sys, sv, flag);
     958    checkPrnRange(CurrentObs._prn);
    956959    GETBITS(code, 1)
    957960    GETBITS(freq, 5)
    958     GLOFreq[sv - 1] = 100 + freq - 7; /* store frequency for other users (MSM) */
     961    if (sv >= 1) { /* 6-bit slot 1..63 fits GLOFreq[64]; 0 is invalid */
     962      GLOFreq[sv - 1] = 100 + freq - 7; /* store frequency for other users (MSM) */
     963    }
    959964
    960965    t_frqObs *frqObs = new t_frqObs;
    … …  
    10661071    }
    10671072    eph._prn.set('G', i);
     1073    checkPrnRange(eph._prn);
    10681074    GETBITS(week, 10)
    10691075    if (week < 0 || week > 1023) {
    … …  
    11851191    }
    11861192    eph._prn.set('R', sv);
     1193    checkPrnRange(eph._prn);
    11871194
    11881195    GETBITS(i, 5)
    … …  
    13731380    }
    13741381    eph._prn.set('J', i);
     1382    checkPrnRange(eph._prn);
    13751383
    13761384    GETBITS(i, 16)
    … …  
    14931501    }
    14941502    eph._prn.set('I', i);
     1503    checkPrnRange(eph._prn);
    14951504    GETBITS(week, 10)
    14961505    if (week < 0 || week > 1023) {
    … …  
    16271636    }
    16281637    eph._prn.set('S', 20 + i);
     1638    checkPrnRange(eph._prn);
    16291639    GETBITS(eph._IODN, 8)
    16301640    GETBITS(i, 13)
    … …  
    17161726    }
    17171727    eph._prn.set('E', i, eph._inav ? t_eph::INAV : t_eph::FNAV);
     1728    checkPrnRange(eph._prn);
    17181729
    17191730    GETBITS(week, 12) //FIXME: roll-over after week 4095!!
    … …  
    18631874    }
    18641875    eph._prn.set('C', i);
     1876    checkPrnRange(eph._prn);
    18651877
    18661878    GETBITS(week, 13)
    … …  
    24392451}
    24402452
    2441 
    2442 
    2443 
     2453// Satellite numbers on the wire can exceed BNC's supported range (e.g. 6-bit
     2454// IDs up to 63, while t_prn supports G01-G32). They are passed on unchanged,
     2455// but t_prn::toInt() maps them to the unused index 0, so they are not usable
     2456// for per-satellite processing - report that once per satellite.
     2457////////////////////////////////////////////////////////////////////////////
     2458void RTCM3Decoder::checkPrnRange(const t_prn& prn) {
     2459  if (prn.toInt() != 0) {
     2460    return;
     2461  }
     2462  QString prnStr = QString::fromStdString(prn.toString());
     2463  if (!_prnOutOfRangeLogged.contains(prnStr)) {
     2464    _prnOutOfRangeLogged.insert(prnStr);
     2465    emit newMessage(QString("%1: satellite %2 outside the range supported by BNC - not usable for processing")
     2466                    .arg(_staID).arg(prnStr).toLatin1(), true);
     2467  }
     2468}
  • trunk/BNC/src/RTCM3/RTCM3Decoder.h

    r11024 r11047  
    6060
    6161 private:
     62  // Log (once per satellite) a satellite number outside BNC's supported
     63  // range (t_prn::MAXPRN_*); such satellites map to the unused index 0.
     64  void checkPrnRange(const t_prn& prn);
     65  QSet<QString>                         _prnOutOfRangeLogged;
    6266  /**
    6367   * Extract a RTCM3 message. Data is passed in the follow fields:<br>
  • trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp

    r11041 r11047  
    105105
    106106  for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
    107     _antennaSentIOD[s] = UINT_MAX; // "never sent"
     107    _antennaReceived[s]    = false;
     108    _antennaIODKnown[s]    = false;
     109    _antennaIODProvider[s] = 0;
     110    _antennaIODCur[s]      = 0;
     111    _antennaIODPrev[s]     = 0;
     112  }
     113  for (unsigned i = 0; i < CLOCKORBIT_COUNTSAT; i++) {
     114    _antennaSentProviderID[i] = 0;
     115    _antennaSentIOD[i]        = 0; // meaningless while _antennaSentTime[i] is invalid ("never sent")
    108116  }
    109117
    … …  
    203211    memcpy(&_stateSnapshot->antenna,   &_antenna,   sizeof(_antenna));
    204212
     213    // _antenna persists across messages, so its content alone can't tell
     214    // whether a Satellite Antenna message arrived in this frame (an unchanged
     215    // retransmission looks identical). Clear messageType[] as a marker: the
     216    // COBOFS_SATANT decoder sets it (always non-zero) for each system it
     217    // decodes. Restored with the snapshot if the frame turns out incomplete.
     218    for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
     219      _antenna.messageType[s] = 0;
     220    }
     221
    205222    int bytesused = 0;
    206223
    … …  
    240257
    241258      if (irc == GCOBR_OK || irc == GCOBR_MESSAGEFOLLOWS ) {
     259        // Latch the reception until sendResults() consumes it - it may not
     260        // run for this frame if _lastTime is still invalid.
     261        for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
     262          if (_antenna.messageType[s] != 0) {
     263            _antennaReceived[s] = true;
     264            checkAntennaIOD(s);
     265          }
     266        }
    242267        setEpochTime(); // sets _lastTime
    243268
    … …  
    342367      t_orbCorr orbCorr;
    343368      orbCorr._prn.set(sys, num, flag);
     369      checkPrnRange(orbCorr._prn);
    344370      orbCorr._staID     = _staID.toStdString();
    345371      orbCorr._iod       = _clkOrb.Sat[ii].IOD;
    … …  
    378404      t_clkCorr clkCorr;
    379405      clkCorr._prn.set(sys, _clkOrb.Sat[ii].ID, flag);
     406      checkPrnRange(clkCorr._prn);
    380407      clkCorr._staID      = _staID.toStdString();
    381408      clkCorr._time       = _lastTime;
    … …  
    470497    t_satCodeBias satCodeBias;
    471498    satCodeBias._prn.set(sys, num, flag);
     499    checkPrnRange(satCodeBias._prn);
    472500    satCodeBias._staID     = _staID.toStdString();
    473501    satCodeBias._time      = _lastTime;
    474502    satCodeBias._updateInt = _codeBias.UpdateInterval;
     503    satCodeBias._ssrIOD        = _codeBias.SSRIOD;
     504    satCodeBias._ssrProviderID = _codeBias.SSRProviderID;
    475505    for (unsigned jj = 0; jj < _codeBias.Sat[ii].NumberOfCodeBiases; jj++) {
    476506      const SsrCorr::CodeBias::BiasSat::CodeBiasEntry& biasEntry = _codeBias.Sat[ii].Biases[jj];
    … …  
    560590    t_satPhaseBias satPhaseBias;
    561591    satPhaseBias._prn.set(sys, num, flag);
     592    checkPrnRange(satPhaseBias._prn);
    562593    satPhaseBias._staID      = _staID.toStdString();
    563594    satPhaseBias._time       = _lastTime;
    564595    satPhaseBias._updateInt  = _phaseBias.UpdateInterval;
     596    satPhaseBias._ssrIOD        = _phaseBias.SSRIOD;
     597    satPhaseBias._ssrProviderID = _phaseBias.SSRProviderID;
    565598    satPhaseBias._ssrFormat  = (_type == RTCMssr) ? ssrRtcmOld :
    566599                               (_type == RTCMnewssr) ? ssrRtcmNew : ssrUnknown;
    … …  
    649682  // _antenna, like _metaData, carries no epoch field of its own and is never
    650683  // wiped by reset(), so each system's data persists across unrelated
    651   // messages. _antennaSentIOD tracks the last SatelliteAntennaIOD actually
    652   // emitted per system, so unrelated messages (and unchanged retransmissions
    653   // of the same antenna data) don't cause duplicate output - the same
    654   // freshness problem _phaseBiasSentEpoch solves for phase biases.
     684  // messages. Only a system for which an Antenna message was actually
     685  // received (_antennaReceived, latched in Decode()) is considered, so
     686  // persisted data is never re-stamped with a newer time.
     687  //
     688  // The SatelliteAntennaIOD is unique only per SSR Provider ID (DF414) and
     689  // only within 64 days, and a client that has not received the stream for
     690  // 64 days must discard cached antenna data (enforced downstream against
     691  // t_satAntenna::_time, see ssrSatAntennaTrusted() in pppSatObs.cpp). So an
     692  // unchanged (provider ID, IOD) retransmission is suppressed only if it was
     693  // last emitted less than ANT_REFRESH_SEC ago. Re-emitting it after that
     694  // keeps _time close to the last reception - not the first - so continuously
     695  // received data never ages out. It also means a reception after a long
     696  // gap is always emitted, even when the provider has legitimately reused
     697  // the IOD for different content, and each .ssr file contains the antenna
     698  // data at least once per refresh interval.
     699  //
     700  // This is tracked per satellite, not per system: providers may split a
     701  // system's satellites across several messages with rotating satellite
     702  // masks (DF394), all carrying the same IOD - a per-system check would
     703  // emit only whichever subset happened to arrive first.
    655704  {
     705    const double ANT_REFRESH_SEC = 3600.0;
    656706    struct SysInfo { unsigned sysIdx; char sysChar; unsigned numSat; unsigned offset; };
    657707    const SysInfo sysInfos[] = {
    … …  
    664714    for (const SysInfo& si : sysInfos) {
    665715      unsigned s = si.sysIdx;
    666       if (_antenna.SatelliteAntennaIOD[s] == _antennaSentIOD[s]) {
    667         continue; // nothing new for this system since the last emit
    668       }
     716      if (!_antennaReceived[s]) {
     717        continue; // no Antenna message received for this system since the last check
     718      }
     719      _antennaReceived[s] = false;
    669720      if (_antenna.SatelliteMask[s] == 0) {
    670         continue; // this system's antenna data was never decoded
     721        continue; // no satellites in this system's antenna data
    671722      }
    672723      for (unsigned k = 0; k < si.numSat; k++) {
    … …  
    674725          continue;
    675726        }
     727        unsigned i = si.offset + k;
     728        if (_antennaSentTime[i].valid() &&
     729            _antenna.SSRProviderID[s]       == _antennaSentProviderID[i] &&
     730            _antenna.SatelliteAntennaIOD[s] == _antennaSentIOD[i] &&
     731            _lastTime - _antennaSentTime[i] <  ANT_REFRESH_SEC) {
     732          continue; // unchanged retransmission, emitted recently
     733        }
     734        _antennaSentProviderID[i] = _antenna.SSRProviderID[s];
     735        _antennaSentIOD[i]        = _antenna.SatelliteAntennaIOD[s];
     736        _antennaSentTime[i]       = _lastTime;
    676737        int num  = k + 1; // PRN within this system, DF394 MSB-first convention
    677738        int flag = 0;
    … …  
    696757        satAntenna._nadirAngleRangeExtension  = _antenna.NadirAngleDependentCorrectionRangeExtension[s];
    697758
    698         const SsrCorr::Antenna::SatellitePart& satPart = _antenna.Sat[si.offset + k];
     759        const SsrCorr::Antenna::SatellitePart& satPart = _antenna.Sat[i];
    699760        for (unsigned f = 0; f < ANT_MAXFREQUENCIES; f++) {
    700761          if (!((satPart.GnssFrequencyMask >> (ANT_MAXFREQUENCIES - 1 - f)) & 1U)) {
    … …  
    718779        _satAntennas[_lastTime].append(satAntenna);
    719780      }
    720       _antennaSentIOD[s] = _antenna.SatelliteAntennaIOD[s];
    721781    }
    722782  }
    … …  
    11091169  }
    11101170}
     1171
     1172// Satellite numbers on the wire can exceed BNC's supported range (e.g. 6-bit
     1173// IDs up to 63, while t_prn supports G01-G32). They are passed on unchanged,
     1174// but t_prn::toInt() maps them to the unused index 0, so they are not usable
     1175// for per-satellite processing - report that once per satellite.
     1176////////////////////////////////////////////////////////////////////////////
     1177void RTCM3coDecoder::checkPrnRange(const t_prn& prn) {
     1178  if (prn.toInt() != 0) {
     1179    return;
     1180  }
     1181  QString prnStr = QString::fromStdString(prn.toString());
     1182  if (!_prnOutOfRangeLogged.contains(prnStr)) {
     1183    _prnOutOfRangeLogged.insert(prnStr);
     1184    emit newMessage(QString("%1: satellite %2 outside the range supported by BNC - not usable for processing")
     1185                    .arg(_staID).arg(prnStr).toLatin1(), true);
     1186  }
     1187}
     1188
     1189// All Satellite Antenna messages of one GNSS must use the same Satellite
     1190// Antenna IOD (a satellite set may be split over several messages). During
     1191// a legitimate IOD change old and new IOD are both seen for a while, until
     1192// every message has been sent with the new one - but the IOD never returns
     1193// to a replaced value within 64 days (uniqueness per SSR Provider ID), so a
     1194// switch back to the replaced IOD within that period reveals messages of the
     1195// same GNSS using different IODs. Reported once per GNSS and IOD pair; the
     1196// data itself is not changed (PPP checks each satellite's IOD individually).
     1197////////////////////////////////////////////////////////////////////////////
     1198void RTCM3coDecoder::checkAntennaIOD(unsigned s) {
     1199  const double MAX_AGE_SEC = 64.0 * 86400.0;
     1200  unsigned int iod      = _antenna.SatelliteAntennaIOD[s];
     1201  unsigned int provider = _antenna.SSRProviderID[s];
     1202
     1203  int    currentWeek = 0;
     1204  double currentSec  = 0.0;
     1205  currentGPSWeeks(currentWeek, currentSec);
     1206  bncTime currentTime(currentWeek, currentSec);
     1207
     1208  if (!_antennaIODKnown[s] || provider != _antennaIODProvider[s]) {
     1209    _antennaIODKnown[s]    = true;
     1210    _antennaIODProvider[s] = provider;
     1211    _antennaIODCur[s]      = iod;
     1212    _antennaIODPrevTime[s].reset(); // no replaced IOD yet
     1213    return;
     1214  }
     1215  if (iod == _antennaIODCur[s]) {
     1216    return;
     1217  }
     1218  if (_antennaIODPrevTime[s].valid() && iod == _antennaIODPrev[s] &&
     1219      currentTime - _antennaIODPrevTime[s] < MAX_AGE_SEC) {
     1220    const char sysChars[CLOCKORBIT_SATNUM] = {'G', 'R', 'E', 'J', 'S', 'C'};
     1221    char sys = (s < CLOCKORBIT_SATNUM) ? sysChars[s] : '?';
     1222    QString key = QString("%1_%2_%3").arg(sys).arg(_antennaIODCur[s]).arg(iod);
     1223    if (!_antennaIODLogged.contains(key)) {
     1224      _antennaIODLogged.insert(key);
     1225      emit newMessage(QString("%1: GNSS %2 Satellite Antenna IOD switches back %3 -> %4"
     1226                              " - messages of one GNSS must use the same IOD")
     1227                      .arg(_staID).arg(sys).arg(_antennaIODCur[s]).arg(iod).toLatin1(), true);
     1228    }
     1229  }
     1230  _antennaIODPrev[s]     = _antennaIODCur[s];
     1231  _antennaIODPrevTime[s] = currentTime;
     1232  _antennaIODCur[s]      = iod;
     1233}
  • trunk/BNC/src/RTCM3/RTCM3coDecoder.h

    r11038 r11047  
    3030#include <QList>
    3131#include <QMap>
     32#include <QSet>
    3233#include "GPSDecoder.h"
    3334#include "../RTCM3/clock_and_orbit/clock_orbit_igs.h"
    … …  
    7576  void checkProviderID();
    7677  bool corrIsOutOfRange(const SsrCorr::ClockOrbit::SatData& coSat);
     78  // Log (once per satellite) a satellite number outside BNC's supported
     79  // range (t_prn::MAXPRN_*); such satellites map to the unused index 0.
     80  void checkPrnRange(const t_prn& prn);
     81  QSet<QString>                         _prnOutOfRangeLogged;
     82
    7783
    7884  struct t_stateSnapshot {
    … …  
    111117  QMap<bncTime, QList<t_satAntenna> >   _satAntennas;
    112118  unsigned int                          _phaseBiasSentEpoch[CLOCKORBIT_SATNUM];
    113   unsigned int                          _antennaSentIOD[CLOCKORBIT_SATNUM];
     119  bool                                  _antennaReceived[CLOCKORBIT_SATNUM];
     120  unsigned int                          _antennaSentProviderID[CLOCKORBIT_COUNTSAT];
     121  unsigned int                          _antennaSentIOD[CLOCKORBIT_COUNTSAT];
     122  bncTime                               _antennaSentTime[CLOCKORBIT_COUNTSAT];
     123  // per GNSS: Satellite Antenna IOD currently in use and the one it replaced,
     124  // to detect messages of one GNSS using different IODs (see checkAntennaIOD())
     125  void checkAntennaIOD(unsigned s);
     126  bool                                  _antennaIODKnown[CLOCKORBIT_SATNUM];
     127  unsigned int                          _antennaIODProvider[CLOCKORBIT_SATNUM];
     128  unsigned int                          _antennaIODCur[CLOCKORBIT_SATNUM];
     129  unsigned int                          _antennaIODPrev[CLOCKORBIT_SATNUM];
     130  bncTime                               _antennaIODPrevTime[CLOCKORBIT_SATNUM]; // when _antennaIODPrev was last seen
     131  QSet<QString>                         _antennaIODLogged;
    114132};
    115133
  • trunk/BNC/src/bncantex.cpp

    r11021 r11047  
    169169    else if (line.indexOf("END OF ANTENNA") == 60) {
    170170      if (newAntMap) {
     171        if (!newAntMap->blockType.isEmpty()) { // satellite antenna
     172          t_satBlock satBlock;
     173          satBlock.validFrom = newAntMap->validFrom;
     174          satBlock.validTo   = newAntMap->validTo;
     175          satBlock.blockType = newAntMap->blockType;
     176          _satBlocks[newAntMap->antName].append(satBlock);
     177        }
    171178        if (_maps.contains(newAntMap->antName)) {
    172179          delete _maps[newAntMap->antName];
    … …  
    213220        else {
    214221          newAntMap->antName = line.mid(0,20);
     222        }
     223      }
     224      else if (line.indexOf("VALID FROM") == 60 || line.indexOf("VALID UNTIL") == 60) {
     225        QTextStream inLine(&line, QIODevice::ReadOnly);
     226        int year, month, day, hour, min;
     227        double sec;
     228        inLine >> year >> month >> day >> hour >> min >> sec;
     229        bncTime tt;
     230        tt.set(year, month, day, hour, min, sec);
     231        if (line.indexOf("VALID FROM") == 60) {
     232          newAntMap->validFrom = tt;
     233        }
     234        else {
     235          newAntMap->validTo = tt;
    215236        }
    216237      }
    … …  
    942963
    943964}
     965
     966// Satellite block type valid at the given epoch
     967////////////////////////////////////////////////////////////////////////////
     968QString bncAntex::satBlockType(const QString& prn, const bncTime& epoch) const {
     969  QMap<QString, QVector<t_satBlock> >::const_iterator it = _satBlocks.find(prn.mid(0,3));
     970  if (it == _satBlocks.end() || it.value().isEmpty()) {
     971    return QString();
     972  }
     973  const QVector<t_satBlock>& blocks = it.value();
     974  if (!epoch.valid()) {
     975    return blocks.last().blockType;
     976  }
     977  for (int ii = 0; ii < blocks.size(); ii++) {
     978    const t_satBlock& bb = blocks[ii];
     979    if ((!bb.validFrom.valid() || !(epoch < bb.validFrom)) &&
     980        (!bb.validTo.valid()   || epoch < bb.validTo)) {
     981      return bb.blockType;
     982    }
     983  }
     984  return QString();
     985}
  • trunk/BNC/src/bncantex.h

    r11021 r11047  
    4646  double  rcvCorr(const std::string& antName, t_frequency::type frqType,
    4747                  double eleSat, double azSat, bool& found) const;
     48  // Satellite block type (e.g. "IIF", "2I", "3M-CAST") valid for the PRN at
     49  // the given epoch, taking PRN reassignments into account (VALID FROM/UNTIL);
     50  // empty if unknown
     51  QString satBlockType(const QString& prn, const bncTime& epoch) const;
    4852
    4953  // Attitude model selection for satCoMcorrection().
    … …  
    194198
    195199  QMap<QString, t_antMap*> _maps;
     200
     201  // Block type history per satellite PRN. _maps keeps only the last ANTEX
     202  // entry per PRN, which is wrong for epochs before a PRN was reassigned.
     203  struct t_satBlock {
     204    bncTime validFrom;
     205    bncTime validTo;   // invalid: still valid
     206    QString blockType;
     207  };
     208  QMap<QString, QVector<t_satBlock> > _satBlocks;
    196209};
    197210
  • trunk/BNC/src/bncephuser.cpp

    r11021 r11047  
    185185//
    186186////////////////////////////////////////////////////////////////////////////
     187bool bncEphUser::radialDistanceOk(const t_eph* eph) {
     188  ColumnVector xc(6);
     189  ColumnVector vv(3);
     190  if (eph->getCrd(eph->TOC(), xc, vv, false) != success) {
     191    return false;
     192  }
     193  double rr = xc.Rows(1,3).NormFrobenius();
     194
     195  const double MINDIST = 2.e7;
     196  const double MAXDIST = 6.e7;
     197  if (rr < MINDIST || rr > MAXDIST || std::isnan(rr)) {
     198    return false;
     199  }
     200  return true;
     201}
     202
     203//
     204////////////////////////////////////////////////////////////////////////////
    187205void bncEphUser::checkEphemeris(t_eph* eph, bool realTime) {
    188206
    … …  
    211229  // Simple Check - check satellite radial distance
    212230  // ----------------------------------------------
     231  if (!radialDistanceOk(eph)) {
     232    eph->setCheckState(t_eph::bad);
     233    return;
     234  }
    213235  ColumnVector xc(6);
    214236  ColumnVector vv(3);
    215   if (eph->getCrd(eph->TOC(), xc, vv, false) != success) {
    216     eph->setCheckState(t_eph::bad);
    217     return;
    218   }
    219   double rr = xc.Rows(1,3).NormFrobenius();
    220 
    221   const double MINDIST = 2.e7;
    222   const double MAXDIST = 6.e7;
    223   if (rr < MINDIST || rr > MAXDIST || std::isnan(rr)) {
    224     eph->setCheckState(t_eph::bad);
    225     return;
    226   }
     237  eph->getCrd(eph->TOC(), xc, vv, false);
    227238
    228239  // Check consistency with older ephemeris
  • trunk/BNC/src/bncephuser.h

    r11021 r11047  
    5151  t_irc putNewEph(t_eph* newEph, bool check);
    5252
     53  // Plausibility check: satellite position computable at TOC and its
     54  // distance from the geocenter within 20000-60000 km
     55  static bool radialDistanceOk(const t_eph* eph);
     56
    5357  t_eph* ephLast(const QString& prn) {
    5458    if (_eph.contains(prn)) {
  • trunk/BNC/src/bncrinex.cpp

    r11021 r11047  
    390390    QString monNum = "0";
    391391    QString recNum = "0";
    392     if (statIDlength == 9) {
     392    if (statIDlength == 9 &&
     393        isdigit((unsigned char) _statID[4]) &&
     394        isdigit((unsigned char) _statID[5])) {
    393395      monNum = QChar(_statID[4]);
    394396      recNum = QChar(_statID[5]);
    … …  
    410412            "_S_" + // stream
    411413            QString("%1").arg(datTim.date().year()) +
    412             QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
     414            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) + 
    413415            hlpStr + // HMS_period
    414416            QString("%1").arg(_samplingRateStr) + // sampling rate
  • trunk/BNC/src/combination/bnccomb.cpp

    r10962 r11047  
    12961296      t_satCodeBias satCodeBias(corr->_satCodeBias);
    12971297      satCodeBias._staID = "INTERNAL";
     1298      satCodeBias._ssrIOD        = -1; // combined, not any single provider's solution
     1299      satCodeBias._ssrProviderID = -1;
    12981300      satCodeBiasList.push_back(satCodeBias);
    12991301      hlp = QString().asprintf(" CodeBias %2lu", satCodeBias._bias.size());
    … …  
    15921594      im.remove();
    15931595    }
     1596    // a newer ephemeris may have arrived (or an older one been re-classified)
     1597    // since the correction was accepted - never switch to an unusable one
     1598    else if (ephLast->checkState() == t_eph::bad      ||
     1599             ephLast->checkState() == t_eph::outdated ||
     1600             ephLast->checkState() == t_eph::unhealthy) {
     1601      out << "checkOrbit: last eph not ok (" << ephLast->checkStateToString() << ") " << corr->_prn.mid(0,3) << "\n";
     1602      delete corr;
     1603      im.remove();
     1604    }
    15941605    else {
    15951606      if ( corr->_eph == ephLast ||
  • trunk/BNC/src/pppRun.cpp

    r11041 r11047  
    575575  }
    576576
     577  // Taken from the same mountpoint as the Metadata (see slotNewMetaData()),
     578  // since only Metadata of the same provider can reference this antenna
     579  // information (IOD unique per SSR Provider ID only).
    577580  if (_opt->_realTime) {
    578     if (_opt->_corrMount.empty() && _opt->_biasMount.empty()) {
     581    if (_opt->_corrMount.empty() || _opt->_corrMount != satAntennas[0]._staID) {
    579582      return;
    580     }
    581     if      (!_opt->_biasMount.empty()) {
    582       if (_opt->_biasMount != satAntennas[0]._staID) {
    583         return;
    584       }
    585     }
    586     else if (!_opt->_corrMount.empty()) {
    587       if (_opt->_corrMount != satAntennas[0]._staID) {
    588         return;
    589       }
    590583    }
    591584  }
  • trunk/BNC/src/satObs.cpp

    r11041 r11047  
    22#include <iomanip>
    33#include <sstream>
     4#include <cstdlib>
    45#include <newmatio.h>
    56
    … …  
    132133
    133134  e_ssrFormat ssrFormat = ssrUnknown;
     135
     136  // TEMPORARY BACKWARD COMPATIBILITY - remove later on:
     137  // .ssr files written before the SSR format line (a single integer) was
     138  // added after the "> ORBIT" epoch line don't have it - their first line
     139  // is already the first satellite. Such a line is kept (firstLine) and
     140  // processed as the first correction below, with ssrFormat unknown.
     141  // To revert: read the format line unconditionally again, i.e. drop the
     142  // "only one integer on the line" test and the firstLine handling in the
     143  // loop below.
     144  string firstLine;
     145  getline(inStream, firstLine);
    134146  {
    135     string line;
    136     getline(inStream, line);
    137     istringstream in(line.c_str());
    138     int ssrFormatInt = 0;
    139     if (in >> ssrFormatInt) {
     147    istringstream in(firstLine.c_str());
     148    int    ssrFormatInt = 0;
     149    string rest;
     150    if ((in >> ssrFormatInt) && !(in >> rest)) { // format line
    140151      ssrFormat = static_cast<e_ssrFormat>(ssrFormatInt);
     152      firstLine.clear();
    141153    }
    142154  }
    … …  
    150162
    151163    string line;
    152     getline(inStream, line);
     164    if (ii == 0 && !firstLine.empty()) { // TEMPORARY BACKWARD COMPATIBILITY, see above
     165      line = firstLine;
     166    }
     167    else {
     168      getline(inStream, line);
     169    }
    153170    istringstream in(line.c_str());
    154171
    … …  
    245262      *out << "> CODE_BIAS " << epoTime.datestr(' ') << ' ' << epoTime.timestr(1, ' ') << " "
    246263        << satCodeBias._updateInt << " "
    247         << biasList.size() << ' ' << satCodeBias._staID << endl;
     264        << biasList.size() << ' ' << satCodeBias._staID << ' '
     265        << satCodeBias._ssrIOD << ' ' << satCodeBias._ssrProviderID << endl;
    248266    }
    249267    *out << satCodeBias._prn.toString() << "   " << setw(2) << satCodeBias._bias.size();
    … …  
    268286    return;
    269287  }
     288  int ssrIOD, ssrProviderID;
     289  t_corrSSR::readSsrIOD(epoLine, ssrIOD, ssrProviderID);
    270290  for (int ii = 0; ii < numSat; ii++) {
    271291    t_satCodeBias satCodeBias;
    … …  
    273293    satCodeBias._updateInt = updateInt;
    274294    satCodeBias._staID = staID;
     295    satCodeBias._ssrIOD = ssrIOD;
     296    satCodeBias._ssrProviderID = ssrProviderID;
    275297
    276298    string line;
    … …  
    313335      *out << "> PHASE_BIAS " << epoTime.datestr(' ') << ' ' << epoTime.timestr(1, ' ') << " "
    314336        << satPhaseBias._updateInt << " "
    315         << biasList.size() << ' ' << satPhaseBias._staID << endl;
     337        << biasList.size() << ' ' << satPhaseBias._staID << ' '
     338        << satPhaseBias._ssrIOD << ' ' << satPhaseBias._ssrProviderID << endl;
    316339
    317340      if (satPhaseBias._ssrFormat == e_ssrFormat::ssrRtcmNew) {
    … …  
    373396    return;
    374397  }
     398  int ssrIOD, ssrProviderID;
     399  t_corrSSR::readSsrIOD(epoLine, ssrIOD, ssrProviderID);
    375400  for (int ii = 0; ii <= numSat; ii++) {
    376401    t_satPhaseBias satPhaseBias;
    … …  
    378403    satPhaseBias._updateInt = updateInt;
    379404    satPhaseBias._staID = staID;
     405    satPhaseBias._ssrIOD = ssrIOD;
     406    satPhaseBias._ssrProviderID = ssrProviderID;
    380407
    381408    string line;
    … …  
    392419    switch (ssrFormat) {
    393420    case e_ssrFormat::ssrRtcmNew:
    394       satPhaseBias._dispBiasConsistInd = metadata1;
    395       satPhaseBias._MelbWuebConsistInd = metadata2;
     421      satPhaseBias._satYawInfoInd = metadata1;
     422      satPhaseBias._extPBPhaseInd = metadata2;
    396423      break;
    397424    case e_ssrFormat::ssrRtcmOld:
    398425    case e_ssrFormat::ssrUnknown:
    399       satPhaseBias._satYawInfoInd = metadata1;
    400       satPhaseBias._extPBPhaseInd = metadata2;
     426      satPhaseBias._dispBiasConsistInd = metadata1;
     427      satPhaseBias._MelbWuebConsistInd = metadata2;
    401428      break;
    402429    }
    … …  
    575602      in >> frqAntenna._frqType >> frqAntenna._nadirCorrectionIndicator >> frqAntenna._nadirCorrection;
    576603      for (unsigned kk = 0; kk < numAngles; kk++) {
    577         double val;
    578         in >> val;
    579         frqAntenna._nadirAngleCorrection.push_back(val);
     604        string valStr; // may be "nan" (invalid value), which operator>> can't parse
     605        in >> valStr;
     606        frqAntenna._nadirAngleCorrection.push_back(strtod(valStr.c_str(), 0));
    580607      }
    581608      satAntenna._freq.push_back(frqAntenna);
    … …  
    684711
    685712  return unknown;
     713}
     714
     715// Optional SSR IOD and Provider ID trailing the staID in CODE_BIAS and
     716// PHASE_BIAS epoch lines (older files don't have them: -1 = unknown)
     717////////////////////////////////////////////////////////////////////////////
     718void t_corrSSR::readSsrIOD(const string& line, int& ssrIOD, int& ssrProviderID) {
     719  istringstream inLine(line.c_str());
     720  string hlp;
     721  for (int ii = 0; ii < 11; ii++) { // '>', type, 6 x date/time, updateInt, numEntries, staID
     722    inLine >> hlp;
     723  }
     724  if (!(inLine >> ssrIOD >> ssrProviderID)) {
     725    ssrIOD        = -1;
     726    ssrProviderID = -1;
     727  }
    686728}
    687729
  • trunk/BNC/src/satObs.h

    r11041 r11047  
    166166 public:
    167167  t_satCodeBias() {
    168     _updateInt = 0;
     168    _updateInt     = 0;
     169    _ssrIOD        = -1;
     170    _ssrProviderID = -1;
    169171  }
    170172  static void writeEpoch(std::ostream* out, const QList<t_satCodeBias>& biasList);
    … …  
    174176  bncTime                    _time;
    175177  unsigned int               _updateInt;
     178  int                        _ssrIOD;        // SSR IOD (DF413), -1 if unknown; not satellite specific
     179  int                        _ssrProviderID; // SSR Provider ID (DF414), -1 if unknown; not satellite specific
    176180  std::vector<t_frqCodeBias> _bias;
    177181};
    … …  
    196200  t_satPhaseBias() {
    197201    _updateInt  = 0;
     202    _ssrIOD        = -1;
     203    _ssrProviderID = -1;
    198204    _ssrFormat  = ssrUnknown;
    199205    _dispBiasConsistInd = 0;
    … …  
    210216  bncTime                     _time;
    211217  unsigned int                _updateInt;           // not satellite specific
     218  int                         _ssrIOD;              // SSR IOD (DF413), -1 if unknown; not satellite specific
     219  int                         _ssrProviderID;       // SSR Provider ID (DF414), -1 if unknown; not satellite specific
    212220  e_ssrFormat                 _ssrFormat;           // not satellite specific
    213221  // IGS-SSR
    … …  
    307315  std::string               _staID;
    308316  t_prn                     _prn;
    309   bncTime                   _time;                       // time this message was decoded (no epoch field on the wire)
     317  bncTime                   _time;                       // time this message was last received (no epoch field on the wire)
    310318  unsigned int               _ssrProviderID;              // not satellite specific
    311319  unsigned int               _satelliteAntennaIOD;        // not satellite specific; unique per provider within a 64-day window
    … …  
    323331  static e_type readEpoLine(const std::string& line, bncTime& epoTime,
    324332                            unsigned int& updateInt, int& numEntries, std::string& staID);
     333  static void   readSsrIOD(const std::string& line, int& ssrIOD, int& ssrProviderID);
    325334  static t_eph::e_type getSsrNavTypeFlag(char sys, int num);
    326335};
  • trunk/BNC/src/t_prn.cpp

    r10791 r11047  
    5353//////////////////////////////////////////////////////////////////////////////
    5454int t_prn::toInt() const {
     55  // Every per-satellite array (size MAXPRN+1) and every map keyed by t_prn
     56  // (compared via operator unsigned) relies on this index. A number beyond
     57  // its system's range - e.g. a 6-bit SSR satellite ID up to 63 - would
     58  // otherwise silently map onto a satellite of the next system (G33 = R01)
     59  // or beyond the arrays, so it is mapped to the unused index 0 instead.
     60  unsigned maxNum = 0;
     61  switch (_system) {
     62    case 'G': maxNum = MAXPRN_GPS;     break;
     63    case 'R': maxNum = MAXPRN_GLONASS; break;
     64    case 'E': maxNum = MAXPRN_GALILEO; break;
     65    case 'J': maxNum = MAXPRN_QZSS;    break;
     66    case 'S': maxNum = MAXPRN_SBAS;    break;
     67    case 'C': maxNum = MAXPRN_BDS;     break;
     68    case 'I': maxNum = MAXPRN_NavIC;   break;
     69  }
     70  if (_number < 1 || unsigned(_number) > maxNum) {
     71    return 0;
     72  }
    5573  if      (_system == 'G') {
    5674    return _number;
  • trunk/BNC/src/t_prn.h

    r11036 r11047  
    1010  static const unsigned MAXPRN_GALILEO = 36;
    1111  static const unsigned MAXPRN_QZSS    = 10;
    12   static const unsigned MAXPRN_SBAS    = 38;
     12  static const unsigned MAXPRN_SBAS    = 58; // numbered S20-S58 (PRN 120-158), S01-S19 unused
    1313  static const unsigned MAXPRN_BDS     = 65;
    1414  static const unsigned MAXPRN_NavIC   = 20;
    … …  
    5454    return _flag;
    5555  }
    56   int toInt() const;
     56  int toInt() const; // 0 (unused index) if the number is out of range for its system
    5757  std::string toString() const;
    5858  std::string toInternalString() const;
Note: See TracChangeset for help on using the changeset viewer.