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