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

Last change on this file since 8351 was 8351, checked in by stuerze, 10 months ago

individual satellite system will be considerred now during concatenation of ephemeris files

File size: 17.4 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  _samplingRate = settings.value("rnxSampl").toInt();
93
94  _writeRinexFileOnlyWithSkl = settings.value("rnxOnlyWithSKL").toBool();
95
96  _rnxV3 = (Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked) ? true : false;
97}
98
99// Destructor
100////////////////////////////////////////////////////////////////////////////
101bncRinex::~bncRinex() {
102  bncSettings settings;
103  if ((_header.version() >= 3.0) && ( Qt::CheckState(settings.value("rnxAppend").toInt()) != Qt::Checked) ) {
104    _out << ">                              4  1" << endl;
105    _out << "END OF FILE" << endl;
106  }
107  _out.close();
108}
109
110// Download Skeleton Header File
111////////////////////////////////////////////////////////////////////////////
112t_irc bncRinex::downloadSkeleton() {
113
114  t_irc irc = failure;
115
116  QStringList table;
117  bncTableDlg::getFullTable(_ntripVersion, _mountPoint.host(),
118                            _mountPoint.port(), table, true);
119  QString net;
120  QStringListIterator it(table);
121  while (it.hasNext()) {
122    QString line = it.next();
123    if (line.indexOf("STR") == 0) {
124      QStringList tags = line.split(";");
125      if (tags.size() > 7) {
126        if (tags.at(1) == _mountPoint.path().mid(1).toAscii()) {
127          net = tags.at(7);
128          break;
129        }
130      }
131    }
132  }
133  QString sklDir;
134  if (!net.isEmpty()) {
135    it.toFront();
136    while (it.hasNext()) {
137      QString line = it.next();
138      if (line.indexOf("NET") == 0) {
139        QStringList tags = line.split(";");
140        if (tags.size() > 6) {
141          if (tags.at(1) == net) {
142            sklDir = tags.at(6).trimmed();
143            break;
144          }
145        }
146      }
147    }
148  }
149  if (!sklDir.isEmpty() && sklDir != "none") {
150    QUrl url(sklDir + "/" + _mountPoint.path().mid(1,4).toLower() + ".skl");
151    if (url.port() == -1) {
152      if (sklDir.contains("https", Qt::CaseInsensitive)) {
153        url.setPort(443);
154      }
155      else {
156        url.setPort(80);
157      }
158    }
159
160    bncNetQuery* query = new bncNetQueryV2(true);
161    QByteArray outData;
162    query->waitForRequestResult(url, outData);
163    if (query->status() == bncNetQuery::finished &&
164        outData.contains("END OF HEADER")) {
165      QTextStream in(outData);
166      irc = _sklHeader.read(&in);
167    }
168
169    delete query;
170  }
171
172  return irc;
173}
174
175// Read Skeleton Header File
176////////////////////////////////////////////////////////////////////////////
177bool bncRinex::readSkeleton() {
178
179  bool readDone = false;
180
181  // Read the local file
182  // -------------------
183  QFile skl(_sklName);
184  if ( skl.exists() && skl.open(QIODevice::ReadOnly) ) {
185    QTextStream in(&skl);
186    if (_sklHeader.read(&in) == success) {
187      readDone = true;
188    }
189  }
190
191  // Read downloaded file
192  // --------------------
193  else if ( _ntripVersion != "N" && _ntripVersion != "UN" &&
194            _ntripVersion != "S" ) {
195    QDate currDate = currentDateAndTimeGPS().date();
196    if ( !_skeletonDate.isValid() || _skeletonDate != currDate ) {
197      if (downloadSkeleton() == success) {
198        readDone = true;
199        _skeletonDate = currDate;
200      }
201    } else if (_skeletonDate.isValid()) {
202      readDone = true;
203    }
204  }
205  return readDone;
206}
207
208// Next File Epoch (static)
209////////////////////////////////////////////////////////////////////////////
210QString bncRinex::nextEpochStr(const QDateTime& datTim,
211                               const QString& intStr, bool rnxV3,
212                               QDateTime* nextEpoch) {
213
214  QString epoStr = "";
215
216  QTime nextTime;
217  QDate nextDate;
218
219  int indHlp = intStr.indexOf("min");
220
221  if ( indHlp != -1) {
222    int step = intStr.left(indHlp-1).toInt();
223    if (rnxV3) {
224      epoStr +=  QString("%1").arg(datTim.time().hour(), 2, 10, QChar('0')); // H
225    } else {
226      epoStr +=  'A' + datTim.time().hour();
227    }
228
229    if (datTim.time().minute() >= 60-step) {
230      epoStr += QString("%1").arg(60-step, 2, 10, QChar('0'));               // M
231      if (datTim.time().hour() < 23) {
232        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
233        nextDate = datTim.date();
234      }
235      else {
236        nextTime.setHMS(0, 0, 0);
237        nextDate = datTim.date().addDays(1);
238      }
239    }
240    else {
241      for (int limit = step; limit <= 60-step; limit += step) {
242        if (datTim.time().minute() < limit) {
243          epoStr += QString("%1").arg(limit-step, 2, 10, QChar('0'));        // M
244          nextTime.setHMS(datTim.time().hour(), limit, 0);
245          nextDate = datTim.date();
246          break;
247        }
248      }
249    }
250    if (rnxV3) {
251      epoStr += QString("_%1M").arg(step, 2, 10, QChar('0'));                // period
252    }
253  }
254  else if (intStr == "1 hour") {
255    int step = intStr.left(indHlp-1).toInt();
256    if (rnxV3) {
257      epoStr += QString("%1").arg(datTim.time().hour(), 2, 10, QChar('0'));  // H
258      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                     // M
259      epoStr += QString("_%1H").arg(step+1, 2, 10, QChar('0'));              // period
260    } else {
261      epoStr +=  'A' + datTim.time().hour();
262    }
263    if (datTim.time().hour() < 23) {
264      nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
265      nextDate = datTim.date();
266    }
267    else {
268      nextTime.setHMS(0, 0, 0);
269      nextDate = datTim.date().addDays(1);
270    }
271  }
272  else {
273    int step = intStr.left(indHlp-1).toInt();
274    if (rnxV3) {
275      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                    // H
276      epoStr += QString("%1").arg(0, 2, 10, QChar('0'));                    // M
277      epoStr += QString("_%1D").arg(step+1, 2, 10, QChar('0'));             // period
278    } else {
279      epoStr = "0";
280    }
281    nextTime.setHMS(0, 0, 0);
282    nextDate = datTim.date().addDays(1);
283  }
284
285  if (nextEpoch) {
286   *nextEpoch = QDateTime(nextDate, nextTime);
287  }
288
289  return epoStr;
290}
291
292// File Name according to RINEX Standards
293////////////////////////////////////////////////////////////////////////////
294void bncRinex::resolveFileName(const QDateTime& datTim) {
295
296  bncSettings settings;
297  QString path = settings.value("rnxPath").toString();
298  expandEnvVar(path);
299
300  if ( path.length() > 0 && path[path.length()-1] != QDir::separator() ) {
301    path += QDir::separator();
302  }
303
304  QString hlpStr = nextEpochStr(datTim, settings.value("rnxIntr").toString(),
305                                _rnxV3, &_nextCloseEpoch);
306
307  int statIDlength = _statID.size() -1;
308  QString ID4 = _statID.left(4);
309  ID4 = ID4.toUpper();
310
311  // Check name conflict
312  // -------------------
313  QString distStr;
314  int num = 0;
315  QListIterator<QString> it(settings.value("mountPoints").toStringList());
316  while (it.hasNext()) {
317    QString mp = it.next();
318    if (mp.indexOf(_statID.left(statIDlength)) != -1) {
319      ++num;
320    }
321  }
322  if (num > 1) {
323    distStr = "_" + _statID.right(1);
324  }
325
326  QString sklExt = settings.value("rnxSkel").toString();
327  QString sklPre;
328  if (!sklExt.isEmpty()) {
329    if (sklExt.indexOf("skl",0, Qt::CaseSensitive) != -1)  {
330      sklPre = ID4.toLower();
331    } else if (sklExt.indexOf("SKL",0, Qt::CaseSensitive) != -1) {
332      sklPre = ID4.toUpper();
333    }
334    _sklName = path + sklPre + distStr + "." + sklExt;
335  }
336
337  if (_rnxV3) {
338    QString country;
339    QString monNum = "0";
340    QString recNum = "0";
341    QListIterator<QString> it(settings.value("mountPoints").toStringList());
342    while (it.hasNext()) {
343      QStringList hlp = it.next().split(" ");
344      if (hlp.size() < 7)
345        continue;
346      if (hlp.join(" ").indexOf(_statID, 0) != -1) {
347        country = hlp[2];
348      }
349    }
350    int sampl = settings.value("rnxSampl").toString().mid(0,2).toInt();
351    if (!sampl)
352      sampl++;
353    path += ID4 +
354            QString("%1").arg(monNum, 1, 10) +
355            QString("%1").arg(recNum, 1, 10) +
356            country +
357            "_S_" + // stream
358            QString("%1").arg(datTim.date().year()) +
359            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
360            hlpStr + // HMS_period
361            QString("_%1S").arg(sampl, 2, 10, QChar('0')) + // sampling rate
362            "_MO" + // mixed OBS
363            distStr +
364            ".rnx";
365  }
366  else {
367    path += ID4 +
368            QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
369            hlpStr + distStr + datTim.toString(".yyO");
370  }
371
372  _fName = path.toAscii();
373}
374
375// Write RINEX Header
376////////////////////////////////////////////////////////////////////////////
377void bncRinex::writeHeader(const QByteArray& format, const bncTime& firstObsTime) {
378
379  bncSettings settings;
380
381  // Set RINEX Version
382  // -----------------
383  int intHeaderVers = (Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked ? 3 : 2);
384
385  // Open the Output File
386  // --------------------
387  QDateTime datTimNom  = dateAndTimeFromGPSweek(firstObsTime.gpsw(),
388                                                floor(firstObsTime.gpssec()+0.5));
389
390  resolveFileName(datTimNom);
391
392  // Read Skeleton Header
393  // --------------------
394  if (readSkeleton()) {
395    _header.set(_sklHeader, intHeaderVers);
396  }
397  else {
398    if (_writeRinexFileOnlyWithSkl) {
399      return;
400    }
401    _header.setDefault(_statID, intHeaderVers);
402  }
403
404  // Append to existing file and return
405  // ----------------------------------
406  if ( QFile::exists(_fName) &&
407       (_reconnectFlag || Qt::CheckState(settings.value("rnxAppend").toInt()) == Qt::Checked) ) {
408    _out.open(_fName.data(), ios::app);
409    _out.setf(ios::showpoint | ios::fixed);
410    _headerWritten = true;
411    _reconnectFlag = false;
412  }
413  else {
414    _out.open(_fName.data());
415    _addComments.clear();
416  }
417
418  _out.setf(ios::showpoint | ios::fixed);
419
420  // A Few Additional Comments
421  // -------------------------
422  _addComments << format.left(6) + " " + _mountPoint.host() + _mountPoint.path();
423  if (_nmea == "yes") {
424    _addComments << "NMEA LAT=" + _latitude + " " + "LONG=" + _longitude;
425  }
426
427  // Write the Header
428  // ----------------
429  QByteArray headerLines;
430  QTextStream outHlp(&headerLines);
431
432  QMap<QString, QString> txtMap;
433  txtMap["COMMENT"] = _addComments.join("\\n");
434
435  _header.setStartTime(firstObsTime);
436  _header.write(&outHlp, &txtMap);
437
438  outHlp.flush();
439
440  if (!_headerWritten) {
441    _out << headerLines.data();
442  }
443
444  _headerWritten = true;
445}
446
447// Stores Observation into Internal Array
448////////////////////////////////////////////////////////////////////////////
449void bncRinex::deepCopy(t_satObs obs) {
450  _obs.push_back(obs);
451}
452
453// Write One Epoch into the RINEX File
454////////////////////////////////////////////////////////////////////////////
455void bncRinex::dumpEpoch(const QByteArray& format, const bncTime& maxTime) {
456
457  // Select observations older than maxTime
458  // --------------------------------------
459  QList<t_satObs> obsList;
460  QMutableListIterator<t_satObs> mIt(_obs);
461  while (mIt.hasNext()) {
462    t_satObs obs = mIt.next();
463    if (obs._time < maxTime) {
464      obsList.push_back(obs);
465      mIt.remove();
466    }
467  }
468
469  // Easy Return
470  // -----------
471  if (obsList.isEmpty()) {
472    return;
473  }
474
475  // Time of Epoch
476  // -------------
477  const t_satObs& fObs = obsList.first();
478  QDateTime datTimNom  = dateAndTimeFromGPSweek(fObs._time.gpsw(), floor(fObs._time.gpssec()+0.5));
479
480  // Close the file
481  // --------------
482  if (_nextCloseEpoch.isValid() && datTimNom >= _nextCloseEpoch) {
483    closeFile();
484    _headerWritten = false;
485  }
486
487  // Write RINEX Header
488  // ------------------
489  if (!_headerWritten) {
490    writeHeader(format, fObs._time);
491  }
492  if (!_headerWritten) {
493    return;
494  }
495
496  // Prepare structure t_rnxEpo
497  // --------------------------
498  t_rnxObsFile::t_rnxEpo rnxEpo;
499  rnxEpo.tt = fObs._time;
500
501  QListIterator<t_satObs> it(obsList);
502  while (it.hasNext()) {
503    const t_satObs& satObs = it.next();
504    t_rnxObsFile::t_rnxSat rnxSat;
505    rnxSat.prn = satObs._prn;
506
507    // Initialize all observations mentioned in skeleton header
508    // --------------------------------------------------------
509    char sys = rnxSat.prn.system();
510    for (int iType = 0; iType < _sklHeader.nTypes(sys); iType++) {
511      QString type = _sklHeader.obsType(sys, iType);
512      t_rnxObsFile::t_rnxObs rnxObs; // create an empty observation
513      rnxSat.obs[type] = rnxObs;
514    }
515
516    for (unsigned ii = 0; ii < satObs._obs.size(); ii++) {
517      const t_frqObs* frqObs = satObs._obs[ii];
518      if (frqObs->_codeValid) {
519        QString type = 'C' + QString(frqObs->_rnxType2ch.c_str());
520        t_rnxObsFile::t_rnxObs rnxObs;
521        rnxObs.value = frqObs->_code;
522        rnxSat.obs[type] = rnxObs;
523      }
524      if (frqObs->_phaseValid) {
525        QString type = 'L' + QString(frqObs->_rnxType2ch.c_str());
526        t_rnxObsFile::t_rnxObs rnxObs;
527        rnxObs.value = frqObs->_phase;
528        if (frqObs->_slip) {
529          rnxObs.lli |= 1;
530        }
531        rnxSat.obs[type] = rnxObs;
532      }
533      if (frqObs->_dopplerValid) {
534        QString type = 'D' + QString(frqObs->_rnxType2ch.c_str());
535        t_rnxObsFile::t_rnxObs rnxObs;
536        rnxObs.value = frqObs->_doppler;
537        rnxSat.obs[type] = rnxObs;
538      }
539      if (frqObs->_snrValid) {
540        QString type = 'S' + QString(frqObs->_rnxType2ch.c_str());
541        t_rnxObsFile::t_rnxObs rnxObs;
542        rnxObs.value = frqObs->_snr;
543        rnxSat.obs[type] = rnxObs;
544      }
545    }
546
547
548    rnxEpo.rnxSat.push_back(rnxSat);
549  }
550
551  // Write the epoch
552  // ---------------
553  QByteArray outLines;
554  QTextStream outStream(&outLines);
555  t_rnxObsFile::writeEpoch(&outStream, _header, &rnxEpo);
556
557  _out << outLines.data();
558  _out.flush();
559}
560
561// Close the Old RINEX File
562////////////////////////////////////////////////////////////////////////////
563void bncRinex::closeFile() {
564
565  if (_header.version() == 3) {
566    _out << ">                              4  1" << endl;
567    _out << "END OF FILE" << endl;
568  }
569  _out.close();
570  if (!_rnxScriptName.isEmpty()) {
571    qApp->thread()->wait(100);
572#ifdef WIN32
573    QProcess::startDetached(_rnxScriptName, QStringList() << _fName) ;
574#else
575    QProcess::startDetached("nohup", QStringList() << _rnxScriptName << _fName) ;
576#endif
577
578  }
579}
580
581// One Line in ASCII (Internal) Format
582////////////////////////////////////////////////////////////////////////////
583string bncRinex::asciiSatLine(const t_satObs& obs) {
584
585  ostringstream str;
586  str.setf(ios::showpoint | ios::fixed);
587
588  str << obs._prn.toString();
589
590  for (unsigned ii = 0; ii < obs._obs.size(); ii++) {
591    const t_frqObs* frqObs = obs._obs[ii];
592    if (frqObs->_codeValid) {
593      str << ' '
594          << left  << setw(3)  << "C" + frqObs->_rnxType2ch << ' '
595          << right << setw(14) << setprecision(3) << frqObs->_code;
596    }
597    if (frqObs->_phaseValid) {
598      str << ' '
599          << left  << setw(3) << "L" + frqObs->_rnxType2ch << ' '
600          << right << setw(14) << setprecision(3) << frqObs->_phase << ' '
601          << right << setw(4)                     << frqObs->_slipCounter;
602    }
603    if (frqObs->_dopplerValid) {
604      str << ' '
605          << left  << setw(3) << "D" + frqObs->_rnxType2ch << ' '
606          << right << setw(14) << setprecision(3) << frqObs->_doppler;
607    }
608    if (frqObs->_snrValid) {
609      str << ' '
610          << left  << setw(3) << "S" + frqObs->_rnxType2ch << ' '
611          << right << setw(8) << setprecision(3) << frqObs->_snr;
612    }
613  }
614
615  return str.str();
616}
Note: See TracBrowser for help on using the repository browser.