Changeset 11200 in ntrip for trunk/BNC/src/rinex


Ignore:
Timestamp:
Nov 11, 2026, 11:17:08 AM (2 days ago)
Author:
stuerze
Message:

RINEX concatenation checked and improved

Location:
trunk/BNC/src/rinex
Files:
6 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}
  • 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
Note: See TracChangeset for help on using the changeset viewer.