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

Last change on this file since 7157 was 7150, checked in by stuerze, 10 years ago

skl file download is now always done using the ssl extended class to allow https requests; skl file download is independent from stream download and the chosen ntrip version

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