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

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

Check box for the optional choice of RINEX V3 observation file names is added

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