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

Last change on this file since 9088 was 9088, checked in by stuerze, 22 months ago

adjusted allocation of slip and LTI according to the respective RTCM version

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