Changeset 11087 in ntrip for trunk/BNC/src/ssrQc.cpp


Ignore:
Timestamp:
Oct 16, 2026, 10:19:51 AM (3 weeks ago)
Author:
stuerze
Message:

Bias check is added in ssrQC, refernce signals are configurable now

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/src/ssrQc.cpp

    r11085 r11087  
    4242
    4343#include "ssrQc.h"
     44#include "ssrRefSig.h"
     45#include "combination/bncbiassnx.h"
    4446#include "bnccore.h"
    4547#include "bncutils.h"
    … …  
    126128
    127129  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  }
    128149  connect(BNC_CORE, SIGNAL(newOrbCorrections(QList<t_orbCorr>)),
    129150          this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)), Qt::DirectConnection);
    … …  
    183204           << " completeness w.r.t. healthy broadcast ephemerides," << endl
    184205           << "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;
    186208    }
    187209    _outs[staID]     = out;
    … …  
    199221    delete out;
    200222  }
     223  delete _bsxRef;
    201224}
    202225
    … …  
    207230}
    208231
    209 // Reference signals (IGS conventions, as in the combination)
     232// Reference signals of the clocks (as in the combination, option ssrRefSignals)
    210233////////////////////////////////////////////////////////////////////////////
    211234bool t_ssrQc::refSignals(char sys, QString& sig1, QString& sig2,
    212235                         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;
    221245}
    222246
    … …  
    376400      _codeSignals[staSys].insert(sig);
    377401      values[sig] = frqBias._value;
     402    }
     403    if (!values.isEmpty()) {
     404      _cbLast[QString::fromStdString(bias._staID)][prn] = values;
    378405    }
    379406    t_satStat& stat = _satStat[QString::fromStdString(bias._staID) + ' ' + prn];
    … …  
    425452    if (yawProvided) {
    426453      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////////////////////////////////////////////////////////////////////////////
     462bool 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////////////////////////////////////////////////////////////////////////////
     549void 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));
    427703    }
    428704  }
    … …  
    8651141        }
    8661142      }
     1143      compareCodeBiases(staID, sys, lines);
    8671144      if (_phaseSignals.contains(staSys)) {
    8681145        QStringList sigs = _phaseSignals[staSys].values();
Note: See TracChangeset for help on using the changeset viewer.