PRSolution.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 
43 #include "MathBase.hpp"
44 #include "PRSolution.hpp"
45 #include "GPSEllipsoid.hpp"
46 #include "GlobalTropModel.hpp"
47 #include "Combinations.hpp"
48 #include "TimeString.hpp"
49 #include "logstream.hpp"
50 
51 using namespace std;
52 using namespace gnsstk;
53 
54 namespace gnsstk
55 {
56  const string PRSolution::calfmt = string("%04Y/%02m/%02d %02H:%02M:%02S %P");
57  const string PRSolution::gpsfmt = string("%4F %10.3g");
58  const string PRSolution::timfmt = gpsfmt + string(" ") + calfmt;
59 
60  ostream& operator<<(ostream& os, const WtdAveStats& was)
61  { was.dump(os,was.getMessage()); return os;}
62 
63  // -------------------------------------------------------------------------
64  // Prepare for the autonomous solution by computing direction cosines,
65  // corrected pseudoranges and satellite system.
66  int PRSolution::PreparePRSolution(const CommonTime& Tr,
67  vector<SatID>& Sats,
68  const vector<double>& Pseudorange,
69  NavLibrary& eph,
70  Matrix<double>& SVP,
71  NavSearchOrder order) const
72  {
73  LOG(DEBUG) << "PreparePRSolution at time " << printTime(Tr,timfmt);
74 
75  int j,noeph(0),N,NSVS;
76  size_t i;
77  CommonTime tx;
78  Xvt PVT;
79 
80  // catch undefined allowedGNSS
81  if(allowedGNSS.size() == 0) {
82  Exception e("Must define systems vector allowedGNSS before processing");
83  GNSSTK_THROW(e);
84  }
85 
86  // must ignore and mark satellites if system is not found in allowedGNSS
87  for(N=0,i=0; i<Sats.size(); i++) {
88  if(Sats[i].id <= 0) continue; // already marked
89  if(vectorindex(allowedGNSS, Sats[i].system) == -1) {
90  LOG(DEBUG) << " PRSolution ignores satellite (system) "
91  << RinexSatID(Sats[i]) << " at time " << printTime(Tr,timfmt);
92  Sats[i].id = -Sats[i].id; // don't have system - mark it
93  continue;
94  }
95  LOG(DEBUG) << " Count sat " << RinexSatID(Sats[i]);
96  ++N; // count good sat
97  }
98 
99  LOG(DEBUG) << "Sats.size is " << Sats.size();
100  SVP = Matrix<double>(Sats.size(),4,0.0); // define the matrix to return
101  if(N <= 0) return 0; // nothing to do
102  NSVS = 0; // count good sats w/ ephem
103 
104  // loop over all satellites
105  for(i=0; i<Sats.size(); i++) {
106 
107  // skip marked satellites
108  if(Sats[i].id <= 0) {
109  LOG(DEBUG) << " PRSolution ignores marked satellite "
110  << RinexSatID(Sats[i]) << " at time " << printTime(Tr,timfmt);
111  continue;
112  }
113  LOG(DEBUG) << " Process sat " << RinexSatID(Sats[i]);
114 
115  // first estimate of transmit time
116  tx = Tr;
117 
118  // must align time systems.
119  // know system of Tr, and must assume system of eph(sat) is system(sat).
120  // eph must do calc in its sys, so must transform Tr to system(sat).
121  // convert time system of tx to that of Sats[i]
122 
123  tx -= Pseudorange[i]/C_MPS;
124  try {
125  LOG(DEBUG) << " go to getXvt with time " << printTime(tx,timfmt);
129  GNSSTK_ASSERT(eph.getXvt(NavSatelliteID(Sats[i]), tx, PVT, false,
130  SVHealth::Healthy,
131  NavValidityType::ValidOnly, order));
132  LOG(DEBUG) << " returned from getXvt";
133  }
134  catch(AssertionFailure& e) {
135  LOG(DEBUG) << "Warning - PRSolution ignores satellite (no ephemeris) "
136  << RinexSatID(Sats[i]) << " at time " << printTime(tx,timfmt)
137  << " [" << e.getText() << "]";
138  Sats[i].id = -::abs(Sats[i].id);
139  ++noeph;
140  continue;
141  }
142  catch(Exception& e) {
143  LOG(DEBUG) << "Oops - Exception " << e.getText();
144  }
145 
146  // update transmit time and get ephemeris range again
147  tx -= PVT.clkbias + PVT.relcorr;
148  try {
152  GNSSTK_ASSERT(eph.getXvt(NavSatelliteID(Sats[i]), tx, PVT, false,
153  SVHealth::Healthy,
154  NavValidityType::ValidOnly, order));
155  }
156  catch(AssertionFailure& e) { // unnecessary....you'd think!
157  LOG(DEBUG) << "Warning - PRSolution ignores satellite (no ephemeris 2) "
158  << RinexSatID(Sats[i]) << " at time " << printTime(tx,timfmt)
159  << " [" << e.getText() << "]";
160  Sats[i].id = -::abs(Sats[i].id);
161  ++noeph;
162  continue;
163  }
164 
165  // SVP = {SV position at transmit time}, raw range + clk + rel
166  for(j=0; j<3; j++) SVP(i,j) = PVT.x[j];
167  SVP(i,3) = Pseudorange[i] + C_MPS * (PVT.clkbias + PVT.relcorr);
168 
169  LOG(DEBUG) << "SVP: Sat " << RinexSatID(Sats[i])
170  << " PR " << fixed << setprecision(3) << Pseudorange[i]
171  << " clkbias " << C_MPS*PVT.clkbias
172  << " relcorr " << C_MPS*PVT.relcorr;
173 
174  // count the good satellites
175  NSVS++;
176  }
177 
178  if(noeph == N) return -4; // no ephemeris for any good sat
179 
180  return NSVS;
181 
182  } // end PreparePRSolution
183 
184 
185  // -------------------------------------------------------------------------
186  // Compute a straightforward solution using all the unmarked data.
187  // Call PreparePRSolution first.
188  int PRSolution::SimplePRSolution(const CommonTime& T,
189  const vector<SatID>& Sats,
190  const Matrix<double>& SVP,
191  const Matrix<double>& invMC,
192  TropModel *pTropModel,
193  const int& niterLimit,
194  const double& convLimit,
195  Vector<double>& Resids,
196  Vector<double>& Slopes)
197  {
198  if(!pTropModel) {
199  Exception e("Undefined tropospheric model");
200  GNSSTK_THROW(e);
201  }
202  if(Sats.size() != SVP.rows() ||
203  (invMC.rows() > 0 && invMC.rows() != Sats.size())) {
204  LOG(ERROR) << "Sats has length " << Sats.size();
205  LOG(ERROR) << "SVP has dimension " << SVP.rows() << "x" << SVP.cols();
206  LOG(ERROR) << "invMC has dimension " << invMC.rows() << "x" << invMC.cols();
207  Exception e("Invalid dimensions");
208  GNSSTK_THROW(e);
209  }
210  if(allowedGNSS.size() == 0) {
211  Exception e("Must define systems vector allowedGNSS before processing");
212  GNSSTK_THROW(e);
213  }
214 
215  int iret(0),k,n;
216  size_t i, j;
217  double rho,wt,svxyz[3];
218  GPSEllipsoid ellip;
219 
220  Valid = false;
221 
222  try {
223  // -----------------------------------------------------------
224  // counts, systems and dimensions
225  vector<SatelliteSystem> currGNSS;
226  {
227  // define the current systems vector, and count good satellites
228  vector<SatelliteSystem> tempGNSS;
229  for(Nsvs=0,i=0; i<Sats.size(); i++) {
230  if(Sats[i].id <= 0) // reject marked sats
231  continue;
232 
233  SatelliteSystem sys(Sats[i].system); // get this system
234  if(vectorindex(allowedGNSS, sys) == -1) // reject disallowed sys
235  continue;
236 
237  Nsvs++; // count it
238  if(vectorindex(tempGNSS, sys) == -1) // add unique system
239  tempGNSS.push_back(sys);
240  }
241 
242  // must sort as in allowedGNSS
243  for(i=0; i<allowedGNSS.size(); i++)
244  if(vectorindex(tempGNSS, allowedGNSS[i]) != -1)
245  currGNSS.push_back(allowedGNSS[i]);
246  }
247 
248  // dimension of the solution vector (3 pos + 1 clk/sys)
249  const size_t dim(3 + currGNSS.size());
250 
251  // require number of good satellites to be >= number unknowns (no RAIM here)
252  if(Nsvs < dim) return -3;
253 
254  // -----------------------------------------------------------
255  // build the measurement covariance matrix
256  Matrix<double> iMC;
257  if(invMC.rows() > 0) {
258  LOG(DEBUG) << "Build inverse MCov";
259  iMC = Matrix<double>(Nsvs,Nsvs,0.0);
260  for(n=0,i=0; i<Sats.size(); i++) {
261  if(Sats[i].id <= 0) continue;
262  for(k=0,j=0; j<Sats.size(); j++) {
263  if(Sats[j].id <= 0) continue;
264  iMC(n,k) = invMC(i,j);
265  ++k;
266  }
267  ++n;
268  }
269  LOG(DEBUG) << "inv MCov matrix is\n" << fixed << setprecision(4) << iMC;
270  }
271 
272  // -----------------------------------------------------------
273  // define for computation
274  Vector<double> CRange(Nsvs),dX(dim);
275  Matrix<double> P(Nsvs,dim,0.0),PT,G(dim,Nsvs),PG(Nsvs,Nsvs),Rotation;
276  Triple dirCos;
277  Xvt SV,RX;
278 
279  Solution.resize(dim);
280  Covariance.resize(dim,dim);
281  Resids.resize(Nsvs);
282  Slopes.resize(Nsvs);
283  LOG(DEBUG) << " Solution dimension is " << dim << " and Nsvs is " << Nsvs;
284 
285  // prepare for iteration loop
286  // iterate at least twice so that trop model gets evaluated
287  int n_iterate(0), niter_limit(niterLimit < 2 ? 2 : niterLimit);
288  double converge(0.0);
289 
290  // start with solution = apriori, cut down to match current dimension
291  Vector<double> localAPSol(dim,0.0);
292  if(hasMemory) {
293  for(i=0; i<3; i++) localAPSol[i] = APSolution[i];
294  for(i=0; i<currGNSS.size(); i++) {
295  k = vectorindex(allowedGNSS,currGNSS[i]);
296  localAPSol[3+i] = (k == -1 ? 0.0 : APSolution[3+k]);
297  }
298  //LOG(INFO) << " apriori solution (" << APSolution.size() << ") is [ "
299  // << fixed << setprecision(3) << APSolution << " ]";
300  }
301  else {
302  LOG(DEBUG) << " no memory - no apriori solution";
303  }
304  Solution = localAPSol;
305 
306  // -----------------------------------------------------------
307  // iteration loop
308  vector<RinexSatID> RSats;
309  do {
310  TropFlag = false; // true means the trop corr was NOT applied
311 
312  // current estimate of position solution
313  RX.x = Triple(Solution(0),Solution(1),Solution(2));
314 
315  // loop over satellites, computing partials matrix
316  for(n=0,i=0; i<Sats.size(); i++) {
317  // ignore marked satellites
318  if(Sats[i].id <= 0) continue;
319  RinexSatID rs(Sats[i].id, Sats[i].system);
320  RSats.push_back(rs);
321 
322  // ------------ ephemeris
323  // rho is time of flight (sec)
324  if(n_iterate == 0)
325  rho = 0.070; // initial guess: 70ms
326  else
327  rho = RSS(SVP(i,0)-Solution(0),
328  SVP(i,1)-Solution(1), SVP(i,2)-Solution(2))/ellip.c();
329 
330  // correct for earth rotation
331  wt = ellip.angVelocity()*rho; // radians
332  svxyz[0] = ::cos(wt)*SVP(i,0) + ::sin(wt)*SVP(i,1);
333  svxyz[1] = -::sin(wt)*SVP(i,0) + ::cos(wt)*SVP(i,1);
334  svxyz[2] = SVP(i,2);
335 
336  // rho is now geometric range
337  rho = RSS(svxyz[0]-Solution(0),
338  svxyz[1]-Solution(1),
339  svxyz[2]-Solution(2));
340 
341  // direction cosines
342  dirCos[0] = (Solution(0)-svxyz[0])/rho;
343  dirCos[1] = (Solution(1)-svxyz[1])/rho;
344  dirCos[2] = (Solution(2)-svxyz[2])/rho;
345 
346  // ------------ data
347  // corrected pseudorange (m) minus geometric range
348  CRange(n) = SVP(i,3) - rho;
349 
350  // correct for troposphere and PCOs (but not on the first iteration)
351  if(n_iterate > 0) {
352  SV.x = Triple(svxyz[0],svxyz[1],svxyz[2]);
353  Position R,S;
354  R.setECEF(RX.x[0],RX.x[1],RX.x[2]);
355  S.setECEF(SV.x[0],SV.x[1],SV.x[2]);
356 
357  // trop
358  // must test R for reasonableness to avoid corrupting TropModel
359  double tc(R.getHeight()); // tc is a dummy here
360 
361  // Global model sets the upper limit - first test it
362  GlobalTropModel* p = dynamic_cast<GlobalTropModel*>(pTropModel);
363  bool bad(p && tc > p->getHeightLimit());
364 
365  if(bad || R.elevation(S) < 0.0 || tc < -1000.0)
366  {
367  tc = 0.0;
368  TropFlag = true; // true means failed to apply trop corr
369  }
370  else {
371  tc = pTropModel->correction(R,S,T); // pTropModel not const
372  }
373 
374  CRange(n) -= tc;
375  LOG(DEBUG) << "Trop " << i << " " << Sats[i] << " "
376  << fixed << setprecision(3) << tc;
377 
378  } // end if n_iterate > 0
379 
380  // get the index, for this clock, in the solution vector
381  j = 3 + vectorindex(currGNSS, Sats[i].system); // Solution ~ X,Y,Z,clks
382 
383  // find the clock for the sat's system
384  const double clk(Solution(j));
385  LOG(DEBUG) << "Clock is (" << j << ") " << clk;
386 
387  // data vector: corrected range residual
388  Resids(n) = CRange(n) - clk;
389 
390  // ------------ least squares
391  // partials matrix
392  P(n,0) = dirCos[0]; // x direction cosine
393  P(n,1) = dirCos[1]; // y direction cosine
394  P(n,2) = dirCos[2]; // z direction cosine
395  P(n,j) = 1.0; // clock
396 
397  // ------------ increment index
398  // n is index and number of good satellites - also used for Slope
399  n++;
400 
401  } // end loop over satellites
402 
403  if(n != Nsvs) {
404  Exception e("Counting error after satellite loop");
405  GNSSTK_THROW(e);
406  }
407 
408  LOG(DEBUG) << "Partials (" << P.rows() << "x" << P.cols() << ")\n"
409  << fixed << setprecision(4) << P;
410  LOG(DEBUG) << "Resids (" << Resids.size() << ") "
411  << fixed << setprecision(3) << Resids;
412 
413  // ------------------------------------------------------
414  // compute information matrix (inverse covariance) and generalized inverse
415  PT = transpose(P);
416 
417  // weight matrix = measurement covariance inverse
418  if(invMC.rows() > 0) Covariance = PT * iMC * P;
419  else Covariance = PT * P;
420 
421  // invert using SVD
422  try {
423  Covariance = inverseSVD(Covariance);
424  }
425  catch(MatrixException& sme) { return -2; }
426  LOG(DEBUG) << "InvCov (" << Covariance.rows() << "x" << Covariance.cols()
427  << ")\n" << fixed << setprecision(4) << Covariance;
428 
429  // generalized inverse
430  if(invMC.rows() > 0) G = Covariance * PT * iMC;
431  else G = Covariance * PT;
432 
433  // PG is used for Slope computation
434  PG = P * G;
435  LOG(DEBUG) << "PG (" << PG.rows() << "x" << PG.cols()
436  << ")\n" << fixed << setprecision(4) << PG;
437 
438  n_iterate++; // increment number iterations
439 
440  // ------------------------------------------------------
441  // compute solution
442  dX = G * Resids;
443  LOG(DEBUG) << "Computed dX(" << dX.size() << ")";
444  Solution += dX;
445 
446  // ------------------------------------------------------
447  // test for convergence
448  converge = norm(dX);
449  if(n_iterate > 1 && converge < convLimit) { // success: quit
450  iret = 0;
451 
453  //for(n=0; n<P.rows(); n++)
454  // LOG(INFO) << "LPRSP " << fixed << setprecision(6)
455  // << " " << printTime(T,gpsfmt)
456  // << " " << setw(2) << n << " " << RSats[n]
457  // << " " << setw(9) << P(n,0)
458  // << " " << setw(9) << P(n,1)
459  // << " " << setw(9) << P(n,2)
460  // << " " << setw(13) << Resids(n)
461  // << " " << setw(13) << CRange(n);
462 
463  break;
464  }
465  if(n_iterate >= niter_limit || converge > 1.e10) { // failure: quit
466  iret = -1;
467  break;
468  }
469 
470  } while(1); // end iteration loop
471  LOG(DEBUG) << "Out of iteration loop";
472 
473  if(TropFlag) LOG(DEBUG) << "Trop correction not applied at time "
474  << printTime(T,timfmt);
475 
476  // compute slopes and find max member
477  MaxSlope = 0.0;
478  Slopes = 0.0;
479  if(iret == 0) for(j=0,i=0; i<Sats.size(); i++) {
480  if(Sats[i].id <= 0) continue;
481 
482  // NB when one (few) sats have their own clock, PG(j,j) = 1 (nearly 1)
483  // and slope is inf (large)
484  if(::fabs(1.0-PG(j,j)) < 1.e-8) continue;
485 
486  for(int k=0; k<dim; k++) Slopes(j) += G(k,j)*G(k,j); // TD dim=4 here?
487  Slopes(j) = SQRT(Slopes(j)*double(n-dim)/(1.0-PG(j,j)));
488  if(Slopes(j) > MaxSlope) MaxSlope = Slopes(j);
489  j++;
490  }
491  LOG(DEBUG) << "Computed slopes, found max member";
492 
493  // compute pre-fit residuals
494  if(hasMemory)
495  PreFitResidual = P*(Solution-localAPSol) - Resids;
496  LOG(DEBUG) << "Computed pre-fit residuals";
497 
498  // Compute RMS residual (member)
499  RMSResidual = RMS(Resids);
500  LOG(DEBUG) << "Computed RMS residual";
501 
502  // save to member data
503  currTime = T;
504  SatelliteIDs = Sats;
505  dataGNSS = currGNSS;
506  invMeasCov = iMC;
507  Partials = P;
508  NIterations = n_iterate;
509  Convergence = converge;
510  Valid = true;
511 
512  return iret;
513 
514  } catch(Exception& e) { GNSSTK_RETHROW(e); }
515 
516  } // end PRSolution::SimplePRSolution
517 
518 
519  // -------------------------------------------------------------------------
520  // Compute a RAIM solution with no measurement covariance matrix (no weighting)
521  int PRSolution::RAIMComputeUnweighted(const CommonTime& Tr,
522  vector<SatID>& Sats,
523  const vector<double>& Pseudorange,
524  NavLibrary& eph,
525  TropModel *pTropModel,
526  NavSearchOrder order)
527  {
528  try {
529  Matrix<double> invMC; // measurement covariance is empty
530  return (RAIMCompute(Tr, Sats, Pseudorange, invMC, eph, pTropModel,
531  order));
532  }
533  catch(Exception& e) {
534  GNSSTK_RETHROW(e);
535  }
536  } // end PRSolution::RAIMComputeUnweighted()
537 
538 
539  // -------------------------------------------------------------------------
540  // Compute a solution using RAIM.
541  int PRSolution::RAIMCompute(const CommonTime& Tr,
542  vector<SatID>& Sats,
543  const vector<double>& Pseudorange,
544  const Matrix<double>& invMC,
545  NavLibrary& eph,
546  TropModel *pTropModel,
547  NavSearchOrder order)
548  {
549  try {
550  // uncomment to turn on DEBUG output to stdout
551  //LOGlevel = ConfigureLOG::Level("DEBUG");
552 
553  LOG(DEBUG) << "RAIMCompute at time " << printTime(Tr,gpsfmt);
554 
555  int iret,N;
556  size_t i,j;
557  vector<int> GoodIndexes;
558  // use these to save the 'best' solution within the loop.
559  // BestRMS marks the 'Best' set as unused.
560  bool BestTropFlag(false);
561  int BestNIter(0),BestIret(-5);
562  double BestRMS(-1.0),BestSL(0.0),BestConv(0.0);
563  Vector<double> BestSol(3,0.0),BestPFR;
564  vector<SatID> BestSats,SaveSats;
565  Matrix<double> SVP,BestCov,BestInvMCov,BestPartials;
566  vector<SatelliteSystem> BestGNSS;
567 
568  // initialize
569  Valid = false;
570  currTime = Tr;
571  TropFlag = SlopeFlag = RMSFlag = false;
572 
573  // ----------------------------------------------------------------
574  // fill the SVP matrix, and use it for every solution
575  // NB this routine will reject sat systems not found in allowedGNSS, and
576  // sats without ephemeris.
577  N = PreparePRSolution(Tr, Sats, Pseudorange, eph, SVP, order);
578 
579  if(LOGlevel >= ConfigureLOG::Level("DEBUG")) {
580  LOG(DEBUG) << "Prepare returns " << N;
581  ostringstream oss;
582  oss << "RAIMCompute: after PrepareAS(): Satellites:";
583  for(i=0; i<Sats.size(); i++) {
584  RinexSatID rs(::abs(Sats[i].id), Sats[i].system);
585  oss << " " << (Sats[i].id < 0 ? "-" : "") << rs;
586  }
587  oss << endl;
588 
589  oss << " SVP matrix("
590  << SVP.rows() << "," << SVP.cols() << ")" << endl;
591  oss << fixed << setw(16) << setprecision(5) << SVP;
592 
593  LOG(DEBUG) << oss.str();
594  }
595 
596  // return is >=0(number of good sats) or -4(no ephemeris)
597  if(N <= 0) return -4;
598 
599  // ----------------------------------------------------------------
600  // Build GoodIndexes based on Sats; save Sats as SaveSats.
601  // Sats is used to mark good sats (id > 0) and those to ignore (id <= 0).
602  // SaveSats saves the original so it can be reused for each RAIM solution.
603  // Let GoodIndexes be all the indexes of Sats that are good:
604  // Sats[GoodIndexes[.]].id > 0 by definition.
605  SaveSats = Sats;
606  for(i=0; i<Sats.size(); i++)
607  if(Sats[i].id > 0)
608  GoodIndexes.push_back(i);
609 
610  // dump good satellites for debug
611  if(LOGlevel >= ConfigureLOG::Level("DEBUG")) {
612  ostringstream oss;
613  oss << " Good satellites (" << N << ") are:";
614  for(i=0; i<GoodIndexes.size(); i++)
615  oss << " " << RinexSatID(Sats[GoodIndexes[i]]);
616  LOG(DEBUG) << oss.str();
617  }
618 
619  // ----------------------------------------------------------------
620  // now compute the solution, first with all the data. If this fails,
621  // RAIM: reject 1 satellite at a time and try again, then 2, etc.
622 
623  // Slopes for each satellite are computed (cf. the RAIM algorithm)
624  Vector<double> Slopes;
625  // Resids stores the post-fit data residuals.
626  Vector<double> Resids;
627 
628  // stage is the number of satellites to reject.
629  int stage(0);
630 
631  do {
632  // compute all the combinations of N satellites taken stage at a time
633  Combinations Combo(N,stage);
634 
635  // compute a solution for each combination of marked satellites
636  do {
637  // Mark the satellites for this combination
638  Sats = SaveSats;
639  for(i=0; i<GoodIndexes.size(); i++)
640  if(Combo.isSelected(i))
641  Sats[GoodIndexes[i]].id = -::abs(Sats[GoodIndexes[i]].id);
642 
643  if(LOGlevel >= ConfigureLOG::Level("DEBUG")) {
644  ostringstream oss;
645  oss << " RAIM: Try the combo ";
646  for(i=0; i<Sats.size(); i++) {
647  RinexSatID rs(::abs(Sats[i].id), Sats[i].system);
648  oss << " " << (Sats[i].id < 0 ? "-" : " ") << rs;
649  }
650  LOG(DEBUG) << oss.str();
651  }
652 
653  // ----------------------------------------------------------------
654  // Compute a solution given the data; ignore ranges for marked
655  // satellites. Fill Vector 'Slopes' with slopes for each unmarked
656  // satellite.
657  // Return 0 ok
658  // -1 failed to converge
659  // -2 singular problem
660  // -3 not enough good data
661  // -4 no ephemeris
662  iret = SimplePRSolution(Tr, Sats, SVP, invMC, pTropModel,
663  MaxNIterations, ConvergenceLimit, Resids, Slopes);
664 
665  LOG(DEBUG) << " RAIM: SimplePRS returns " << iret;
666  if(iret <= 0 && iret > BestIret) BestIret = iret;
667 
668  // ----------------------------------------------------------------
669  // if error, either quit or continue with next combo (SPS sets Valid F)
670  if(iret < 0) {
671  if(iret == -1) {
672  LOG(DEBUG) << " SPS: Failed to converge - go on";
673  continue;
674  }
675  else if(iret == -2) {
676  LOG(DEBUG) << " SPS: singular - go on";
677  continue;
678  }
679  else if(iret == -3) {
680  LOG(DEBUG) <<" SPS: not enough satellites: quit";
681  break;
682  }
683  else if(iret == -4) {
684  LOG(DEBUG) <<" SPS: no ephemeris: quit";
685  break;
686  }
687  }
688 
689  // ----------------------------------------------------------------
690  // print solution with diagnostic information
691  LOG(DEBUG) << outputString(string("RPS"),iret);
692 
693  // do again for residuals
694  // if memory exists, output residuals
695  //if(hasMemory) LOG(DEBUG) << outputString(string("RAP"), -99,
696  //(Solution-memory.APSolution));
697 
698  // deal with the results of SimplePRSolution()
699  // save 'best' solution for later
700  if(BestRMS < 0.0 || RMSResidual < BestRMS) {
701  BestRMS = RMSResidual;
702  BestSol = Solution;
703  BestSats = SatelliteIDs;
704  BestGNSS = dataGNSS;
705  BestSL = MaxSlope;
706  BestConv = Convergence;
707  BestNIter = NIterations;
708  BestCov = Covariance;
709  BestInvMCov = invMeasCov;
710  BestPartials = Partials;
711  BestPFR = PreFitResidual;
712  BestTropFlag = TropFlag;
713  BestIret = iret;
714  }
715 
716  if(stage==0 && RMSResidual < RMSLimit)
717  break;
718 
719  } while(Combo.Next() != -1); // get the next combinations and repeat
720 
721  // end of the stage
722  if(BestRMS > 0.0 && BestRMS < RMSLimit) { // success
723  LOG(DEBUG) << " RAIM: Success in the RAIM loop";
724  iret = 0;
725  break;
726  }
727 
728  // go to next stage
729  stage++;
730 
731  // but not if too many are being rejected
732  if(NSatsReject > -1 && stage > NSatsReject) {
733  LOG(DEBUG) << " RAIM: break before stage " << stage
734  << " due to NSatsReject " << NSatsReject;
735  break;
736  }
737 
738  // already broke out of the combo loop...
739  if(iret == -3 || iret == -4) {
740  LOG(DEBUG) << " RAIM: break before stage " << stage
741  << "; " << (iret==-3 ? "too few sats" : "no ephemeris");
742  break;
743  }
744 
745  LOG(DEBUG) << " RAIM: go to stage " << stage;
746 
747  } while(1); // end loop over stages
748 
749  // ----------------------------------------------------------------
750  // copy out the best solution
751  if(iret >= 0) {
752  Sats = SatelliteIDs = BestSats;
753  dataGNSS = BestGNSS;
754  Solution = BestSol;
755  Covariance = BestCov;
756  invMeasCov = BestInvMCov;
757  Partials = BestPartials;
758  PreFitResidual = BestPFR;
759  Convergence = BestConv;
760  NIterations = BestNIter;
761  RMSResidual = BestRMS;
762  MaxSlope = BestSL;
763  TropFlag = BestTropFlag;
764  iret = BestIret;
765 
766  // must add zeros to state, covariance and partials if these don't match
767  if(dataGNSS.size() != BestGNSS.size()) {
768  N = 3+dataGNSS.size();
769  Solution = Vector<double>(N,0.0);
770  Covariance = Matrix<double>(N,N,0.0);
771  Partials = Matrix<double>(BestPartials.rows(),N,0.0);
772 
773  // build a little map of indexes; note systems are sorted alike
774  vector<int> indexes;
775  for(j=0,i=0; i<dataGNSS.size(); i++) {
776  indexes.push_back(dataGNSS[i] == BestGNSS[j] ? j++ : -1);
777  }
778 
779  // copy over position and position,clk parts
780  for(i=0; i<3; i++) {
781  Solution(i) = BestSol(i);
782  for(j=0; j<Partials.rows(); j++) Partials(j,i) = BestPartials(j,i);
783  for(j=0; j<3; j++) Covariance(i,j) = BestCov(i,j);
784  for(j=0; j<indexes.size(); j++) {
785  Covariance(i,3+j)=(indexes[j]==-1 ? 0.:BestCov(i,3+indexes[j]));
786  Covariance(3+j,i)=(indexes[j]==-1 ? 0.:BestCov(3+indexes[j],i));
787  }
788  }
789 
790  // copy the clock part, inserting zeros where needed
791  for(j=0; j<indexes.size(); j++) {
792  int n(indexes[j]);
793  bool ok(n != -1);
794  Solution(3+j) = (ok ? BestSol(3+n) : 0.0);
795  for(i=0; i<Partials.rows(); i++)
796  Partials(i,3+j) = (ok ? BestPartials(i,3+n) : 0.0);
797  for(i=0; i<indexes.size(); i++) {
798  Covariance(3+i,3+j) = (ok ? BestCov(3+i,3+n) : 0.0);
799  Covariance(3+j,3+i) = (ok ? BestCov(3+n,3+i) : 0.0);
800  }
801  }
802  }
803 
804  // compute number of satellites actually used
805  for(Nsvs=0,i=0; i<SatelliteIDs.size(); i++)
806  if(SatelliteIDs[i].id > 0)
807  Nsvs++;
808 
809  if(iret==0)
810  DOPCompute(); // compute DOPs
811  if(hasMemory && iret==0) {
812  // update memory solution
813  addToMemory(Solution,Covariance,PreFitResidual,Partials,invMeasCov);
814  // update apriori solution
815  updateAPSolution(Solution);
816  }
817 
818  }
819 
820  // ----------------------------------------------------------------
821  if(iret==0) {
822  if(BestSL > SlopeLimit) { iret = 1; SlopeFlag = true; }
823  if(BestSL > SlopeLimit/2.0 && Nsvs == 5) { iret = 1; SlopeFlag = true; }
824  if(BestRMS >= RMSLimit) { iret = 1; RMSFlag = true; }
825  if(TropFlag) iret = 1;
826  Valid = true;
827  }
828  else if(iret == -1) Valid = false;
829 
830  LOG(DEBUG) << " RAIM exit with ret value " << iret
831  << " and Valid " << (Valid ? "T":"F");
832 
833  return iret;
834  }
835  catch(Exception& e) {
836  GNSSTK_RETHROW(e);
837  }
838  } // end PRSolution::RAIMCompute()
839 
840 
841  // -------------------------------------------------------------------------
842  int PRSolution::DOPCompute(void)
843  {
844  try {
845  Matrix<double> PTP(transpose(Partials)*Partials);
846  Matrix<double> Cov(inverseLUD(PTP));
847  PDOP = SQRT(Cov(0,0)+Cov(1,1)+Cov(2,2));
848  TDOP = 0.0;
849  for(size_t i=3; i<Cov.rows(); i++) TDOP += Cov(i,i);
850  TDOP = SQRT(TDOP);
851  GDOP = RSS(PDOP,TDOP);
852  return 0;
853  }
854  catch(Exception& e) { GNSSTK_RETHROW(e); }
855  }
856 
857  // -------------------------------------------------------------------------
858  // conveniences for printing the solutions
859  string PRSolution::outputValidString(int iret)
860  {
861  ostringstream oss;
862  if(iret != -99) {
863  oss << " (" << iret << " " << errorCodeString(iret);
864  if(iret==1) {
865  oss << " due to";
866  if(RMSFlag) oss << " large RMS residual";
867  if(SlopeFlag) oss << " large slope";
868  if(TropFlag) oss << " missed trop. corr.";
869  }
870  oss << ") " << (Valid ? "" : "N") << "V";
871  }
872  return oss.str();
873  } // end PRSolution::outputValidString
874 
875  string PRSolution::outputNAVString(string tag, int iret, const Vector<double>& Vec)
876  {
877  ostringstream oss;
878 
879  // output header describing regular output
880  if(iret==-999) {
881  oss << printTime(currTime,gpsfmt);
882  int len = oss.str().size();
883  oss.str("");
884  oss << "#" << tag << " NAV " << setw(len) << "time"
885  << " " << setw(18) << "Sol/Resid:X(m)"
886  << " " << setw(18) << "Sol/Resid:Y(m)"
887  << " " << setw(18) << "Sol/Resid:Z(m)"
888  << " " << setw(18) << "sys clock" << " [sys clock ...] Valid/Not";
889  return oss.str();
890  }
891 
892  // tag NAV timetag X Y Z clks endtag
893  oss << tag << " NAV " << printTime(currTime,gpsfmt)
894  << fixed << setprecision(6)
895  << " " << setw(16) << (&Vec==&PRSNullVector ? Solution(0) : Vec(0))
896  << " " << setw(16) << (&Vec==&PRSNullVector ? Solution(1) : Vec(1))
897  << " " << setw(16) << (&Vec==&PRSNullVector ? Solution(2) : Vec(2))
898  << fixed << setprecision(3);
899  for(size_t i=0; i<dataGNSS.size(); i++) {
900  RinexSatID sat(1,dataGNSS[i]);
901  oss << " " << sat.systemString3() << " " << setw(11) << Solution(3+i);
902  }
903  oss << outputValidString(iret);
904 
905  return oss.str();
906  } // end PRSolution::outputNAVString
907 
908  string PRSolution::outputPOSString(string tag, int iret, const Vector<double>& Vec)
909  {
910  ostringstream oss;
911 
912  // output header describing regular output
913  if(iret==-999) {
914  oss << printTime(currTime,gpsfmt);
915  int len = oss.str().size();
916  if(len > 3) len -= 3;
917  oss.str("");
918  oss << "#" << tag << " POS " << setw(len) << "time"
919  << " " << setw(16) << "Sol-X(m)"
920  << " " << setw(16) << "Sol-Y(m)"
921  << " " << setw(16) << "Sol-Z(m)"
922  << " (ret code) Valid/Not";
923  return oss.str();
924  }
925 
926  // tag POS timetag X Y Z endtag
927  oss << tag << " POS " << printTime(currTime,gpsfmt)
928  << fixed << setprecision(6)
929  << " " << setw(16) << (&Vec==&PRSNullVector ? Solution(0) : Vec(0))
930  << " " << setw(16) << (&Vec==&PRSNullVector ? Solution(1) : Vec(1))
931  << " " << setw(16) << (&Vec==&PRSNullVector ? Solution(2) : Vec(2))
932  << outputValidString(iret);
933 
934  return oss.str();
935  } // end PRSolution::outputPOSString
936 
937  string PRSolution::outputCLKString(string tag, int iret)
938  {
939  ostringstream oss;
940 
941  // output header describing regular output
942  if(iret==-999) {
943  oss << printTime(currTime,gpsfmt);
944  int len = oss.str().size();
945  if(len > 3) len -= 3;
946  oss.str("");
947  oss << "#" << tag << " CLK " << setw(len) << "time"
948  << " sys " << setw(11) << "clock" << " ...";
949  return oss.str();
950  }
951 
952  // tag CLK timetag SYS clk [SYS clk SYS clk ...] endtag
953  oss << tag << " CLK " << printTime(currTime,gpsfmt)
954  << fixed << setprecision(3);
955  //for(size_t i=0; i<SystemIDs.size(); i++) {
956  // RinexSatID sat(1,SystemIDs[i]);
957  for(size_t i=0; i<dataGNSS.size(); i++) {
958  RinexSatID sat(1,dataGNSS[i]);
959  oss << " " << sat.systemString3() << " " << setw(11) << Solution(3+i);
960  }
961  oss << outputValidString(iret);
962 
963  return oss.str();
964  } // end PRSolution::outputCLKString
965 
966  // NB must call DOPCompute() if SimplePRSol() only was called.
967  string PRSolution::outputRMSString(string tag, int iret)
968  {
969  ostringstream oss;
970 
971  // output header describing regular output
972  if(iret==-999) {
973  oss << printTime(currTime,gpsfmt);
974  int len = oss.str().size();
975  if(len > 3) len -= 3;
976  oss.str("");
977  oss << "#" << tag << " RMS " << setw(len) << "time"
978  << " " << setw(2) << "Ngood"
979  << " " << setw(8) << "resid"
980  << " " << setw(7) << "TDOP"
981  << " " << setw(7) << "PDOP"
982  << " " << setw(7) << "GDOP"
983  << " " << setw(5) << "Slope"
984  << " " << setw(2) << "nit"
985  << " " << setw(8) << "converge"
986  << " sats(-rej)... (ret code) Valid/Not";
987  return oss.str();
988  }
989 
990  // remove duplicates from satellite list, and find "any good data" ones
991  // this gets tricky since there may be >1 datum from one satellite
992  // in the following, good means 1+ good data exist; bad means all data bad
993  size_t i;
994  vector<RinexSatID> sats,goodsats;
995  for(i=0; i<SatelliteIDs.size(); i++) {
996  RinexSatID rs(::abs(SatelliteIDs[i].id), SatelliteIDs[i].system);
997  if(find(sats.begin(),sats.end(),rs) == sats.end())
998  sats.push_back(rs); // all satellites
999  if(SatelliteIDs[i].id > 0) {
1000  if(find(goodsats.begin(),goodsats.end(),rs) == goodsats.end())
1001  goodsats.push_back(rs); // good satellites
1002  }
1003  }
1004 
1005  //# tag RMS week sec.of.wk nsat rmsres tdop pdop gdop slope it
1006  // converg sats... (return) [N]V"
1007  oss << tag << " RMS " << printTime(currTime,gpsfmt)
1008  << " " << setw(2) << goodsats.size()
1009  << fixed << setprecision(3)
1010  << " " << setw(8) << RMSResidual
1011  << setprecision(2)
1012  << " " << setw(7) << TDOP
1013  << " " << setw(7) << PDOP
1014  << " " << setw(7) << GDOP
1015  << setprecision(1)
1016  << " " << setw(5) << MaxSlope
1017  << " " << setw(2) << NIterations
1018  << scientific << setprecision(2)
1019  << " " << setw(8) << Convergence;
1020  for(size_t i=0; i<sats.size(); i++) {
1021  if(find(goodsats.begin(),goodsats.end(),sats[i]) != goodsats.end())
1022  oss << " " << sats[i];
1023  else
1024  oss << " -" << sats[i];
1025  }
1026  oss << outputValidString(iret);
1027 
1028  return oss.str();
1029  } // end PRSolution::outputRMSString
1030 
1031  string PRSolution::outputString(string tag, int iret, const Vector<double>& Vec)
1032  {
1033  ostringstream oss;
1034  //oss << outputPOSString(tag,iret) << endl; // repeated in NAV
1035  oss << outputNAVString(tag,iret,Vec) << endl;
1036  oss << outputRMSString(tag,iret);
1037 
1038  return oss.str();
1039  } // end PRSolution::outputString
1040 
1041  string PRSolution::errorCodeString(int iret)
1042  {
1043  string str("unknown");
1044  if(iret == 1) str = string("ok but perhaps degraded");
1045  else if(iret== 0) str = string("ok");
1046  else if(iret==-1) str = string("failed to converge");
1047  else if(iret==-2) str = string("singular solution");
1048  else if(iret==-3) str = string("not enough satellites");
1049  else if(iret==-4) str = string("not any ephemeris");
1050  return str;
1051  }
1052 
1053  string PRSolution::configString(string tag)
1054  {
1055  ostringstream oss;
1056  oss << tag
1057  << "\n iterations " << MaxNIterations
1058  << "\n convergence " << scientific << setprecision(2) << ConvergenceLimit
1059  << "\n RMS residual limit " << fixed << RMSLimit
1060  << "\n RAIM slope limit " << fixed << SlopeLimit << " meters"
1061  << "\n Maximum number of satellites to reject is " << NSatsReject
1062  << "\n Memory information IS " << (hasMemory ? "":"NOT ") << "stored"
1063  ;
1064 
1065  // output memory information
1066  //if(APrioriSol.size() >= 4) oss
1067  // << "\n APriori is " << fixed << setprecision(3) << APrioriSol(0)
1068  // << " " << APrioriSol(1) << " " << APrioriSol(2) << " " << APrioriSol(3);
1069  //else oss << "\n APriori is undefined";
1070 
1071  return oss.str();
1072  }
1073 
1074  const Vector<double> PRSolution::PRSNullVector;
1075 
1076 } // namespace gnsstk
gnsstk::TropModel::correction
virtual double correction(double elevation) const
Definition: TropModel.cpp:53
gnsstk::inverseLUD
Matrix< T > inverseLUD(const ConstMatrixBase< T, BaseClass > &m)
Definition: MatrixOperators.hpp:591
gnsstk::GalDataValid::Valid
@ Valid
Navigation data valid.
SQRT
#define SQRT(x)
Definition: MathBase.hpp:74
PRSolution.hpp
gnsstk::GPSEllipsoid::angVelocity
virtual double angVelocity() const noexcept
Definition: GPSEllipsoid.hpp:72
gnsstk::RinexSatID::systemString3
std::string systemString3() const noexcept
Definition: RinexSatID.cpp:102
Combinations.hpp
gnsstk::Combinations
Definition: Combinations.hpp:58
gnsstk::NavSatelliteID
Definition: NavSatelliteID.hpp:57
gnsstk::SatelliteSystem
SatelliteSystem
Supported satellite systems.
Definition: SatelliteSystem.hpp:55
gnsstk::NavLibrary::getXvt
bool getXvt(const NavSatelliteID &sat, const CommonTime &when, Xvt &xvt, bool useAlm, SVHealth xmitHealth=SVHealth::Any, NavValidityType valid=NavValidityType::ValidOnly, NavSearchOrder order=NavSearchOrder::User)
Definition: NavLibrary.cpp:52
logstream.hpp
gnsstk::Matrix::cols
size_t cols() const
The number of columns in the matrix.
Definition: Matrix.hpp:167
gnsstk::TropModel
Definition: TropModel.hpp:105
gnsstk::GPSEllipsoid
Definition: GPSEllipsoid.hpp:67
gnsstk::Matrix::rows
size_t rows() const
The number of rows in the matrix.
Definition: Matrix.hpp:165
gnsstk::inverseSVD
Matrix< T > inverseSVD(const ConstMatrixBase< T, BaseClass > &m, const T tol=T(1.e-8))
Definition: MatrixOperators.hpp:652
gnsstk::NavSearchOrder
NavSearchOrder
Specify the behavior of nav data searches in NavLibrary/NavDataFactory.
Definition: NavSearchOrder.hpp:51
gnsstk::Triple
Definition: Triple.hpp:68
gnsstk
For Sinex::InputHistory.
Definition: BasicFramework.cpp:50
gnsstk::GlobalTropModel::getHeightLimit
double getHeightLimit()
Get the height limit for this model, in meters.
Definition: GlobalTropModel.hpp:268
LOGlevel
#define LOGlevel
Definition: logstream.hpp:323
std::sin
double sin(gnsstk::Angle x)
Definition: Angle.hpp:144
gnsstk::Xvt::relcorr
double relcorr
relativity correction (standard 2R.V/c^2 term), seconds
Definition: Xvt.hpp:155
gnsstk::Vector::resize
Vector & resize(const size_t index)
Definition: Vector.hpp:262
gnsstk::WtdAveStats::dump
void dump(std::ostream &os, std::string msg="") const
Definition: PRSolution.hpp:147
gnsstk::Combinations::Next
int Next(void) noexcept
Definition: Combinations.hpp:90
gnsstk::Exception
Definition: Exception.hpp:151
gnsstk::Position::setECEF
Position & setECEF(const double X, const double Y, const double Z) noexcept
Definition: Position.cpp:601
gnsstk::Exception::getText
std::string getText(const size_t &index=0) const
Definition: Exception.cpp:139
gnsstk::Xvt::x
Triple x
Sat position ECEF Cartesian (X,Y,Z) meters.
Definition: Xvt.hpp:151
gnsstk::Position::getHeight
double getHeight() const noexcept
return height above ellipsoid (meters)
Definition: Position.hpp:474
example4.RMSLimit
RMSLimit
Definition: example4.py:95
gnsstk::GPSEllipsoid::c
virtual double c() const noexcept
Definition: GPSEllipsoid.hpp:87
gnsstk::transpose
SparseMatrix< T > transpose(const SparseMatrix< T > &M)
transpose
Definition: SparseMatrix.hpp:829
gnsstk::NavLibrary
Definition: NavLibrary.hpp:944
gnsstk::Matrix< double >
gnsstk::GlobalTropModel
Definition: GlobalTropModel.hpp:118
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::ERROR
@ ERROR
Definition: logstream.hpp:57
gnsstk::CommonTime
Definition: CommonTime.hpp:84
gnsstk::Xvt
Definition: Xvt.hpp:60
std::cos
double cos(gnsstk::Angle x)
Definition: Angle.hpp:146
gnsstk::RSS
T RSS(T aa, T bb, T cc)
Perform the root sum square of aa, bb and cc.
Definition: MiscMath.hpp:246
GNSSTK_RETHROW
#define GNSSTK_RETHROW(exc)
Definition: Exception.hpp:369
gnsstk::Vector< double >
gnsstk::TrackingCode::P
@ P
Legacy GPS precise code.
GPSEllipsoid.hpp
std::operator<<
std::ostream & operator<<(std::ostream &s, gnsstk::StringUtils::FFLead v)
Definition: FormattedDouble_T.cpp:44
gnsstk::WtdAveStats::getMessage
std::string getMessage(void) const
Definition: PRSolution.hpp:88
GNSSTK_ASSERT
#define GNSSTK_ASSERT(CONDITION)
Provide an "ASSERT" type macro.
Definition: Exception.hpp:373
LOG
#define LOG(level)
define the macro that is used to write to the log stream
Definition: logstream.hpp:315
gnsstk::WtdAveStats
Definition: PRSolution.hpp:67
gnsstk::printTime
std::string printTime(const CommonTime &t, const std::string &fmt)
Definition: TimeString.cpp:64
gnsstk::Vector::size
size_t size() const
STL size.
Definition: Vector.hpp:207
gnsstk::RinexSatID
Definition: RinexSatID.hpp:63
std
Definition: Angle.hpp:142
gnsstk::Position
Definition: Position.hpp:136
GNSSTK_THROW
#define GNSSTK_THROW(exc)
Definition: Exception.hpp:366
gnsstk::norm
T norm(const SparseVector< T > &SV)
Definition: SparseVector.hpp:705
gnsstk::DEBUG
@ DEBUG
Definition: logstream.hpp:57
gnsstk::Xvt::clkbias
double clkbias
Sat clock correction in seconds.
Definition: Xvt.hpp:153
GlobalTropModel.hpp
MathBase.hpp
gnsstk::RMS
T RMS(const ConstVectorBase< T, BaseClass > &l)
Definition: VectorOperators.hpp:197
gnsstk::Combinations::isSelected
bool isSelected(int j) noexcept
Definition: Combinations.hpp:106
gnsstk::Position::elevation
double elevation(const Position &Target) const
Definition: Position.cpp:1308
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