Changeset 11081 in ntrip


Ignore:
Timestamp:
Oct 8, 2026, 3:42:54 PM (66 minutes ago)
Author:
stuerze
Message:

SSR QC improved

Location:
trunk/BNC
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/src/bnchelp.html

    r11079 r11081  
    50835083    ('--file'). Specify a 'QC Directory' to activate this function. 'QC Streams' selects the streams to be checked:
    50845084    'ALL' (default) for all decoded Broadcast Correction streams, or a comma separated list of stream names, e.g.
    5085     'SSRA00CNE1,SSRA00GMV0'. 'QC Reference' selects the reference of the stream comparison (see below). Select
     5085    'SSRA00CNE1,SSRA00GMV0'. A stream name with a leading '!' excludes the stream from the QC and from the consensus,
     5086    e.g. 'ALL,!SIRGAS200001' for a stream referring to another reference frame than the ITRF. 'QC Reference' selects the reference of the stream comparison (see below). Select
    50865087    the 'QC Interval' for the reports (default: 1 hour). One daily file
    50875088    '<stream>_S_<YYYYDDD>0000_01D_QC.txt' is written per stream; at the end of each interval and at the
    … …  
    51465147  </p>
    51475148  <ul>
    5148     <li>'CONSENSUS' (default): per satellite the median of all other streams (at least two). A single erroneous stream
    5149       does not affect it, and no combination is needed. The combined stream 'INTERNAL' is not part of the consensus,
    5150       as it is derived from the other streams.</li>
     5149    <li>'CONSENSUS' (default): per satellite the median of the other analysis centers (at least two). All decoded
     5150      streams contribute, also those not selected under 'QC Streams' (except the excluded ones). An AC with several
     5151      streams (IGS-SSR and RTCM-SSR format, APC and CoM) has one vote, the median of its streams; the AC is derived
     5152      from the stream name (e.g. 'CNE' from 'SSRA00CNE1'). A stream is compared without the streams of its own AC.
     5153      Combination products ('INTERNAL' and the IGS combinations '...IGS.') do not vote, as they are derived from the
     5154      ACs, but are compared with the consensus. A single erroneous AC does not affect the consensus, and no
     5155      combination is needed.</li>
    51515156    <li>the name of a stream, e.g. 'INTERNAL' for the result of the combination (see section 'Combination') or the
    51525157      stream of a selected AC. Note that an AC contributing to the combination is compared partly with itself, and
  • trunk/BNC/src/bncmain.cpp

    r11075 r11081  
    211211        "   corrPort {Output port [integer number]}\n"
    212212        "   ssrQcPath    {Directory for SSR QC files, one per stream [character string]}\n"
    213         "   ssrQcStreams {Streams to check [character string: ALL or comma separated list of stream names]}\n"
     213        "   ssrQcStreams {Streams to check [character string: ALL or comma separated list of stream names; '!' excludes a stream, e.g. ALL,!SIRGAS200001]}\n"
    214214        "   ssrQcRef     {Reference of the stream comparison [character string: CONSENSUS or a stream name, e.g. INTERNAL]}\n"
    215215        "   ssrQcIntr    {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n"
  • trunk/BNC/src/bncwindow.cpp

    r11075 r11081  
    15961596  _ssrQcPathLineEdit->setWhatsThis(tr("<p>Specify a directory for the quality control of decoded Broadcast Correction streams: epochs, update intervals, gaps, latency, completeness, IODs, code and phase bias signals, code biases of the reference signals, clock jumps and orbit correction size. One daily file is written per stream.</p><p>Default is an empty option field, meaning that no SSR QC is done. <i>[key: ssrQcPath]</i></p>"));
    15971597  _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>"));
    1598   _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.</p><p>Default is 'ALL'. <i>[key: ssrQcStreams]</i></p>"));
     1598  _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>"));
    15991599  _ssrQcIntrComboBox->setWhatsThis(tr("<p>Select the interval of the SSR QC reports. <i>[key: ssrQcIntr]</i></p>"));
    16001600  _corrPortLineEdit->setWhatsThis(tr("<p>BNC can produce Broadcast Ephemeris Corrections on your local host through an IP port.</p><p>Specify a port number here to activate this function. <i>[key: corrPort]</i></p>"));
  • trunk/BNC/src/ssrQc.cpp

    r11079 r11081  
    107107  _reference = reference.isEmpty() ? QString("CONSENSUS") : reference;
    108108  _interval = interval > 0.0 ? interval : 3600.0;
     109  // "ALL" or a list of streams; streams with a leading '!' are excluded
     110  // (from the reports and from the consensus)
     111  bool all = false;
    109112  for (const QString& sta : streams) {
    110     if (sta.toUpper() == "ALL") {
    111       _streams.clear();
    112       break;
    113     }
    114     _streams.insert(sta);
     113    if (sta.startsWith('!')) {
     114      _excluded.insert(sta.mid(1));
     115    }
     116    else if (sta.toUpper() == "ALL") {
     117      all = true;
     118    }
     119    else {
     120      _streams.insert(sta);
     121    }
     122  }
     123  if (all) {
     124    _streams.clear();
    115125  }
    116126
    … …  
    136146////////////////////////////////////////////////////////////////////////////
    137147bool t_ssrQc::selected(const string& staID) const {
    138   return _streams.isEmpty() || _streams.contains(QString::fromStdString(staID));
     148  QString sta = QString::fromStdString(staID);
     149  return !_excluded.contains(sta) && (_streams.isEmpty() || _streams.contains(sta));
     150}
     151
     152// Analysis center of a stream: the 3 characters of the AC in mountpoints
     153// like SSRA00CNE1, otherwise the stream name itself
     154////////////////////////////////////////////////////////////////////////////
     155QString t_ssrQc::acOf(const QString& staID) {
     156  static const QRegularExpression re("^SSR[AC][0-9]{2}([A-Z0-9]{3})[0-9]$");
     157  QRegularExpressionMatch mm = re.match(staID);
     158  return mm.hasMatch() ? mm.captured(1) : staID;
     159}
     160
     161// Stream part of the consensus: not excluded, and not a combination product
     162// (INTERNAL, IGS combinations), as these are derived from the other streams
     163////////////////////////////////////////////////////////////////////////////
     164bool t_ssrQc::votes(const QString& staID) const {
     165  return !_excluded.contains(staID) && staID != "INTERNAL" && acOf(staID) != "IGS";
    139166}
    140167
    … …  
    278305  QMutexLocker locker(&_mutex);
    279306  for (const t_orbCorr& corr : orbCorrections) {
    280     if (selected(corr._staID) || corr._staID == _reference.toStdString()) {
     307    if (!_excluded.contains(QString::fromStdString(corr._staID)) || corr._staID == _reference.toStdString()) {
    281308      storeHist(&corr, 0);
    282309    }
    … …  
    300327  QMutexLocker locker(&_mutex);
    301328  for (const t_clkCorr& corr : clkCorrections) {
    302     if (selected(corr._staID) || corr._staID == _reference.toStdString()) {
     329    if (!_excluded.contains(QString::fromStdString(corr._staID)) || corr._staID == _reference.toStdString()) {
    303330      storeHist(0, &corr);
    304331    }
    … …  
    336363  for (const t_satCodeBias& bias : codeBiases) {
    337364    bool sel = selected(bias._staID);
    338     if (!sel && bias._staID != _reference.toStdString()) continue;
     365    if (_excluded.contains(QString::fromStdString(bias._staID)) && bias._staID != _reference.toStdString()) continue;
    339366    char sys = bias._prn.system();
    340367    QString prn = QString::fromStdString(bias._prn.toString());
    … …  
    507534    }
    508535
    509     // Clock datum: offset of each stream w.r.t. the per-satellite median of
    510     // all streams (or w.r.t. the reference stream)
     536    // Consensus: one value per AC (median of its streams), so that an AC
     537    // with several streams (formats, APC/CoM) has one vote; the streams of
     538    // the AC compared are left out (leave-one-AC-out)
     539    auto acClocks = [&](const QString& prn, const QMap<QString, double>& off, bool useOff) {
     540      QMap<QString, QVector<double> > perAC;
     541      for (const QString& oo : staList) {
     542        if (!votes(oo) || !crd[oo].contains(prn) || !crd[oo][prn].clkOK) continue;
     543        if (useOff && !off.contains(oo)) continue;
     544        perAC[acOf(oo)] << crd[oo][prn].clk - (useOff ? off[oo] : 0.0);
     545      }
     546      QMap<QString, double> res;
     547      for (const QString& ac : perAC.keys()) res[ac] = median(perAC[ac]);
     548      return res;
     549    };
     550
     551    // Clock datum: offset of each stream w.r.t. the per-satellite consensus
     552    // of all ACs (or w.r.t. the reference stream)
    511553    QMap<QString, double> offset;
     554    QMap<QString, double> noOff;
     555    QMap<QString, QMap<QString, double> > rawAC;  // PRN, AC
     556    if (_reference == "CONSENSUS") {
     557      for (const QString& prn : sats) rawAC[prn] = acClocks(prn, noOff, false);
     558    }
    512559    for (const QString& staID : staList) {
    513560      QVector<double> diffs;
    … …  
    515562        if (!crd[staID].contains(prn) || !crd[staID][prn].clkOK) continue;
    516563        if (_reference == "CONSENSUS") {
    517           QVector<double> all;
    518           for (const QString& oo : staList) {
    519             if (oo != "INTERNAL" && crd[oo].contains(prn) && crd[oo][prn].clkOK) all << crd[oo][prn].clk;
    520           }
    521           if (all.size() >= 3) diffs << crd[staID][prn].clk - median(all);
     564          if (rawAC[prn].size() >= 3) diffs << crd[staID][prn].clk - median(rawAC[prn].values().toVector());
    522565        }
    523566        else if (crd.value(_reference).contains(prn) && crd[_reference][prn].clkOK) {
    … …  
    528571        offset[staID] = median(diffs);
    529572      }
     573    }
     574    QMap<QString, QMap<QString, double> > alignedAC;  // PRN, AC
     575    if (_reference == "CONSENSUS") {
     576      for (const QString& prn : sats) alignedAC[prn] = acClocks(prn, offset, true);
    530577    }
    531578
    … …  
    535582        continue;
    536583      }
     584      const QString ownAC = acOf(staID);
    537585      QMap<QString, double> dClk;
    538586      QMap<QString, ColumnVector> dRac;
    … …  
    540588        if (!crd[staID].contains(prn)) continue;
    541589        const t_satCrd& sc = crd[staID][prn];
    542         QStringList refs;
     590        QVector<double> clks, xs[3];
    543591        if (_reference == "CONSENSUS") {
     592          QMapIterator<QString, double> itA(alignedAC[prn]);
     593          while (itA.hasNext()) {
     594            itA.next();
     595            if (itA.key() != ownAC) clks << itA.value();
     596          }
     597          // orbits: per AC the median of its streams with the same reference point
     598          QMap<QString, QVector<double> > perAC[3];
    544599          for (const QString& oo : staList) {
    545             // the combination (INTERNAL) is derived from the other streams
    546             if (oo != staID && oo != "INTERNAL" && crd[oo].contains(prn)) refs << oo;
    547           }
    548           if (refs.size() < 2) continue;
    549           refs.clear();
    550           for (const QString& oo : staList) {
    551             if (oo != staID && oo != "INTERNAL" && crd[oo].contains(prn)) refs << oo;
    552           }
     600            if (!votes(oo) || acOf(oo) == ownAC || !crd[oo].contains(prn)) continue;
     601            const t_satCrd& rc = crd[oo][prn];
     602            if (rc.refPoint != sc.refPoint || sc.refPoint == '?') continue;
     603            for (int ii = 0; ii < 3; ii++) perAC[ii][acOf(oo)] << rc.xx[ii];
     604          }
     605          for (const QString& ac : perAC[0].keys()) {
     606            for (int ii = 0; ii < 3; ii++) xs[ii] << median(perAC[ii][ac]);
     607          }
     608          if (clks.size() < 2) clks.clear();
     609          if (xs[0].size() < 2) for (int ii = 0; ii < 3; ii++) xs[ii].clear();
    553610        }
    554611        else {
    555612          if (!crd.value(_reference).contains(prn)) continue;
    556           refs << _reference;
    557         }
    558         QVector<double> clks, xs[3];
    559         for (const QString& oo : refs) {
    560           const t_satCrd& rc = crd[oo][prn];
    561           if (rc.clkOK && (oo == _reference || offset.contains(oo))) {
    562             clks << rc.clk - (oo == _reference ? 0.0 : offset[oo]);
    563           }
     613          const t_satCrd& rc = crd[_reference][prn];
     614          if (rc.clkOK) clks << rc.clk;
    564615          if (rc.refPoint == sc.refPoint && sc.refPoint != '?') {
    565616            for (int ii = 0; ii < 3; ii++) xs[ii] << rc.xx[ii];
    566617          }
    567618        }
    568         bool clkCmp = sc.clkOK && offset.contains(staID) &&
    569                       (_reference == "CONSENSUS" ? clks.size() >= 2 : clks.size() == 1);
    570         if (clkCmp) {
     619        if (sc.clkOK && offset.contains(staID) && !clks.isEmpty()) {
    571620          dClk[prn] = sc.clk - offset[staID] - median(clks);
    572621        }
    … …  
    574623          _cmpStat[staID][prn].clkNotReferred += 1;
    575624        }
    576         if (xs[0].size() == refs.size() || (xs[0].size() >= 2 && _reference == "CONSENSUS")) {
     625        if (!xs[0].isEmpty()) {
    577626          ColumnVector dx(3);
    578627          for (int ii = 0; ii < 3; ii++) dx[ii] = sc.xx[ii] - median(xs[ii]);
    … …  
    827876        }
    828877        if (nSat) {
    829           QString refText = (_reference == "CONSENSUS") ? QString("consensus of the other streams")
     878          QString refText = (_reference == "CONSENSUS") ? QString("consensus of the other ACs")
    830879                                                        : QString("stream %1").arg(_reference);
    831880          QStringList parts;
  • trunk/BNC/src/ssrQc.h

    r11079 r11081  
    8080  QMutex                    _mutex;
    8181  bool           selected(const std::string& staID) const;
     82  bool           votes(const QString& staID) const;
     83  static QString acOf(const QString& staID);
    8284
    8385  // Stream comparison: every CMP_SAMPL seconds, the orbits and clocks of each
    … …  
    116118  QString                   _dirName;
    117119  QSet<QString>             _streams;   // selected streams, empty: all
     120  QSet<QString>             _excluded;  // excluded streams ('!' in the selection)
    118121  QMap<QString, std::ofstream*> _outs;  // per stream
    119122  QMap<QString, QString>    _outNames;  // per stream
Note: See TracChangeset for help on using the changeset viewer.