Changeset 11058 in ntrip


Ignore:
Timestamp:
Sep 30, 2026, 10:52:22 PM (12 hours ago)
Author:
stuerze
Message:

some more fixes

Location:
trunk/BNC
Files:
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/Example_Configs/10_PPPQuickStart.bnc

    r10968 r11058  
    3333miscPort=
    3434miscScanRTCM=0
    35 mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA01CAS1 RTCM_3.1 CHN 40.07 116.28 no 2
     35mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
    3636ntripVersion=2
    3737onTheFlyInterval=no
    … …  
    151151constraints=Ionosphere: pseudo-obs
    152152corrFile=
    153 corrMount=SSRA00CAS1
     153corrMount=SSRA00CNE1
    154154corrWaitTime=5
    155155crdFile=Input/BNC_IGS20.CRD
  • trunk/BNC/Example_Configs/17_Combi.bnc

    r10968 r11058  
    1717cmbNavic=0
    1818cmbQzss=0
    19 cmbSampl=1 sec
     19cmbSampl=5 sec
    2020cmbSbas=0
    2121cmbStreams="SSRA00BKG1 BKG 1.0  Computed ", "SSRA00CNE1 CNE 1.0  Computed "
  • trunk/BNC/Example_Configs/18_CombiPPP.bnc

    r10968 r11058  
    1717cmbNavic=0
    1818cmbQzss=0
    19 cmbSampl=1 sec
     19cmbSampl=5 sec
    2020cmbSbas=0
    2121cmbStreams="SSRA00BKG1 BKG 1.0  Computed ", "SSRA00CNE1 CNE 1.0  Computed "
  • trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp

    r11057 r11058  
    11321132  // Set _lastTime close to currentTime
    11331133  // ----------------------------------
     1134  if (_type == IGSssr) {
     1135    if      (epoSecGPS  != -1) _igsRefEpoSec = epoSecGPS;
     1136    else if (epoSecGal  != -1) _igsRefEpoSec = epoSecGal;
     1137    else if (epoSecQzss != -1) _igsRefEpoSec = epoSecQzss;
     1138    else if (epoSecSbas != -1) _igsRefEpoSec = epoSecSbas;
     1139  }
     1140
    11341141  if      (epoSecGPS != -1) {
    11351142    _lastTime.set(currentWeek, epoSecGPS);
    … …  
    11371144  else if (epoSecGlo != -1) {
    11381145    QDate date = dateAndTimeFromGPSweek(currentTime.gpsw(), currentTime.gpssec()).date();
     1146    double leap = gnumleap(date.year(), date.month(), date.day());
    11391147    // IGS-SSR: the SSR Epoch Time (IDF003, 20 bit) is given in one continuous
    11401148    // time scale for all GNSS, i.e. GPS seconds of week also for GLONASS
    1141     // (IGS SSR v1.00) - no conversion
     1149    // (IGS SSR v1.00) - no conversion. Old encoders (e.g. BNC before
     1150    // 2026-09-30) wrote UTC seconds of day instead: recognised by comparison
     1151    // with the GPS/Galileo epoch of the same stream and converted, with a
     1152    // warning, during a transition period.
     1153    if (_type == IGSssr && _igsRefEpoSec >= 0.0 &&
     1154        igsLegacyEpoch(epoSecGlo, fmod(_igsRefEpoSec - leap + 7 * 86400.0, 86400.0), 86400.0)) {
     1155      double sow = floor(_igsRefEpoSec / 86400.0) * 86400.0 + epoSecGlo + leap;
     1156      if      (sow - _igsRefEpoSec >  43200.0) sow -= 86400.0;
     1157      else if (_igsRefEpoSec - sow >  43200.0) sow += 86400.0;
     1158      epoSecGlo = sow;
     1159      bncTime& warned = _legacyEpochWarned['R'];
     1160      if (!warned.valid() || currentTime - warned >= 3600.0) {
     1161        warned = currentTime;
     1162        emit newMessage(_staID.toLatin1() + ": IGS-SSR GLONASS epoch time in outdated convention "
     1163                        "(UTC seconds of day instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
     1164                        "detected and converted - the SSR encoder should be updated", true);
     1165      }
     1166    }
    11421167    if (_type == RTCMssr || _type == RTCMnewssr) {
    11431168      // Same GLONASS epoch wire convention (UTC+3h) as RTCMssr
    1144       epoSecGlo = epoSecGlo - 3 * 3600 + gnumleap(date.year(), date.month(), date.day());
     1169      epoSecGlo = epoSecGlo - 3 * 3600 + leap;
    11451170    }
    11461171    _lastTime.set(currentWeek, epoSecGlo);
    … …  
    11561181  }
    11571182  else if (epoSecBds != -1) {
    1158     // RTCM-SSR: BDT; IGS-SSR: GPS time scale for all GNSS (IDF003)
    1159     if (_type != IGSssr) {
     1183    // RTCM-SSR: BDT; IGS-SSR: GPS time scale for all GNSS (IDF003). Old
     1184    // IGS-SSR encoders wrote BDT: recognised and converted as for GLONASS.
     1185    bool bdt = (_type != IGSssr);
     1186    if (_type == IGSssr && _igsRefEpoSec >= 0.0 &&
     1187        igsLegacyEpoch(epoSecBds, fmod(_igsRefEpoSec - 14.0 + 604800.0, 604800.0), 604800.0)) {
     1188      bdt = true;
     1189      bncTime& warned = _legacyEpochWarned['C'];
     1190      if (!warned.valid() || currentTime - warned >= 3600.0) {
     1191        warned = currentTime;
     1192        emit newMessage(_staID.toLatin1() + ": IGS-SSR BDS epoch time in outdated convention "
     1193                        "(BDT instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
     1194                        "detected and converted - the SSR encoder should be updated", true);
     1195      }
     1196    }
     1197    if (bdt) {
    11601198      epoSecBds += 14.0;
    11611199      if (epoSecBds > 604800.0) {
    … …  
    11821220    }
    11831221  }
     1222}
     1223
     1224// IGS-SSR: true if value (seconds, periodic with period) matches the epoch
     1225// expected in the outdated convention (legacyRef) better than the GPS time
     1226// reference of the stream, within the spread of epochs of one update cycle
     1227////////////////////////////////////////////////////////////////////////////
     1228bool RTCM3coDecoder::igsLegacyEpoch(double value, double legacyRef, double period) const {
     1229  auto dist = [period](double a, double b) {
     1230    double d = fmod(fabs(a - b), period);
     1231    return std::min(d, period - d);
     1232  };
     1233  double dSpec   = dist(value, fmod(_igsRefEpoSec, period));
     1234  double dLegacy = dist(value, legacyRef);
     1235  return dLegacy < dSpec && dLegacy <= 30.0;
    11841236}
    11851237
  • trunk/BNC/src/RTCM3/RTCM3coDecoder.h

    r11057 r11058  
    8282  void checkPrnRange(const t_prn& prn);
    8383  QSet<QString>                         _prnOutOfRangeLogged;
     84  // IGS-SSR: latest GPS/Galileo/QZSS/SBAS epoch (GPS seconds of week) of
     85  // this stream, reference to detect GLONASS/BDS epochs in the outdated
     86  // convention of old encoders (GLONASS: UTC seconds of day, BDS: BDT)
     87  double                                _igsRefEpoSec = -1.0;
     88  QMap<QChar, bncTime>                  _legacyEpochWarned; // warned at most once per hour
     89  bool   igsLegacyEpoch(double value, double legacyRef, double period) const;
    8490
    8591
  • trunk/BNC/src/bnchelp.html

    r11057 r11058  
    81058105    Specify a combination sampling interval. Orbit and clock corrections will be produced following that interval.
    81068106    A value of 10 sec may be an appropriate choice.</p>
     8107  <p>
     8108    As the incoming corrections of the individual providers arrive with different latencies, BNC combines an epoch as
     8109    soon as each provider has delivered corrections for a later epoch, i.e. has completed the epoch. The combined
     8110    product is thus only delayed by the latency of the slowest provider. If a provider is late or missing, the epoch is
     8111    combined without it at the latest when the newest incoming corrections are 10 seconds (or the sampling interval,
     8112    if larger) younger than the epoch. Corrections arriving after an epoch was combined are ignored.</p>
    81078113
    81088114  <p>
    … …  
    91089114    A value of 10 sec may be appropriate.
    91099115    A value of zero '0' tells BNC to store all available samples into Clock RINEX files.
     9116  </p>
     9117  <p>
     9118    SP3 and Clock RINEX epochs are multiples of the sampling interval. Corrections whose epochs are not on this grid,
     9119    e.g. Galileo HAS corrections at ...:02, ...:12 seconds, are written at the preceding grid epoch if they refer to an
     9120    epoch at most 5 seconds (and less than half the sampling interval) after it. The satellite clock is then propagated
     9121    to the grid epoch using its rate, the orbit using the broadcast orbit at the grid epoch plus the difference between
     9122    precise and broadcast orbit at the correction epoch. Corrections on the grid are always used unchanged.
    91109123  </p>
    91119124
  • trunk/BNC/src/bncoutf.h

    r10608 r11058  
    1616  QString agencyFromFileName();
    1717  bool isProductFile();
     18  int  sampl() const {return _sampl;}
    1819 protected:
    1920  virtual t_irc reopen(int GPSweek, double GPSweeks);
  • trunk/BNC/src/combination/bnccomb.cpp

    r11047 r11058  
    535535
    536536
     537// True if every AC has delivered clock corrections for an epoch after epoTime
     538////////////////////////////////////////////////////////////////////////////
     539bool bncComb::allACsBeyond(const bncTime& epoTime) const {
     540  QListIterator<cmbAC*> itAC(_ACs);
     541  while (itAC.hasNext()) {
     542    const cmbAC* AC = itAC.next();
     543    bncTime acLast = _acLastClkTime.value(AC->name);
     544    if (!acLast.valid() || !(acLast > epoTime)) {
     545      return false;
     546    }
     547  }
     548  return true;
     549}
     550
    537551// Process clock corrections
    538552////////////////////////////////////////////////////////////////////////////
    … …  
    552566    if (!_cmbSysPrn.contains(sys)){
    553567      continue;
     568    }
     569
     570    // Remember the latest correction epoch of each AC (whether or not the
     571    // correction is used), to know when an AC has finished an epoch
     572    // --------------------------------------------------------------------
     573    QListIterator<cmbAC*> itAC(_ACs);
     574    while (itAC.hasNext()) {
     575      cmbAC* AC = itAC.next();
     576      if (AC->mountPoint == QString(newClk._staID.c_str())) {
     577        bncTime& acLast = _acLastClkTime[AC->name];
     578        if (!acLast.valid() || newClk._time > acLast) {
     579          acLast = newClk._time;
     580        }
     581        break;
     582      }
    554583    }
    555584
    … …  
    665694      }
    666695    }
    667     // Process the front epoch
    668     // -----------------------
    669     if (epoTime < (_lastClkCorrTime - outWait)) {
     696    // Process the front epoch as soon as all ACs have delivered corrections
     697    // for a later epoch (i.e. have finished this one), at the latest when
     698    // the newest correction is maxWait newer (an AC may be late or missing).
     699    // Waiting only outWait after the newest correction lost the corrections
     700    // of ACs with a few seconds more latency ("old correction").
     701    // -----------------------------------------------------------------------
     702    const double maxWait = max(10.0, outWait);
     703    if (allACsBeyond(epoTime) || epoTime < (_lastClkCorrTime - maxWait)) {
    670704      _resTime = epoTime;
    671705      processEpoch(_resTime, clkCorrVec);
    … …  
    834868              codeBiasesRefSig[frqType] = bias._value;
    835869            }
     870          }
     871        }
     872        // Code biases present, but not for both reference signals (e.g.
     873        // Galileo HAS): the AC clocks cannot be referred to the reference
     874        // signals and are used as they are - warn (at most once per hour
     875        // per AC and system)
     876        if (codeBiasesRefSig.size() < 2) {
     877          QString key = acName + sys;
     878          bncTime& warned = _refBiasWarned[key];
     879          if (!warned.valid() || _newCorr->_time - warned >= 3600.0) {
     880            warned = _newCorr->_time;
     881            QString refSig;
     882            for (unsigned ii = 1; ii < cmbRefSig::cIF; ii++) {
     883              t_frequency::type frqType = cmbRefSig::toFreq(sys, static_cast<cmbRefSig::type>(ii));
     884              refSig += QString(" C%1%2").arg(t_frequency::toString(frqType)[1])
     885                                         .arg(cmbRefSig::toAttrib(sys, static_cast<cmbRefSig::type>(ii)));
     886            }
     887            emit newMessage("bncComb: " + acName.toLatin1() + " provides no code biases for the " +
     888                            QByteArray(1, sys) + " reference signals" + refSig.toLatin1() +
     889                            " - its clocks are combined without referring them to these signals", false);
    836890          }
    837891        }
    … …  
    13981452    double dC_apriori_corr = dC_orb;// + dC_att; can be added if useful values are provided by the ACs
    13991453
    1400     ll(iObs) = ((corr->_clkCorr._dClk * t_CST::c) - dC_apriori_corr - corr->_satCodeBiasIF) - DotProduct(AA.Row(iObs), x0);
     1454    // Clock referred to the ionosphere-free combination of the reference
     1455    // signals: dt - (alpha b1 + beta b2) with biases in Bias-SINEX sign
     1456    // (subtracted from observations, Banville et al. 2020, Eq. 1). The code
     1457    // biases taken from the SSR streams have the opposite sign (added to
     1458    // observations), hence + _satCodeBiasIF.
     1459    ll(iObs) = ((corr->_clkCorr._dClk * t_CST::c) - dC_apriori_corr + corr->_satCodeBiasIF) - DotProduct(AA.Row(iObs), x0);
    14011460
    14021461    PP(iObs, iObs) *= 1.0 / (corr->_weightFactor * corr->_weightFactor);
  • trunk/BNC/src/combination/bnccomb.h

    r10955 r11058  
    267267  std::deque<epoClkData*>                    _epoClkData;
    268268  bncTime                                    _lastClkCorrTime;
     269  QMap<QString, bncTime>                     _acLastClkTime; // latest correction epoch per AC
     270  QMap<QString, bncTime>                     _refBiasWarned; // last warning per AC and system
     271  bool allACsBeyond(const bncTime& epoTime) const;
    269272  bncTime                                    _resTime;
    270273  cmbCorr*                                   _newCorr;
  • trunk/BNC/src/upload/bncrtnetuploadcaster.cpp

    r11057 r11058  
    548548            // Make sure the clock messages refer to same IOD as orbit messages
    549549            // ----------------------------------------------------------------
     550            // The ephemeris is selected at the first correction epoch of each
     551            // sampling interval: the grid epoch itself for corrections on the
     552            // sampling grid, otherwise (e.g. Galileo HAS at ...:02, ...:12)
     553            // the first epoch after it, which previously never selected one
    550554            if (_usedEph) {
    551                 if (fmod(epoTime.gpssec(), _samplRtcmEphCorr) == 0.0) {
     555                bool select = (fmod(epoTime.gpssec(), _samplRtcmEphCorr) == 0.0);
     556                if (!select && _samplRtcmEphCorr > 0.0) {
     557                    select = !_usedEphEpoch.valid() || epoTime == _usedEphEpoch ||
     558                             epoTime.gpsw() != _usedEphEpoch.gpsw() ||
     559                             floor(epoTime.gpssec() / _samplRtcmEphCorr) !=
     560                             floor(_usedEphEpoch.gpssec() / _samplRtcmEphCorr);
     561                }
     562                if (select) {
    552563                    (*_usedEph)[prnInternalStr] = eph;
     564                    _usedEphEpoch = epoTime;
    553565                }
    554566                else {
    … …  
    11701182    double clkRnxAcc = rtnClk[2] / t_CST::c;         // [s/s² = -/s]
    11711183
    1172     if (_rnx) {
     1184    // Corrections on epochs off the output sampling grid (e.g. Galileo HAS
     1185    // at ...:02, ...:12) are written at the nearest grid epoch, see gridEpoch;
     1186    // the clock is propagated with its rate, the orbit via the broadcast
     1187    // orbit at the grid epoch plus the (slowly varying) difference
     1188    // precise - broadcast at the correction epoch
     1189    // -----------------------------------------------------------------------
     1190    bncTime tt(GPSweek, GPSweeks);
     1191    bncTime tg;
     1192    double  clkRate = (clkRnxRate != 0.0) ? clkRnxRate : xB(5);
     1193
     1194    if (_rnx && gridEpoch(_rnx->sampl(), tt, tg, _rnxGridEpo, _rnxGridSrc)) {
    11731195        double clkRnxSig, clkRnxRateSig, clkRnxAccSig;
    11741196        int s = rtnClkSig.size();
    … …  
    11901212            break;
    11911213        }
    1192         _rnx->write(GPSweek, GPSweeks, prn, clkRnx, clkRnxRate, clkRnxAcc,
    1193             clkRnxSig, clkRnxRateSig, clkRnxAccSig);
    1194     }
    1195     if (_sp3) {
    1196         _sp3->write(GPSweek, GPSweeks, prn, rtnCoM, clkRnx, rtnVel, clkRnxRate);
     1214        _rnx->write(tg.gpsw(), tg.gpssec(), prn, clkRnx + clkRate * (tg - tt),
     1215            clkRnxRate, clkRnxAcc, clkRnxSig, clkRnxRateSig, clkRnxAccSig);
     1216    }
     1217    if (_sp3 && gridEpoch(_sp3->sampl(), tt, tg, _sp3GridEpo, _sp3GridSrc)) {
     1218        ColumnVector xyz = rtnCoM;
     1219        ColumnVector vel = rtnVel;
     1220        if (!(tg == tt)) {
     1221            ColumnVector xBg(6);
     1222            ColumnVector vBg(3);
     1223            if (eph->getCrd(tg, xBg, vBg, false) != success) {
     1224                return success;
     1225            }
     1226            xyz = xBg.Rows(1, 3) + (rtnCoM - xB.Rows(1, 3));
     1227            vel = vBg + (rtnVel - vB);
     1228        }
     1229        _sp3->write(tg.gpsw(), tg.gpssec(), prn, xyz, clkRnx + clkRate * (tg - tt),
     1230            vel, clkRnxRate);
    11971231    }
    11981232    return success;
     1233}
     1234
     1235// Output epoch for SP3/CLK files with sampling interval sampl [s]: the
     1236// correction epoch itself if it is on the sampling grid, otherwise the
     1237// preceding grid epoch if at most 5 s (and less than half the sampling
     1238// interval) earlier. Only preceding grid epochs are used, so a correction
     1239// epoch on the grid always arrives first and takes precedence. Each grid
     1240// epoch is derived from one correction epoch only.
     1241////////////////////////////////////////////////////////////////////////////
     1242bool bncRtnetUploadCaster::gridEpoch(int sampl, const bncTime& tt, bncTime& tg,
     1243                                     bncTime& lastGrid, bncTime& lastSrc) const {
     1244    if (sampl <= 0) {
     1245        tg = tt;
     1246        return true;
     1247    }
     1248    double sec  = tt.gpssec();
     1249    double back = sec - floor(sec / sampl) * sampl;   // [0, sampl)
     1250    if (back > 1.e-6 && (back > 5.0 || back >= 0.5 * sampl)) {
     1251        return false;
     1252    }
     1253    tg = (back <= 1.e-6) ? tt : tt - back;
     1254    if (lastGrid.valid() && tg == lastGrid && !(tt == lastSrc)) {
     1255        return false;
     1256    }
     1257    lastGrid = tg;
     1258    lastSrc  = tt;
     1259    return true;
    11991260}
    12001261
  • trunk/BNC/src/upload/bncrtnetuploadcaster.h

    r10754 r11058  
    103103    bncClockRinex* _rnx;
    104104    bncSP3* _sp3;
     105    // grid epoch last written to SP3/CLK and the epoch it was derived from
     106    bncTime        _sp3GridEpo, _sp3GridSrc, _rnxGridEpo, _rnxGridSrc;
     107    bncTime        _usedEphEpoch; // epoch the used ephemerides were last selected
     108    bool gridEpoch(int sampl, const bncTime& tt, bncTime& tg,
     109                   bncTime& lastGrid, bncTime& lastSrc) const;
    105110    bncBiasSinex* _bsx;
    106111    QMap<QString, const t_eph*>* _usedEph;
  • trunk/BNC/tests/README.md

    r11055 r11058  
    4646  `replay_rinex_eph` (02), `replay_feed_engine` (07, IP port disabled) and
    4747  `replay_save_sp3` (14, with SSRA00CNE1 instead of SSRA00BKG1).
     48* `replay_save_sp3_has`: 14 with Galileo HAS (SSRA00EUH0) only; its epochs are
     49  off the SP3/CLK sampling grid and are written at the preceding grid epoch.
    4850* Real-time PPP replays (`ppp_rt_*`) from 11 min slices of the CNES and WHU
    4951  captures (`data/wtzr_cnes_11min.raw`, `data/wtzr_whu_11min.raw`):
    50   `ppp_rt_float` (08), `ppp_rt_quickstart` (10, with SSRA00CNE1 instead of
    51   SSRA01CAS1, NMEA port disabled), `ppp_rt_ar_cnes` (29) and `ppp_rt_ar_whu`
     52  `ppp_rt_float` (08), `ppp_rt_quickstart` (10, NMEA port disabled), `ppp_rt_ar_cnes` (29) and `ppp_rt_ar_whu`
    5253  (30), compared exactly.
     54* `ppp_rt_net` (09, two stations) and `ppp_rt_has` (13, Galileo HAS via
     55  SSRA00EUH0) from `data/merged_260930_11min.raw`, a slice of two simultaneous
     56  recordings merged by time (BCEP00BKG0, WTZR00DEU0, FFMJ00DEU0, SSRA00CNE1,
     57  SSRA00EUH0).
     58* Combination (`combi`, `combi_ppp`; 17 and 18) from `data/combi_260930_11min.raw`
     59  (SSRA00BKG1, SSRA00CNE1, BCEP00BKG0, FFMJ00DEU0), upload to the caster
     60  disabled, SP3/CLK written to files.
    5361* SSR decoding (`ssr_*`): 3 min slices of real streams replayed through
    5462  `03_BrdcCorr.bnc`. They compare the `.ssr` files and the `BNC_DEBUG_SSR`
  • trunk/BNC/tests/cases/ppp_rt_quickstart/case.json

    r11055 r11058  
    11{
    2   "description": "10_PPPQuickStart replay: Quick-Start PPP with SSRA00CNE1 instead of SSRA01CAS1, NMEA port disabled, 11 min",
     2  "description": "10_PPPQuickStart replay: Quick-Start PPP with SSRA00CNE1, NMEA port disabled, 11 min",
    33  "config": "Example_Configs/10_PPPQuickStart.bnc",
    44  "raw": "data/wtzr_cnes_11min.raw",
    … …  
    99    "rawOutFile",
    1010    "",
    11     "-key",
    12     "PPP/corrMount",
    13     "SSRA00CNE1",
    1411    "-key",
    1512    "PPP/staTable",
Note: See TracChangeset for help on using the changeset viewer.