Changeset 11200 in ntrip for trunk/BNC/src/rinex/reqcedit.cpp


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

RINEX concatenation checked and improved

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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}
Note: See TracChangeset for help on using the changeset viewer.