source: ntrip/trunk/BNC/src/ephemeris.cpp@ 10993

Last change on this file since 10993 was 10993, checked in by stuerze, 9 days ago

minor changes to allow different formulas for the computation of the relativistic effects in GPS position determination for RTCM-SSR and IGS-SSR

File size: 91.0 KB
Line 
1#include <sstream>
2#include <iostream>
3#include <iomanip>
4#include <cstring>
5
6#include <newmatio.h>
7
8#include "ephemeris.h"
9#include "bncutils.h"
10#include "bnctime.h"
11#include "bnccore.h"
12#include "bncutils.h"
13#include "satObs.h"
14#include "pppInclude.h"
15#include "pppModel.h"
16#include "RTCM3/bits.h"
17
18using namespace std;
19
20// Constructor
21////////////////////////////////////////////////////////////////////////////
22t_eph::t_eph() {
23 _checkState = unchecked;
24 _type = undefined;
25 _orbCorr = 0;
26 _clkCorr = 0;
27}
28// Destructor
29////////////////////////////////////////////////////////////////////////////
30t_eph::~t_eph() {
31 if (_orbCorr)
32 delete _orbCorr;
33 if (_clkCorr)
34 delete _clkCorr;
35}
36
37//
38////////////////////////////////////////////////////////////////////////////
39void t_eph::setOrbCorr(const t_orbCorr *orbCorr) {
40 if (_orbCorr) {
41 delete _orbCorr;
42 _orbCorr = 0;
43 }
44 _orbCorr = new t_orbCorr(*orbCorr);
45}
46
47//
48////////////////////////////////////////////////////////////////////////////
49void t_eph::setClkCorr(const t_clkCorr *clkCorr) {
50 if (_clkCorr) {
51 delete _clkCorr;
52 _clkCorr = 0;
53 }
54 _clkCorr = new t_clkCorr(*clkCorr);
55}
56
57//
58////////////////////////////////////////////////////////////////////////////
59t_irc t_eph::getCrd(const bncTime &tt, ColumnVector &xc, ColumnVector &vv,
60 bool useCorr) const {
61
62 if (_checkState == bad ||
63 _checkState == unhealthy ||
64 _checkState == outdated) {
65 return failure;
66 }
67
68 xc.ReSize(6);
69 vv.ReSize(3);
70 if (position(tt.gpsw(), tt.gpssec(), xc.data(), vv.data()) != success) {
71 return failure;
72 }
73 if (useCorr) {
74 if (_orbCorr && _clkCorr) {
75 // Refuse to extrapolate a correction far beyond its own declared
76 // update interval - without this, a single missed/dropped
77 // correction message (network blip, encoder hiccup, ...) gets
78 // extrapolated via its rate term (_dotXr/_dotDClk) forever, with no
79 // warning, producing an unbounded, silently growing position error.
80 // ----------------------------------------------------------------
81 // _updateInt defaults to 0 (1s) when not explicitly set by the
82 // source stream, so a pure multiple of it would be far too strict -
83 // floor it at MIN_STALE_BOUND so normal, slightly slower-than-1Hz
84 // streams are not falsely flagged.
85 const double MAX_STALE_FACTOR = 3.0;
86 const double MIN_STALE_BOUND = 60.0; // [s]
87 double maxAgeO = max(MIN_STALE_BOUND, MAX_STALE_FACTOR * ssrUpdateInt[_orbCorr->_updateInt]);
88 double maxAgeC = max(MIN_STALE_BOUND, MAX_STALE_FACTOR * ssrUpdateInt[_clkCorr->_updateInt]);
89 if (fabs(tt - _orbCorr->_time) > maxAgeO ||
90 fabs(tt - _clkCorr->_time) > maxAgeC) {
91 return failure;
92 }
93
94 double dtO = tt - _orbCorr->_time;
95 if (_orbCorr->_updateInt) {
96 dtO -= (0.5 * ssrUpdateInt[_orbCorr->_updateInt]);
97 }
98 ColumnVector dx(3);
99 dx[0] = _orbCorr->_xr[0] + _orbCorr->_dotXr[0] * dtO;
100 dx[1] = _orbCorr->_xr[1] + _orbCorr->_dotXr[1] * dtO;
101 dx[2] = _orbCorr->_xr[2] + _orbCorr->_dotXr[2] * dtO;
102
103 RSW_to_XYZ(xc.Rows(1, 3), vv.Rows(1, 3), dx, dx);
104
105 xc[0] -= dx[0];
106 xc[1] -= dx[1];
107 xc[2] -= dx[2];
108
109 ColumnVector dv(3);
110 RSW_to_XYZ(xc.Rows(1, 3), vv.Rows(1, 3), _orbCorr->_dotXr, dv);
111
112 vv[0] -= dv[0];
113 vv[1] -= dv[1];
114 vv[2] -= dv[2];
115
116 double dtC = tt - _clkCorr->_time;
117 if (_clkCorr->_updateInt) {
118 dtC -= (0.5 * ssrUpdateInt[_clkCorr->_updateInt]);
119 }
120
121 xc[3] += _clkCorr->_dClk + _clkCorr->_dotDClk * dtC + _clkCorr->_dotDotDClk * dtC * dtC;
122 } else {
123 return failure;
124 }
125 }
126 return success;
127}
128
129//
130//////////////////////////////////////////////////////////////////////////////
131void t_eph::setType(QString typeStr) {
132
133 if (typeStr == "LNAV") {
134 _type = t_eph::LNAV;
135 } else if (typeStr == "FDMA") {
136 _type = t_eph::FDMA;
137 } else if (typeStr == "FNAV") {
138 _type = t_eph::FNAV;
139 } else if (typeStr == "INAV") {
140 _type = t_eph::INAV;
141 } else if (typeStr == "D1") {
142 _type = t_eph::D1;
143 } else if (typeStr == "D2") {
144 _type = t_eph::D2;
145 } else if (typeStr == "SBAS") {
146 _type = t_eph::SBASL1;
147 } else if (typeStr == "CNAV") {
148 _type = t_eph::CNAV;
149 } else if (typeStr == "CNV1") {
150 _type = t_eph::CNV1;
151 } else if (typeStr == "CNV2") {
152 _type = t_eph::CNV2;
153 } else if (typeStr == "CNV3") {
154 _type = t_eph::CNV3;
155 } else if (typeStr == "L1NV") {
156 _type = t_eph::L1NV;
157 } else if (typeStr == "L1OC") {
158 _type = t_eph::L1OC;
159 } else if (typeStr == "L3OC") {
160 _type = t_eph::L3OC;
161 } else {
162 _type = t_eph::undefined;
163 }
164
165}
166
167
168//
169//////////////////////////////////////////////////////////////////////////////
170QString t_eph::typeStr(e_type type, const t_prn &prn, double version) {
171 QString typeStr = "";
172 QString epochStart;
173 QString eolStr;
174
175 if (version < 4.0) {
176 return typeStr;
177 }
178
179 if (version == 99.0) { // log output for OUTDATED, WRONG or UNHEALTHY satellites
180 epochStart = "";
181 eolStr = "";
182 } else {
183 epochStart = "> ";
184 eolStr = "\n";
185 }
186
187 QString ephStr = QString("EPH %1 ").arg(prn.toString().c_str());
188 switch (type) {
189 case undefined:
190 typeStr = epochStart + ephStr + "unknown" + eolStr;
191 break;
192 case LNAV:
193 typeStr = epochStart + ephStr + "LNAV" + eolStr;
194 break;
195 case FDMA:
196 case FDMA_M:
197 typeStr = epochStart + ephStr + "FDMA" + eolStr;
198 break;
199 case FNAV:
200 typeStr = epochStart + ephStr + "FNAV" + eolStr;
201 break;
202 case INAV:
203 typeStr = epochStart + ephStr + "INAV" + eolStr;
204 break;
205 case D1:
206 typeStr = epochStart + ephStr + "D1 " + eolStr;
207 break;
208 case D2:
209 typeStr = epochStart + ephStr + "D2 " + eolStr;
210 break;
211 case SBASL1:
212 typeStr = epochStart + ephStr + "SBAS" + eolStr;
213 break;
214 case CNAV:
215 typeStr = epochStart + ephStr + "CNAV" + eolStr;
216 break;
217 case CNV1:
218 typeStr = epochStart + ephStr + "CNV1" + eolStr;
219 break;
220 case CNV2:
221 typeStr = epochStart + ephStr + "CNV2" + eolStr;
222 break;
223 case CNV3:
224 typeStr = epochStart + ephStr + "CNV3" + eolStr;
225 break;
226 case L1NV:
227 typeStr = epochStart + ephStr + "L1NV" + eolStr;
228 break;
229 case L1OC:
230 typeStr = epochStart + ephStr + "L1OC" + eolStr;
231 break;
232 case L3OC:
233 typeStr = epochStart + ephStr + "L3OC" + eolStr;
234 break;
235 }
236 return typeStr;
237}
238
239//
240//////////////////////////////////////////////////////////////////////////////
241QString t_eph::rinexDateStr(const bncTime &tt, const t_prn &prn,
242 double version) {
243 QString prnStr(prn.toString().c_str());
244 return rinexDateStr(tt, prnStr, version);
245}
246
247//
248//////////////////////////////////////////////////////////////////////////////
249QString t_eph::rinexDateStr(const bncTime &tt, const QString &prnStr,
250 double version) {
251
252 QString datStr;
253
254 unsigned year, month, day, hour, min;
255 double sec;
256 tt.civil_date(year, month, day);
257 tt.civil_time(hour, min, sec);
258
259 QTextStream out(&datStr);
260
261 if (version < 3.0) {
262 QString prnHlp = prnStr.mid(1, 2);
263 if (prnHlp[0] == '0')
264 prnHlp[0] = ' ';
265 out << prnHlp
266 << QString(" %1 %2 %3 %4 %5%6").arg(year % 100, 2, 10, QChar('0')).arg(
267 month, 2).arg(day, 2).arg(hour, 2).arg(min, 2).arg(sec, 5, 'f', 1);
268 }
269 else if (version == 99) {
270 out
271 << QString(" %1 %2 %3 %4 %5 %6").arg(year, 4).arg(month, 2, 10,
272 QChar('0')).arg(day, 2, 10, QChar('0')).arg(hour, 2, 10, QChar('0')).arg(
273 min, 2, 10, QChar('0')).arg(int(sec), 2, 10, QChar('0'));
274 }
275 else {
276 out << prnStr
277 << QString(" %1 %2 %3 %4 %5 %6").arg(year, 4).arg(month, 2, 10,
278 QChar('0')).arg(day, 2, 10, QChar('0')).arg(hour, 2, 10, QChar('0')).arg(
279 min, 2, 10, QChar('0')).arg(int(sec), 2, 10, QChar('0'));
280 }
281
282 return datStr;
283}
284
285// Constructor
286//////////////////////////////////////////////////////////////////////////////
287t_ephGPS::t_ephGPS(double rnxVersion, const QStringList &lines, QString typeStr) {
288
289 setType(typeStr);
290
291 int nLines = 8; // LNAV
292 // Source RINEX version < 4
293 if (type() == t_eph::undefined) {
294 _type = t_eph::LNAV;
295 }
296
297 if (type() == t_eph::CNAV ||
298 type() == t_eph::L1NV) {
299 nLines += 1;
300 }
301 if (type() == t_eph::CNV2) {
302 nLines += 2;
303 }
304
305 if (lines.size() != nLines) {
306 _checkState = bad;
307 return;
308 }
309
310 // RINEX Format
311 // ------------
312 int fieldLen = 19;
313 double statusflags = 0.0;
314
315 int pos[4];
316 pos[0] = (rnxVersion <= 2.12) ? 3 : 4;
317 pos[1] = pos[0] + fieldLen;
318 pos[2] = pos[1] + fieldLen;
319 pos[3] = pos[2] + fieldLen;
320
321 // Read nLines lines
322 // ------------------
323 for (int iLine = 0; iLine < nLines; iLine++) {
324 QString line = lines[iLine];
325
326 if (iLine == 0) {
327 QTextStream in(line.left(pos[1]).toLatin1());
328 int year, month, day, hour, min;
329 double sec;
330
331 QString prnStr, n;
332 in >> prnStr;
333
334 if (prnStr.size() == 1 &&
335 (prnStr[0] == 'G' ||
336 prnStr[0] == 'J' ||
337 prnStr[0] == 'I')) {
338 in >> n;
339 prnStr.append(n);
340 }
341
342 if ( prnStr.at(0) == 'G') {
343 _prn.set('G', prnStr.mid(1).toInt());
344 } else if (prnStr.at(0) == 'J') {
345 _prn.set('J', prnStr.mid(1).toInt());
346 } else if (prnStr.at(0) == 'I') {
347 _prn.set('I', prnStr.mid(1).toInt());
348 } else {
349 _prn.set('G', prnStr.toInt());
350 }
351 _prn.setFlag(type());
352
353 in >> year >> month >> day >> hour >> min >> sec;
354
355 if (year < 80) {
356 year += 2000;
357 } else if (year < 100) {
358 year += 1900;
359 }
360
361 _TOC.set(year, month, day, hour, min, sec);
362
363 if ( readDbl(line, pos[1], fieldLen, _clock_bias)
364 || readDbl(line, pos[2], fieldLen, _clock_drift)
365 || readDbl(line, pos[3], fieldLen, _clock_driftrate)) {
366 _checkState = bad;
367 return;
368 }
369 }
370 // =====================
371 // BROADCAST ORBIT - 1
372 // =====================
373 else if (iLine == 1) {
374 if (type() == t_eph::CNAV ||
375 type() == t_eph::CNV2 ||
376 type() == t_eph::L1NV) {
377 if ( readDbl(line, pos[0], fieldLen, _ADOT)
378 || readDbl(line, pos[1], fieldLen, _Crs)
379 || readDbl(line, pos[2], fieldLen, _Delta_n)
380 || readDbl(line, pos[3], fieldLen, _M0)) {
381 _checkState = bad;
382 return;
383 }
384 } else { // LNAV
385 if ( readDbl(line, pos[0], fieldLen, _IODE)
386 || readDbl(line, pos[1], fieldLen, _Crs)
387 || readDbl(line, pos[2], fieldLen, _Delta_n)
388 || readDbl(line, pos[3], fieldLen, _M0)) {
389 _checkState = bad;
390 return;
391 }
392 }
393 }
394 // =====================
395 // BROADCAST ORBIT - 2
396 // =====================
397 else if (iLine == 2) {
398 if ( readDbl(line, pos[0], fieldLen, _Cuc)
399 || readDbl(line, pos[1], fieldLen, _e)
400 || readDbl(line, pos[2], fieldLen, _Cus)
401 || readDbl(line, pos[3], fieldLen, _sqrt_A)) {
402 _checkState = bad;
403 return;
404 }
405 }
406 // =====================
407 // BROADCAST ORBIT - 3
408 // =====================
409 else if (iLine == 3) {
410 if (type() == t_eph::CNAV ||
411 type() == t_eph::CNV2) {
412 if ( readDbl(line, pos[0], fieldLen, _top)
413 || readDbl(line, pos[1], fieldLen, _Cic)
414 || readDbl(line, pos[2], fieldLen, _OMEGA0)
415 || readDbl(line, pos[3], fieldLen, _Cis)) {
416 _checkState = bad;
417 return;
418 }
419 } else if (type() == t_eph::L1NV) {
420 if ( readDbl(line, pos[0], fieldLen, _IODE)
421 || readDbl(line, pos[1], fieldLen, _Cic)
422 || readDbl(line, pos[2], fieldLen, _OMEGA0)
423 || readDbl(line, pos[3], fieldLen, _Cis)) {
424 _checkState = bad;
425 return;
426 }
427 } else { // LNAV
428 if ( readDbl(line, pos[0], fieldLen, _TOEsec)
429 || readDbl(line, pos[1], fieldLen, _Cic)
430 || readDbl(line, pos[2], fieldLen, _OMEGA0)
431 || readDbl(line, pos[3], fieldLen, _Cis)) {
432 _checkState = bad;
433 return;
434 }
435 }
436 }
437 // =====================
438 // BROADCAST ORBIT - 4
439 // =====================
440 else if (iLine == 4) {
441 if ( readDbl(line, pos[0], fieldLen, _i0)
442 || readDbl(line, pos[1], fieldLen, _Crc)
443 || readDbl(line, pos[2], fieldLen, _omega)
444 || readDbl(line, pos[3], fieldLen, _OMEGADOT)) {
445 _checkState = bad;
446 return;
447 }
448 }
449 // =====================
450 // BROADCAST ORBIT - 5
451 // =====================
452 else if (iLine == 5 && system() != t_eph::NavIC) {
453 if (type() == t_eph::CNAV ||
454 type() == t_eph::CNV2) {
455 if ( readDbl(line, pos[0], fieldLen, _IDOT)
456 || readDbl(line, pos[1], fieldLen, _Delta_n_dot)
457 || readDbl(line, pos[2], fieldLen, _URAI_NED0)
458 || readDbl(line, pos[3], fieldLen, _URAI_NED1)) {
459 _checkState = bad;
460 return;
461 }
462 }
463 else { // LNAV
464 if ( readDbl(line, pos[0], fieldLen, _IDOT)
465 || readDbl(line, pos[1], fieldLen, _L2Codes)
466 || readDbl(line, pos[2], fieldLen, _TOEweek)
467 || readDbl(line, pos[3], fieldLen, _L2PFlag)) {
468 _checkState = bad;
469 return;
470 }
471 }
472 } else if (iLine == 5 && system() == t_eph::NavIC) {
473 if (type() == t_eph::LNAV) {
474 if ( readDbl(line, pos[0], fieldLen, _IDOT)
475 || readDbl(line, pos[2], fieldLen, _TOEweek)) {
476 _checkState = bad;
477 return;
478 }
479 }
480 else if (type() == t_eph::L1NV) {
481 if ( readDbl(line, pos[0], fieldLen, _IDOT)
482 || readDbl(line, pos[1], fieldLen, _Delta_n_dot)
483 || readDbl(line, pos[3], fieldLen, _RSF)) {
484 _checkState = bad;
485 return;
486 }
487 }
488 }
489 // =====================
490 // BROADCAST ORBIT - 6
491 // =====================
492 else if (iLine == 6 && system() != t_eph::NavIC) {
493 if (type() == t_eph::CNAV ||
494 type() == t_eph::CNV2) {
495 if ( readDbl(line, pos[0], fieldLen, _URAI_ED)
496 || readDbl(line, pos[1], fieldLen, _health)
497 || readDbl(line, pos[2], fieldLen, _TGD)
498 || readDbl(line, pos[3], fieldLen, _URAI_NED2)) {
499 _checkState = bad;
500 return;
501 }
502 } else { // LNAV
503 if ( readDbl(line, pos[0], fieldLen, _ura)
504 || readDbl(line, pos[1], fieldLen, _health)
505 || readDbl(line, pos[2], fieldLen, _TGD)
506 || readDbl(line, pos[3], fieldLen, _IODC)) {
507 _checkState = bad;
508 return;
509 }
510 }
511 }
512 else if (iLine == 6 && system() == t_eph::NavIC) {
513 if (type() == t_eph::LNAV) {
514 if ( readDbl(line, pos[0], fieldLen, _ura)
515 || readDbl(line, pos[1], fieldLen, _health)
516 || readDbl(line, pos[2], fieldLen, _TGD)) {
517 _checkState = bad;
518 return;
519 }
520 }
521 else if (type() == t_eph::L1NV) {
522 int i = 0;
523 (!_RSF) ? i = 2 : i = 3;
524 if ( readDbl(line, pos[0], fieldLen, _URAI)
525 || readDbl(line, pos[1], fieldLen, _health)
526 || readDbl(line, pos[i], fieldLen, _TGD)) {
527 _checkState = bad;
528 return;
529 }
530 _ura = accuracyFromIndex(int(_URAI), system());
531 }
532 }
533 // =====================
534 // BROADCAST ORBIT - 7
535 // =====================
536 else if (iLine == 7) {
537 if (type() == t_eph::LNAV) {
538 if (readDbl(line, pos[0], fieldLen, _TOT)) {
539 _checkState = bad;
540 return;
541 }
542 if (system() != t_eph::NavIC) {
543 double fitIntervalRnx;
544 if (readDbl(line, pos[1], fieldLen, fitIntervalRnx)) {
545 //fit interval BLK, do nothing
546 _flags_unknown = true;
547 } else {
548 _flags_unknown = false;
549 if (system() == t_eph::GPS) { // in RINEX specified always as time period for GPS
550 _fitInterval = fitIntervalRnx;
551 }
552 else if (system() == t_eph::QZSS) { // specified as flag for QZSS
553 if (rnxVersion == 3.02) {
554 _fitInterval = fitIntervalRnx; // specified as time period
555 } else {
556 _fitInterval = fitIntervalFromFlag(fitIntervalRnx, _IODC, t_eph::QZSS);
557 }
558 }
559 }
560 }
561 }
562 else if (type() == t_eph::CNAV ||
563 type() == t_eph::CNV2) {
564 if ( readDbl(line, pos[0], fieldLen, _ISC_L1CA)
565 || readDbl(line, pos[1], fieldLen, _ISC_L2C)
566 || readDbl(line, pos[2], fieldLen, _ISC_L5I5)
567 || readDbl(line, pos[3], fieldLen, _ISC_L5Q5)) {
568 _checkState = bad;
569 return;
570 }
571 }
572 else if (type() == t_eph::L1NV) {
573 if (!_RSF) {
574 if ( readDbl(line, pos[0], fieldLen, _ISC_S)
575 || readDbl(line, pos[1], fieldLen, _ISC_L1D)) {
576 _checkState = bad;
577 return;
578 }
579 } else {
580 if ( readDbl(line, pos[2], fieldLen, _ISC_L1P)
581 || readDbl(line, pos[3], fieldLen, _ISC_L1D)) {
582 _checkState = bad;
583 return;
584 }
585 }
586 }
587 }
588 // =====================
589 // BROADCAST ORBIT - 8
590 // =====================
591 else if (iLine == 8) {
592 if (type() == t_eph::CNAV) {
593 if ( readDbl(line, pos[0], fieldLen, _TOT)
594 || readDbl(line, pos[1], fieldLen, _wnop)) {
595 _checkState = bad;
596 return;
597 }
598 if (readDbl(line, pos[2], fieldLen, statusflags)) {
599 _flags_unknown = true;
600 }
601 else {
602 _flags_unknown = false;
603 // Bit 0:
604 _intSF = double(bitExtracted(unsigned(statusflags), 1, 0));
605 // Bit 1:
606 _L2Cphasing = double(bitExtracted(unsigned(statusflags), 1, 1));
607 // Bit 2:
608 _alert = double(bitExtracted(unsigned(statusflags), 1, 2));
609 }
610 }
611 else if (type() == t_eph::CNV2) {
612 if ( readDbl(line, pos[0], fieldLen, _ISC_L1Cd)
613 || readDbl(line, pos[1], fieldLen, _ISC_L1Cp)) {
614 _checkState = bad;
615 return;
616 }
617 }
618 else if (type() == t_eph::L1NV) {
619 if ( readDbl(line, pos[0], fieldLen, _TOT)) {
620 _checkState = bad;
621 return;
622 }
623 }
624 }
625 // =====================
626 // BROADCAST ORBIT - 9
627 // =====================
628 else if (iLine == 9) {
629 if (type() == t_eph::CNV2) {
630 if ( readDbl(line, pos[0], fieldLen, _TOT)
631 || readDbl(line, pos[1], fieldLen, _wnop)) {
632 _checkState = bad;
633 return;
634 }
635 if (readDbl(line, pos[2], fieldLen, statusflags)) {
636 _flags_unknown = true;
637 }
638 else {
639 _flags_unknown = false;
640 // Bit 0:
641 _intSF = double(bitExtracted(unsigned(statusflags), 1, 0));
642 if (system() == t_eph::QZSS) {
643 // Bit 1:
644 _ephSF = double(bitExtracted(unsigned(statusflags), 1, 1));
645 }
646 }
647 }
648 }
649 }
650}
651
652// Compute GPS Satellite Position (virtual)
653////////////////////////////////////////////////////////////////////////////
654t_irc t_ephGPS::position(int GPSweek, double GPSweeks, double *xc,
655 double *vv) const {
656
657 static const double omegaEarth = 7292115.1467e-11;
658 static const double gmGRS = 398.6005e12;
659
660 memset(xc, 0, 6 * sizeof(double));
661 memset(vv, 0, 3 * sizeof(double));
662
663 double a0 = _sqrt_A * _sqrt_A;
664 if (a0 == 0) {
665 return failure;
666 }
667
668 double n0 = sqrt(gmGRS / (a0 * a0 * a0));
669
670 bncTime tt(GPSweek, GPSweeks);
671 double tk = tt - bncTime(int(_TOEweek), _TOEsec);
672
673 double n = n0 + _Delta_n;
674 double M = _M0 + n * tk;
675 double E = M;
676 double E_last;
677 int nLoop = 0;
678 do {
679 E_last = E;
680 E = M + _e * sin(E);
681
682 if (++nLoop == 100) {
683 return failure;
684 }
685 } while (fabs(E - E_last) * a0 > 0.001);
686 double v = 2.0 * atan(sqrt((1.0 + _e) / (1.0 - _e)) * tan(E / 2));
687 double u0 = v + _omega;
688 double sin2u0 = sin(2 * u0);
689 double cos2u0 = cos(2 * u0);
690 double r = a0 * (1 - _e * cos(E)) + _Crc * cos2u0 + _Crs * sin2u0;
691 double i = _i0 + _IDOT * tk + _Cic * cos2u0 + _Cis * sin2u0;
692 double u = u0 + _Cuc * cos2u0 + _Cus * sin2u0;
693 double xp = r * cos(u);
694 double yp = r * sin(u);
695 double OM = _OMEGA0 + (_OMEGADOT - omegaEarth) * tk - omegaEarth * _TOEsec;
696
697 double sinom = sin(OM);
698 double cosom = cos(OM);
699 double sini = sin(i);
700 double cosi = cos(i);
701 xc[0] = xp * cosom - yp * cosi * sinom;
702 xc[1] = xp * sinom + yp * cosi * cosom;
703 xc[2] = yp * sini;
704
705 double tc = tt - _TOC;
706 xc[3] = _clock_bias + _clock_drift * tc + _clock_driftrate * tc * tc;
707
708 // Velocity
709 // --------
710 double tanv2 = tan(v / 2);
711 double dEdM = 1 / (1 - _e * cos(E));
712 double dotv = sqrt((1.0 + _e) / (1.0 - _e)) / cos(E / 2) / cos(E / 2)
713 / (1 + tanv2 * tanv2) * dEdM * n;
714 double dotu = dotv + (-_Cuc * sin2u0 + _Cus * cos2u0) * 2 * dotv;
715 double dotom = _OMEGADOT - omegaEarth;
716 double doti = _IDOT + (-_Cic * sin2u0 + _Cis * cos2u0) * 2 * dotv;
717 double dotr = a0 * _e * sin(E) * dEdM * n
718 + (-_Crc * sin2u0 + _Crs * cos2u0) * 2 * dotv;
719 double dotx = dotr * cos(u) - r * sin(u) * dotu;
720 double doty = dotr * sin(u) + r * cos(u) * dotu;
721
722 vv[0] = cosom * dotx - cosi * sinom * doty // dX / dr
723 - xp * sinom * dotom - yp * cosi * cosom * dotom // dX / dOMEGA
724 + yp * sini * sinom * doti; // dX / di
725
726 vv[1] = sinom * dotx + cosi * cosom * doty + xp * cosom * dotom
727 - yp * cosi * sinom * dotom - yp * sini * cosom * doti;
728
729 vv[2] = sini * doty + yp * cosi * doti;
730
731 // Relativistic Correction
732 // -----------------------
733 // Both forms are equivalent per the ICD for an unperturbed broadcast orbit,
734 // but differ numerically once harmonic (Crc/Crs/Cuc/Cus/Cic/Cis) terms are
735 // included in xc/vv. The velocity-based form (IS-GPS-200D 20.3.3.3.3.1) is
736 // the one recommended together with RTCM-SSR corrections; the classic
737 // eccentricity-based form is used otherwise (broadcast-only or IGS-SSR).
738 if (_orbCorr && _orbCorr->_rtcmSsr) {
739 xc[3] -= 2.0 * (xc[0]*vv[0] + xc[1]*vv[1] + xc[2]*vv[2]) / t_CST::c / t_CST::c;
740 }
741 else {
742 xc[3] -= 4.442807633e-10 * _e * sqrt(a0) * sin(E);
743 }
744
745 xc[4] = _clock_drift + _clock_driftrate * tc;
746 xc[5] = _clock_driftrate;
747
748 return success;
749}
750
751// RINEX Format String
752//////////////////////////////////////////////////////////////////////////////
753QString t_ephGPS::toString(double version) const {
754
755 if (version < 4.0 &&
756 (type() == t_eph::CNAV ||
757 type() == t_eph::CNV2 ||
758 type() == t_eph::L1NV )) {
759 return "";
760 }
761
762 QString ephStr = typeStr(_type, _prn, version);
763 QString rnxStr = ephStr + rinexDateStr(_TOC, _prn, version);
764
765 QTextStream out(&rnxStr);
766
767 out
768 << QString("%1%2%3\n")
769 .arg(_clock_bias, 19, 'e', 12)
770 .arg(_clock_drift, 19, 'e', 12)
771 .arg(_clock_driftrate, 19, 'e', 12);
772
773 QString fmt = version < 3.0 ? " %1%2%3%4\n" : " %1%2%3%4\n";
774
775 // =====================
776 // BROADCAST ORBIT - 1
777 // =====================
778 if (type() == t_eph::CNAV ||
779 type() == t_eph::CNV2 ||
780 type() == t_eph::L1NV) {
781 out
782 << QString(fmt)
783 .arg(_ADOT, 19, 'e', 12)
784 .arg(_Crs, 19, 'e', 12)
785 .arg(_Delta_n, 19, 'e', 12)
786 .arg(_M0, 19, 'e', 12);
787 } else { // LNAV, undefined
788 out
789 << QString(fmt)
790 .arg(_IODE, 19, 'e', 12)
791 .arg(_Crs, 19, 'e', 12)
792 .arg(_Delta_n, 19, 'e', 12)
793 .arg(_M0, 19, 'e', 12);
794 }
795 // =====================
796 // BROADCAST ORBIT - 2
797 // =====================
798 out
799 << QString(fmt)
800 .arg(_Cuc, 19, 'e', 12)
801 .arg(_e, 19, 'e', 12)
802 .arg(_Cus, 19, 'e', 12)
803 .arg(_sqrt_A, 19, 'e', 12);
804 // =====================
805 // BROADCAST ORBIT - 3
806 // =====================
807 if (type() == t_eph::CNAV ||
808 type() == t_eph::CNV2) {
809 out
810 << QString(fmt)
811 .arg(_top, 19, 'e', 12)
812 .arg(_Cic, 19, 'e', 12)
813 .arg(_OMEGA0, 19, 'e', 12)
814 .arg(_Cis, 19, 'e', 12);
815 }
816 else if (type() == t_eph::L1NV) {
817 out
818 << QString(fmt)
819 .arg(_IODE, 19, 'e', 12)
820 .arg(_Cic, 19, 'e', 12)
821 .arg(_OMEGA0, 19, 'e', 12)
822 .arg(_Cis, 19, 'e', 12);
823 }
824 else { // LNAV, undefined
825 out
826 << QString(fmt)
827 .arg(_TOEsec, 19, 'e', 12)
828 .arg(_Cic, 19, 'e', 12)
829 .arg(_OMEGA0, 19, 'e', 12)
830 .arg(_Cis, 19, 'e', 12);
831 }
832 // =====================
833 // BROADCAST ORBIT - 4
834 // =====================
835 out
836 << QString(fmt)
837 .arg(_i0, 19, 'e', 12)
838 .arg(_Crc, 19, 'e', 12)
839 .arg(_omega, 19, 'e', 12)
840 .arg(_OMEGADOT, 19, 'e', 12);
841 // =====================
842 // BROADCAST ORBIT - 5
843 // =====================
844 if (system() != t_eph::NavIC) {
845 if (type() == t_eph::CNAV ||
846 type() == t_eph::CNV2) {
847 out
848 << QString(fmt)
849 .arg(_IDOT, 19, 'e', 12)
850 .arg(_Delta_n_dot, 19, 'e', 12)
851 .arg(_URAI_NED0, 19, 'e', 12)
852 .arg(_URAI_NED1, 19, 'e', 12);
853 }
854 else { // LNAV, undefined
855 out
856 << QString(fmt)
857 .arg(_IDOT, 19, 'e', 12)
858 .arg(_L2Codes, 19, 'e', 12)
859 .arg(_TOEweek, 19, 'e', 12)
860 .arg(_L2PFlag, 19, 'e', 12);
861 }
862 }
863 else {
864 if (type() == t_eph::LNAV ||
865 type() == t_eph::undefined) {
866 out
867 << QString(fmt)
868 .arg(_IDOT, 19, 'e', 12)
869 .arg("", 19, QChar(' '))
870 .arg(_TOEweek, 19, 'e', 12)
871 .arg("", 19, QChar(' '));
872 }
873 else if (type() == t_eph::L1NV) {
874 out
875 << QString(fmt)
876 .arg(_IDOT, 19, 'e', 12)
877 .arg(_Delta_n_dot, 19, 'e', 12)
878 .arg("", 19, QChar(' '))
879 .arg(_RSF, 19, 'e', 12);
880 }
881 }
882 // =====================
883 // BROADCAST ORBIT - 6
884 // =====================
885 if (system() != t_eph::NavIC) {
886 if (type() == t_eph::CNAV ||
887 type() == t_eph::CNV2) {
888 out
889 << QString(fmt)
890 .arg(_URAI_ED, 19, 'e', 12)
891 .arg(_health, 19, 'e', 12)
892 .arg(_TGD, 19, 'e', 12)
893 .arg(_URAI_NED2, 19, 'e', 12);
894 }
895 else { // LNAV, undefined
896 out
897 << QString(fmt)
898 .arg(_ura, 19, 'e', 12)
899 .arg(_health, 19, 'e', 12)
900 .arg(_TGD, 19, 'e', 12)
901 .arg(_IODC, 19, 'e', 12);
902 }
903 }
904 else {
905 if (type() == t_eph::LNAV ||
906 type() == t_eph::undefined) {
907 out
908 << QString(fmt)
909 .arg(_ura, 19, 'e', 12)
910 .arg(_health, 19, 'e', 12)
911 .arg(_TGD, 19, 'e', 12)
912 .arg("", 19, QChar(' '));
913 }
914 else if (type() == t_eph::L1NV) {
915 int i = 0; (!_RSF) ? i = 2 : i = 3;
916 if (i == 2) {
917 out
918 << QString(fmt)
919 .arg(_URAI, 19, 'e', 12)
920 .arg(_health, 19, 'e', 12)
921 .arg(_TGD, 19, 'e', 12)
922 .arg("", 19, QChar(' '));
923 }
924 else {
925 out
926 << QString(fmt)
927 .arg(_URAI, 19, 'e', 12)
928 .arg(_health, 19, 'e', 12)
929 .arg("", 19, QChar(' '))
930 .arg(_TGD, 19, 'e', 12);
931 }
932 }
933 }
934 // =====================
935 // BROADCAST ORBIT - 7
936 // =====================
937 if (type() == t_eph::LNAV ||
938 type() == t_eph::undefined) {
939
940 double tot = _TOT;
941 if (tot == 0.9999e9 && version < 3.0) {
942 tot = 0.0;
943 }
944 // fitInterval
945 if (system() == t_eph::NavIC) {
946 out
947 << QString(fmt)
948 .arg(tot, 19, 'e', 12)
949 .arg("", 19, QChar(' '))
950 .arg("", 19, QChar(' '))
951 .arg("", 19, QChar(' '));
952 }
953 else {
954 if (_flags_unknown) {
955 out
956 << QString(fmt)
957 .arg(tot, 19, 'e', 12)
958 .arg("", 19, QChar(' '))
959 .arg("", 19, QChar(' '))
960 .arg("", 19, QChar(' '));
961 }
962 else {
963 // for GPS and QZSS in version 3.02 specified in hours
964 double fitIntervalRnx = _fitInterval;
965 // otherwise specified as flag
966 if (system() == t_eph::QZSS && version != 3.02) {
967 (_fitInterval == 2.0) ? fitIntervalRnx = 0.0 : fitIntervalRnx = 1.0;
968 }
969 out
970 << QString(fmt)
971 .arg(tot, 19, 'e', 12)
972 .arg(fitIntervalRnx, 19, 'e', 12)
973 .arg("", 19, QChar(' '))
974 .arg("", 19, QChar(' '));
975 }
976 }
977 }
978 else if (type() == t_eph::CNAV ||
979 type() == t_eph::CNV2) {
980 out
981 << QString(fmt)
982 .arg(_ISC_L1CA, 19, 'e', 12)
983 .arg(_ISC_L2C, 19, 'e', 12)
984 .arg(_ISC_L5I5, 19, 'e', 12)
985 .arg(_ISC_L5Q5, 19, 'e', 12);
986 }
987 else if (type() == t_eph::L1NV) {
988 if (_RSF) {
989 out
990 << QString(fmt)
991 .arg(_ISC_S, 19, 'e', 12)
992 .arg(_ISC_L1D, 19, 'e', 12)
993 .arg("", 19, QChar(' '))
994 .arg("", 19, QChar(' '));
995 }
996 else {
997 out
998 << QString(fmt)
999 .arg("", 19, QChar(' '))
1000 .arg("", 19, QChar(' '))
1001 .arg(_ISC_L1P, 19, 'e', 12)
1002 .arg(_ISC_L1D, 19, 'e', 12);
1003 }
1004 }
1005 // =====================
1006 // BROADCAST ORBIT - 8
1007 // =====================
1008 if (type() == t_eph::CNAV) {
1009 int intFlags = 0;
1010 if (!_flags_unknown) {
1011 // Bit 0:
1012 if (_intSF) {intFlags |= (1 << 0);}
1013 // Bit 1:
1014 if (_L2Cphasing) {intFlags |= (1 << 1);}
1015 // Bit 2:
1016 if (_alert) {intFlags |= (1 << 2);}
1017 out
1018 << QString(fmt)
1019 .arg(_TOT, 19, 'e', 12)
1020 .arg(_wnop, 19, 'e', 12)
1021 .arg(double(intFlags), 19, 'e', 12)
1022 .arg("", 19, QChar(' '));
1023 }
1024 else {
1025 out
1026 << QString(fmt)
1027 .arg(_TOT, 19, 'e', 12)
1028 .arg(_wnop, 19, 'e', 12)
1029 .arg("", 19, QChar(' '))
1030 .arg("", 19, QChar(' '));
1031 }
1032 }
1033 else if (type() == t_eph::CNV2) {
1034 out
1035 << QString(fmt)
1036 .arg(_ISC_L1Cd, 19, 'e', 12)
1037 .arg(_ISC_L1Cp, 19, 'e', 12)
1038 .arg("", 19, QChar(' '))
1039 .arg("", 19, QChar(' '));
1040 }
1041 else if (type() == t_eph::L1NV) {
1042 out
1043 << QString(fmt)
1044 .arg(_TOT, 19, 'e', 12)
1045 .arg("", 19, QChar(' '))
1046 .arg("", 19, QChar(' '))
1047 .arg("", 19, QChar(' '));
1048 }
1049 // =====================
1050 // BROADCAST ORBIT - 9
1051 // =====================
1052 if (type() == t_eph::CNV2) {
1053 int intFlags = 0;
1054 if (!_flags_unknown) {
1055 // Bit 0:
1056 if (_intSF) {intFlags |= (1 << 0);}
1057 if (system() == t_eph::QZSS) {
1058 // Bit 1:
1059 if (_ephSF) {intFlags |= (1 << 1);}
1060 }
1061 out
1062 << QString(fmt)
1063 .arg(_TOT, 19, 'e', 12)
1064 .arg(_wnop, 19, 'e', 12)
1065 .arg(double(intFlags), 19, 'e', 12)
1066 .arg("", 19, QChar(' '));
1067 }
1068 else {
1069 out
1070 << QString(fmt)
1071 .arg(_TOT, 19, 'e', 12)
1072 .arg(_wnop, 19, 'e', 12)
1073 .arg("", 19, QChar(' '))
1074 .arg("", 19, QChar(' '));
1075 }
1076 }
1077 return rnxStr;
1078}
1079
1080// Health status of GPS Ephemeris (virtual)
1081////////////////////////////////////////////////////////////////////////////
1082unsigned int t_ephGPS::isUnhealthy() const {
1083
1084 switch (system()) {
1085 case t_eph::GPS:
1086 case t_eph::QZSS:
1087 switch (type()) {
1088 case t_eph::LNAV:
1089 case t_eph::CNAV:
1090 case t_eph::CNV2:
1091 if (_health == 0.0) {
1092 return 0;
1093 }
1094 else {
1095 return 1;
1096 }
1097 break;
1098 }
1099 break;
1100 case t_eph::NavIC:
1101 switch (type()) {
1102 case t_eph::LNAV:
1103 if (_health == 0.0) { // L1 & S healthy
1104 return 0;
1105 }
1106 if (_health == 1.0 || // L5 healthy and S unhealthy,
1107 _health == 2.0 || // L5 unhealthy and S healthy
1108 _health == 3.0) { // both L5 and S unhealthy
1109 return 1;
1110 }
1111 break;
1112 case t_eph::L1NV:
1113 if (_health == 0.0) { // All navigation data on L1-SPS signal OK
1114 return 0;
1115 }
1116 if (_health == 1.0) { // Some or all navigation data on L1-SPS signal bad
1117 return 1;
1118 }
1119 break;
1120 }
1121 break;
1122 }
1123 return 0;
1124}
1125
1126// Constructor
1127//////////////////////////////////////////////////////////////////////////////
1128t_ephGlo::t_ephGlo(double rnxVersion, const QStringList &lines, const QString typeStr) {
1129
1130 setType(typeStr);
1131
1132 int nLines = 4;
1133
1134 // Source RINEX version < 4
1135 if (type() == t_eph::undefined) {
1136 // cannot be determined from input data
1137 // but is set to be able to work with old RINEX files in BNC applications
1138 _type = t_eph::FDMA_M;
1139 }
1140
1141 if (rnxVersion >= 3.05) {
1142 nLines += 1;
1143 } else {
1144 _M_delta_tau = 0.9999e9; // unknown
1145 _M_FT = 1.5e1; // unknown
1146 _statusflags_unknown = true;
1147 _healthflags_unknown = true;
1148 }
1149
1150 if (lines.size() != nLines) {
1151 _checkState = bad;
1152 return;
1153 }
1154
1155 // RINEX Format
1156 // ------------
1157 int fieldLen = 19;
1158 double statusflags = 0.0;
1159 double healthflags = 0.0;
1160 double sourceflags = 0.0;
1161 _tauC = 0.0;
1162 _tau1 = 0.0;
1163 _tau2 = 0.0;
1164 _additional_data_availability = 0.0;
1165
1166 int pos[4];
1167 pos[0] = (rnxVersion <= 2.12) ? 3 : 4;
1168 pos[1] = pos[0] + fieldLen;
1169 pos[2] = pos[1] + fieldLen;
1170 pos[3] = pos[2] + fieldLen;
1171
1172 // Read four lines
1173 // ---------------
1174 for (int iLine = 0; iLine < nLines; iLine++) {
1175 QString line = lines[iLine];
1176
1177 if (iLine == 0) {
1178 QTextStream in(line.left(pos[1]).toLatin1());
1179
1180 int year, month, day, hour, min;
1181 double sec;
1182
1183 QString prnStr, n;
1184 in >> prnStr;
1185 if (prnStr.size() == 1 && prnStr[0] == 'R') {
1186 in >> n;
1187 prnStr.append(n);
1188 }
1189
1190 if (prnStr.at(0) == 'R') {
1191 _prn.set('R', prnStr.mid(1).toInt());
1192 } else {
1193 _prn.set('R', prnStr.toInt());
1194 }
1195
1196 in >> year >> month >> day >> hour >> min >> sec;
1197 if (year < 80) {
1198 year += 2000;
1199 } else if (year < 100) {
1200 year += 1900;
1201 }
1202
1203 _gps_utc = gnumleap(year, month, day);
1204
1205 _TOC.set(year, month, day, hour, min, sec);
1206 _TOC = _TOC + _gps_utc;
1207 int nd = int((_TOC.gpssec())) / (24.0 * 60.0 * 60.0);
1208 if ( readDbl(line, pos[1], fieldLen, _tau)
1209 || readDbl(line, pos[2], fieldLen, _gamma)
1210 || readDbl(line, pos[3], fieldLen, _tki)) {
1211 _checkState = bad;
1212 return;
1213 }
1214 _tki -= nd * 86400.0;
1215 _tau = -_tau;
1216 }
1217 // =====================
1218 // BROADCAST ORBIT - 1
1219 // =====================
1220 else if (iLine == 1) {
1221 if ( readDbl(line, pos[0], fieldLen, _x_pos)
1222 || readDbl(line, pos[1], fieldLen, _x_vel)
1223 || readDbl(line, pos[2], fieldLen, _x_acc)
1224 || readDbl(line, pos[3], fieldLen, _health)) {
1225 _checkState = bad;
1226 return;
1227 }
1228 }
1229 // =====================
1230 // BROADCAST ORBIT - 2
1231 // =====================
1232 else if (iLine == 2) {
1233 if (type() == t_eph::FDMA ||
1234 type() == t_eph::FDMA_M) {
1235 if ( readDbl(line, pos[0], fieldLen, _y_pos)
1236 || readDbl(line, pos[1], fieldLen, _y_vel)
1237 || readDbl(line, pos[2], fieldLen, _y_acc)
1238 || readDbl(line, pos[3], fieldLen, _frq_num)) {
1239 _checkState = bad;
1240 return;
1241 }
1242 }
1243 else { //L1OC, L3OC
1244 if ( readDbl(line, pos[0], fieldLen, _y_pos)
1245 || readDbl(line, pos[1], fieldLen, _y_vel)
1246 || readDbl(line, pos[2], fieldLen, _y_acc)
1247 || readDbl(line, pos[3], fieldLen, statusflags)) {
1248 _checkState = bad;
1249 return;
1250 }
1251 _data_validity = int(statusflags);
1252 }
1253 }
1254 // =====================
1255 // BROADCAST ORBIT - 3
1256 // =====================
1257 else if (iLine == 3) {
1258 if (type() == t_eph::FDMA ||
1259 type() == t_eph::FDMA_M) {
1260 if ( readDbl(line, pos[0], fieldLen, _z_pos)
1261 || readDbl(line, pos[1], fieldLen, _z_vel)
1262 || readDbl(line, pos[2], fieldLen, _z_acc)
1263 || readDbl(line, pos[3], fieldLen, _E)) {
1264 _checkState = bad;
1265 return;
1266 }
1267 }
1268 else if (type() == t_eph::L1OC) {
1269 if ( readDbl(line, pos[0], fieldLen, _z_pos)
1270 || readDbl(line, pos[1], fieldLen, _z_vel)
1271 || readDbl(line, pos[2], fieldLen, _z_acc)
1272 || readDbl(line, pos[3], fieldLen, _TGD_L2OCp)) {
1273 _checkState = bad;
1274 return;
1275 }
1276 }
1277 else if (type() == t_eph::L3OC) {
1278 if ( readDbl(line, pos[0], fieldLen, _z_pos)
1279 || readDbl(line, pos[1], fieldLen, _z_vel)
1280 || readDbl(line, pos[2], fieldLen, _z_acc)
1281 || readDbl(line, pos[3], fieldLen, _TGD_L3OCp)) {
1282 _checkState = bad;
1283 return;
1284 }
1285 }
1286 }
1287 // =====================
1288 // BROADCAST ORBIT - 4
1289 // =====================
1290 else if (iLine == 4) {
1291 if (type() == t_eph::FDMA ||
1292 type() == t_eph::FDMA_M) {
1293 if (readDbl(line, pos[0], fieldLen, statusflags)) {
1294 //status flags BLK, do nothing
1295 _statusflags_unknown = true;
1296 } else {
1297 _statusflags_unknown = false;
1298 // status flags
1299 // ============
1300 // bit 0-1
1301 _M_P = double(bitExtracted(unsigned(statusflags), 2, 0));
1302 // bit 2-3
1303 _P1 = double(bitExtracted(unsigned(statusflags), 2, 2));
1304 // bit 4
1305 _P2 = double(bitExtracted(unsigned(statusflags), 1, 4));
1306 // bit 5
1307 _P3 = double(bitExtracted(unsigned(statusflags), 1, 5));
1308 // bit 6
1309 _M_P4 = double(bitExtracted(unsigned(statusflags), 1, 6));
1310 // bit 7-8
1311 _M_M = double(bitExtracted(unsigned(statusflags), 2, 7));
1312 /// GLO M/K exclusive flags/values only valid if flag M is set to '01'
1313 if (!_M_M) {
1314 _M_P = 0.0;
1315 _M_l3 = 0.0;
1316 _M_P4 = 0.0;
1317 _M_FE = 0.0;
1318 _M_FT = 0.0;
1319 _M_NA = 0.0;
1320 _M_NT = 0.0;
1321 _M_N4 = 0.0;
1322 _M_l5 = 0.0;
1323 _M_tau_GPS = 0.0;
1324 _M_delta_tau = 0.0;
1325 _type = t_eph::FDMA;
1326 }
1327 else {
1328 _type = t_eph::FDMA_M;
1329 }
1330 }
1331 if ( readDbl(line, pos[1], fieldLen, _M_delta_tau)
1332 || readDbl(line, pos[2], fieldLen, _M_FT)) {
1333 _checkState = bad;
1334 return;
1335 }
1336 if (readDbl(line, pos[3], fieldLen, healthflags)) {
1337 // health flags BLK
1338 _healthflags_unknown = true;
1339 } else {
1340 _healthflags_unknown = false;
1341 // health flags
1342 // ============
1343 // bit 0 (is to be ignored, if bit 1 is zero)
1344 _almanac_health = double(bitExtracted(unsigned(healthflags), 1, 0));
1345 // bit 1
1346 _almanac_health_availablility_indicator =
1347 double(bitExtracted(unsigned(healthflags), 1, 1));
1348 // bit 2; GLO-M/K only, health bit of string 3
1349 _M_l3 = double(bitExtracted(unsigned(healthflags), 1, 2));
1350 }
1351 }
1352 else if (type() == t_eph::L1OC ||
1353 type() == t_eph::L3OC) {
1354 if ( readDbl(line, pos[0], fieldLen, _sat_type)
1355 || readDbl(line, pos[1], fieldLen, sourceflags)
1356 || readDbl(line, pos[2], fieldLen, _EE)
1357 || readDbl(line, pos[3], fieldLen, _ET)) {
1358 _checkState = bad;
1359 return;
1360 }
1361 // sourceflags:
1362 // ============
1363 // bit 0-1
1364 _RT = double(bitExtracted(unsigned(sourceflags), 2, 0));
1365 // bit 2-3:
1366 _RE = double(bitExtracted(unsigned(sourceflags), 2, 2));
1367 }
1368 }
1369 // =====================
1370 // BROADCAST ORBIT - 5
1371 // =====================
1372 else if (iLine == 5) {
1373 if (type() == t_eph::L1OC ||
1374 type() == t_eph::L3OC) {
1375 if ( readDbl(line, pos[0], fieldLen, _attitude_P2)
1376 || readDbl(line, pos[1], fieldLen, _Tin)
1377 || readDbl(line, pos[2], fieldLen, _tau1)
1378 || readDbl(line, pos[3], fieldLen, _tau2)) {
1379 _checkState = bad;
1380 return;
1381 }
1382 }
1383 }
1384 // =====================
1385 // BROADCAST ORBIT - 6
1386 // =====================
1387 else if (iLine == 6) {
1388 if (type() == t_eph::L1OC ||
1389 type() == t_eph::L3OC) {
1390 if ( readDbl(line, pos[0], fieldLen, _yaw)
1391 || readDbl(line, pos[1], fieldLen, _sn)
1392 || readDbl(line, pos[2], fieldLen, _angular_rate)
1393 || readDbl(line, pos[3], fieldLen, _angular_acc)) {
1394 _checkState = bad;
1395 return;
1396 }
1397 }
1398 }
1399 // =====================
1400 // BROADCAST ORBIT - 7
1401 // =====================
1402 else if (iLine == 7) {
1403 if (type() == t_eph::L1OC ||
1404 type() == t_eph::L3OC) {
1405 if ( readDbl(line, pos[0], fieldLen, _angular_rate_max)
1406 || readDbl(line, pos[1], fieldLen, _X_PC)
1407 || readDbl(line, pos[2], fieldLen, _Y_PC)
1408 || readDbl(line, pos[3], fieldLen, _Z_PC)) {
1409 _checkState = bad;
1410 return;
1411 }
1412 }
1413 }
1414 // =====================
1415 // BROADCAST ORBIT - 8
1416 // =====================
1417 else if (iLine == 8) {
1418 if (type() == t_eph::L1OC ||
1419 type() == t_eph::L3OC) {
1420 if ( readDbl(line, pos[0], fieldLen, _M_FE)
1421 || readDbl(line, pos[1], fieldLen, _M_FT)
1422 || readDbl(line, pos[3], fieldLen, _TOT)) {
1423 _checkState = bad;
1424 return;
1425 }
1426 }
1427 }
1428 }
1429
1430 _prn.setFlag(type());
1431
1432 // Initialize status vector
1433 // ------------------------
1434 _tt = _TOC;
1435 _xv.ReSize(6);
1436 _xv = 0.0;
1437 _xv(1) = _x_pos * 1.e3;
1438 _xv(2) = _y_pos * 1.e3;
1439 _xv(3) = _z_pos * 1.e3;
1440 _xv(4) = _x_vel * 1.e3;
1441 _xv(5) = _y_vel * 1.e3;
1442 _xv(6) = _z_vel * 1.e3;
1443}
1444
1445// Compute Glonass Satellite Position (virtual)
1446////////////////////////////////////////////////////////////////////////////
1447t_irc t_ephGlo::position(int GPSweek, double GPSweeks, double *xc,
1448 double *vv) const {
1449
1450 static const double nominalStep = 10.0;
1451
1452 memset(xc, 0, 6 * sizeof(double));
1453 memset(vv, 0, 3 * sizeof(double));
1454
1455 bncTime tt(GPSweek, GPSweeks);
1456 double dtPos = tt - _TOC;
1457
1458 if (fabs(dtPos) > 24 * 3600.0) {
1459 return failure;
1460 }
1461
1462 int nSteps = int(fabs(dtPos) / nominalStep) + 1;
1463 double step = dtPos / nSteps;
1464
1465 double acc[3];
1466 acc[0] = _x_acc * 1.e3;
1467 acc[1] = _y_acc * 1.e3;
1468 acc[2] = _z_acc * 1.e3;
1469
1470 // Always integrate fresh from the pristine, decoded TOC state rather
1471 // than rolling _tt/_xv forward across repeated calls. position() can be
1472 // called very frequently (multiple times per epoch, from multiple call
1473 // sites) - chaining many short integration steps across those calls,
1474 // instead of one direct integration to the requested time, lets small
1475 // per-call numerical error accumulate over wall-clock time independent
1476 // of how far the request actually is from TOC, producing a slow but
1477 // steady drift (observed: ~0.5-0.7 mm/s along-track for a GLONASS
1478 // satellite whose broadcast message was barely a few minutes old).
1479 // ----------------------------------------------------------------
1480 ColumnVector xv(6);
1481 xv(1) = _x_pos * 1.e3;
1482 xv(2) = _y_pos * 1.e3;
1483 xv(3) = _z_pos * 1.e3;
1484 xv(4) = _x_vel * 1.e3;
1485 xv(5) = _y_vel * 1.e3;
1486 xv(6) = _z_vel * 1.e3;
1487
1488 bncTime ttLocal = _TOC;
1489 for (int ii = 1; ii <= nSteps; ii++) {
1490 xv = rungeKutta4(ttLocal.gpssec(), xv, step, acc, glo_deriv);
1491 ttLocal = ttLocal + step;
1492 }
1493
1494 // Position and Velocity
1495 // ---------------------
1496 xc[0] = xv(1);
1497 xc[1] = xv(2);
1498 xc[2] = xv(3);
1499
1500 vv[0] = xv(4);
1501 vv[1] = xv(5);
1502 vv[2] = xv(6);
1503
1504 // Clock Correction
1505 // ----------------
1506 double dtClk = tt - _TOC;
1507 xc[3] = -_tau + _gamma * dtClk;
1508
1509 xc[4] = _gamma;
1510 xc[5] = 0.0;
1511
1512 return success;
1513}
1514
1515// RINEX Format String
1516//////////////////////////////////////////////////////////////////////////////
1517QString t_ephGlo::toString(double version) const {
1518
1519 if (version < 4.0 &&
1520 (type() == t_eph::L1OC ||
1521 type() == t_eph::L3OC )) {
1522 return "";
1523 }
1524
1525 QString ephStr = typeStr(_type, _prn, version);
1526 QString rnxStr = ephStr + rinexDateStr(_TOC - _gps_utc, _prn, version);
1527 int nd = int((_TOC - _gps_utc).gpssec()) / (24.0 * 60.0 * 60.0);
1528 QTextStream out(&rnxStr);
1529
1530 out
1531 << QString("%1%2%3\n")
1532 .arg(-_tau, 19, 'e', 12)
1533 .arg(_gamma, 19, 'e', 12)
1534 .arg(_tki + nd * 86400.0, 19, 'e', 12);
1535
1536 QString fmt = version < 3.0 ? " %1%2%3%4\n" : " %1%2%3%4\n";
1537 // =====================
1538 // BROADCAST ORBIT - 1
1539 // =====================
1540 out
1541 << QString(fmt)
1542 .arg(_x_pos, 19, 'e', 12)
1543 .arg(_x_vel, 19, 'e', 12)
1544 .arg(_x_acc, 19, 'e', 12)
1545 .arg(_health, 19, 'e', 12);
1546
1547 // =====================
1548 // BROADCAST ORBIT - 2
1549 // =====================
1550 if (type() == t_eph::FDMA ||
1551 type() == t_eph::FDMA_M) {
1552 out
1553 << QString(fmt)
1554 .arg(_y_pos, 19, 'e', 12)
1555 .arg(_y_vel, 19, 'e', 12)
1556 .arg(_y_acc, 19, 'e', 12)
1557 .arg(_frq_num, 19, 'e', 12);
1558 }
1559 else { //L1OC, L3OC
1560 out
1561 << QString(fmt)
1562 .arg(_y_pos, 19, 'e', 12)
1563 .arg(_y_vel, 19, 'e', 12)
1564 .arg(_y_acc, 19, 'e', 12)
1565 .arg(double(_data_validity), 19, 'e', 12);
1566 }
1567 // =====================
1568 // BROADCAST ORBIT - 3
1569 // =====================
1570 if (type() == t_eph::FDMA ||
1571 type() == t_eph::FDMA_M) {
1572 out
1573 << QString(fmt)
1574 .arg(_z_pos, 19, 'e', 12)
1575 .arg(_z_vel, 19, 'e', 12)
1576 .arg(_z_acc, 19, 'e', 12)
1577 .arg(_E, 19, 'e', 12);
1578 }
1579 else if (type() == t_eph::L1OC) {
1580 out
1581 << QString(fmt)
1582 .arg(_z_pos, 19, 'e', 12)
1583 .arg(_z_vel, 19, 'e', 12)
1584 .arg(_z_acc, 19, 'e', 12)
1585 .arg(_TGD_L2OCp, 19, 'e', 12);
1586 }
1587 else if (type() == t_eph::L3OC) {
1588 out
1589 << QString(fmt)
1590 .arg(_z_pos, 19, 'e', 12)
1591 .arg(_z_vel, 19, 'e', 12)
1592 .arg(_z_acc, 19, 'e', 12)
1593 .arg(_TGD_L3OCp, 19, 'e', 12);
1594 }
1595 if (version >= 3.05) {
1596 // =====================
1597 // BROADCAST ORBIT - 4
1598 // =====================
1599 if (type() == t_eph::FDMA ||
1600 type() == t_eph::FDMA_M){
1601 int statusflags = 0;
1602 int healthflags = 0;
1603 if (!_statusflags_unknown ) {
1604 // bit 0-1
1605 if (_M_P == 1.0) {statusflags |= (1 << 0);}
1606 else if (_M_P == 2.0) {statusflags |= (1 << 1);}
1607 else if (_M_P == 3.0) {statusflags |= (1 << 0); statusflags |= (1 << 1);}
1608 // bit 2-3
1609 if (_P1 == 1.0) {statusflags |= (1 << 2);}
1610 else if (_P1 == 2.0) {statusflags |= (1 << 3);}
1611 else if (_P1 == 3.0) {statusflags |= (1 << 2); statusflags |= (1 << 3);}
1612 // bit 4
1613 if (_P2) {statusflags |= (1 << 4);}
1614 // bit 5
1615 if (_P3) {statusflags |= (1 << 5);}
1616 // bit 6
1617 if (_M_P4) {statusflags |= (1 << 6);}
1618 // bit 7-8
1619 if (_M_M == 1.0) {statusflags |= (1 << 7);}
1620 }
1621 if (!_healthflags_unknown) {
1622 // bit 0 (is to be ignored, if bit 1 is zero)
1623 if (_almanac_health) {healthflags |= (1 << 0);}
1624 // bit 1
1625 if (_almanac_health_availablility_indicator) {healthflags |= (1 << 1);}
1626 // bit 2
1627 if (_M_l3) {healthflags |= (1 << 2);}
1628 }
1629
1630 if (_statusflags_unknown && _healthflags_unknown) {
1631 out
1632 << QString(fmt)
1633 .arg("", 19, QChar(' ')) // status-flags BNK (unknown)
1634 .arg(_M_delta_tau, 19, 'e', 12)
1635 .arg(_M_FT, 19, 'e', 12)
1636 .arg("", 19, QChar(' '));// health-flags BNK (unknown)
1637 }
1638 else if (!_statusflags_unknown && _healthflags_unknown) {
1639 out
1640 << QString(fmt)
1641 .arg(double(statusflags), 19, 'e', 12)
1642 .arg(_M_delta_tau, 19, 'e', 12)
1643 .arg(_M_FT, 19, 'e', 12)
1644 .arg("", 19, QChar(' '));// health-flags BNK (unknown)
1645 }
1646 else if (_statusflags_unknown && !_healthflags_unknown) {
1647 out
1648 << QString(fmt)
1649 .arg("", 19, QChar(' ')) // status-flags BNK (unknown)
1650 .arg(_M_delta_tau, 19, 'e', 12)
1651 .arg(_M_FT, 19, 'e', 12)
1652 .arg(double(healthflags), 19, 'e', 12);
1653 }
1654 else if (!_statusflags_unknown && !_healthflags_unknown) {
1655 out
1656 << QString(fmt)
1657 .arg(double(statusflags), 19, 'e', 12)
1658 .arg(_M_delta_tau, 19, 'e', 12)
1659 .arg(_M_FT, 19, 'e', 12)
1660 .arg(double(healthflags), 19, 'e', 12);
1661 }
1662 }
1663 else if (type() == t_eph::L1OC ||
1664 type() == t_eph::L3OC) {
1665 int sourceflags = 0;
1666 // bit 0-1
1667 if (_RT == 1.0) {sourceflags |= (1 << 0);}
1668 else if (_RT == 2.0) {sourceflags |= (1 << 1);}
1669 else if (_RT == 3.0) {sourceflags |= (1 << 0); sourceflags |= (1 << 1);}
1670 // bit 2-3
1671 if (_RE == 1.0) {sourceflags |= (1 << 2);}
1672 else if (_RE == 2.0) {sourceflags |= (1 << 3);}
1673 else if (_RE == 3.0) {sourceflags |= (1 << 2); sourceflags |= (1 << 3);}
1674 out
1675 << QString(fmt)
1676 .arg(_sat_type , 19, 'e', 12)
1677 .arg(double(sourceflags), 19, 'e', 12)
1678 .arg(_ET, 19, 'e', 12)
1679 .arg(_EE, 19, 'e', 12);
1680 }
1681 // =====================
1682 // BROADCAST ORBIT - 5
1683 // =====================
1684 if (type() == t_eph::L1OC ||
1685 type() == t_eph::L3OC) {
1686 out
1687 << QString(fmt)
1688 .arg(_attitude_P2, 19, 'e', 12)
1689 .arg(_Tin, 19, 'e', 12)
1690 .arg(_tau1, 19, 'e', 12)
1691 .arg(_tau2, 19, 'e', 12);
1692 }
1693 // =====================
1694 // BROADCAST ORBIT - 6
1695 // =====================
1696 if (type() == t_eph::L1OC ||
1697 type() == t_eph::L3OC) {
1698 out
1699 << QString(fmt)
1700 .arg(_yaw, 19, 'e', 12)
1701 .arg(_sn, 19, 'e', 12)
1702 .arg(_angular_rate, 19, 'e', 12)
1703 .arg(_angular_acc, 19, 'e', 12);
1704 }
1705 // =====================
1706 // BROADCAST ORBIT - 7
1707 // =====================
1708 if (type() == t_eph::L1OC ||
1709 type() == t_eph::L3OC) {
1710 out
1711 << QString(fmt)
1712 .arg(_angular_rate_max, 19, 'e', 12)
1713 .arg(_X_PC, 19, 'e', 12)
1714 .arg(_Y_PC, 19, 'e', 12)
1715 .arg(_Z_PC, 19, 'e', 12);
1716 }
1717 // =====================
1718 // BROADCAST ORBIT - 8
1719 // =====================
1720 if (type() == t_eph::L1OC ||
1721 type() == t_eph::L3OC) {
1722 out
1723 << QString(fmt)
1724 .arg(_M_FE, 19, 'e', 12)
1725 .arg(_M_FT, 19, 'e', 12)
1726 .arg("", 19, QChar(' '))
1727 .arg(_TOT, 19, 'e', 12);
1728 }
1729 }
1730 return rnxStr;
1731}
1732
1733// Derivative of the state vector using a simple force model (static)
1734////////////////////////////////////////////////////////////////////////////
1735ColumnVector t_ephGlo::glo_deriv(double /* tt */, const ColumnVector &xv,
1736 double *acc) {
1737
1738 // State vector components
1739 // -----------------------
1740 ColumnVector rr = xv.rows(1, 3);
1741 ColumnVector vv = xv.rows(4, 6);
1742
1743 // Acceleration
1744 // ------------
1745 static const double gmWGS = 398.60044e12;
1746 static const double AE = 6378136.0;
1747 static const double OMEGA = 7292115.e-11;
1748 static const double C20 = -1082.6257e-6;
1749
1750 double rho = rr.NormFrobenius();
1751 double t1 = -gmWGS / (rho * rho * rho);
1752 double t2 = 3.0 / 2.0 * C20 * (gmWGS * AE * AE)
1753 / (rho * rho * rho * rho * rho);
1754 double t3 = OMEGA * OMEGA;
1755 double t4 = 2.0 * OMEGA;
1756 double z2 = rr(3) * rr(3);
1757
1758 // Vector of derivatives
1759 // ---------------------
1760 ColumnVector va(6);
1761 va(1) = vv(1);
1762 va(2) = vv(2);
1763 va(3) = vv(3);
1764 va(4) = (t1 + t2 * (1.0 - 5.0 * z2 / (rho * rho)) + t3) * rr(1) + t4 * vv(2)
1765 + acc[0];
1766 va(5) = (t1 + t2 * (1.0 - 5.0 * z2 / (rho * rho)) + t3) * rr(2) - t4 * vv(1)
1767 + acc[1];
1768 va(6) = (t1 + t2 * (3.0 - 5.0 * z2 / (rho * rho))) * rr(3) + acc[2];
1769
1770 return va;
1771}
1772
1773// IOD of Glonass Ephemeris (virtual)
1774////////////////////////////////////////////////////////////////////////////
1775unsigned int t_ephGlo::IOD() const {
1776 bncTime tMoscow = _TOC - _gps_utc + 3 * 3600.0;
1777 return (unsigned long) tMoscow.daysec() / 900;
1778}
1779
1780// Health status of Glonass Ephemeris (virtual)
1781////////////////////////////////////////////////////////////////////////////
1782unsigned int t_ephGlo::isUnhealthy() const {
1783
1784 if (_almanac_health_availablility_indicator) {
1785 if ((_health == 0 && _almanac_health == 0)
1786 || (_health == 1 && _almanac_health == 0)
1787 || (_health == 1 && _almanac_health == 1)) {
1788 return 1;
1789 }
1790 } else if (!_almanac_health_availablility_indicator) {
1791 if (_health) {
1792 return 1;
1793 }
1794 }
1795 return 0; /* (_health == 0 && _almanac_health == 1) or (_health == 0) */
1796}
1797
1798// Constructor
1799//////////////////////////////////////////////////////////////////////////////
1800t_ephGal::t_ephGal(double rnxVersion, const QStringList &lines, const QString typeStr) {
1801
1802 setType(typeStr);
1803
1804 const int nLines = 8;
1805
1806 if (lines.size() != nLines) {
1807 _checkState = bad;
1808 return;
1809 }
1810
1811 // RINEX Format
1812 // ------------
1813 int fieldLen = 19;
1814 double SVhealth = 0.0;
1815 double datasource = 0.0;
1816
1817 int pos[4];
1818 pos[0] = (rnxVersion <= 2.12) ? 3 : 4;
1819 pos[1] = pos[0] + fieldLen;
1820 pos[2] = pos[1] + fieldLen;
1821 pos[3] = pos[2] + fieldLen;
1822
1823 // Read eight lines
1824 // ----------------
1825 for (int iLine = 0; iLine < nLines; iLine++) {
1826 QString line = lines[iLine];
1827
1828 if (iLine == 0) {
1829 QTextStream in(line.left(pos[1]).toLatin1());
1830
1831 int year, month, day, hour, min;
1832 double sec;
1833
1834 QString prnStr, n;
1835 in >> prnStr;
1836 if (prnStr.size() == 1 && prnStr[0] == 'E') {
1837 in >> n;
1838 prnStr.append(n);
1839 }
1840 if (prnStr.at(0) == 'E') {
1841 _prn.set('E', prnStr.mid(1).toInt());
1842 } else {
1843 _prn.set('E', prnStr.toInt());
1844 }
1845
1846 in >> year >> month >> day >> hour >> min >> sec;
1847 if (year < 80) {
1848 year += 2000;
1849 } else if (year < 100) {
1850 year += 1900;
1851 }
1852
1853 _TOC.set(year, month, day, hour, min, sec);
1854
1855 if ( readDbl(line, pos[1], fieldLen, _clock_bias)
1856 || readDbl(line, pos[2], fieldLen, _clock_drift)
1857 || readDbl(line, pos[3], fieldLen, _clock_driftrate)) {
1858 _checkState = bad;
1859 return;
1860 }
1861 }
1862 // =====================
1863 // BROADCAST ORBIT - 1
1864 // =====================
1865 else if (iLine == 1) {
1866 if ( readDbl(line, pos[0], fieldLen, _IODnav)
1867 || readDbl(line, pos[1], fieldLen, _Crs)
1868 || readDbl(line, pos[2], fieldLen, _Delta_n)
1869 || readDbl(line, pos[3], fieldLen, _M0)) {
1870 _checkState = bad;
1871 return;
1872 }
1873 }
1874 // =====================
1875 // BROADCAST ORBIT - 2
1876 // =====================
1877 else if (iLine == 2) {
1878 if ( readDbl(line, pos[0], fieldLen, _Cuc)
1879 || readDbl(line, pos[1], fieldLen, _e)
1880 || readDbl(line, pos[2], fieldLen, _Cus)
1881 || readDbl(line, pos[3], fieldLen, _sqrt_A)) {
1882 _checkState = bad;
1883 return;
1884 }
1885 }
1886 // =====================
1887 // BROADCAST ORBIT - 3
1888 // =====================
1889 else if (iLine == 3) {
1890 if ( readDbl(line, pos[0], fieldLen, _TOEsec)
1891 || readDbl(line, pos[1], fieldLen, _Cic)
1892 || readDbl(line, pos[2], fieldLen, _OMEGA0)
1893 || readDbl(line, pos[3], fieldLen, _Cis)) {
1894 _checkState = bad;
1895 return;
1896 }
1897 }
1898 // =====================
1899 // BROADCAST ORBIT - 4
1900 // =====================
1901 else if (iLine == 4) {
1902 if ( readDbl(line, pos[0], fieldLen, _i0)
1903 || readDbl(line, pos[1], fieldLen, _Crc)
1904 || readDbl(line, pos[2], fieldLen, _omega)
1905 || readDbl(line, pos[3], fieldLen, _OMEGADOT)) {
1906 _checkState = bad;
1907 return;
1908 }
1909 }
1910 // =====================
1911 // BROADCAST ORBIT - 5
1912 // =====================
1913 else if (iLine == 5) {
1914 if ( readDbl(line, pos[0], fieldLen, _IDOT)
1915 || readDbl(line, pos[1], fieldLen, datasource)
1916 || readDbl(line, pos[2], fieldLen, _TOEweek)) {
1917 _checkState = bad;
1918 return;
1919 }
1920 else {
1921 if (bitExtracted(unsigned(datasource), 1, 8)) {
1922 _fnav = true;
1923 _type = t_eph::FNAV;
1924 _inav = false;
1925 /* set unused I/NAV values */
1926 _E5b_HS = 0.0;
1927 _E1B_HS = 0.0;
1928 _E1B_DataInvalid = false;
1929 _E5b_DataInvalid = false;
1930 }
1931 if (bitExtracted(unsigned(datasource), 1, 9)) {
1932 _fnav = false;
1933 _inav = true;
1934 _type = t_eph::INAV;
1935 /* set unused F/NAV values */
1936 _E5a_HS = 0.0;
1937 _E5a_DataInvalid = false;
1938 }
1939 // GAL week # in RINEX is aligned/identical to continuous GPS week # used in RINEX
1940 // but GST week # started at the first GPS roll-over (continuous GPS week 1024)
1941 _TOEweek -= 1024.0;
1942 }
1943 }
1944 // =====================
1945 // BROADCAST ORBIT - 6
1946 // =====================
1947 else if (iLine == 6) {
1948 if ( readDbl(line, pos[0], fieldLen, _SISA)
1949 || readDbl(line, pos[1], fieldLen, SVhealth)
1950 || readDbl(line, pos[2], fieldLen, _BGD_1_5A)
1951 || readDbl(line, pos[3], fieldLen, _BGD_1_5B)) {
1952 _checkState = bad;
1953 return;
1954 } else {
1955 // Bit 0
1956 _E1B_DataInvalid = bitExtracted(unsigned(SVhealth), 1, 0);
1957 // Bit 1-2
1958 _E1B_HS = double(bitExtracted(unsigned(SVhealth), 2, 1));
1959 // Bit 3
1960 _E5a_DataInvalid = bitExtracted(unsigned(SVhealth), 1, 3);
1961 // Bit 4-5
1962 _E5a_HS = double(bitExtracted(unsigned(SVhealth), 2, 4));
1963 // Bit 6
1964 _E5b_DataInvalid = bitExtracted(unsigned(SVhealth), 1, 6);
1965 // Bit 7-8
1966 _E5b_HS = double(bitExtracted(unsigned(SVhealth), 2, 7));
1967 if (_fnav) {
1968 _BGD_1_5B = 0.0;
1969 }
1970 }
1971 }
1972 // =====================
1973 // BROADCAST ORBIT - 7
1974 // =====================
1975 else if (iLine == 7) {
1976 if (readDbl(line, pos[0], fieldLen, _TOT)) {
1977 _checkState = bad;
1978 return;
1979 }
1980 }
1981 }
1982 _prn.setFlag(type());
1983}
1984
1985// Compute Galileo Satellite Position (virtual)
1986////////////////////////////////////////////////////////////////////////////
1987t_irc t_ephGal::position(int GPSweek, double GPSweeks, double *xc,
1988 double *vv) const {
1989
1990 static const double omegaEarth = 7292115.1467e-11;
1991 static const double gmWGS = 398.6004418e12;
1992
1993 memset(xc, 0, 6 * sizeof(double));
1994 memset(vv, 0, 3 * sizeof(double));
1995
1996 double a0 = _sqrt_A * _sqrt_A;
1997 if (a0 == 0) {
1998 return failure;
1999 }
2000
2001 double n0 = sqrt(gmWGS / (a0 * a0 * a0));
2002
2003 bncTime tt(GPSweek, GPSweeks);
2004 double tk = tt - bncTime(_TOC.gpsw(), _TOEsec);
2005
2006 double n = n0 + _Delta_n;
2007 double M = _M0 + n * tk;
2008 double E = M;
2009 double E_last;
2010 int nLoop = 0;
2011 do {
2012 E_last = E;
2013 E = M + _e * sin(E);
2014
2015 if (++nLoop == 100) {
2016 return failure;
2017 }
2018 } while (fabs(E - E_last) * a0 > 0.001);
2019 double v = 2.0 * atan(sqrt((1.0 + _e) / (1.0 - _e)) * tan(E / 2));
2020 double u0 = v + _omega;
2021 double sin2u0 = sin(2 * u0);
2022 double cos2u0 = cos(2 * u0);
2023 double r = a0 * (1 - _e * cos(E)) + _Crc * cos2u0 + _Crs * sin2u0;
2024 double i = _i0 + _IDOT * tk + _Cic * cos2u0 + _Cis * sin2u0;
2025 double u = u0 + _Cuc * cos2u0 + _Cus * sin2u0;
2026 double xp = r * cos(u);
2027 double yp = r * sin(u);
2028 double OM = _OMEGA0 + (_OMEGADOT - omegaEarth) * tk - omegaEarth * _TOEsec;
2029
2030 double sinom = sin(OM);
2031 double cosom = cos(OM);
2032 double sini = sin(i);
2033 double cosi = cos(i);
2034 xc[0] = xp * cosom - yp * cosi * sinom;
2035 xc[1] = xp * sinom + yp * cosi * cosom;
2036 xc[2] = yp * sini;
2037
2038 double tc = tt - _TOC;
2039 xc[3] = _clock_bias + _clock_drift * tc + _clock_driftrate * tc * tc;
2040
2041 // Velocity
2042 // --------
2043 double tanv2 = tan(v / 2);
2044 double dEdM = 1 / (1 - _e * cos(E));
2045 double dotv = sqrt((1.0 + _e) / (1.0 - _e)) / cos(E / 2) / cos(E / 2)
2046 / (1 + tanv2 * tanv2) * dEdM * n;
2047 double dotu = dotv + (-_Cuc * sin2u0 + _Cus * cos2u0) * 2 * dotv;
2048 double dotom = _OMEGADOT - omegaEarth;
2049 double doti = _IDOT + (-_Cic * sin2u0 + _Cis * cos2u0) * 2 * dotv;
2050 double dotr = a0 * _e * sin(E) * dEdM * n
2051 + (-_Crc * sin2u0 + _Crs * cos2u0) * 2 * dotv;
2052 double dotx = dotr * cos(u) - r * sin(u) * dotu;
2053 double doty = dotr * sin(u) + r * cos(u) * dotu;
2054
2055 vv[0] = cosom * dotx - cosi * sinom * doty // dX / dr
2056 - xp * sinom * dotom - yp * cosi * cosom * dotom // dX / dOMEGA
2057 + yp * sini * sinom * doti; // dX / di
2058
2059 vv[1] = sinom * dotx + cosi * cosom * doty + xp * cosom * dotom
2060 - yp * cosi * sinom * dotom - yp * sini * cosom * doti;
2061
2062 vv[2] = sini * doty + yp * cosi * doti;
2063
2064 // Relativistic Correction
2065 // -----------------------
2066 // See t_ephGPS::position() for the rationale behind selecting between the
2067 // eccentricity-based and velocity-based forms.
2068 if (_orbCorr && _orbCorr->_rtcmSsr) {
2069 xc[3] -= 2.0 * (xc[0]*vv[0] + xc[1]*vv[1] + xc[2]*vv[2]) / t_CST::c / t_CST::c;
2070 }
2071 else {
2072 xc[3] -= 4.442807309e-10 * _e * sqrt(a0) * sin(E);
2073 }
2074
2075 xc[4] = _clock_drift + _clock_driftrate * tc;
2076 xc[5] = _clock_driftrate;
2077
2078 return success;
2079}
2080
2081// Health status of Galileo Ephemeris (virtual)
2082////////////////////////////////////////////////////////////////////////////
2083unsigned int t_ephGal::isUnhealthy() const {
2084 // SHS; 1 = Out of Service, 3 = In Test, 0 = Signal Ok, 2 = Extended Operations Mode
2085 if (_E5a_HS == 1 || _E5a_HS == 3 ||
2086 _E5b_HS == 1 || _E5b_HS == 3 ||
2087 _E1B_HS == 1 || _E1B_HS == 3) {
2088 return 1;
2089 }
2090 if (_E5a_DataInvalid ||
2091 _E5b_DataInvalid ||
2092 _E1B_DataInvalid) {
2093 return 1;
2094 }
2095 if (_SISA == 255.0) { // NAPA: No Accuracy Prediction Available
2096 return 1;
2097 }
2098 /*
2099 * SDD v1.3: SHS=2 leads to a newly-defined "EOM" status.
2100 * It also means that the satellite signal may be used for PNT.
2101 if (_E5aHS == 2 ||
2102 _E5bHS == 2 ||
2103 _E1_bHS == 2 ) {
2104 return 1;
2105 }
2106 */
2107 return 0;
2108
2109}
2110
2111// RINEX Format String
2112//////////////////////////////////////////////////////////////////////////////
2113QString t_ephGal::toString(double version) const {
2114
2115 QString ephStr = typeStr(_type, _prn, version);
2116 QString rnxStr = ephStr + rinexDateStr(_TOC, _prn, version);
2117
2118 QTextStream out(&rnxStr);
2119
2120 out
2121 << QString("%1%2%3\n").arg(_clock_bias, 19, 'e', 12).arg(_clock_drift, 19,
2122 'e', 12).arg(_clock_driftrate, 19, 'e', 12);
2123
2124 QString fmt = version < 3.0 ? " %1%2%3%4\n" : " %1%2%3%4\n";
2125 // =====================
2126 // BROADCAST ORBIT - 1
2127 // =====================
2128 out
2129 << QString(fmt)
2130 .arg(_IODnav, 19, 'e', 12)
2131 .arg(_Crs, 19, 'e', 12)
2132 .arg(_Delta_n, 19, 'e', 12)
2133 .arg(_M0, 19, 'e', 12);
2134 // =====================
2135 // BROADCAST ORBIT - 2
2136 // =====================
2137 out
2138 << QString(fmt)
2139 .arg(_Cuc, 19, 'e', 12).
2140 arg(_e, 19, 'e', 12)
2141 .arg(_Cus, 19, 'e', 12)
2142 .arg(_sqrt_A, 19, 'e', 12);
2143 // =====================
2144 // BROADCAST ORBIT - 3
2145 // =====================
2146 out
2147 << QString(fmt).
2148 arg(_TOEsec, 19, 'e', 12)
2149 .arg(_Cic, 19, 'e', 12)
2150 .arg(_OMEGA0, 19, 'e', 12)
2151 .arg(_Cis, 19, 'e', 12);
2152 // =====================
2153 // BROADCAST ORBIT - 4
2154 // =====================
2155 out
2156 << QString(fmt)
2157 .arg(_i0, 19, 'e', 12)
2158 .arg(_Crc, 19, 'e', 12)
2159 .arg(_omega, 19, 'e', 12)
2160 .arg(_OMEGADOT, 19, 'e', 12);
2161 // =====================
2162 // BROADCAST ORBIT - 5/6
2163 // =====================
2164 int dataSource = 0;
2165 int SVhealth = 0;
2166 double BGD_1_5A = _BGD_1_5A;
2167 double BGD_1_5B = _BGD_1_5B;
2168 if (_fnav) {
2169 dataSource |= (1 << 1);
2170 dataSource |= (1 << 8);
2171 BGD_1_5B = 0.0;
2172 // SVhealth
2173 // Bit 3 : E5a DVS
2174 if (_E5a_DataInvalid) {
2175 SVhealth |= (1 << 3);
2176 }
2177 // Bit 4-5: E5a HS
2178 if (_E5a_HS == 1.0) {
2179 SVhealth |= (1 << 4);
2180 }
2181 else if (_E5a_HS == 2.0) {
2182 SVhealth |= (1 << 5);
2183 }
2184 else if (_E5a_HS == 3.0) {
2185 SVhealth |= (1 << 4);
2186 SVhealth |= (1 << 5);
2187 }
2188 } else if (_inav) {
2189 // Bit 2 and 0 are set because from MT1046 the data source cannot be determined
2190 // and RNXv3.03 says both can be set if the navigation messages were merged
2191 dataSource |= (1 << 0);
2192 dataSource |= (1 << 2);
2193 dataSource |= (1 << 9);
2194 // SVhealth
2195 // Bit 0 : E1-B DVS
2196 if (_E1B_DataInvalid) {
2197 SVhealth |= (1 << 0);
2198 }
2199 // Bit 1-2: E1-B HS
2200 if (_E1B_HS == 1.0) {
2201 SVhealth |= (1 << 1);
2202 }
2203 else if (_E1B_HS == 2.0) {
2204 SVhealth |= (1 << 2);
2205 }
2206 else if (_E1B_HS == 3.0) {
2207 SVhealth |= (1 << 1);
2208 SVhealth |= (1 << 2);
2209 }
2210 // Bit 6 : E5b DVS
2211 if (_E5b_DataInvalid) {
2212 SVhealth |= (1 << 6);
2213 }
2214 // Bit 7-8: E5b HS
2215 if (_E5b_HS == 1.0) {
2216 SVhealth |= (1 << 7);
2217 }
2218 else if (_E5b_HS == 2.0) {
2219 SVhealth |= (1 << 8);
2220 }
2221 else if (_E5b_HS == 3.0) {
2222 SVhealth |= (1 << 7);
2223 SVhealth |= (1 << 8);
2224 }
2225 }
2226 // =====================
2227 // BROADCAST ORBIT - 5
2228 // =====================
2229 out
2230 << QString(fmt)
2231 .arg(_IDOT, 19, 'e', 12)
2232 .arg(double(dataSource), 19, 'e', 12)
2233 .arg(_TOEweek + 1024.0, 19, 'e', 12)
2234 .arg(0.0, 19, 'e', 12);
2235 // =====================
2236 // BROADCAST ORBIT - 6
2237 // =====================
2238 out
2239 << QString(fmt)
2240 .arg(_SISA, 19, 'e', 12)
2241 .arg(double(SVhealth), 19, 'e', 12)
2242 .arg(BGD_1_5A, 19, 'e', 12)
2243 .arg(BGD_1_5B, 19, 'e', 12);
2244 // =====================
2245 // BROADCAST ORBIT - 7
2246 // =====================
2247 double tot = _TOT;
2248 if (tot == 0.9999e9 && version < 3.0) {
2249 tot = 0.0;
2250 }
2251 out
2252 << QString(fmt)
2253 .arg(tot, 19, 'e', 12)
2254 .arg("", 19, QChar(' '))
2255 .arg("", 19, QChar(' '))
2256 .arg("", 19, QChar(' '));
2257
2258 return rnxStr;
2259}
2260
2261// Constructor
2262//////////////////////////////////////////////////////////////////////////////
2263t_ephSBAS::t_ephSBAS(double rnxVersion, const QStringList &lines, const QString typeStr) {
2264
2265 setType(typeStr);
2266
2267 const int nLines = 4;
2268
2269 // Source RINEX version < 4
2270 if (type() == t_eph::undefined) {
2271 _type = t_eph::SBASL1;
2272 }
2273
2274 if (lines.size() != nLines) {
2275 _checkState = bad;
2276 return;
2277 }
2278
2279 // RINEX Format
2280 // ------------
2281 int fieldLen = 19;
2282
2283 int pos[4];
2284 pos[0] = (rnxVersion <= 2.12) ? 3 : 4;
2285 pos[1] = pos[0] + fieldLen;
2286 pos[2] = pos[1] + fieldLen;
2287 pos[3] = pos[2] + fieldLen;
2288
2289 // Read four lines
2290 // ---------------
2291 for (int iLine = 0; iLine < nLines; iLine++) {
2292 QString line = lines[iLine];
2293
2294 if (iLine == 0) {
2295 QTextStream in(line.left(pos[1]).toLatin1());
2296
2297 int year, month, day, hour, min;
2298 double sec;
2299
2300 QString prnStr, n;
2301 in >> prnStr;
2302 if (prnStr.size() == 1 && prnStr[0] == 'S') {
2303 in >> n;
2304 prnStr.append(n);
2305 }
2306 if (prnStr.at(0) == 'S') {
2307 _prn.set('S', prnStr.mid(1).toInt());
2308 } else {
2309 _prn.set('S', prnStr.toInt());
2310 }
2311 _prn.setFlag(type());
2312
2313 in >> year >> month >> day >> hour >> min >> sec;
2314
2315 if (year < 80) {
2316 year += 2000;
2317 } else if (year < 100) {
2318 year += 1900;
2319 }
2320
2321 _TOC.set(year, month, day, hour, min, sec);
2322
2323 if ( readDbl(line, pos[1], fieldLen, _agf0)
2324 || readDbl(line, pos[2], fieldLen, _agf1)
2325 || readDbl(line, pos[3], fieldLen, _TOT)) {
2326 _checkState = bad;
2327 return;
2328 }
2329 }
2330 // =====================
2331 // BROADCAST ORBIT - 1
2332 // =====================
2333 else if (iLine == 1) {
2334 if ( readDbl(line, pos[0], fieldLen, _x_pos)
2335 || readDbl(line, pos[1], fieldLen, _x_vel)
2336 || readDbl(line, pos[2], fieldLen, _x_acc)
2337 || readDbl(line, pos[3], fieldLen, _health)) {
2338 _checkState = bad;
2339 return;
2340 }
2341 }
2342 // =====================
2343 // BROADCAST ORBIT - 2
2344 // =====================
2345 else if (iLine == 2) {
2346 if ( readDbl(line, pos[0], fieldLen, _y_pos)
2347 || readDbl(line, pos[1], fieldLen, _y_vel)
2348 || readDbl(line, pos[2], fieldLen, _y_acc)
2349 || readDbl(line, pos[3], fieldLen, _ura)) {
2350 _checkState = bad;
2351 return;
2352 }
2353 }
2354 // =====================
2355 // BROADCAST ORBIT - 3
2356 // =====================
2357 else if (iLine == 3) {
2358 double iodn;
2359 if ( readDbl(line, pos[0], fieldLen, _z_pos)
2360 || readDbl(line, pos[1], fieldLen, _z_vel)
2361 || readDbl(line, pos[2], fieldLen, _z_acc)
2362 || readDbl(line, pos[3], fieldLen, iodn)) {
2363 _checkState = bad;
2364 return;
2365 } else {
2366 _IODN = int(iodn);
2367 }
2368 }
2369 }
2370 _x_pos *= 1.e3;
2371 _y_pos *= 1.e3;
2372 _z_pos *= 1.e3;
2373 _x_vel *= 1.e3;
2374 _y_vel *= 1.e3;
2375 _z_vel *= 1.e3;
2376 _x_acc *= 1.e3;
2377 _y_acc *= 1.e3;
2378 _z_acc *= 1.e3;
2379}
2380
2381// IOD of SBAS Ephemeris (virtual)
2382////////////////////////////////////////////////////////////////////////////
2383
2384unsigned int t_ephSBAS::IOD() const {
2385 unsigned char buffer[80];
2386 int size = 0;
2387 int numbits = 0;
2388 long long bitbuffer = 0;
2389 unsigned char *startbuffer = buffer;
2390
2391 SBASADDBITSFLOAT(30, this->_x_pos, 0.08)
2392 SBASADDBITSFLOAT(30, this->_y_pos, 0.08)
2393 SBASADDBITSFLOAT(25, this->_z_pos, 0.4)
2394 SBASADDBITSFLOAT(17, this->_x_vel, 0.000625)
2395 SBASADDBITSFLOAT(17, this->_y_vel, 0.000625)
2396 SBASADDBITSFLOAT(18, this->_z_vel, 0.004)
2397 SBASADDBITSFLOAT(10, this->_x_acc, 0.0000125)
2398 SBASADDBITSFLOAT(10, this->_y_acc, 0.0000125)
2399 SBASADDBITSFLOAT(10, this->_z_acc, 0.0000625)
2400 SBASADDBITSFLOAT(12, this->_agf0,
2401 1.0 / static_cast<double>(1 << 30) / static_cast<double>(1 << 1))
2402 SBASADDBITSFLOAT(8, this->_agf1,
2403 1.0 / static_cast<double>(1 << 30) / static_cast<double>(1 << 10))
2404 SBASADDBITS(5, 0); // the last byte is filled by 0-bits to obtain a length of an integer multiple of 8
2405
2406 return CRC24(size, startbuffer);
2407}
2408
2409// Compute SBAS Satellite Position (virtual)
2410////////////////////////////////////////////////////////////////////////////
2411t_irc t_ephSBAS::position(int GPSweek, double GPSweeks, double *xc,
2412 double *vv) const {
2413
2414 bncTime tt(GPSweek, GPSweeks);
2415 double dt = tt - _TOC;
2416
2417 xc[0] = _x_pos + _x_vel * dt + _x_acc * dt * dt / 2.0;
2418 xc[1] = _y_pos + _y_vel * dt + _y_acc * dt * dt / 2.0;
2419 xc[2] = _z_pos + _z_vel * dt + _z_acc * dt * dt / 2.0;
2420
2421 vv[0] = _x_vel + _x_acc * dt;
2422 vv[1] = _y_vel + _y_acc * dt;
2423 vv[2] = _z_vel + _z_acc * dt;
2424
2425 xc[3] = _agf0 + _agf1 * dt;
2426
2427 xc[4] = _agf1;
2428 xc[5] = 0.0;
2429
2430 return success;
2431}
2432
2433// Health status of SBAS Ephemeris (virtual)
2434////////////////////////////////////////////////////////////////////////////
2435unsigned int t_ephSBAS::isUnhealthy() const {
2436
2437// Bit 5
2438 bool URAindexIs15 = bitExtracted(unsigned(_health), 1, 5);
2439 if (URAindexIs15) {
2440 // in this case it is recommended
2441 // to set the bits 0,1,2,3 to 1 (MT17health = 15)
2442 return 1;
2443 }
2444
2445 // Bit 4
2446 bool MT17HealthIsUnavailable = bitExtracted(unsigned(_health), 1, 4);
2447 if (MT17HealthIsUnavailable) {
2448 return 0;
2449 }
2450
2451 // Bit 0-3
2452 int MT17health = bitExtracted(unsigned(_health), 4, 0);
2453 if (MT17health) {
2454 return 1;
2455 }
2456
2457 return 0;
2458}
2459
2460// RINEX Format String
2461//////////////////////////////////////////////////////////////////////////////
2462QString t_ephSBAS::toString(double version) const {
2463
2464 QString ephStr = typeStr(_type, _prn, version);
2465 QString rnxStr = ephStr + rinexDateStr(_TOC, _prn, version);
2466
2467 QTextStream out(&rnxStr);
2468
2469 out
2470 << QString("%1%2%3\n")
2471 .arg(_agf0, 19, 'e', 12)
2472 .arg(_agf1, 19, 'e', 12)
2473 .arg(_TOT, 19, 'e', 12);
2474
2475 QString fmt = version < 3.0 ? " %1%2%3%4\n" : " %1%2%3%4\n";
2476 // =====================
2477 // BROADCAST ORBIT - 1
2478 // =====================
2479 out
2480 << QString(fmt)
2481 .arg(1.e-3 * _x_pos, 19, 'e', 12)
2482 .arg(1.e-3 * _x_vel, 19, 'e', 12)
2483 .arg(1.e-3 * _x_acc, 19, 'e', 12)
2484 .arg(_health, 19, 'e', 12);
2485 // =====================
2486 // BROADCAST ORBIT - 2
2487 // =====================
2488 out
2489 << QString(fmt)
2490 .arg(1.e-3 * _y_pos, 19, 'e', 12)
2491 .arg(1.e-3 * _y_vel, 19, 'e', 12)
2492 .arg(1.e-3 * _y_acc, 19, 'e', 12)
2493 .arg(_ura, 19, 'e', 12);
2494 // =====================
2495 // BROADCAST ORBIT - 3
2496 // =====================
2497 out
2498 << QString(fmt)
2499 .arg(1.e-3 * _z_pos, 19, 'e', 12)
2500 .arg(1.e-3 * _z_vel, 19, 'e', 12)
2501 .arg(1.e-3 * _z_acc, 19, 'e', 12)
2502 .arg(double(_IODN), 19, 'e', 12);
2503
2504 return rnxStr;
2505}
2506
2507// Constructor
2508//////////////////////////////////////////////////////////////////////////////
2509t_ephBDS::t_ephBDS(double rnxVersion, const QStringList &lines, const QString typeStr) {
2510
2511 setType(typeStr);
2512
2513 int nLines = 8;
2514
2515 if (type() == t_eph::CNV1 ||
2516 type() == t_eph::CNV2) {
2517 nLines += 2;
2518 }
2519 if (type() == t_eph::CNV3) {
2520 nLines += 1;
2521 }
2522
2523 if (lines.size() != nLines) {
2524 _checkState = bad;
2525 return;
2526 }
2527
2528 // RINEX Format
2529 // ------------
2530 int fieldLen = 19;
2531
2532 int pos[4];
2533 pos[0] = (rnxVersion <= 2.12) ? 3 : 4;
2534 pos[1] = pos[0] + fieldLen;
2535 pos[2] = pos[1] + fieldLen;
2536 pos[3] = pos[2] + fieldLen;
2537
2538 // Read eight lines
2539 // ----------------
2540 for (int iLine = 0; iLine < nLines; iLine++) {
2541 QString line = lines[iLine];
2542
2543 if (iLine == 0) {
2544 QTextStream in(line.left(pos[1]).toLatin1());
2545
2546 int year, month, day, hour, min;
2547 double sec;
2548
2549 QString prnStr, n;
2550 in >> prnStr;
2551 if (prnStr.size() == 1 && prnStr[0] == 'C') {
2552 in >> n;
2553 prnStr.append(n);
2554 }
2555 if (prnStr.at(0) == 'C') {
2556 _prn.set('C', prnStr.mid(1).toInt());
2557 } else {
2558 _prn.set('C', prnStr.toInt());
2559 }
2560
2561 in >> year >> month >> day >> hour >> min >> sec;
2562 if (year < 80) {
2563 year += 2000;
2564 } else if (year < 100) {
2565 year += 1900;
2566 }
2567
2568 _TOC.setBDS(year, month, day, hour, min, sec);
2569
2570 if ( readDbl(line, pos[1], fieldLen, _clock_bias)
2571 || readDbl(line, pos[2], fieldLen, _clock_drift)
2572 || readDbl(line, pos[3], fieldLen, _clock_driftrate)) {
2573 _checkState = bad;
2574 return;
2575 }
2576 }
2577 // =====================
2578 // BROADCAST ORBIT - 1
2579 // =====================
2580 else if (iLine == 1) {
2581 if (type() == t_eph::D1 ||
2582 type() == t_eph::D2 ||
2583 type() == t_eph::undefined) {
2584 double aode;
2585 if ( readDbl(line, pos[0], fieldLen, aode)
2586 || readDbl(line, pos[1], fieldLen, _Crs)
2587 || readDbl(line, pos[2], fieldLen, _Delta_n)
2588 || readDbl(line, pos[3], fieldLen, _M0)) {
2589 _checkState = bad;
2590 return;
2591 }
2592 _AODE = int(aode);
2593 }
2594 else { //CNV1, CNV2, CNV3
2595 if ( readDbl(line, pos[0], fieldLen, _ADOT)
2596 || readDbl(line, pos[1], fieldLen, _Crs)
2597 || readDbl(line, pos[2], fieldLen, _Delta_n)
2598 || readDbl(line, pos[3], fieldLen, _M0)) {
2599 _checkState = bad;
2600 return;
2601 }
2602 }
2603 }
2604 // =====================
2605 // BROADCAST ORBIT - 2
2606 // =====================
2607 else if (iLine == 2) {
2608 if ( readDbl(line, pos[0], fieldLen, _Cuc)
2609 || readDbl(line, pos[1], fieldLen, _e)
2610 || readDbl(line, pos[2], fieldLen, _Cus)
2611 || readDbl(line, pos[3], fieldLen, _sqrt_A)) {
2612 _checkState = bad;
2613 return;
2614 }
2615 }
2616 // =====================
2617 // BROADCAST ORBIT - 3
2618 // =====================
2619 else if (iLine == 3) {
2620 if ( readDbl(line, pos[0], fieldLen, _TOEsec)
2621 || readDbl(line, pos[1], fieldLen, _Cic)
2622 || readDbl(line, pos[2], fieldLen, _OMEGA0)
2623 || readDbl(line, pos[3], fieldLen, _Cis)) {
2624 _checkState = bad;
2625 return;
2626 }
2627 }
2628 // =====================
2629 // BROADCAST ORBIT - 4
2630 // =====================
2631 else if (iLine == 4) {
2632 if ( readDbl(line, pos[0], fieldLen, _i0)
2633 || readDbl(line, pos[1], fieldLen, _Crc)
2634 || readDbl(line, pos[2], fieldLen, _omega)
2635 || readDbl(line, pos[3], fieldLen, _OMEGADOT)) {
2636 _checkState = bad;
2637 return;
2638 }
2639 else {
2640 // Source RINEX version < 4
2641 if (type() == t_eph::undefined) {
2642 const double iMaxGEO = 10.0 / 180.0 * M_PI;
2643 if (_i0 > iMaxGEO) {
2644 _type = t_eph::D1;
2645 }
2646 else {
2647 _type = t_eph::D2;
2648 }
2649 }
2650 }
2651 }
2652 // =====================
2653 // BROADCAST ORBIT - 5
2654 // =====================
2655 else if (iLine == 5) {
2656 if (type() == t_eph::CNV1 ||
2657 type() == t_eph::CNV2 ||
2658 type() == t_eph::CNV3) {
2659 if ( readDbl(line, pos[0], fieldLen, _IDOT)
2660 || readDbl(line, pos[1], fieldLen, _Delta_n_dot)
2661 || readDbl(line, pos[2], fieldLen, _satType)
2662 || readDbl(line, pos[3], fieldLen, _top)) {
2663 _checkState = bad;
2664 return;
2665 }
2666 }
2667 else { // D1, D2
2668 if ( readDbl(line, pos[0], fieldLen, _IDOT)
2669 || readDbl(line, pos[2], fieldLen, _BDTweek)) {
2670 _checkState = bad;
2671 return;
2672 }
2673 }
2674 }
2675 // =====================
2676 // BROADCAST ORBIT - 6
2677 // =====================
2678 else if (iLine == 6) {
2679 if (type() == t_eph::CNV1 ||
2680 type() == t_eph::CNV2 ||
2681 type() == t_eph::CNV3) {
2682 if ( readDbl(line, pos[0], fieldLen, _SISAI_oe)
2683 || readDbl(line, pos[1], fieldLen, _SISAI_ocb)
2684 || readDbl(line, pos[2], fieldLen, _SISAI_oc1)
2685 || readDbl(line, pos[3], fieldLen, _SISAI_oc2)) {
2686 _checkState = bad;
2687 return;
2688 }
2689 }
2690 else { // D1, D2
2691 double SatH1;
2692 if ( readDbl(line, pos[0], fieldLen, _ura)
2693 || readDbl(line, pos[1], fieldLen, SatH1)
2694 || readDbl(line, pos[2], fieldLen, _TGD1)
2695 || readDbl(line, pos[3], fieldLen, _TGD2)) {
2696 _checkState = bad;
2697 return;
2698 }
2699 _SatH1 = int(SatH1);
2700 }
2701 }
2702 // =====================
2703 // BROADCAST ORBIT - 7
2704 // =====================
2705 else if (iLine == 7) {
2706 if (type() == t_eph::CNV1) {
2707 if ( readDbl(line, pos[0], fieldLen, _ISC_B1Cd)
2708 || readDbl(line, pos[2], fieldLen, _TGD_B1Cp)
2709 || readDbl(line, pos[3], fieldLen, _TGD_B2ap)) {
2710 _checkState = bad;
2711 return;
2712 }
2713 }
2714 else if (type() == t_eph::CNV2) {
2715 if ( readDbl(line, pos[1], fieldLen, _ISC_B2ad)
2716 || readDbl(line, pos[2], fieldLen, _TGD_B1Cp)
2717 || readDbl(line, pos[3], fieldLen, _TGD_B2ap)) {
2718 _checkState = bad;
2719 return;
2720 }
2721 }
2722 else if (type() == t_eph::CNV3) {
2723 double health;
2724 if ( readDbl(line, pos[0], fieldLen, _SISMAI)
2725 || readDbl(line, pos[1], fieldLen, health)
2726 || readDbl(line, pos[2], fieldLen, _INTEGRITYF_B2b)
2727 || readDbl(line, pos[3], fieldLen, _TGD_B2bI)) {
2728 _checkState = bad;
2729 return;
2730 }
2731 _health = int(health);
2732 }
2733 else { // D1, D2
2734 double aodc;
2735 if ( readDbl(line, pos[0], fieldLen, _TOT)
2736 || readDbl(line, pos[1], fieldLen, aodc)) {
2737 _checkState = bad;
2738 return;
2739 }
2740 if (_TOT == 0.9999e9) { // 0.9999e9 means not known (RINEX standard)
2741 _TOT = _TOEsec;
2742 }
2743 _AODC = int(aodc);
2744 }
2745 }
2746 // =====================
2747 // BROADCAST ORBIT - 8
2748 // =====================
2749 else if (iLine == 8) {
2750 double health;
2751 if (type() == t_eph::CNV1) {
2752 if ( readDbl(line, pos[0], fieldLen, _SISMAI)
2753 || readDbl(line, pos[1], fieldLen, health)
2754 || readDbl(line, pos[2], fieldLen, _INTEGRITYF_B1C)
2755 || readDbl(line, pos[3], fieldLen, _IODC)) {
2756 _checkState = bad;
2757 return;
2758 }
2759 _health = int(health);
2760 }
2761 else if (type() == t_eph::CNV2) {
2762 if ( readDbl(line, pos[0], fieldLen, _SISMAI)
2763 || readDbl(line, pos[1], fieldLen, health)
2764 || readDbl(line, pos[2], fieldLen, _INTEGRITYF_B2aB1C)
2765 || readDbl(line, pos[3], fieldLen, _IODC)) {
2766 _checkState = bad;
2767 return;
2768 }
2769 _health = int(health);
2770 }
2771 else if (type() == t_eph::CNV3) {
2772 if (readDbl(line, pos[0], fieldLen, _TOT)) {
2773 _checkState = bad;
2774 return;
2775 }
2776 }
2777 }
2778 // =====================
2779 // BROADCAST ORBIT - 9
2780 // =====================
2781 else if (iLine == 9) {
2782 if (type() == t_eph::CNV1 ||
2783 type() == t_eph::CNV2) {
2784 if ( readDbl(line, pos[0], fieldLen, _TOT)
2785 || readDbl(line, pos[3], fieldLen, _IODE)) {
2786 _checkState = bad;
2787 return;
2788 }
2789 }
2790 }
2791 }
2792 _prn.setFlag(type());
2793
2794 _TOE.setBDS(int(_BDTweek), _TOEsec);
2795 // remark: actually should be computed from second_tot
2796 // but it seems to be unreliable in RINEX files
2797 //_TOT = _TOC.bdssec();
2798}
2799
2800// IOD of BDS Ephemeris (virtual)
2801////////////////////////////////////////////////////////////////////////////
2802unsigned int t_ephBDS::IOD() const {
2803 if (type() == t_eph::D1 ||
2804 type() == t_eph::D2) {
2805 return (int(_TOC.gpssec()) / 720) % 240;
2806 }
2807 else {
2808 return _IODE;
2809 }
2810}
2811
2812// Compute BDS Satellite Position (virtual)
2813//////////////////////////////////////////////////////////////////////////////
2814t_irc t_ephBDS::position(int GPSweek, double GPSweeks, double *xc,
2815 double *vv) const {
2816
2817 static const double gmBDS = 398.6004418e12;
2818 static const double omegaBDS = 7292115.0000e-11;
2819
2820 xc[0] = xc[1] = xc[2] = xc[3] = 0.0;
2821 vv[0] = vv[1] = vv[2] = 0.0;
2822
2823 bncTime tt(GPSweek, GPSweeks);
2824
2825 if (_sqrt_A == 0) {
2826 return failure;
2827 }
2828 double a0 = _sqrt_A * _sqrt_A;
2829
2830 double n0 = sqrt(gmBDS / (a0 * a0 * a0));
2831 double tk = tt - _TOE;
2832 double n = n0 + _Delta_n;
2833 double M = _M0 + n * tk;
2834 double E = M;
2835 double E_last;
2836 int nLoop = 0;
2837 do {
2838 E_last = E;
2839 E = M + _e * sin(E);
2840
2841 if (++nLoop == 100) {
2842 return failure;
2843 }
2844 } while (fabs(E - E_last) * a0 > 0.001);
2845
2846 double v = atan2(sqrt(1 - _e * _e) * sin(E), cos(E) - _e);
2847 double u0 = v + _omega;
2848 double sin2u0 = sin(2 * u0);
2849 double cos2u0 = cos(2 * u0);
2850 double r = a0 * (1 - _e * cos(E)) + _Crc * cos2u0 + _Crs * sin2u0;
2851 double i = _i0 + _IDOT * tk + _Cic * cos2u0 + _Cis * sin2u0;
2852 double u = u0 + _Cuc * cos2u0 + _Cus * sin2u0;
2853 double xp = r * cos(u);
2854 double yp = r * sin(u);
2855 double toesec = (_TOE.gpssec() - 14.0);
2856 double sinom = 0;
2857 double cosom = 0;
2858 double sini = 0;
2859 double cosi = 0;
2860
2861 // Velocity
2862 // --------
2863 double tanv2 = tan(v / 2);
2864 double dEdM = 1 / (1 - _e * cos(E));
2865 double dotv = sqrt((1.0 + _e) / (1.0 - _e)) / cos(E / 2) / cos(E / 2)
2866 / (1 + tanv2 * tanv2) * dEdM * n;
2867 double dotu = dotv + (-_Cuc * sin2u0 + _Cus * cos2u0) * 2 * dotv;
2868 double doti = _IDOT + (-_Cic * sin2u0 + _Cis * cos2u0) * 2 * dotv;
2869 double dotr = a0 * _e * sin(E) * dEdM * n
2870 + (-_Crc * sin2u0 + _Crs * cos2u0) * 2 * dotv;
2871
2872 double dotx = dotr * cos(u) - r * sin(u) * dotu;
2873 double doty = dotr * sin(u) + r * cos(u) * dotu;
2874
2875 const double iMaxGEO = 10.0 / 180.0 * M_PI;
2876
2877 // MEO/IGSO satellite
2878 // ------------------
2879 if (_i0 > iMaxGEO) {
2880 double OM = _OMEGA0 + (_OMEGADOT - omegaBDS) * tk - omegaBDS * toesec;
2881
2882 sinom = sin(OM);
2883 cosom = cos(OM);
2884 sini = sin(i);
2885 cosi = cos(i);
2886
2887 xc[0] = xp * cosom - yp * cosi * sinom;
2888 xc[1] = xp * sinom + yp * cosi * cosom;
2889 xc[2] = yp * sini;
2890
2891 // Velocity
2892 // --------
2893
2894 double dotom = _OMEGADOT - t_CST::omega;
2895
2896 vv[0] = cosom * dotx - cosi * sinom * doty // dX / dr
2897 - xp * sinom * dotom - yp * cosi * cosom * dotom // dX / dOMEGA
2898 + yp * sini * sinom * doti; // dX / di
2899
2900 vv[1] = sinom * dotx + cosi * cosom * doty + xp * cosom * dotom
2901 - yp * cosi * sinom * dotom - yp * sini * cosom * doti;
2902
2903 vv[2] = sini * doty + yp * cosi * doti;
2904
2905 }
2906
2907 // GEO satellite
2908 // -------------
2909 else {
2910 double OM = _OMEGA0 + _OMEGADOT * tk - omegaBDS * toesec;
2911 double ll = omegaBDS * tk;
2912
2913 sinom = sin(OM);
2914 cosom = cos(OM);
2915 sini = sin(i);
2916 cosi = cos(i);
2917
2918 double xx = xp * cosom - yp * cosi * sinom;
2919 double yy = xp * sinom + yp * cosi * cosom;
2920 double zz = yp * sini;
2921
2922 Matrix RX = BNC_PPP::t_astro::rotX(-5.0 / 180.0 * M_PI);
2923 Matrix RZ = BNC_PPP::t_astro::rotZ(ll);
2924
2925 ColumnVector X1(3);
2926 X1 << xx << yy << zz;
2927 ColumnVector X2 = RZ * RX * X1;
2928
2929 xc[0] = X2(1);
2930 xc[1] = X2(2);
2931 xc[2] = X2(3);
2932
2933 double dotom = _OMEGADOT;
2934
2935 double vx = cosom * dotx - cosi * sinom * doty - xp * sinom * dotom
2936 - yp * cosi * cosom * dotom + yp * sini * sinom * doti;
2937
2938 double vy = sinom * dotx + cosi * cosom * doty + xp * cosom * dotom
2939 - yp * cosi * sinom * dotom - yp * sini * cosom * doti;
2940
2941 double vz = sini * doty + yp * cosi * doti;
2942
2943 ColumnVector V(3);
2944 V << vx << vy << vz;
2945
2946 Matrix RdotZ(3, 3);
2947 double C = cos(ll);
2948 double S = sin(ll);
2949 Matrix UU(3, 3);
2950 UU[0][0] = -S;
2951 UU[0][1] = +C;
2952 UU[0][2] = 0.0;
2953 UU[1][0] = -C;
2954 UU[1][1] = -S;
2955 UU[1][2] = 0.0;
2956 UU[2][0] = 0.0;
2957 UU[2][1] = 0.0;
2958 UU[2][2] = 0.0;
2959 RdotZ = omegaBDS * UU;
2960
2961 ColumnVector VV(3);
2962 VV = RZ * RX * V + RdotZ * RX * X1;
2963
2964 vv[0] = VV(1);
2965 vv[1] = VV(2);
2966 vv[2] = VV(3);
2967 }
2968
2969 double tc = tt - _TOC;
2970 xc[3] = _clock_bias + _clock_drift * tc + _clock_driftrate * tc * tc;
2971
2972// dotC = _clock_drift + _clock_driftrate*tc
2973// - 4.442807309e-10*_e * sqrt(a0) * cos(E) * dEdM * n;
2974
2975 // Relativistic Correction
2976 // -----------------------
2977 // See t_ephGPS::position() for the rationale behind selecting between the
2978 // eccentricity-based and velocity-based forms.
2979 if (_orbCorr && _orbCorr->_rtcmSsr) {
2980 xc[3] -= 2.0 * (xc[0]*vv[0] + xc[1]*vv[1] + xc[2]*vv[2]) / t_CST::c / t_CST::c;
2981 }
2982 else {
2983 xc[3] -= 4.442807309e-10 * _e * sqrt(a0) * sin(E);
2984 }
2985
2986 xc[4] = _clock_drift + _clock_driftrate * tc;
2987 xc[5] = _clock_driftrate;
2988
2989 return success;
2990}
2991
2992// Health status of BDS Ephemeris (virtual)
2993////////////////////////////////////////////////////////////////////////////
2994unsigned int t_ephBDS::isUnhealthy() const {
2995
2996 if (type() == t_eph::CNV1 ||
2997 type() == t_eph::CNV2 ||
2998 type() == t_eph::CNV3) {
2999 return static_cast<unsigned int>(_health);
3000 }
3001
3002 return static_cast<unsigned int>(_SatH1);
3003
3004}
3005
3006// RINEX Format String
3007//////////////////////////////////////////////////////////////////////////////
3008QString t_ephBDS::toString(double version) const {
3009
3010 if (version < 4.0 &&
3011 (type() == t_eph::CNV1 ||
3012 type() == t_eph::CNV2 ||
3013 type() == t_eph::CNV3 )) {
3014 return "";
3015 }
3016
3017 QString ephStr = typeStr(_type, _prn, version);
3018 QString rnxStr = ephStr + rinexDateStr(_TOC - 14.0, _prn, version);
3019
3020 QTextStream out(&rnxStr);
3021
3022 out
3023 << QString("%1%2%3\n")
3024 .arg(_clock_bias, 19, 'e', 12)
3025 .arg(_clock_drift, 19, 'e', 12)
3026 .arg(_clock_driftrate, 19, 'e', 12);
3027
3028 QString fmt = version < 3.0 ? " %1%2%3%4\n" : " %1%2%3%4\n";
3029 // =====================
3030 // BROADCAST ORBIT - 1
3031 // =====================
3032 if (type() == t_eph::D1 ||
3033 type() == t_eph::D2 ||
3034 type() == t_eph::undefined) {
3035 out
3036 << QString(fmt)
3037 .arg(double(_AODE), 19, 'e', 12)
3038 .arg(_Crs, 19, 'e', 12)
3039 .arg(_Delta_n, 19, 'e', 12)
3040 .arg(_M0, 19, 'e', 12);
3041 }
3042 else { //CNV1, CNV2, CNV3
3043 out
3044 << QString(fmt)
3045 .arg(_ADOT, 19, 'e', 12)
3046 .arg(_Crs, 19, 'e', 12)
3047 .arg(_Delta_n, 19, 'e', 12)
3048 .arg(_M0, 19, 'e', 12);
3049 }
3050
3051 // =====================
3052 // BROADCAST ORBIT - 2
3053 // =====================
3054 out
3055 << QString(fmt)
3056 .arg(_Cuc, 19, 'e', 12)
3057 .arg(_e, 19, 'e', 12)
3058 .arg(_Cus, 19, 'e', 12)
3059 .arg(_sqrt_A, 19, 'e', 12);
3060
3061 // =====================
3062 // BROADCAST ORBIT - 3
3063 // =====================
3064 out
3065 << QString(fmt)
3066 .arg(_TOEsec, 19, 'e', 12)
3067 .arg(_Cic, 19, 'e', 12)
3068 .arg(_OMEGA0, 19, 'e', 12)
3069 .arg(_Cis, 19, 'e', 12);
3070 // =====================
3071 // BROADCAST ORBIT - 4
3072 // =====================
3073 out
3074 << QString(fmt)
3075 .arg(_i0, 19, 'e', 12)
3076 .arg(_Crc, 19, 'e', 12)
3077 .arg(_omega, 19, 'e', 12)
3078 .arg(_OMEGADOT, 19, 'e', 12);
3079 // =====================
3080 // BROADCAST ORBIT - 5
3081 // =====================
3082 if (type() == t_eph::CNV1 ||
3083 type() == t_eph::CNV2 ||
3084 type() == t_eph::CNV3) {
3085 out
3086 << QString(fmt)
3087 .arg(_IDOT, 19, 'e', 12)
3088 .arg(_Delta_n_dot, 19, 'e', 12)
3089 .arg(_satType, 19, 'e', 12)
3090 .arg(_top, 19, 'e', 12);
3091 }
3092 else { // D1, D2,
3093 out
3094 << QString(fmt)
3095 .arg(_IDOT, 19, 'e', 12)
3096 .arg("", 19, QChar(' '))
3097 .arg(_BDTweek, 19, 'e', 12)
3098 .arg("", 19, QChar(' '));
3099 }
3100 // =====================
3101 // BROADCAST ORBIT - 6
3102 // =====================
3103 if (type() == t_eph::CNV1 ||
3104 type() == t_eph::CNV2 ||
3105 type() == t_eph::CNV3) {
3106 out
3107 << QString(fmt)
3108 .arg(_SISAI_oe, 19, 'e', 12)
3109 .arg(_SISAI_ocb, 19, 'e', 12)
3110 .arg(_SISAI_oc1, 19, 'e', 12)
3111 .arg(_SISAI_oc2, 19, 'e', 12);
3112 }
3113 else { // D1, D2, undefined
3114 out
3115 << QString(fmt)
3116 .arg(_ura, 19, 'e', 12)
3117 .arg(double(_SatH1), 19, 'e', 12)
3118 .arg(_TGD1, 19, 'e', 12)
3119 .arg(_TGD2, 19, 'e', 12);
3120 }
3121 // =====================
3122 // BROADCAST ORBIT - 7
3123 // =====================
3124 if (type() == t_eph::CNV1) {
3125 out
3126 << QString(fmt)
3127 .arg(_ISC_B1Cd, 19, 'e', 12)
3128 .arg("", 19, QChar(' '))
3129 .arg(_TGD_B1Cp, 19, 'e', 12)
3130 .arg(_TGD_B2ap, 19, 'e', 12);
3131 }
3132 else if (type() == t_eph::CNV2) {
3133 out
3134 << QString(fmt)
3135 .arg("", 19, QChar(' '))
3136 .arg(_ISC_B2ad, 19, 'e', 12)
3137 .arg(_TGD_B1Cp, 19, 'e', 12)
3138 .arg(_TGD_B2ap, 19, 'e', 12);
3139 }
3140 else if (type() == t_eph::CNV3) {
3141 out
3142 << QString(fmt)
3143 .arg(_SISMAI, 19, 'e', 12)
3144 .arg(double(_health), 19, 'e', 12)
3145 .arg(_INTEGRITYF_B2b, 19, 'e', 12)
3146 .arg(_TGD_B2bI, 19, 'e', 12);
3147 }
3148 else { // D1, D2, undefined
3149 double tots = 0.0;
3150 if (_receptDateTime.isValid()) { // RTCM stream input
3151 tots = _TOE.bdssec();
3152 } else { // RINEX input
3153 tots = _TOT;
3154 }
3155 out
3156 << QString(fmt)
3157 .arg(tots, 19, 'e', 12)
3158 .arg(double(_AODC), 19, 'e', 12)
3159 .arg("", 19, QChar(' '))
3160 .arg("", 19, QChar(' '));
3161 }
3162
3163 // =====================
3164 // BROADCAST ORBIT - 8
3165 // =====================
3166 if (type() == t_eph::CNV1) {
3167 out
3168 << QString(fmt)
3169 .arg(_SISMAI, 19, 'e', 12)
3170 .arg(double(_health), 19, 'e', 12)
3171 .arg(_INTEGRITYF_B1C, 19, 'e', 12)
3172 .arg(_IODC, 19, 'e', 12);
3173 }
3174 else if (type() == t_eph::CNV2) {
3175 out
3176 << QString(fmt)
3177 .arg(_SISMAI, 19, 'e', 12)
3178 .arg(double(_health), 19, 'e', 12)
3179 .arg(_INTEGRITYF_B2aB1C, 19, 'e', 12)
3180 .arg(_IODC, 19, 'e', 12);
3181 }
3182 else if (type() == t_eph::CNV3) {
3183 out
3184 << QString(fmt)
3185 .arg(_TOT, 19, 'e', 12)
3186 .arg("", 19, QChar(' '))
3187 .arg("", 19, QChar(' '))
3188 .arg("", 19, QChar(' '));
3189 }
3190
3191 // =====================
3192 // BROADCAST ORBIT - 9
3193 // =====================
3194 if (type() == t_eph::CNV1 ||
3195 type() == t_eph::CNV2) {
3196 out
3197 << QString(fmt)
3198 .arg(_TOT, 19, 'e', 12)
3199 .arg("", 19, QChar(' '))
3200 .arg("", 19, QChar(' '))
3201 .arg(_IODE, 19, 'e', 12);
3202 }
3203
3204 return rnxStr;
3205}
Note: See TracBrowser for help on using the repository browser.