Changeset 11075 in ntrip


Ignore:
Timestamp:
Oct 5, 2026, 6:39:54 PM (6 hours ago)
Author:
stuerze
Message:

ssr qc

Location:
trunk/BNC
Files:
5 added
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/Example_Configs/31_SsrQC.bnc

    r11071 r11075  
    2323corrPath=
    2424corrPort=
    25 ssrQcFile=Output/SsrQC.txt
     25ssrQcPath=Output
     26ssrQcStreams=ALL
     27ssrQcRef=CONSENSUS
    2628ssrQcIntr=1 hour
    2729ephIntr=1 day
  • trunk/BNC/src/bnccore.cpp

    r11066 r11075  
    838838  delete _ssrQc; _ssrQc = 0;
    839839  bncSettings settings;
    840   QString fileName = settings.value("ssrQcFile").toString();
    841   if (fileName.isEmpty()) {
     840  QString dirName = settings.value("ssrQcPath").toString();
     841  if (dirName.isEmpty()) {
    842842    return;
    843843  }
    844   expandEnvVar(fileName);
     844  expandEnvVar(dirName);
     845  QStringList streams = settings.value("ssrQcStreams").toString().split(QRegularExpression("[ ,;]+"), Qt::SkipEmptyParts);
     846  if (streams.isEmpty()) {
     847    streams << "ALL";
     848  }
    845849  QStringList hlp = settings.value("ssrQcIntr").toString().split(' ', Qt::SkipEmptyParts);
    846850  double interval = 3600.0;
    … …  
    851855    }
    852856  }
    853   _ssrQc = new t_ssrQc(fileName, interval);
     857  _ssrQc = new t_ssrQc(dirName, interval, streams, settings.value("ssrQcRef").toString().trimmed());
    854858}
    855859
  • trunk/BNC/src/bnchelp.html

    r11072 r11075  
    147147    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.2 <a href="#corrint">Interval</a><br>
    148148    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.3 <a href="#corrport">Port</a><br>
    149     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#ssrqc">QC Logfile, QC Interval</a><br>
     149    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#ssrqc">QC Directory, QC Streams, QC Reference, QC Interval</a><br>
    150150    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#corrwait">Wait for Full Corr Epoch</a><br>
    151151    &nbsp; &nbsp; &nbsp; 2.9 <a href="#syncout"><b>Feed Engine</b></a><br>
    … …  
    22452245      The configuration reads a Broadcast Ephemeris stream and three Broadcast Correction streams in
    22462246      IGS-SSR format (SSRA00CNE1, SSRA00CHC1) and RTCM-SSR format (Galileo HAS, SSRA00EUH0; needs an account
    2247       at the Galileo HAS caster). An hourly QC report per stream and system is saved in the QC logfile
    2248       'SsrQC.txt'. The configuration can be applied to a raw file as well:
     2247      at the Galileo HAS caster). Hourly QC reports per system are saved in one file per stream in directory
     2248      'Output'. The configuration can be applied to a raw file as well:
    22492249      bnc --conf 31_SsrQC.bnc --file &lt;rawFile&gt; --key ssrQcIntr "5 min"
    22502250    </li>
    … …  
    50665066
    50675067  <p>
    5068   <h4 id="ssrqc">2.8.4 QC Logfile, QC Interval - optional</h4>
     5068  <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval - optional</h4>
    50695069  </p>
    50705070  <p>
    50715071    BNC can check the quality of all Broadcast Correction streams it decodes, in real time or when reading a raw file
    5072     ('--file'). Specify the full path to a 'QC Logfile' to activate this function, and select the 'QC Interval' for
    5073     the reports (default: 1 hour). At the end of each interval and at the end of the program, a report is written per
    5074     stream and system. Broadcast ephemerides must be received as well, e.g. via stream 'BCEP00BKG0'.
     5072    ('--file'). Specify a 'QC Directory' to activate this function. 'QC Streams' selects the streams to be checked:
     5073    'ALL' (default) for all decoded Broadcast Correction streams, or a comma separated list of stream names, e.g.
     5074    'SSRA00CNE1,SSRA00GMV0'. 'QC Reference' selects the reference of the stream comparison (see below). Select
     5075    the 'QC Interval' for the reports (default: 1 hour). One daily file
     5076    '&lt;stream&gt;_S_&lt;YYYYDDD&gt;0000_01D_QC.txt' is written per stream; at the end of each interval and at the
     5077    end of the program, a report per system is appended. Broadcast ephemerides must be received as well, e.g. via
     5078    stream 'BCEP00BKG0'.
    50755079  </p>
    50765080  <p>
    … …  
    51155119  </p>
    51165120  <pre>
    5117 Stream SSRA00GMV0
     5121=== SSR QC SSRA00GMV0 2026-10-02 11:40:00 - 2026-10-02 11:45:00 ===
    51185122  Sys Type        Epochs  Interval nom/med/max [s]  Gaps  Out-of-order  Latency med/max [s]  Sats
    51195123  G   ORBIT           47         1 /   5.0 /    5.0     0             0      11.0 /   12.0     32
    … …  
    51245128  G ionosphere-free combination B_IF of the reference signal biases: std. dev. 0.586 m, max |B_IF| 2.058 m (32 satellites)
    51255129  G largest orbit corrections: radial 1.06 m (G11), along/cross 2.05 m (G11)
     5130</pre>
     5131  <p>
     5132    <b>Stream comparison:</b> In real time no ground truth is available. Therefore every 30 seconds (with a delay of 60
     5133    seconds, so that the corrections of all streams have arrived), the orbits and clocks of each checked stream are
     5134    compared with a reference, selected with option 'QC Reference':
     5135  </p>
     5136  <ul>
     5137    <li>'CONSENSUS' (default): per satellite the median of all other streams (at least two). A single erroneous stream
     5138      does not affect it, and no combination is needed. The combined stream 'INTERNAL' is not part of the consensus,
     5139      as it is derived from the other streams.</li>
     5140    <li>the name of a stream, e.g. 'INTERNAL' for the result of the combination (see section 'Combination') or the
     5141      stream of a selected AC. Note that an AC contributing to the combination is compared partly with itself, and
     5142      that the combined orbits are those of the master AC (orbit differences of the master AC are zero).</li>
     5143  </ul>
     5144  <p>
     5145    For each stream the satellite positions and clocks are computed from the broadcast ephemeris and the latest
     5146    orbit and clock corrections at the comparison epoch.
     5147  </p>
     5148  <ul>
     5149    <li>Orbits are only compared between streams referring to the same reference point: Antenna Phase Center (stream
     5150      names 'SSRA...') or Center of Mass ('SSRC...'); the differences are given in radial, along-track and cross-track
     5151      direction.</li>
     5152    <li>Clocks are referred to the reference signals by adding the ionosphere-free linear combination B_IF of the code
     5153      biases of the reference signals of the stream (as in the combination). Clocks of satellites without the code
     5154      biases of both reference signals are not compared. A common clock offset per stream, system and epoch (the
     5155      clock datum) is removed.</li>
     5156    <li>The signal-in-space difference SIS = radial orbit difference - clock difference is the part of the orbit and
     5157      clock difference affecting the user's range.</li>
     5158  </ul>
     5159  <p>
     5160    The report gives per system the RMS of the differences and, for clock and SIS, the standard deviation (STD) about
     5161    the mean of each satellite. Constant differences per satellite (a large RMS with a small STD) are typical between
     5162    ACs, as each AC realizes the satellite clocks and code biases with its own datum; a user applies the clocks and
     5163    biases of one AC consistently. Satellites whose RMS exceeds three times the median of the system (at least 5 cm)
     5164    are listed as deviating, e.g.:
     5165  </p>
     5166  <pre>
     5167  G comparison with consensus of the other streams (27 satellites), RMS (STD about the satellite means) [m]:
     5168    clock 0.689 (STD 0.008), radial 0.020, along 0.036, cross 0.022, SIS (radial - clock) 0.687 (STD 0.008)
     5169  G satellites deviating from the reference, RMS SIS [m]: G02(0.38) G16(0.33) G20(0.91) G26(3.36)
    51265170</pre>
    51275171  <p>
  • trunk/BNC/src/bncmain.cpp

    r11066 r11075  
    210210        "   corrIntr {File interval [character string: 1 min|2 min|5 min|10 min|15 min|30 min|1 hour|1 day]}\n"
    211211        "   corrPort {Output port [integer number]}\n"
    212         "   ssrQcFile {SSR QC logfile, full path [character string]}\n"
    213         "   ssrQcIntr {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n"
     212        "   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"
     214        "   ssrQcRef     {Reference of the stream comparison [character string: CONSENSUS or a stream name, e.g. INTERNAL]}\n"
     215        "   ssrQcIntr    {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n"
    214216        "\n"
    215217        "Feed Engine Panel keys:\n"
  • trunk/BNC/src/bncsettings.cpp

    r11066 r11075  
    138138        setValue_p("corrIntr", "1 day");
    139139        setValue_p("corrPort", "");
    140         setValue_p("ssrQcFile", "");
     140        setValue_p("ssrQcPath", "");
     141        setValue_p("ssrQcStreams", "ALL");
     142        setValue_p("ssrQcRef", "CONSENSUS");
    141143        setValue_p("ssrQcIntr", "1 hour");
    142144        // Feed Engine
  • trunk/BNC/src/bncwindow.cpp

    r11066 r11075  
    298298  }
    299299  _corrPortLineEdit = new QLineEdit(settings.value("corrPort").toString());
    300   _ssrQcFileLineEdit = new QLineEdit(settings.value("ssrQcFile").toString());
     300  _ssrQcPathLineEdit = new QLineEdit(settings.value("ssrQcPath").toString());
     301  _ssrQcRefLineEdit = new QLineEdit(settings.value("ssrQcRef").toString().isEmpty() ? QString("CONSENSUS") : settings.value("ssrQcRef").toString());
     302  _ssrQcStreamsLineEdit = new QLineEdit(settings.value("ssrQcStreams").toString().isEmpty() ? QString("ALL") : settings.value("ssrQcStreams").toString());
    301303  _ssrQcIntrComboBox = new QComboBox();
    302304  _ssrQcIntrComboBox->setEditable(false);
    … …  
    309311    _ssrQcIntrComboBox->setCurrentIndex(_ssrQcIntrComboBox->findText("1 hour"));
    310312  }
    311   connect(_ssrQcFileLineEdit, SIGNAL(textChanged(const QString&)),
     313  connect(_ssrQcPathLineEdit, SIGNAL(textChanged(const QString&)),
    312314          this, SLOT(slotBncTextChanged()));
    313315
    … …  
    916918  cLayout->addWidget(new QLabel("Port"), 3, 0);
    917919  cLayout->addWidget(_corrPortLineEdit, 3, 1);
    918   cLayout->addWidget(new QLabel("QC Logfile"), 4, 0);
    919   cLayout->addWidget(_ssrQcFileLineEdit, 4, 1, 1, 30);
    920   cLayout->addWidget(new QLabel("QC Interval"), 5, 0);
    921   cLayout->addWidget(_ssrQcIntrComboBox, 5, 1);
    922   cLayout->addWidget(new QLabel(""), 6, 1);
     920  cLayout->addWidget(new QLabel("QC Directory"), 4, 0);
     921  cLayout->addWidget(_ssrQcPathLineEdit, 4, 1, 1, 30);
     922  cLayout->addWidget(new QLabel("QC Streams"), 5, 0);
     923  cLayout->addWidget(_ssrQcStreamsLineEdit, 5, 1, 1, 30);
     924  cLayout->addWidget(new QLabel("QC Reference"), 6, 0);
     925  cLayout->addWidget(_ssrQcRefLineEdit, 6, 1, 1, 30);
     926  cLayout->addWidget(new QLabel("QC Interval"), 7, 0);
     927  cLayout->addWidget(_ssrQcIntrComboBox, 7, 1);
     928  cLayout->addWidget(new QLabel(""), 8, 1);
    923929  cLayout->setRowStretch(7, 999);
    924930  cgroup->setLayout(cLayout);
    … …  
    15881594  _corrPathLineEdit->setWhatsThis(tr("<p>Specify a directory for saving Broadcast Ephemeris Correction files.</p><p>If the specified directory does not exist, BNC will not create the files. <i>[key: corrPath]</i></p>"));
    15891595  _corrIntrComboBox->setWhatsThis(tr("<p>Select the length of Broadcast Ephemeris Correction files. <i>[key: corrIntr]</i></p>"));
    1590   _ssrQcFileLineEdit->setWhatsThis(tr("<p>Specify the full path to a logfile for the quality control of all decoded Broadcast Correction streams: epochs, update intervals, gaps, latency, completeness, IODs, code biases of the reference signals, clock jumps and orbit correction size.</p><p>Default is an empty option field, meaning that no SSR QC is done. <i>[key: ssrQcFile]</i></p>"));
     1596  _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>"));
     1597  _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>"));
    15911599  _ssrQcIntrComboBox->setWhatsThis(tr("<p>Select the interval of the SSR QC reports. <i>[key: ssrQcIntr]</i></p>"));
    15921600  _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>"));
    … …  
    18631871  delete _corrPathLineEdit;
    18641872  delete _corrIntrComboBox;
    1865   delete _ssrQcFileLineEdit;
     1873  delete _ssrQcPathLineEdit;
     1874  delete _ssrQcStreamsLineEdit;
     1875  delete _ssrQcRefLineEdit;
    18661876  delete _ssrQcIntrComboBox;
    18671877  delete _corrPortLineEdit;
    … …  
    23912401  settings.setValue("corrPath", _corrPathLineEdit->text());
    23922402  settings.setValue("corrIntr", _corrIntrComboBox->currentText());
    2393   settings.setValue("ssrQcFile", _ssrQcFileLineEdit->text());
     2403  settings.setValue("ssrQcPath", _ssrQcPathLineEdit->text());
     2404  settings.setValue("ssrQcStreams", _ssrQcStreamsLineEdit->text());
     2405  settings.setValue("ssrQcRef", _ssrQcRefLineEdit->text());
    23942406  settings.setValue("ssrQcIntr", _ssrQcIntrComboBox->currentText());
    23952407  settings.setValue("corrPort", _corrPortLineEdit->text());
    … …  
    29272939    enableWidget(enable, _corrIntrComboBox);
    29282940  }
    2929   if (sender() == 0 || sender() == _ssrQcFileLineEdit) {
    2930     enableWidget(!_ssrQcFileLineEdit->text().isEmpty(), _ssrQcIntrComboBox);
     2941  if (sender() == 0 || sender() == _ssrQcPathLineEdit) {
     2942    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcIntrComboBox);
     2943    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcStreamsLineEdit);
     2944    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcRefLineEdit);
    29312945  }
    29322946
  • trunk/BNC/src/bncwindow.h

    r11066 r11075  
    151151    QLineEdit* _ephPathLineEdit;
    152152    QLineEdit* _corrPathLineEdit;
    153     QLineEdit* _ssrQcFileLineEdit;
     153    QLineEdit* _ssrQcPathLineEdit;
     154    QLineEdit* _ssrQcStreamsLineEdit;
     155    QLineEdit* _ssrQcRefLineEdit;
    154156    QLineEdit* _miscMountLineEdit;
    155157    QLineEdit* _miscPortLineEdit;
  • trunk/BNC/src/combination/bnccomb.h

    r11067 r11075  
    3737  bncComb(const bncComb&) = delete;
    3838  bncComb& operator=(const bncComb&) = delete;
     39  static bool exists() {return instance != 0;}
    3940  static void destruct() {
    4041    delete instance;
  • trunk/BNC/src/ssrQc.cpp

    r11073 r11075  
    4545#include "bncutils.h"
    4646#include "bncconst.h"
     47#include "bncsettings.h"
     48#include "combination/bnccomb.h"
    4749
    4850using namespace std;
    … …  
    5153  const double MAX_CLK_JUMP   = 0.5;  // [m] clock change beyond its rate between consecutive epochs
    5254  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  }
    5364  const char*  TYPE_NAME[]    = {"ORBIT", "CLOCK", "CODE_BIAS", "PHASE_BIAS"};
    5465
    … …  
    91102// Constructor
    92103////////////////////////////////////////////////////////////////////////////
    93 t_ssrQc::t_ssrQc(const QString& fileName, double interval) : _ephUser(true) {
    94   _fileName = fileName;
     104t_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  }
    95113  _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  }
    97121
    98122  connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));
    … …  
    105129  connect(BNC_CORE, SIGNAL(newPhaseBiases(QList<t_satPhaseBias>)),
    106130          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////////////////////////////////////////////////////////////////////////////
     142bool 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////////////////////////////////////////////////////////////////////////////
     148ofstream& 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];
    113169}
    114170
    … …  
    118174  QMutexLocker locker(&_mutex);
    119175  writeReport(true);
    120   delete _out;
     176  for (ofstream* out : _outs) {
     177    delete out;
     178  }
    121179}
    122180
    … …  
    144202////////////////////////////////////////////////////////////////////////////
    145203void t_ssrQc::checkInterval(const bncTime& time) {
    146   if (!_startTime.valid()) {
    147     _startTime = time;
    148   }
    149204  if (!_intervalStart.valid()) {
    150205    double sec = floor(time.gpssec() / _interval) * _interval;
    151206    _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;
    152216  }
    153217  if (time - _intervalStart >= _interval) {
    … …  
    165229void t_ssrQc::newEpoch(const string& staID, char sys, e_type type, const bncTime& time,
    166230                       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  }
    167238  t_epoStat& stat = _epoStat[key(staID, sys, type)];
    168239  if (updateInt < unsigned(ssrUpdateInt.size())) {
    … …  
    185256// Broadcast ephemeris the correction refers to
    186257////////////////////////////////////////////////////////////////////////////
    187 void t_ssrQc::checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time) {
     258void t_ssrQc::checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time,
     259                       const string& staID) {
    188260  // not before all broadcast ephemerides may have been received
    189   if (time - _startTime < EPH_WARMUP) {
     261  if (time - _staStart[QString::fromStdString(staID)] < EPH_WARMUP) {
    190262    return;
    191263  }
    … …  
    211283  QMutexLocker locker(&_mutex);
    212284  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;
    213289    QString prn = QString::fromStdString(corr._prn.toString());
    214290    checkInterval(corr._time);
    … …  
    216292    t_satStat& stat = _satStat[QString::fromStdString(corr._staID) + ' ' + prn];
    217293    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);
    219295    double radial = fabs(corr._xr[0]);
    220296    double alongCross = max(fabs(corr._xr[1]), fabs(corr._xr[2]));
    … …  
    229305  QMutexLocker locker(&_mutex);
    230306  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;
    231311    QString prn = QString::fromStdString(corr._prn.toString());
    232312    checkInterval(corr._time);
    … …  
    234314    t_satStat& stat = _satStat[QString::fromStdString(corr._staID) + ' ' + prn];
    235315    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);
    237317
    238318    // jump: change between consecutive epochs (same IOD) beyond the clock rate
    … …  
    260340  QMutexLocker locker(&_mutex);
    261341  for (const t_satCodeBias& bias : codeBiases) {
     342    bool sel = selected(bias._staID);
     343    if (!sel && bias._staID != _reference.toStdString()) continue;
    262344    char sys = bias._prn.system();
    263345    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    }
    266350    QString staSys = QString("%1 %2").arg(bias._staID.c_str()).arg(sys);
    267351    QMap<QString, double> values;
    … …  
    302386  QMutexLocker locker(&_mutex);
    303387  for (const t_satPhaseBias& bias : phaseBiases) {
     388    if (!selected(bias._staID)) continue;
    304389    checkInterval(bias._time);
    305390    newEpoch(bias._staID, bias._prn.system(), PBIAS, bias._time, bias._updateInt,
    … …  
    312397}
    313398
     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////////////////////////////////////////////////////////////////////////////
     403char 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////////////////////////////////////////////////////////////////////////////
     412void 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////////////////////////////////////////////////////////////////////////////
     433void 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
    314628// Write the report of the current interval and reset the statistics
    315629////////////////////////////////////////////////////////////////////////////
    316630void t_ssrQc::writeReport(bool final) {
    317   if (!_out || _epoStat.isEmpty()) {
     631  if (_epoStat.isEmpty()) {
    318632    return;
    319633  }
    320634  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;
    324635
    325636  // Healthy and current broadcast ephemerides per system
    … …  
    347658    QList<char> systems = itSta.value().values();
    348659    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"
    351669          << "  Latency med/max [s]  Sats" << endl;
    352670    for (char sys : systems) {
    … …  
    369687        double maxLat = stat.latencies.isEmpty() ? 0.0 :
    370688                        *std::max_element(stat.latencies.begin(), stat.latencies.end());
    371         *_out << "  " << sys << "   " << left << setw(10) << TYPE_NAME[type] << right
     689        out << "  " << sys << "   " << left << setw(10) << TYPE_NAME[type] << right
    372690              << setw(8) << stat.numEpo
    373691              << setw(10) << stat.nominal << " /" << setw(6) << setprecision(1) << fixed << medInt
    … …  
    484802                 .arg(maxRadial, 0, 'f', 2).arg(maxRadialPrn).arg(maxAlongCross, 0, 'f', 2).arg(maxAlongCrossPrn);
    485803      }
     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      }
    486868      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")
    494876                  .arg(QString::fromStdString(_intervalStart.datestr()))
    495877                  .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();
    497882
    498883  // reset, keeping the state needed across intervals
  • trunk/BNC/src/ssrQc.h

    r11073 r11075  
    1818
    1919 public:
    20   t_ssrQc(const QString& fileName, double interval);
     20  t_ssrQc(const QString& dirName, double interval, const QStringList& streams,
     21          const QString& reference);
    2122  ~t_ssrQc();
    2223
    … …  
    6970  void   newEpoch(const std::string& staID, char sys, e_type type, const bncTime& time,
    7071                  unsigned updateInt, const QString& prn);
    71   void   checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time);
     72  void   checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time,
     73                  const std::string& staID);
    7274  void   checkInterval(const bncTime& time);
    7375  void   writeReport(bool final);
    … …  
    7779
    7880  QMutex                    _mutex;
    79   QString                   _fileName;
    80   std::ofstream*            _out;
     81  bool           selected(const std::string& staID) const;
     82
     83  // Stream comparison: every CMP_SAMPL seconds, the orbits and clocks of each
     84  // stream are compared with a reference: the consensus (median) of all
     85  // other streams, or a selected stream (e.g. INTERNAL, the combination)
     86  class t_cmpStat {          // per stream and satellite
     87   public:
     88    t_cmpStat() : numClk(0), numOrb(0), numSis(0), clkNotReferred(0),
     89                  sClk(0.0), sR(0.0), sA(0.0), sC(0.0), sSis(0.0), mClk(0.0), mSis(0.0) {}
     90    unsigned numClk;
     91    unsigned numOrb;
     92    unsigned numSis;
     93    unsigned clkNotReferred;          // epochs without reference signal biases
     94    double   sClk, sR, sA, sC, sSis;  // sums of squares [m^2]
     95    double   mClk, mSis;              // sums [m]
     96  };
     97  class t_satCrd {           // satellite position, velocity, clock of a stream
     98   public:
     99    ColumnVector xx;   // [m]
     100    ColumnVector vv;   // [m/s]
     101    double       clk;  // [m], referred to the reference signals (+ B_IF) if clkOK
     102    bool         clkOK;
     103    char         refPoint; // 'A' APC, 'C' CoM, '?' unknown
     104  };
     105  void   storeHist(const t_orbCorr* orb, const t_clkCorr* clk);
     106  void   compareEpoch(const bncTime& tt);
     107  char   refPoint(const QString& staID) const;
     108  QString                                      _reference;   // "CONSENSUS" or stream name
     109  QString                                      _cmbMaster;   // mountpoint of the combination's first AC
     110  bncTime                                      _nextCmpEpoch;
     111  QMap<QString, QMap<QString, QMap<double, t_orbCorr> > > _orbHist; // stream, PRN (internal), time
     112  QMap<QString, QMap<QString, QMap<double, t_clkCorr> > > _clkHist;
     113  QMap<QString, QMap<QString, t_cmpStat> >     _cmpStat;     // stream, PRN
     114  QMap<QString, unsigned>                      _cmpEpochs;   // stream: compared epochs
     115  std::ofstream& streamOut(const QString& staID, const bncTime& time);
     116
     117  QString                   _dirName;
     118  QSet<QString>             _streams;   // selected streams, empty: all
     119  QMap<QString, std::ofstream*> _outs;  // per stream
     120  QMap<QString, QString>    _outNames;  // per stream
    81121  double                    _interval;
    82122  bncTime                   _intervalStart;
    83123  bncTime                   _lastTime;
    84   bncTime                   _startTime;  // first correction epoch
     124  QMap<QString, bncTime>    _staStart;   // first correction epoch per stream
     125  QMap<QString, bncTime>    _staLast;    // latest correction epoch per stream
    85126  bncEphUser                _ephUser;
    86127  QMap<QString, t_epoStat>  _epoStat;    // key "STA SYS TYPE"
  • trunk/BNC/tests/README.md

    r11069 r11075  
    7979  * `ssr_rtcm_SSRA00CNE0`, `ssr_rtcm_WHU1_OSBC`: RTCM 1059-1270; these test
    8080    whichever class `RTCM3Decoder.cpp` selects for non-4076 SSR.
    81 * `ssr_qc`: SSR QC report (`ssrQcFile`, 5 min interval) of the 7 AC streams of
     81* `ssr_qc`, `ssr_qc_select`: SSR QC (31_SsrQC, one file per stream, 5 min interval;
     82  all streams resp. `ssrQcStreams SSRA00CNE1,SSRA00GMV0`) of the 7 AC streams of
    8283  `data/combi_multi_261002_5min.raw`.
    8384
  • trunk/BNC/tests/cases/ssr_qc/case.json

    r11074 r11075  
    11{
    2   "description": "31_SsrQC replay: SSR QC report of the 7 AC streams of data/combi_multi_261002_5min.raw, 5 min report interval",
     2  "description": "31_SsrQC replay: SSR QC reports (one file per stream, ALL streams) of the 7 AC streams of data/combi_multi_261002_5min.raw, 5 min report interval",
    33  "config": "Example_Configs/31_SsrQC.bnc",
    44  "raw": "data/combi_multi_261002_5min.raw",
    … …  
    1111  ],
    1212  "outputs": [
    13     {
    14       "glob": "Output/SsrQC.txt",
    15       "name": "ssrqc.txt"
    16     }
     13    "Output/*_QC.txt"
    1714  ]
    1815}
Note: See TracChangeset for help on using the changeset viewer.