Changeset 11087 in ntrip


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

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

Location:
trunk/BNC
Files:
22 edited

Legend:

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

    r11085 r11087  
    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 Directory, QC Streams, QC Reference, 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, QC Bias File</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>
    … …  
    274274    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.14.1.6 <a href=#combisatsys>Satellite
    275275      Systems</a><br>
     276    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.14.1.7 <a href=#combirefsig>Reference
     277      Signals</a><br>
    276278    &nbsp; &nbsp; &nbsp; 2.15 <a href=#upclk><b>Upload Corrections</b></a><br>
    277279    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.15.1 <a href=#upadd>Add, Delete Row</a><br>
    … …  
    50885090
    50895091  <p>
    5090   <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval - optional</h4>
     5092  <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File - optional</h4>
    50915093  </p>
    50925094  <p>
    … …  
    50965098    'SSRA00CNE1,SSRA00GMV0'. A stream name with a leading '!' excludes the stream from the QC and from the consensus,
    50975099    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
    5098     the 'QC Interval' for the reports (default: 1 hour). One daily file
     5100    the 'QC Interval' for the reports (default: 1 hour). A 'QC Bias File' (Bias SINEX, OSB or DSB, e.g. a CAS DCB or
     5101    a GFZ OSB product) is an optional reference for the code bias check (see below). One daily file
    50995102    '&lt;stream&gt;_S_&lt;YYYYDDD&gt;0000_01D_QC.txt' is written per stream; at the end of each interval and at the
    51005103    end of the program, a report per system is appended. Broadcast ephemerides must be received as well, e.g. via
    … …  
    51265129      broadcast ephemeris, or to a satellite without broadcast ephemeris (number of corrections in brackets; not
    51275130      checked during the first 2 minutes, while the broadcast ephemerides are received),</li>
    5128     <li>the code bias signals provided, satellites without code biases of the reference signals (GPS C1W/C2W, GLONASS
    5129       C1P/C2P, Galileo C1C/C5Q, BDS C2I/C6I, QZSS C1C/C2L), and the standard deviation and maximum of the
     5131    <li>the code bias signals provided, satellites without code biases of the reference signals (default GPS C1W/C2W,
     5132      GLONASS C1P/C2P, Galileo C1C/C5Q, BDS C2I/C6I, QZSS C1C/C2L, SBAS C1C/C5Q; as in the combination, see option
     5133      'Reference Signals' in <a href=#combirefsig>section 2.14.1.7</a>), and the standard deviation and maximum of the
    51305134      ionosphere-free combination B_IF of the reference signal biases. A B_IF varying between the satellites is
    51315135      legitimate if the clocks are estimated with other signals, but must then be consistent with the clocks
    51325136      (see section 'Combination'),</li>
     5137    <li>the code bias check: the DCBs of the stream, i.e. the differences of its code biases to the first reference
     5138      signal (or to another common signal, if the stream lacks it), are compared with
     5139      <ul>
     5140        <li>each other AC separately (one vote per AC, own AC left out, combinations not voting), with the RMS of
     5141          the differences and the number of DCBs with opposite sign (only DCBs of at least 0.3 m are used for the
     5142          sign). A stream is reported to use the opposite sign if it has the opposite sign for most DCBs to more than
     5143          half of at least two other ACs - with only two ACs, it cannot be decided which one is wrong. Satellites
     5144          with DCBs deviating from the median of at least 3 other ACs are listed,</li>
     5145        <li>the selected 'QC Reference' stream instead, if it is not 'CONSENSUS',</li>
     5146        <li>the 'QC Bias File', if specified.</li>
     5147      </ul>
     5148      DCBs do not depend on the clock datum, so the check also works for streams whose clocks are estimated with
     5149      other signals. A sign opposite to the reference for most DCBs indicates code biases in the sign convention of
     5150      Bias SINEX (biases to be subtracted) instead of SSR (biases to be added). Such a stream passes the B_IF check
     5151      above (B_IF is zero for both signs), but a PPP user applying its biases to other than the reference signals
     5152      gets errors of twice the biases, e.g.
     5153      <pre>
     5154  G code biases compared with the other ACs (DCBs to C1W): BKG RMS 2.17 m, opposite sign 48/49; CNE RMS 2.44 m, opposite sign 98/101
     5155  G code biases with the opposite sign of 2 of 2 other ACs (BKG CNE) - SINEX sign convention instead of the SSR one?
     5156  G code biases compared with Bias SINEX file CAS1MGXRAP_01D_01D_DCB.BSX (DCBs to C1W, 32 satellites, 141 DCBs): RMS of the differences 2.401 m, opposite sign for 106 of 107 DCBs (|DCB| &gt;= 0.3 m)
     5157</pre>
     5158      The latest code biases of each stream are used,</li>
    51335159    <li>the phase bias signals provided,</li>
    51345160    <li>the availability of the satellite yaw angle with the phase biases: 'not provided', or the number of
    … …  
    80668092  </p>
    80678093  <p>
    8068     The combination is done system-wise for the following reference signals as specified in the IGS Real-Time Committee:
     8094    The combination is done system-wise for the following reference signals as specified in the IGS Real-Time Committee
     8095    (default, they can be changed per system with the option 'Reference Signals', see
     8096    <a href=#combirefsig>section 2.14.1.7</a>):
    80698097  <p>
    80708098  <table>
    … …  
    84788506  <p>
    84798507    Specify for each satellite system whether the clock corrections shall be combined.</p>
     8508
     8509  <p>
     8510  <h4 id="combirefsig">2.14.1.7 Reference Signals - optional</h4>
     8511  </p>
     8512  <p>
     8513    Specify the reference signals of the satellite clocks per GNSS as '&lt;system&gt;:&lt;band&gt;&lt;attribute&gt;&amp;&lt;band&gt;&lt;attribute&gt;',
     8514    separated by blanks, e.g. 'C:1P&amp;5P J:1C&amp;5Q'. The clocks refer to the ionosphere-free linear combination of
     8515    the code signals of these two signals. Systems not specified keep the default of the IGS Real-Time Committee:
     8516  </p>
     8517  <pre>
     8518G:1W&amp;2W R:1P&amp;2P E:1C&amp;5Q C:2I&amp;6I J:1C&amp;2L S:1C&amp;5Q
     8519</pre>
     8520  <p>
     8521    The reference signals are used
     8522  </p>
     8523  <ul>
     8524    <li>by the combination, to refer the AC clocks to the reference signals with the code biases of each AC (B_IF, see
     8525      above); an AC without the code biases of both reference signals of a satellite is excluded from the clock datum,
     8526    </li>
     8527    <li>by the combination, to derive the code biases of the combined clocks from a Bias SINEX file ('BSX File'): the
     8528      ionosphere-free linear combination of the two reference signal biases is set to zero and all other biases are
     8529      derived from the DCBs. With the default reference signals, the DCBs of the IGS conventions are used as before;
     8530      with other reference signals, all DCBs of the satellite in the file are used, and the DCB of the reference
     8531      signals themselves may also be derived via other signals,</li>
     8532    <li>by the SSR QC (see section 'Broadcast Corrections'), for the code biases of the reference signals, B_IF and the
     8533      clock comparison. The reference signals used are written to the header of each SSR QC report.</li>
     8534  </ul>
     8535  <p>
     8536    Changing the reference signals is meant for a coordinated switch of the IGS Real-Time convention, e.g. for BDS from
     8537    C2I/C6I to C1P/C5P (B1C/B2a) following the IGS products, or for QZSS to an L1/L5 pair because the QZSS Block III
     8538    satellites no longer transmit L2. All ACs of a combination should then provide the code biases of the new reference
     8539    signals. A switch changes the combined clocks of the affected satellites by the ionosphere-free combination of the
     8540    DCBs between the old and the new reference signals.
     8541  </p>
     8542  <p>
     8543    Invalid entries (unknown system or band, wrong format) are reported in the logfile and ignored, i.e. the system
     8544    keeps its default. Non-default reference signals are written to the logfile when the combination starts.
     8545  </p>
     8546  <p>
     8547    Default is an empty option field, meaning the default reference signals. <i>[key: ssrRefSignals]</i>
     8548  </p>
    84808549
    84818550  <p>
  • trunk/BNC/src/bncmain.cpp

    r11081 r11087  
    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"
     216        "   ssrQcBiasFile {Bias SINEX file (OSB or DSB) as reference for the SSR QC code bias check, full path [character string]}\n"
    216217        "\n"
    217218        "Feed Engine Panel keys:\n"
    … …  
    314315        "   cmbMethodFilter    {Combination approach [character string: Single-Epoch|Filter]}\n"
    315316        "   cmbBsxFile         {SINEX Bias file, full path [char string]}\n"
     317        "   ssrRefSignals      {Reference signals of the clocks per GNSS, also used by the SSR QC [character string,\n"
     318        "                       example: C:1P&5P J:1C&5Q; empty: G:1W&2W R:1P&2P E:1C&5Q C:2I&6I J:1C&2L S:1C&5Q]}\n"
    316319        "   cmbMaxres          {Clock outlier residuum threshold in meters [floating-point number]}\n"
    317320        "   cmbMaxdisplacement {Maximal orbit displacement from the mean of corrections for a satellite [floating-point number]}\n"
  • trunk/BNC/src/bncsettings.cpp

    r11075 r11087  
    142142        setValue_p("ssrQcRef", "CONSENSUS");
    143143        setValue_p("ssrQcIntr", "1 hour");
     144        setValue_p("ssrQcBiasFile", "");
     145        setValue_p("ssrRefSignals", "");
    144146        // Feed Engine
    145147        setValue_p("outPort", "");
  • trunk/BNC/src/bncwindow.cpp

    r11081 r11087  
    301301  _ssrQcRefLineEdit = new QLineEdit(settings.value("ssrQcRef").toString().isEmpty() ? QString("CONSENSUS") : settings.value("ssrQcRef").toString());
    302302  _ssrQcStreamsLineEdit = new QLineEdit(settings.value("ssrQcStreams").toString().isEmpty() ? QString("ALL") : settings.value("ssrQcStreams").toString());
     303  _ssrQcBiasFileChooser = new qtFileChooser(0, qtFileChooser::File);
     304  _ssrQcBiasFileChooser->setFileName(settings.value("ssrQcBiasFile").toString());
    303305  _ssrQcIntrComboBox = new QComboBox();
    304306  _ssrQcIntrComboBox->setEditable(false);
    … …  
    571573  _cmbBsxFile = new qtFileChooser(0, qtFileChooser::File);
    572574  _cmbBsxFile->setFileName(settings.value("cmbBsxFile").toString());
     575
     576  _cmbRefSigLineEdit = new QLineEdit(settings.value("ssrRefSignals").toString());
     577  _cmbRefSigLineEdit->setPlaceholderText("default");
    573578
    574579  // Upload Results
    … …  
    926931  cLayout->addWidget(new QLabel("QC Interval"), 7, 0);
    927932  cLayout->addWidget(_ssrQcIntrComboBox, 7, 1);
    928   cLayout->addWidget(new QLabel(""), 8, 1);
    929   cLayout->setRowStretch(7, 999);
     933  cLayout->addWidget(new QLabel("QC Bias File"), 8, 0);
     934  cLayout->addWidget(_ssrQcBiasFileChooser, 8, 1, 1, 30);
     935  cLayout->addWidget(new QLabel(""), 9, 1);
     936  cLayout->setRowStretch(8, 999);
    930937  cgroup->setLayout(cLayout);
    931938
    … …  
    14181425  cmbLayout->addWidget(new QLabel("Sampling"), 7, 10, Qt::AlignLeft);
    14191426  cmbLayout->addWidget(_cmbSamplComboBox, 7, 11, Qt::AlignRight);
     1427  cmbLayout->addWidget(new QLabel("Reference signals"), 8, 10, Qt::AlignLeft);
     1428  cmbLayout->addWidget(_cmbRefSigLineEdit, 8, 11, 1, 5);
    14201429
    14211430
    14221431  cmbLayout->addWidget(new QLabel("GNSS"), 0, 14, Qt::AlignLeft);
    1423   cmbLayout->addWidget(new QLabel("GPS (C1W/C2W)"), 1, 14);
     1432  cmbLayout->addWidget(new QLabel("GPS"), 1, 14);
    14241433  cmbLayout->addWidget(_cmbGpsCheckBox, 1, 15);
    14251434
    1426   cmbLayout->addWidget(new QLabel("GLONASS (C1P/C2P)"), 2, 14);
     1435  cmbLayout->addWidget(new QLabel("GLONASS"), 2, 14);
    14271436  cmbLayout->addWidget(_cmbGloCheckBox, 2, 15);
    14281437
    1429   cmbLayout->addWidget(new QLabel("Galileo (C1C/C5Q)"), 3, 14);
     1438  cmbLayout->addWidget(new QLabel("Galileo"), 3, 14);
    14301439  cmbLayout->addWidget(_cmbGalCheckBox, 3, 15);
    14311440
    1432   cmbLayout->addWidget(new QLabel("Beidou (C2I/C6I)"), 4, 14);
     1441  cmbLayout->addWidget(new QLabel("Beidou"), 4, 14);
    14331442  cmbLayout->addWidget(_cmbBdsCheckBox, 4, 15);
    14341443
    1435   cmbLayout->addWidget(new QLabel("QZSS (C1C/C2L)"), 5, 14);
     1444  cmbLayout->addWidget(new QLabel("QZSS"), 5, 14);
    14361445  cmbLayout->addWidget(_cmbQzssCheckBox, 5, 15);
    14371446
    1438   cmbLayout->addWidget(new QLabel("SBAS (C1C/C5Q)"), 6, 14);
     1447  cmbLayout->addWidget(new QLabel("SBAS"), 6, 14);
    14391448  cmbLayout->addWidget(_cmbSbasCheckBox, 6, 15);
    14401449
    … …  
    15971606  _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>"));
    15981607  _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>"));
     1608  _ssrQcBiasFileChooser->setWhatsThis(tr("<p>Specify a Bias SINEX file (OSB or DSB, e.g. a CAS DCB or a GFZ OSB product) as reference for the code bias check of the SSR QC: the DCBs of each stream are compared with the DCBs of this file, in addition to the comparison with the other ACs. A sign opposite to the reference for most DCBs indicates a stream using the SINEX instead of the SSR sign convention.</p><p>Default is an empty option field, meaning the code biases are compared with the other ACs only. <i>[key: ssrQcBiasFile]</i></p>"));
    15991609  _ssrQcIntrComboBox->setWhatsThis(tr("<p>Select the interval of the SSR QC reports. <i>[key: ssrQcIntr]</i></p>"));
    16001610  _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>"));
    … …  
    17451755  _cmbSbasCheckBox->setWhatsThis(tr("<p>SBAS clock corrections shall be combined; SBAS Broadcast ephemeris and corrections are required. <i>[key: cmbSbas]</i></p>"));
    17461756  _cmbNavicCheckBox->setWhatsThis(tr("<p>NavIC clock corrections shall be combined; NavIC Broadcast ephemeris and corrections are required. <i>[key: cmbNavic]</i></p>"));
     1757  _cmbRefSigLineEdit->setWhatsThis(tr("<p>Specify the reference signals of the satellite clocks per GNSS: the clocks refer to the ionosphere-free combination of the code signals of these two signals. They are used by the combination to refer the AC clocks with their code biases and to derive the code biases from a Bias SINEX file, and by the SSR QC.</p><p>Systems not specified keep the default 'G:1W&2W R:1P&2P E:1C&5Q C:2I&6I J:1C&2L S:1C&5Q' (IGS conventions). Example: 'C:1P&5P J:1C&5Q'.</p><p>Default is an empty option field, meaning the default reference signals. <i>[key: ssrRefSignals]</i></p>"));
    17471758  _cmbBsxFile->setWhatsThis(tr("<p> Specify a Bias SINEX File that will be used to add satellite code biases to the combined clocks. <i>[key: cmbBsxFile]</i></p>"));
    17481759
    … …  
    18751886  delete _ssrQcRefLineEdit;
    18761887  delete _ssrQcIntrComboBox;
     1888  delete _ssrQcBiasFileChooser;
    18771889  delete _corrPortLineEdit;
    18781890  delete _outPortLineEdit;
    … …  
    19181930  delete _cmbNavicCheckBox;
    19191931  delete _cmbBsxFile;
     1932  delete _cmbRefSigLineEdit;
    19201933  delete _uploadSamplRtcmEphCorrComboBox;
    19211934  _uploadEphTable->deleteLater();
    … …  
    24052418  settings.setValue("ssrQcRef", _ssrQcRefLineEdit->text());
    24062419  settings.setValue("ssrQcIntr", _ssrQcIntrComboBox->currentText());
     2420  settings.setValue("ssrQcBiasFile", _ssrQcBiasFileChooser->fileName());
    24072421  settings.setValue("corrPort", _corrPortLineEdit->text());
    24082422  // Feed Engine
    … …  
    24712485  settings.setValue("cmbNavic", _cmbNavicCheckBox->checkState());
    24722486  settings.setValue("cmbBsxFile", _cmbBsxFile->fileName());
     2487  settings.setValue("ssrRefSignals", _cmbRefSigLineEdit->text());
    24732488
    24742489  // Upload Corrections
    … …  
    29432958    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcStreamsLineEdit);
    29442959    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcRefLineEdit);
     2960    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcBiasFileChooser);
    29452961  }
    29462962
  • trunk/BNC/src/bncwindow.h

    r11075 r11087  
    154154    QLineEdit* _ssrQcStreamsLineEdit;
    155155    QLineEdit* _ssrQcRefLineEdit;
     156    qtFileChooser* _ssrQcBiasFileChooser;
    156157    QLineEdit* _miscMountLineEdit;
    157158    QLineEdit* _miscPortLineEdit;
    … …  
    241242    QCheckBox* _cmbNavicCheckBox;
    242243    qtFileChooser* _cmbBsxFile;
     244    QLineEdit* _cmbRefSigLineEdit;
    243245
    244246    QTableWidget* _uploadTable;
  • trunk/BNC/src/combination/bncbiassnx.cpp

    r11023 r11087  
    4040
    4141#include "bncbiassnx.h"
     42#include "ssrRefSig.h"
    4243
    4344#include <iostream>
    … …  
    4849// Constructor
    4950////////////////////////////////////////////////////////////////////////////
    50 bncBiasSnx::bncBiasSnx() {
     51bncBiasSnx::bncBiasSnx(bool allGnss) {
    5152  bncSettings settings;
     53  if (allGnss) {
     54    for (char sys : QString("GRECJSI").toLatin1()) {
     55      _useGnss[sys] = true;
     56    }
     57    return;
     58  }
    5259  _useGnss['G'] = (Qt::CheckState(settings.value("cmbGps").toInt()) == Qt::Checked) ? true : false;
    5360  _useGnss['R'] = (Qt::CheckState(settings.value("cmbGlo").toInt()) == Qt::Checked) ? true : false;
    … …  
    307314}
    308315
    309 //
     316// DCBs (OSB_A - OSB_B) used to derive the code biases from the default
     317// reference signals (see t_ssrRefSig), in the order of evaluation
     318////////////////////////////////////////////////////////////////////////////
     319static const QVector<QPair<QString, QString> >& defaultDcbs(char sys) {
     320  static QMap<char, QVector<QPair<QString, QString> > > dcbs;
     321  static QMutex mutex;
     322  QMutexLocker locker(&mutex);
     323  if (dcbs.isEmpty()) {
     324    auto add = [](QVector<QPair<QString, QString> >& vv, const char* list) {
     325      for (const QString& dcb : QString(list).split(' ', Qt::SkipEmptyParts)) {
     326        vv.append(qMakePair(dcb.section('-', 0, 0), dcb.section('-', 1, 1)));
     327      }
     328    };
     329    add(dcbs['G'], "1C-1W 1C-5Q 1C-5X 1C-2W 2C-2W 2W-2S 2W-2L 2W-2X");
     330    add(dcbs['R'], "1C-1P 2C-2P");
     331    add(dcbs['E'], "1C-6C 1C-7Q 1C-8Q");
     332    add(dcbs['C'], "2I-7I 1X-6I 1X-7Z 1X-8X 1X-5X 1P-6I 1P-5P 1D-6I 1D-5D");
     333    add(dcbs['J'], "1C-1X 1X-2X 1C-5X");
     334  }
     335  static const QVector<QPair<QString, QString> > none;
     336  return dcbs.contains(sys) ? dcbs[sys] : none;
     337}
     338
     339// Satellite code biases (OSB) from the DCBs, referred to the reference
     340// signals of the clocks: aIF1 * OSB_ref1 + aIF2 * OSB_ref2 = 0
    310341////////////////////////////////////////////////////////////////////////////
    311342void bncBiasSnx::determineSsrSatCodeBiases(QString prn, double aIF1, double aIF2,
    312343                                           t_satCodeBias& satCodeBias) {
    313   std::string rnxType2ch;
    314   double value;
    315   if (_snxSatCodeBiasMap.contains(prn)) {
    316     char sys = prn.at(0).toLatin1();
    317     QMap<QString, double>& cb = _snxSatCodeBiasMap[prn]->_biasMap;
    318     switch (sys) {
    319       case 'G':
    320         if  (cb.contains("C1W-C2W")) {
    321           double dcb_1W2W = cb.find("C1W-C2W").value();
    322           rnxType2ch = "1W";
    323           value =  aIF2 * dcb_1W2W;
    324           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    325           rnxType2ch = "2W";
    326           value = -aIF1 * dcb_1W2W;
    327           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    328           if   (cb.contains("C1C-C1W")) {
    329             double dcb_1C1W = cb.find("C1C-C1W").value();
    330             rnxType2ch = "1C";
    331             value = dcb_1C1W + aIF2 * dcb_1W2W;
    332             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    333             if  (cb.contains("C1C-C5Q")) {
    334               double dcb_1C5Q = cb.find("C1C-C5Q").value();
    335               rnxType2ch = "5Q";
    336               value = dcb_1C1W + aIF2 * dcb_1W2W - dcb_1C5Q;
    337               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    338             }
    339             if  (cb.contains("C1C-C5X")) {
    340               double dcb_1C5X = cb.find("C1C-C5X").value();
    341               rnxType2ch = "5X";
    342               value = dcb_1C1W + aIF2 * dcb_1W2W - dcb_1C5X;
    343               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    344             }
    345           }
    346           else {
    347             if  (cb.contains("C1C-C2W")) {
    348               double dcb_1C2W = cb.find("C1C-C2W").value();
    349               rnxType2ch = "1C";
    350               value = dcb_1C2W - aIF1 * dcb_1W2W;
    351               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    352             }
    353           }
    354           if   (cb.contains("C2C-C2W")) {
    355             double dcb_2C2W = cb.find("C2C-C2W").value();
    356             rnxType2ch = "2C";
    357             value = dcb_2C2W - aIF1 * dcb_1W2W;
    358             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    359           }
    360           if   (cb.contains("C2W-C2S")) {
    361             double dcb_2W2S = cb.find("C2W-C2S").value();
    362             rnxType2ch = "2S";
    363             value = -aIF1 * dcb_1W2W - dcb_2W2S;
    364             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    365           }
    366           if   (cb.contains("C2W-C2L")) {
    367             double dcb_2W2L = cb.find("C2W-C2L").value();
    368             rnxType2ch = "2L";
    369             value = -aIF1 * dcb_1W2W - dcb_2W2L;
    370             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    371           }
    372           if   (cb.contains("C2W-C2X")) {
    373             double dcb_2W2X = cb.find("C2W-C2X").value();
    374             rnxType2ch = "2X";
    375             value = -aIF1 * dcb_1W2W - dcb_2W2X;
    376             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    377           }
    378         }
    379         break;
    380       case 'R':
    381         if  (cb.contains("C1P-C2P")) {
    382           double dcb_1P2P = cb.find("C1P-C2P").value();
    383           rnxType2ch = "1P";
    384           value =  aIF2 * dcb_1P2P;
    385           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    386           rnxType2ch = "2P";
    387           value = -aIF1 * dcb_1P2P;
    388           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    389           if   (cb.contains("C1C-C1P")) {
    390             double dcb_1C1P = cb.find("C1C-C1P").value();
    391             rnxType2ch = "1C";
    392             value = dcb_1C1P + aIF2 * dcb_1P2P;
    393             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    394           }
    395           if   (cb.contains("C2C-C2P")) {
    396             double dcb_2C2P = cb.find("C2C-C2P").value();
    397             rnxType2ch = "2C";
    398             value = dcb_2C2P - aIF1 * dcb_1P2P;
    399             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    400           }
    401         }
    402         break;
    403       case 'E':
    404         if  (cb.contains("C1C-C5Q")) {
    405           double dcb_1C5Q = cb.find("C1C-C5Q").value();
    406           rnxType2ch = "5Q";
    407           value = -aIF1 * dcb_1C5Q;
    408           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    409           rnxType2ch = "1C";
    410           value =  aIF2 * dcb_1C5Q;
    411           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    412           if  (cb.contains("C1C-C6C")) {
    413             double dcb_1C6C = cb.find("C1C-C6C").value();
    414             rnxType2ch = "6C";
    415             value = aIF2 * dcb_1C5Q - dcb_1C6C;
    416             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    417           }
    418           if  (cb.contains("C1C-C7Q")) {
    419             double dcb_1C7Q = cb.find("C1C-C7Q").value();
    420             rnxType2ch = "7Q";
    421             value = aIF2 * dcb_1C5Q - dcb_1C7Q;
    422             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    423           }
    424           if  (cb.contains("C1C-C8Q")) {
    425             double dcb_1C8Q = cb.find("C1C-C8Q").value();
    426             rnxType2ch = "8Q";
    427             value = aIF2 * dcb_1C5Q - dcb_1C8Q;
    428             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    429           }
    430         }
    431         break;
    432       case 'C':
    433         if  (cb.contains("C2I-C6I")) {
    434           double dcb_2I6I = cb.find("C2I-C6I").value();
    435           rnxType2ch = "2I";
    436           value =  aIF2 * dcb_2I6I;
    437           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    438           rnxType2ch = "6I";
    439           value = -aIF1 * dcb_2I6I;
    440           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    441           if (cb.contains("C2I-C7I")) {
    442             double dcb_2I7I = cb.find("C2I-C7I").value();
    443             rnxType2ch = "7I";
    444             value = aIF2 * dcb_2I6I - dcb_2I7I;
    445             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    446           }
    447           if (cb.contains("C1X-C6I")) {
    448             double dcb_1X6I = cb.find("C1X-C6I").value();
    449             rnxType2ch = "1X";
    450             value = dcb_1X6I - aIF1 * dcb_2I6I;
    451             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    452             if (cb.contains("C1X-C7Z")) {
    453               double dcb_1X7Z = cb.find("C1X-C7Z").value();
    454               rnxType2ch = "7Z";
    455               value = dcb_1X6I - aIF1 * dcb_2I6I - dcb_1X7Z;
    456               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    457             }
    458             if (cb.contains("C1X-C8X")) {
    459               double dcb_1X8X = cb.find("C1X-C8X").value();
    460               rnxType2ch = "8X";
    461               value = dcb_1X6I - aIF1 * dcb_2I6I - dcb_1X8X;
    462               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    463             }
    464             if (cb.contains("C1X-C5X")) {
    465               double dcb_1X5X = cb.find("C1X-C5X").value();
    466               rnxType2ch = "5X";
    467               value = dcb_1X6I - aIF1 * dcb_2I6I - dcb_1X5X;
    468               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    469             }
    470           }
    471           if (cb.contains("C1P-C6I")) {
    472             double dcb_1P6I = cb.find("C1P-C6I").value();
    473             rnxType2ch = "1P";
    474             value = dcb_1P6I - aIF1 * dcb_2I6I;
    475             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    476             if (cb.contains("C1P-C5P")) {
    477               double dcb_1P5P = cb.find("C1P-C5P").value();
    478               rnxType2ch = "5P";
    479               value = dcb_1P6I - aIF1 * dcb_2I6I - dcb_1P5P;
    480               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    481             }
    482           }
    483           if (cb.contains("C1D-C6I")) {
    484             double dcb_1D6I = cb.find("C1D-C6I").value();
    485             rnxType2ch = "1D";
    486             value = dcb_1D6I - aIF1 * dcb_2I6I;
    487             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    488             if (cb.contains("C1D-C5D")) {
    489               double dcb_1D5D = cb.find("C1D-C5D").value();
    490               rnxType2ch = "5D";
    491               value = dcb_1D6I - aIF1 * dcb_2I6I - dcb_1D5D;
    492               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    493             }
    494           }
    495         }
    496         break;
    497       case 'J':
    498         if (cb.contains("C1C-C2L")) {
    499           double dcb_1C2L = cb.find("C1C-C2L").value();
    500           rnxType2ch = "1C";
    501           value =  aIF2 * dcb_1C2L;
    502           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    503           rnxType2ch = "2L";
    504           value = -aIF1 * dcb_1C2L;
    505           setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    506           if (cb.contains("C1C-C1X")) {
    507             double dcb_1C1X = cb.find("C1C-C1X").value();
    508             rnxType2ch = "1X";
    509             value =  aIF2 * dcb_1C2L - dcb_1C1X;
    510             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    511             if (cb.contains("C1X-C2X")) {
    512               double dcb_1X2X = cb.find("C1X-C2X").value();
    513               rnxType2ch = "2X";
    514               value =  aIF2 * dcb_1C2L - dcb_1C1X - dcb_1X2X;
    515               setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    516             }
    517           }
    518           if (cb.contains("C1C-C5X")) {
    519             double dcb_1C5X = cb.find("C1C-C5X").value();
    520             rnxType2ch = "5X";
    521             value =  aIF2 * dcb_1C2L - dcb_1C5X;
    522             setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
    523           }
    524         }
    525         break;
    526       default:
    527         break;
    528     }
    529   }
    530   return;
     344  if (!_snxSatCodeBiasMap.contains(prn)) {
     345    return;
     346  }
     347  char sys = prn.at(0).toLatin1();
     348  t_ssrRefSig::t_pair pair;
     349  if (!t_ssrRefSig::get(sys, pair)) {
     350    return;
     351  }
     352  const QString ref1 = QString::fromStdString(pair.sig1());
     353  const QString ref2 = QString::fromStdString(pair.sig2());
     354  const QMap<QString, double>& cb = _snxSatCodeBiasMap[prn]->_biasMap;
     355  auto dcb = [&cb](const QString& sig1, const QString& sig2, double& value) {
     356    QString key = "C" + sig1 + "-C" + sig2;
     357    if (cb.contains(key)) {
     358      value = cb[key];
     359      return true;
     360    }
     361    return false;
     362  };
     363
     364  QMap<QString, double> osb;
     365  QStringList           order;
     366
     367  // Default reference signals: the DCB of the reference signals and the
     368  // DCBs of the IGS conventions, evaluated in one pass as before
     369  if (t_ssrRefSig::isDefault(sys)) {
     370    const QVector<QPair<QString, QString> >& dcbs = defaultDcbs(sys);
     371    double dcbRef;
     372    if (dcbs.isEmpty() || !dcb(ref1, ref2, dcbRef)) {
     373      return;
     374    }
     375    osb[ref1] =  aIF2 * dcbRef;
     376    osb[ref2] = -aIF1 * dcbRef;
     377    if (sys == 'E') {   // order of the output as before
     378      order << ref2 << ref1;
     379    }
     380    else {
     381      order << ref1 << ref2;
     382    }
     383    for (const QPair<QString, QString>& dd : dcbs) {
     384      double value;
     385      if (!dcb(dd.first, dd.second, value)) {
     386        continue;
     387      }
     388      if (osb.contains(dd.first) && !osb.contains(dd.second)) {
     389        osb[dd.second] = osb[dd.first] - value;
     390        order << dd.second;
     391      }
     392      else if (osb.contains(dd.second) && !osb.contains(dd.first)) {
     393        osb[dd.first] = value + osb[dd.second];
     394        order << dd.first;
     395      }
     396    }
     397  }
     398
     399  // Other reference signals: all DCBs of the satellite, the DCB of the
     400  // reference signals may also be derived via other signals
     401  else {
     402    QMap<QString, double> rel;   // OSB - OSB_ref1
     403    QStringList           relOrder;
     404    rel[ref1] = 0.0;
     405    relOrder << ref1;
     406    bool changed = true;
     407    while (changed) {
     408      changed = false;
     409      QMapIterator<QString, double> it(cb);
     410      while (it.hasNext()) {
     411        it.next();
     412        QStringList hlp = it.key().split('-');
     413        if (hlp.size() != 2 || hlp[0].size() != 3 || hlp[1].size() != 3) {
     414          continue;
     415        }
     416        QString sigA = hlp[0].mid(1);
     417        QString sigB = hlp[1].mid(1);
     418        if (rel.contains(sigA) && !rel.contains(sigB)) {
     419          rel[sigB] = rel[sigA] - it.value();
     420          relOrder << sigB;
     421          changed = true;
     422        }
     423        else if (rel.contains(sigB) && !rel.contains(sigA)) {
     424          rel[sigA] = rel[sigB] + it.value();
     425          relOrder << sigA;
     426          changed = true;
     427        }
     428      }
     429    }
     430    if (!rel.contains(ref2)) {
     431      return;
     432    }
     433    double osbRef1 = -aIF2 * rel[ref2];
     434    order << ref1 << ref2;
     435    for (const QString& sig : relOrder) {
     436      osb[sig] = osbRef1 + rel[sig];
     437      if (sig != ref1 && sig != ref2) {
     438        order << sig;
     439      }
     440    }
     441  }
     442
     443  for (const QString& sig : order) {
     444    setSsrSatCodeBias(sig.toStdString(), osb[sig], satCodeBias);
     445  }
    531446}
    532447
  • trunk/BNC/src/combination/bncbiassnx.h

    r11023 r11087  
    5858class bncBiasSnx {
    5959 public:
    60   bncBiasSnx();
     60  bncBiasSnx(bool allGnss = false); // allGnss: all systems, not only the combined ones
    6161  ~bncBiasSnx();
    6262  t_irc readFile(const QString& fileName);
  • trunk/BNC/src/combination/bnccomb.cpp

    r11086 r11087  
    206206
    207207  connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));
     208
     209  // Reference signals of the clocks
     210  for (const QString& err : t_ssrRefSig::init()) {
     211    emit newMessage(("bncComb: " + err).toLatin1(), true);
     212  }
     213  if (!t_ssrRefSig::allDefault()) {
     214    emit newMessage(("bncComb: reference signals of the clocks " + t_ssrRefSig::currentString()).toLatin1(), true);
     215  }
     216
    208217  connect(BNC_CORE, SIGNAL(providerIDChanged(QString)), this, SLOT(slotProviderIDChanged(QString)));
    209218  connect(BNC_CORE, SIGNAL(newOrbCorrections(QList<t_orbCorr>)), this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)));
  • trunk/BNC/src/combination/bnccomb.h

    r11086 r11087  
    1818#include "bncconst.h"
    1919#include "bncoutf.h"
     20#include "ssrRefSig.h"
    2021
    2122class bncRtnetDecoder;
    … …  
    176177    enum type {dummy = 0, c1, c2, cIF};
    177178
     179    // reference signals of the clocks, see t_ssrRefSig (option ssrRefSignals)
    178180    static t_frequency::type toFreq(char sys, type tt) {
     181      t_ssrRefSig::t_pair pair;
     182      if (!t_ssrRefSig::get(sys, pair)) {
     183        return t_frequency::dummy;
     184      }
    179185      switch (tt) {
    180       case c1:
    181         if      (sys == 'G') return t_frequency::G1;
    182         else if (sys == 'R') return t_frequency::R1;
    183         else if (sys == 'E') return t_frequency::E1;
    184         else if (sys == 'C') return t_frequency::C2;
    185         else if (sys == 'J') return t_frequency::J1;
    186         else if (sys == 'S') return t_frequency::S1;
    187         else                 return t_frequency::dummy;
    188       case c2:
    189         if      (sys == 'G') return t_frequency::G2;
    190         else if (sys == 'R') return t_frequency::R2;
    191         else if (sys == 'E') return t_frequency::E5;
    192         else if (sys == 'C') return t_frequency::C6;
    193         else if (sys == 'J') return t_frequency::J2;
    194         else if (sys == 'S') return t_frequency::S5;
    195         else                 return t_frequency::dummy;
    196       case dummy:
    197       case cIF:
    198         return t_frequency::dummy;
     186        case c1: return pair.frq1;
     187        case c2: return pair.frq2;
     188        case dummy:
     189        case cIF:
     190          return t_frequency::dummy;
    199191      }
    200192      return t_frequency::dummy;
    201193    }
    202194
    203     static char toAttrib(char sys, type LC) {
    204       switch (LC) {
    205         case c1:
    206           if      (sys == 'G') return 'W';
    207           else if (sys == 'R') return 'P';
    208           else if (sys == 'E') return 'C';
    209           else if (sys == 'C') return 'I';
    210           else if (sys == 'J') return 'C';
    211           else if (sys == 'S') return 'C';
    212           break;
    213         case c2:
    214           if      (sys == 'G') return 'W';
    215           else if (sys == 'R') return 'P';
    216           else if (sys == 'E') return 'Q';
    217           else if (sys == 'C') return 'I';
    218           else if (sys == 'J') return 'L';
    219           else if (sys == 'S') return 'Q';
    220           break;
    221       case dummy:
    222       case cIF:
     195    static char toAttrib(char sys, type tt) {
     196      t_ssrRefSig::t_pair pair;
     197      if (!t_ssrRefSig::get(sys, pair)) {
    223198        return '_';
    224         break;
     199      }
     200      switch (tt) {
     201        case c1: return pair.att1;
     202        case c2: return pair.att2;
     203        case dummy:
     204        case cIF:
     205          return '_';
    225206      }
    226207      return '_';
  • trunk/BNC/src/src.pri

    r11079 r11087  
    4747              -lnewmat -lqwtpolar -lqwt
    4848
    49 HEADERS = bnchelp.html bncgetthread.h ssrQc.h   bncwindow.h   bnctabledlg.h  \
     49HEADERS = bnchelp.html bncgetthread.h ssrQc.h ssrRefSig.h bncwindow.h   bnctabledlg.h  \
    5050          bnccaster.h bncrinex.h bnccore.h bncutils.h   bnchlpdlg.h   \
    5151          bncconst.h bnchtml.h bnctableitem.h bnczerodecoder.h        \
    … …  
    8989win32:HEADERS += serial/win_qextserialport.h
    9090
    91 SOURCES = bncgetthread.cpp ssrQc.cpp  bncwindow.cpp bnctabledlg.cpp             \
     91SOURCES = bncgetthread.cpp ssrQc.cpp ssrRefSig.cpp bncwindow.cpp bnctabledlg.cpp             \
    9292          bnccaster.cpp bncrinex.cpp bnccore.cpp bncutils.cpp         \
    9393          bncconst.cpp bnchtml.cpp bnchlpdlg.cpp bnctableitem.cpp     \
  • 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();
  • trunk/BNC/src/ssrQc.h

    r11085 r11087  
    1414// each interval and at the end of the program.
    1515////////////////////////////////////////////////////////////////////////////
     16class bncBiasSnx;
     17
    1618class t_ssrQc : public QObject {
    1719 Q_OBJECT
    … …  
    133135  QMap<QString, QSet<QString> > _codeSignals;  // key "STA SYS": signals with code biases
    134136  QMap<QString, QSet<QString> > _phaseSignals; // key "STA SYS": signals with phase biases
     137
     138  // Code bias check: DCBs of each stream compared with the other ACs (or
     139  // the reference stream) and optionally with a Bias SINEX file
     140  void   compareCodeBiases(const QString& staID, char sys, QStringList& lines);
     141  bool   refDcbs(const QString& staID, const QString& prn, const QString& base, bool fromFile,
     142                 QMap<QString, double>& dcbs, const QString& onlyAC = QString(),
     143                 QMap<QString, int>* numVotes = 0);
     144  QMap<QString, QMap<QString, QMap<QString, double> > > _cbLast; // stream, PRN, signal: latest code bias [m]
     145  bncBiasSnx*               _bsxRef;    // reference code biases (option ssrQcBiasFile)
     146  QString                   _bsxName;
    135147};
    136148
  • trunk/BNC/tests/README.md

    r11083 r11087  
    6666  encodings, missing reference-signal biases, inconsistent biases and
    6767  persistent outliers. The longest case (about 4 min).
     68* `combi_refsig`: `combi` with non-default reference signals (`ssrRefSignals
     69  C:1P&5P`): BDS clock referral and code biases from the Bias SINEX file on
     70  C1P/C5P (generic DCB derivation), CLK/BIA and the combination log compared.
    6871* Upload (`upload_*`; 16, 19, 20) from `data/upload_rtnet_5min.raw` (RTNET engine
    6972  output + BCEP00BKG0): BNC uploads to a local fake caster
    … …  
    8487* `ssr_qc`, `ssr_qc_select`: SSR QC (31_SsrQC, one file per stream, 5 min interval;
    8588  all streams resp. `ssrQcStreams SSRA00CNE1,SSRA00GMV0`) of the 7 AC streams of
    86   `data/combi_multi_261002_5min.raw`.
     89  `data/combi_multi_261002_5min.raw`. `ssr_qc_bias` adds the CAS DCB file as
     90  'QC Bias File' (code bias check against the other ACs and the file).
    8791
    8892## Adding a replay case
Note: See TracChangeset for help on using the changeset viewer.