1 //==============================================================================
2 //
3 // This file is part of GPSTk, the GPS Toolkit.
4 //
5 // The GPSTk 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 GPSTk 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 GPSTk; 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-2020, 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
39 /// @file PRSolve.cpp
40 /// Read Rinex observation files (version 2 or 3) and ephemeris store, and compute a
41 /// a pseudorange-only position solution.
42
43 // system
44 #include <string>
45 #include <vector>
46 #include <map>
47 #include <iostream>
48 #include <fstream>
49 #include <algorithm>
50
51 // GPSTK
52 #include "Exception.hpp"
53 #include "MathBase.hpp"
54 #include "GNSSconstants.hpp"
55 #include "GNSSconstants.hpp"
56
57 #include "singleton.hpp"
58 #include "expandtilde.hpp"
59 #include "logstream.hpp"
60 #include "CommandLine.hpp"
61
62 #include "CommonTime.hpp"
63 #include "Epoch.hpp"
64 #include "TimeString.hpp"
65 #include "GPSWeekSecond.hpp"
66 #include "ObsID.hpp"
67
68 #include "RinexSatID.hpp"
69 #include "RinexObsID.hpp"
70
71 #include "Rinex3ObsStream.hpp"
72 #include "Rinex3ObsHeader.hpp"
73 #include "Rinex3ObsData.hpp"
74
75 #include "Rinex3NavBase.hpp"
76 #include "Rinex3NavHeader.hpp"
77 #include "Rinex3NavData.hpp"
78 #include "Rinex3NavStream.hpp"
79
80 #include "RinexMetHeader.hpp"
81 #include "RinexMetData.hpp"
82 #include "RinexMetStream.hpp"
83
84 #include "SP3Header.hpp"
85 #include "SP3Data.hpp"
86 #include "SP3Stream.hpp"
87
88 #include "SP3EphemerisStore.hpp"
89 #include "Rinex3EphemerisStore.hpp"
90
91 #include "Position.hpp"
92 #include "SimpleTropModel.hpp"
93 #include "SaasTropModel.hpp"
94 #include "NBTropModel.hpp"
95 #include "GGTropModel.hpp"
96 #include "GGHeightTropModel.hpp"
97 #include "NeillTropModel.hpp"
98 #include "GlobalTropModel.hpp"
99
100 #include "EphemerisRange.hpp"
101 #include "RinexUtilities.hpp"
102
103 #include "Stats.hpp"
104 #include "Namelist.hpp"
105 #include "HelmertTransform.hpp"
106
107 #include "PRSolution.hpp"
108
109 //------------------------------------------------------------------------------------
110 using namespace std;
111 using namespace gpstk;
112 using namespace gpstk::StringUtils;
113
114 //------------------------------------------------------------------------------------
115 string Version(string("5.3 1/27/20"));
116
117 // forward declarations
118 class SolutionObject;
119
120 //------------------------------------------------------------------------------------
121 // Object for command line input and global data
122 class Configuration : public Singleton<Configuration> {
123
124 public:
125
126 // Default and only constructor
Configuration()127 Configuration() throw() { SetDefaults(); }
~Configuration()128 virtual ~Configuration()
129 {
130 delete pTrop;
131 }
132
133 // Create, parse and process command line options and user input
134 int ProcessUserInput(int argc, char **argv) throw();
135
136 // Create and output help message for --sol
137 void SolDescHelp(void);
138
139 // Design the command line
140 string BuildCommandLine(void) throw();
141
142 // Open the output file, and parse the strings used on the command line
143 // return -4 if log file could not be opened
144 //int ExtraProcessing(void) throw();
145 //TD on clau, this leads to the SPS algorithm failing to converge on some problems.
146 int ExtraProcessing(string& errors, string& extras) throw();
147
148 /** update weather in the trop model using the Met store
149 * @throw Exception */
150 void setWeather(const CommonTime& ttag);
151
152 private:
153
154 // Define default values
155 void SetDefaults(void) throw();
156
157 public:
158
159 // member data
160 CommandLine opts; // command line options and syntax page
161 static const string PrgmName; // program name
162 string Title; // id line printed to screen and log
163
164 // start command line input
165 bool help, verbose;
166 int debug;
167 string filedummy;
168
169 vector<string> InputObsFiles; // RINEX obs file names
170 vector<string> InputSP3Files; // SP3 ephemeris+clock file names
171 vector<string> InputClkFiles; // RINEX clock file names
172 vector<string> InputNavFiles; // RINEX nav file names
173 vector<string> InputMetFiles; // RINEX met file names
174 vector<string> InputDCBFiles; // differential code bias C1-P1 file names
175
176 string Obspath,SP3path,Clkpath,Navpath,Metpath,DCBpath; // paths
177
178 // times derived from --start and --stop
179 string defaultstartStr,startStr;
180 string defaultstopStr,stopStr;
181 CommonTime beginTime,endTime,gpsBeginTime,decTime;
182
183 double decimate; // decimate input data
184 double elevLimit; // limit sats to elevation mask
185 bool forceElev; // use elevLimit even without --ref
186 bool searchUser; // use SearchUser() for BCE, else SearchNear()
187 vector<RinexSatID> exclSat;// exclude satellites
188
189 bool PisY; // Interpret RINEX 2 P code as if the receiver was keyed
190 bool SPSout,ORDout; // output autonomous solutions? ORDs?
191 bool outver2; // output RINEX version 2 (OutputObsFile)
192 string LogFile; // output log file (required)
193 string OutputORDFile; // output ORD file
194 string OutputObsFile; // output RINEX obs file
195 string userfmt; // user's time format for output
196 string refPosStr; // temp used to parse --ref input
197
198 vector<string> inSolDesc; // input: strings sys,freq,code e.g. GPS+GLO,1+2,PC
199 bool SOLhelp; // print more help info
200
201 // config for PRSolution
202 bool weight; // build a measurement covariance if true
203 double RMSLimit; // Upper limit on RMS post-fit residual (m)
204 double SlopeLimit; // Upper limit on RAIM 'slope'
205 int maxReject; // Max number of sats to reject [-1 for no limit]
206 int nIter; // Maximum iteration count in linearized LS
207 double convLimit; // Minimum convergence criterion in estimation (meters)
208
209 string TropStr; // temp used to parse --trop
210
211 // end of command line input
212
213 // output file streams
214 ofstream logstrm, ordstrm; // for LogFile, OutputORDFile
215
216 // time formats
217 static const string calfmt, gpsfmt, longfmt;
218
219 // stores
220 XvtStore<SatID> *pEph;
221 SP3EphemerisStore SP3EphStore;
222 Rinex3EphemerisStore RinEphStore;
223 list<RinexMetData> MetStore;
224 map<RinexSatID,double> P1C1bias;
225 map<RinexSatID,int> GLOfreqChannel;
226 int PZ90ITRFold, PZ90WGS84old; // Helmert transforms before 20 Sept 07
227 int PZ90ITRF, PZ90WGS84; // Helmert transforms after 20 Sept 07
228
229 // trop models
230 TropModel *pTrop; // to pass to PRS
231 string TropType; // key ~ Black, NewB, etc; use to identify model
232 bool TropPos,TropTime; // true when trop model has been init with Pos,time
233 // default weather
234 double defaultTemp,defaultPress,defaultHumid;
235
236 // solutions to build
237 vector<SolutionObject> SolObjs; // solution objects to process
238
239 // reference position and rotation matrix
240 Position knownPos; // position derived from --ref
241 Matrix<double> Rot; // Rotation matrix (R*XYZ=NEU) :
242
243 // useful stuff
244 string msg; // temp used everywhere
245 // vector of 1-char strings containing systems needed in all solutions: G,R,E,C,S,J
246 vector<string> allSystemChars;
247
248 string PrgmDesc, cmdlineUsage, cmdlineErrors, cmdlineExtras;
249 vector<string> cmdlineUnrecognized;
250
251 }; // end class Configuration
252
253 //------------------------------------------------------------------------------------
254 // const members of Configuration
255 const string Configuration::PrgmName = string("PRSolve");
256 const string Configuration::calfmt = string("%04Y/%02m/%02d %02H:%02M:%02S");
257 const string Configuration::gpsfmt = string("%4F %10.3g");
258 const string Configuration::longfmt = calfmt + " = %4F %w %10.3g %P";
259
260 //------------------------------------------------------------------------------------
261 //------------------------------------------------------------------------------------
262 // Encapsulate one observation datum that will be input to PRSolution, including a
263 // string that comes from the solution descriptor, constants in the linear combination
264 // and RinexObsID observation types matching those available in RINEX header.
265 // e.g. GPS:1:PC => "G1PC" => 1.0 * GC1P or GC1C
266 // GPS:12:PC => "G12PC" => a+1/a * GC1P/C , -1/a * GC2P/C a=alpha(G,12)
267 // SolutionObject contains a vector of these:
268 // e.g. GPS:12:PC+GLO:12:PC => "G12PC" + "R12PC" =>
269 // (a+1/a * GC1P/C, -1/a * GC2P/C) + (a+1/a * RC1P/C, -1/a * RC2P/C)
270 // Used within SolutionObject only; assumes valid input everywhere!
271 class SolutionData {
272 public:
273 // short string version of descriptor, e.g. GPS:12:PC => G12PC
274 string sfcodes;
275 // vector of constants in linear combination, parallel to obsids
276 // probably (a+1)/a and -1/a where a is alpha(freq1,freq2)
277 vector<double> consts;
278 // vector of RinexObsIDs in linear combination, parallel to consts
279 vector<vector<string> > obsids;
280 // vector of indexes into the RinexObsData map for each obsids
281 vector<vector<int> > indexes;
282
283 // For use in ComputeData()
284 // ObsIDs actually used - parallel to consts - passed to DAT output
285 vector<string> usedobsids;
286 // raw pseudoranges
287 vector<double> RawPR;
288 // computed pseudorange and iono delay
289 double PR, RI;
290
291 // default and only constructor; input must NOT have + but may have dual freq
SolutionData(const string & desc)292 SolutionData(const string& desc)
293 {
294 vector<string> fields = split(desc,':');
295 sfcodes = RinexObsID::map3to1sys[fields[0]]; // first char of sfcodes
296 // const char csys(sfcodes[0]);
297 sfcodes += fields[1]; // 1 or 2 freq chars
298 sfcodes += fields[2];
299 }
300
301 // Destructor
~SolutionData()302 ~SolutionData() {}
303
304 // get the system as 1-char string
getSys(void)305 string getSys(void) { return string(1,sfcodes[0]); }
306
307 // get the freqs as string
getFreq(void)308 string getFreq(void)
309 {
310 if(isDigitString(sfcodes.substr(1,2)))
311 return sfcodes.substr(1,2);
312 return sfcodes.substr(1,1);
313 }
314
315 // get codes
getCodes(void)316 string getCodes(void)
317 {
318 if(isDigitString(sfcodes.substr(1,2)))
319 return sfcodes.substr(3);
320 return sfcodes.substr(2);
321 }
322
323 // dump
asString(void)324 string asString(void)
325 {
326 size_t i,j;
327 ostringstream oss;
328 oss << "(" << sfcodes << ")";
329 oss << " " << RinexObsID::map1to3sys[sfcodes.substr(0,1)];
330 for(i=0; i<consts.size(); i++) {
331 oss << " [c=" << fixed << setprecision(3) << consts[i];
332 for(j=0; j<obsids[i].size(); j++)
333 oss << (j==0 ? " o=":",") << obsids[i][j];
334 oss << "]";
335 }
336 return oss.str();
337 }
338
339 // define the consts and obsids vectors, given the obstype map from RINEX header
ChooseObsIDs(map<string,vector<RinexObsID>> & mapObsTypes)340 bool ChooseObsIDs(map<string,vector<RinexObsID> >& mapObsTypes)
341 {
342 size_t i,j,k;
343 string sys1=getSys();
344 string frs=getFreq();
345 string codes=getCodes();
346
347 for(i=0; i<frs.size(); i++) // loop over frequencies
348 {
349 // add place holders now
350 consts.push_back(1.0);
351 vector<string> vs;
352 obsids.push_back(vs);
353 vector<int> vi;
354 indexes.push_back(vi);
355
356 for(j=0; j<codes.size(); j++)
357 { // loop over codes
358 // the desired ObsID
359 string obsid = string("C") + string(1,frs[i]) + string(1,codes[j]);
360
361 // now loop over available RinexObsTypes : map<1-char sys, string RObsID>
362 map<string,vector<RinexObsID> >::const_iterator it;
363 for(it=mapObsTypes.begin(); it != mapObsTypes.end(); ++it)
364 {
365 // wrong GNSS system
366 if(it->first != sys1)
367 continue;
368
369 // loop over obs types
370 const vector<RinexObsID>& vecROID(it->second);
371 for(k=0; k<vecROID.size(); k++)
372 {
373 if(vecROID[k].asString() == obsid)
374 {
375 obsids[i].push_back(obsid);
376 indexes[i].push_back(k);
377 }
378 }
379 } // end loop over RINEX obs types in header
380 } // end loop over codes
381 } // end loop over freqs
382
383 // no obs ids found, for either frequency
384 if(obsids[0].size() == 0 || (obsids.size() > 1 && obsids[1].size() == 0))
385 return false;
386
387 // compute constants
388 if(obsids.size() > 1) {
389 int na(asInt(frs.substr(0,1)));
390 int nb(asInt(frs.substr(1,1)));
391 RinexSatID sat(sys1);
392 double alpha = getAlpha(sat.system,na,nb);
393 if(alpha == 0.0) return false;
394 consts[1] = -1.0/alpha;
395 consts[0] = 1.0 - consts[1];
396 }
397
398 return true;
399 } // end ChooseObsIDs()
400
401 // compute the actual datum, for the given satellite, given the RinexObsData vector
402 // remember which ObsIDs were actually used, in usedobsids
403 // return true if the data could be computed
ComputeData(const RinexSatID & sat,const vector<RinexDatum> & vrd)404 bool ComputeData(const RinexSatID& sat, const vector<RinexDatum>& vrd)
405 {
406 usedobsids.clear();
407 RawPR.clear();
408 PR = RI = 0.0;
409
410 string sys1=getSys();
411 if(sys1[0] != sat.systemChar()) // wrong system
412 return false;
413
414 string frs=getFreq();
415 for(size_t i=0; i<frs.size(); i++) { // loop over frequencies
416 RawPR.push_back(0.0); // placeholder = 0 == missing
417 usedobsids.push_back(string("---")); // placeholder == none
418 for(size_t j=0; j<indexes[i].size(); j++) {// loop over codes (RINEX indexes)
419 int k = indexes[i][j];
420 if(vrd[k].data == 0.0) // data is no good
421 continue;
422 usedobsids[i] = obsids[i][j]; // use this ObsID
423 RawPR[i] = vrd[k].data; // use this data
424 PR += RawPR[i] * consts[i]; // compute (dual-freq) PR
425 break;
426 }
427 }
428
429 // missing data?
430 if(RawPR[0]==0.0 || (frs.size()>1 && RawPR[1]==0.0)) return false;
431
432 // iono delay
433 if(consts.size() > 1) RI = consts[1]*(RawPR[0] - RawPR[1]);
434
435 return true;
436 } // end ComputeData()
437
438 // compute and return a string of the form fc[fc] (freq code freq code) giving
439 // the frequency and code of the data actually used by ComputeData. If no data
440 // was available, then use '-' for the code.
usedString(void)441 string usedString(void)
442 {
443 string msg;
444 string frs = getFreq();
445 for(size_t i=0; i<frs.size(); i++) {
446 msg += frs[i];
447 msg += (usedobsids.size() > i ? usedobsids[i].substr(2,1) : "?");
448 }
449 return msg;
450 }
451
452 }; // end class SolutionData
453
454 //------------------------------------------------------------------------------------
455 //------------------------------------------------------------------------------------
456 // Object to encapsulate everything for one solution (system:freq:code[+s:f:c])
457 class SolutionObject {
458 public:
459 // member functions
460 // Default and only constructor
SolutionObject(const string & desc)461 SolutionObject(const string& desc) throw() { Initialize(desc); }
462
463 // Destructor
~SolutionObject()464 ~SolutionObject() throw() { }
465
466 // static function to determine consistency of input descriptor
467 // msg returns explanation
468 static bool ValidateDescriptor(string desc, string& msg);
469
470 // check validity of input descriptor, set default values
Initialize(const string & desc)471 void Initialize(const string& desc) throw()
472 {
473 if(!ValidateDescriptor(desc, Descriptor)) {
474 isValid = false;
475 return;
476 }
477 isValid = true;
478
479 // parse desc into systems, freqs, codes, etc
480 Descriptor = desc;
481 // this also specifies systems in PRSolution::allowedGNSS
482 ParseDescriptor();
483
484 nepochs = 0;
485
486 // for initialization of constants and PRSolution
487 Configuration& C(Configuration::Instance());
488
489 // set defaults in PRSolution
490 prs.RMSLimit = C.RMSLimit;
491 prs.SlopeLimit = C.SlopeLimit;
492 prs.NSatsReject = C.maxReject;
493 prs.MaxNIterations = C.nIter;
494 prs.ConvergenceLimit = C.convLimit;
495
496 // initialize apriori solution
497 if(C.knownPos.getCoordinateSystem() != Position::Unknown)
498 prs.fixAPSolution(C.knownPos.X(),C.knownPos.Y(),C.knownPos.Z());
499
500 return;
501 }
502
503 // parse descriptor into member data and 'sysChars', define PRSolution::allowedGNSS
504 void ParseDescriptor(void) throw();
505
506 // Given a RINEX header, verify that the necessary ObsIDs are present, and
507 // define an ordered set of ObsIDs for each component and SolutionData required.
508 // Return true if enough ObsIDs are found to compute the solution.
509 bool ChooseObsIDs(map<string,vector<RinexObsID> >& mapObsTypes) throw();
510
511 // dump. level 0: descriptor and all available obs types
512 // level 1: descriptor and obs types actually used
513 // level 2: level 1 plus pseudorange data
514 // return string containing dump
515 string dump(int level, string msg="SOLN", string msg2="") throw();
516
517 // reset the object before each epoch
518 void EpochReset(void) throw();
519
520 // Given a RINEX data object, pull out the data to be used, and set the flag
521 // indicating whether there is sufficient good data.
522 void CollectData(const RinexSatID& s,
523 const double& elev, const double& ER,
524 const vector<RinexDatum>& v) throw();
525
526 /** Compute a solution for the given epoch; call after
527 * CollectData() same return value as RAIMCompute()
528 * @throw Exception
529 */
530 int ComputeSolution(const CommonTime& t);
531
532 /** Write out ORDs - call after ComputeSolution pass it iret
533 * from ComputeSolution
534 * @throw Exception
535 */
536 int WriteORDs(const CommonTime& t, const int iret);
537
538 /** Output final results
539 * @throw Exception
540 */
541 void FinalOutput(void);
542
543 // member data
544
545 // true unless descriptor is not valid, or required ObsIDs are not available
546 bool isValid;
547
548 // solution descriptor: sys[+sys]:freq[+freq]:codes[+codes]
549 // sys+sys implies codes+codes; codes is string of 'attribute' characters (ObsID)
550 // giving the preferred ObsID (sys/C/freq/attr) to use as the data
551 string Descriptor;
552
553 // vector of SolutionData, one for each data component required in solution (1+).
554 vector<SolutionData> vecSolData;
555
556 // vector of 1-char strings containing systems needed in this solution: G,R,E,C,S,J
557 vector<string> sysChars;
558
559 // data for PR solution algorithm
560 bool hasData; // true if enough data for solution
561 vector<SatID> Satellites; // sats with data
562 vector<double> PRanges; // data, parallel to Satellites
563 vector<double> Elevations; // elevations, parallel to Satellites
564 vector<double> ERanges; // corr eph range, parallel to Satellites
565 vector<double> RIono; // range iono, parallel to Satellites
566 vector<double> R1,R2; // raw ranges, parallel to Satellites
567 multimap<RinexSatID,string> UsedObsIDs; // valid or not; may be comma-sep. list
568
569 // the PRS itself
570 PRSolution prs;
571
572 // statistics on the solution residuals
573 int nepochs;
574 WtdAveStats statsXYZresid; // RPF (XYZ) minus reference position
575 WtdAveStats statsNEUresid; // RNE above rotated into local NEU
576 //WtdAveStats statsSPSXYZresid; // SPF (XYZ) minus reference position
577 //WtdAveStats statsSPSNEUresid; // SNE above rotated into local NEU
578
579 }; // end class SolutionObject
580
581 //------------------------------------------------------------------------------------
582 // prototypes
583 /**
584 * @throw Exception */
585 int Initialize(string& errors);
586 /**
587 * @throw Exception */
588 int ProcessFiles(void);
589
590 //------------------------------------------------------------------------------------
591 //------------------------------------------------------------------------------------
main(int argc,char ** argv)592 int main(int argc, char **argv)
593 {
594 // get the (single) instance of the configuration
595 Configuration& C(Configuration::Instance());
596
597 try {
598 int iret;
599 clock_t totaltime(clock());
600 Epoch wallclkbeg;
601 wallclkbeg.setLocalTime();
602
603 // build title = first line of output
604 C.Title = C.PrgmName + ", part of the GPS Toolkit, Ver " + Version
605 + ", Run " + printTime(wallclkbeg,C.calfmt);
606 //cout << C.Title << endl;
607
608 for(;;) {
609 // get information from the command line
610 // iret -2 -3 -4
611 if((iret = C.ProcessUserInput(argc, argv)) != 0) break;
612
613 // read stores, check input etc
614 string errs;
615 if((iret = Initialize(errs)) != 0) {
616 LOG(ERROR) << "------- Input is not valid: ----------\n" << errs
617 << "------- end errors -----------";
618 break;
619 }
620
621 // open files, read, compute solutions and output
622 int nfiles = ProcessFiles();
623 if(nfiles < 0) break;
624 LOG(VERBOSE) << "Successfully read " << nfiles
625 << " RINEX observation file" << (nfiles > 1 ? "s.":".");
626
627 // output final results
628 for(size_t i=0; i<C.SolObjs.size(); ++i) {
629 LOG(INFO) << "\n ----- Final output " << C.SolObjs[i].Descriptor << " -----";
630 C.SolObjs[i].FinalOutput();
631 }
632
633 break; // mandatory
634 }
635
636 if(iret == 0) {
637 // print elapsed time
638 totaltime = clock()-totaltime;
639 Epoch wallclkend;
640 wallclkend.setLocalTime();
641 ostringstream oss;
642 oss << C.PrgmName << " timing: processing " << fixed << setprecision(3)
643 << double(totaltime)/double(CLOCKS_PER_SEC) << " sec, wallclock: "
644 << setprecision(0) << (wallclkend-wallclkbeg) << " sec.\n";
645 LOGstrm << oss.str();
646 cout << oss.str();
647 }
648
649 return iret;
650 }
651 catch(FFStreamError& e) { cerr << "FFStreamError: " << e.what(); }
652 catch(Exception& e) { cerr << "Exception: " << e.what(); }
653 catch (...) { cerr << "Unknown exception. Abort." << endl; }
654 return 1;
655
656 } // end main()
657
658 //------------------------------------------------------------------------------------
659 // return -5 if input is not valid
Initialize(string & errors)660 int Initialize(string& errors)
661 {
662 try {
663 Configuration& C(Configuration::Instance());
664 bool isValid(true);
665 int nread,nrec;
666 size_t i,j,nfile;
667 ostringstream ossE;
668 CommonTime typtime; // typical time for the dataset
669
670 errors = string("");
671
672 // add path to filenames, and expand tilde (~)
673 include_path(C.Obspath, C.InputObsFiles);
674 include_path(C.SP3path, C.InputSP3Files);
675 include_path(C.Clkpath, C.InputClkFiles);
676 include_path(C.Navpath, C.InputNavFiles);
677 include_path(C.Metpath, C.InputMetFiles);
678 include_path(C.DCBpath, C.InputDCBFiles);
679
680 //expand_filename(C.InputObsFiles);
681 expand_filename(C.InputSP3Files);
682 expand_filename(C.InputClkFiles);
683 expand_filename(C.InputNavFiles);
684 expand_filename(C.InputMetFiles);
685 expand_filename(C.InputDCBFiles);
686
687 // -------- quick check that obs files exist and are RINEX -------
688 if(C.InputObsFiles.size() > 0) {
689 try {
690 for(nread=0,nfile=0; nfile<C.InputObsFiles.size(); nfile++) {
691 Rinex3ObsStream rostrm(C.InputObsFiles[nfile].c_str(), ios_base::in);
692 if(!rostrm.is_open()) {
693 ossE << "Error : failed to open RINEX obs file: "
694 << C.InputObsFiles[nfile] << endl;
695 isValid = false;
696 continue;
697 }
698 Rinex3ObsHeader rhead;
699 rostrm >> rhead;
700
701 typtime = rhead.firstObs.convertToCommonTime();
702 typtime.setTimeSystem(TimeSystem::Any);
703
704 rostrm.close();
705
706 if(!isRinex3ObsFile(C.InputObsFiles[nfile])) {
707 ossE << "Error : File: " << C.InputObsFiles[nfile]
708 << " is not a valid RINEX file." << endl;
709 isValid = false;
710 }
711 nread++;
712 LOG(VERBOSE) << "Found RINEX obs file " << C.InputObsFiles[nfile];
713 }
714 }
715 catch(Exception& e) {
716 ossE << "Error : failed to read RINEX obs files: " << e.getText(0) << endl;
717 isValid = false;
718 }
719
720 }
721 else {
722 ossE << "Error : no RINEX observation files specified.\n";
723 isValid = false;
724 }
725
726 // -------- SP3 files --------------------------
727 // if Rinex clock files are to be loaded, tell the SP3 reader so
728 bool useSP3clocks(C.InputClkFiles.size() == 0);
729
730 // read ephemeris files and fill store
731 // first sort them on start time; this for ultra-rapid files, which overlap in time
732 if(C.InputSP3Files.size() > 0) {
733 if(!useSP3clocks) {
734 // if RINEX clocks are to be loaded, ignore the clock in the SP3 files
735 C.SP3EphStore.rejectBadClocks(false);
736 // this causes the store to ignore the SP3 clock; read RINEX clock later
737 C.SP3EphStore.useRinexClockData();
738 }
739
740 ostringstream os;
741 multimap<CommonTime,string> startNameMap;
742 for(nfile=0; nfile<C.InputSP3Files.size(); nfile++) {
743 SP3Header header;
744 try {
745 SP3Stream strm(C.InputSP3Files[nfile].c_str());
746 if(!strm.is_open()) {
747 os << "Failed to open file " << C.InputSP3Files[nfile] << endl;
748 isValid = false;
749 continue;
750 }
751
752 strm.exceptions(ios_base::failbit);
753 strm >> header;
754 }
755 catch(Exception& e) {
756 os << "Exception: " << e.what() << endl; isValid = false; continue; }
757 catch(std::exception& e) {
758 os << "exception: " << e.what(); isValid = false; continue; }
759 startNameMap.insert(multimap<CommonTime,string>::value_type(
760 header.time,C.InputSP3Files[nfile]));
761 }
762
763 ossE << os.str();
764 C.InputSP3Files.clear();
765 for(multimap<CommonTime,string>::const_iterator it = startNameMap.begin();
766 it != startNameMap.end(); ++it)
767 C.InputSP3Files.push_back(it->second);
768
769 // read sorted ephemeris files and fill store
770 if(isValid) {
771 try {
772 if(isValid) for(nread=0,nfile=0; nfile<C.InputSP3Files.size(); nfile++) {
773 LOG(VERBOSE) << "Load SP3 file " << C.InputSP3Files[nfile];
774 C.SP3EphStore.loadSP3File(C.InputSP3Files[nfile]);
775 nread++;
776 }
777 }
778 catch(Exception& e) {
779 ossE << "Error : failed to read ephemeris files: "
780 << e.getText(0) << endl;
781 isValid = false;
782 }
783
784 }
785 }
786
787 // -------- RINEX clock files --------------------------
788 // Read RINEX clock files for clock (only) store.
789 // This will set the clock store to use RINEX clock rather than SP3.
790 // Read clock files before SP3, in case there are none and SP3 clock to be used.
791 if(C.InputClkFiles.size() > 0) {
792 try {
793 for(nread=0,nfile=0; nfile<C.InputClkFiles.size(); nfile++) {
794 LOG(VERBOSE) << "Load Clock file " << C.InputClkFiles[nfile];
795 C.SP3EphStore.loadRinexClockFile(C.InputClkFiles[nfile]);
796 nread++;
797 }
798 }
799 catch(Exception& e) {
800 ossE << "Error : failed to read RINEX clock files: " << e.getText(0) << endl;
801 isValid = false;
802 }
803
804 LOG(VERBOSE) << "Read " << nread << " RINEX clock files into store.\n"
805 << "RINEX clock file store contains " << C.SP3EphStore.ndataClock()
806 << " data.";
807 }
808 else LOG(VERBOSE) << "No RINEX clock files";
809
810 // ------------- configure and dump SP3 and clock stores -----------------
811 if(isValid && C.SP3EphStore.ndata() > 0) {
812 LOG(VERBOSE) << "Read " << nread << " SP3 ephemeris files into store.";
813 LOG(VERBOSE) << "SP3 Ephemeris store contains "
814 << C.SP3EphStore.ndata() << " data";
815
816 // set to linear interpolation, as this is best estimate for clocks - TD input?
817 C.SP3EphStore.setClockLinearInterp(); // changes 'interp order' to 1
818
819 vector<SatID> sats(C.SP3EphStore.getSatList());
820 RinexSatID sat(sats[sats.size()-1]);
821 double dtp = C.SP3EphStore.getPositionTimeStep(sat);
822 double dtc = C.SP3EphStore.getClockTimeStep(sat);
823 LOG(VERBOSE) << "\nSP3 Ephemeris Store time intervals for " << sat
824 << " are " << dtp << " (pos), and " << dtc << " (clk)";
825 LOG(VERBOSE) << "SP3 Ephemeris store time system "
826 << gpstk::StringUtils::asString(C.SP3EphStore.getTimeSystem());
827
828 // set gap checking - don't b/c TimeStep may vary GPS/GLO
829 // TD this is a problem
830 //C.SP3EphStore.setClockGapInterval(C.SP3EphStore.getClockTimeStep()+1.);
831 //C.SP3EphStore.setClockMaxInterval(2*C.SP3EphStore.getClockTimeStep()+1.);
832
833 // ignore predictions for now // TD make user input?
834 C.SP3EphStore.rejectPredPositions(true);
835 C.SP3EphStore.rejectPredClocks(true);
836
837 // set gap checking NB be sure InterpolationOrder is set first
838 C.SP3EphStore.setPositionInterpOrder(10);
839 C.SP3EphStore.setPosGapInterval(dtp+1.);
840 C.SP3EphStore.setPosMaxInterval(
841 (C.SP3EphStore.getInterpolationOrder()-1) * dtp + 1.);
842
843 // dump the SP3 ephemeris store; while looping, check the GLO freq channel
844 LOG(VERBOSE) << "\nDump clock and position stores, including file stores";
845 // NB clock dumps are huge!
846 if(C.verbose) C.SP3EphStore.dump(LOGstrm, (C.debug > 6 ? 2 : 1));
847 LOG(VERBOSE) << "End of clock store and ephemeris store dumps.";
848
849 // dump a list of satellites, with counts, times and GLO channel
850 C.msg = "";
851 LOG(VERBOSE) << "\nDump ephemeris sat list with count, times and GLO channel.";
852 for(i=0; i<sats.size(); i++) { // loop over sats
853 // check for some GLO channel - can't compute b/c we don't have data yet
854 if(sats[i].system == SatelliteSystem::Glonass) {
855 map<RinexSatID,int>::const_iterator it(C.GLOfreqChannel.find(sats[i]));
856 if(it == C.GLOfreqChannel.end()
857 && sats[i].system == SatelliteSystem::Glonass) {
858 //LOG(WARNING) << "Warning - no input GLONASS frequency channel "
859 // << "for satellite " << RinexSatID(sats[i]);
860 // set it to zero
861 C.GLOfreqChannel.insert(map<RinexSatID,int>::value_type(sats[i],0));
862 it = C.GLOfreqChannel.find(sats[i]);
863 }
864 C.msg = string(" frch ") + rightJustify(asString(it->second),2);
865 }
866
867 LOG(VERBOSE) << " Sat: " << RinexSatID(sats[i])
868 << " Neph: " << setw(3) << C.SP3EphStore.ndata(sats[i])
869 << " Beg: " << printTime(C.SP3EphStore.getInitialTime(sats[i]),C.longfmt)
870 << " End: " << printTime(C.SP3EphStore.getFinalTime(sats[i]),C.longfmt)
871 << C.msg;
872 } // end loop over sats
873 }
874
875 // -------- Nav files --------------------------
876 // NB Nav files may set GLOfreqChannel
877 if(C.InputNavFiles.size() > 0) {
878 try {
879 // configure - input?
880 C.RinEphStore.setOnlyHealthyFlag(true); // keep only healthy ephemerides
881
882 for(nrec=0,nread=0,nfile=0; nfile < C.InputNavFiles.size(); nfile++) {
883 string filename(C.InputNavFiles[nfile]);
884 int n(C.RinEphStore.loadFile(filename,(C.debug > -1),LOGstrm));
885 if(n == -1) { // failed to open
886 LOG(WARNING) << C.RinEphStore.what;
887 continue;
888 }
889 else if(n == -2) { // failed to read header
890 LOG(WARNING) << "Warning - Failed to read header: "
891 << C.RinEphStore.what << "\nHeader dump follows.";
892 C.RinEphStore.Rhead.dump(LOGstrm);
893 continue;
894 }
895 else if(n == -3) { // failed to read data
896 LOG(WARNING) << "Warning - Failed to read data: "
897 << C.RinEphStore.what << "\nData dump follows.";
898 C.RinEphStore.Rdata.dump(LOGstrm);
899 continue;
900 }
901
902 nrec += n; // number of records read
903 nread += 1;
904
905 if(C.verbose) {
906 LOG(VERBOSE) << "Read " << n << " ephemeris data from file "
907 << filename << "; header follows.";
908 C.RinEphStore.Rhead.dump(LOGstrm);
909 }
910 } // end loop over InputNavFiles
911
912 // expand the network of time system corrections
913 C.RinEphStore.expandTimeCorrMap();
914
915 // set search method
916 if(C.searchUser) C.RinEphStore.SearchUser();
917 else C.RinEphStore.SearchNear();
918 }
919 catch(Exception& e) {
920 ossE << "Error : while reading RINEX nav files: " << e.what() << endl;
921 isValid = false;
922 }
923
924 if(nread == 0) {
925 ossE << "Error : Unable to read any RINEX nav files." << endl;
926 isValid = false;
927 }
928
929 if(isValid) {
930 LOG(VERBOSE) << "Read " << nread << " RINEX navigation files, containing "
931 << nrec << " records, into store.";
932 LOG(VERBOSE) << "GPS ephemeris store contains "
933 << C.RinEphStore.size(SatelliteSystem::GPS) << " ephemerides.";
934 LOG(VERBOSE) << "GAL ephemeris store contains "
935 << C.RinEphStore.size(SatelliteSystem::Galileo) << " ephemerides.";
936 LOG(VERBOSE) << "BDS ephemeris store contains "
937 << C.RinEphStore.size(SatelliteSystem::BeiDou) << " ephemerides.";
938 LOG(VERBOSE) << "QZS ephemeris store contains "
939 << C.RinEphStore.size(SatelliteSystem::QZSS) << " ephemerides.";
940 LOG(VERBOSE) << "GLO ephemeris store contains "
941 << C.RinEphStore.size(SatelliteSystem::Glonass) << " satellites.";
942 // dump the entire store
943 C.RinEphStore.dump(LOGstrm,(C.debug > -1 ? 2:0));
944 }
945 }
946
947 // assign pEph
948 if(isValid) {
949 if(C.SP3EphStore.ndata() > 0)
950 C.pEph = &C.SP3EphStore;
951 else if(C.RinEphStore.size() > 0)
952 C.pEph = &C.RinEphStore;
953 }
954
955 // -------- Met files --------------------------
956 // get met files and build MetStore
957 if(C.InputMetFiles.size() > 0) {
958 try {
959 for(nfile=0; nfile < C.InputMetFiles.size(); nfile++) {
960 RinexMetStream mstrm(C.InputMetFiles[nfile].c_str());
961 if(!mstrm.is_open()) {
962 ossE << "Error : failed to open RINEX meteorological file "
963 << C.InputMetFiles[nfile] << endl;
964 isValid = false;
965 continue;
966 }
967 mstrm.exceptions(ios_base::failbit);
968
969 RinexMetHeader mhead;
970 RinexMetData mdata;
971
972 mstrm >> mhead;
973 while(mstrm >> mdata)
974 C.MetStore.push_back(mdata);
975
976 mstrm.close();
977 } // end loop over met file names
978
979 // sort on time
980 C.MetStore.sort();
981
982 // dump
983 if(isValid && C.verbose) {
984 list<RinexMetData>::const_iterator it = C.MetStore.begin();
985 LOG(VERBOSE) << "Meteorological store contains "
986 << C.MetStore.size() << " records:";
987 if(C.MetStore.size() > 0) {
988 it = C.MetStore.begin();
989 if(C.MetStore.size() == 1) {
990 LOG(VERBOSE) << " Met store is at single time "
991 << printTime(it->time,C.longfmt);
992 }
993 else {
994 LOG(VERBOSE) << " Met store starts at time "
995 << printTime(it->time,C.longfmt);
996 it = C.MetStore.end();
997 it--;
998 LOG(VERBOSE) << " Met store ends at time "
999 << printTime(it->time,C.longfmt);
1000 }
1001 }
1002
1003 if(C.debug > -1) {
1004 LOG(DEBUG) << "Dump of meteorological data store ("
1005 << C.MetStore.size() << "):";
1006 for(it = C.MetStore.begin(); it != C.MetStore.end(); it++) {
1007 ostringstream os;
1008 os << printTime(it->time,C.longfmt) << fixed << setprecision(1);
1009 RinexMetData::RinexMetMap::const_iterator jt=it->data.begin();
1010 for( ; jt != it->data.end(); jt++) {
1011 os << " " << RinexMetHeader::convertObsType(jt->first)
1012 << " = " << setw(6) << jt->second;
1013 }
1014 LOG(DEBUG) << os.str();
1015 }
1016 LOG(DEBUG) << "End dump of meteorological data store.";
1017 }
1018
1019 if(C.MetStore.size() == 0) {
1020 C.InputMetFiles.clear();
1021 LOG(WARNING) << "Warning : Met data store is empty - clear file names";
1022 }
1023 }
1024 }
1025 catch(Exception& e) {
1026 ossE << "Error : failed to read meteorological files: " << e.what() << endl;
1027 isValid = false;
1028 C.MetStore.clear();
1029 }
1030 } // end if there are met file names
1031
1032 // -------- DCB files --------------------------
1033 // load the P1C1 files
1034 if(C.InputDCBFiles.size() > 0) {
1035 for(nfile=0; nfile<C.InputDCBFiles.size(); nfile++) {
1036 string line,word,filename(C.InputDCBFiles[nfile]);
1037 RinexSatID sat;
1038 ifstream ifs(filename.c_str());
1039 if(!ifs.is_open()) {
1040 ossE << "Error : Failed to open P1-C1 bias file name " << filename <<endl;
1041 isValid = false;
1042 continue;
1043 }
1044 LOG(VERBOSE) << "Opened P1C1 file " << filename;
1045
1046 // loop over lines in the file
1047 while(1) {
1048 getline(ifs,line);
1049 if(ifs.eof() || !ifs.good()) break;
1050
1051 stripTrailing(line,"\r\n"); // remove trailing CRLF
1052 stripLeading(line," \t"); // remove leading whitespace
1053 if(line.empty()) continue; // skip empty lines
1054
1055 word = stripFirstWord(line); // get sat
1056 if(word.empty()) continue;
1057
1058 try { sat.fromString(word); }
1059 catch(Exception& e) { continue; }
1060 if(sat.system == SatelliteSystem::Unknown || sat.id == -1) continue;
1061
1062 word = stripFirstWord(line); // get bias
1063 if(word.empty()) continue;
1064 if(!isScientificString(word)) continue;
1065 double bias(asDouble(word)*C_MPS*1.e-9); // ns to m
1066
1067 if(C.P1C1bias.find(sat) != C.P1C1bias.end()) {
1068 LOG(WARNING) << "Warning : satellite " << sat
1069 << " is duplicated in P1-C1 bias file(s)";
1070 }
1071 else {
1072 C.P1C1bias.insert(map<RinexSatID,double>::value_type(sat,bias));
1073 LOG(DEBUG) << " Found P1-C1 bias for sat " << sat
1074 << " = " << setw(6) << word << " ns = "
1075 << fixed << setprecision(3) << setw(6) << bias
1076 << " m (from " << filename << ")";
1077 }
1078 }
1079 }
1080 } // end InputP1C1Name processing
1081
1082 // ------ compute and save a reference time for decimation
1083 if(C.decimate > 0.0) {
1084 C.decTime = C.beginTime;
1085 double s,sow(static_cast<GPSWeekSecond>(C.decTime).sow);
1086 s = int(C.decimate * int(sow/C.decimate));
1087 if(::fabs(s-sow) > 1.0) LOG(WARNING) << "Warning : decimation reference time "
1088 << "(--start) is not an even GPS-seconds-of-week mark.";
1089 }
1090
1091 // ------ compute rotation matrix for knownPos
1092 if(C.knownPos.getCoordinateSystem() != Position::Unknown) {
1093 double lat = C.knownPos.geodeticLatitude() * DEG_TO_RAD;
1094 double lon = C.knownPos.longitude() * DEG_TO_RAD;
1095 double ca = ::cos(lat);
1096 double sa = ::sin(lat);
1097 double co = ::cos(lon);
1098 double so = ::sin(lon);
1099 // Rotation matrix (R*XYZ=NEU) :
1100 C.Rot = Matrix<double>(3,3);
1101 // NEU
1102 C.Rot(2,0) = ca*co; C.Rot(2,1) = ca*so; C.Rot(2,2) = sa;
1103 C.Rot(1,0) = -so; C.Rot(1,1) = co; C.Rot(1,2) = 0.0;
1104 C.Rot(0,0) = -sa*co; C.Rot(0,1) = -sa*so; C.Rot(0,2) = ca;
1105 }
1106
1107 // ------- initialize trop model
1108 // NB only Saas,NewB and Neill require this input, but calls to others are harmless
1109 if(C.knownPos.getCoordinateSystem() != Position::Unknown) {
1110 C.pTrop->setReceiverLatitude(C.knownPos.getGeodeticLatitude());
1111 C.pTrop->setReceiverHeight(C.knownPos.getHeight());
1112 C.TropPos = true;
1113 }
1114 else {
1115 C.pTrop->setReceiverLatitude(0.0);
1116 C.pTrop->setReceiverHeight(0.0);
1117 }
1118
1119 if(C.beginTime != C.gpsBeginTime) {
1120 C.pTrop->setDayOfYear(static_cast<YDSTime>(C.beginTime).doy);
1121 C.TropTime = true;
1122 }
1123 else if(C.endTime != CommonTime::END_OF_TIME) {
1124 C.pTrop->setDayOfYear(static_cast<YDSTime>(C.endTime).doy);
1125 C.TropTime = true;
1126 }
1127 else
1128 C.pTrop->setDayOfYear(100);
1129
1130 // Choose transforms to be used; dump the available Helmert Tranformations
1131 LOG(VERBOSE) << "\nAvailable Helmert Tranformations:";
1132 for(i=0; i<(size_t)HelmertTransform::stdCount; i++) {
1133 const HelmertTransform& ht(HelmertTransform::stdTransforms[i]);
1134 // pick the ones to use
1135 C.msg = "";
1136 if(ht.getFromFrame() == ReferenceFrame::PZ90) {
1137 if(ht.getToFrame() == ReferenceFrame::ITRF) {
1138 if(ht.getEpoch() >= HelmertTransform::PZ90Epoch)
1139 { C.PZ90ITRF = i; C.msg = "\n [use this for PZ90-ITRF]"; }
1140 else
1141 { C.PZ90ITRFold = i; C.msg = "\n [use this for PZ90-ITRF old]"; }
1142 }
1143 else if(ht.getToFrame() == ReferenceFrame::WGS84) {
1144 if(ht.getEpoch() >= HelmertTransform::PZ90Epoch)
1145 { C.PZ90WGS84 = i; C.msg = "\n [use this for PZ90-WGS84]"; }
1146 else
1147 { C.PZ90WGS84old = i; C.msg = "\n [use this for PZ90-WGS84 old]"; }
1148 }
1149 }
1150
1151 LOG(VERBOSE) << i << " " << ht.asString() << C.msg;
1152 }
1153 LOG(VERBOSE) << "End of Available Helmert Tranformations.\n";
1154
1155 // ----- build SolutionObjects from solution descriptors inSolDesc -----
1156 // these may be invalid, or there may not be data for them -> invalid
1157 for(j=0,i=0; i<C.inSolDesc.size(); i++) {
1158 string msg;
1159 if(!SolutionObject::ValidateDescriptor(C.inSolDesc[i],msg)) {
1160 LOG(WARNING) << "Warning : --sol " << msg;
1161 continue;
1162 }
1163
1164 // create a solution object
1165 SolutionObject SObj(C.inSolDesc[i]);
1166 if(!SObj.isValid) {
1167 LOG(WARNING) << "Warning : solution descriptor " << C.inSolDesc[i]
1168 << " could not be created - ignore";
1169 continue;
1170 }
1171
1172 if(i==0) LOG(INFO) << SObj.prs.configString("PRS configuration:");
1173 LOG(INFO) << "\nValidate solution descriptor " << C.inSolDesc[i];
1174
1175 // is there ephemeris for each system?
1176 bool ok=true;
1177 for(size_t k=0; k<SObj.sysChars.size(); k++) {
1178 RinexSatID sat;
1179 sat.fromString(SObj.sysChars[k]);
1180 int n;
1181 if(C.pEph == &C.RinEphStore) n = C.RinEphStore.size(sat.system);
1182 if(C.pEph == &C.SP3EphStore) n = C.SP3EphStore.ndata(sat.system);
1183 if(n == 0) {
1184 LOG(WARNING) << "Warning - no ephemeris found for system "
1185 << RinexObsID::map1to3sys[SObj.sysChars[k]] << ", in solution descriptor "
1186 << C.inSolDesc[i] << " => invalidate.";
1187 ok = false;
1188 }
1189 else {
1190 LOG(INFO) << " Found system " << RinexObsID::map1to3sys[SObj.sysChars[k]]
1191 << " with " << n << " ephemeris data.";
1192 }
1193 }
1194 if(!ok) continue;
1195 LOG(INFO) << " ...valid.";
1196
1197 // save the SolutionObject
1198 C.SolObjs.push_back(SObj);
1199 LOG(DEBUG) << "Initial solution #" << ++j << " " << C.inSolDesc[i];
1200 } // end loop over input solution descriptors
1201
1202 if(C.SolObjs.size() == 0) {
1203 LOG(ERROR) << "Error: No valid solution descriptors";
1204 isValid = false;
1205 }
1206
1207 // keep a list of all system characters used, for convenience
1208 C.allSystemChars.clear();
1209 for(i=0; i<C.SolObjs.size(); i++) {
1210 for(j=0; j<C.SolObjs[i].sysChars.size(); j++) {
1211 if(find(C.allSystemChars.begin(), C.allSystemChars.end(),
1212 C.SolObjs[i].sysChars[j]) == C.allSystemChars.end())
1213 C.allSystemChars.push_back(C.SolObjs[i].sysChars[j]);
1214 }
1215 }
1216 if(C.debug > -1) {
1217 ostringstream oss;
1218 oss << "List of all systems needed for solutions";
1219 for(i=0; i<C.allSystemChars.size(); i++) oss << " " << C.allSystemChars[i];
1220 LOG(DEBUG) << oss.str();
1221 }
1222
1223 // save errors and output
1224 errors = ossE.str();
1225
1226 if(!isValid) return -5;
1227 return 0;
1228 }
1229 catch(Exception& e) { GPSTK_RETHROW(e); }
1230 } // end Initialize()
1231
1232 //------------------------------------------------------------------------------------
1233 // Return 0 ok, >0 number of files successfully read, <0 fatal error
ProcessFiles(void)1234 int ProcessFiles(void)
1235 {
1236 try {
1237 Configuration& C(Configuration::Instance());
1238 bool firstepoch(true);
1239 int k,iret,nfiles;
1240 size_t i,j,nfile;
1241 Position PrevPos(C.knownPos);
1242 Rinex3ObsStream ostrm;
1243
1244 for(nfiles=0,nfile=0; nfile<C.InputObsFiles.size(); nfile++) {
1245 Rinex3ObsStream istrm;
1246 Rinex3ObsHeader Rhead, Rheadout;
1247 Rinex3ObsData Rdata;
1248 string filename(C.InputObsFiles[nfile]);
1249
1250 if (C.PisY)
1251 {
1252 LOG(DEBUG) << "Converting P/W code data to Y code";
1253 Rhead.PisY = C.PisY;
1254 }
1255
1256 // iret is set to 0 ok, or could not: 1 open file, 2 read header, 3 read data
1257 iret = 0;
1258
1259 // open the file ------------------------------------------------
1260 istrm.open(filename.c_str(),ios::in);
1261 if(!istrm.is_open()) {
1262 LOG(WARNING) << "Warning : could not open file " << filename;
1263 iret = 1;
1264 continue;
1265 }
1266 else
1267 LOG(VERBOSE) << "Opened input file " << filename;
1268 istrm.exceptions(ios::failbit);
1269
1270 // read the header ----------------------------------------------
1271 try { istrm >> Rhead; }
1272 catch(Exception& e) {
1273 LOG(WARNING) << "Warning : Failed to read header; dump follows.";
1274 Rhead.dump(LOGstrm);
1275 istrm.close();
1276 iret = 2;
1277 continue;
1278 }
1279 if(C.verbose) {
1280 LOG(VERBOSE) << "Input header for RINEX file " << filename;
1281 Rhead.dump(LOGstrm);
1282 LOG(VERBOSE) << "Time system for RINEX file " << filename
1283 << " is " << gpstk::StringUtils::asString(istrm.timesystem);
1284 }
1285
1286 // does header include C1C (for DCB correction)?
1287 bool DCBcorr(false);
1288 map<string,int> mapDCBindex;
1289 for(;;) {
1290 map<string,vector<RinexObsID> >::const_iterator sit;
1291 sit = Rhead.mapObsTypes.begin();
1292 for( ; sit != Rhead.mapObsTypes.end(); ++sit) {
1293 for(i=0; i<sit->second.size(); i++) {
1294 if(asString(sit->second[i]) == string("C1C")) {
1295 DCBcorr = true;
1296 mapDCBindex.insert(map<string,int>::value_type(sit->first,i));
1297 LOG(DEBUG) << "Correct for DCB: found " << asString(sit->second[i])
1298 << " for system " << sit->first << " at index " << i;
1299 break;
1300 }
1301 }
1302 }
1303 break;
1304 }
1305
1306 // do on first epoch only
1307 if(firstepoch) {
1308 // if writing to output RINEX, open and write header ---------
1309 if(!C.OutputObsFile.empty()) {
1310 ostrm.open(C.OutputObsFile.c_str(),ios::out);
1311 if(!ostrm.is_open()) {
1312 LOG(WARNING) << "Warning : could not open output file "
1313 << C.OutputObsFile;
1314 C.OutputObsFile = string();
1315 }
1316 else {
1317 LOG(VERBOSE) << "Opened output RINEX file " << C.OutputObsFile;
1318 ostrm.exceptions(ios::failbit);
1319
1320 // copy header and modify it?
1321 Rheadout = Rhead;
1322 Rheadout.fileProgram = C.PrgmName;
1323
1324 // output version 2
1325 if(C.outver2)
1326 Rheadout.prepareVer2Write();
1327
1328 ostrm << Rheadout;
1329 }
1330 }
1331
1332 // if writing out ORDs, open the file
1333 if(!C.OutputORDFile.empty()) {
1334 C.ordstrm.open(C.OutputORDFile.c_str(),ios::out);
1335 if(!C.ordstrm.is_open()) {
1336 LOG(WARNING) << "Warning : failed to open output ORDs file "
1337 << C.OutputORDFile << " - abort ORD output.";
1338 C.ORDout = false;
1339 }
1340 else {
1341 C.ORDout = true;
1342 // write header
1343 C.ordstrm << "ORD sat week sec-of-wk elev iono ORD1"
1344 << " ORD2 ORD Clock Solution_descriptor\n";
1345 }
1346 }
1347
1348 //see below in loop firstepoch = false;
1349 }
1350
1351 // Dump the solution descriptors and needed conversions ---------
1352 LOG(VERBOSE) << "\nSolutions to be computed for this file:";
1353 for(i=0; i<C.SolObjs.size(); ++i) {
1354 bool ok(C.SolObjs[i].ChooseObsIDs(Rhead.mapObsTypes));
1355
1356 LOG(VERBOSE) << (ok ? " OK ":" NO ") << i+1 << " " << C.SolObjs[i].dump(0);
1357 LOG(VERBOSE) << C.SolObjs[i].dump(0);
1358 if(C.verbose) for(j=0; j<C.SolObjs[i].sysChars.size(); j++) {
1359 TimeSystem ts;
1360 // TD nice if this mapping were in the library somwhere...
1361 if(C.SolObjs[i].sysChars[j] == "G") ts = TimeSystem::GPS;
1362 if(C.SolObjs[i].sysChars[j] == "R") ts = TimeSystem::GLO;
1363 if(C.SolObjs[i].sysChars[j] == "E") ts = TimeSystem::GAL;
1364 if(C.SolObjs[i].sysChars[j] == "C") ts = TimeSystem::BDT;
1365 if(C.SolObjs[i].sysChars[j] == "S") ts = TimeSystem::GPS;
1366 if(C.SolObjs[i].sysChars[j] == "J") ts = TimeSystem::QZS;
1367 LOG(INFO) << C.RinEphStore.dumpTimeSystemCorrection(istrm.timesystem,ts);
1368 }
1369 }
1370
1371 // loop over epochs ---------------------------------------------
1372 while(1) {
1373 try { istrm >> Rdata; }
1374 catch(Exception& e) {
1375 LOG(WARNING) << " Warning : Failed to read obs data (Exception "
1376 << e.getText(0) << "); dump follows.";
1377 Rdata.dump(LOGstrm,Rhead);
1378 istrm.close();
1379 iret = 3;
1380 break;
1381 }
1382 catch(std::exception& e) {
1383 Exception ge(string("Std excep: ") + e.what());
1384 GPSTK_THROW(ge);
1385 }
1386 catch(...) {
1387 Exception ue("Unknown exception while reading RINEX data.");
1388 GPSTK_THROW(ue);
1389 }
1390
1391 // normal EOF
1392 if(!istrm.good() || istrm.eof()) { iret = 0; break; }
1393
1394 // if aux header data, or no data, skip it
1395 if(Rdata.epochFlag > 1 || Rdata.obs.empty()) {
1396 LOG(DEBUG) << " RINEX Data is aux header or empty.";
1397 continue;
1398 }
1399
1400 LOG(DEBUG) << "\n Read RINEX data: flag " << Rdata.epochFlag
1401 << ", timetag " << printTime(Rdata.time,C.longfmt);
1402
1403 // stay within time limits
1404 if(Rdata.time < C.beginTime) {
1405 LOG(DEBUG) << " RINEX data timetag " << printTime(C.beginTime,C.longfmt)
1406 << " is before begin time.";
1407 continue;
1408 }
1409 if(Rdata.time > C.endTime) {
1410 LOG(DEBUG) << " RINEX data timetag " << printTime(C.endTime,C.longfmt)
1411 << " is after end time.";
1412 break;
1413 }
1414
1415 // decimate
1416 if(C.decimate > 0.0) {
1417 double dt(::fabs(Rdata.time - C.decTime));
1418 dt -= C.decimate * long(0.5 + dt/C.decimate);
1419 if(::fabs(dt) > 0.25) {
1420 LOG(DEBUG) << " Decimation rejects RINEX data timetag "
1421 << printTime(Rdata.time,C.longfmt);
1422 continue;
1423 }
1424 }
1425
1426 // reset solution objects for this epoch
1427 for(i=0; i<C.SolObjs.size(); ++i)
1428 C.SolObjs[i].EpochReset();
1429
1430 // loop over satellites -----------------------------
1431 RinexSatID sat;
1432 Rinex3ObsData::DataMap::iterator it;
1433 for(it=Rdata.obs.begin(); it!=Rdata.obs.end(); ++it) {
1434 sat = it->first;
1435 vector<RinexDatum>& vrdata(it->second);
1436 string sys(asString(sat.systemChar()));
1437
1438 // is this system excluded?
1439 if(find(C.allSystemChars.begin(),C.allSystemChars.end(),sys)
1440 == C.allSystemChars.end())
1441 {
1442 LOG(DEBUG) << " Sat " << sat << " : system " << sys
1443 << " is not needed.";
1444 continue;
1445 }
1446
1447 // has user excluded this satellite?
1448 if(find(C.exclSat.begin(),C.exclSat.end(),sat) != C.exclSat.end()) {
1449 LOG(DEBUG) << " Sat " << sat << " is excluded.";
1450 continue;
1451 }
1452
1453 // correct for DCB
1454 if(DCBcorr && mapDCBindex.find(sys) != mapDCBindex.end()) {
1455 i = mapDCBindex[sys];
1456 if(C.P1C1bias.find(sat) != C.P1C1bias.end()) {
1457 LOG(DEBUG) << "Correct data " << asString(Rhead.mapObsTypes[sys][i])
1458 << " = " << fixed << setprecision(2) << vrdata[i].data
1459 << " for DCB with " << C.P1C1bias[sat];
1460 vrdata[i].data += C.P1C1bias[sat];
1461 }
1462 }
1463
1464 // elevation mask, azimuth and ephemeris range corrected with trop
1465 // - pass elev to CollectData for m-cov matrix and ORDs
1466 double elev(0), ER(0), tcorr;
1467 if((C.elevLimit > 0 || C.weight || C.ORDout)
1468 && PrevPos.getCoordinateSystem() != Position::Unknown) {
1469 CorrectedEphemerisRange CER;
1470 try {
1471 CER.ComputeAtReceiveTime(Rdata.time, PrevPos, sat, *C.pEph);
1472 elev = CER.elevation;
1473 // const double azim = CER.azimuth;
1474 if(C.ORDout) {
1475 tcorr = C.pTrop->correction(PrevPos,CER.svPosVel.x,Rdata.time);
1476 ER = CER.rawrange - CER.svclkbias - CER.relativity + tcorr;
1477 }
1478 if(elev < C.elevLimit) { // TD add elev mask [azim]
1479 LOG(VERBOSE) << " Reject sat " << sat << " for elevation "
1480 << fixed << setprecision(2) << elev << " at time "
1481 << printTime(Rdata.time,C.longfmt);
1482 continue;
1483 }
1484 }
1485 catch(Exception& e) {
1486 LOG(WARNING) << "WARNING : Failed to get elevation for sat "
1487 << sat << " at time " << printTime(Rdata.time,C.longfmt);
1488 continue;
1489 }
1490 }
1491
1492 // pick out data for each solution object
1493 for(i=0; i<C.SolObjs.size(); ++i)
1494 C.SolObjs[i].CollectData(sat,elev,ER,vrdata);
1495
1496 } // end loop over satellites
1497
1498 // debug: dump the RINEX data object
1499 if(C.debug > -1) Rdata.dump(LOGstrm,Rhead);
1500
1501 // update the trop model's weather ------------------
1502 if(C.MetStore.size() > 0) C.setWeather(Rdata.time);
1503
1504 // put a blank line here for readability
1505 LOG(INFO) << "";
1506
1507 // compute the solution(s) --------------------------
1508 // tag for DAT - required for PRSplot
1509 C.msg = printTime(Rdata.time,"DAT "+C.gpsfmt);
1510
1511 // compute and print the solution(s) ----------------
1512 for(i=0; i<C.SolObjs.size(); ++i) {
1513 // skip invalid descriptors
1514 if(!C.SolObjs[i].isValid) continue;
1515
1516 // dump the "DAT" record
1517 if(firstepoch)
1518 LOG(VERBOSE) << C.SolObjs[i].dump(-1, "RPF", "DAT");
1519 LOG(INFO) << C.SolObjs[i].dump((C.debug > -1 ? 2:1), "RPF", C.msg);
1520
1521 // compute the solution
1522 if(firstepoch) LOG(VERBOSE) << C.SolObjs[i].prs.outputString(
1523 string("RPF ")+C.SolObjs[i].Descriptor,-999);
1524 if(firstepoch) LOG(VERBOSE) << C.SolObjs[i].prs.outputPOSString(
1525 string("RPR ")+C.SolObjs[i].Descriptor,-999);
1526 if(firstepoch) LOG(VERBOSE) << C.SolObjs[i].prs.outputPOSString(
1527 string("RNE ")+C.SolObjs[i].Descriptor,-999);
1528 j = C.SolObjs[i].ComputeSolution(Rdata.time);
1529
1530 // write ORDs, even if solution is not good
1531 if(C.ORDout) C.SolObjs[i].WriteORDs(Rdata.time,j);
1532 }
1533
1534 // write to output RINEX ----------------------------
1535 if(!C.OutputObsFile.empty()) {
1536 Rinex3ObsData auxData;
1537 auxData.time = Rdata.time;
1538 auxData.clockOffset = Rdata.clockOffset;
1539 auxData.epochFlag = 4;
1540 ostringstream oss;
1541 // loop over valid descriptors
1542 for(k=0,i=0; i<C.SolObjs.size(); ++i) if(C.SolObjs[i].isValid) {
1543 if(!C.SolObjs[i].prs.isValid())
1544 {
1545 LOG(ERROR) << "Invalid soution!";
1546 break;
1547 }
1548 oss.str("");
1549 oss << "XYZ" << fixed << setprecision(3)
1550 << " " << setw(12) << C.SolObjs[i].prs.Solution(0)
1551 << " " << setw(12) << C.SolObjs[i].prs.Solution(1)
1552 << " " << setw(12) << C.SolObjs[i].prs.Solution(2);
1553 oss << " " << C.SolObjs[i].Descriptor; // may get truncated
1554 auxData.auxHeader.commentList.push_back(oss.str());
1555 k++;
1556 oss.str("");
1557 oss << "CLK" << fixed << setprecision(3);
1558
1559 for(j=0; j<C.SolObjs[i].prs.dataGNSS.size(); j++) {
1560 RinexSatID sat(1,C.SolObjs[i].prs.dataGNSS[j]);
1561 oss << " " << sat.systemString3()
1562 << " " << setw(11) << C.SolObjs[i].prs.Solution(3+j);
1563 }
1564 oss << " " << C.SolObjs[i].Descriptor; // may get truncated
1565 auxData.auxHeader.commentList.push_back(oss.str());
1566 k++;
1567 oss.str("");
1568 oss << "DIA" << setw(2) << C.SolObjs[i].prs.Nsvs
1569 << fixed << setprecision(2)
1570 << " " << setw(4) << C.SolObjs[i].prs.PDOP
1571 << " " << setw(4) << C.SolObjs[i].prs.GDOP
1572 << " " << setw(8) << C.SolObjs[i].prs.RMSResidual
1573 << " " << C.SolObjs[i].Descriptor; // may get truncated
1574 auxData.auxHeader.commentList.push_back(oss.str());
1575 k++;
1576 }
1577 auxData.numSVs = k; // number of lines to write
1578 auxData.auxHeader.valid |= Rinex3ObsHeader::validComment;
1579 ostrm << auxData;
1580
1581 ostrm << Rdata;
1582 }
1583
1584 firstepoch = false;
1585
1586 } // end while loop over epochs
1587
1588 istrm.close();
1589
1590 // failure due to critical error
1591 if(iret < 0) break;
1592
1593 if(iret == 0) nfiles++;
1594
1595 } // end loop over files
1596
1597 if(!C.OutputObsFile.empty()) ostrm.close();
1598
1599 if(iret < 0) return iret;
1600
1601 return nfiles;
1602 }
1603 catch(Exception& e) { GPSTK_RETHROW(e); }
1604 } // end ProcessFiles()
1605
1606 //------------------------------------------------------------------------------------
1607 //------------------------------------------------------------------------------------
routine(void)1608 int routine(void)
1609 {
1610 try {
1611 Configuration& C(Configuration::Instance());
1612
1613 return 0;
1614 }
1615 catch(Exception& e) { GPSTK_RETHROW(e); }
1616 } // end routine()
1617
1618 //------------------------------------------------------------------------------------
1619 //------------------------------------------------------------------------------------
SetDefaults(void)1620 void Configuration::SetDefaults(void) throw()
1621 {
1622 SPSout = ORDout = false;
1623 LogFile = string("prs.log");
1624
1625 decimate = elevLimit = 0.0;
1626 forceElev = false;
1627 searchUser = false;
1628 weight = false;
1629 defaultstartStr = string("[Beginning of dataset]");
1630 defaultstopStr = string("[End of dataset]");
1631 beginTime = gpsBeginTime = GPSWeekSecond(0,0.,TimeSystem::Any);
1632 endTime = CommonTime::END_OF_TIME;
1633
1634 PisY = false;
1635 SOLhelp = false;
1636
1637 TropType = string("NewB");
1638 TropPos = TropTime = false;
1639 defaultTemp = 20.0;
1640 defaultPress = 1013.0;
1641 defaultHumid = 50.0;
1642 TropStr = TropType + string(",") + asString(defaultTemp,1) + string(",")
1643 + asString(defaultPress,1) + string(",") + asString(defaultHumid,1);
1644
1645 // get defaults from PRSolution
1646 {
1647 PRSolution dummy;
1648 RMSLimit = dummy.RMSLimit;
1649 SlopeLimit = dummy.SlopeLimit;
1650 maxReject = dummy.NSatsReject;
1651 nIter = dummy.MaxNIterations;
1652 convLimit = dummy.ConvergenceLimit;
1653 }
1654
1655 userfmt = gpsfmt;
1656 help = verbose = false;
1657 debug = -1;
1658
1659 } // end Configuration::SetDefaults()
1660
1661 //------------------------------------------------------------------------------------
SolDescHelp(void)1662 void Configuration::SolDescHelp(void)
1663 {
1664 // build the table
1665 string systs(RinexObsID::validRinexSystems);
1666 string freqs(RinexObsID::validRinexFrequencies);
1667 string codes;
1668 string space(" ");
1669 size_t i,j, k;
1670
1671 // first find the length of the longest codes entry for each system
1672 map<char,int> syslen;
1673 for(i=0; i<systs.size(); i++) {
1674 for(k=0, j=0; j<freqs.size(); j++) {
1675 codes = RinexObsID::validRinexTrackingCodes[systs[i]][freqs[j]];
1676 strip(codes,' '); strip(codes,'*');
1677 // GPS C1N and C2N are not allowed
1678 if(systs[i] == 'G' && (freqs[j] == '1' || freqs[j] == '2')) strip(codes,'N');
1679 if(codes.length() > k) k=codes.length();
1680 }
1681 syslen[systs[i]] = k;
1682 }
1683 string table;
1684 table = space + string("Valid PR tracking codes for systems and frequencies:\n");
1685 string head;
1686 for(i=0; i<systs.size(); i++) {
1687 head += (i==0 ? space+string("freq| ") : string(" | "));
1688 codes = RinexObsID::map1to3sys[string(1,systs[i])];
1689 head += center(codes,syslen[systs[i]]);
1690 }
1691 //head += string("\n") + space + string(head.size()-space.size()+1,'-');
1692 table += head + string("\n");
1693 for(i=0; i<freqs.size(); i++) {
1694 table += space + string(" ") + string(1,freqs[i]);
1695 for(j=0; j<systs.size(); j++) {
1696 codes = RinexObsID::validRinexTrackingCodes[systs[j]][freqs[i]];
1697 strip(codes,' '); strip(codes,'*');
1698 // GPS C1N and C2N are not allowed
1699 if(systs[i] == 'G' && (freqs[j] == '1' || freqs[j] == '2')) strip(codes,'N');
1700 if(codes.empty()) codes = string("---");
1701 table += string(" | ") + center(codes,syslen[systs[j]]);
1702 }
1703 if(i < freqs.size()-1) table += string("\n");
1704 }
1705
1706 ostringstream os;
1707 os << "=== Help for Solution Descriptors, option --sol <S:F:C> ===\n"
1708 << " The --sol option is repeatable, so all --sol solutions, if valid,\n"
1709 << " will be computed and output in one run of the program.\n\n"
1710 << " Solution descriptors are of the form S:F:C where\n"
1711 << " S is a system, one of:";
1712 for(i=0; i<systs.size(); i++)
1713 os << " " << RinexObsID::map1to3sys[string(1,systs[i])];
1714 os << endl;
1715 os << " F is a frequency, one or two of:";
1716 for(i=0; i<freqs.size(); i++)
1717 os << " " << freqs[i];
1718 os << endl;
1719 os << " C is an ordered set of one or more tracking codes, for example WPC\n"
1720 << " These must be consistent - not all F and C apply to all systems.\n\n";
1721 os << " The S:F:C are the RINEX codes used to identify pseudorange "
1722 << "observations." << endl;
1723 os << table << endl << endl;
1724 os << " Example solution descriptors are GPS:1:P GLO:3:I BDS:7:Q\n"
1725 << " These are single-frequency solutions, that is the GPS:1:P solution\n"
1726 << " will use GPS L1 P-code pseudorange data to find a solution.\n"
1727 << " Dual frequency solutions are allowed; they combine data of different\n"
1728 << " frequencies to eliminate the ionospheric delay, for example\n"
1729 << " GPS:12:PC is the usual L1/L2-ionosphere-corrected GPS solution.\n"
1730 << " Triple frequency solutions are not supported.\n\n"
1731 << " More that one tracking code may be provided, for example GPS:12:PWC\n"
1732 << " This tells PRSolve to prefer P, but if it is not available, use W,\n"
1733 << " and if neither P nor W are available, use C.\n\n"
1734 << " Finally, combined solutions may be specified, in which different\n"
1735 << " data types, even from different systems, are used together.\n"
1736 << " The component descriptors are combined using a '+'. For example\n"
1737 << " GPS:12:PC+GLO:12:PC\n"
1738 << " describes a dual frequency solution that uses both GPS and GLO\n"
1739 << " L1/L2 P-code (or C/A) data in a single solution algorithm.\n";
1740
1741 LOG(INFO) << Title;
1742 LOG(INFO) << os.str();
1743 }
1744
1745 //------------------------------------------------------------------------------------
ProcessUserInput(int argc,char ** argv)1746 int Configuration::ProcessUserInput(int argc, char **argv) throw()
1747 {
1748 // build the command line
1749 opts.DefineUsageString(PrgmName + " [options]");
1750 PrgmDesc = BuildCommandLine();
1751
1752 // let CommandLine parse options; write all errors, etc to the passed strings
1753 int iret = opts.ProcessCommandLine(argc, argv, PrgmDesc,
1754 cmdlineUsage, cmdlineErrors, cmdlineUnrecognized);
1755
1756 // handle return values
1757 if(iret == -2) return iret; // bad alloc
1758 if(iret == -3) return iret; // invalid command line
1759
1760 // SOLhelp: print explanation of Solution Descriptors
1761 if(SOLhelp) {
1762 SolDescHelp();
1763 return 1;
1764 }
1765
1766 // help: print syntax page and quit
1767 if(opts.hasHelp()) {
1768 LOG(INFO) << Title;
1769 LOG(INFO) << cmdlineUsage;
1770 return 1;
1771 }
1772
1773 // extra parsing (perhaps add to cmdlineErrors, cmdlineExtras)
1774 iret = ExtraProcessing(cmdlineErrors, cmdlineExtras);
1775 if(iret == -4) return iret; // log file could not be opened
1776
1777 // output warning / error messages
1778 if(cmdlineUnrecognized.size() > 0) {
1779 LOG(INFO) << "Warning - unrecognized arguments:";
1780 for(size_t i=0; i<cmdlineUnrecognized.size(); i++)
1781 LOG(INFO) << " " << cmdlineUnrecognized[i];
1782 LOG(INFO) << "End of unrecognized arguments";
1783 }
1784
1785 // fatal errors
1786 if(!cmdlineErrors.empty()) {
1787 stripTrailing(cmdlineErrors,'\n');
1788 replaceAll(cmdlineErrors,"\n","\n ");
1789 LOG(INFO) << "Errors found on command line:\n " << cmdlineErrors
1790 << "\nEnd of command line errors.";
1791 return 1;
1792 }
1793
1794 // success: dump configuration summary
1795 ostringstream oss;
1796 oss << "------ Summary of " << PrgmName << " command line configuration ------\n";
1797 opts.DumpConfiguration(oss);
1798 if(!cmdlineExtras.empty()) oss << "# Extra Processing:\n" << cmdlineExtras;
1799 oss << "------ End configuration summary ------\n";
1800 LOG(INFO) << oss.str();
1801
1802 return 0;
1803
1804 } // end Configuration::CommandLine()
1805
1806 //------------------------------------------------------------------------------------
BuildCommandLine(void)1807 string Configuration::BuildCommandLine(void) throw()
1808 {
1809 // Program description will appear at the top of the syntax page
1810 string PrgmDesc = " Program " + PrgmName +
1811 " reads one or more RINEX (v.2+) observation files, plus one or more\n"
1812 " ephemeris (RINEX nav or SP3) files, and computes a pseudorange position-and-clock\n"
1813 " solution, using a RAIM algorithm to eliminate outliers. Either single- or\n"
1814 " mixed-system (GNSSs) processing may be selected; input data is determined\n"
1815 " by, and solutions are labelled with, the 'solution descriptor' (see below).\n"
1816 " Output is to a log file, and also optionally to a RINEX observation file with\n"
1817 " the position solutions in comments in auxiliary header blocks. A final solution,\n"
1818 " covariance and statistics are given at the bottom of the log file.\n"
1819 "\n"
1820 " In the log file, results at each time tag appear in lines with the format:\n"
1821 " \"TAG descriptor LABEL week sec.of.week CONTENT (code) [N]V\"\n"
1822 " where TAG denotes the type of solution or solution residuals:\n"
1823 " RPF RAIM ECEF XYZ solution\n"
1824 " RPR RAIM ECEF XYZ solution residuals [only if --ref given]\n"
1825 " RNE RAIM North-East-Up solution residuals [only if --ref given]\n"
1826 " SPS Simple ECEF XYZ solution [only if --SPSout given]\n"
1827 " SPR Simple ECEF XYZ solution residuals [only if both SPS & ref given]\n"
1828 " SNE Simple North-East-Up solution residuals [only if SPS & ref given]\n"
1829 " and LABEL followed by CONTENT is:\n"
1830 " NAV X Y Z SYS clock_bias [SYS clock_bias ...]\n"
1831 " POS X Y Z\n"
1832 " CLK SYS clock_bias [SYS clock_bias ...]\n"
1833 " RMS Nsats RMS TDOP PDOP GDOP Slope niter conv SAT [SAT ...]\n"
1834 " DAT Ngood Ndata <SAT>:<freq><code> ... (list of sats with freq+code found)\n"
1835 " and where\n"
1836 " X Y Z = position solution, or solution residuals, depending on TAG;\n"
1837 " RNE and SNE yield North-East-Up residuals, at --ref position\n"
1838 " SYS = system or GNSS, e.g. GPS GLO GAL ... (identifies system of clock bias)\n"
1839 " Nsats = number of satellites in the RINEX file at this time\n"
1840 " Ngood = number of satellites used in the solution algorithm\n"
1841 " Nrej = number of satellites rejected by the RAIM algorithm\n"
1842 " RMS = RMS residual of fit (meters)\n"
1843 " Slope = RAIM 'slope' value\n"
1844 " xDOP = Dilution of precision (T=time, P=position, G=geometric=T+P)\n"
1845 " niter = number of iterations performed by the solution algorithm\n"
1846 " conv = final convergence value (delta RMS position) of the solution algorithm\n"
1847 " SAT = satellite identifier (e.g. G10, R07); minus sign means rejected\n"
1848 " CODE = return value from solution algorithm (with words if --verbose)\n"
1849 " [N]V = V for valid solution, NV for not valid (don't use!)\n"
1850 "\n"
1851 " Default values appear in () after options below.\n"
1852 ;
1853
1854 // options to appear on the syntax page, and to be accepted on command line
1855 //opts.Add(char, opt, arg, repeat?, required?, &target, pre-desc, desc);
1856 // NB filedummy is a dummy, but it must exist when cmdline is processed.
1857 opts.Add('f', "file", "fn", true, false, &filedummy,
1858 "# Input via configuration file:",
1859 "Name of file with more options [#->EOL = comment]");
1860
1861 // required
1862 opts.Add(0, "obs", "fn", true, true, &InputObsFiles,
1863 "# Required input:",
1864 "RINEX observation file name(s)");
1865 opts.Add(0, "sol", "S:F:C", true, true, &inSolDesc, "",
1866 "Solution(s) to compute: Sys:Freqs:Codes (cf. --SOLhelp)");
1867 opts.Add(0, "eph", "fn", true, false, &InputSP3Files,
1868 "# (require --eph OR --nav, but NOT both)",
1869 "Ephemeris+clock (SP3 format) file name(s)");
1870 opts.Add(0, "nav", "fn", true, false, &InputNavFiles, "",
1871 "RINEX nav file name(s) (also cf. --BCEpast)");
1872
1873 // optional
1874 opts.Add(0, "clk", "fn", true, false, &InputClkFiles,
1875 "# Optional input\n# Other input files",
1876 "Clock (RINEX format) file name(s)");
1877 opts.Add(0, "met", "fn", true, false, &InputMetFiles, "",
1878 "RINEX meteorological file name(s)");
1879 opts.Add(0, "dcb", "fn", true, false, &InputDCBFiles, "",
1880 "Differential code bias (P1-C1) file name(s)");
1881
1882 opts.Add(0, "obspath", "p", false, false, &Obspath,
1883 "# Paths of input files:", "Path of input RINEX observation file(s)");
1884 opts.Add(0, "ephpath", "p", false, false, &SP3path, "",
1885 "Path of input ephemeris+clock file(s)");
1886 opts.Add(0, "navpath", "p", false, false, &Navpath, "",
1887 "Path of input RINEX navigation file(s)");
1888 opts.Add(0, "clkpath", "p", false, false, &Clkpath, "",
1889 "Path of input RINEX clock file(s)");
1890 opts.Add(0, "metpath", "p", false, false, &Metpath, "",
1891 "Path of input RINEX meteorological file(s)");
1892 opts.Add(0, "dcbpath", "p", false, false, &DCBpath, "",
1893 "Path of input DCB (P1-C1) bias file(s)");
1894
1895 startStr = defaultstartStr;
1896 stopStr = defaultstopStr;
1897 opts.Add(0, "start", "t[:f]", false, false, &startStr,
1898 "# Editing [t(time),f(format) = strings; "
1899 "default wk,sec.of.wk OR YYYY,mon,d,h,min,s]",
1900 "Start processing data at this epoch");
1901 opts.Add(0, "stop", "t[:f]", false, false, &stopStr, "",
1902 "Stop processing data at this epoch");
1903 opts.Add(0, "decimate", "dt", false, false, &decimate, "",
1904 "Decimate data to time interval dt (0: no decimation)");
1905 opts.Add(0, "elev", "deg", false, false, &elevLimit, "",
1906 "Minimum elevation angle (deg) [--ref or --forceElev req'd]");
1907 opts.Add(0, "forceElev", "", false, false, &forceElev, "",
1908 "Apply elev mask (--elev, w/o --ref) using sol. at prev. time tag");
1909 opts.Add(0, "exSat", "sat", true, false, &exclSat, "",
1910 "Exclude this satellite [eg. G24 | R | R23,G31]");
1911 opts.Add(0, "BCEpast", "", false, false, &searchUser, "",
1912 "Use 'User' find-ephemeris-algorithm (else nearest) (--nav only)");
1913 opts.Add(0, "PisY", "", false, false, &PisY, "",
1914 "P code data is actually Y code data");
1915
1916 opts.Add(0, "wt", "", false, false, &weight,
1917 "# Solution Algorithm:",
1918 "Weight the measurements using elevation [--ref req'd]");
1919 opts.Add(0, "rms", "lim", false, false, &RMSLimit, "",
1920 "Upper limit on RMS post-fit residual (m)");
1921 opts.Add(0, "slope", "lim", false, false, &SlopeLimit, "",
1922 "Upper limit on maximum RAIM 'slope'");
1923 opts.Add(0, "nrej", "n", false, false, &maxReject, "",
1924 "Maximum number of satellites to reject [-1 for no limit]");
1925 opts.Add(0, "niter", "lim", false, false, &nIter, "",
1926 "Maximum iteration count in linearized LS");
1927 opts.Add(0, "conv", "lim", false, false, &convLimit, "",
1928 "Maximum convergence criterion in estimation in meters");
1929 opts.Add(0, "Trop", "m,T,P,H", false, false, &TropStr, "",
1930 "Trop model <m> [one of Zero,Black,Saas,NewB,Neill,GG,GGHt,Global\n"
1931 " with optional weather T(C),P(mb),RH(%)]");
1932
1933 opts.Add(0, "log", "fn", false, false, &LogFile, "# Output [for formats see "
1934 "GPSTK::Position (--ref) and GPSTK::Epoch (--timefmt)] :",
1935 "Output log file name");
1936 opts.Add(0, "out", "fn", false, false, &OutputObsFile, "",
1937 "Output RINEX observations (with position solution in comments)");
1938 opts.Add(0, "ver2", "", false, false, &outver2, "",
1939 "In output RINEX (--out), write RINEX version 2.11 [otherwise 3.01]");
1940 opts.Add(0, "ref", "p[:f]", false, false, &refPosStr, "",
1941 "Known position p in fmt f (def. '%x,%y,%z'), for resids, elev and ORDs");
1942 opts.Add(0, "SPSout", "", false, false, &SPSout, "",
1943 "Output autonomous pseudorange solution [tag SPS, no RAIM]");
1944 opts.Add(0, "ORDs", "fn", false, false, &OutputORDFile, "",
1945 "Write ORDs (Observed Range Deviations) to file <fn> [--ref req'd]");
1946 //opts.Add(0, "memory", "", false, false, &doMemory, "",
1947 // "Keep information between epochs, output APV etc.");
1948 opts.Add(0, "timefmt", "f", false, false, &userfmt, "",
1949 "Format for time tags in output");
1950
1951 opts.Add(0, "SOLhelp", "", false, false, &SOLhelp, "# Help",
1952 "Show more information and examples for --sol <Solution Descriptor>");
1953
1954 // deprecated (old,new)
1955 opts.Add_deprecated("--SP3","--eph");
1956
1957 return PrgmDesc;
1958
1959 } // end Configuration::BuildCommandLine()
1960
1961 //------------------------------------------------------------------------------------
ExtraProcessing(string & errors,string & extras)1962 int Configuration::ExtraProcessing(string& errors, string& extras) throw()
1963 {
1964 int i,n;
1965 vector<string> fld;
1966 ostringstream oss,ossx; // oss for Errors, ossx for Warnings and info
1967
1968 // reference position
1969 if(!refPosStr.empty()) {
1970 bool hasfmt(refPosStr.find('%') != string::npos);
1971 if(hasfmt) {
1972 fld = split(refPosStr,':');
1973 if(fld.size() != 2)
1974 oss << "Error : invalid arg pos:fmt for --ref: " << refPosStr << endl;
1975 else try {
1976 knownPos.setToString(fld[0],fld[1]);
1977 ossx << " Reference position --ref is "
1978 << knownPos.printf("XYZ(m): %.3x %.3y %.3z = LLH: %.9A %.9L %.3h\n");
1979 }
1980 catch(Exception& e) {
1981 oss << "Error: invalid pos or format for --ref: " << refPosStr << endl;
1982 }
1983 }
1984 else {
1985 fld = split(refPosStr,',');
1986 if(fld.size() != 3)
1987 oss << "Error : invalid format or number of fields in --ref arg: "
1988 << refPosStr << endl;
1989 else {
1990 try {
1991 double X(asDouble(fld[0])),Y(asDouble(fld[1])),Z(asDouble(fld[2]));
1992 knownPos.setECEF(X,Y,Z);
1993 ossx << " Reference position --ref is "
1994 << knownPos.printf("XYZ(m): %.3x %.3y %.3z = LLH: %.9A %.9L %.3h\n");
1995 }
1996 catch(Exception& e) {
1997 oss << "Error : invalid position in --ref arg: " << refPosStr
1998 << endl;
1999 }
2000 }
2001 }
2002 }
2003
2004 // start and stop times
2005 for(i=0; i<2; i++) {
2006 static const string fmtGPS("%F,%g"),fmtCAL("%Y,%m,%d,%H,%M,%S");
2007 msg = (i==0 ? startStr : stopStr);
2008 if(msg == (i==0 ? defaultstartStr : defaultstopStr)) continue;
2009
2010 bool ok(true);
2011 bool hasfmt(msg.find('%') != string::npos);
2012 n = numWords(msg,',');
2013 if(hasfmt) {
2014 fld = split(msg,':');
2015 if(fld.size() != 2) { ok = false; }
2016 else try {
2017 Epoch ep;
2018 stripLeading(fld[0]," \t");
2019 stripLeading(fld[1]," \t");
2020 ep.scanf(fld[0],fld[1]);
2021 (i==0 ? beginTime : endTime) = static_cast<CommonTime>(ep);
2022 }
2023 catch(Exception& e) { ok = false; LOG(INFO) << "excep " << e.what(); }
2024 }
2025 else if(n == 2 || n == 6) { // try the defaults
2026 try {
2027 Epoch ep;
2028 ep.scanf(msg,(n==2 ? fmtGPS : fmtCAL));
2029 (i==0 ? beginTime : endTime) = static_cast<CommonTime>(ep);
2030 (i==0 ? beginTime : endTime).setTimeSystem(TimeSystem::Any);
2031 }
2032 catch(Exception& e) { ok = false; LOG(INFO) << "excep " << e.what(); }
2033 }
2034
2035 if(ok) {
2036 msg = printTime((i==0 ? beginTime : endTime),fmtGPS+" = "+fmtCAL+" %P");
2037 if(msg.find("Error") != string::npos) ok = false;
2038 }
2039
2040 if(!ok)
2041 oss << "Error : invalid time or format in --" << (i==0 ? "start" : "stop")
2042 << " " << (i==0 ? startStr : stopStr) << endl;
2043 else
2044 ossx << (i==0 ? " Begin time --start" : " End time --stop") << " is "
2045 << printTime((i==0 ? beginTime : endTime), fmtGPS+" = "+fmtCAL+" %P\n");
2046 }
2047
2048 // trop model and default weather
2049 if(!TropStr.empty()) {
2050 fld = split(TropStr,',');
2051 if(fld.size() != 1 && fld.size() != 4) {
2052 oss << "Error : invalid format or number of fields in --Trop arg: "
2053 << TropStr << endl;
2054 }
2055 else {
2056 msg = fld[0];
2057 upperCase(msg);
2058 if (msg=="ZERO") { pTrop = new ZeroTropModel(); TropType = "Zero"; }
2059 else if(msg=="BLACK") { pTrop = new SimpleTropModel(); TropType = "Black"; }
2060 else if(msg=="SAAS") { pTrop = new SaasTropModel(); TropType = "Saas"; }
2061 else if(msg=="NEWB") { pTrop = new NBTropModel(); TropType = "NewB"; }
2062 else if(msg=="GG") { pTrop = new GGTropModel(); TropType = "GG"; }
2063 else if(msg=="GGHT") { pTrop = new GGHeightTropModel(); TropType = "GGht"; }
2064 else if(msg=="NEILL") { pTrop = new NeillTropModel(); TropType = "Neill"; }
2065 else if(msg=="GLOBAL") { pTrop = new GlobalTropModel(); TropType = "Global";}
2066 else {
2067 msg = string();
2068 oss << "Error : invalid trop model (" << fld[0] << "); choose one of "
2069 << "Zero,Black,Saas,NewB,GG,GGht,Neill,Global (cf. gpstk::TropModel)"
2070 << endl;
2071 }
2072
2073 if(!msg.empty() && !pTrop) {
2074 oss << "Error : failed to create trop model " << TropType << endl;
2075 }
2076
2077 if(fld.size() == 4) {
2078 defaultTemp = asDouble(fld[1]);
2079 defaultPress = asDouble(fld[2]);
2080 defaultHumid = asDouble(fld[3]);
2081 }
2082
2083 pTrop->setWeather(defaultTemp,defaultPress,defaultHumid);
2084 }
2085 }
2086
2087 // open the log file (so warnings, configuration summary, etc can go there) -----
2088 logstrm.open(LogFile.c_str(), ios::out);
2089 if(!logstrm.is_open()) {
2090 LOG(ERROR) << "Error : Failed to open log file " << LogFile;
2091 return -4;
2092 }
2093 LOG(INFO) << "Output redirected to log file " << LogFile;
2094 pLOGstrm = &logstrm;
2095 LOG(INFO) << Title;
2096
2097 // help, debug and verbose handeled automatically by CommandLine
2098 verbose = (LOGlevel >= VERBOSE);
2099 debug = (LOGlevel - DEBUG);
2100
2101 // check consistency
2102 if(elevLimit>0. && knownPos.getCoordinateSystem()==Position::Unknown && !forceElev)
2103 oss << "Error : --elev with no --ref input requires --forceElev\n";
2104
2105 if(forceElev && elevLimit <= 0.0)
2106 ossx << " Warning : --forceElev with no --elev <= 0 appears.\n";
2107 else if(forceElev && knownPos.getCoordinateSystem() == Position::Unknown)
2108 ossx << " Warning : with --ref input, --forceElev is not required.\n";
2109
2110 if(!OutputORDFile.empty() && knownPos.getCoordinateSystem() == Position::Unknown)
2111 oss << "Error : --ORDs requires --ref\n";
2112
2113 if(InputNavFiles.size() > 0 && InputSP3Files.size() > 0)
2114 oss << "Error : Both --nav and --eph appear: provide only one.\n";
2115
2116 //
2117 if(LOGlevel != 2)
2118 ossx << " LOG level is " << ConfigureLOG::ToString(LOGlevel) << "\n";
2119
2120 // add new errors to the list
2121 msg = oss.str();
2122 //if(!msg.empty()) cmdlineErrors += msg;
2123 if(!msg.empty()) errors += msg;
2124 msg = ossx.str();
2125 //if(!msg.empty()) cmdlineExtras += msg;
2126 if(!msg.empty()) extras += msg;
2127
2128 return 0;
2129
2130 } // end Configuration::ExtraProcessing() throw()
2131
2132 //------------------------------------------------------------------------------------
2133 // update weather in the trop model using the Met store
setWeather(const CommonTime & ttag)2134 void Configuration::setWeather(const CommonTime& ttag)
2135 {
2136 try {
2137 Configuration& C(Configuration::Instance());
2138 static list<RinexMetData>::iterator it(MetStore.begin());
2139 list<RinexMetData>::iterator nextit;
2140 static CommonTime currentTime(C.gpsBeginTime);
2141 double dt;
2142
2143 while(it != MetStore.end()) {
2144 (nextit = it)++; // point to next entry after it
2145
2146 // // if ttag is before next but after current,
2147 if( (nextit != MetStore.end() && ttag < nextit->time && ttag >= it->time)
2148 || // OR there is no next, but ttag is w/in 15 min of current
2149 (nextit == MetStore.end() && (dt=ttag-it->time) >= 0.0 && dt < 900.0))
2150 {
2151 // skip if its already done
2152 if(it->time == currentTime) break;
2153 currentTime = it->time;
2154
2155 if(it->data.count(RinexMetHeader::TD) > 0)
2156 defaultTemp = it->data[RinexMetHeader::TD];
2157 if(it->data.count(RinexMetHeader::PR) > 0)
2158 defaultPress = it->data[RinexMetHeader::PR];
2159 if(it->data.count(RinexMetHeader::HR) > 0)
2160 defaultHumid = it->data[RinexMetHeader::HR];
2161
2162 LOG(DEBUG) << "Reset weather at "
2163 << printTime(ttag,longfmt) << " to " << printTime(currentTime,longfmt)
2164 << " " << defaultTemp
2165 << " " << defaultPress
2166 << " " << defaultHumid;
2167
2168 pTrop->setWeather(defaultTemp,defaultPress,defaultHumid);
2169
2170 break;
2171 }
2172
2173 // time is beyond next epoch
2174 else if(nextit != MetStore.end() && ttag >= nextit->time)
2175 ++it;
2176
2177 // do nothing, because ttag is before the next epoch
2178 else break;
2179 }
2180 }
2181 catch(Exception& e) { GPSTK_RETHROW(e); }
2182 }
2183
2184 //------------------------------------------------------------------------------------
2185 //------------------------------------------------------------------------------------
2186 // handles mixed system descriptors (desc+desc) by split and call itself
2187 // return true if system/freq/codes are consistent
ValidateDescriptor(const string desc,string & msg)2188 bool SolutionObject::ValidateDescriptor(const string desc, string& msg)
2189 {
2190 stripLeading(desc);
2191 stripTrailing(desc);
2192 if(desc.empty()) return false;
2193
2194 size_t i,j;
2195 vector<string> fields = split(desc,'+');
2196 if(fields.size() > 1) {
2197 for(i=0; i<fields.size(); i++)
2198 if(! ValidateDescriptor(fields[i],msg))
2199 return false;
2200 return true;
2201 }
2202
2203 // Now we can assumes descriptor is single system and does NOT contain +
2204 fields = split(desc,':');
2205
2206 // test system
2207 string sys(fields[0]);
2208 string sys1(RinexObsID::map3to1sys[sys]);
2209 if(!sys1.size() || RinexObsID::validRinexSystems.find_first_of(sys1[0])==string::npos) {
2210 msg = desc + " : invalid system /" + sys + "/";
2211 return false;
2212 }
2213 char csys(sys1[0]);
2214
2215 // test freq(s) and code(s)
2216 if(fields[1].size() > 2) {
2217 msg = desc + " : only 1 or 2 frequencies allowed";
2218 return false;
2219 }
2220
2221 for(i=0; i<fields[1].size(); i++) {
2222 if(RinexObsID::validRinexTrackingCodes[csys].find(fields[1][i]) ==
2223 RinexObsID::validRinexTrackingCodes[csys].end())
2224 {
2225 msg = desc + string(" : invalid frequency /") + fields[1][i] + string("/");
2226 return false;
2227 }
2228 string codes = RinexObsID::validRinexTrackingCodes[csys][fields[1][i]];
2229 // GPS C1N and C2N are not allowed
2230 if(csys == 'G' && (fields[1][i] == '1'||fields[1][i] == '2')) strip(codes,'N');
2231 for(j=0; j<fields[2].size(); j++) {
2232 if(codes.find_first_of(fields[2][j]) == string::npos) {
2233 msg = desc + string(" : invalid code /") + fields[2][j] + string("/");
2234 return false;
2235 }
2236 }
2237 }
2238
2239 return true;
2240 }
2241
2242 //------------------------------------------------------------------------------------
2243 // parse descriptor into member data and 'sysChars'
2244 // called by Initialize() which is called by c'tor
2245 // assumes descriptor has been validated.
ParseDescriptor(void)2246 void SolutionObject::ParseDescriptor(void) throw()
2247 {
2248 size_t i;
2249 vector<string> fields;
2250 sysChars.clear(); // sysChars ~ vec<string> ~ G,R,E,C,S,etc
2251
2252 // split into components on '+'
2253 vector<string> descs(split(Descriptor,"+"));
2254 for(i=0; i<descs.size(); i++) { // loop over component descriptors
2255
2256 // create a SolutionData object for each component, of form SYS:F:Codes
2257 // vector<SolutionData> vecSolData;
2258 SolutionData sd(descs[i]);
2259 LOG(DEBUG) << "Parser(" << i << "): " << sd.asString();
2260
2261 string sys1(sd.getSys()), sys3(RinexObsID::map3to1sys[sys1]), frs(sd.getFreq());
2262
2263 // systems allowed in the PRSolution estimation
2264 if(find(sysChars.begin(),sysChars.end(),sys1) == sysChars.end()) {
2265 sysChars.push_back(sys1); // add to sysChars
2266 RinexSatID sat(sys1);
2267 prs.allowedGNSS.push_back(sat.system);
2268 }
2269
2270 vecSolData.push_back(sd);
2271
2272 } // end loop over component descriptors
2273 }
2274
2275 //------------------------------------------------------------------------------------
2276 // Given a RINEX header, verify that the necessary ObsIDs are present, and
2277 // define an ordered set of ObsIDs for each required SolutionData.
2278 // Return true if enough ObsIDs are found (in header) to compute the solution.
ChooseObsIDs(map<string,vector<RinexObsID>> & mapObsTypes)2279 bool SolutionObject::ChooseObsIDs(map<string,vector<RinexObsID> >& mapObsTypes)
2280 throw()
2281 {
2282 size_t i;
2283
2284 Configuration& C(Configuration::Instance());
2285
2286 isValid = true;
2287
2288 for (i=0; i<vecSolData.size(); i++)
2289 {
2290 SolutionData& sd(vecSolData[i]);
2291 bool coi = sd.ChooseObsIDs(mapObsTypes);
2292 if (!coi)
2293 {
2294 isValid = false;
2295 return false;
2296 }
2297 LOG(DEBUG) << " Chooser: " << vecSolData[i].asString();
2298 }
2299
2300 return isValid;
2301 }
2302
2303 //------------------------------------------------------------------------------------
dump(int level,string msg1,string msg2)2304 string SolutionObject::dump(int level, string msg1, string msg2) throw()
2305 {
2306 int j;
2307 size_t i;
2308 ostringstream oss;
2309
2310 Configuration& C(Configuration::Instance());
2311
2312 if(level == -1) { // header
2313 oss << "#" << msg1 << " " << Descriptor;
2314 oss << " DAT time Nsat Nobs sat:freq1obs1freq2obs2 ...";
2315 }
2316
2317 else if(level == 0) {
2318 oss << msg1 << " " << Descriptor << (msg2.empty() ? "" : " "+msg2);
2319 for(i=0; i<vecSolData.size(); i++)
2320 oss << " [" << i << "]" << vecSolData[i].asString();
2321 }
2322
2323 else if(level >= 1) {
2324 oss << msg1 << " " << Descriptor << (msg2.empty() ? "" : " "+msg2);
2325 // Descriptor ndata [-]sat:ot,ot[:PR] ... (-: no data, PR: data in level 2)
2326 oss << " " << setw(2) << Satellites.size()
2327 << " " << setw(2) << UsedObsIDs.size();
2328
2329 // loop over all potential data: sats+code(s); j counts good values
2330 multimap<RinexSatID,string>::const_iterator it(UsedObsIDs.begin());
2331 for(j=0; it != UsedObsIDs.end(); ++it) {
2332 // is the sat (it->first) found in Satellites (i.e. does it have data)?
2333 vector<SatID>::const_iterator jt;
2334 jt = find(Satellites.begin(),Satellites.end(),it->first);
2335
2336 // and all code(s) found ?
2337 bool good(jt != Satellites.end() && it->second.find('-') == string::npos);
2338
2339 // dump it, putting a - in front of sat if its not good
2340 oss << " " << (good ? "":"-") << it->first << ":" << it->second;
2341
2342 // add data if level 2 and its available
2343 if(level > 1 && good)
2344 oss << ":" << fixed << setprecision(3) << PRanges[j++];
2345 }
2346 }
2347
2348 // valid?
2349 if(!isValid) oss << " Invalid";
2350
2351 return oss.str();
2352 }
2353
2354 //------------------------------------------------------------------------------------
EpochReset(void)2355 void SolutionObject::EpochReset(void) throw()
2356 {
2357 Satellites.clear();
2358 PRanges.clear();
2359 Elevations.clear();
2360 ERanges.clear();
2361 RIono.clear();
2362 R1.clear();
2363 R2.clear();
2364 UsedObsIDs.clear();
2365 }
2366
2367 //------------------------------------------------------------------------------------
CollectData(const RinexSatID & sat,const double & elev,const double & ER,const vector<RinexDatum> & vrd)2368 void SolutionObject::CollectData(const RinexSatID& sat,
2369 const double& elev, const double& ER,
2370 const vector<RinexDatum>& vrd)
2371 throw()
2372 {
2373 if(!isValid) return;
2374
2375 for(size_t i=0; i<vecSolData.size(); i++) {
2376 if(vecSolData[i].ComputeData(sat,vrd)) {
2377 // add to data for this solution
2378 Satellites.push_back(sat);
2379 PRanges.push_back(vecSolData[i].PR);
2380 Elevations.push_back(elev);
2381 ERanges.push_back(ER);
2382 RIono.push_back(vecSolData[i].RI);
2383 R1.push_back(vecSolData[i].RawPR[0]);
2384 if(vecSolData[i].RawPR.size() > 1) R2.push_back(vecSolData[i].RawPR[1]);
2385 else R2.push_back(0.0);
2386 UsedObsIDs.insert(
2387 multimap<RinexSatID,string>::value_type(sat,vecSolData[i].usedString()));
2388 }
2389 }
2390 }
2391
2392 //------------------------------------------------------------------------------------
2393 // return 0 good, negative failure - same as RAIMCompute
ComputeSolution(const CommonTime & ttag)2394 int SolutionObject::ComputeSolution(const CommonTime& ttag)
2395 {
2396 try {
2397 int i,n,iret;
2398 Configuration& C(Configuration::Instance());
2399
2400 // is there data?
2401 if(Satellites.size() < 4) {
2402 LOG(VERBOSE) << "Solution algorithm failed, not enough data"
2403 << " for " << Descriptor
2404 << " at time " << printTime(ttag,C.longfmt);
2405 return -3;
2406 }
2407
2408 // compute the inverse measurement covariance
2409 Matrix<double> invMCov; // default is empty - no weighting
2410 if(C.weight) {
2411 n = Elevations.size();
2412 invMCov = Matrix<double>(n,n);
2413 ident(invMCov); // start with identity
2414 static const double elev0(30.0);
2415 static const double sin0(::sin(elev0 * DEG_TO_RAD));
2416 for(i=0; i<n; i++) if(Elevations[i] < elev0) { // mod only el<30
2417 double invsig(::sin(Elevations[i] * DEG_TO_RAD) / sin0);
2418 invMCov(i,i) = invsig*invsig;
2419 }
2420 LOG(DEBUG) << "invMeasCov for " << Descriptor
2421 << " at time " << printTime(ttag,C.longfmt) << "\n"
2422 << fixed << setprecision(4) << invMCov;
2423 }
2424
2425 // get the straight solution --------------------------------------
2426 if(C.SPSout) {
2427 Matrix<double> SVP;
2428 iret=prs.PreparePRSolution(ttag, Satellites, PRanges, C.pEph, SVP);
2429
2430 if(iret > -3) {
2431 Vector<double> Resid,Slopes;
2432 iret = prs.SimplePRSolution(ttag, Satellites, SVP, invMCov, C.pTrop,
2433 prs.MaxNIterations, prs.ConvergenceLimit,
2434 Resid, Slopes);
2435 }
2436
2437 if(iret < 0) { LOG(VERBOSE) << "SimplePRS failed "
2438 << (iret==-4 ? "to find ANY ephemeris" :
2439 (iret==-3 ? "to find enough satellites with data" :
2440 (iret==-2 ? "because the problem is singular" :
2441 /*iret==-1*/ "because the algorithm failed to converge")))
2442 << " for " << Descriptor
2443 << " at time " << printTime(ttag,C.longfmt) << " iret " << iret;
2444 }
2445 else {
2446 // at this point we have a good solution
2447
2448 // output XYZ solution
2449 LOG(INFO) << prs.outputString(string("SPS ")+Descriptor,iret);
2450
2451 if(prs.RMSFlag || prs.SlopeFlag || prs.TropFlag)
2452 LOG(WARNING) << "Warning for " << Descriptor
2453 << " - possible degraded SPS solution at "
2454 << printTime(ttag,C.longfmt) << " due to"
2455 << (prs.RMSFlag ? " large RMS":"") // NB strings are used
2456 << (prs.SlopeFlag ? " large slope":"") // in PRSplot.pl
2457 << (prs.TropFlag ? " missed trop. corr.":"");
2458
2459 // compute residuals using known position, output XYZ resids, NEU resids
2460 if(C.knownPos.getCoordinateSystem() != Position::Unknown && iret >= 0) {
2461 Matrix<double> Cov;
2462 Vector<double> V(3);
2463
2464 // compute residuals in XYZ
2465 Position pos(prs.Solution(0), prs.Solution(1), prs.Solution(2));
2466 Position res=pos-C.knownPos;
2467 // their covariance
2468 Cov = Matrix<double>(prs.Covariance,0,0,3,3);
2469 // output these as SPR record
2470 V(0) = res.X(); V(1) = res.Y(); V(2) = res.Z();
2471 LOG(INFO) << prs.outputPOSString(string("SPR ")+Descriptor,iret,V);
2472 // and accumulate statistics on XYZ residuals
2473 //statsSPSXYZresid.add(V,Cov);
2474
2475 // convert to NEU
2476 V = C.Rot * V;
2477 Cov = C.Rot * Cov * transpose(C.Rot);
2478 // output them as RNE record
2479 LOG(INFO) << prs.outputPOSString(string("SNE ")+Descriptor,iret,V);
2480 // and accumulate statistics on NEU residuals
2481 //statsSPSNEUresid.add(V,Cov);
2482 }
2483 }
2484 } // end if SPSout
2485
2486 // get the RAIM solution ------------------------------------------
2487 iret = prs.RAIMCompute(ttag, Satellites, PRanges, invMCov, C.pEph, C.pTrop);
2488
2489 if(iret < 0) {
2490 LOG(VERBOSE) << "RAIMCompute failed "
2491 << (iret==-4 ? "to find ANY ephemeris" :
2492 (iret==-3 ? "to find enough satellites with data" :
2493 (iret==-2 ? "because the problem is singular" :
2494 /*iret==-1*/ "because the algorithm failed to converge")))
2495 << " for " << Descriptor
2496 << " at time " << printTime(ttag,C.longfmt);
2497 return iret;
2498 }
2499
2500 // at this point we have a good RAIM solution
2501
2502 // output XYZ solution
2503 LOG(INFO) << prs.outputString(string("RPF ")+Descriptor,iret);
2504
2505 if(prs.RMSFlag || prs.SlopeFlag || prs.TropFlag)
2506 LOG(WARNING) << "Warning for " << Descriptor
2507 << " - possible degraded RPF solution at "
2508 << printTime(ttag,C.longfmt) << " due to"
2509 << (prs.RMSFlag ? " large RMS":"") // NB these strings are used
2510 << (prs.SlopeFlag ? " large slope":"") // in PRSplot.pl
2511 << (prs.TropFlag ? " missed trop. corr.":"");
2512
2513 // dump pre-fit residuals
2514 if(prs.hasMemory && ++nepochs > 1)
2515 LOG(VERBOSE) << "RPF " << Descriptor << " PFR"
2516 << " " << printTime(ttag,C.gpsfmt) // time
2517 << fixed << setprecision(3)
2518 << " " << ::sqrt(prs.getAPV()) // sig(APV)
2519 << " " << setw(2) << prs.PreFitResidual.size() // n resids
2520 << " " << prs.PreFitResidual; // pre-fit residuals
2521
2522 // compute residuals using known position, and output XYZ resids, NEU resids
2523 if(C.knownPos.getCoordinateSystem() != Position::Unknown && iret >= 0) {
2524 Matrix<double> Cov;
2525 Vector<double> V(3);
2526
2527 // compute residuals in XYZ
2528 Position pos(prs.Solution(0), prs.Solution(1), prs.Solution(2));
2529 Position res=pos-C.knownPos;
2530 // their covariance
2531 Cov = Matrix<double>(prs.Covariance,0,0,3,3);
2532 // output these as RPR record
2533 V(0) = res.X(); V(1) = res.Y(); V(2) = res.Z();
2534 LOG(INFO) << prs.outputPOSString(string("RPR ")+Descriptor,iret,V);
2535 // and accumulate statistics on XYZ residuals
2536 statsXYZresid.add(V,Cov);
2537
2538 // convert to NEU
2539 V = C.Rot * V;
2540 Cov = C.Rot * Cov * transpose(C.Rot);
2541 // output them as RNE record
2542 LOG(INFO) << prs.outputPOSString(string("RNE ")+Descriptor,iret,V);
2543 // and accumulate statistics on NEU residuals
2544 //if(iret == 0) // TD ? but not if RMS/Slope/TropFlag?
2545 statsNEUresid.add(V,Cov);
2546 }
2547
2548 // prepare for next epoch
2549
2550 // if trop model has not been initialized, do so
2551 if(!C.TropPos) {
2552 Position pos(prs.Solution(0), prs.Solution(1), prs.Solution(2));
2553 C.pTrop->setReceiverLatitude(pos.getGeodeticLatitude());
2554 C.pTrop->setReceiverHeight(pos.getHeight());
2555 C.TropPos = true;
2556 }
2557 if(!C.TropTime) {
2558 C.pTrop->setDayOfYear(static_cast<YDSTime>(ttag).doy);
2559 C.TropTime = true;
2560 }
2561
2562 return iret;
2563 }
2564 catch(Exception& e) { GPSTK_RETHROW(e); }
2565 }
2566
2567 //------------------------------------------------------------------------------------
WriteORDs(const CommonTime & time,const int iret)2568 int SolutionObject::WriteORDs(const CommonTime& time, const int iret)
2569 {
2570 try {
2571 Configuration& C(Configuration::Instance());
2572
2573 int j;
2574 size_t i;
2575 double clk;
2576 for(i=0; i<Satellites.size(); i++) {
2577 if(Satellites[i].id < 0) continue;
2578
2579 // get the system, then clock solution for this system
2580 vector<SatelliteSystem>::const_iterator jt;
2581 jt = find(prs.dataGNSS.begin(),prs.dataGNSS.end(),Satellites[i].system);
2582 if(jt == prs.dataGNSS.end()) continue; // should never happen
2583 j = jt - prs.dataGNSS.begin(); // index
2584 clk = prs.Solution(3+j);
2585
2586 C.ordstrm << "ORD " << RinexSatID(Satellites[i]).toString()
2587 << " " << printTime(time,C.userfmt) << fixed << setprecision(3)
2588 << " " << setw(6) << Elevations[i]
2589 << " " << setw(6) << RIono[i]
2590 << " " << setw(8) << R1[i] - ERanges[i] - clk
2591 << " " << setw(8) << R2[i] - ERanges[i] - clk
2592 << " " << setw(8) << PRanges[i] - ERanges[i] - clk
2593 << " " << setw(13) << clk
2594 //<< " " << setw(12) << PRanges[i]
2595 //<< " " << setw(12) << ERanges[i]
2596 << " " << Descriptor
2597 << " " << iret
2598 << endl;
2599 }
2600
2601 return 0;
2602 }
2603 catch(Exception& e) { GPSTK_RETHROW(e); }
2604 }
2605
2606 //------------------------------------------------------------------------------------
FinalOutput(void)2607 void SolutionObject::FinalOutput(void)
2608 {
2609 try {
2610 Configuration& C(Configuration::Instance());
2611
2612 if(prs.was.getN() <= 0) {
2613 LOG(INFO) << " No data!";
2614 return;
2615 }
2616
2617 prs.dumpSolution(LOGstrm,Descriptor+" RAIM solution");
2618 LOG(INFO) << " ";
2619
2620 if(C.knownPos.getCoordinateSystem() != Position::Unknown) {
2621 // output stats on XYZ residuals
2622 statsXYZresid.setMessage(Descriptor + " RAIM XYZ position residuals (m)");
2623 LOG(INFO) << statsXYZresid << endl;
2624
2625 // output stats on NEU residuals
2626 statsNEUresid.setMessage(Descriptor + " RAIM NEU position residuals (m)");
2627 statsNEUresid.setLabels("North","East ","Up ");
2628 LOG(INFO) << statsNEUresid;
2629
2630 // output the covariance for NEU
2631 double apv(::sqrt(prs.getAPV())); // APV from XYZ stats
2632 if(apv > 0.0) {
2633 Matrix<double> Cov(statsNEUresid.getCov()); // cov from NEU stats
2634
2635 // scale the covariance
2636 for(size_t i=0; i<Cov.rows(); i++) for(size_t j=i; j<Cov.cols(); j++)
2637 Cov(i,j) = Cov(j,i) = Cov(i,j)*apv;
2638
2639 // print this covariance as labelled matrix
2640 Namelist NL;
2641 NL += "North"; NL += "East "; NL += "Up ";
2642 LabeledMatrix LM(NL,Cov);
2643 LM.scientific().setprecision(3).setw(14);
2644 LOG(INFO) << "Covariance of " << statsNEUresid.getMessage() << endl << LM;
2645 }
2646 }
2647
2648 }
2649 catch(Exception& e) { GPSTK_RETHROW(e); }
2650 }
2651
2652 //------------------------------------------------------------------------------------
2653 //------------------------------------------------------------------------------------
2654