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

Last change on this file since 8792 was 8792, checked in by stuerze, 10 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.