Changeset 11129 in ntrip for trunk/BNC/src/ssrQc.cpp
- Timestamp:
- Oct 28, 2026, 9:31:18 AM (37 hours ago)
- File:
-
- 1 edited
-
trunk/BNC/src/ssrQc.cpp (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
-
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;
Note:
See TracChangeset
for help on using the changeset viewer.
