msecHandler.cpp
Go to the documentation of this file.
1 //==============================================================================
2 //
3 // This file is part of GNSSTk, the ARL:UT GNSS Toolkit.
4 //
5 // The GNSSTk is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU Lesser General Public License as published
7 // by the Free Software Foundation; either version 3.0 of the License, or
8 // any later version.
9 //
10 // The GNSSTk is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with GNSSTk; if not, write to the Free Software Foundation,
17 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
18 //
19 // This software was developed by Applied Research Laboratories at the
20 // University of Texas at Austin.
21 // Copyright 2004-2022, The Board of Regents of The University of Texas System
22 //
23 //==============================================================================
24 
25 //==============================================================================
26 //
27 // This software was developed by Applied Research Laboratories at the
28 // University of Texas at Austin, under contract to an agency or agencies
29 // within the U.S. Department of Defense. The U.S. Government retains all
30 // rights to use, duplicate, distribute, disclose, or release this software.
31 //
32 // Pursuant to DoD Directive 523024
33 //
34 // DISTRIBUTION STATEMENT A: This software has been approved for public
35 // release, distribution is unlimited.
36 //
37 //==============================================================================
38 
42 #include "GNSSconstants.hpp"
43 #include "Stats.hpp" // for median
44 #include "StringUtils.hpp"
45 #include "TimeString.hpp"
46 #include "stl_helpers.hpp" // for vectorindex
47 #include <algorithm>
48 #include <map>
49 #include <vector>
50 
51 #include "msecHandler.hpp"
52 
53 using namespace std;
54 
55 namespace gnsstk
56 {
57 
58  // -------------------------------------------------------------------------------
59  const double msecHandler::Rfact = 0.001 * C_MPS; // 1ms in meters
60 
61  // -------------------------------------------------------------------------------
62  // empty and only constructor
63  msecHandler::msecHandler()
64  {
65  dt = -1.0;
66  N = 6; // default only - see setObstypes
67  obstypes.push_back(string("L1"));
68  wavelengths.push_back(L1_WAVELENGTH_GPS);
69  obstypes.push_back(string("L2"));
70  wavelengths.push_back(L2_WAVELENGTH_GPS);
71  obstypes.push_back(string("C1"));
72  wavelengths.push_back(0.0);
73  obstypes.push_back(string("C2"));
74  wavelengths.push_back(0.0);
75  obstypes.push_back(string("P1"));
76  wavelengths.push_back(0.0);
77  obstypes.push_back(string("P2"));
78  wavelengths.push_back(0.0);
79  reset();
80  }
81 
82  // -------------------------------------------------------------------------------
83  // Reset the object
84  void msecHandler::reset()
85  {
86  // don't reset dt
87  prevttag = currttag = CommonTime::BEGINNING_OF_TIME;
88  curr = vector<map<SatID, double>>(N);
89  past = vector<map<SatID, double>>(N);
90  ave = vector<double>(N, 0.0);
91  npt = vector<int>(N, 0);
92 
93  typesMap.clear();
94  findMsg = fixMsg = string();
95 
96  times.clear();
97  nms.clear();
98  ots.clear();
99  adjMsgs.clear();
100  badMsgs.clear();
101 
102  doPR = false;
103  rmvClk = false;
104  }
105 
106  // -------------------------------------------------------------------------------
107  // define obstypes and wavelengths; NB set wavelength(code) = 0
108  void msecHandler::setObstypes(const vector<string> &ots,
109  const vector<double> &waves)
110  {
111  if (ots.size() != waves.size())
112  {
113  GNSSTK_THROW(Exception("Inconsistent input"));
114  }
115  N = ots.size();
116  obstypes = ots;
117  wavelengths = waves;
118  reset();
119  }
120 
121  // -------------------------------------------------------------------------------
122  /* add data at one epoch. May be repeated at the same epoch, but MUST be done
123  in time order. NB assumes, as in RINEX, that data==0 means it is missing. */
124  void msecHandler::add(CommonTime ttag, const SatID sat, const string obstype,
125  double data)
126  {
127  if (dt == -1.0)
128  {
129  GNSSTK_THROW(Exception("Must set nominal timestep first"));
130  }
131 
132  if (data == 0.0)
133  {
134  return; // NB assumes, as in RINEX, that data==0 is missing.
135  }
136 
137  if (currttag == CommonTime::BEGINNING_OF_TIME)
138  {
139  currttag = ttag;
140  }
141  if (ttag != currttag)
142  {
143  compute(ttag);
144  }
145 
146  // first find it in obstypes
147  vector<string>::const_iterator it;
148  it = find(obstypes.begin(), obstypes.end(), obstype);
149  if (it == obstypes.end())
150  {
151  return; // not one of the obstypes
152  }
153  int i = (it - obstypes.begin());
154 
155  // store current value
156  curr[i][sat] = data;
157  // difference with past
158  if (past[i].find(sat) != past[i].end() // if past is there
159  && curr[i][sat] != 0.0 // and curr is not zero
160  && past[i][sat] != 0.0) // and past is not zero
161  {
162  ave[i] += curr[i][sat] - past[i][sat]; // first difference
163  npt[i]++; // count it
164  }
165  }
166 
167  // -------------------------------------------------------------------------------
168  /* After all add() calls, and before calling fix()
169  @return number of fixes to apply */
170  int msecHandler::afterAddbeforeFix()
171  {
172  // compute adjusts based on all the saved data
173  compute(CommonTime::END_OF_TIME);
174 
175  if (times.size() == 0)
176  {
177  fixMsg = string("No valid adjusts found - nothing to do");
178  return 0;
179  }
180  if (times.size() == 1 && rmvClk)
181  {
182  rmvClk = false;
183  fixMsg =
184  string("Warning - cannot remove gross clock with only 1 ms adjust");
185  }
186 
187  doPR = false;
188  if (find(ots[0].begin(), ots[0].end(), string("C1")) != ots[0].end() ||
189  find(ots[0].begin(), ots[0].end(), string("C2")) != ots[0].end() ||
190  find(ots[0].begin(), ots[0].end(), string("P1")) != ots[0].end() ||
191  find(ots[0].begin(), ots[0].end(), string("P2")) != ots[0].end())
192  {
193  doPR = true;
194  fixMsg += string("Adjusts applied to pseudorange, ") +
195  string("so apply fix to the timetags.");
196  }
197  else
198  {
199  fixMsg += string("Do not apply adjusts to timtags.");
200  }
201 
202  ims = ntot = 0;
204 
205  return times.size();
206  }
207 
208  // -------------------------------------------------------------------------------
209  /* edit data by removing the millisecond adjusts, and optionally a piece-wise
210  linear model of the adjusts. Must be called in time order, as add() was.
211  NB may call repeatedly with the same ttag, however
212  NB ttag gets fixed every call, so don't keep calling with same variable
213  ttag. */
214  void msecHandler::fix(CommonTime &ttag, const SatID sat, const string obstype,
215  double &data)
216  {
217  // define the first linear clock
218  if (rmvClk && tref == CommonTime::BEGINNING_OF_TIME)
219  {
220  tref = ttag;
221  slope = double(nms[1]) / (times[1] - times[0]);
222  intercept = double(nms[0]) - slope * (times[0] - ttag);
223  }
224 
225  // advance to the next ms adjust?
226  if (ims < times.size() && ::fabs(ttag - times[ims]) < 1.e-3)
227  {
228  ntot += nms[ims];
229  fixMsg += string("\nFixed ") + adjMsgs[ims];
230  ims++;
231  if (rmvClk && ims < times.size())
232  {
233  tref = times[ims - 1];
234  slope = double(nms[ims]) / (times[ims] - tref);
235  intercept = double(ntot);
236  } // else just leave them...extrapolation
237  }
238 
239  // find index and wavelength for this obstype
240  int index = vectorindex(obstypes, obstype);
241  if (index == -1)
242  {
243  GNSSTK_THROW(Exception("Invalid obstype, internal error: " + obstype));
244  }
245  double wl = wavelengths[index];
246 
247  // remove adjusts
248  if (ims > 0 && ntot != 0)
249  {
250  // remove adjust from the time tag
251  if (doPR)
252  {
253  ttag -= ntot * 0.001;
254  }
255 
256  // remove adjust from the data
257  if (find(ots[ims - 1].begin(), ots[ims - 1].end(), obstype) !=
258  ots[ims - 1].end())
259  {
260  if (data != 0.0)
261  {
262  data -= ntot * (wl == 0.0 ? Rfact : Rfact / wl);
263  }
264  }
265  }
266 
267  /* remove gross (piece-wise linear) clock by adjusting time tags and all
268  data */
269  if (rmvClk)
270  {
271  // compute the model at this time
272  double dtot = (intercept + slope * (ttag - tref)) * Rfact;
273  ttag += dtot / C_MPS;
274  if (wl != 0.0)
275  {
276  dtot /= wl;
277  }
278  if (data != 0.0)
279  {
280  data += dtot;
281  }
282  }
283  }
284 
285  // -------------------------------------------------------------------------------
286  // get messages generated during detection phase
287  string msecHandler::getFindMessage(bool verbose)
288  {
289  size_t i;
290  findMsg = string();
291 
292  findMsg += string("Searched for millisecond adjusts on obs types:");
293  for (i = 0; i < obstypes.size(); i++)
294  {
295  findMsg += string(" ") + obstypes[i];
296  }
297  findMsg += string("\n");
298 
299  findMsg += string("Millisecond adjusts: ") +
300  StringUtils::asString(adjMsgs.size() + badMsgs.size()) +
301  string(" total adjusts found, ") +
302  StringUtils::asString(badMsgs.size()) + string(" invalid");
303 
304  for (map<string, int>::iterator tit = typesMap.begin();
305  tit != typesMap.end(); ++tit)
306  {
307  findMsg += string("\n Found ") + StringUtils::asString(tit->second) +
308  string(" adjusts for ") + tit->first;
309  }
310 
311  if (typesMap.size() > 1)
312  {
313  findMsg +=
314  string("\n Warning - detected millisecond adjusts are not ") +
315  string("consistently applied to the observables.");
316  }
317 
318  if (adjMsgs.size() > 0 && badMsgs.size() > adjMsgs.size() / 2)
319  {
320  findMsg +=
321  string("\n Warning - millisecond adjust detection seems to be ") +
322  string("of poor quality - consider rerunning with option --noMS");
323  }
324 
325  if (verbose)
326  {
327  for (i = 0; i < adjMsgs.size(); i++)
328  {
329  findMsg += string("\n") + adjMsgs[i];
330  }
331  for (i = 0; i < badMsgs.size(); i++)
332  {
333  findMsg += string("\n") + badMsgs[i];
334  }
335  }
336 
337  return findMsg;
338  }
339 
340  // -------------------------------------------------------------------------------
341  // get map<CommonTime,int> of valid adjusts
342  map<CommonTime, int> msecHandler::getAdjusts()
343  {
344  map<CommonTime, int> adjusts;
345  for (unsigned int i = 0; i < times.size(); i++)
346  {
347  adjusts.insert(map<CommonTime, int>::value_type(times[i], nms[i]));
348  }
349 
350  return adjusts;
351  }
352 
353  // -------------------------------------------------------------------------------
354  /* compute - pass it the upcoming ttag
355  NB. ineq1620.14o - trimble has 2 and 3 ms adjusts */
356  void msecHandler::compute(const CommonTime ttag)
357  {
358  size_t i, j;
359  int ii, in, nadj;
360  static CommonTime lastttag(CommonTime::BEGINNING_OF_TIME);
361  const static double mstol(0.2);
362 
363  if (prevttag != CommonTime::BEGINNING_OF_TIME)
364  {
365  // convert to millisecs and compute averages
366  for (i = 0; i < N; i++)
367  {
368  if (wavelengths[i] != 0.0)
369  {
370  ave[i] *= wavelengths[i];
371  }
372 
373  if (npt[i] > 0)
374  {
375  // form average and convert to ms
376  ave[i] *= 1000.0 / (npt[i] * C_MPS);
377  }
378  else
379  {
380  ave[i] = 0.0;
381  }
382  }
383 
384  // do for time tag as well
385  double del = dt - (currttag - prevttag);
386  del = ::fmod(del, dt);
387  del *= 1000.0;
388 
389  // round to nearest integer ms
390  vector<int> iave(N + 1); // element [N] is timetag
391  for (i = 0; i < N; i++) // L1 L2 C1 C2 P1 P2
392  {
393  iave[i] = int(ave[i] + (ave[i] >= 0.0 ? 0.5 : -0.5));
394  }
395  iave[N] = (del + (del > 0.0 ? 0.5 : -0.5)); // N is timetag
396 
397  // test - is there an adjust? are the non-zero number-of-ms consistent?
398  bool adj(false), consist(true), adjPh(false), adjPR(false);
399  nadj = 0; // the adjust
400  for (i = 0; i < N; i++)
401  { // test only the data, not timetags
402  if (iave[i] != 0)
403  {
404  adj = true;
405  if (wavelengths[i] != 0.0)
406  {
407  adjPh = true;
408  }
409  else
410  {
411  adjPR = true;
412  }
413 
414  if (nadj == 0)
415  {
416  nadj = iave[i];
417  }
418  else if (nadj != iave[i])
419  {
420  consist = false;
421  }
422  }
423  }
424 
425  /* treat phases specially - there can be large cycleslips that
426  interfere with determination of adjusts. these will be isolated to
427  one sat, so use robust stats to find the average (median). TD
428  consider median for all ave[] */
429  if (adjPh && adjPR)
430  {
431  bool foundPhase(false);
432  for (i = 0; i < N; i++)
433  { // just phases
434  if (wavelengths[i] == 0.0)
435  {
436  continue;
437  }
438 
439  double med, mad;
440  vector<double> deltas;
441  map<SatID, double>::const_iterator it;
442 
443  // collect the differences, one per sat
444  for (it = curr[i].begin(); it != curr[i].end(); ++it)
445  {
446  if (past[i].find(it->first) != past[i].end())
447  {
448  // diff for this sat in ms
449  del = (curr[i][it->first] - past[i][it->first]) /
450  (Rfact / wavelengths[i]);
451  deltas.push_back(del);
452  }
453  }
454 
455  // get the median, an outlier will not affect this
456  med = median<double>(deltas);
457 
458  // compute abs(deviation from median)
459  for (j = 0; j < deltas.size(); j++)
460  {
461  deltas[j] = ::fabs(deltas[j] - med);
462  }
463 
464  // get median absolute deviation
465  mad = median<double>(deltas);
466 
467  /* replace average with median, which will be insensitive to
468  outliers */
469  if (mad < 0.5)
470  {
471  ave[i] = med;
472  iave[i] = int(ave[i] + (ave[i] >= 0.0 ? 0.5 : -0.5));
473  }
474 
475  if (iave[i] != 0)
476  {
477  foundPhase = true;
478  }
479  }
480 
481  // fix it - duplicate code above
482  if (!foundPhase)
483  {
484  adj = false;
485  consist = true;
486  adjPh = false;
487  nadj = 0;
488  for (i = 0; i < N; i++)
489  {
490  if (iave[i] != 0)
491  {
492  adj = true;
493  if (nadj == 0)
494  {
495  nadj = iave[i];
496  }
497  else if (nadj != iave[i])
498  {
499  consist = false;
500  }
501  }
502  }
503  }
504  }
505 
506  // if there is an adjust, test it further, then store it
507  if (adj)
508  {
509  // string conmsg(consist ? "" : (!isOne ? " adjust is not +-1ms"
510  string conmsg(consist ? "" : " adjust sizes are inconsistent");
511 
512  /* test for shaky determination - adjust is not close to integer
513  millisec, and/or number of sats is low. do only if adjust is
514  consistent */
515  double frac(::fabs(ave[0] - iave[0]));
516  // TD should this include || npt[0] < 3 ?? yes tripwire brst 195
517  if (frac > mstol || npt[0] < 3)
518  {
519  conmsg = string(" not well determined");
520  consist = false;
521  }
522 
523  // are they consistent? is phase consistent with code?
524  ostringstream oss;
525  in = -1;
526  bool onphase(false);
527  for (i = 0; i < N; i++) // phase only
528  {
529  if (wavelengths[i] == 0.0)
530  {
531  continue; // skip PR
532  }
533  if (npt[i] == 0)
534  {
535  continue; // skip no data
536  }
537  if (iave[i] != 0)
538  {
539  onphase = true;
540  }
541  if (in == -1)
542  {
543  in = i;
544  }
545  if (iave[i] != iave[in])
546  {
547  consist = false;
548  conmsg += string(" " + obstypes[in] + "!=" + obstypes[i]);
549  }
550  }
551  in = -1;
552  bool oncode(false);
553  for (i = 0; i < N; i++)
554  { // code only
555  if (wavelengths[i] != 0.0)
556  {
557  continue; // skip Phase
558  }
559  if (npt[i] == 0)
560  {
561  continue; // skip no data
562  }
563  if (iave[i] != 0)
564  {
565  oncode = true;
566  }
567  if (in == -1)
568  {
569  in = i;
570  }
571  if (iave[i] != iave[in])
572  {
573  consist = false;
574  conmsg += string(" " + obstypes[in] + "!=" + obstypes[i]);
575  }
576  }
577  if (consist && onphase && !oncode)
578  {
579  conmsg += string(" (Phase-only)");
580  }
581  if (consist && !onphase && oncode)
582  {
583  conmsg += string(" (PR-only)");
584  }
585  if (!consist)
586  {
587  conmsg += string(" invalid");
588  }
589 
590  // create the types str, the ots and the msg
591  oss.str("");
592  string types;
593  vector<string> no, ot;
594  if (iave[N] == 0)
595  {
596  no.push_back("TT");
597  }
598  else
599  {
600  ot.push_back("TT");
601  }
602  oss << (iave[N] == 0 ? "!" : "") << "TT";
603  for (i = 0; i < N; i++)
604  {
605  if (npt[i])
606  {
607  if (iave[i] == 0)
608  {
609  no.push_back(obstypes[i]);
610  }
611  else
612  {
613  ot.push_back(obstypes[i]);
614  }
615  oss << " " << (iave[i] == 0 ? "!" : "") << obstypes[i];
616  }
617  }
618  types = oss.str();
619 
620  // compute time since last adjust
621  del =
622  (lastttag != CommonTime::BEGINNING_OF_TIME ? currttag - lastttag
623  : 0.0);
624  lastttag = currttag;
625 
626  // NB gps sow at .7 b/c RINEX time tags have this precision
627  oss.str("");
628  oss << "msAdjust "
629  << printTime(currttag,
630  "%04Y/%02m/%02d %02H:%02M:%06.3f = %4F %14.7g")
631  << " dt=" << int(del + 0.5) << " " << nadj << " ms " << types;
632  oss << conmsg;
633 
634  if (consist)
635  { // found a valid adjust
636  // save
637  nms.push_back(nadj);
638  times.push_back(currttag);
639 
640  // save ots
641  ots.push_back(ot);
642 
643  // increment types map
644  if (typesMap.find(types) == typesMap.end())
645  {
646  typesMap.insert(map<string, int>::value_type(types, 0));
647  }
648  typesMap[types]++;
649 
650  adjMsgs.push_back(oss.str());
651 
652  // RinEdit commands
653  for (i = 0; i < N; i++)
654  {
655  if (iave[i] == 0)
656  {
657  continue;
658  }
659  oss.str("");
660  oss << "--BD+ ";
661  if (obstypes[i].length() > 2)
662  {
663  oss << obstypes[i][0] << "," << obstypes[i].substr(1);
664  }
665  else
666  {
667  oss << "G" << obstypes[i];
668  }
669  oss << printTime(currttag, ",%F,%.3g");
670  oss << "," << fixed << setprecision(5)
671  << -double(nadj) * (wavelengths[i] == 0.0
672  ? Rfact
673  : Rfact / wavelengths[i])
674  << " # edit cmd for " << nadj << " millisecond adjust";
675 
676  editCmds.push_back(oss.str());
677  }
678  }
679  else
680  { // not a valid adjust
681  badMsgs.push_back(oss.str());
682  }
683  }
684  } // end if prevttag != BEGIN
685 
686  // prepare for next epoch
687  prevttag = currttag;
688  currttag = ttag;
689  for (i = 0; i < N; i++)
690  {
691  past[i].clear();
692  past[i] = curr[i];
693  curr[i].clear();
694  }
695  ave = vector<double>(N, 0.0);
696  npt = vector<int>(N, 0);
697 
698  } // end void compute(ttag)
699 
700 } // namespace gnsstk
gnsstk::BEGINNING_OF_TIME
const Epoch BEGINNING_OF_TIME(CommonTime::BEGINNING_OF_TIME)
Earliest representable Epoch.
StringUtils.hpp
stl_helpers.hpp
gnsstk::SatID
Definition: SatID.hpp:89
gnsstk::StringUtils::asString
std::string asString(IonexStoreStrategy e)
Convert a IonexStoreStrategy to a whitespace-free string name.
Definition: IonexStoreStrategy.cpp:46
GNSSconstants.hpp
gnsstk
For Sinex::InputHistory.
Definition: BasicFramework.cpp:50
Stats.hpp
gnsstk::L2_WAVELENGTH_GPS
const double L2_WAVELENGTH_GPS
GPS L2 carrier wavelength in meters.
Definition: DeprecatedConsts.hpp:59
gnsstk::Exception
Definition: Exception.hpp:151
msecHandler.hpp
gnsstk::C_MPS
const double C_MPS
m/s, speed of light; this value defined by GPS but applies to GAL and GLO.
Definition: GNSSconstants.hpp:74
gnsstk::CommonTime
Definition: CommonTime.hpp:84
gnsstk::END_OF_TIME
const Epoch END_OF_TIME(CommonTime::END_OF_TIME)
Latest Representable Epoch.
example3.data
data
Definition: example3.py:22
gnsstk::printTime
std::string printTime(const CommonTime &t, const std::string &fmt)
Definition: TimeString.cpp:64
std
Definition: Angle.hpp:142
GNSSTK_THROW
#define GNSSTK_THROW(exc)
Definition: Exception.hpp:366
gnsstk::L1_WAVELENGTH_GPS
const double L1_WAVELENGTH_GPS
GPS L1 carrier wavelength in meters.
Definition: DeprecatedConsts.hpp:57
gnsstk::mad
T mad(const gnsstk::Vector< T > &v)
median absolute deviation of a gnsstk::Vector
Definition: Stats.hpp:85
gnsstk::vectorindex
int vectorindex(const std::vector< T > &vec, const T &value)
Definition: stl_helpers.hpp:123
TimeString.hpp


gnsstk
Author(s):
autogenerated on Wed Oct 25 2023 02:40:40