Changeset 11200 in ntrip


Ignore:
Timestamp:
Nov 11, 2026, 11:17:08 AM (less than one hour ago)
Author:
stuerze
Message:

RINEX concatenation checked and improved

Location:
trunk/BNC
Files:
66 added
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/CHANGELOG.md

    r11196 r11200  
    11# Changelog
    22## 2.13.8 (2026-11-04)
     3- FIXED: RINEX 3/4 observation files: MARKER NAME was cut to 9 characters (A60 in RINEX 3/4); RINEX 2 keeps the 4-character station ID, the marker name derived from the stream name in real-time RINEX files stays 9 characters (e.g. WTZR00DEU)
     4- CHANGED: RINEX 4 -> 3 navigation conversion: of the NavIC IRUT offsets the one to UTCIRN is written (RINEX 3 has no UTC identifier for UTC(NPLI))
     5- ADDED: **RINEX navigation conversion of the ionosphere and time system parameters**: RINEX 4 ION/STO records are written as RINEX 3 IONOSPHERIC CORR (GPS/QZS/BDS/IRN Klobuchar, Galileo NeQuick-G; BDS with time mark and SV, GEO/IGSO preferred) and TIME SYSTEM CORR (T, W from t_ot, SV, UTC identifier) resp. RINEX 2 ION ALPHA/ION BETA/DELTA-UTC and CORR TO SYSTEM TIME (first record of each kind); RINEX 2 <-> 3 header records are converted for GPS and GLONASS. Records without RINEX 3/2 equivalent (EOP, BDGIM, NavIC L1NV, STO with A2 or of other systems) are reported in the log; RINEX 2/3 header records cannot become RINEX 4 records (no transmission time and satellite) and are reported as well
     6- FIXED: **RINEX concatenation of overlapping observation files: epochs were written twice** (time going back at the file boundary); now an epoch is written only if later than the last one written (first file wins), the number skipped is logged
     7- ADDED: RINEX concatenation: changes of the station header records between the observation files (e.g. receiver, antenna, marker, eccentricities, after the header editing options) are written as event flag 4 before the first epoch of the next file; before, only the header of the first file was used
     8- ADDED: **RINEX navigation concatenation keeps the RINEX 4 STO, EOP and ION records** (before always skipped): duplicates removed, records in the time window written in time order with the ephemerides; when writing RINEX 3 or 2 the number of records not written is logged
     9- ADDED: RINEX navigation concatenation keeps the header records of input files of the same RINEX version (e.g. IONOSPHERIC CORR, TIME SYSTEM CORR, ION ALPHA, ION BETA, DELTA-UTC: A0,A1,T,W, DOI; the first one of a kind, IONOSPHERIC CORR/TIME SYSTEM CORR per correction type) and LEAP SECONDS of the input (incl. future leap seconds) in RINEX 3 and 4; records of other versions are reported in the log as not converted
     10- FIXED: RINEX navigation concatenation: MERGED FILE written with 19 instead of 9 digits, also for a single file and with the number of file name patterns instead of files (input files already merged now count with their MERGED FILE value); comments and PGM / RUN BY / DATE records of input files given with wildcards were lost
    311- ADDED: **PPP post-fit residual RMS**: RMS of the code and of the phase post-fit residuals per epoch (observations used in the final filter update, outliers removed), written to the PPP logfile ('RMS code ... (n) phase ... (n)' after the residuals) and as fields 'res_rms_code' and 'res_rms_phase' of the structured 'ppp_position' record
    412- FIXED: PPP ambiguity resolution: ambiguities of satellites not observed in the current epoch are left out of the search (listed as 'not observed' in the PPP logfile); before, an ambiguity parameter kept up to 60 s after the last observation (e.g. satellite set below the minimum elevation) was still searched and fixed with its last float value
  • trunk/BNC/src/bncrinex.cpp

    r11057 r11200  
    475475      return;
    476476    }
    477     _header.setDefault(_statID, intHeaderVers);
     477    // marker name from the stream name, e.g. WTZR00DEU0 -> WTZR00DEU
     478    _header.setDefault(_statID.left(9), intHeaderVers);
    478479  }
    479480
  • trunk/BNC/src/rinex/reqcedit.cpp

    r11192 r11200  
    327327  // Loop over all input observation files
    328328  // -------------------------------------
     329  bncTime lastWritten;     // epochs of overlapping files are written once
     330  int     numDuplicates = 0;
    329331  for (int ii = 0; ii < _rnxObsFiles.size(); ii++) {
    330332    t_rnxObsFile* obsFile = _rnxObsFiles[ii];
    … …  
    334336            << obsFile->startTime().timestr(0).c_str() << Qt::endl;
    335337    }
     338    // header records changed with respect to the previous file (after the
     339    // header editing options): event with header information (flag 4)
     340    editRnxObsHeader(*obsFile);
     341    QList<t_rnxObsFile::t_rnxEvent> headerChange;
     342    if (ii > 0) {
     343      QStringList records = headerChangeRecords(_rnxObsFiles[ii-1]->header(),
     344                                                obsFile->header(),
     345                                                outObsFile.header().version());
     346      if (!records.isEmpty()) {
     347        t_rnxObsFile::t_rnxEvent event;
     348        event.flag    = 4;
     349        event.version = outObsFile.header().version();
     350        event.records = records;
     351        headerChange.append(event);
     352        if (_logStream) {
     353          *_logStream << "Header changes    : " << records.size() << " records ("
     354                      << obsFile->fileName() << ")" << Qt::endl;
     355        }
     356      }
     357    }
    336358    if (ii == 0) {
    337359      outObsFile.setHeader(obsFile->header(), int(_rnxVersion), &useObsTypes,
    … …  
    365387      // sampling) are written with the next epoch; events with an epoch
    366388      // outside the time window are skipped
    367       QList<t_rnxObsFile::t_rnxEvent> pendingEvents;
     389      QList<t_rnxObsFile::t_rnxEvent> pendingEvents = headerChange;
    368390      bool pendingPowerFail = false;
    369391      bool endReached       = false;
    … …  
    379401          endReached = true;
    380402          break;
     403        }
     404        if (lastWritten.valid() && !(epo->tt > lastWritten)) {
     405          ++numDuplicates; // already written from the previous file
     406          continue;
    381407        }
    382408
    … …  
    396422          }
    397423          outObsFile.writeEpoch(epo);
     424          lastWritten = epo->tt;
    398425        }
    399426        else {
    … …  
    426453    }
    427454  }
     455  if (_logStream && numDuplicates) {
     456    *_logStream << "Epochs skipped    : " << numDuplicates
     457                << " (not later than the last epoch written, e.g. overlapping files)" << Qt::endl;
     458  }
     459}
     460
     461// Station header records of header2 differing from header1 (e.g. new
     462// receiver or antenna in the next file), in the output RINEX version
     463////////////////////////////////////////////////////////////////////////////
     464QStringList t_reqcEdit::headerChangeRecords(const t_rnxObsHeader& header1,
     465                                            const t_rnxObsHeader& header2,
     466                                            double outVersion) {
     467  QMap<QString, QStringList> records1, records2;
     468  QStringList labels;
     469  for (int iHdr = 0; iHdr < 2; iHdr++) {
     470    const t_rnxObsHeader& header = (iHdr == 0) ? header1 : header2;
     471    QMap<QString, QStringList>& records = (iHdr == 0) ? records1 : records2;
     472    QStringList lines = header.stationRecords(outVersion);
     473    QStringList other = header.otherRecords();
     474    for (int ii = 0; ii < other.size(); ii++) {
     475      QString record = t_rnxObsHeader::convertRecord(other[ii], outVersion,
     476                                                     header.otherRecordsVersion());
     477      if (!record.isEmpty()) {
     478        lines << record;
     479      }
     480    }
     481    if (header.rcvClkOffsAppl() < 0) { // not given: default 0
     482      lines << QString("%1").arg(0, 6).leftJustified(60) + "RCV CLOCK OFFS APPL";
     483    }
     484    for (int ii = 0; ii < lines.size(); ii++) {
     485      QString label = lines[ii].mid(60).trimmed();
     486      records[label] << lines[ii];
     487      if (iHdr == 1 && !labels.contains(label)) {
     488        labels << label;
     489      }
     490    }
     491  }
     492  QStringList changed;
     493  for (int ii = 0; ii < labels.size(); ii++) {
     494    if (records1.value(labels[ii]) != records2[labels[ii]]) {
     495      changed << records2[labels[ii]];
     496    }
     497  }
     498  return changed;
    428499}
    429500
    … …  
    709780    comments.append(comment);
    710781  }
    711   QStringListIterator it(_navFileNames);
     782  // (number of merged files: input files already merged count with their
     783  // MERGED FILE value)
     784  int mergedNavFiles = 0;
     785  QStringList navFiles = expandFileNames(_navFileNames);
     786  QStringListIterator it(navFiles);
    712787  while (it.hasNext()) {
    713788    QString fileName = it.next();
    714789
    715790    t_rnxNavFile rnxNavFile(fileName, t_rnxNavFile::input);
     791    mergedNavFiles += qMax(1, rnxNavFile.mergedFiles());
    716792    QStringListIterator itCmnt(rnxNavFile.comments());
    717793    while (itCmnt.hasNext()) {
    … …  
    813889  }
    814890
    815   int mergedNavFiles = _navFileNames.size();
     891  // Header records and RINEX 4 STO/EOP/ION records of all input files:
     892  // header records of the same RINEX version (the first one of a kind),
     893  // records without duplicates, in the time window
     894  // ---------------------------------------------------------------------
     895  QStringList headerRecords;
     896  QList<t_rnxNavFile::t_rnxNavRecord> records;
     897  collectNavRecords(outNavFile, headerRecords, records);
     898
    816899  unsigned year, month, day;
    817900  int gps_utc = 0;
    … …  
    820903    gps_utc = gnumleap(year, month, day);
    821904  }
    822   outNavFile.writeHeader(&txtMap, mergedNavFiles, gps_utc);
     905  outNavFile.writeHeader(&txtMap, mergedNavFiles, gps_utc, &headerRecords);
     906
     907  // records without epoch first, then in time order with the ephemerides
     908  int iRec = 0;
     909  while (iRec < records.size() && !records[iRec].tt.valid()) {
     910    outNavFile.writeRecord(records[iRec++]);
     911  }
    823912
    824913  // Loop over all ephemerides
    … …  
    850939      }
    851940    }
     941    while (iRec < records.size() && records[iRec].tt <= eph->TOC()) {
     942      outNavFile.writeRecord(records[iRec++]);
     943    }
    852944    outNavFile.writeEph(eph);
     945  }
     946  while (iRec < records.size()) {
     947    outNavFile.writeRecord(records[iRec++]);
     948  }
     949}
     950
     951// File names with wildcards expanded (as in readEphemerides)
     952////////////////////////////////////////////////////////////////////////////
     953QStringList t_reqcEdit::expandFileNames(const QStringList& fileNames) {
     954  QStringList expanded;
     955  QStringListIterator itNames(fileNames);
     956  while (itNames.hasNext()) {
     957    QString fileName = itNames.next();
     958    if (fileName.indexOf('*') != -1 || fileName.indexOf('?') != -1) {
     959      QFileInfo fileInfo(fileName);
     960      QDir dir = fileInfo.dir();
     961      QStringList filters; filters << fileInfo.fileName();
     962      QListIterator<QFileInfo> it(dir.entryInfoList(filters));
     963      while (it.hasNext()) {
     964        expanded << it.next().filePath();
     965      }
     966    }
     967    else {
     968      expanded << fileName;
     969    }
     970  }
     971  return expanded;
     972}
     973
     974// Key of a navigation header record: one record per kind is written (the
     975// first one), IONOSPHERIC CORR and TIME SYSTEM CORR per correction type
     976////////////////////////////////////////////////////////////////////////////
     977static QString navHeaderKey(const QString& record) {
     978  QString label = record.mid(60).trimmed();
     979  if (label == "IONOSPHERIC CORR") {
     980    return label + record.left(4) + record.mid(54, 6); // type, time mark, SV
     981  }
     982  if (label == "TIME SYSTEM CORR") {
     983    return label + record.left(4);
     984  }
     985  static const QStringList single = QStringList()
     986    << "LEAP SECONDS" << "ION ALPHA" << "ION BETA" << "DELTA-UTC: A0,A1,T,W"
     987    << "CORR TO SYSTEM TIME" << "D-UTC A0,A1,T,W,S,U" << "DOI";
     988  if (single.contains(label)) {
     989    return label;
     990  }
     991  return record; // e.g. LICENSE OF USE, STATION INFORMATION: identical ones once
     992}
     993
     994// Conversion of navigation header records between RINEX versions and of
     995// RINEX 4 STO/ION records to RINEX 3/2 header records (RINEX 3.05 Table A5,
     996// RINEX 2.11, RINEX 4.02 Tables 26-28, A33-A37: same sign conventions)
     997////////////////////////////////////////////////////////////////////////////
     998namespace {
     999
     1000double navField(const QString& line, int pos, int len = 19) {
     1001  double value = 0.0;
     1002  readDbl(line, pos, len, value);
     1003  return value;
     1004}
     1005
     1006bool navEpoch(const QString& line, bncTime& tt) {
     1007  QTextStream in(line.mid(4, 19).toLatin1(), QIODevice::ReadOnly);
     1008  int    year = 0, month = 0, day = 0, hour = 0, min = 0;
     1009  double sec = 0.0;
     1010  in >> year >> month >> day >> hour >> min >> sec;
     1011  if (year <= 0) {
     1012    return false;
     1013  }
     1014  tt.set(year, month, day, hour, min, sec);
     1015  return true;
     1016}
     1017
     1018QString navRecord(const QString& value, const QString& label) {
     1019  return value.leftJustified(60) + label;
     1020}
     1021
     1022// RINEX 3 IONOSPHERIC CORR: A4,1X,4D12.4,1X,A1,1X,I2
     1023QString ionCorrV3(const QString& type, const double par[4], QChar timeMark, int sv) {
     1024  QString value = type.leftJustified(4) + " ";
     1025  for (int ii = 0; ii < 4; ii++) {
     1026    value += QString("%1").arg(par[ii], 12, 'E', 4);
     1027  }
     1028  value += QString(" %1 ").arg(timeMark);
     1029  value += (sv > 0) ? QString("%1").arg(sv, 2) : QString("  ");
     1030  return navRecord(value, "IONOSPHERIC CORR");
     1031}
     1032
     1033// RINEX 3 TIME SYSTEM CORR: A4,1X,D17.10,D16.9,1X,I6,1X,I4,1X,A5,1X,I2
     1034QString timeCorrV3(const QString& type, double a0, double a1, int tt, int week,
     1035                   const QString& svId, int utcId) {
     1036  QString value = type.leftJustified(4) + " "
     1037    + QString("%1").arg(a0, 17, 'E', 10) + QString("%1").arg(a1, 16, 'E', 9)
     1038    + QString(" %1 %2 %3 %4").arg(tt, 6).arg(week, 4).arg(svId, 5).arg(utcId, 2);
     1039  return navRecord(value, "TIME SYSTEM CORR");
     1040}
     1041
     1042// RINEX 2 ION ALPHA/ION BETA: 2X,4D12.4
     1043QString ionV2(const QString& label, const double par[4]) {
     1044  QString value = "  ";
     1045  for (int ii = 0; ii < 4; ii++) {
     1046    value += QString("%1").arg(par[ii], 12, 'E', 4);
     1047  }
     1048  return navRecord(value, label);
     1049}
     1050
     1051// RINEX 2 DELTA-UTC: A0,A1,T,W: 3X,2D19.12,2I9
     1052QString deltaUtcV2(double a0, double a1, int tt, int week) {
     1053  QString value = "   " + QString("%1").arg(a0, 19, 'E', 12) + QString("%1").arg(a1, 19, 'E', 12)
     1054    + QString("%1%2").arg(tt, 9).arg(week, 9);
     1055  return navRecord(value, "DELTA-UTC: A0,A1,T,W");
     1056}
     1057
     1058// RINEX 2 CORR TO SYSTEM TIME: 3I6,3X,D19.12 (-TauC)
     1059QString corrSysTimeV2(int year, int month, int day, double minusTauC) {
     1060  QString value = QString("%1%2%3   ").arg(year, 6).arg(month, 6).arg(day, 6)
     1061    + QString("%1").arg(minusTauC, 19, 'E', 12);
     1062  return navRecord(value, "CORR TO SYSTEM TIME");
     1063}
     1064
     1065// UTC identifier of RINEX 3 TIME SYSTEM CORR from the RINEX 4 UTC ID
     1066int utcIdV3(const QString& utcId) {
     1067  if (utcId == "UTC(NIST)")                         return 1;
     1068  if (utcId == "UTC(USNO)")                         return 2;
     1069  if (utcId == "UTC(SU)")                           return 3;
     1070  if (utcId == "UTC(BIPM)")                         return 4;
     1071  if (utcId == "UTCGAL")                            return 5; // UTC(Europe Lab)
     1072  if (utcId == "UTC(CRL)" || utcId == "UTC(NICT)")  return 6;
     1073  if (utcId == "UTC(NTSC)")                         return 7;
     1074  return 0;
     1075}
     1076
     1077// Priority of BDS satellites for the ionosphere parameters (RINEX 3.05
     1078// Table A5 Note 2): GEO, IGSO, MEO
     1079int bdsPriority(int prn) {
     1080  if (prn <= 5 || (prn >= 59 && prn <= 62)) {
     1081    return 0;
     1082  }
     1083  if ((prn >= 6 && prn <= 10) || prn == 13 || prn == 16 || prn == 31 ||
     1084      (prn >= 38 && prn <= 40) || prn == 56) {
     1085    return 1;
     1086  }
     1087  return 2;
     1088}
     1089
     1090} // namespace
     1091
     1092// RINEX 4 STO/ION records -> RINEX 3 or 2 header records (one per kind:
     1093// the first record in time, BDS by satellite priority); returns the number
     1094// of records not convertible
     1095////////////////////////////////////////////////////////////////////////////
     1096int t_reqcEdit::navRecordsToHeader(const QList<t_rnxNavFile::t_rnxNavRecord>& records,
     1097                                   double outVersion, bool outGlonass,
     1098                                   QStringList& headerRecords, QStringList& notConverted) {
     1099  QMap<QString, QPair<int, QString> > converted; // key -> (priority, record)
     1100  int numNotConverted = 0;
     1101
     1102  for (int iRec = 0; iRec < records.size(); iRec++) {
     1103    const t_rnxNavFile::t_rnxNavRecord& rec = records[iRec];
     1104    QStringList hlp = rec.lines[0].split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
     1105    QString prnStr  = hlp.value(2);
     1106    QString navType = hlp.value(3);
     1107    QString subType = hlp.value(4);
     1108    char    sys     = prnStr.isEmpty() ? ' ' : prnStr[0].toLatin1();
     1109    int     prnNum  = prnStr.mid(1).toInt();
     1110    QString key;
     1111    QString record;
     1112    int     priority = 0;
     1113
     1114    // (rec.lines[0] is the "> " line, the data lines follow)
     1115    if (rec.key == "ION" && rec.lines.size() >= 4 && subType.isEmpty() &&
     1116        ((sys == 'G' && (navType == "LNAV" || navType == "CNVX")) ||
     1117         (sys == 'J' &&  navType == "LNAV") ||
     1118         (sys == 'C' &&  navType == "D1D2") ||
     1119         (sys == 'I' &&  navType == "LNAV"))) {
     1120      // Klobuchar
     1121      double alpha[4], beta[4];
     1122      alpha[0] = navField(rec.lines[1], 23);
     1123      alpha[1] = navField(rec.lines[1], 42);
     1124      alpha[2] = navField(rec.lines[1], 61);
     1125      alpha[3] = navField(rec.lines[2],  4);
     1126      beta[0]  = navField(rec.lines[2], 23);
     1127      beta[1]  = navField(rec.lines[2], 42);
     1128      beta[2]  = navField(rec.lines[2], 61);
     1129      beta[3]  = navField(rec.lines[3],  4);
     1130      priority = (navType == "CNVX") ? 1 : 0; // LNAV preferred
     1131      if (outVersion >= 3.0) {
     1132        QString prefix = (sys == 'G') ? "GPS" : (sys == 'J') ? "QZS" : (sys == 'C') ? "BDS" : "IRN";
     1133        QChar timeMark = ' ';
     1134        int   sv       = 0;
     1135        if (sys == 'C') { // mandatory for BDS: hour of the transmission time, SV
     1136          bncTime tt;
     1137          if (navEpoch(rec.lines[1], tt)) {
     1138            unsigned hour, min; double sec;
     1139            tt.civil_time(hour, min, sec);
     1140            timeMark = QChar('A' + int(hour));
     1141          }
     1142          sv       = prnNum;
     1143          priority = bdsPriority(prnNum);
     1144        }
     1145        key    = prefix;
     1146        record = ionCorrV3(prefix + "A", alpha, timeMark, sv) + "\n" +
     1147                 ionCorrV3(prefix + "B", beta,  timeMark, sv);
     1148      }
     1149      else if (sys == 'G' && !outGlonass) {
     1150        key    = "ION";
     1151        record = ionV2("ION ALPHA", alpha) + "\n" + ionV2("ION BETA", beta);
     1152      }
     1153    }
     1154    else if (rec.key == "ION" && rec.lines.size() >= 2 && sys == 'E' && navType == "IFNV" &&
     1155             outVersion >= 3.0) {
     1156      // NeQuick-G
     1157      double ai[4];
     1158      ai[0] = navField(rec.lines[1], 23);
     1159      ai[1] = navField(rec.lines[1], 42);
     1160      ai[2] = navField(rec.lines[1], 61);
     1161      ai[3] = 0.0;
     1162      key    = "GAL";
     1163      record = ionCorrV3("GAL", ai, ' ', 0);
     1164      record = record.left(5 + 36) + QString().leftJustified(12) + record.mid(5 + 48); // ai3 blank
     1165    }
     1166    else if (rec.key == "STO" && rec.lines.size() >= 3) {
     1167      QString type  = rec.lines[1].mid(24, 18).trimmed();
     1168      QString sbas  = rec.lines[1].mid(43, 18).trimmed();
     1169      QString utcId = rec.lines[1].mid(62, 18).trimmed();
     1170      double  a0    = navField(rec.lines[2], 23);
     1171      double  a1    = navField(rec.lines[2], 42);
     1172      double  a2    = navField(rec.lines[2], 61);
     1173      bncTime tot;
     1174      static const QStringList v3Types = QStringList()
     1175        << "GPUT" << "GLUT" << "GAUT" << "BDUT" << "QZUT" << "IRUT" << "SBUT"
     1176        << "GLGP" << "GAGP" << "QZGP" << "IRGP";
     1177      if (a2 == 0.0 && navEpoch(rec.lines[1], tot) && v3Types.contains(type)) {
     1178        int week = 0, sow = 0;
     1179        if (!type.startsWith("GL")) {
     1180          week = int(tot.gpsw());
     1181          sow  = int(nint(tot.gpssec()));
     1182          if (type.startsWith("BD")) {
     1183            week -= 1356; // BDS week
     1184          }
     1185        }
     1186        // one record per offset type (RINEX 3: no UTC ID for UTC(NPLI));
     1187        // NavIC: UTCIRN preferred to UTC(NPLI), SBAS: per SBAS ID
     1188        key = type + (type == "SBUT" ? sbas : QString());
     1189        if (type == "IRUT" && utcId != "UTCIRN") {
     1190          priority = 1;
     1191        }
     1192        if (outVersion >= 3.0) {
     1193          QString svId = (type == "SBUT" && !sbas.isEmpty()) ? sbas : prnStr;
     1194          record = timeCorrV3(type, a0, a1, sow, week, svId.length() > 1 ? svId : QString(),
     1195                              utcIdV3(utcId));
     1196        }
     1197        else if (type == "GPUT" && !outGlonass) {
     1198          key    = "GPUT";
     1199          record = deltaUtcV2(a0, a1, sow, week);
     1200        }
     1201        else if (type == "GLUT" && outGlonass) {
     1202          unsigned year, month, day;
     1203          tot.civil_date(year, month, day);
     1204          key    = "GLUT";
     1205          record = corrSysTimeV2(int(year), int(month), int(day), a0);
     1206        }
     1207      }
     1208    }
     1209
     1210    if (record.isEmpty()) {
     1211      // not convertible to the output version (e.g. EOP, BDGIM, NavIC
     1212      // L1NV, STO with A2, STO types not in RINEX 3) or not used in a
     1213      // RINEX 2 file of another system
     1214      bool usable = !(outVersion < 3.0 &&
     1215                      ((outGlonass && sys != 'R') || (!outGlonass && sys != 'G')));
     1216      if (usable) {
     1217        ++numNotConverted;
     1218        QString what = rec.key + " " + QString(sys) + " " + navType
     1219                       + (subType.isEmpty() ? QString() : " " + subType);
     1220        if (!notConverted.contains(what)) {
     1221          notConverted << what;
     1222        }
     1223      }
     1224      continue;
     1225    }
     1226    if (!converted.contains(key) || priority < converted[key].first) {
     1227      converted[key] = QPair<int, QString>(priority, record);
     1228    }
     1229  }
     1230
     1231  QMapIterator<QString, QPair<int, QString> > it(converted);
     1232  while (it.hasNext()) {
     1233    it.next();
     1234    headerRecords << it.value().second.split("\n");
     1235  }
     1236  return numNotConverted;
     1237}
     1238
     1239// Navigation header record converted between RINEX 2 and 3 (GPS ionosphere
     1240// and UTC parameters, GLONASS time correction); empty list: not convertible
     1241////////////////////////////////////////////////////////////////////////////
     1242QStringList t_reqcEdit::convertNavHeaderRecord(const QString& record, double inVersion,
     1243                                               double outVersion, bool outGlonass) {
     1244  QStringList result;
     1245  QString label = record.mid(60).trimmed();
     1246  if (int(inVersion) == 2 && int(outVersion) == 3) {
     1247    if (label == "ION ALPHA" || label == "ION BETA") {
     1248      double par[4];
     1249      for (int ii = 0; ii < 4; ii++) {
     1250        par[ii] = navField(record, 2 + 12*ii, 12);
     1251      }
     1252      result << ionCorrV3(label == "ION ALPHA" ? "GPSA" : "GPSB", par, ' ', 0);
     1253    }
     1254    else if (label == "DELTA-UTC: A0,A1,T,W") {
     1255      int tt = 0, week = 0;
     1256      readInt(record, 41, 9, tt);
     1257      readInt(record, 50, 9, week);
     1258      result << timeCorrV3("GPUT", navField(record, 3), navField(record, 22),
     1259                           tt, week, QString(), 2); // GPS: UTC(USNO)
     1260    }
     1261    else if (label == "CORR TO SYSTEM TIME") {
     1262      result << timeCorrV3("GLUT", navField(record, 21), 0.0, 0, 0, QString(), 3); // UTC(SU)
     1263    }
     1264  }
     1265  else if (int(inVersion) == 3 && int(outVersion) == 2 && !outGlonass) {
     1266    QString type = record.left(4);
     1267    if (label == "IONOSPHERIC CORR" && (type == "GPSA" || type == "GPSB")) {
     1268      double par[4];
     1269      for (int ii = 0; ii < 4; ii++) {
     1270        par[ii] = navField(record, 5 + 12*ii, 12);
     1271      }
     1272      result << ionV2(type == "GPSA" ? "ION ALPHA" : "ION BETA", par);
     1273    }
     1274    else if (label == "TIME SYSTEM CORR" && type == "GPUT") {
     1275      int tt = 0, week = 0;
     1276      readInt(record, 39, 6, tt);
     1277      readInt(record, 46, 4, week);
     1278      result << deltaUtcV2(navField(record, 5, 17), navField(record, 22, 16), tt, week);
     1279    }
     1280  }
     1281  return result;
     1282}
     1283
     1284// Header records and STO/EOP/ION records of the input navigation files
     1285////////////////////////////////////////////////////////////////////////////
     1286void t_reqcEdit::collectNavRecords(const t_rnxNavFile& outNavFile,
     1287                                   QStringList& headerRecords,
     1288                                   QList<t_rnxNavFile::t_rnxNavRecord>& records) {
     1289  QSet<QString> headerKeys;
     1290  QSet<QString> recordKeys;
     1291  int numNotConverted = 0;
     1292  int numRecordsLost  = 0;
     1293  QStringList notConvertedLabels;
     1294
     1295  QStringList navFiles = expandFileNames(_navFileNames);
     1296
     1297  for (int iFile = 0; iFile < navFiles.size(); iFile++) {
     1298    t_rnxNavFile navFile(navFiles[iFile], t_rnxNavFile::input);
     1299
     1300    // header records: same RINEX version (RINEX 2: same file type)
     1301    bool sameVersion = (int(navFile.version()) == int(outNavFile.version()));
     1302    if (sameVersion && outNavFile.version() < 3.0 && navFile.glonass() != outNavFile.glonass()) {
     1303      sameVersion = false;
     1304    }
     1305    QStringList hdr = navFile.headerRecords();
     1306    for (int ii = 0; ii < hdr.size(); ii++) {
     1307      QString label  = hdr[ii].mid(60).trimmed();
     1308      QString record = hdr[ii];
     1309      if (label == "LEAP SECONDS") { // RINEX 3 and 4: same format, RINEX 2: I6
     1310        if (outNavFile.version() < 3.0) {
     1311          record = record.left(6).leftJustified(60) + label;
     1312        }
     1313      }
     1314      else if (!sameVersion) {
     1315        QStringList conv = convertNavHeaderRecord(record, navFile.version(),
     1316                                                  outNavFile.version(), outNavFile.glonass());
     1317        if (conv.isEmpty()) {
     1318          ++numNotConverted;
     1319          if (!notConvertedLabels.contains(label)) {
     1320            notConvertedLabels << label;
     1321          }
     1322        }
     1323        for (int iConv = 0; iConv < conv.size(); iConv++) {
     1324          QString key = navHeaderKey(conv[iConv]);
     1325          if (!headerKeys.contains(key)) {
     1326            headerKeys.insert(key);
     1327            headerRecords << conv[iConv];
     1328          }
     1329        }
     1330        continue;
     1331      }
     1332      QString key = navHeaderKey(record);
     1333      if (!headerKeys.contains(key)) {
     1334        headerKeys.insert(key);
     1335        headerRecords << record;
     1336      }
     1337    }
     1338
     1339    // STO/EOP/ION records (RINEX 4)
     1340    const QList<t_rnxNavFile::t_rnxNavRecord>& recs = navFile.records();
     1341    for (int ii = 0; ii < recs.size(); ii++) {
     1342      const t_rnxNavFile::t_rnxNavRecord& rec = recs[ii];
     1343      if (rec.tt.valid() &&
     1344          ((_begTime.valid() && rec.tt < _begTime) ||
     1345           (_endTime.valid() && rec.tt > _endTime))) {
     1346        continue;
     1347      }
     1348      QStringList trimmed;
     1349      for (int iLine = 0; iLine < rec.lines.size(); iLine++) {
     1350        trimmed << rec.lines[iLine].trimmed();
     1351      }
     1352      QString key = trimmed.join("|");
     1353      if (recordKeys.contains(key)) {
     1354        continue;
     1355      }
     1356      recordKeys.insert(key);
     1357      records.append(rec);
     1358    }
     1359  }
     1360
     1361  std::stable_sort(records.begin(), records.end(),
     1362                   [](const t_rnxNavFile::t_rnxNavRecord& r1,
     1363                      const t_rnxNavFile::t_rnxNavRecord& r2) {
     1364                     if (!r1.tt.valid() || !r2.tt.valid()) {
     1365                       return !r1.tt.valid() && r2.tt.valid();
     1366                     }
     1367                     return r1.tt < r2.tt;
     1368                   });
     1369
     1370  // RINEX 3/2 output: STO/ION records as header records (header records of
     1371  // the input files of the output version have priority)
     1372  QStringList notConvertedRecords;
     1373  if (outNavFile.version() < 4.0) {
     1374    QStringList conv;
     1375    numRecordsLost = navRecordsToHeader(records, outNavFile.version(), outNavFile.glonass(),
     1376                                        conv, notConvertedRecords);
     1377    for (int iConv = 0; iConv < conv.size(); iConv++) {
     1378      QString key = navHeaderKey(conv[iConv]);
     1379      if (!headerKeys.contains(key)) {
     1380        headerKeys.insert(key);
     1381        headerRecords << conv[iConv];
     1382      }
     1383    }
     1384    records.clear();
     1385  }
     1386
     1387  if (_logStream) {
     1388    if (numNotConverted) {
     1389      *_logStream << "Nav header records not converted to RINEX version "
     1390                  << outNavFile.version() << ": " << numNotConverted
     1391                  << " (" << notConvertedLabels.join(", ") << ")" << Qt::endl;
     1392    }
     1393    if (numRecordsLost) {
     1394      *_logStream << "STO/EOP/ION records not convertible to RINEX version "
     1395                  << outNavFile.version() << ": " << numRecordsLost
     1396                  << " (" << notConvertedRecords.join(", ") << ")" << Qt::endl;
     1397    }
    8531398  }
    8541399}
  • trunk/BNC/src/rinex/reqcedit.h

    r11192 r11200  
    2828#include <QtCore>
    2929#include "rnxobsfile.h"
     30#include "rnxnavfile.h"
    3031#include "rnxnavfile.h"
    3132#include "ephemeris.h"
    … …  
    6667  void editEphemerides();
    6768  void editRnxObsHeader(t_rnxObsFile& obsFile);
     69  static QStringList expandFileNames(const QStringList& fileNames);
     70  static QStringList headerChangeRecords(const t_rnxObsHeader& header1,
     71                                         const t_rnxObsHeader& header2, double outVersion);
     72  static int navRecordsToHeader(const QList<t_rnxNavFile::t_rnxNavRecord>& records,
     73                                double outVersion, bool outGlonass,
     74                                QStringList& headerRecords, QStringList& notConverted);
     75  static QStringList convertNavHeaderRecord(const QString& record, double inVersion,
     76                                            double outVersion, bool outGlonass);
     77  void collectNavRecords(const t_rnxNavFile& outNavFile, QStringList& headerRecords,
     78                         QList<t_rnxNavFile::t_rnxNavRecord>& records);
    6879  void collectEvents(const QList<t_rnxObsFile::t_rnxEvent>& events,
    6980                     QList<t_rnxObsFile::t_rnxEvent>& pendingEvents) const;
  • trunk/BNC/src/rinex/rnxnavfile.cpp

    r11192 r11200  
    5252////////////////////////////////////////////////////////////////////////////
    5353t_rnxNavFile::t_rnxNavHeader::t_rnxNavHeader() {
     54  _mergedFiles = 0;
    5455  _version = 0.0;
    5556  _glonass = false;
    … …  
    8687    else if (key == "PGM / RUN BY / DATE") {
    8788      _runByDate.append(value.trimmed());
     89    }
     90    else if (key == "MERGED FILE") {
     91      // number of merged files, set for the written file
     92      _mergedFiles = qMax(1, value.toInt());
     93    }
     94    else if (!key.isEmpty()) {
     95      // e.g. LEAP SECONDS, IONOSPHERIC CORR, TIME SYSTEM CORR, ION ALPHA,
     96      // ION BETA, DELTA-UTC: A0,A1,T,W, DOI, LICENSE OF USE: kept verbatim
     97      _otherRecords.append(line.left(60).leftJustified(60) + key);
    8898    }
    8999  }
    … …  
    153163  QString prn;
    154164  bool    ephPending = false; // RINEX 4: "> EPH" line read, data lines follow
     165  bool    recordPending = false; // RINEX 4: other record, data lines follow
    155166
    156167  while (stream->status() == QTextStream::Ok && !stream->atEnd()) {
    … …  
    177188      navType = "";
    178189    }
    179     // RINEX version 4: data lines of an EPH record follow its "> EPH" line;
    180     // anything else (e.g. the remainder of a record that could not be read)
    181     // is skipped
     190    // RINEX version 4: data lines of an EPH record follow its "> EPH" line,
     191    // those of other records (STO, EOP, ION, unknown types) are kept
     192    // verbatim up to the next ">" line; anything else is skipped
    182193    else if (version() >= 4.0 && firstStr != ">") {
     194      if (recordPending) {
     195        t_rnxNavRecord& record = _records.last();
     196        if (record.lines.size() == 1) { // first data line: epoch
     197          QTextStream in(line.mid(4, 19).toLatin1(), QIODevice::ReadOnly);
     198          int    year = 0, month = 0, day = 0, hour = 0, min = 0;
     199          double sec = 0.0;
     200          in >> year >> month >> day >> hour >> min >> sec;
     201          if (year > 0) {
     202            record.tt.set(year, month, day, hour, min, sec);
     203          }
     204        }
     205        record.lines << line;
     206        continue;
     207      }
    183208      if (!ephPending || line.left(1) != prn.left(1)) {
    184209        ephPending = false;
    … …  
    189214    }
    190215    else if (version() >= 4.0 && firstStr == ">") {
    191       ephPending = false;
     216      ephPending    = false;
     217      recordPending = false;
    192218      if (hlp.size() < 4) {
    193219        continue;
    … …  
    197223      sys = prn[0].toLatin1();
    198224      navType = hlp.at(3);
    199       ephPending = (key == "EPH");
    200 
    201       // ALL Non-EPH messages are currently ignored
    202       int lines2skip = 0;
    203       // STO
    204       if (key == "STO") {
    205         lines2skip = 2;
    206       }
    207       // EOP
    208       else if (key == "EOP") {
    209         lines2skip = 3;
    210       }
    211       // ION
    212       else if (key == "ION") {
    213         if (sys == 'R') {
    214           lines2skip = 1; //if (navType == "LXOC") {}
    215         }
    216         else if (sys == 'E') {
    217           lines2skip = 2;
    218         }
    219         else if (sys == 'G' || sys == 'C' ||
    220                  sys == 'J' || sys == 'I') {
    221           lines2skip = 3;
    222           if ((sys == 'I' && navType == "L1NV") || // I: KLOB, NEQN
    223               (sys == 'J' && navType == "CNVX")) { // J: WIDE, JAPN
    224             QString navSubType = hlp.value(4); // RINEX >= 4.02 only
    225             if      (navSubType == "KLOB") {
    226               lines2skip += 1;
    227             }
    228             else if (navSubType == "NEQN") {
    229               lines2skip += 4;
    230             }
    231           }
    232         }
    233       }
    234       if (lines2skip) {
    235         for (int ii = 0; ii < lines2skip; ii++) {
    236           stream->readLine();
    237         }
     225      if (key == "EPH") {
     226        ephPending = true;
     227      }
     228      else {
     229        t_rnxNavRecord record;
     230        record.key = key;
     231        record.lines << line;
     232        _records.append(record);
     233        recordPending = true;
    238234      }
    239235      continue;
    … …  
    379375//
    380376////////////////////////////////////////////////////////////////////////////
    381 void t_rnxNavFile::writeHeader(const QMap<QString, QString>* txtMap, int numMergedFiles, int leapSecs) {
     377void t_rnxNavFile::writeHeader(const QMap<QString, QString>* txtMap, int numMergedFiles, int leapSecs,
     378                               const QStringList* headerRecords) {
     379
     380  // LEAP SECONDS of the input header (may hold future leap seconds) preferred
     381  // ---------------------------------------------------------------------------
     382  bool haveLeapSecs = false;
     383  if (headerRecords) {
     384    for (int ii = 0; ii < headerRecords->size(); ii++) {
     385      if (headerRecords->at(ii).mid(60).trimmed() == "LEAP SECONDS") {
     386        haveLeapSecs = true;
     387      }
     388    }
     389  }
     390
    382391
    383392  QString     runBy = BNC_CORE->userName();
    … …  
    459468               << "PGM / RUN BY / DATE\n";
    460469        }
    461     *_stream << QString("%1").arg(leapSecs, 6, 10, QLatin1Char(' ')).left(60).leftJustified(60)
    462         << "LEAP SECONDS\n";
    463 
    464     *_stream << QString("%1").arg(numMergedFiles, 19, 10, QLatin1Char(' ')).leftJustified(60)
    465         << "MERGED FILE\n";
     470    if (!haveLeapSecs) {
     471      *_stream << QString("%1").arg(leapSecs, 6, 10, QLatin1Char(' ')).left(60).leftJustified(60)
     472          << "LEAP SECONDS\n";
     473    }
     474
     475    if (numMergedFiles > 1) {
     476      *_stream << QString("%1").arg(numMergedFiles, 9).leftJustified(60)
     477          << "MERGED FILE\n";
     478    }
    466479  }
    467480
    … …  
    471484  }
    472485
     486  if (headerRecords) {
     487    for (int ii = 0; ii < headerRecords->size(); ii++) {
     488      *_stream << headerRecords->at(ii) << "\n";
     489    }
     490  }
     491
    473492  *_stream << QString()
    474493    .leftJustified(60)
    … …  
    476495}
    477496
     497// Write RINEX 4 STO, EOP or ION record (verbatim)
     498////////////////////////////////////////////////////////////////////////////
     499void t_rnxNavFile::writeRecord(const t_rnxNavRecord& record) {
     500  if (version() < 4.0) {
     501    return;
     502  }
     503  for (int ii = 0; ii < record.lines.size(); ii++) {
     504    QString line = record.lines[ii];
     505    int len = line.length();
     506    while (len > 0 && line[len-1] == ' ') {
     507      len--;
     508    }
     509    line.truncate(len);
     510    *_stream << line << "\n";
     511  }
     512}
     513
    478514//
    479515////////////////////////////////////////////////////////////////////////////
  • trunk/BNC/src/rinex/rnxnavfile.h

    r11023 r11200  
    5555    QStringList     _comments;
    5656    QStringList     _runByDate;
     57    QStringList     _otherRecords; // other header records, verbatim
     58    int             _mergedFiles;  // MERGED FILE (0: not given)
    5759  };
    5860
    5961 public:
     62  // RINEX 4 STO, EOP and ION records (and records of unknown type), verbatim
     63  class t_rnxNavRecord {
     64   public:
     65    QString     key;    // STO, EOP, ION, ...
     66    bncTime     tt;     // epoch of the first data line (for sorting)
     67    QStringList lines;  // incl. the "> " line
     68  };
     69
    6070  t_rnxNavFile(const QString& fileName, e_inpOut inpOut);
    6171  ~t_rnxNavFile();
    … …  
    6878  QStringList comments() const {return _header._comments;}
    6979  QStringList runByDate() const {return _header._runByDate;}
     80  QStringList headerRecords() const {return _header._otherRecords;}
     81  int         mergedFiles() const {return _header._mergedFiles;}
     82  const QList<t_rnxNavRecord>& records() const {return _records;}
    7083  t_eph::e_system satSystem() const {return _header._satSys;}
    7184  void   setGlonass(bool glo) {_header._glonass = glo;}
    7285  void   setGnssTypeV3(t_eph::e_system system) {_header._satSys = system;}
    73   void   writeHeader(const QMap<QString, QString>* txtMap = 0, int numMergedFiles = 0, int leapSecs = 0);
     86  void   writeHeader(const QMap<QString, QString>* txtMap = 0, int numMergedFiles = 0, int leapSecs = 0,
     87                     const QStringList* headerRecords = 0);
    7488  void   writeEph(const t_eph* eph);
     89  void   writeRecord(const t_rnxNavRecord& record);
    7590
    7691 protected:
    … …  
    88103  QTextStream*        _stream;
    89104  std::vector<t_eph*> _ephs;
     105  QList<t_rnxNavRecord> _records;
    90106  t_rnxNavHeader      _header;
    91107};
  • trunk/BNC/src/rinex/rnxobsfile.cpp

    r11192 r11200  
    701701}
    702702
     703// Station records of the header (marker, observer, receiver, antenna,
     704// position), also used for the header changes between concatenated files
     705////////////////////////////////////////////////////////////////////////////
     706QStringList t_rnxObsHeader::stationRecords(double version) const {
     707  if (version == 0.0) {
     708    version = _version;
     709  }
     710  QStringList records;
     711  // RINEX 3/4: MARKER NAME A60, RINEX 2: 4-character station ID
     712  QString markerName = _markerName;
     713  if (version < 3.0) {
     714    markerName = _markerName.left(4);
     715  }
     716  records << QString("%1")
     717    .arg(markerName, -60)
     718    .leftJustified(60)
     719           + "MARKER NAME";
     720
     721
     722  if (!_markerNumber.isEmpty()) {
     723    records << QString("%1")
     724      .arg(_markerNumber, -20)
     725      .leftJustified(60)
     726             + "MARKER NUMBER";
     727  }
     728
     729  if (version >= 3.0) {
     730    records << QString("%1")
     731      .arg(_markerType, -60)
     732      .leftJustified(60)
     733             + "MARKER TYPE";
     734  }
     735
     736  records << QString("%1%2")
     737    .arg(_observer, -20)
     738    .arg(_agency,   -40)
     739    .leftJustified(60)
     740           + "OBSERVER / AGENCY";
     741
     742  records << QString("%1%2%3")
     743    .arg(_receiverNumber,  -20)
     744    .arg(_receiverType,    -20)
     745    .arg(_receiverVersion, -20)
     746    .leftJustified(60)
     747           + "REC # / TYPE / VERS";
     748
     749  records << QString("%1%2")
     750    .arg(_antennaNumber, -20)
     751    .arg(_antennaName,   -20)
     752    .leftJustified(60)
     753           + "ANT # / TYPE";
     754
     755  records << QString("%1%2%3")
     756    .arg(_xyz(1), 14, 'f', 4)
     757    .arg(_xyz(2), 14, 'f', 4)
     758    .arg(_xyz(3), 14, 'f', 4)
     759    .leftJustified(60)
     760           + "APPROX POSITION XYZ";
     761
     762  records << QString("%1%2%3")
     763    .arg(_antNEU(3), 14, 'f', 4)
     764    .arg(_antNEU(2), 14, 'f', 4)
     765    .arg(_antNEU(1), 14, 'f', 4)
     766    .leftJustified(60)
     767           + "ANTENNA: DELTA H/E/N";
     768
     769  if (_rcvClkOffsAppl >= 0) {
     770    records << QString("%1")
     771      .arg(_rcvClkOffsAppl, 6)
     772      .leftJustified(60)
     773             + "RCV CLOCK OFFS APPL";
     774  }
     775  return records;
     776}
     777
    703778// Write Header
    704779////////////////////////////////////////////////////////////////////////////
    … …  
    764839    *stream << itCmnt.next().trimmed().left(60).leftJustified(60) << "COMMENT\n";
    765840  }
    766   QString markerName = _markerName.left(9);
    767   if (_version < 3.0) {
    768     markerName = _markerName.left(4);
    769   }
    770   *stream << QString("%1")
    771     .arg(markerName, -60)
    772     .leftJustified(60)
    773            << "MARKER NAME\n";
    774 
    775 
    776   if (!_markerNumber.isEmpty()) {
    777     *stream << QString("%1")
    778       .arg(_markerNumber, -20)
    779       .leftJustified(60)
    780              << "MARKER NUMBER\n";
    781   }
    782 
    783   if (_version >= 3.0) {
    784     *stream << QString("%1")
    785       .arg(_markerType, -60)
    786       .leftJustified(60)
    787              << "MARKER TYPE\n";
    788   }
    789 
    790   *stream << QString("%1%2")
    791     .arg(_observer, -20)
    792     .arg(_agency,   -40)
    793     .leftJustified(60)
    794            << "OBSERVER / AGENCY\n";
    795 
    796   *stream << QString("%1%2%3")
    797     .arg(_receiverNumber,  -20)
    798     .arg(_receiverType,    -20)
    799     .arg(_receiverVersion, -20)
    800     .leftJustified(60)
    801            << "REC # / TYPE / VERS\n";
    802 
    803   *stream << QString("%1%2")
    804     .arg(_antennaNumber, -20)
    805     .arg(_antennaName,   -20)
    806     .leftJustified(60)
    807            << "ANT # / TYPE\n";
    808 
    809   *stream << QString("%1%2%3")
    810     .arg(_xyz(1), 14, 'f', 4)
    811     .arg(_xyz(2), 14, 'f', 4)
    812     .arg(_xyz(3), 14, 'f', 4)
    813     .leftJustified(60)
    814            << "APPROX POSITION XYZ\n";
    815 
    816   *stream << QString("%1%2%3")
    817     .arg(_antNEU(3), 14, 'f', 4)
    818     .arg(_antNEU(2), 14, 'f', 4)
    819     .arg(_antNEU(1), 14, 'f', 4)
    820     .leftJustified(60)
    821            << "ANTENNA: DELTA H/E/N\n";
    822 
    823   if (_rcvClkOffsAppl >= 0) {
    824     *stream << QString("%1")
    825       .arg(_rcvClkOffsAppl, 6)
    826       .leftJustified(60)
    827              << "RCV CLOCK OFFS APPL\n";
    828   }
     841  *stream << stationRecords().join("\n") << "\n";
    829842
    830843  if (_version < 3.0) {
  • trunk/BNC/src/rinex/rnxobsfile.h

    r11192 r11200  
    7676  int         scaleFactor(char sys, const QString& type) const;
    7777  static QString convertRecord(const QString& line, double outVersion, double inVersion);
     78  QStringList stationRecords(double version = 0.0) const;
     79  QStringList otherRecords() const {return _otherRecords;}
     80  double      otherRecordsVersion() const {return _otherRecordsVersion;}
    7881  void        setStartTime(const bncTime& startTime) {_startTime = startTime;}
    7982
  • trunk/BNC/tests/README.md

    r11192 r11200  
    2828| `data/rnx402_MO.rnx`, `data/rnx402_MN.rnx` | crafted RINEX 4.02 files: 3 BRUX epochs with receiver clock offset, pico-sec digits and `RCV CLOCK OFFS APPL`; GLONASS FDMA/L1OC/L3OC ephemerides and STO/EOP/ION examples of the RINEX 4.02 document (ION with subtypes KLOB/NEQN, ION J CNVX without subtype) |
    2929| `data/rnx402_events_MO.rnx` | `data/rnx402_MO.rnx` with event records (flags 1-6, blank epochs, pico-sec event epoch, events after the last epoch) and further header records (SYS / SCALE FACTOR, DCBS/PCVS APPLIED, CENTER OF MASS: XYZ, PRN / # OF OBS, an unknown record) |
     30| `data/nav402_p?_MN.rnx`, `data/nav305_?_MN.rnx`, `data/nav211_?.26n` | navigation files for the concatenation: 3 overlapping pieces of the 1 h BRUX RINEX 4.01 file (with STO/ION records); 2 RINEX 3.05 and 2 RINEX 2.11 GPS files (converted by BNC) with added IONOSPHERIC CORR/TIME SYSTEM CORR/LEAP SECONDS resp. ION ALPHA/ION BETA/DELTA-UTC records, differing between the files |
     31| `data/nav401_gps_MN.rnx` | GPS LNAV records of the daily BRUX RINEX 4.01 file incl. ION and STO records (conversion to RINEX 3/2 header records) |
     32| `data/obs_p1_MO.rnx`, `data/obs_p2_MO.rnx` | epochs 0-14 and 10-24 of the 1 h BRUX observation file, the second with another receiver firmware and antenna |
     33| `data/nav402_navic_MN.rnx` | NavIC STO IRUT records for UTC(NPLI) and UTCIRN (RINEX 4.02 examples) and one GPS ephemeris |
    3034| `tools/fakecaster.py` | local Ntrip caster recording an upload as a BNC raw file (case key `fakecaster`); optionally TLS and serving a stream |
    3135| `tools/bncraw.py` | `info` (streams, time span, RTCM3 message types), `slice` and `crs` (decode 1300/1301/1302) for raw captures |
    … …  
    6266  types), scaled observations, header records kept, TIME OF LAST OBS,
    6367  # OF SATELLITES and PRN / # OF OBS of the written file.
     68* `rinex_nav_concat_v4`, `rinex_nav_concat_v4_to_v3`, `rinex_nav_concat_v3`,
     69  `rinex_nav_concat_v3_to_v4`, `rinex_nav_concat_v2`: navigation file
     70  concatenation: duplicates removed, RINEX 4 STO/ION records and header
     71  records (first one of a kind) kept, records not convertible to the output
     72  version reported in the log; `rinex_nav_concat_v4` equals the edit of the
     73  original 1 h file.
     74* `rinex_nav_v4_gps_to_v2`, `rinex_nav_v4_gps_to_v3`, `rinex_nav_v2_to_v3`:
     75  conversion of RINEX 4 ION/STO records to RINEX 2/3 header records (ION
     76  ALPHA/BETA, DELTA-UTC resp. IONOSPHERIC CORR, TIME SYSTEM CORR) and of
     77  RINEX 2 to RINEX 3 header records; `rinex_nav_concat_v4_to_v3` also covers
     78  BDS (time mark, GEO/IGSO priority), Galileo, GLONASS.
     79* `rinex_nav_navic_irut_to_v3`: of two NavIC IRUT offsets, the one to UTCIRN
     80  becomes TIME SYSTEM CORR IRUT in RINEX 3.
     81* `rinex_edit_long_marker`: marker name longer than 9 characters kept in RINEX 4.
     82* `rinex_concat_overlap`: overlapping observation files (epochs written once)
     83  with header changes between the files (event flag 4).
    6484* Stream replays of the real-time examples (`replay_*`), all from the 5 min
    6585  slice `data/wtzr_cnes_5min.raw` (BCEP00BKG0, WTZR00DEU0, SSRA00CNE1):
Note: See TracChangeset for help on using the changeset viewer.