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

Last change on this file since 8664 was 8664, checked in by stuerze, 5 months ago

minor changes reagarding skl filenames

File size: 18.6 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 statIDlength = _statID.size() -1;
344  QString ID = _statID.left(statIDlength);
345  ID = ID.toUpper();
346
347  // Check name conflict
348  // -------------------
349  QString distStr;
350  int num = 0;
351  QListIterator<QString> it(settings.value("mountPoints").toStringList());
352  while (it.hasNext()) {
353    QString mp = it.next();
354    if (mp.indexOf(_statID.left(statIDlength)) != -1) {
355      ++num;
356    }
357  }
358  if (num > 1) {
359    distStr = "_" + _statID.right(1);
360  }
361
362  QString sklExt = settings.value("rnxSkel").toString();
363  if (!sklExt.isEmpty()) {
364    _sklName = path + ID + distStr + "." + sklExt;
365  }
366
367  if (_rnxV3) {
368    QString country;
369    QString monNum = "0";
370    QString recNum = "0";
371    QListIterator<QString> it(settings.value("mountPoints").toStringList());
372    while (it.hasNext()) {
373      QStringList hlp = it.next().split(" ");
374      if (hlp.size() < 7)
375        continue;
376      if (hlp.join(" ").indexOf(_statID, 0) != -1) {
377        country = hlp[2];
378      }
379    }
380
381    path += ID.left(4) +
382            QString("%1").arg(monNum, 1, 10) +
383            QString("%1").arg(recNum, 1, 10) +
384            country +
385            "_S_" + // stream
386            QString("%1").arg(datTim.date().year()) +
387            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
388            hlpStr + // HMS_period
389            QString("%1").arg(_samplingRateStr) + // sampling rate
390            "_MO" + // mixed OBS
391            distStr +
392            ".rnx";
393  }
394  else {
395    path += ID.left(4) +
396            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
397            hlpStr + distStr + datTim.toString(".yyO");
398  }
399
400  _fName = path.toLatin1();
401}
402
403// Write RINEX Header
404////////////////////////////////////////////////////////////////////////////
405void bncRinex::writeHeader(const QByteArray& format, const bncTime& firstObsTime) {
406
407  bncSettings settings;
408
409  // Set RINEX Version
410  // -----------------
411  int intHeaderVers = (Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked ? 3 : 2);
412
413  // Open the Output File
414  // --------------------
415  QDateTime datTimNom  = dateAndTimeFromGPSweek(firstObsTime.gpsw(),
416                                                floor(firstObsTime.gpssec()+0.5));
417
418  resolveFileName(datTimNom);
419
420  // Read Skeleton Header
421  // --------------------
422  if (readSkeleton()) {
423    _header.set(_sklHeader, intHeaderVers);
424  }
425  else {
426    if (_writeRinexFileOnlyWithSkl) {
427      return;
428    }
429    _header.setDefault(_statID, intHeaderVers);
430  }
431
432  // Append to existing file and return
433  // ----------------------------------
434  if ( QFile::exists(_fName) &&
435       (_reconnectFlag || Qt::CheckState(settings.value("rnxAppend").toInt()) == Qt::Checked) ) {
436    _out.open(_fName.data(), ios::app);
437    _out.setf(ios::showpoint | ios::fixed);
438    _headerWritten = true;
439    _reconnectFlag = false;
440  }
441  else {
442    _out.open(_fName.data());
443    _addComments.clear();
444  }
445
446  _out.setf(ios::showpoint | ios::fixed);
447
448  // A Few Additional Comments
449  // -------------------------
450  _addComments << format.left(6) + " " + _mountPoint.host() + _mountPoint.path();
451  if (_nmea == "yes") {
452    _addComments << "NMEA LAT=" + _latitude + " " + "LONG=" + _longitude;
453  }
454
455  // Write the Header
456  // ----------------
457  QByteArray headerLines;
458  QTextStream outHlp(&headerLines);
459
460  QMap<QString, QString> txtMap;
461  txtMap["COMMENT"] = _addComments.join("\\n");
462
463  _header.setStartTime(firstObsTime);
464  _header.write(&outHlp, &txtMap);
465
466  outHlp.flush();
467
468  if (!_headerWritten) {
469    _out << headerLines.data();
470  }
471
472  _headerWritten = true;
473}
474
475// Stores Observation into Internal Array
476////////////////////////////////////////////////////////////////////////////
477void bncRinex::deepCopy(t_satObs obs) {
478  _obs.push_back(obs);
479}
480
481// Write One Epoch into the RINEX File
482////////////////////////////////////////////////////////////////////////////
483void bncRinex::dumpEpoch(const QByteArray& format, const bncTime& maxTime) {
484
485  // Select observations older than maxTime
486  // --------------------------------------
487  QList<t_satObs> obsList;
488  QMutableListIterator<t_satObs> mIt(_obs);
489  while (mIt.hasNext()) {
490    t_satObs obs = mIt.next();
491    if (obs._time < maxTime) {
492      obsList.push_back(obs);
493      mIt.remove();
494    }
495  }
496
497  // Easy Return
498  // -----------
499  if (obsList.isEmpty()) {
500    return;
501  }
502
503  // Time of Epoch
504  // -------------
505  const t_satObs& fObs = obsList.first();
506  QDateTime datTimNom  = dateAndTimeFromGPSweek(fObs._time.gpsw(), fObs._time.gpssec());
507
508  // Close the file
509  // --------------
510  if (_nextCloseEpoch.isValid() && datTimNom >= _nextCloseEpoch) {
511    closeFile();
512    _headerWritten = false;
513  }
514
515  // Write RINEX Header
516  // ------------------
517  if (!_headerWritten) {
518    writeHeader(format, fObs._time);
519  }
520  if (!_headerWritten) {
521    return;
522  }
523
524  // Prepare structure t_rnxEpo
525  // --------------------------
526  t_rnxObsFile::t_rnxEpo rnxEpo;
527  rnxEpo.tt = fObs._time;
528
529  QListIterator<t_satObs> it(obsList);
530  while (it.hasNext()) {
531    const t_satObs& satObs = it.next();
532    t_rnxObsFile::t_rnxSat rnxSat;
533    rnxSat.prn = satObs._prn;
534
535    // Initialize all observations mentioned in skeleton header
536    // --------------------------------------------------------
537    char sys = rnxSat.prn.system();
538    for (int iType = 0; iType < _sklHeader.nTypes(sys); iType++) {
539      QString type = _sklHeader.obsType(sys, iType);
540      t_rnxObsFile::t_rnxObs rnxObs; // create an empty observation
541      rnxSat.obs[type] = rnxObs;
542    }
543
544    for (unsigned ii = 0; ii < satObs._obs.size(); ii++) {
545      const t_frqObs* frqObs = satObs._obs[ii];
546      if (frqObs->_codeValid) {
547        QString type = 'C' + QString(frqObs->_rnxType2ch.c_str());
548        t_rnxObsFile::t_rnxObs rnxObs;
549        rnxObs.value = frqObs->_code;
550        rnxSat.obs[type] = rnxObs;
551      }
552      if (frqObs->_phaseValid) {
553        QString type = 'L' + QString(frqObs->_rnxType2ch.c_str());
554        t_rnxObsFile::t_rnxObs rnxObs;
555        rnxObs.value = frqObs->_phase;
556        if (frqObs->_slip) {
557          rnxObs.lli |= 1;
558        }
559        rnxSat.obs[type] = rnxObs;
560      }
561      if (frqObs->_dopplerValid) {
562        QString type = 'D' + QString(frqObs->_rnxType2ch.c_str());
563        t_rnxObsFile::t_rnxObs rnxObs;
564        rnxObs.value = frqObs->_doppler;
565        rnxSat.obs[type] = rnxObs;
566      }
567      if (frqObs->_snrValid) {
568        QString type = 'S' + QString(frqObs->_rnxType2ch.c_str());
569        t_rnxObsFile::t_rnxObs rnxObs;
570        rnxObs.value = frqObs->_snr;
571        rnxSat.obs[type] = rnxObs;
572      }
573    }
574
575
576    rnxEpo.rnxSat.push_back(rnxSat);
577  }
578
579  // Write the epoch
580  // ---------------
581  QByteArray outLines;
582  QTextStream outStream(&outLines);
583  t_rnxObsFile::writeEpoch(&outStream, _header, &rnxEpo);
584
585  _out << outLines.data();
586  _out.flush();
587}
588
589// Close the Old RINEX File
590////////////////////////////////////////////////////////////////////////////
591void bncRinex::closeFile() {
592
593  if (_header.version() == 3) {
594    _out << ">                              4  1" << endl;
595    _out << "END OF FILE" << endl;
596  }
597  _out.close();
598  if (!_rnxScriptName.isEmpty()) {
599    qApp->thread()->wait(100);
600#ifdef WIN32
601    QProcess::startDetached(_rnxScriptName, QStringList() << _fName) ;
602#else
603    QProcess::startDetached("nohup", QStringList() << _rnxScriptName << _fName) ;
604#endif
605
606  }
607}
608
609// One Line in ASCII (Internal) Format
610////////////////////////////////////////////////////////////////////////////
611string bncRinex::asciiSatLine(const t_satObs& obs, bool outLockTime) {
612
613  ostringstream str;
614  str.setf(ios::showpoint | ios::fixed);
615
616  str << obs._prn.toString();
617
618  for (unsigned ii = 0; ii < obs._obs.size(); ii++) {
619    const t_frqObs* frqObs = obs._obs[ii];
620    if (frqObs->_codeValid) {
621      str << ' '
622          << left  << setw(3)  << "C" + frqObs->_rnxType2ch << ' '
623          << right << setw(14) << setprecision(3) << frqObs->_code;
624    }
625    if (frqObs->_phaseValid) {
626      str << ' '
627          << left  << setw(3) << "L" + frqObs->_rnxType2ch << ' '
628          << right << setw(14) << setprecision(3) << frqObs->_phase << ' '
629          << right << setw(4)                     << frqObs->_slipCounter;
630    }
631    if (frqObs->_dopplerValid) {
632      str << ' '
633          << left  << setw(3) << "D" + frqObs->_rnxType2ch << ' '
634          << right << setw(14) << setprecision(3) << frqObs->_doppler;
635    }
636    if (frqObs->_snrValid) {
637      str << ' '
638          << left  << setw(3) << "S" + frqObs->_rnxType2ch << ' '
639          << right << setw(8) << setprecision(3) << frqObs->_snr;
640    }
641    if (frqObs->_lockTimeValid && outLockTime) {
642      str << ' '
643          << left  << setw(3) << "T" + frqObs->_rnxType2ch << ' '
644          << right << setw(9) << setprecision(3) << frqObs->_lockTime;
645    }
646  }
647
648  return str.str();
649}
Note: See TracBrowser for help on using the repository browser.