Changeset 11058 in ntrip
- Timestamp:
- Sep 30, 2026, 10:52:22 PM (12 hours ago)
- Location:
- trunk/BNC
- Files:
-
- 15 edited
-
Example_Configs/10_PPPQuickStart.bnc (modified) (2 diffs)
-
Example_Configs/17_Combi.bnc (modified) (1 diff)
-
Example_Configs/18_CombiPPP.bnc (modified) (1 diff)
-
src/RTCM3/RTCM3coDecoder.cpp (modified) (4 diffs)
-
src/RTCM3/RTCM3coDecoder.h (modified) (1 diff)
-
src/bnchelp.html (modified) (2 diffs)
-
src/bncoutf.h (modified) (1 diff)
-
src/combination/bnccomb.cpp (modified) (5 diffs)
-
src/combination/bnccomb.h (modified) (1 diff)
-
src/upload/bncrtnetuploadcaster.cpp (modified) (3 diffs)
-
src/upload/bncrtnetuploadcaster.h (modified) (1 diff)
-
tests/README.md (modified) (1 diff)
-
tests/cases/ppp_rt_quickstart/case.json (modified) (2 diffs)
-
tests/cases/replay_save_sp3/expected/BKG0MGXRTS_20253210000_01D_05S_CLK.CLK.gz (modified) ( previous)
-
tests/cases/replay_save_sp3/expected/BKG0MGXRTS_20253210000_01D_30S_ORB.SP3.gz (modified) ( previous)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/Example_Configs/10_PPPQuickStart.bnc
r10968 r11058 33 33 miscPort= 34 34 miscScanRTCM=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/SSRA0 1CAS1 RTCM_3.1CHN 40.07 116.28 no 235 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/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2 36 36 ntripVersion=2 37 37 onTheFlyInterval=no … … 151 151 constraints=Ionosphere: pseudo-obs 152 152 corrFile= 153 corrMount=SSRA00C AS1153 corrMount=SSRA00CNE1 154 154 corrWaitTime=5 155 155 crdFile=Input/BNC_IGS20.CRD -
trunk/BNC/Example_Configs/17_Combi.bnc
r10968 r11058 17 17 cmbNavic=0 18 18 cmbQzss=0 19 cmbSampl= 1sec19 cmbSampl=5 sec 20 20 cmbSbas=0 21 21 cmbStreams="SSRA00BKG1 BKG 1.0 Computed ", "SSRA00CNE1 CNE 1.0 Computed " -
trunk/BNC/Example_Configs/18_CombiPPP.bnc
r10968 r11058 17 17 cmbNavic=0 18 18 cmbQzss=0 19 cmbSampl= 1sec19 cmbSampl=5 sec 20 20 cmbSbas=0 21 21 cmbStreams="SSRA00BKG1 BKG 1.0 Computed ", "SSRA00CNE1 CNE 1.0 Computed " -
trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp
r11057 r11058 1132 1132 // Set _lastTime close to currentTime 1133 1133 // ---------------------------------- 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 1134 1141 if (epoSecGPS != -1) { 1135 1142 _lastTime.set(currentWeek, epoSecGPS); … … 1137 1144 else if (epoSecGlo != -1) { 1138 1145 QDate date = dateAndTimeFromGPSweek(currentTime.gpsw(), currentTime.gpssec()).date(); 1146 double leap = gnumleap(date.year(), date.month(), date.day()); 1139 1147 // IGS-SSR: the SSR Epoch Time (IDF003, 20 bit) is given in one continuous 1140 1148 // 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 } 1142 1167 if (_type == RTCMssr || _type == RTCMnewssr) { 1143 1168 // 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; 1145 1170 } 1146 1171 _lastTime.set(currentWeek, epoSecGlo); … … 1156 1181 } 1157 1182 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) { 1160 1198 epoSecBds += 14.0; 1161 1199 if (epoSecBds > 604800.0) { … … 1182 1220 } 1183 1221 } 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 //////////////////////////////////////////////////////////////////////////// 1228 bool 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; 1184 1236 } 1185 1237 -
trunk/BNC/src/RTCM3/RTCM3coDecoder.h
r11057 r11058 82 82 void checkPrnRange(const t_prn& prn); 83 83 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; 84 90 85 91 -
trunk/BNC/src/bnchelp.html
r11057 r11058 8105 8105 Specify a combination sampling interval. Orbit and clock corrections will be produced following that interval. 8106 8106 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> 8107 8113 8108 8114 <p> … … 9108 9114 A value of 10 sec may be appropriate. 9109 9115 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. 9110 9123 </p> 9111 9124 -
trunk/BNC/src/bncoutf.h
r10608 r11058 16 16 QString agencyFromFileName(); 17 17 bool isProductFile(); 18 int sampl() const {return _sampl;} 18 19 protected: 19 20 virtual t_irc reopen(int GPSweek, double GPSweeks); -
trunk/BNC/src/combination/bnccomb.cpp
r11047 r11058 535 535 536 536 537 // True if every AC has delivered clock corrections for an epoch after epoTime 538 //////////////////////////////////////////////////////////////////////////// 539 bool 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 537 551 // Process clock corrections 538 552 //////////////////////////////////////////////////////////////////////////// … … 552 566 if (!_cmbSysPrn.contains(sys)){ 553 567 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 } 554 583 } 555 584 … … 665 694 } 666 695 } 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)) { 670 704 _resTime = epoTime; 671 705 processEpoch(_resTime, clkCorrVec); … … 834 868 codeBiasesRefSig[frqType] = bias._value; 835 869 } 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); 836 890 } 837 891 } … … 1398 1452 double dC_apriori_corr = dC_orb;// + dC_att; can be added if useful values are provided by the ACs 1399 1453 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); 1401 1460 1402 1461 PP(iObs, iObs) *= 1.0 / (corr->_weightFactor * corr->_weightFactor); -
trunk/BNC/src/combination/bnccomb.h
r10955 r11058 267 267 std::deque<epoClkData*> _epoClkData; 268 268 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; 269 272 bncTime _resTime; 270 273 cmbCorr* _newCorr; -
trunk/BNC/src/upload/bncrtnetuploadcaster.cpp
r11057 r11058 548 548 // Make sure the clock messages refer to same IOD as orbit messages 549 549 // ---------------------------------------------------------------- 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 550 554 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) { 552 563 (*_usedEph)[prnInternalStr] = eph; 564 _usedEphEpoch = epoTime; 553 565 } 554 566 else { … … 1170 1182 double clkRnxAcc = rtnClk[2] / t_CST::c; // [s/s² = -/s] 1171 1183 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)) { 1173 1195 double clkRnxSig, clkRnxRateSig, clkRnxAccSig; 1174 1196 int s = rtnClkSig.size(); … … 1190 1212 break; 1191 1213 } 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); 1197 1231 } 1198 1232 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 //////////////////////////////////////////////////////////////////////////// 1242 bool 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; 1199 1260 } 1200 1261 -
trunk/BNC/src/upload/bncrtnetuploadcaster.h
r10754 r11058 103 103 bncClockRinex* _rnx; 104 104 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; 105 110 bncBiasSinex* _bsx; 106 111 QMap<QString, const t_eph*>* _usedEph; -
trunk/BNC/tests/README.md
r11055 r11058 46 46 `replay_rinex_eph` (02), `replay_feed_engine` (07, IP port disabled) and 47 47 `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. 48 50 * Real-time PPP replays (`ppp_rt_*`) from 11 min slices of the CNES and WHU 49 51 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` 52 53 (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. 53 61 * SSR decoding (`ssr_*`): 3 min slices of real streams replayed through 54 62 `03_BrdcCorr.bnc`. They compare the `.ssr` files and the `BNC_DEBUG_SSR` -
trunk/BNC/tests/cases/ppp_rt_quickstart/case.json
r11055 r11058 1 1 { 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", 3 3 "config": "Example_Configs/10_PPPQuickStart.bnc", 4 4 "raw": "data/wtzr_cnes_11min.raw", … … 9 9 "rawOutFile", 10 10 "", 11 "-key",12 "PPP/corrMount",13 "SSRA00CNE1",14 11 "-key", 15 12 "PPP/staTable",
Note:
See TracChangeset
for help on using the changeset viewer.
