Changeset 11117 in ntrip
- Timestamp:
- Oct 22, 2026, 5:22:15 PM (24 hours ago)
- Location:
- trunk/BNC
- Files:
-
- 4 edited
-
CHANGELOG.md (modified) (2 diffs)
-
src/bnchelp.html (modified) (1 diff)
-
src/ssrQc.cpp (modified) (10 diffs)
-
src/ssrQc.h (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/CHANGELOG.md
r11114 r11117 4 4 - ADDED: **SSR QC** of decoded Broadcast Correction streams (options 'QC Directory', 'QC Streams', 'QC Reference', 'QC Interval'; example configuration '31_SsrQC.bnc'). One daily report file per stream with epochs, declared and actual update intervals, gaps, latency, completeness w.r.t. healthy broadcast ephemerides, IOD matching, code and phase bias signals, yaw angle availability, missing code biases of the reference signals, clock jumps and largest orbit corrections 5 5 - ADDED: Stream comparison in the SSR QC: orbits (radial, along, cross), clocks referred to the reference signals and signal-in-space differences (SIS = radial - clock) of each stream compared with the consensus of the other ACs (one vote per AC, own AC left out, combination products not voting) or with a selected stream (e.g. 'INTERNAL'), as RMS and standard deviation per satellite and system 6 - ADDED: VTEC check in the SSR QC (pseudo system 'V'): update interval, gaps and latency of the VTEC messages, model (layers, heights, degree/order), VTEC range on a global grid and comparison with the consensus of the other AC solutions (or the 'QC Reference' stream) in TECU; ionosphere streams (IONO...) are assigned to their AC solution 6 7 - ADDED: Code bias check in the SSR QC: the DCBs of each stream are compared with each other AC and optionally with a Bias SINEX file (option 'QC Bias File'); a stream with the opposite sign for most DCBs (SINEX instead of SSR sign convention) is reported 7 8 - ADDED: SIS (radial - clock, satellite-specific clock offset included) per epoch and SISRMS/SISSig per satellite and system in the SP3 comparison … … 39 40 - FIXED: GPS attitude model ('Computed'): at the end of a noon/midnight turn the yaw jumped to the nominal yaw (e.g. by 95 deg); the satellite now keeps yawing at its maximum rate until it has caught up with the nominal yaw 40 41 - FIXED: GLONASS attitude model ('Computed'): the yaw was frozen while the nominal yaw rate exceeded the maximum rate and then jumped to the nominal yaw; now following Dilssner et al. (2011), the satellite yaws at the maximum rate (0.25 deg/s) during noon turns until it has caught up, and in the Earth's shadow until it reaches the nominal yaw at shadow exit 42 - FIXED: Source table window: streams with an incomplete source table entry (e.g. without the fields authentication, fee, bitrate, misc) were not listed; they are now shown as long as the entry contains the fields up to 'nmea' 41 43 - FIXED: Upload of raw streams lost data chunks 42 44 - FIXED: RTCM3 decoder: a 0xD3 byte followed by a zero length field at the end of the received data caused an endless loop until std::bad_alloc, and the stream was deleted -
trunk/BNC/src/bnchelp.html
r11113 r11117 5235 5235 clock 0.689 (STD 0.008), radial 0.020, along 0.036, cross 0.022, SIS (radial - clock) 0.687 (STD 0.008) 5236 5236 G satellites deviating from the reference, RMS SIS [m]: G02(0.38) G16(0.33) G20(0.91) G26(3.36) 5237 </pre> 5238 <p> 5239 <b>Ionospheric models (VTEC):</b> Streams with VTEC messages (e.g. the ionosphere streams 'IONO...', or SSR 5240 streams also carrying VTEC) get the pseudo system 'V' (VTEC) in the report: epochs, declared and actual update interval, 5241 gaps and latency of the VTEC messages, the model (number of layers, layer heights, degree and order of the 5242 spherical harmonics), and the range of the VTEC values on a global grid (10 degrees in latitude from -80 to 80 5243 degrees, 15 degrees in longitude), including the percentage of negative values, which are set to zero as in PPP. 5244 Every 300 seconds (with a delay of 120 seconds), the latest model of each stream not older than twice its update 5245 interval (at least 120 seconds, at most one hour) is evaluated on this grid and compared with the median of the 5246 models of the other AC solutions (one vote per solution, own AC left out, combinations like 'IONO00IGS1' not 5247 voting; at least one other solution), or with the selected 'QC Reference' stream. The mean difference (a bias 5248 of the model) and the RMS are given in TEC units, e.g. 5249 </p> 5250 <pre> 5251 V VTEC 60 60 / 60.0 / 60.0 0 0 1.0 / 5.0 1 5252 V VTEC model: 1 layer(s): 450 km (degree/order 15/15) 5253 V VTEC on a global 10 x 15 deg grid: 2.1 ... 65.7 TECU, negative (set to zero) 0.0 % 5254 V VTEC comparison with consensus of the other ACs (1.0 solutions on average), 12 epochs: mean 3.47 TECU, RMS 7.55 TECU, max |diff| 24.0 TECU 5237 5255 </pre> 5238 5256 <p> -
trunk/BNC/src/ssrQc.cpp
r11091 r11117 60 60 const double DEV_MIN = 0.05; // [m] deviating satellites: RMS above max(DEV_MIN, 61 61 const double DEV_FACTOR = 3.0; // DEV_FACTOR x median RMS of the system) 62 const double VTEC_CMP_SAMPL = 300.0; // [s] sampling of the VTEC comparison 63 const double VTEC_CMP_DELAY = 120.0; // [s] VTEC epochs compared when data this much newer arrived 64 const double VTEC_HIST = 3600.0; // [s] VTEC models kept for the comparison 65 const double VTEC_GRID_LAT = 10.0; // [deg] grid of the VTEC evaluation, latitudes -80 ... 80 66 const double VTEC_GRID_LON = 15.0; // [deg] 62 67 63 68 double tKey(const bncTime& tt) { 64 69 return tt.gpsw() * 604800.0 + tt.gpssec(); 65 70 } 66 const char* TYPE_NAME[] = {"ORBIT", "CLOCK", "CODE_BIAS", "PHASE_BIAS"}; 71 const char* TYPE_NAME[] = {"ORBIT", "CLOCK", "CODE_BIAS", "PHASE_BIAS", "VTEC"}; 67 72 68 73 double median(QVector<double> vv) { … … 155 160 connect(BNC_CORE, SIGNAL(newPhaseBiases(QList<t_satPhaseBias>)), 156 161 this, SLOT(slotNewPhaseBiases(QList<t_satPhaseBias>)), Qt::DirectConnection); 162 connect(BNC_CORE, SIGNAL(newTec(t_vTec)), 163 this, SLOT(slotNewTec(t_vTec)), Qt::DirectConnection); 157 164 // combined corrections (stream INTERNAL) 158 165 if (bncComb::exists()) { … … 175 182 //////////////////////////////////////////////////////////////////////////// 176 183 QString t_ssrQc::acOf(const QString& staID) { 177 static const QRegularExpression re("^(?:SSR |OSB)[AC][0-9]{2}([A-Z0-9]{3})[0-9]$");184 static const QRegularExpression re("^(?:SSR[AC]|OSB[AC]|IONO)[0-9]{2}([A-Z0-9]{3})[0-9]$"); 178 185 QRegularExpressionMatch mm = re.match(staID); 179 186 return mm.hasMatch() ? mm.captured(1) : staID; … … 185 192 //////////////////////////////////////////////////////////////////////////// 186 193 QString t_ssrQc::solOf(const QString& staID) { 187 static const QRegularExpression re("^(?:SSR |OSB)[AC]([0-9]{2})([A-Z0-9]{3})[0-9]$");194 static const QRegularExpression re("^(?:SSR[AC]|OSB[AC]|IONO)([0-9]{2})([A-Z0-9]{3})[0-9]$"); 188 195 QRegularExpressionMatch mm = re.match(staID); 189 196 return mm.hasMatch() ? mm.captured(2) + mm.captured(1) : staID; … … 222 229 << " completeness w.r.t. healthy broadcast ephemerides," << endl 223 230 << "IOD matching, code and phase bias signals, yaw angle, code biases of the reference signals, clock jumps > " 224 << MAX_CLK_JUMP << " m, largest orbit corrections" << endl 231 << MAX_CLK_JUMP << " m, largest orbit corrections, VTEC models (system V)" << endl 225 232 << "Reference signals of the clocks: " << t_ssrRefSig::currentString().toStdString() << endl; 226 233 } … … 278 285 compareEpoch(_nextCmpEpoch); 279 286 _nextCmpEpoch = _nextCmpEpoch + CMP_SAMPL; 287 } 288 // VTEC comparison 289 if (!_vtecHist.isEmpty()) { 290 if (!_nextVtecCmp.valid()) { 291 double sec = ceil(time.gpssec() / VTEC_CMP_SAMPL) * VTEC_CMP_SAMPL; 292 _nextVtecCmp = bncTime(time.gpsw(), sec); 293 } 294 while (time - _nextVtecCmp >= VTEC_CMP_DELAY) { 295 compareVtec(_nextVtecCmp); 296 _nextVtecCmp = _nextVtecCmp + VTEC_CMP_SAMPL; 297 } 280 298 } 281 299 if (time - _intervalStart >= _interval) { … … 983 1001 } 984 1002 1003 // Ionospheric model (VTEC) 1004 //////////////////////////////////////////////////////////////////////////// 1005 void t_ssrQc::slotNewTec(t_vTec vTec) { 1006 QMutexLocker locker(&_mutex); 1007 if (vTec._layers.empty()) { 1008 return; 1009 } 1010 QString sta = QString::fromStdString(vTec._staID); 1011 if (!_excluded.contains(sta) || sta == _reference) { 1012 QMap<double, t_vTec>& hist = _vtecHist[sta]; 1013 hist[tKey(vTec._time)] = vTec; 1014 while (!hist.isEmpty() && tKey(vTec._time) - hist.firstKey() > VTEC_HIST) { 1015 hist.erase(hist.begin()); 1016 } 1017 } 1018 if (selected(vTec._staID)) { 1019 newEpoch(vTec._staID, 'V', VTEC, vTec._time, vTec._updateInt, "VTEC"); 1020 QStringList layers; 1021 for (const t_vTecLayer& layer : vTec._layers) { 1022 layers << QString("%1 km (degree/order %2/%3)").arg(layer._height / 1000.0, 0, 'f', 0) 1023 .arg(layer._C.Nrows() - 1).arg(layer._C.Ncols() - 1); 1024 } 1025 _vtecStat[sta].model = QString("%1 layer(s): %2").arg(vTec._layers.size()).arg(layers.join(", ")); 1026 } 1027 checkInterval(vTec._time); 1028 } 1029 1030 // VTEC [TECU] of a model at a geocentric latitude and longitude [rad] and 1031 // epoch: spherical harmonics in the sun-fixed frame as used in PPP, sum of 1032 // the layers (negative layer values set to zero as in PPP) 1033 //////////////////////////////////////////////////////////////////////////// 1034 double t_ssrQc::vtecAt(const t_vTec& vTec, double lat, double lon, const bncTime& tt, bool& negative) { 1035 double epoch = fmod(tt.gpssec(), 86400.0); 1036 double lonS = fmod(lon + (epoch - 50400.0) * M_PI / 43200.0, 2.0 * M_PI); 1037 double vtec = 0.0; 1038 negative = false; 1039 for (const t_vTecLayer& layer : vTec._layers) { 1040 int N = layer._C.Nrows() - 1; 1041 int M = layer._C.Ncols() - 1; 1042 double vv = 0.0; 1043 for (int n = 0; n <= N; n++) { 1044 for (int m = 0; m <= min(n, M); m++) { 1045 double pnm = associatedLegendreFunction(n, m, sin(lat)); 1046 double fac = (m == 0) ? sqrt(2.0 * n + 1.0) 1047 : sqrt(2.0 * (2.0 * n + 1.0) * factorial(n - m) / factorial(n + m)); 1048 vv += (layer._C[n][m] * cos(m * lonS) + layer._S[n][m] * sin(m * lonS)) * pnm * fac; 1049 } 1050 } 1051 if (vv < 0.0) { 1052 negative = true; 1053 vv = 0.0; 1054 } 1055 vtec += vv; 1056 } 1057 return vtec; 1058 } 1059 1060 // VTEC comparison: each model (latest one not older than twice its update 1061 // interval) evaluated on a global grid and compared with the median of the 1062 // other AC solutions (one vote per solution, own AC left out, combination 1063 // products not voting) or with the reference stream 1064 //////////////////////////////////////////////////////////////////////////// 1065 void t_ssrQc::compareVtec(const bncTime& tt) { 1066 double tk = tKey(tt); 1067 1068 QMap<QString, const t_vTec*> models; 1069 QMapIterator<QString, QMap<double, t_vTec> > itH(_vtecHist); 1070 while (itH.hasNext()) { 1071 itH.next(); 1072 QMap<double, t_vTec>::const_iterator it = itH.value().upperBound(tk); 1073 if (it == itH.value().constBegin()) { 1074 continue; 1075 } 1076 --it; 1077 const t_vTec& vTec = it.value(); 1078 double interval = (vTec._updateInt < unsigned(ssrUpdateInt.size()) && ssrUpdateInt[vTec._updateInt] > 0) 1079 ? ssrUpdateInt[vTec._updateInt] : VTEC_CMP_SAMPL; 1080 double maxAge = min(3600.0, max(120.0, 2.0 * interval)); 1081 if (tk - it.key() <= maxAge) { 1082 models[itH.key()] = &vTec; 1083 } 1084 } 1085 if (models.isEmpty()) { 1086 return; 1087 } 1088 1089 // grid values of all models 1090 QVector<QPair<double, double> > grid; 1091 for (double lat = -80.0; lat <= 80.0 + 1e-6; lat += VTEC_GRID_LAT) { 1092 for (double lon = -180.0; lon < 180.0 - 1e-6; lon += VTEC_GRID_LON) { 1093 grid << QPair<double, double>(lat * M_PI / 180.0, lon * M_PI / 180.0); 1094 } 1095 } 1096 QMap<QString, QVector<double> > val; 1097 QMapIterator<QString, const t_vTec*> itM(models); 1098 while (itM.hasNext()) { 1099 itM.next(); 1100 QVector<double>& vv = val[itM.key()]; 1101 vv.resize(grid.size()); 1102 bool selectedSta = selected(itM.key().toStdString()); 1103 t_vtecStat* st = selectedSta ? &_vtecStat[itM.key()] : 0; 1104 for (int ii = 0; ii < grid.size(); ii++) { 1105 bool negative = false; 1106 vv[ii] = vtecAt(*itM.value(), grid[ii].first, grid[ii].second, tt, negative); 1107 if (st) { 1108 if (st->numGrid == 0 || vv[ii] < st->minVal) st->minVal = vv[ii]; 1109 if (st->numGrid == 0 || vv[ii] > st->maxVal) st->maxVal = vv[ii]; 1110 st->numGrid += 1; 1111 if (negative) st->numNeg += 1; 1112 } 1113 } 1114 } 1115 1116 // comparison 1117 for (const QString& staID : models.keys()) { 1118 if (!selected(staID.toStdString())) { 1119 continue; 1120 } 1121 QMap<QString, QStringList> perSol; // solution: streams 1122 for (const QString& oo : models.keys()) { 1123 if (oo == staID) continue; 1124 if (_reference == "CONSENSUS") { 1125 if (!votes(oo) || acOf(oo) == acOf(staID)) continue; 1126 } 1127 else if (oo != _reference) { 1128 continue; 1129 } 1130 perSol[solOf(oo)] << oo; 1131 } 1132 if (perSol.isEmpty()) { 1133 continue; 1134 } 1135 t_vtecStat& st = _vtecStat[staID]; 1136 st.numCmp += 1; 1137 st.numSol += perSol.size(); 1138 for (int ii = 0; ii < grid.size(); ii++) { 1139 QVector<double> solVal; 1140 for (const QStringList& streams : perSol) { 1141 QVector<double> vv; 1142 for (const QString& oo : streams) vv << val[oo][ii]; 1143 solVal << median(vv); 1144 } 1145 double diff = val[staID][ii] - median(solVal); 1146 st.numDiff += 1; 1147 st.sDiff += diff; 1148 st.sDiff2 += diff * diff; 1149 st.maxAbs = max(st.maxAbs, fabs(diff)); 1150 } 1151 } 1152 } 1153 1154 985 1155 // Write the report of the current interval and reset the statistics 986 1156 //////////////////////////////////////////////////////////////////////////// … … 1029 1199 << " Latency med/max [s] Sats" << endl; 1030 1200 for (char sys : systems) { 1031 for (int type = ORB; type <= PBIAS; type++) {1201 for (int type = ORB; type <= VTEC; type++) { 1032 1202 QString kk = QString("%1 %2 %3").arg(staID).arg(sys).arg(type); 1033 1203 if (!_epoStat.contains(kk)) { … … 1059 1229 // Per system: completeness, ephemeris, biases, plausibility 1060 1230 for (char sys : systems) { 1231 // Ionosphere (VTEC): model, values on the grid, comparison 1232 if (sys == 'V') { 1233 QStringList lines; 1234 QString kk = QString("%1 %2 %3").arg(staID).arg(sys).arg(int(VTEC)); 1235 if (_epoStat.contains(kk)) { 1236 const t_epoStat& stat = _epoStat[kk]; 1237 double medInt = median(stat.intervals); 1238 if (stat.nominal > 0 && stat.intervals.size() > 1 && 1239 (medInt > 1.5 * stat.nominal || medInt < stat.nominal / 1.5)) { 1240 lines << QString("VTEC: declared update interval %1 s, actual %2 s") 1241 .arg(stat.nominal).arg(medInt, 0, 'f', 1); 1242 } 1243 } 1244 if (_vtecStat.contains(staID)) { 1245 const t_vtecStat& vs = _vtecStat[staID]; 1246 if (!vs.model.isEmpty()) { 1247 lines << "VTEC model: " + vs.model; 1248 } 1249 if (vs.numGrid) { 1250 lines << QString("VTEC on a global %1 x %2 deg grid: %3 ... %4 TECU, negative (set to zero) %5 %") 1251 .arg(VTEC_GRID_LAT, 0, 'f', 0).arg(VTEC_GRID_LON, 0, 'f', 0) 1252 .arg(vs.minVal, 0, 'f', 1).arg(vs.maxVal, 0, 'f', 1) 1253 .arg(100.0 * vs.numNeg / vs.numGrid, 0, 'f', 1); 1254 } 1255 if (vs.numDiff) { 1256 QString refText = (_reference == "CONSENSUS") 1257 ? QString("consensus of the other ACs (%1 solutions on average)") 1258 .arg(double(vs.numSol) / vs.numCmp, 0, 'f', 1) 1259 : QString("stream %1").arg(_reference); 1260 double mean = vs.sDiff / vs.numDiff; 1261 double rms = sqrt(vs.sDiff2 / vs.numDiff); 1262 lines << QString("VTEC comparison with %1, %2 epochs: mean %3 TECU, RMS %4 TECU, max |diff| %5 TECU") 1263 .arg(refText).arg(vs.numCmp).arg(mean, 0, 'f', 2).arg(rms, 0, 'f', 2) 1264 .arg(vs.maxAbs, 0, 'f', 1); 1265 } 1266 else { 1267 lines << "VTEC not compared: no other model available"; 1268 } 1269 } 1270 for (const QString& line : lines) { 1271 out << " " << sys << " " << line.toStdString() << endl; 1272 } 1273 continue; 1274 } 1061 1275 QMap<QString, unsigned> missOrb, missClk, missCB, missPB, iodUnknown, unhealthy, noEph, 1062 1276 jumps, missRef, noYaw, yawPartly; … … 1266 1480 _cmpStat.clear(); 1267 1481 _cmpEpochs.clear(); 1482 for (QMap<QString, t_vtecStat>::iterator it = _vtecStat.begin(); it != _vtecStat.end(); ++it) { 1483 t_vtecStat fresh; 1484 fresh.model = it.value().model; 1485 it.value() = fresh; 1486 } 1268 1487 1269 1488 // reset, keeping the state needed across intervals -
trunk/BNC/src/ssrQc.h
r11091 r11117 10 10 // Quality control of the decoded SSR streams: epochs and update intervals, 11 11 // latency, completeness with respect to the broadcast ephemerides, IOD 12 // matching, code bias signals, clock jumps and orbit correction size. A 12 // matching, code bias signals, clock jumps, orbit correction size and the 13 // ionospheric (VTEC) models. A 13 14 // report per stream and system is written to the QC logfile at the end of 14 15 // each interval and at the end of the program. … … 29 30 void slotNewCodeBiases(QList<t_satCodeBias> codeBiases); 30 31 void slotNewPhaseBiases(QList<t_satPhaseBias> phaseBiases); 32 void slotNewTec(t_vTec vTec); 31 33 32 34 signals: … … 34 36 35 37 private: 36 enum e_type {ORB = 0, CLK = 1, CBIAS = 2, PBIAS = 3}; 38 enum e_type {ORB = 0, CLK = 1, CBIAS = 2, PBIAS = 3, VTEC = 4}; 37 39 38 40 class t_epoStat { // per stream, system and message type … … 147 149 bncBiasSnx* _bsxRef; // reference code biases (option ssrQcBiasFile) 148 150 QString _bsxName; 151 152 // VTEC check: model content, values on a global grid and comparison with 153 // the consensus of the other ACs (or the reference stream) 154 class t_vtecStat { // per stream 155 public: 156 unsigned numCmp = 0; // compared epochs 157 unsigned numSol = 0; // sum of the solutions in the consensus 158 unsigned numDiff = 0; // compared grid values 159 double sDiff = 0.0; // sum of the differences [TECU] 160 double sDiff2 = 0.0; // sum of squares [TECU^2] 161 double maxAbs = 0.0; // [TECU] 162 unsigned numGrid = 0; // evaluated grid values 163 unsigned numNeg = 0; // negative grid values 164 double minVal = 0.0; // [TECU] 165 double maxVal = 0.0; 166 QString model; // layers, heights, degree/order of the latest model 167 }; 168 void compareVtec(const bncTime& tt); 169 static double vtecAt(const t_vTec& vTec, double lat, double lon, const bncTime& tt, bool& negative); 170 QMap<QString, QMap<double, t_vTec> > _vtecHist; // stream, time 171 QMap<QString, t_vtecStat> _vtecStat; // stream 172 bncTime _nextVtecCmp; 149 173 }; 150 174
Note:
See TracChangeset
for help on using the changeset viewer.
