source: ntrip/trunk/BNC/src/bncrinex.cpp @ 8792

Last change on this file since 8792 was 8792, checked in by stuerze, 2 months ago

minor changes for correct marker and receiver numbers in rinex v3 file names

File size: 18.9 KB
Line 
1// Part of BNC, a utility for retrieving decoding and
2// converting GNSS data streams from NTRIP broadcasters.
3//
4// Copyright (C) 2007
5// German Federal Agency for Cartography and Geodesy (BKG)
6// http://www.bkg.bund.de
7// Czech Technical University Prague, Department of Geodesy
8// http://www.fsv.cvut.cz
9//
10// Email: euref-ip@bkg.bund.de
11//
12// This program is free software; you can redistribute it and/or
13// modify it under the terms of the GNU General Public License
14// as published by the Free Software Foundation, version 2.
15//
16// This program is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU General Public License for more details.
20//
21// You should have received a copy of the GNU General Public License
22// along with this program; if not, write to the Free Software
23// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25/* -------------------------------------------------------------------------
26 * BKG NTRIP Client
27 * -------------------------------------------------------------------------
28 *
29 * Class:      bncRinex
30 *
31 * Purpose:    writes RINEX files
32 *
33 * Author:     L. Mervart
34 *
35 * Created:    27-Aug-2006
36 *
37 * Changes:
38 *
39 * -----------------------------------------------------------------------*/
40
41#include <stdlib.h>
42#include <iostream>
43#include <iomanip>
44#include <math.h>
45#include <sstream>
46
47#include <QtCore>
48#include <QUrl>
49#include <QString>
50
51#include "bncrinex.h"
52#include "bnccore.h"
53#include "bncutils.h"
54#include "bncconst.h"
55#include "bnctabledlg.h"
56#include "bncgetthread.h"
57#include "bncnetqueryv1.h"
58#include "bncnetqueryv2.h"
59#include "bncsettings.h"
60#include "bncversion.h"
61
62using namespace std;
63
64// Constructor
65////////////////////////////////////////////////////////////////////////////
66bncRinex::bncRinex(const QByteArray& statID, const QUrl& mountPoint,
67                   const QByteArray& latitude, const QByteArray& longitude,
68                   const QByteArray& nmea, const QByteArray& ntripVersion) {
69
70  _statID        = statID;
71  _mountPoint    = mountPoint;
72  _latitude      = latitude;
73  _longitude     = longitude;
74  _nmea          = nmea;
75  _ntripVersion  = ntripVersion;
76  _headerWritten = false;
77  _reconnectFlag = false;
78
79  bncSettings settings;
80  _rnxScriptName = settings.value("rnxScript").toString();
81  expandEnvVar(_rnxScriptName);
82
83  _pgmName  = QString(BNCPGMNAME).leftJustified(20, ' ', true);
84#ifdef WIN32
85  _userName = QString("${USERNAME}");
86#else
87  _userName = QString("${USER}");
88#endif
89  expandEnvVar(_userName);
90  _userName = _userName.leftJustified(20, ' ', true);
91
92  _samplingRateMult10 = int(settings.value("rnxSampl").toString().split("sec").first().toDouble() * 10.0);
93
94  if      (settings.value("rnxSampl").toString() == "0.1 sec") {
95    _samplingRateStr = "_10Z";
96  }
97  else if (settings.value("rnxSampl").toString() == "1 sec") {
98    _samplingRateStr = "_01S";
99  }
100  else if (settings.value("rnxSampl").toString() == "5 sec") {
101    _samplingRateStr = "_05S";
102  }
103  else if (settings.value("rnxSampl").toString() == "10 sec") {
104    _samplingRateStr = "_10S";
105  }
106  else if (settings.value("rnxSampl").toString() == "15 sec") {
107    _samplingRateStr = "_15S";
108  }
109  else if (settings.value("rnxSampl").toString() == "30 sec") {
110    _samplingRateStr = "_30S";
111  }
112  else if (settings.value("rnxSampl").toString() == "60 sec") {
113    _samplingRateStr = "_01M";
114  }
115
116  _writeRinexFileOnlyWithSkl = settings.value("rnxOnlyWithSKL").toBool();
117
118  _rnxV3 = (Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked) ? true : false;
119}
120
121// Destructor
122////////////////////////////////////////////////////////////////////////////
123bncRinex::~bncRinex() {
124  bncSettings settings;
125  if ((_header.version() >= 3.0) && ( Qt::CheckState(settings.value("rnxAppend").toInt()) != Qt::Checked) ) {
126    _out << ">                              4  1" << endl;
127    _out << "END OF FILE" << endl;
128  }
129  _out.close();
130}
131
132// Download Skeleton Header File
133////////////////////////////////////////////////////////////////////////////
134t_irc bncRinex::downloadSkeleton() {
135
136  t_irc irc = failure;
137
138  QStringList table;
139  bncTableDlg::getFullTable(_ntripVersion, _mountPoint.host(), _mountPoint.port(),
140      _mountPoint.userName(), _mountPoint.password(), table, true);
141  QString net;
142  QStringListIterator it(table);
143  while (it.hasNext()) {
144    QString line = it.next();
145    if (line.indexOf("STR") == 0) {
146      QStringList tags = line.split(";");
147      if (tags.size() > 7) {
148        if (tags.at(1) == _mountPoint.path().mid(1).toLatin1()) {
149          net = tags.at(7);
150          break;
151        }
152      }
153    }
154  }
155  QString sklDir;
156  if (!net.isEmpty()) {
157    it.toFront();
158    while (it.hasNext()) {
159      QString line = it.next();
160      if (line.indexOf("NET") == 0) {
161        QStringList tags = line.split(";");
162        if (tags.size() > 6) {
163          if (tags.at(1) == net) {
164            sklDir = tags.at(6).trimmed();
165            break;
166          }
167        }
168      }
169    }
170  }
171  if (!sklDir.isEmpty() && sklDir != "none") {
172    QString staID, staIDalternative;
173    QUrl url;
174    int stIdLength = _mountPoint.path().length()-2;
175    staID =            _mountPoint.path().mid(1,stIdLength).toUpper() + ".skl";
176    staIDalternative = _mountPoint.path().mid(1,stIdLength).toLower() + ".skl";
177    url = sklDir + "/" + staID;
178    if (url.port() == -1) {
179      if (sklDir.contains("https", Qt::CaseInsensitive)) {
180        url.setPort(443);
181      }
182      else {
183        url.setPort(80);
184      }
185    }
186
187    bncNetQuery* query = new bncNetQueryV2(true);
188    QByteArray outData;
189    query->waitForRequestResult(url, outData);
190    if (query->status() == bncNetQuery::finished &&
191        outData.contains("END OF HEADER")) {
192      QTextStream in(outData);
193      irc = _sklHeader.read(&in);
194    }
195    else {
196      url = sklDir + "/" + staIDalternative;
197      query->waitForRequestResult(url, outData);
198      if (query->status() == bncNetQuery::finished &&
199              outData.contains("END OF HEADER")) {
200            QTextStream in(outData);
201            irc = _sklHeader.read(&in);
202          }
203    }
204
205    delete query;
206  }
207
208  return irc;
209}
210
211// Read Skeleton Header File
212////////////////////////////////////////////////////////////////////////////
213bool bncRinex::readSkeleton() {
214
215  bool readDone = false;
216
217  // Read the local file
218  // -------------------
219  QFile skl(_sklName);
220  if ( skl.exists() && skl.open(QIODevice::ReadOnly) ) {
221    QTextStream in(&skl);
222    if (_sklHeader.read(&in) == success) {
223      readDone = true;
224    }
225  }
226
227  // Read downloaded file
228  // --------------------
229  else if ( _ntripVersion != "N" && _ntripVersion != "UN" &&
230            _ntripVersion != "S" ) {
231    QDate currDate = currentDateAndTimeGPS().date();
232    if ( !_skeletonDate.isValid() || _skeletonDate != currDate ) {
233      if (downloadSkeleton() == success) {
234        readDone = true;
235        _skeletonDate = currDate;
236      }
237    } else if (_skeletonDate.isValid()) {
238      readDone = true;
239    }
240  }
241  return readDone;
242}
243
244// Next File Epoch (static)
245////////////////////////////////////////////////////////////////////////////
246QString bncRinex::nextEpochStr(const QDateTime& datTim,
247                               const QString& intStr, bool rnxV3,
248                               QDateTime* nextEpoch) {
249
250  QString epoStr = "";
251
252  QTime nextTime;
253  QDate nextDate;
254
255  int indHlp = intStr.indexOf("min");
256
257  if ( indHlp != -1) {
258    int step = intStr.left(indHlp-1).toInt();
259    if (rnxV3) {
260      epoStr +=  QString("%1").arg(datTim.time().hour(), 2, 10, QChar('0')); // H
261    } else {
262      epoStr +=  'A' + datTim.time().hour();
263    }
264
265    if (datTim.time().minute() >= 60-step) {
266      epoStr += QString("%1").arg(60-step, 2, 10, QChar('0'));               // M
267      if (datTim.time().hour() < 23) {
268        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
269        nextDate = datTim.date();
270      }
271      else {
272        nextTime.setHMS(0, 0, 0);
273        nextDate = datTim.date().addDays(1);
274      }
275    }
276    else {
277      for (int limit = step; limit <= 60-step; limit += step) {
278        if (datTim.time().minute() < limit) {
279          epoStr += QString("%1").arg(limit-step, 2, 10, QChar('0'));        // M
280          nextTime.setHMS(datTim.time().hour(), limit, 0);
281          nextDate = datTim.date();
282          break;
283        }
284      }
285    }
286    if (rnxV3) {
287      epoStr += QString("_%1M").arg(step, 2, 10, QChar('0'));                // period
288    }
289  }
290  else if (intStr == "1 hour") {
291    int step = intStr.left(indHlp-1).toInt();
292    if (rnxV3) {
293      epoStr += QString("%1").arg(datTim.time().hour(), 2, 10, QChar('0'));  // H
294      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                     // M
295      epoStr += QString("_%1H").arg(step+1, 2, 10, QChar('0'));              // period
296    } else {
297      epoStr +=  'A' + datTim.time().hour();
298    }
299    if (datTim.time().hour() < 23) {
300      nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
301      nextDate = datTim.date();
302    }
303    else {
304      nextTime.setHMS(0, 0, 0);
305      nextDate = datTim.date().addDays(1);
306    }
307  }
308  else {
309    int step = intStr.left(indHlp-1).toInt();
310    if (rnxV3) {
311      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                    // H
312      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                    // M
313      epoStr += QString("_%1D").arg(step+1, 2, 10, QChar('0'));             // period
314    } else {
315      epoStr = "0";
316    }
317    nextTime.setHMS(0, 0, 0);
318    nextDate = datTim.date().addDays(1);
319  }
320
321  if (nextEpoch) {
322   *nextEpoch = QDateTime(nextDate, nextTime);
323  }
324
325  return epoStr;
326}
327
328// File Name according to RINEX Standards
329////////////////////////////////////////////////////////////////////////////
330void bncRinex::resolveFileName(const QDateTime& datTim) {
331
332  bncSettings settings;
333  QString path = settings.value("rnxPath").toString();
334  expandEnvVar(path);
335
336  if ( path.length() > 0 && path[path.length()-1] != QDir::separator() ) {
337    path += QDir::separator();
338  }
339
340  QString hlpStr = nextEpochStr(datTim, settings.value("rnxIntr").toString(),
341                                _rnxV3, &_nextCloseEpoch);
342
343  int n = _statID.size();
344  int statIDlength;
345  if (n > 9) {
346    statIDlength = 9; // rnx3
347  }
348  else if (n > 4) {
349    statIDlength = 4; // rnx2
350  }
351  else {
352    statIDlength = n;
353  }
354  QString ID = _statID.left(statIDlength);
355  ID = ID.toUpper();
356
357  // Check name conflict
358  // -------------------
359  QString distStr;
360  int num = 0;
361  QListIterator<QString> it(settings.value("mountPoints").toStringList());
362  while (it.hasNext()) {
363    QString mp = it.next();
364    if (mp.indexOf(_statID.left(statIDlength)) != -1) {
365      ++num;
366    }
367  }
368  if (num > 1) {
369    distStr = "_" + _statID.right(1);
370  }
371
372  QString sklExt = settings.value("rnxSkel").toString();
373  if (!sklExt.isEmpty()) {
374    _sklName = path + ID + distStr + "." + sklExt;
375  }
376
377  if (_rnxV3) {
378    QString country;
379    QString monNum = "0";
380    QString recNum = "0";
381    if (statIDlength == 9) {
382      monNum = QChar(_statID[4]);
383      recNum = QChar(_statID[5]);
384    }
385    QListIterator<QString> it(settings.value("mountPoints").toStringList());
386    while (it.hasNext()) {
387      QStringList hlp = it.next().split(" ");
388      if (hlp.size() < 7)
389        continue;
390      if (hlp.join(" ").indexOf(_statID, 0) != -1) {
391        country = hlp[2].left(3);
392      }
393    }
394
395    path += ID.left(4) +
396            QString("%1").arg(monNum, 1, 10) +
397            QString("%1").arg(recNum, 1, 10) +
398            country +
399            "_S_" + // stream
400            QString("%1").arg(datTim.date().year()) +
401            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
402            hlpStr + // HMS_period
403            QString("%1").arg(_samplingRateStr) + // sampling rate
404            "_MO" + // mixed OBS
405            distStr +
406            ".rnx";
407  }
408  else {
409    path += ID.left(4) +
410            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
411            hlpStr + distStr + datTim.toString(".yyO");
412  }
413
414  _fName = path.toLatin1();
415}
416
417// Write RINEX Header
418////////////////////////////////////////////////////////////////////////////
419void bncRinex::writeHeader(const QByteArray& format, const bncTime& firstObsTime) {
420
421  bncSettings settings;
422
423  // Set RINEX Version
424  // -----------------
425  int intHeaderVers = (Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked ? 3 : 2);
426
427  // Open the Output File
428  // --------------------
429  QDateTime datTimNom  = dateAndTimeFromGPSweek(firstObsTime.gpsw(),
430                                                floor(firstObsTime.gpssec()+0.5));
431
432  resolveFileName(datTimNom);
433
434  // Read Skeleton Header
435  // --------------------
436  if (readSkeleton()) {
437    _header.set(_sklHeader, intHeaderVers);
438  }
439  else {
440    if (_writeRinexFileOnlyWithSkl) {
441      return;
442    }
443    _header.setDefault(_statID, intHeaderVers);
444  }
445
446  // Append to existing file and return
447  // ----------------------------------
448  if ( QFile::exists(_fName) &&
449       (_reconnectFlag || Qt::CheckState(settings.value("rnxAppend").toInt()) == Qt::Checked) ) {
450    _out.open(_fName.data(), ios::app);
451    _out.setf(ios::showpoint | ios::fixed);
452    _headerWritten = true;
453    _reconnectFlag = false;
454  }
455  else {
456    _out.open(_fName.data());
457    _addComments.clear();
458  }
459
460  _out.setf(ios::showpoint | ios::fixed);
461
462  // A Few Additional Comments
463  // -------------------------
464  _addComments << format.left(6) + " " + _mountPoint.host() + _mountPoint.path();
465  if (_nmea == "yes") {
466    _addComments << "NMEA LAT=" + _latitude + " " + "LONG=" + _longitude;
467  }
468
469  // Write the Header
470  // ----------------
471  QByteArray headerLines;
472  QTextStream outHlp(&headerLines);
473
474  QMap<QString, QString> txtMap;
475  txtMap["COMMENT"] = _addComments.join("\\n");
476
477  _header.setStartTime(firstObsTime);
478  _header.write(&outHlp, &txtMap);
479
480  outHlp.flush();
481
482  if (!_headerWritten) {
483    _out << headerLines.data();
484  }
485
486  _headerWritten = true;
487}
488
489// Stores Observation into Internal Array
490////////////////////////////////////////////////////////////////////////////
491void bncRinex::deepCopy(t_satObs obs) {
492  _obs.push_back(obs);
493}
494
495// Write One Epoch into the RINEX File
496////////////////////////////////////////////////////////////////////////////
497void bncRinex::dumpEpoch(const QByteArray& format, const bncTime& maxTime) {
498
499  // Select observations older than maxTime
500  // --------------------------------------
501  QList<t_satObs> obsList;
502  QMutableListIterator<t_satObs> mIt(_obs);
503  while (mIt.hasNext()) {
504    t_satObs obs = mIt.next();
505    if (obs._time < maxTime) {
506      obsList.push_back(obs);
507      mIt.remove();
508    }
509  }
510
511  // Easy Return
512  // -----------
513  if (obsList.isEmpty()) {
514    return;
515  }
516
517  // Time of Epoch
518  // -------------
519  const t_satObs& fObs = obsList.first();
520  QDateTime datTimNom  = dateAndTimeFromGPSweek(fObs._time.gpsw(), fObs._time.gpssec());
521
522  // Close the file
523  // --------------
524  if (_nextCloseEpoch.isValid() && datTimNom >= _nextCloseEpoch) {
525    closeFile();
526    _headerWritten = false;
527  }
528
529  // Write RINEX Header
530  // ------------------
531  if (!_headerWritten) {
532    writeHeader(format, fObs._time);
533  }
534  if (!_headerWritten) {
535    return;
536  }
537
538  // Prepare structure t_rnxEpo
539  // --------------------------
540  t_rnxObsFile::t_rnxEpo rnxEpo;
541  rnxEpo.tt = fObs._time;
542
543  QListIterator<t_satObs> it(obsList);
544  while (it.hasNext()) {
545    const t_satObs& satObs = it.next();
546    t_rnxObsFile::t_rnxSat rnxSat;
547    rnxSat.prn = satObs._prn;
548
549    // Initialize all observations mentioned in skeleton header
550    // --------------------------------------------------------
551    char sys = rnxSat.prn.system();
552    for (int iType = 0; iType < _sklHeader.nTypes(sys); iType++) {
553      QString type = _sklHeader.obsType(sys, iType);
554      t_rnxObsFile::t_rnxObs rnxObs; // create an empty observation
555      rnxSat.obs[type] = rnxObs;
556    }
557
558    for (unsigned ii = 0; ii < satObs._obs.size(); ii++) {
559      const t_frqObs* frqObs = satObs._obs[ii];
560      if (frqObs->_codeValid) {
561        QString type = 'C' + QString(frqObs->_rnxType2ch.c_str());
562        t_rnxObsFile::t_rnxObs rnxObs;
563        rnxObs.value = frqObs->_code;
564        rnxSat.obs[type] = rnxObs;
565      }
566      if (frqObs->_phaseValid) {
567        QString type = 'L' + QString(frqObs->_rnxType2ch.c_str());
568        t_rnxObsFile::t_rnxObs rnxObs;
569        rnxObs.value = frqObs->_phase;
570        if (frqObs->_slip) {
571          rnxObs.lli |= 1;
572        }
573        rnxSat.obs[type] = rnxObs;
574      }
575      if (frqObs->_dopplerValid) {
576        QString type = 'D' + QString(frqObs->_rnxType2ch.c_str());
577        t_rnxObsFile::t_rnxObs rnxObs;
578        rnxObs.value = frqObs->_doppler;
579        rnxSat.obs[type] = rnxObs;
580      }
581      if (frqObs->_snrValid) {
582        QString type = 'S' + QString(frqObs->_rnxType2ch.c_str());
583        t_rnxObsFile::t_rnxObs rnxObs;
584        rnxObs.value = frqObs->_snr;
585        rnxSat.obs[type] = rnxObs;
586      }
587    }
588
589
590    rnxEpo.rnxSat.push_back(rnxSat);
591  }
592
593  // Write the epoch
594  // ---------------
595  QByteArray outLines;
596  QTextStream outStream(&outLines);
597  t_rnxObsFile::writeEpoch(&outStream, _header, &rnxEpo);
598
599  _out << outLines.data();
600  _out.flush();
601}
602
603// Close the Old RINEX File
604////////////////////////////////////////////////////////////////////////////
605void bncRinex::closeFile() {
606
607  if (_header.version() == 3) {
608    _out << ">                              4  1" << endl;
609    _out << "END OF FILE" << endl;
610  }
611  _out.close();
612  if (!_rnxScriptName.isEmpty()) {
613    qApp->thread()->wait(100);
614#ifdef WIN32
615    QProcess::startDetached(_rnxScriptName, QStringList() << _fName) ;
616#else
617    QProcess::startDetached("nohup", QStringList() << _rnxScriptName << _fName) ;
618#endif
619
620  }
621}
622
623// One Line in ASCII (Internal) Format
624////////////////////////////////////////////////////////////////////////////
625string bncRinex::asciiSatLine(const t_satObs& obs, bool outLockTime) {
626
627  ostringstream str;
628  str.setf(ios::showpoint | ios::fixed);
629
630  str << obs._prn.toString();
631
632  for (unsigned ii = 0; ii < obs._obs.size(); ii++) {
633    const t_frqObs* frqObs = obs._obs[ii];
634    if (frqObs->_codeValid) {
635      str << ' '
636          << left  << setw(3)  << "C" + frqObs->_rnxType2ch << ' '
637          << right << setw(14) << setprecision(3) << frqObs->_code;
638    }
639    if (frqObs->_phaseValid) {
640      str << ' '
641          << left  << setw(3) << "L" + frqObs->_rnxType2ch << ' '
642          << right << setw(14) << setprecision(3) << frqObs->_phase << ' '
643          << right << setw(4)                     << frqObs->_slipCounter;
644    }
645    if (frqObs->_dopplerValid) {
646      str << ' '
647          << left  << setw(3) << "D" + frqObs->_rnxType2ch << ' '
648          << right << setw(14) << setprecision(3) << frqObs->_doppler;
649    }
650    if (frqObs->_snrValid) {
651      str << ' '
652          << left  << setw(3) << "S" + frqObs->_rnxType2ch << ' '
653          << right << setw(8) << setprecision(3) << frqObs->_snr;
654    }
655    if (frqObs->_lockTimeValid && outLockTime) {
656      str << ' '
657          << left  << setw(3) << "T" + frqObs->_rnxType2ch << ' '
658          << right << setw(9) << setprecision(3) << frqObs->_lockTime;
659    }
660  }
661
662  return str.str();
663}
Note: See TracBrowser for help on using the repository browser.