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

Last change on this file since 6880 was 6796, checked in by stuerze, 10 years ago

add an option to write only RINEX files if the respective SKL file is available

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