Changeset 11075 in ntrip for trunk/BNC/src/ssrQc.cpp
- Timestamp:
- Oct 5, 2026, 6:39:54 PM (5 days ago)
- File:
-
- 1 edited
-
trunk/BNC/src/ssrQc.cpp (modified) (18 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/src/ssrQc.cpp
r11073 r11075 45 45 #include "bncutils.h" 46 46 #include "bncconst.h" 47 #include "bncsettings.h" 48 #include "combination/bnccomb.h" 47 49 48 50 using namespace std; … … 51 53 const double MAX_CLK_JUMP = 0.5; // [m] clock change beyond its rate between consecutive epochs 52 54 const double EPH_WARMUP = 120.0; // [s] no ephemeris checks at the start 55 const double CMP_SAMPL = 30.0; // [s] sampling of the stream comparison 56 const double CMP_DELAY = 60.0; // [s] epochs compared when data this much newer arrived 57 const double HIST_LENGTH = 300.0; // [s] corrections kept for the comparison 58 const double DEV_MIN = 0.05; // [m] deviating satellites: RMS above max(DEV_MIN, 59 const double DEV_FACTOR = 3.0; // DEV_FACTOR x median RMS of the system) 60 61 double tKey(const bncTime& tt) { 62 return tt.gpsw() * 604800.0 + tt.gpssec(); 63 } 53 64 const char* TYPE_NAME[] = {"ORBIT", "CLOCK", "CODE_BIAS", "PHASE_BIAS"}; 54 65 … … 91 102 // Constructor 92 103 //////////////////////////////////////////////////////////////////////////// 93 t_ssrQc::t_ssrQc(const QString& fileName, double interval) : _ephUser(true) { 94 _fileName = fileName; 104 t_ssrQc::t_ssrQc(const QString& dirName, double interval, const QStringList& streams, 105 const QString& reference) : _ephUser(true) { 106 _dirName = dirName; 107 _reference = reference.isEmpty() ? QString("CONSENSUS") : reference; 108 bncSettings settings; 109 QStringList cmbStreams = settings.value("cmbStreams").toStringList(); 110 if (!cmbStreams.isEmpty()) { 111 _cmbMaster = cmbStreams[0].split(' ', Qt::SkipEmptyParts).value(0); 112 } 95 113 _interval = interval > 0.0 ? interval : 3600.0; 96 _out = new ofstream(fileName.toLocal8Bit().data()); 114 for (const QString& sta : streams) { 115 if (sta.toUpper() == "ALL") { 116 _streams.clear(); 117 break; 118 } 119 _streams.insert(sta); 120 } 97 121 98 122 connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool))); … … 105 129 connect(BNC_CORE, SIGNAL(newPhaseBiases(QList<t_satPhaseBias>)), 106 130 this, SLOT(slotNewPhaseBiases(QList<t_satPhaseBias>)), Qt::DirectConnection); 107 108 *_out << "SSR QC of decoded correction streams, report interval " << _interval << " s" << endl 109 << "Checks: epochs and update intervals, gaps (interval > 2 x max(declared, median)), latency," 110 << " completeness w.r.t. healthy broadcast ephemerides," << endl 111 << "IOD matching, code biases of the reference signals, clock jumps > " << MAX_CLK_JUMP 112 << " m, largest orbit corrections" << endl; 131 // combined corrections (stream INTERNAL) 132 if (bncComb::exists()) { 133 connect(BNC_CMB, SIGNAL(newOrbCorrections(QList<t_orbCorr>)), 134 this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)), Qt::DirectConnection); 135 connect(BNC_CMB, SIGNAL(newClkCorrections(QList<t_clkCorr>)), 136 this, SLOT(slotNewClkCorrections(QList<t_clkCorr>)), Qt::DirectConnection); 137 } 138 } 139 140 // Stream selected for the QC 141 //////////////////////////////////////////////////////////////////////////// 142 bool t_ssrQc::selected(const string& staID) const { 143 return _streams.isEmpty() || _streams.contains(QString::fromStdString(staID)); 144 } 145 146 // Daily QC file of a stream: <staID>_S_<YYYYDDD>0000_01D_QC.txt 147 //////////////////////////////////////////////////////////////////////////// 148 ofstream& t_ssrQc::streamOut(const QString& staID, const bncTime& time) { 149 unsigned year, month, day; 150 time.civil_date(year, month, day); 151 QDate date(year, month, day); 152 QString name = QDir(_dirName).filePath(QString("%1_S_%2%3%4_01D_QC.txt").arg(staID) 153 .arg(year).arg(date.dayOfYear(), 3, 10, QChar('0')).arg("0000")); 154 if (_outNames.value(staID) != name) { 155 delete _outs.value(staID, 0); 156 bool exists = QFile::exists(name); 157 ofstream* out = new ofstream(name.toLocal8Bit().data(), ios_base::out | ios_base::app); 158 if (!exists) { 159 *out << "SSR QC of stream " << staID.toStdString() << ", report interval " << _interval << " s" << endl 160 << "Checks: epochs and update intervals, gaps (interval > 2 x max(declared, median)), latency," 161 << " completeness w.r.t. healthy broadcast ephemerides," << endl 162 << "IOD matching, code and phase bias signals, code biases of the reference signals, clock jumps > " 163 << MAX_CLK_JUMP << " m, largest orbit corrections" << endl; 164 } 165 _outs[staID] = out; 166 _outNames[staID] = name; 167 } 168 return *_outs[staID]; 113 169 } 114 170 … … 118 174 QMutexLocker locker(&_mutex); 119 175 writeReport(true); 120 delete _out; 176 for (ofstream* out : _outs) { 177 delete out; 178 } 121 179 } 122 180 … … 144 202 //////////////////////////////////////////////////////////////////////////// 145 203 void t_ssrQc::checkInterval(const bncTime& time) { 146 if (!_startTime.valid()) {147 _startTime = time;148 }149 204 if (!_intervalStart.valid()) { 150 205 double sec = floor(time.gpssec() / _interval) * _interval; 151 206 _intervalStart = bncTime(time.gpsw(), sec); 207 } 208 // stream comparison of the epochs already complete for all streams 209 if (!_nextCmpEpoch.valid()) { 210 double sec = ceil(time.gpssec() / CMP_SAMPL) * CMP_SAMPL; 211 _nextCmpEpoch = bncTime(time.gpsw(), sec); 212 } 213 while (time - _nextCmpEpoch >= CMP_DELAY) { 214 compareEpoch(_nextCmpEpoch); 215 _nextCmpEpoch = _nextCmpEpoch + CMP_SAMPL; 152 216 } 153 217 if (time - _intervalStart >= _interval) { … … 165 229 void t_ssrQc::newEpoch(const string& staID, char sys, e_type type, const bncTime& time, 166 230 unsigned updateInt, const QString& prn) { 231 QString sta = QString::fromStdString(staID); 232 if (!_staStart.contains(sta)) { 233 _staStart[sta] = time; 234 } 235 if (!_staLast.contains(sta) || _staLast[sta] < time) { 236 _staLast[sta] = time; 237 } 167 238 t_epoStat& stat = _epoStat[key(staID, sys, type)]; 168 239 if (updateInt < unsigned(ssrUpdateInt.size())) { … … 185 256 // Broadcast ephemeris the correction refers to 186 257 //////////////////////////////////////////////////////////////////////////// 187 void t_ssrQc::checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time) { 258 void t_ssrQc::checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time, 259 const string& staID) { 188 260 // not before all broadcast ephemerides may have been received 189 if (time - _sta rtTime< EPH_WARMUP) {261 if (time - _staStart[QString::fromStdString(staID)] < EPH_WARMUP) { 190 262 return; 191 263 } … … 211 283 QMutexLocker locker(&_mutex); 212 284 for (const t_orbCorr& corr : orbCorrections) { 285 if (selected(corr._staID) || corr._staID == _reference.toStdString()) { 286 storeHist(&corr, 0); 287 } 288 if (!selected(corr._staID)) continue; 213 289 QString prn = QString::fromStdString(corr._prn.toString()); 214 290 checkInterval(corr._time); … … 216 292 t_satStat& stat = _satStat[QString::fromStdString(corr._staID) + ' ' + prn]; 217 293 stat.numOrb += 1; 218 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time); 294 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time, corr._staID); 219 295 double radial = fabs(corr._xr[0]); 220 296 double alongCross = max(fabs(corr._xr[1]), fabs(corr._xr[2])); … … 229 305 QMutexLocker locker(&_mutex); 230 306 for (const t_clkCorr& corr : clkCorrections) { 307 if (selected(corr._staID) || corr._staID == _reference.toStdString()) { 308 storeHist(0, &corr); 309 } 310 if (!selected(corr._staID)) continue; 231 311 QString prn = QString::fromStdString(corr._prn.toString()); 232 312 checkInterval(corr._time); … … 234 314 t_satStat& stat = _satStat[QString::fromStdString(corr._staID) + ' ' + prn]; 235 315 stat.numClk += 1; 236 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time); 316 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time, corr._staID); 237 317 238 318 // jump: change between consecutive epochs (same IOD) beyond the clock rate … … 260 340 QMutexLocker locker(&_mutex); 261 341 for (const t_satCodeBias& bias : codeBiases) { 342 bool sel = selected(bias._staID); 343 if (!sel && bias._staID != _reference.toStdString()) continue; 262 344 char sys = bias._prn.system(); 263 345 QString prn = QString::fromStdString(bias._prn.toString()); 264 checkInterval(bias._time); 265 newEpoch(bias._staID, sys, CBIAS, bias._time, bias._updateInt, prn); 346 if (sel) { 347 checkInterval(bias._time); 348 newEpoch(bias._staID, sys, CBIAS, bias._time, bias._updateInt, prn); 349 } 266 350 QString staSys = QString("%1 %2").arg(bias._staID.c_str()).arg(sys); 267 351 QMap<QString, double> values; … … 302 386 QMutexLocker locker(&_mutex); 303 387 for (const t_satPhaseBias& bias : phaseBiases) { 388 if (!selected(bias._staID)) continue; 304 389 checkInterval(bias._time); 305 390 newEpoch(bias._staID, bias._prn.system(), PBIAS, bias._time, bias._updateInt, … … 312 397 } 313 398 399 // Reference point of the orbits of a stream: APC (SSRA...), CoM (SSRC...); 400 // the combined stream INTERNAL refers to that of its master AC (the first 401 // AC of the combination) 402 //////////////////////////////////////////////////////////////////////////// 403 char t_ssrQc::refPoint(const QString& staID) const { 404 QString sta = (staID == "INTERNAL") ? _cmbMaster : staID; 405 if (sta.startsWith("SSRA")) return 'A'; 406 if (sta.startsWith("SSRC")) return 'C'; 407 return '?'; 408 } 409 410 // Keep the recent orbit and clock corrections of a stream 411 //////////////////////////////////////////////////////////////////////////// 412 void t_ssrQc::storeHist(const t_orbCorr* orb, const t_clkCorr* clk) { 413 if (orb) { 414 QMap<double, t_orbCorr>& hist = _orbHist[QString::fromStdString(orb->_staID)] 415 [QString::fromStdString(orb->_prn.toInternalString())]; 416 hist[tKey(orb->_time)] = *orb; 417 while (!hist.isEmpty() && hist.firstKey() < tKey(orb->_time) - HIST_LENGTH) { 418 hist.erase(hist.begin()); 419 } 420 } 421 if (clk) { 422 QMap<double, t_clkCorr>& hist = _clkHist[QString::fromStdString(clk->_staID)] 423 [QString::fromStdString(clk->_prn.toInternalString())]; 424 hist[tKey(clk->_time)] = *clk; 425 while (!hist.isEmpty() && hist.firstKey() < tKey(clk->_time) - HIST_LENGTH) { 426 hist.erase(hist.begin()); 427 } 428 } 429 } 430 431 // Compare the orbits and clocks of all streams at epoch tt 432 //////////////////////////////////////////////////////////////////////////// 433 void t_ssrQc::compareEpoch(const bncTime& tt) { 434 double tk = tKey(tt); 435 436 // Satellite positions and clocks of all streams (latest corrections <= tt) 437 QMap<QString, QMap<QString, t_satCrd> > crd; // stream, PRN (external) 438 QMapIterator<QString, QMap<QString, QMap<double, t_clkCorr> > > itSta(_clkHist); 439 while (itSta.hasNext()) { 440 itSta.next(); 441 const QString& staID = itSta.key(); 442 QMapIterator<QString, QMap<double, t_clkCorr> > itPrn(itSta.value()); 443 while (itPrn.hasNext()) { 444 itPrn.next(); 445 const QString& prnInt = itPrn.key(); 446 QMap<double, t_clkCorr>::const_iterator itC = itPrn.value().upperBound(tk); 447 if (itC == itPrn.value().constBegin()) { 448 continue; 449 } 450 const t_clkCorr& clk = (--itC).value(); 451 const QMap<double, t_orbCorr>& orbHist = _orbHist[staID][prnInt]; 452 QMap<double, t_orbCorr>::const_iterator itO = orbHist.upperBound(tk); 453 if (itO == orbHist.constBegin()) { 454 continue; 455 } 456 const t_orbCorr& orb = (--itO).value(); 457 t_eph* eph = 0; 458 t_eph* ephLast = _ephUser.ephLast(prnInt); 459 t_eph* ephPrev = _ephUser.ephPrev(prnInt); 460 if (ephLast && ephLast->IOD() == orb._iod) eph = ephLast; 461 else if (ephPrev && ephPrev->IOD() == orb._iod) eph = ephPrev; 462 if (!eph || clk._iod != orb._iod) { 463 continue; 464 } 465 eph->setOrbCorr(&orb); 466 eph->setClkCorr(&clk); 467 ColumnVector xc(6), vv(3); 468 if (eph->getCrd(tt, xc, vv, true) != success) { 469 continue; 470 } 471 t_satCrd& sc = crd[staID][QString::fromStdString(clk._prn.toString())]; 472 sc.xx = xc.Rows(1,3); 473 sc.vv = vv; 474 sc.clk = xc(4) * t_CST::c; 475 sc.refPoint = refPoint(staID); 476 // clock referred to the reference signals (as in the combination): 477 // + B_IF of the stream's code biases; the combination (INTERNAL) 478 // refers to them already 479 sc.clkOK = (staID == "INTERNAL"); 480 QString satKey = staID + ' ' + QString::fromStdString(clk._prn.toString()); 481 if (!sc.clkOK && _satStat.contains(satKey) && _satStat[satKey].refBiasOK) { 482 sc.clk += _satStat[satKey].biasIF; 483 sc.clkOK = true; 484 } 485 } 486 } 487 488 // Satellites per system 489 QMap<char, QSet<QString> > satsSys; 490 QMapIterator<QString, QMap<QString, t_satCrd> > itCrd(crd); 491 while (itCrd.hasNext()) { 492 itCrd.next(); 493 for (const QString& prn : itCrd.value().keys()) { 494 satsSys[prn[0].toLatin1()].insert(prn); 495 } 496 } 497 498 QMapIterator<char, QSet<QString> > itSys(satsSys); 499 while (itSys.hasNext()) { 500 itSys.next(); 501 const QSet<QString>& sats = itSys.value(); 502 QStringList staList; 503 for (const QString& staID : crd.keys()) { 504 for (const QString& prn : sats) { 505 if (crd[staID].contains(prn)) { 506 staList << staID; 507 break; 508 } 509 } 510 } 511 512 // Clock datum: offset of each stream w.r.t. the per-satellite median of 513 // all streams (or w.r.t. the reference stream) 514 QMap<QString, double> offset; 515 for (const QString& staID : staList) { 516 QVector<double> diffs; 517 for (const QString& prn : sats) { 518 if (!crd[staID].contains(prn) || !crd[staID][prn].clkOK) continue; 519 if (_reference == "CONSENSUS") { 520 QVector<double> all; 521 for (const QString& oo : staList) { 522 if (oo != "INTERNAL" && crd[oo].contains(prn) && crd[oo][prn].clkOK) all << crd[oo][prn].clk; 523 } 524 if (all.size() >= 3) diffs << crd[staID][prn].clk - median(all); 525 } 526 else if (crd.value(_reference).contains(prn) && crd[_reference][prn].clkOK) { 527 diffs << crd[staID][prn].clk - crd[_reference][prn].clk; 528 } 529 } 530 if (!diffs.isEmpty()) { 531 offset[staID] = median(diffs); 532 } 533 } 534 535 // Differences of each selected stream to its reference 536 for (const QString& staID : staList) { 537 if (!selected(staID.toStdString()) || staID == _reference) { 538 continue; 539 } 540 QMap<QString, double> dClk; 541 QMap<QString, ColumnVector> dRac; 542 for (const QString& prn : sats) { 543 if (!crd[staID].contains(prn)) continue; 544 const t_satCrd& sc = crd[staID][prn]; 545 QStringList refs; 546 if (_reference == "CONSENSUS") { 547 for (const QString& oo : staList) { 548 // the combination (INTERNAL) is derived from the other streams 549 if (oo != staID && oo != "INTERNAL" && crd[oo].contains(prn)) refs << oo; 550 } 551 if (refs.size() < 2) continue; 552 refs.clear(); 553 for (const QString& oo : staList) { 554 if (oo != staID && oo != "INTERNAL" && crd[oo].contains(prn)) refs << oo; 555 } 556 } 557 else { 558 if (!crd.value(_reference).contains(prn)) continue; 559 refs << _reference; 560 } 561 QVector<double> clks, xs[3]; 562 for (const QString& oo : refs) { 563 const t_satCrd& rc = crd[oo][prn]; 564 if (rc.clkOK && (oo == _reference || offset.contains(oo))) { 565 clks << rc.clk - (oo == _reference ? 0.0 : offset[oo]); 566 } 567 if (rc.refPoint == sc.refPoint && sc.refPoint != '?') { 568 for (int ii = 0; ii < 3; ii++) xs[ii] << rc.xx[ii]; 569 } 570 } 571 bool clkCmp = sc.clkOK && offset.contains(staID) && 572 (_reference == "CONSENSUS" ? clks.size() >= 2 : clks.size() == 1); 573 if (clkCmp) { 574 dClk[prn] = sc.clk - offset[staID] - median(clks); 575 } 576 else if (!sc.clkOK) { 577 _cmpStat[staID][prn].clkNotReferred += 1; 578 } 579 if (xs[0].size() == refs.size() || (xs[0].size() >= 2 && _reference == "CONSENSUS")) { 580 ColumnVector dx(3); 581 for (int ii = 0; ii < 3; ii++) dx[ii] = sc.xx[ii] - median(xs[ii]); 582 ColumnVector er = sc.xx / sc.xx.NormFrobenius(); 583 ColumnVector ec = crossproduct(sc.xx, sc.vv); 584 ec /= ec.NormFrobenius(); 585 ColumnVector ea = crossproduct(ec, er); 586 ColumnVector rac(3); 587 rac[0] = DotProduct(dx, er); 588 rac[1] = DotProduct(dx, ea); 589 rac[2] = DotProduct(dx, ec); 590 dRac[prn] = rac; 591 } 592 } 593 if (dClk.isEmpty() && dRac.isEmpty()) { 594 continue; 595 } 596 // remaining common clock offset of this stream and epoch 597 double off = dClk.isEmpty() ? 0.0 : median(dClk.values().toVector()); 598 _cmpEpochs[staID] += 1; 599 QSet<QString> prns; 600 for (const QString& prn : dClk.keys()) prns.insert(prn); 601 for (const QString& prn : dRac.keys()) prns.insert(prn); 602 for (const QString& prn : prns) { 603 t_cmpStat& stat = _cmpStat[staID][prn]; 604 if (dRac.contains(prn)) { 605 const ColumnVector& rac = dRac[prn]; 606 stat.numOrb += 1; 607 stat.sR += rac[0] * rac[0]; 608 stat.sA += rac[1] * rac[1]; 609 stat.sC += rac[2] * rac[2]; 610 } 611 if (dClk.contains(prn)) { 612 double dc = dClk[prn] - off; 613 stat.numClk += 1; 614 stat.sClk += dc * dc; 615 stat.mClk += dc; 616 if (dRac.contains(prn)) { 617 double sis = dRac[prn][0] - dc; 618 stat.numSis += 1; 619 stat.sSis += sis * sis; 620 stat.mSis += sis; 621 } 622 } 623 } 624 } 625 } 626 } 627 314 628 // Write the report of the current interval and reset the statistics 315 629 //////////////////////////////////////////////////////////////////////////// 316 630 void t_ssrQc::writeReport(bool final) { 317 if ( !_out ||_epoStat.isEmpty()) {631 if (_epoStat.isEmpty()) { 318 632 return; 319 633 } 320 634 bncTime endTime = final ? _lastTime : _intervalStart + _interval; 321 *_out << endl << "=== SSR QC " << _intervalStart.datestr() << ' ' << _intervalStart.timestr(0)322 << " - " << endTime.datestr() << ' ' << endTime.timestr(0)323 << (final ? " (end of data)" : "") << " ===" << endl;324 635 325 636 // Healthy and current broadcast ephemerides per system … … 347 658 QList<char> systems = itSta.value().values(); 348 659 std::sort(systems.begin(), systems.end()); 349 *_out << endl << "Stream " << staID.toStdString() << endl 350 << " Sys Type Epochs Interval nom/med/max [s] Gaps Out-of-order" 660 ofstream& out = streamOut(staID, _intervalStart); 661 if (final) { 662 endTime = _staLast.value(staID, _lastTime); 663 } 664 out << endl << "=== SSR QC " << staID.toStdString() << ' ' 665 << _intervalStart.datestr() << ' ' << _intervalStart.timestr(0) 666 << " - " << endTime.datestr() << ' ' << endTime.timestr(0) 667 << (final ? " (end of data)" : "") << " ===" << endl 668 << " Sys Type Epochs Interval nom/med/max [s] Gaps Out-of-order" 351 669 << " Latency med/max [s] Sats" << endl; 352 670 for (char sys : systems) { … … 369 687 double maxLat = stat.latencies.isEmpty() ? 0.0 : 370 688 *std::max_element(stat.latencies.begin(), stat.latencies.end()); 371 *_out << " " << sys << " " << left << setw(10) << TYPE_NAME[type] << right689 out << " " << sys << " " << left << setw(10) << TYPE_NAME[type] << right 372 690 << setw(8) << stat.numEpo 373 691 << setw(10) << stat.nominal << " /" << setw(6) << setprecision(1) << fixed << medInt … … 484 802 .arg(maxRadial, 0, 'f', 2).arg(maxRadialPrn).arg(maxAlongCross, 0, 'f', 2).arg(maxAlongCrossPrn); 485 803 } 804 // stream comparison 805 if (_cmpStat.contains(staID)) { 806 unsigned nSat = 0, nClk = 0, nOrb = 0, nSis = 0; 807 double sClk = 0.0, sR = 0.0, sA = 0.0, sC = 0.0, sSis = 0.0; 808 double vClk = 0.0, vSis = 0.0; // sums of squares about the satellite means 809 QMap<QString, double> rmsSis, rmsClk, rmsOrb; 810 QMap<QString, unsigned> notReferred; 811 QMapIterator<QString, t_cmpStat> itC(_cmpStat[staID]); 812 while (itC.hasNext()) { 813 itC.next(); 814 if (itC.key()[0].toLatin1() != sys) continue; 815 const t_cmpStat& st = itC.value(); 816 if (st.numClk == 0 && st.numOrb == 0) { 817 if (st.clkNotReferred) notReferred[itC.key()] = 0; 818 continue; 819 } 820 ++nSat; 821 if (st.clkNotReferred) notReferred[itC.key()] = 0; 822 nClk += st.numClk; sClk += st.sClk; 823 nOrb += st.numOrb; sR += st.sR; sA += st.sA; sC += st.sC; 824 nSis += st.numSis; sSis += st.sSis; 825 if (st.numClk) vClk += st.sClk - st.mClk * st.mClk / st.numClk; 826 if (st.numSis) vSis += st.sSis - st.mSis * st.mSis / st.numSis; 827 if (st.numSis) rmsSis[itC.key()] = sqrt(st.sSis / st.numSis); 828 else if (st.numClk) rmsClk[itC.key()] = sqrt(st.sClk / st.numClk); 829 if (st.numOrb) rmsOrb[itC.key()] = sqrt((st.sR + st.sA + st.sC) / st.numOrb); 830 } 831 if (nSat) { 832 QString refText = (_reference == "CONSENSUS") ? QString("consensus of the other streams") 833 : QString("stream %1").arg(_reference); 834 QStringList parts; 835 if (nClk) parts << QString("clock %1 (STD %2)").arg(sqrt(sClk / nClk), 0, 'f', 3) 836 .arg(sqrt(max(0.0, vClk) / nClk), 0, 'f', 3); 837 if (nOrb) parts << QString("radial %1, along %2, cross %3") 838 .arg(sqrt(sR / nOrb), 0, 'f', 3).arg(sqrt(sA / nOrb), 0, 'f', 3) 839 .arg(sqrt(sC / nOrb), 0, 'f', 3); 840 if (nSis) parts << QString("SIS (radial - clock) %1 (STD %2)").arg(sqrt(sSis / nSis), 0, 'f', 3) 841 .arg(sqrt(max(0.0, vSis) / nSis), 0, 'f', 3); 842 lines << QString("comparison with %1 (%2 satellites), RMS (STD about the satellite means) [m]: %3") 843 .arg(refText).arg(nSat).arg(parts.join(", ")); 844 if (!nOrb) { 845 lines << "orbits not compared: no other stream with the same reference point (APC/CoM)"; 846 } 847 addSats("clocks not compared (code biases of the reference signals missing)", notReferred); 848 auto deviating = [&lines](const QString& what, const QMap<QString, double>& rms) { 849 if (rms.size() < 2) return; 850 double med = median(rms.values().toVector()); 851 QStringList dev; 852 QMapIterator<QString, double> itR(rms); 853 while (itR.hasNext()) { 854 itR.next(); 855 if (itR.value() > max(DEV_MIN, DEV_FACTOR * med)) { 856 dev << QString("%1(%2)").arg(itR.key()).arg(itR.value(), 0, 'f', 2); 857 } 858 } 859 if (!dev.isEmpty()) { 860 lines << QString("satellites deviating from the reference, RMS %1 [m]: %2").arg(what).arg(dev.join(" ")); 861 } 862 }; 863 deviating("SIS", rmsSis); 864 deviating("clock", rmsClk); 865 deviating("orbit (3D)", rmsOrb); 866 } 867 } 486 868 for (const QString& line : lines) { 487 *_out << " " << sys << " " << line.toStdString() << endl;488 } 489 } 490 }491 _out->flush();492 493 emit newMessage(QString("SSR QC: report %1 %2 written to % 3")869 out << " " << sys << " " << line.toStdString() << endl; 870 } 871 } 872 out.flush(); 873 } 874 875 emit newMessage(QString("SSR QC: report %1 %2 of %3 stream(s) written to %4") 494 876 .arg(QString::fromStdString(_intervalStart.datestr())) 495 877 .arg(QString::fromStdString(_intervalStart.timestr(0))) 496 .arg(_fileName).toLatin1(), false); 878 .arg(streams.size()).arg(_dirName).toLatin1(), false); 879 880 _cmpStat.clear(); 881 _cmpEpochs.clear(); 497 882 498 883 // reset, keeping the state needed across intervals
Note:
See TracChangeset
for help on using the changeset viewer.
