source: ntrip/branches/BNC_2.12/src/bncrinex.cpp @ 8668

Last change on this file since 8668 was 8668, checked in by stuerze, 16 months ago

some updates to support RINEX Version 3.04 filenames from short or erromeous sourcetable entries

File size: 18.8 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).toAscii()) {
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    delete query;
205  }
206
207  return irc;
208}
209
210// Read Skeleton Header File
211////////////////////////////////////////////////////////////////////////////
212bool bncRinex::readSkeleton() {
213
214  bool readDone = false;
215
216  // Read the local file
217  // -------------------
218  QFile skl(_sklName);
219  if ( skl.exists() && skl.open(QIODevice::ReadOnly) ) {
220    QTextStream in(&skl);
221    if (_sklHeader.read(&in) == success) {
222      readDone = true;
223    }
224  }
225
226  // Read downloaded file
227  // --------------------
228  else if ( _ntripVersion != "N" && _ntripVersion != "UN" &&
229            _ntripVersion != "S" ) {
230    QDate currDate = currentDateAndTimeGPS().date();
231    if ( !_skeletonDate.isValid() || _skeletonDate != currDate ) {
232      if (downloadSkeleton() == success) {
233        readDone = true;
234        _skeletonDate = currDate;
235      }
236    } else if (_skeletonDate.isValid()) {
237      readDone = true;
238    }
239  }
240  return readDone;
241}
242
243// Next File Epoch (static)
244////////////////////////////////////////////////////////////////////////////
245QString bncRinex::nextEpochStr(const QDateTime& datTim,
246                               const QString& intStr, bool rnxV3,
247                               QDateTime* nextEpoch) {
248
249  QString epoStr = "";
250
251  QTime nextTime;
252  QDate nextDate;
253
254  int indHlp = intStr.indexOf("min");
255
256  if ( indHlp != -1) {
257    int step = intStr.left(indHlp-1).toInt();
258    if (rnxV3) {
259      epoStr +=  QString("%1").arg(datTim.time().hour(), 2, 10, QChar('0')); // H
260    } else {
261      epoStr +=  'A' + datTim.time().hour();
262    }
263
264    if (datTim.time().minute() >= 60-step) {
265      epoStr += QString("%1").arg(60-step, 2, 10, QChar('0'));               // M
266      if (datTim.time().hour() < 23) {
267        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
268        nextDate = datTim.date();
269      }
270      else {
271        nextTime.setHMS(0, 0, 0);
272        nextDate = datTim.date().addDays(1);
273      }
274    }
275    else {
276      for (int limit = step; limit <= 60-step; limit += step) {
277        if (datTim.time().minute() < limit) {
278          epoStr += QString("%1").arg(limit-step, 2, 10, QChar('0'));        // M
279          nextTime.setHMS(datTim.time().hour(), limit, 0);
280          nextDate = datTim.date();
281          break;
282        }
283      }
284    }
285    if (rnxV3) {
286      epoStr += QString("_%1M").arg(step, 2, 10, QChar('0'));                // period
287    }
288  }
289  else if (intStr == "1 hour") {
290    int step = intStr.left(indHlp-1).toInt();
291    if (rnxV3) {
292      epoStr += QString("%1").arg(datTim.time().hour(), 2, 10, QChar('0'));  // H
293      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                     // M
294      epoStr += QString("_%1H").arg(step+1, 2, 10, QChar('0'));              // period
295    } else {
296      epoStr +=  'A' + datTim.time().hour();
297    }
298    if (datTim.time().hour() < 23) {
299      nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
300      nextDate = datTim.date();
301    }
302    else {
303      nextTime.setHMS(0, 0, 0);
304      nextDate = datTim.date().addDays(1);
305    }
306  }
307  else {
308    int step = intStr.left(indHlp-1).toInt();
309    if (rnxV3) {
310      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                    // H
311      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                    // M
312      epoStr += QString("_%1D").arg(step+1, 2, 10, QChar('0'));             // period
313    } else {
314      epoStr = "0";
315    }
316    nextTime.setHMS(0, 0, 0);
317    nextDate = datTim.date().addDays(1);
318  }
319
320  if (nextEpoch) {
321   *nextEpoch = QDateTime(nextDate, nextTime);
322  }
323
324  return epoStr;
325}
326
327// File Name according to RINEX Standards
328////////////////////////////////////////////////////////////////////////////
329void bncRinex::resolveFileName(const QDateTime& datTim) {
330
331  bncSettings settings;
332  QString path = settings.value("rnxPath").toString();
333  expandEnvVar(path);
334
335  if ( path.length() > 0 && path[path.length()-1] != QDir::separator() ) {
336    path += QDir::separator();
337  }
338
339  QString hlpStr = nextEpochStr(datTim, settings.value("rnxIntr").toString(),
340                                _rnxV3, &_nextCloseEpoch);
341
342  int n = _statID.size();
343  int statIDlength;
344  if (n > 9) {
345    statIDlength = 9; // rnx3
346  }
347  else if (n > 4) {
348    statIDlength = 4; // rnx2
349  }
350  else {
351    statIDlength = n;
352  }
353  QString ID = _statID.left(statIDlength);
354  ID = ID.toUpper();
355
356  // Check name conflict
357  // -------------------
358  QString distStr;
359  int num = 0;
360  QListIterator<QString> it(settings.value("mountPoints").toStringList());
361  while (it.hasNext()) {
362    QString mp = it.next();
363    if (mp.indexOf(_statID.left(statIDlength)) != -1) {
364      ++num;
365    }
366  }
367  if (num > 1) {
368    distStr = "_" + _statID.right(1);
369  }
370
371  QString sklExt = settings.value("rnxSkel").toString();
372  if (!sklExt.isEmpty()) {
373    _sklName = path + ID + distStr + "." + sklExt;
374  }
375
376  if (_rnxV3) {
377    QString country;
378    QString monNum = "0";
379    QString recNum = "0";
380    QListIterator<QString> it(settings.value("mountPoints").toStringList());
381    while (it.hasNext()) {
382      QStringList hlp = it.next().split(" ");
383      if (hlp.size() < 7)
384        continue;
385      if (hlp.join(" ").indexOf(_statID, 0) != -1) {
386        country = hlp[2].left(3);
387      }
388    }
389
390    path += ID.left(4) +
391            QString("%1").arg(monNum, 1, 10) +
392            QString("%1").arg(recNum, 1, 10) +
393            country +
394            "_S_" + // stream
395            QString("%1").arg(datTim.date().year()) +
396            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
397            hlpStr + // HMS_period
398            QString("%1").arg(_samplingRateStr) + // sampling rate
399            "_MO" + // mixed OBS
400            distStr +
401            ".rnx";
402  }
403  else {
404    path += ID.left(4) +
405            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
406            hlpStr + distStr + datTim.toString(".yyO");
407  }
408
409  _fName = path.toAscii();
410}
411
412// Write RINEX Header
413////////////////////////////////////////////////////////////////////////////
414void bncRinex::writeHeader(const QByteArray& format, const bncTime& firstObsTime) {
415
416  bncSettings settings;
417
418  // Set RINEX Version
419  // -----------------
420  int intHeaderVers = (Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked ? 3 : 2);
421
422  // Open the Output File
423  // --------------------
424  QDateTime datTimNom  = dateAndTimeFromGPSweek(firstObsTime.gpsw(),
425                                                floor(firstObsTime.gpssec()+0.5));
426
427  resolveFileName(datTimNom);
428
429  // Read Skeleton Header
430  // --------------------
431  if (readSkeleton()) {
432    _header.set(_sklHeader, intHeaderVers);
433  }
434  else {
435    if (_writeRinexFileOnlyWithSkl) {
436      return;
437    }
438    _header.setDefault(_statID, intHeaderVers);
439  }
440
441  // Append to existing file and return
442  // ----------------------------------
443  if ( QFile::exists(_fName) &&
444       (_reconnectFlag || Qt::CheckState(settings.value("rnxAppend").toInt()) == Qt::Checked) ) {
445    _out.open(_fName.data(), ios::app);
446    _out.setf(ios::showpoint | ios::fixed);
447    _headerWritten = true;
448    _reconnectFlag = false;
449  }
450  else {
451    _out.open(_fName.data());
452    _addComments.clear();
453  }
454
455  _out.setf(ios::showpoint | ios::fixed);
456
457  // A Few Additional Comments
458  // -------------------------
459  _addComments << format.left(6) + " " + _mountPoint.host() + _mountPoint.path();
460  if (_nmea == "yes") {
461    _addComments << "NMEA LAT=" + _latitude + " " + "LONG=" + _longitude;
462  }
463
464  // Write the Header
465  // ----------------
466  QByteArray headerLines;
467  QTextStream outHlp(&headerLines);
468
469  QMap<QString, QString> txtMap;
470  txtMap["COMMENT"] = _addComments.join("\\n");
471
472  _header.setStartTime(firstObsTime);
473  _header.write(&outHlp, &txtMap);
474
475  outHlp.flush();
476
477  if (!_headerWritten) {
478    _out << headerLines.data();
479  }
480
481  _headerWritten = true;
482}
483
484// Stores Observation into Internal Array
485////////////////////////////////////////////////////////////////////////////
486void bncRinex::deepCopy(t_satObs obs) {
487  _obs.push_back(obs);
488}
489
490// Write One Epoch into the RINEX File
491////////////////////////////////////////////////////////////////////////////
492void bncRinex::dumpEpoch(const QByteArray& format, const bncTime& maxTime) {
493
494  // Select observations older than maxTime
495  // --------------------------------------
496  QList<t_satObs> obsList;
497  QMutableListIterator<t_satObs> mIt(_obs);
498  while (mIt.hasNext()) {
499    t_satObs obs = mIt.next();
500    if (obs._time < maxTime) {
501      obsList.push_back(obs);
502      mIt.remove();
503    }
504  }
505
506  // Easy Return
507  // -----------
508  if (obsList.isEmpty()) {
509    return;
510  }
511
512  // Time of Epoch
513  // -------------
514  const t_satObs& fObs = obsList.first();
515  QDateTime datTimNom  = dateAndTimeFromGPSweek(fObs._time.gpsw(), fObs._time.gpssec());
516
517  // Close the file
518  // --------------
519  if (_nextCloseEpoch.isValid() && datTimNom >= _nextCloseEpoch) {
520    closeFile();
521    _headerWritten = false;
522  }
523
524  // Write RINEX Header
525  // ------------------
526  if (!_headerWritten) {
527    writeHeader(format, fObs._time);
528  }
529  if (!_headerWritten) {
530    return;
531  }
532
533  // Prepare structure t_rnxEpo
534  // --------------------------
535  t_rnxObsFile::t_rnxEpo rnxEpo;
536  rnxEpo.tt = fObs._time;
537
538  QListIterator<t_satObs> it(obsList);
539  while (it.hasNext()) {
540    const t_satObs& satObs = it.next();
541    t_rnxObsFile::t_rnxSat rnxSat;
542    rnxSat.prn = satObs._prn;
543
544    // Initialize all observations mentioned in skeleton header
545    // --------------------------------------------------------
546    char sys = rnxSat.prn.system();
547    for (int iType = 0; iType < _sklHeader.nTypes(sys); iType++) {
548      QString type = _sklHeader.obsType(sys, iType);
549      t_rnxObsFile::t_rnxObs rnxObs; // create an empty observation
550      rnxSat.obs[type] = rnxObs;
551    }
552
553    for (unsigned ii = 0; ii < satObs._obs.size(); ii++) {
554      const t_frqObs* frqObs = satObs._obs[ii];
555      if (frqObs->_codeValid) {
556        QString type = 'C' + QString(frqObs->_rnxType2ch.c_str());
557        t_rnxObsFile::t_rnxObs rnxObs;
558        rnxObs.value = frqObs->_code;
559        rnxSat.obs[type] = rnxObs;
560      }
561      if (frqObs->_phaseValid) {
562        QString type = 'L' + QString(frqObs->_rnxType2ch.c_str());
563        t_rnxObsFile::t_rnxObs rnxObs;
564        rnxObs.value = frqObs->_phase;
565        if (frqObs->_slip) {
566          rnxObs.lli |= 1;
567        }
568        rnxSat.obs[type] = rnxObs;
569      }
570      if (frqObs->_dopplerValid) {
571        QString type = 'D' + QString(frqObs->_rnxType2ch.c_str());
572        t_rnxObsFile::t_rnxObs rnxObs;
573        rnxObs.value = frqObs->_doppler;
574        rnxSat.obs[type] = rnxObs;
575      }
576      if (frqObs->_snrValid) {
577        QString type = 'S' + QString(frqObs->_rnxType2ch.c_str());
578        t_rnxObsFile::t_rnxObs rnxObs;
579        rnxObs.value = frqObs->_snr;
580        rnxSat.obs[type] = rnxObs;
581      }
582    }
583
584
585    rnxEpo.rnxSat.push_back(rnxSat);
586  }
587
588  // Write the epoch
589  // ---------------
590  QByteArray outLines;
591  QTextStream outStream(&outLines);
592  t_rnxObsFile::writeEpoch(&outStream, _header, &rnxEpo);
593
594  _out << outLines.data();
595  _out.flush();
596}
597
598// Close the Old RINEX File
599////////////////////////////////////////////////////////////////////////////
600void bncRinex::closeFile() {
601
602  if (_header.version() == 3) {
603    _out << ">                              4  1" << endl;
604    _out << "END OF FILE" << endl;
605  }
606  _out.close();
607  if (!_rnxScriptName.isEmpty()) {
608    qApp->thread()->wait(100);
609#ifdef WIN32
610    QProcess::startDetached(_rnxScriptName, QStringList() << _fName) ;
611#else
612    QProcess::startDetached("nohup", QStringList() << _rnxScriptName << _fName) ;
613#endif
614
615  }
616}
617
618// One Line in ASCII (Internal) Format
619////////////////////////////////////////////////////////////////////////////
620string bncRinex::asciiSatLine(const t_satObs& obs, bool outLockTime) {
621
622  ostringstream str;
623  str.setf(ios::showpoint | ios::fixed);
624
625  str << obs._prn.toString();
626
627  for (unsigned ii = 0; ii < obs._obs.size(); ii++) {
628    const t_frqObs* frqObs = obs._obs[ii];
629    if (frqObs->_codeValid) {
630      str << ' '
631          << left  << setw(3)  << "C" + frqObs->_rnxType2ch << ' '
632          << right << setw(14) << setprecision(3) << frqObs->_code;
633    }
634    if (frqObs->_phaseValid) {
635      str << ' '
636          << left  << setw(3) << "L" + frqObs->_rnxType2ch << ' '
637          << right << setw(14) << setprecision(3) << frqObs->_phase << ' '
638          << right << setw(4)                     << frqObs->_slipCounter;
639    }
640    if (frqObs->_dopplerValid) {
641      str << ' '
642          << left  << setw(3) << "D" + frqObs->_rnxType2ch << ' '
643          << right << setw(14) << setprecision(3) << frqObs->_doppler;
644    }
645    if (frqObs->_snrValid) {
646      str << ' '
647          << left  << setw(3) << "S" + frqObs->_rnxType2ch << ' '
648          << right << setw(8) << setprecision(3) << frqObs->_snr;
649    }
650    if (frqObs->_lockTimeValid && outLockTime) {
651      str << ' '
652          << left  << setw(3) << "T" + frqObs->_rnxType2ch << ' '
653          << right << setw(9) << setprecision(3) << frqObs->_lockTime;
654    }
655  }
656
657  return str.str();
658}
Note: See TracBrowser for help on using the repository browser.