source: ntrip/branches/BNC_2.11.0/src/bncrinex.cpp@ 6778

Last change on this file since 6778 was 6778, checked in by stuerze, 11 years ago

switch to port 443 for skeleton file download if https is used

File size: 19.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#include "rtcm3torinex.h"
62
63using namespace std;
64
65// Constructor
66////////////////////////////////////////////////////////////////////////////
67bncRinex::bncRinex(const QByteArray& statID, const QUrl& mountPoint,
68 const QByteArray& latitude, const QByteArray& longitude,
69 const QByteArray& nmea, const QByteArray& ntripVersion) {
70
71 _statID = statID;
72 _mountPoint = mountPoint;
73 _latitude = latitude;
74 _longitude = longitude;
75 _nmea = nmea;
76 _ntripVersion = ntripVersion;
77 _headerWritten = false;
78 _reconnectFlag = false;
79
80 bncSettings settings;
81 _rnxScriptName = settings.value("rnxScript").toString();
82 expandEnvVar(_rnxScriptName);
83
84 _pgmName = QString(BNCPGMNAME).leftJustified(20, ' ', true);
85#ifdef WIN32
86 _userName = QString("${USERNAME}");
87#else
88 _userName = QString("${USER}");
89#endif
90 expandEnvVar(_userName);
91 _userName = _userName.leftJustified(20, ' ', true);
92
93 _samplingRate = settings.value("rnxSampl").toInt();
94}
95
96// Destructor
97////////////////////////////////////////////////////////////////////////////
98bncRinex::~bncRinex() {
99 bncSettings settings;
100 if ((_header._version >= 3.0) && ( Qt::CheckState(settings.value("rnxAppend").toInt()) != Qt::Checked) ) {
101 _out << "> 4 1" << endl;
102 _out << "END OF FILE" << endl;
103 }
104 _out.close();
105}
106
107// Download Skeleton Header File
108////////////////////////////////////////////////////////////////////////////
109t_irc bncRinex::downloadSkeleton() {
110
111 t_irc irc = failure;
112
113 QStringList table;
114 bncTableDlg::getFullTable(_ntripVersion, _mountPoint.host(),
115 _mountPoint.port(), table, true);
116 QString net;
117 QStringListIterator it(table);
118 while (it.hasNext()) {
119 QString line = it.next();
120 if (line.indexOf("STR") == 0) {
121 QStringList tags = line.split(";");
122 if (tags.size() > 7) {
123 if (tags.at(1) == _mountPoint.path().mid(1).toAscii()) {
124 net = tags.at(7);
125 break;
126 }
127 }
128 }
129 }
130 QString sklDir;
131 if (!net.isEmpty()) {
132 it.toFront();
133 while (it.hasNext()) {
134 QString line = it.next();
135 if (line.indexOf("NET") == 0) {
136 QStringList tags = line.split(";");
137 if (tags.size() > 6) {
138 if (tags.at(1) == net) {
139 sklDir = tags.at(6).trimmed();
140 break;
141 }
142 }
143 }
144 }
145 }
146 if (!sklDir.isEmpty() && sklDir != "none") {
147 QUrl url(sklDir + "/" + _mountPoint.path().mid(1,4).toLower() + ".skl");
148 if (url.port() == -1) {
149 if (sklDir.contains("https")) {
150 url.setPort(443);
151 }
152 else {
153 url.setPort(80);
154 }
155 }
156
157 bncNetQuery* query;
158 if (_ntripVersion == "2s") {
159 query = new bncNetQueryV2(true);
160 }
161 else if (_ntripVersion == "2") {
162 query = new bncNetQueryV2(false);
163 }
164 else {
165 query = new bncNetQueryV1;
166 }
167
168 QByteArray outData;
169 query->waitForRequestResult(url, outData);
170 if (query->status() == bncNetQuery::finished) {
171 irc = success;
172 _header._obsTypesV2.clear();
173 _header._obsTypesV3.clear();
174 QTextStream in(outData);
175 _header.read(&in);
176 }
177
178 delete query;
179 }
180
181 return irc;
182}
183
184// Read Skeleton Header File
185////////////////////////////////////////////////////////////////////////////
186bool bncRinex::readSkeleton() {
187
188 bool readDone = false;
189
190 // Read the local file
191 // -------------------
192 QFile skl(_sklName);
193 if ( skl.exists() && skl.open(QIODevice::ReadOnly) ) {
194 readDone = true;
195 _header._obsTypesV2.clear();
196 _header._obsTypesV3.clear();
197 QTextStream in(&skl);
198 _header.read(&in);
199 }
200
201 // Read downloaded file
202 // --------------------
203 else if ( _ntripVersion != "N" && _ntripVersion != "UN" &&
204 _ntripVersion != "S" ) {
205 QDate currDate = currentDateAndTimeGPS().date();
206 if ( !_skeletonDate.isValid() || _skeletonDate != currDate ) {
207 if (downloadSkeleton() == success) {
208 readDone = true;
209 }
210 _skeletonDate = currDate;
211 }
212 }
213
214 return readDone;
215}
216
217// Next File Epoch (static)
218////////////////////////////////////////////////////////////////////////////
219QString bncRinex::nextEpochStr(const QDateTime& datTim,
220 const QString& intStr, QDateTime* nextEpoch) {
221
222 QString epoStr;
223
224 QTime nextTime;
225 QDate nextDate;
226
227 int indHlp = intStr.indexOf("min");
228
229 if ( indHlp != -1) {
230 int step = intStr.left(indHlp-1).toInt();
231 char ch = 'A' + datTim.time().hour();
232 epoStr = ch;
233 if (datTim.time().minute() >= 60-step) {
234 epoStr += QString("%1").arg(60-step, 2, 10, QChar('0'));
235 if (datTim.time().hour() < 23) {
236 nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
237 nextDate = datTim.date();
238 }
239 else {
240 nextTime.setHMS(0, 0, 0);
241 nextDate = datTim.date().addDays(1);
242 }
243 }
244 else {
245 for (int limit = step; limit <= 60-step; limit += step) {
246 if (datTim.time().minute() < limit) {
247 epoStr += QString("%1").arg(limit-step, 2, 10, QChar('0'));
248 nextTime.setHMS(datTim.time().hour(), limit, 0);
249 nextDate = datTim.date();
250 break;
251 }
252 }
253 }
254 }
255 else if (intStr == "1 hour") {
256 char ch = 'A' + datTim.time().hour();
257 epoStr = ch;
258 if (datTim.time().hour() < 23) {
259 nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
260 nextDate = datTim.date();
261 }
262 else {
263 nextTime.setHMS(0, 0, 0);
264 nextDate = datTim.date().addDays(1);
265 }
266 }
267 else {
268 epoStr = "0";
269 nextTime.setHMS(0, 0, 0);
270 nextDate = datTim.date().addDays(1);
271 }
272
273 if (nextEpoch) {
274 *nextEpoch = QDateTime(nextDate, nextTime);
275 }
276
277 return epoStr;
278}
279
280// File Name according to RINEX Standards
281////////////////////////////////////////////////////////////////////////////
282void bncRinex::resolveFileName(const QDateTime& datTim) {
283
284 bncSettings settings;
285 QString path = settings.value("rnxPath").toString();
286 expandEnvVar(path);
287
288 if ( path.length() > 0 && path[path.length()-1] != QDir::separator() ) {
289 path += QDir::separator();
290 }
291
292 QString hlpStr = nextEpochStr(datTim, settings.value("rnxIntr").toString(),
293 &_nextCloseEpoch);
294
295 QString ID4 = _statID.left(4);
296
297 // Check name conflict
298 // -------------------
299 QString distStr;
300 int num = 0;
301 QListIterator<QString> it(settings.value("mountPoints").toStringList());
302 while (it.hasNext()) {
303 QString mp = it.next();
304 if (mp.indexOf(ID4) != -1) {
305 ++num;
306 }
307 }
308 if (num > 1) {
309 distStr = "_" + _statID.mid(4);
310 }
311
312 QString sklExt = settings.value("rnxSkel").toString();
313 if (!sklExt.isEmpty()) {
314 _sklName = path + ID4 + distStr + "." + sklExt;
315 }
316
317 path += ID4 +
318 QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
319 hlpStr + distStr + datTim.toString(".yyO");
320
321 _fName = path.toAscii();
322}
323
324// Write RINEX Header
325////////////////////////////////////////////////////////////////////////////
326void bncRinex::writeHeader(const QByteArray& format,
327 const QDateTime& datTimNom) {
328
329 bncSettings settings;
330
331 // Open the Output File
332 // --------------------
333 resolveFileName(datTimNom);
334
335 // Append to existing file and return
336 // ----------------------------------
337 if ( QFile::exists(_fName) &&
338 (_reconnectFlag || Qt::CheckState(settings.value("rnxAppend").toInt()) == Qt::Checked) ) {
339 _out.open(_fName.data(), ios::app);
340 _out.setf(ios::showpoint | ios::fixed);
341 _headerWritten = true;
342 _reconnectFlag = false;
343 }
344 else {
345 _out.open(_fName.data());
346 }
347
348 _out.setf(ios::showpoint | ios::fixed);
349
350 // Read Skeleton Header
351 // --------------------
352 bool skelRead = readSkeleton();
353
354 // Set RINEX Version
355 // -----------------
356 if ( Qt::CheckState(settings.value("rnxV3").toInt()) == Qt::Checked) {
357 _header._version = t_rnxObsHeader::defaultRnxObsVersion3;
358 }
359 else {
360 _header._version = t_rnxObsHeader::defaultRnxObsVersion2;
361 }
362
363 // A Few Additional Comments
364 // -------------------------
365 if (skelRead || _addComments.size() == 0) {
366 _addComments.clear();
367 _addComments << format.left(6) + " "
368 + _mountPoint.host() + _mountPoint.path();
369 if (_header._obsTypesV3.size() == 0 && _header._version >= 3.0) {
370 _addComments << "Default set of observation types used";
371 }
372 if (_nmea == "yes") {
373 _addComments << "NMEA LAT=" + _latitude + " " + "LONG=" + _longitude;
374 }
375 }
376
377 // Set Marker Name
378 // ---------------
379 if (_header._markerName.isEmpty()) {
380 _header._markerName = _statID;
381 }
382
383 // Set Default RINEX v2 Types
384 // --------------------------
385 if (_header._obsTypesV2.size() == 0) {
386 _header._obsTypesV2 << "C1" << "P1" << "L1" << "S1"
387 << "C2" << "P2" << "L2" << "S2";
388 }
389
390 // Set Default RINEX v3 Types
391 // ---------------------------
392 if (_header._obsTypesV3.size() == 0) {
393 _header._obsTypesV3['G'] << "C1C" << "L1C" << "D1C" << "S1C"
394 << "C1P" << "L1P" << "D1P" << "S1P"
395 << "C2C" << "L2C" << "D2C" << "S2C"
396 << "C2P" << "L2P" << "D2P" << "S2P"
397 << "C5" << "D5" << "L5" << "S5";
398
399 _header._obsTypesV3['R'] << "C1C" << "L1C" << "D1C" << "S1C"
400 << "C1P" << "L1P" << "D1P" << "S1P"
401 << "C2C" << "L2C" << "D2C" << "S2C"
402 << "C2P" << "L2P" << "D2P" << "S2P";
403
404 _header._obsTypesV3['E'] << "C1" << "L1" << "D1" << "S1"
405 << "C5" << "L5" << "D5" << "S5"
406 << "C6" << "L6" << "D6" << "S6"
407 << "C7" << "L7" << "D7" << "S7"
408 << "C8" << "L8" << "D8" << "S8";
409
410 _header._obsTypesV3['J'] << "C1" << "L1" << "D1" << "S1"
411 << "C2" << "L2" << "D2" << "S2"
412 << "C5" << "L5" << "D5" << "S5"
413 << "C6" << "D6" << "L6" << "S6";
414
415 _header._obsTypesV3['S'] << "C1" << "L1" << "D1" << "S1"
416 << "C5" << "L5" << "D5" << "S5";
417
418 _header._obsTypesV3['C'] << "C1" << "L1" << "D1" << "S1"
419 << "C6" << "L6" << "D6" << "S6"
420 << "C7" << "L7" << "D7" << "S7";
421 }
422
423 // Write the Header
424 // ----------------
425 QByteArray headerLines;
426 QTextStream outHlp(&headerLines);
427
428 QMap<QString, QString> txtMap;
429 txtMap["COMMENT"] = _addComments.join("\\n");
430
431 _header.write(&outHlp, &txtMap);
432
433 outHlp.flush();
434
435 if (!_headerWritten) {
436 _out << headerLines.data();
437 }
438
439 _headerWritten = true;
440}
441
442// Stores Observation into Internal Array
443////////////////////////////////////////////////////////////////////////////
444void bncRinex::deepCopy(t_obs obs) {
445 _obs.push_back(obs);
446}
447
448// Write One Epoch into the RINEX File
449////////////////////////////////////////////////////////////////////////////
450void bncRinex::dumpEpoch(const QByteArray& format, long maxTime) {
451
452 // Select observations older than maxTime
453 // --------------------------------------
454 QList<t_obs> dumpList;
455 QMutableListIterator<t_obs> mIt(_obs);
456 while (mIt.hasNext()) {
457 t_obs obs = mIt.next();
458 if (obs.GPSWeek * 7*24*3600 + obs.GPSWeeks < maxTime - 0.05) {
459 dumpList.push_back(obs);
460 mIt.remove();
461 }
462 }
463
464 // Easy Return
465 // -----------
466 if (dumpList.isEmpty()) {
467 return;
468 }
469
470 // Time of Epoch
471 // -------------
472 const t_obs& fObs = dumpList.first();
473 QDateTime datTim = dateAndTimeFromGPSweek(fObs.GPSWeek, fObs.GPSWeeks);
474 QDateTime datTimNom = dateAndTimeFromGPSweek(fObs.GPSWeek,
475 floor(fObs.GPSWeeks+0.5));
476
477 // Close the file
478 // --------------
479 if (_nextCloseEpoch.isValid() && datTimNom >= _nextCloseEpoch) {
480 closeFile();
481 _headerWritten = false;
482 }
483
484 // Write RINEX Header
485 // ------------------
486 if (!_headerWritten) {
487 _header._startTime.set(fObs.GPSWeek, fObs.GPSWeeks);
488 writeHeader(format, datTimNom);
489 }
490
491 // Check whether observation types available
492 // -----------------------------------------
493 QMutableListIterator<t_obs> mItDump(dumpList);
494 while (mItDump.hasNext()) {
495 t_obs& obs = mItDump.next();
496 if (!_header._obsTypesV3.contains(obs.satSys) && !_header._obsTypesV3.contains(obs.satSys)) {
497 mItDump.remove();
498 }
499 }
500 if (dumpList.isEmpty()) {
501 return;
502 }
503
504 double sec = double(datTim.time().second()) + fmod(fObs.GPSWeeks,1.0);
505
506 // Epoch header line: RINEX Version 3
507 // ----------------------------------
508 if (_header._version >= 3.0) {
509 _out << datTim.toString("> yyyy MM dd hh mm ").toAscii().data()
510 << setw(10) << setprecision(7) << sec
511 << " " << 0 << setw(3) << dumpList.size() << endl;
512 }
513 // Epoch header line: RINEX Version 2
514 // ----------------------------------
515 else {
516 _out << datTim.toString(" yy MM dd hh mm ").toAscii().data()
517 << setw(10) << setprecision(7) << sec
518 << " " << 0 << setw(3) << dumpList.size();
519
520 QListIterator<t_obs> it(dumpList); int iSat = 0;
521 while (it.hasNext()) {
522 iSat++;
523 const t_obs& obs = it.next();
524 _out << obs.satSys << setw(2) << obs.satNum;
525 if (iSat == 12 && it.hasNext()) {
526 _out << endl << " ";
527 iSat = 0;
528 }
529 }
530 _out << endl;
531 }
532
533 QListIterator<t_obs> it(dumpList);
534 while (it.hasNext()) {
535 const t_obs& obs = it.next();
536
537 // Cycle slips detection
538 // ---------------------
539 QString prn = QString("%1%2").arg(obs.satSys)
540 .arg(obs.satNum, 2, 10, QChar('0'));
541
542 char lli1 = ' ';
543 char lli2 = ' ';
544 char lli5 = ' ';
545 if ( obs.slip_cnt_L1 >= 0 ) {
546 if ( _slip_cnt_L1.find(prn) != _slip_cnt_L1.end() &&
547 _slip_cnt_L1.find(prn).value() != obs.slip_cnt_L1 ) {
548 lli1 = '1';
549 }
550 }
551
552 if ( obs.slip_cnt_L2 >= 0 ) {
553 if ( _slip_cnt_L2.find(prn) != _slip_cnt_L2.end() &&
554 _slip_cnt_L2.find(prn).value() != obs.slip_cnt_L2 ) {
555 lli2 = '1';
556 }
557 }
558
559 if ( obs.slip_cnt_L5 >= 0 ) {
560 if ( _slip_cnt_L5.find(prn) != _slip_cnt_L5.end() &&
561 _slip_cnt_L5.find(prn).value() != obs.slip_cnt_L5 ) {
562 lli5 = '1';
563 }
564 }
565
566 _slip_cnt_L1[prn] = obs.slip_cnt_L1;
567 _slip_cnt_L2[prn] = obs.slip_cnt_L2;
568 _slip_cnt_L5[prn] = obs.slip_cnt_L5;
569
570 // Write the data
571 // --------------
572 _out << rinexSatLine(obs, lli1, lli2, lli5) << endl;
573 }
574
575 _out.flush();
576}
577
578// Close the Old RINEX File
579////////////////////////////////////////////////////////////////////////////
580void bncRinex::closeFile() {
581 if (_header._version == 3) {
582 _out << "> 4 1" << endl;
583 _out << "END OF FILE" << endl;
584 }
585 _out.close();
586 if (!_rnxScriptName.isEmpty()) {
587 qApp->thread()->wait(100);
588#ifdef WIN32
589 QProcess::startDetached(_rnxScriptName, QStringList() << _fName) ;
590#else
591 QProcess::startDetached("nohup", QStringList() << _rnxScriptName << _fName) ;
592#endif
593
594 }
595}
596
597// One Line in RINEX v2 or v3
598////////////////////////////////////////////////////////////////////////////
599string bncRinex::rinexSatLine(const t_obs& obs, char lli1, char lli2,
600 char lli5) {
601 ostringstream str;
602 str.setf(ios::showpoint | ios::fixed);
603
604 if (_header._version >= 3.0) {
605 str << obs.satSys
606 << setw(2) << setfill('0') << obs.satNum << setfill(' ');
607 }
608
609 const QVector<QString>& types = (_header._version > 3.0) ?
610 _header._obsTypesV3[obs.satSys] : _header._obsTypesV2;
611 for (int ii = 0; ii < types.size(); ii++) {
612 if (_header._version < 3.0 && ii > 0 && ii % 5 == 0) {
613 str << endl;
614 }
615 double value = obs.measdata(types[ii], _header._version);
616 str << setw(14) << setprecision(3) << value;
617 if (value != 0.0 && types[ii].indexOf("L1") == 0) {
618 str << lli1 << ' ';
619 }
620 else if (value != 0.0 && types[ii].indexOf("L2") == 0) {
621 str << lli2 << ' ';
622 }
623 else if (value != 0.0 && types[ii].indexOf("L5") == 0) {
624 str << lli5 << ' ';
625 }
626 else {
627 str << " ";
628 }
629 }
630
631 return str.str();
632}
633
634//
635////////////////////////////////////////////////////////////////////////////
636string bncRinex::obsToStr(double val, int width, int precision) {
637 if (val != 0.0) {
638 ostringstream str;
639 str.setf(ios::showpoint | ios::fixed);
640 str << setw(width) << setprecision(precision) << val;
641 return str.str();
642 }
643 else {
644 return "0.0";
645 }
646}
647
648// One Line in ASCII (Internal) Format
649////////////////////////////////////////////////////////////////////////////
650string bncRinex::asciiSatLine(const t_obs& obs) {
651
652 ostringstream str;
653 str.setf(ios::showpoint | ios::fixed);
654
655 str << obs.satSys << setw(2) << setfill('0') << obs.satNum << setfill(' ');
656
657 if (obs.satSys == 'R') { // Glonass
658 str << ' ' << setw(2) << obs.slotNum << ' ';
659 }
660 else {
661 str << " ";
662 }
663
664 float rnxVers = 3.0;
665
666 for (int ie = 0; ie < GNSSENTRY_NUMBER; ie++) {
667 QString rnxStr = obs.rnxStr(ie);
668 if (rnxStr.length() >= 2) {
669 double data = obs.measdata(rnxStr, rnxVers);
670 if (data != 0.0) {
671 int width = (rnxStr[0] == 'S') ? 8 : 14;
672 int prec = 3;
673 str << ' ' << rnxStr.toAscii().data()
674 << ' ' << obsToStr(data, width, prec);
675 if (rnxStr[0] == 'L') {
676 int slipCnt = 0;
677 if (rnxStr[1] == '1') {
678 slipCnt = obs.slip_cnt_L1;
679 }
680 else if (rnxStr[1] == '2') {
681 slipCnt = obs.slip_cnt_L2;
682 }
683 else if (rnxStr[1] == '5') {
684 slipCnt = obs.slip_cnt_L5;
685 }
686 str << ' ' << setw(3) << slipCnt;
687 }
688 }
689 }
690 }
691
692 return str.str();
693}
Note: See TracBrowser for help on using the repository browser.