Changeset 11087 in ntrip for trunk/BNC/src/ssrQc.cpp
- Timestamp:
- Oct 16, 2026, 10:19:51 AM (3 weeks ago)
- File:
-
- 1 edited
-
trunk/BNC/src/ssrQc.cpp (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/src/ssrQc.cpp
r11085 r11087 42 42 43 43 #include "ssrQc.h" 44 #include "ssrRefSig.h" 45 #include "combination/bncbiassnx.h" 44 46 #include "bnccore.h" 45 47 #include "bncutils.h" … … 126 128 127 129 connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool))); 130 for (const QString& err : t_ssrRefSig::init()) { 131 emit newMessage(("SSR QC: " + err).toLatin1(), true); 132 } 133 // reference code biases from a Bias SINEX file (optional) 134 _bsxRef = 0; 135 bncSettings settings; 136 QString bsxFile = settings.value("ssrQcBiasFile").toString(); 137 expandEnvVar(bsxFile); 138 if (!bsxFile.isEmpty()) { 139 _bsxRef = new bncBiasSnx(true); 140 if (_bsxRef->readFile(bsxFile) == success) { 141 _bsxName = QFileInfo(bsxFile).fileName(); 142 } 143 else { 144 emit newMessage(("SSR QC: wrong Bias SINEX file " + bsxFile).toLatin1(), true); 145 delete _bsxRef; 146 _bsxRef = 0; 147 } 148 } 128 149 connect(BNC_CORE, SIGNAL(newOrbCorrections(QList<t_orbCorr>)), 129 150 this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)), Qt::DirectConnection); … … 183 204 << " completeness w.r.t. healthy broadcast ephemerides," << endl 184 205 << "IOD matching, code and phase bias signals, yaw angle, code biases of the reference signals, clock jumps > " 185 << MAX_CLK_JUMP << " m, largest orbit corrections" << endl; 206 << MAX_CLK_JUMP << " m, largest orbit corrections" << endl 207 << "Reference signals of the clocks: " << t_ssrRefSig::currentString().toStdString() << endl; 186 208 } 187 209 _outs[staID] = out; … … 199 221 delete out; 200 222 } 223 delete _bsxRef; 201 224 } 202 225 … … 207 230 } 208 231 209 // Reference signals (IGS conventions,as in the combination)232 // Reference signals of the clocks (as in the combination, option ssrRefSignals) 210 233 //////////////////////////////////////////////////////////////////////////// 211 234 bool t_ssrQc::refSignals(char sys, QString& sig1, QString& sig2, 212 235 t_frequency::type& frq1, t_frequency::type& frq2) { 213 switch (sys) { 214 case 'G': sig1 = "1W"; sig2 = "2W"; frq1 = t_frequency::G1; frq2 = t_frequency::G2; return true; 215 case 'R': sig1 = "1P"; sig2 = "2P"; frq1 = t_frequency::R1; frq2 = t_frequency::R2; return true; 216 case 'E': sig1 = "1C"; sig2 = "5Q"; frq1 = t_frequency::E1; frq2 = t_frequency::E5; return true; 217 case 'C': sig1 = "2I"; sig2 = "6I"; frq1 = t_frequency::C2; frq2 = t_frequency::C6; return true; 218 case 'J': sig1 = "1C"; sig2 = "2L"; frq1 = t_frequency::J1; frq2 = t_frequency::J2; return true; 219 } 220 return false; 236 t_ssrRefSig::t_pair pair; 237 if (!t_ssrRefSig::get(sys, pair)) { 238 return false; 239 } 240 sig1 = QString::fromStdString(pair.sig1()); 241 sig2 = QString::fromStdString(pair.sig2()); 242 frq1 = pair.frq1; 243 frq2 = pair.frq2; 244 return true; 221 245 } 222 246 … … 376 400 _codeSignals[staSys].insert(sig); 377 401 values[sig] = frqBias._value; 402 } 403 if (!values.isEmpty()) { 404 _cbLast[QString::fromStdString(bias._staID)][prn] = values; 378 405 } 379 406 t_satStat& stat = _satStat[QString::fromStdString(bias._staID) + ' ' + prn]; … … 425 452 if (yawProvided) { 426 453 stat.numYaw++; 454 } 455 } 456 } 457 458 // Reference DCBs (bias(base) - bias(signal)) of a satellite: median of the 459 // other ACs (one vote per AC, own AC left out, combinations not voting) or 460 // the selected reference stream, or from the Bias SINEX file 461 //////////////////////////////////////////////////////////////////////////// 462 bool t_ssrQc::refDcbs(const QString& staID, const QString& prn, const QString& base, bool fromFile, 463 QMap<QString, double>& dcbs, const QString& onlyAC, QMap<QString, int>* numVotes) { 464 dcbs.clear(); 465 if (numVotes) { 466 numVotes->clear(); 467 } 468 if (fromFile) { 469 if (!_bsxRef) { 470 return false; 471 } 472 char sys = prn[0].toLatin1(); 473 t_ssrRefSig::t_pair pair; 474 if (!t_ssrRefSig::get(sys, pair)) { 475 return false; 476 } 477 // the DCBs do not depend on the coefficients (as long as they add up to 1) 478 double f1 = t_CST::freq(pair.frq1, 0.0); 479 double f2 = t_CST::freq(pair.frq2, 0.0); 480 double aIF1 = f1 * f1 / (f1 * f1 - f2 * f2); 481 double aIF2 = -f2 * f2 / (f1 * f1 - f2 * f2); 482 t_satCodeBias satCodeBias; 483 _bsxRef->determineSsrSatCodeBiases(prn, aIF1, aIF2, satCodeBias); 484 QMap<QString, double> osb; 485 for (const t_frqCodeBias& bb : satCodeBias._bias) { 486 osb[QString::fromStdString(bb._rnxType2ch)] = bb._value; 487 } 488 if (!osb.contains(base)) { 489 return false; 490 } 491 for (auto it = osb.constBegin(); it != osb.constEnd(); ++it) { 492 if (it.key() != base) { 493 dcbs[it.key()] = osb[base] - it.value(); 494 } 495 } 496 return !dcbs.isEmpty(); 497 } 498 499 // per AC: DCBs of its streams 500 QMap<QString, QMap<QString, QVector<double> > > acDcbs; // AC, signal 501 for (auto itS = _cbLast.constBegin(); itS != _cbLast.constEnd(); ++itS) { 502 const QString& sta = itS.key(); 503 if (sta == staID) { 504 continue; 505 } 506 if (_reference == "CONSENSUS") { 507 if (!votes(sta) || acOf(sta) == acOf(staID) || (!onlyAC.isEmpty() && acOf(sta) != onlyAC)) { 508 continue; 509 } 510 } 511 else if (sta != _reference) { 512 continue; 513 } 514 if (!itS.value().contains(prn)) { 515 continue; 516 } 517 const QMap<QString, double>& bb = itS.value()[prn]; 518 if (!bb.contains(base)) { 519 continue; 520 } 521 for (auto it = bb.constBegin(); it != bb.constEnd(); ++it) { 522 if (it.key() != base) { 523 acDcbs[acOf(sta)][it.key()].append(bb[base] - it.value()); 524 } 525 } 526 } 527 QMap<QString, QVector<double> > votesPerSig; 528 for (auto itA = acDcbs.constBegin(); itA != acDcbs.constEnd(); ++itA) { 529 for (auto it = itA.value().constBegin(); it != itA.value().constEnd(); ++it) { 530 votesPerSig[it.key()].append(median(it.value())); 531 } 532 } 533 for (auto it = votesPerSig.constBegin(); it != votesPerSig.constEnd(); ++it) { 534 dcbs[it.key()] = median(it.value()); 535 if (numVotes) { 536 (*numVotes)[it.key()] = it.value().size(); 537 } 538 } 539 return !dcbs.isEmpty(); 540 } 541 542 // Code bias check: the DCBs of the stream (relative to the first reference 543 // signal, otherwise to another common signal) compared with a reference; 544 // DCBs do not depend on the clock datum. A sign opposite to the reference 545 // for most DCBs indicates the SINEX instead of the SSR sign convention. 546 // References: the Bias SINEX file, the selected reference stream, or each 547 // other AC separately (a median of two ACs cannot decide which one is wrong) 548 //////////////////////////////////////////////////////////////////////////// 549 void t_ssrQc::compareCodeBiases(const QString& staID, char sys, QStringList& lines) { 550 const double MIN_DCB = 0.3; // [m] DCBs used for the sign check 551 const double MIN_DEV = 0.5; // [m] deviating DCBs: > max(MIN_DEV, DEV_FACTOR x median) 552 const int MIN_VOTE = 3; // ACs needed for a consensus DCB (deviating satellites) 553 if (!_cbLast.contains(staID)) { 554 return; 555 } 556 t_ssrRefSig::t_pair pair; 557 if (!t_ssrRefSig::get(sys, pair)) { 558 return; 559 } 560 const QString ref1 = QString::fromStdString(pair.sig1()); 561 562 class t_cmp { 563 public: 564 unsigned numSat = 0, numSign = 0, numOpp = 0; 565 double sumSqr = 0.0; 566 QVector<double> absDiffs; 567 QMap<QString, QPair<QString, double> > maxDiff; // PRN: signal, difference 568 QSet<QString> bases; 569 bool opposite() const {return numSign >= 5 && numOpp > numSign / 2;} 570 double rms() const {return absDiffs.isEmpty() ? 0.0 : sqrt(sumSqr / absDiffs.size());} 571 }; 572 // mode 0: file, 1: reference stream or median of the other ACs, 2: one other AC 573 auto compare = [&](int mode, const QString& onlyAC) { 574 t_cmp cc; 575 for (auto itP = _cbLast[staID].constBegin(); itP != _cbLast[staID].constEnd(); ++itP) { 576 const QString& prn = itP.key(); 577 if (prn[0].toLatin1() != sys) { 578 continue; 579 } 580 const QMap<QString, double>& bb = itP.value(); 581 QStringList candidates = QStringList() << ref1 << bb.keys(); 582 QMap<QString, double> dcbs; 583 QMap<QString, int> numVotes; 584 QString base; 585 for (const QString& cand : candidates) { 586 if (bb.contains(cand) && refDcbs(staID, prn, cand, mode == 0, dcbs, onlyAC, &numVotes)) { 587 base = cand; 588 break; 589 } 590 } 591 if (base.isEmpty()) { 592 continue; 593 } 594 bool used = false; 595 for (auto it = dcbs.constBegin(); it != dcbs.constEnd(); ++it) { 596 if (!bb.contains(it.key())) { 597 continue; 598 } 599 if (mode == 1 && _reference == "CONSENSUS" && numVotes.value(it.key()) < MIN_VOTE) { 600 continue; 601 } 602 double dcb = bb[base] - bb[it.key()]; 603 double diff = dcb - it.value(); 604 used = true; 605 cc.sumSqr += diff * diff; 606 cc.absDiffs.append(fabs(diff)); 607 if (fabs(diff) > fabs(cc.maxDiff.value(prn).second)) { 608 cc.maxDiff[prn] = qMakePair("C" + it.key(), diff); 609 } 610 if (fabs(it.value()) >= MIN_DCB) { 611 cc.numSign++; 612 if (dcb * it.value() < 0.0) { 613 cc.numOpp++; 614 } 615 } 616 } 617 if (used) { 618 cc.numSat++; 619 cc.bases.insert("C" + base); 620 } 621 } 622 return cc; 623 }; 624 auto baseStr = [](const t_cmp& cc) { 625 QStringList ll = cc.bases.values(); 626 ll.sort(); 627 return ll.join("/"); 628 }; 629 auto deviating = [&](const t_cmp& cc, const QString& refName) { 630 if (cc.absDiffs.isEmpty() || cc.opposite()) { 631 return; 632 } 633 double limit = std::max(MIN_DEV, DEV_FACTOR * median(cc.absDiffs)); 634 QStringList devs; 635 for (auto it = cc.maxDiff.constBegin(); it != cc.maxDiff.constEnd(); ++it) { 636 if (fabs(it.value().second) > limit) { 637 devs << QString("%1(%2 %3)").arg(it.key()).arg(it.value().first).arg(it.value().second, 0, 'f', 2); 638 } 639 } 640 if (!devs.isEmpty()) { 641 lines << QString("satellites with DCBs deviating from %1 [m]: %2").arg(refName).arg(devs.join(" ")); 642 } 643 }; 644 645 // Bias SINEX file or the selected reference stream 646 for (int mode = 0; mode < 2; mode++) { 647 if ((mode == 0 && !_bsxRef) || (mode == 1 && _reference == "CONSENSUS")) { 648 continue; 649 } 650 QString refName = (mode == 0) ? QString("Bias SINEX file %1").arg(_bsxName) : _reference; 651 t_cmp cc = compare(mode, QString()); 652 if (cc.absDiffs.isEmpty()) { 653 continue; 654 } 655 lines << QString("code biases compared with %1 (DCBs to %2, %3 satellites, %4 DCBs): RMS of the differences " 656 "%5 m, opposite sign for %6 of %7 DCBs (|DCB| >= %8 m)") 657 .arg(refName).arg(baseStr(cc)).arg(cc.numSat).arg(cc.absDiffs.size()) 658 .arg(cc.rms(), 0, 'f', 3).arg(cc.numOpp).arg(cc.numSign).arg(MIN_DCB, 0, 'f', 1); 659 if (cc.opposite()) { 660 lines << QString("code biases with the opposite sign of %1 for %2 of %3 DCBs - " 661 "SINEX sign convention instead of the SSR one?").arg(refName).arg(cc.numOpp).arg(cc.numSign); 662 } 663 deviating(cc, refName); 664 } 665 666 // Each other AC separately, and the median of the other ACs for deviating satellites 667 if (_reference == "CONSENSUS") { 668 QSet<QString> acs; 669 for (auto itS = _cbLast.constBegin(); itS != _cbLast.constEnd(); ++itS) { 670 if (itS.key() != staID && votes(itS.key()) && acOf(itS.key()) != acOf(staID)) { 671 acs.insert(acOf(itS.key())); 672 } 673 } 674 QStringList acList = acs.values(); 675 acList.sort(); 676 QStringList items; 677 QStringList oppACs; 678 int numACs = 0; 679 QSet<QString> bases; 680 for (const QString& ac : acList) { 681 t_cmp cc = compare(2, ac); 682 if (cc.absDiffs.isEmpty()) { 683 continue; 684 } 685 numACs++; 686 bases.unite(cc.bases); 687 items << QString("%1 RMS %2 m, opposite sign %3/%4").arg(ac).arg(cc.rms(), 0, 'f', 2) 688 .arg(cc.numOpp).arg(cc.numSign); 689 if (cc.opposite()) { 690 oppACs << ac; 691 } 692 } 693 if (numACs > 0) { 694 QStringList ll = bases.values(); 695 ll.sort(); 696 lines << QString("code biases compared with the other ACs (DCBs to %1): %2").arg(ll.join("/")).arg(items.join("; ")); 697 if (numACs >= 2 && oppACs.size() > numACs / 2) { 698 lines << QString("code biases with the opposite sign of %1 of %2 other ACs (%3) - " 699 "SINEX sign convention instead of the SSR one?") 700 .arg(oppACs.size()).arg(numACs).arg(oppACs.join(" ")); 701 } 702 deviating(compare(1, QString()), QString("the median of the other ACs (at least %1 ACs)").arg(MIN_VOTE)); 427 703 } 428 704 } … … 865 1141 } 866 1142 } 1143 compareCodeBiases(staID, sys, lines); 867 1144 if (_phaseSignals.contains(staSys)) { 868 1145 QStringList sigs = _phaseSignals[staSys].values();
Note:
See TracChangeset
for help on using the changeset viewer.
