Changeset 11129 in ntrip
- Timestamp:
- Oct 28, 2026, 9:31:18 AM (25 hours ago)
- Location:
- trunk/BNC/src
- Files:
-
- 5 edited
-
bncsettings.cpp (modified) (1 diff)
-
bncwindow.cpp (modified) (6 diffs)
-
bncwindow.h (modified) (1 diff)
-
ssrQc.cpp (modified) (9 diffs)
-
ssrQc.h (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/src/bncsettings.cpp
r11128 r11129 144 144 setValue_p("ssrQcIntr", "1 hour"); 145 145 setValue_p("ssrQcBiasFile", ""); 146 setValue_p("ssrQcAntexFile", ""); 146 147 setValue_p("ssrRefSignals", ""); 147 148 // Feed Engine -
trunk/BNC/src/bncwindow.cpp
r11126 r11129 303 303 _ssrQcBiasFileChooser = new qtFileChooser(0, qtFileChooser::File); 304 304 _ssrQcBiasFileChooser->setFileName(settings.value("ssrQcBiasFile").toString()); 305 _ssrQcAntexFileChooser = new qtFileChooser(0, qtFileChooser::File); 306 _ssrQcAntexFileChooser->setFileName(settings.value("ssrQcAntexFile").toString()); 305 307 _ssrQcIntrComboBox = new QComboBox(); 306 308 _ssrQcIntrComboBox->setEditable(false); … … 933 935 cLayout->addWidget(new QLabel("QC Bias File"), 8, 0); 934 936 cLayout->addWidget(_ssrQcBiasFileChooser, 8, 1, 1, 30); 935 cLayout->addWidget(new QLabel(""), 9, 1); 936 cLayout->setRowStretch(8, 999); 937 cLayout->addWidget(new QLabel("QC ANTEX File"), 9, 0); 938 cLayout->addWidget(_ssrQcAntexFileChooser, 9, 1, 1, 30); 939 cLayout->addWidget(new QLabel(""), 10, 1); 940 cLayout->setRowStretch(9, 999); 937 941 cgroup->setLayout(cLayout); 938 942 … … 1606 1610 _ssrQcRefLineEdit->setWhatsThis(tr("<p>Specify the reference for the comparison of the orbits and clocks of the checked streams: 'CONSENSUS' for the median of all other streams, or the name of a stream, e.g. 'INTERNAL' for the combination.</p><p>Default is 'CONSENSUS'. <i>[key: ssrQcRef]</i></p>")); 1607 1611 _ssrQcStreamsLineEdit->setWhatsThis(tr("<p>Specify the Broadcast Correction streams to be checked, as a comma separated list of stream names (e.g. SSRA00CNE1,SSRA00GMV0), or 'ALL' for all decoded streams. A stream with a leading '!' is excluded from the QC and from the consensus, e.g. 'ALL,!SIRGAS200001' for streams in another reference frame.</p><p>Default is 'ALL'. <i>[key: ssrQcStreams]</i></p>")); 1612 _ssrQcAntexFileChooser->setWhatsThis(tr("<p>Specify an ANTEX file (e.g. the current igs20.atx) to enable the attitude check of the SSR QC: for streams sending the satellite yaw angle, the clock change of the stream relative to the other ACs during noon and midnight turns is compared with the change predicted from the difference of its yaw angle to BNC's attitude model ('Computed', see section 'Combination'). The ANTEX file provides the satellite block types the model needs.</p><p>Default is an empty option field, meaning no attitude check. <i>[key: ssrQcAntexFile]</i></p>")); 1608 1613 _ssrQcBiasFileChooser->setWhatsThis(tr("<p>Specify a Bias SINEX file (OSB or DSB, e.g. a CAS DCB or a GFZ OSB product) as reference for the code bias check of the SSR QC: the DCBs of each stream are compared with the DCBs of this file, in addition to the comparison with the other ACs. A sign opposite to the reference for most DCBs indicates a stream using the SINEX instead of the SSR sign convention.</p><p>Default is an empty option field, meaning the code biases are compared with the other ACs only. <i>[key: ssrQcBiasFile]</i></p>")); 1609 1614 _ssrQcIntrComboBox->setWhatsThis(tr("<p>Select the interval of the SSR QC reports. <i>[key: ssrQcIntr]</i></p>")); … … 1887 1892 delete _ssrQcIntrComboBox; 1888 1893 delete _ssrQcBiasFileChooser; 1894 delete _ssrQcAntexFileChooser; 1889 1895 delete _corrPortLineEdit; 1890 1896 delete _outPortLineEdit; … … 2419 2425 settings.setValue("ssrQcIntr", _ssrQcIntrComboBox->currentText()); 2420 2426 settings.setValue("ssrQcBiasFile", _ssrQcBiasFileChooser->fileName()); 2427 settings.setValue("ssrQcAntexFile", _ssrQcAntexFileChooser->fileName()); 2421 2428 settings.setValue("corrPort", _corrPortLineEdit->text()); 2422 2429 // Feed Engine … … 2959 2966 enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcRefLineEdit); 2960 2967 enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcBiasFileChooser); 2968 enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcAntexFileChooser); 2961 2969 } 2962 2970 -
trunk/BNC/src/bncwindow.h
r11087 r11129 155 155 QLineEdit* _ssrQcRefLineEdit; 156 156 qtFileChooser* _ssrQcBiasFileChooser; 157 qtFileChooser* _ssrQcAntexFileChooser; 157 158 QLineEdit* _miscMountLineEdit; 158 159 QLineEdit* _miscPortLineEdit; -
trunk/BNC/src/ssrQc.cpp
r11119 r11129 49 49 #include "bncsettings.h" 50 50 #include "combination/bnccomb.h" 51 #include "bncantex.h" 51 52 52 53 using namespace std; … … 60 61 const double DEV_MIN = 0.05; // [m] deviating satellites: RMS above max(DEV_MIN, 61 62 const double DEV_FACTOR = 3.0; // DEV_FACTOR x median RMS of the system) 63 const double ATT_ONSET = 20.0 * M_PI / 180.0; // [rad] change of the yaw difference starting a turn 64 const double ATT_STABLE = 2.0 * M_PI / 180.0; // [rad] yaw difference stable again ... 65 const double ATT_STABLE_T = 600.0; // [s] ... for this time: end of the turn 66 const double ATT_REF_FROM = 900.0; // [s] reference before the turn: samples from 15 min 67 const double ATT_REF_TO = 300.0; // to 5 min before the current epoch 68 const double ATT_MAX_GAP = 600.0; // [s] longer gaps interrupt a turn 69 const double ATT_MAX_TURN = 10800.0; // [s] a turn is closed after 3 h at the latest 70 const double ATT_MIN_PRED = 0.03; // [m] smaller predicted clock changes are not assessed 62 71 const double VTEC_CMP_SAMPL = 300.0; // [s] sampling of the VTEC comparison 63 72 const double VTEC_CMP_DELAY = 120.0; // [s] VTEC epochs compared when data this much newer arrived … … 150 159 delete _bsxRef; 151 160 _bsxRef = 0; 161 } 162 } 163 _antex = 0; 164 QString antexFile = settings.value("ssrQcAntexFile").toString(); 165 expandEnvVar(antexFile); 166 if (!antexFile.isEmpty()) { 167 _antex = new bncAntex(); 168 if (_antex->readFile(antexFile) != success || _antex->numSatPrns() == 0) { 169 emit newMessage(("SSR QC: wrong ANTEX file " + antexFile + " - no attitude check").toLatin1(), true); 170 delete _antex; 171 _antex = 0; 152 172 } 153 173 } … … 247 267 } 248 268 delete _bsxRef; 269 delete _antex; 249 270 } 250 271 … … 488 509 if (yawProvided) { 489 510 stat.numYaw++; 511 t_yawLast& yl = _yawLast[QString::fromStdString(bias._staID)][QString::fromStdString(bias._prn.toString())]; 512 double dt = 0.0; 513 if (bias._updateInt < unsigned(ssrUpdateInt.size())) { 514 dt = 0.5 * ssrUpdateInt[bias._updateInt]; // reference time in the middle of the interval 515 } 516 yl.time = bias._time + dt; 517 yl.yaw = bias._yaw; 518 yl.rate = bias._yawRate; 490 519 } 491 520 } … … 986 1015 if (dClk.contains(prn)) { 987 1016 double dc = dClk[prn] - off; 1017 if (_antex && _yawLast.value(staID).contains(prn)) { 1018 attitudeSample(staID, prn, tt, crd[staID][prn].xx, crd[staID][prn].vv, dc); 1019 } 988 1020 stat.numClk += 1; 989 1021 stat.sClk += dc * dc; … … 999 1031 } 1000 1032 } 1033 } 1034 1035 // Attitude check: one sample per comparison epoch for a stream sending the 1036 // yaw angle. The yaw difference of BNC's attitude model ('Computed') minus 1037 // the stream is followed continuously over time; the predicted clock 1038 // difference of the stream to an AC following the model is 1039 // lambda_IF * dpsi / 2pi (see combination, dC_att). A turn starts when the 1040 // yaw difference changes by more than ATT_ONSET against the samples 5-15 1041 // minutes before, and ends when it is stable again for ATT_STABLE_T. 1042 //////////////////////////////////////////////////////////////////////////// 1043 void t_ssrQc::attitudeSample(const QString& staID, const QString& prn, const bncTime& tt, 1044 const ColumnVector& xx, const ColumnVector& vv, double dc) { 1045 const t_yawLast& yl = _yawLast[staID][prn]; 1046 double age = tt - yl.time; 1047 if (!yl.time.valid() || fabs(age) > 120.0) { 1048 return; 1049 } 1050 double psiAC = yl.yaw + yl.rate * age; 1051 1052 // model yaw, once per satellite and epoch (model state in time order) 1053 double tk = tKey(tt); 1054 if (tk != _psiModelT) { 1055 _psiModel.clear(); 1056 _psiModelT = tk; 1057 } 1058 if (!_psiModel.contains(prn)) { 1059 ColumnVector dx(3); 1060 double psiM = 0.0; 1061 double Mjd = tt.mjd() + tt.daysec() / 86400.0; 1062 if (_antex->satCoMcorrection(prn, Mjd, xx, vv, dx, bncAntex::ATT_COMPUTED, 0.0, 1063 "ssrQc", &psiM) != success) { 1064 return; 1065 } 1066 _psiModel[prn] = psiM; 1067 } 1068 1069 // ionosphere-free wavelength of the reference signals, c / (f1 + f2) 1070 QString sig1, sig2; 1071 t_frequency::type frq1, frq2; 1072 if (!refSignals(prn[0].toLatin1(), sig1, sig2, frq1, frq2)) { 1073 return; 1074 } 1075 double lambda = t_CST::c / (t_CST::freq(frq1, 0) + t_CST::freq(frq2, 0)); 1076 1077 t_attTrack& tr = _attTrack[staID][prn]; 1078 if (tr.hasLast && tk - tr.lastT > ATT_MAX_GAP) { 1079 if (tr.active) { 1080 attitudeEvent(staID, prn, tr, "interrupted by a gap"); 1081 } 1082 tr = t_attTrack(); 1083 } 1084 double dpsi = remainder(_psiModel[prn] - psiAC, 2.0 * M_PI); 1085 if (tr.hasLast) { 1086 dpsi = tr.lastDPsi + remainder(dpsi - tr.lastDPsi, 2.0 * M_PI); 1087 } 1088 tr.hasLast = true; 1089 tr.lastDPsi = dpsi; 1090 tr.lastT = tk; 1091 t_attSample smp; 1092 smp.t = tk; 1093 smp.dc = dc; 1094 smp.p = lambda * dpsi / (2.0 * M_PI); 1095 smp.dpsi = dpsi; 1096 1097 if (!tr.active) { 1098 tr.hist << smp; 1099 while (!tr.hist.isEmpty() && tk - tr.hist.first().t > ATT_REF_FROM) { 1100 tr.hist.removeFirst(); 1101 } 1102 // reference: samples 5 to 15 minutes before 1103 QVector<double> rDc, rP, rPsi; 1104 for (const t_attSample& hh : tr.hist) { 1105 if (tk - hh.t >= ATT_REF_TO) { 1106 rDc << hh.dc; rP << hh.p; rPsi << hh.dpsi; 1107 } 1108 } 1109 if (rPsi.size() >= 3 && fabs(dpsi - median(rPsi)) > ATT_ONSET) { 1110 tr.active = true; 1111 tr.dc0 = median(rDc); 1112 tr.p0 = median(rP); 1113 tr.dpsi0 = median(rPsi); 1114 for (const t_attSample& hh : tr.hist) { 1115 if (tk - hh.t < ATT_REF_TO) tr.event << hh; // the beginning of the turn 1116 } 1117 tr.hist.clear(); 1118 } 1119 return; 1120 } 1121 1122 tr.event << smp; 1123 // end: yaw difference stable for ATT_STABLE_T, or the turn too long 1124 double tStart = tr.event.first().t; 1125 bool stable = (tk - tStart >= ATT_STABLE_T); 1126 if (stable) { 1127 for (int ii = tr.event.size() - 1; ii >= 0 && tk - tr.event[ii].t <= ATT_STABLE_T; ii--) { 1128 if (fabs(tr.event[ii].dpsi - dpsi) > ATT_STABLE) { 1129 stable = false; 1130 break; 1131 } 1132 } 1133 } 1134 if (stable || tk - tStart > ATT_MAX_TURN) { 1135 attitudeEvent(staID, prn, tr, stable ? "" : "closed after 3 h"); 1136 double keepPsi = tr.lastDPsi, keepT = tr.lastT; 1137 tr = t_attTrack(); 1138 tr.hasLast = true; 1139 tr.lastDPsi = keepPsi; 1140 tr.lastT = keepT; 1141 tr.hist << smp; 1142 } 1143 } 1144 1145 // Assessment of a turn: observed clock change of the stream relative to the 1146 // consensus against the predicted one (regression slope, RMS reduction) 1147 //////////////////////////////////////////////////////////////////////////// 1148 void t_ssrQc::attitudeEvent(const QString& staID, const QString& prn, t_attTrack& tr, 1149 const QString& end) { 1150 if (tr.event.size() < 3) { 1151 return; 1152 } 1153 double sxx = 0.0, sxy = 0.0, syy = 0.0, srr = 0.0, maxX = 0.0, maxPsi = 0.0; 1154 for (const t_attSample& ss : tr.event) { 1155 double x = ss.p - tr.p0; 1156 double y = ss.dc - tr.dc0; 1157 sxx += x * x; sxy += x * y; syy += y * y; srr += (y - x) * (y - x); 1158 if (fabs(x) > fabs(maxX)) maxX = x; 1159 if (fabs(ss.dpsi - tr.dpsi0) > fabs(maxPsi)) maxPsi = ss.dpsi - tr.dpsi0; 1160 } 1161 int nn = tr.event.size(); 1162 // changes at the end of the turn: mean of the last 5 minutes 1163 double tEnd = tr.event.last().t, xEnd = 0.0, yEnd = 0.0; 1164 int nEnd = 0; 1165 for (const t_attSample& ss : tr.event) { 1166 if (tEnd - ss.t <= 300.0) { 1167 xEnd += ss.p - tr.p0; yEnd += ss.dc - tr.dc0; ++nEnd; 1168 } 1169 } 1170 xEnd /= nEnd; yEnd /= nEnd; 1171 double rmsObs = sqrt(syy / nn); 1172 double rmsRes = sqrt(srr / nn); 1173 double slope = (sxx > 0.0) ? sxy / sxx : 0.0; 1174 1175 bncTime t1, t2; 1176 t1.set(int(tr.event.first().t / 604800.0), fmod(tr.event.first().t, 604800.0)); 1177 t2.set(int(tEnd / 604800.0), fmod(tEnd, 604800.0)); 1178 QString verdict; 1179 if (fabs(maxX) < ATT_MIN_PRED) { 1180 // the consensus is assumed to follow BNC's attitude model; a larger clock 1181 // change shows that the other ACs model this turn differently 1182 verdict = (rmsObs >= ATT_MIN_PRED) ? "clock change not explained by the yaw difference " 1183 "(the other ACs model this turn differently?)" 1184 : "predicted change too small to assess"; 1185 } 1186 else if (slope > 0.7 && slope < 1.3 && rmsRes < 0.5 * rmsObs) { 1187 verdict = "SSR yaw consistent with the clocks"; 1188 } 1189 else { 1190 verdict = "SSR yaw NOT consistent with the clocks"; 1191 } 1192 QString line = QString("attitude check %1 %2-%3: yaw difference to the model %4 deg, clock change vs. " 1193 "consensus %5 cm, predicted %6 cm (max %7 cm), slope %8, clock RMS %9 -> %10 cm: %11") 1194 .arg(prn).arg(QString::fromStdString(t1.timestr(0))).arg(QString::fromStdString(t2.timestr(0))) 1195 .arg(maxPsi * 180.0 / M_PI, 0, 'f', 0) 1196 .arg(yEnd * 100.0, 0, 'f', 1).arg(xEnd * 100.0, 0, 'f', 1).arg(maxX * 100.0, 0, 'f', 1) 1197 .arg(slope, 0, 'f', 2).arg(rmsObs * 100.0, 0, 'f', 1).arg(rmsRes * 100.0, 0, 'f', 1) 1198 .arg(verdict); 1199 if (!end.isEmpty()) { 1200 line += " (" + end + ")"; 1201 } 1202 _attEvents[staID][prn[0].toLatin1()] << line; 1001 1203 } 1002 1204 … … 1406 1608 } 1407 1609 } 1610 for (const QString& line : _attEvents.value(staID).value(sys)) { 1611 lines << line; 1612 } 1408 1613 addSats(QString("clock jumps > %1 m").arg(MAX_CLK_JUMP), jumps); 1409 1614 if (!maxRadialPrn.isEmpty()) { … … 1489 1694 _cmpStat.clear(); 1490 1695 _cmpEpochs.clear(); 1696 _attEvents.clear(); 1491 1697 for (QMap<QString, t_vtecStat>::iterator it = _vtecStat.begin(); it != _vtecStat.end(); ++it) { 1492 1698 t_vtecStat fresh; -
trunk/BNC/src/ssrQc.h
r11117 r11129 16 16 //////////////////////////////////////////////////////////////////////////// 17 17 class bncBiasSnx; 18 class bncAntex; 18 19 19 20 class t_ssrQc : public QObject { … … 171 172 QMap<QString, t_vtecStat> _vtecStat; // stream 172 173 bncTime _nextVtecCmp; 174 175 // Attitude check: for streams sending the yaw angle, the clock change of 176 // the stream relative to the consensus during a turn (yaw difference to 177 // BNC's attitude model changing by more than ATT_ONSET) is compared with 178 // the change predicted by the phase wind-up, lambda_IF * dpsi / 2pi 179 class t_yawLast { // latest SSR yaw angle of a stream and satellite 180 public: 181 bncTime time; 182 double yaw = 0.0; // [rad] 183 double rate = 0.0; // [rad/s] 184 }; 185 class t_attSample { 186 public: 187 double t; // [s] (tKey) 188 double dc; // clock difference to the consensus [m] 189 double p; // predicted clock difference, unwrapped [m] 190 double dpsi; // yaw difference model - stream, unwrapped [rad] 191 }; 192 class t_attTrack { // per stream and satellite 193 public: 194 bool active = false; 195 bool hasLast = false; 196 double lastDPsi = 0.0; 197 double lastT = 0.0; 198 QVector<t_attSample> hist; // recent samples before a turn 199 QVector<t_attSample> event; // samples of the current turn 200 double dc0 = 0.0, p0 = 0.0, dpsi0 = 0.0; // before the turn 201 }; 202 void attitudeSample(const QString& staID, const QString& prn, const bncTime& tt, 203 const ColumnVector& xx, const ColumnVector& vv, double dc); 204 void attitudeEvent(const QString& staID, const QString& prn, t_attTrack& tr, const QString& end); 205 bncAntex* _antex; // option ssrQcAntexFile 206 QMap<QString, QMap<QString, t_yawLast> > _yawLast; // stream, PRN 207 QMap<QString, QMap<QString, t_attTrack> > _attTrack; // stream, PRN 208 QMap<QString, double> _psiModel; // PRN: model yaw at _psiModelT 209 double _psiModelT = 0.0; 210 QMap<QString, QMap<char, QStringList> > _attEvents; // stream, system: report lines 173 211 }; 174 212
Note:
See TracChangeset
for help on using the changeset viewer.
