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 dfix.cpp Discontinuity detection and correction (cycleslip fixer) using
40 ///                  class gdc.
41 
42 //------------------------------------------------------------------------------------
43 // system includes
44 #include <ctime>
45 #include <iostream>
46 #include <iomanip>
47 #include <fstream>
48 #include <string>
49 #include <vector>
50 #include <map>
51 // GPSTk
52 #include "Exception.hpp"
53 #include "StringUtils.hpp"
54 #include "Epoch.hpp"
55 #include "RinexSatID.hpp"
56 #include "Position.hpp"
57 #include "RinexUtilities.hpp"
58 #include "EphemerisRange.hpp"
59 #include "singleton.hpp"
60 #include "GNSSconstants.hpp"
61 #include "msecHandler.hpp"
62 #include "stl_helpers.hpp"
63 // geomatics
64 #include "expandtilde.hpp"
65 #include "logstream.hpp"
66 #include "SatPass.hpp"
67 #include "SatPassUtilities.hpp"
68 #include "Rinex3ObsFileLoader.hpp"
69 // dfix
70 #include "CommandLine.hpp"
71 #include "gdc.hpp"
72 
73 //------------------------------------------------------------------------------------
74 using namespace std;
75 using namespace gpstk;
76 
77 //------------------------------------------------------------------------------------
78 //------------------------------------------------------------------------------------
79 // prototypes
80 /// Read and process command line
81 /// @throw Exception
82 int GetCommandLine(int argc, char **argv);
83 
84 /// Validate input -- check that files exist, append path, quit if user chose validate
85 /// @throw Exception
86 int ValidateInput(void);
87 
88 /// Load ephemeris stores
89 /// @throw Exception
90 int Initialize(void);
91 
92 /// Choose R3ObsIDs given user input, read RINEX file(s) into SatPass list
93 /// @return 0 success, <0 error code
94 /// @throw Exception
95 int ReadRinexFiles(void);
96 
97 /// Remove milliseconds, mark low elevations
98 /// @throw Exception
99 int PreProcess(void);
100 
101 /// Call GDC for each pass and output
102 /// @throw Exception
103 int Process(void);
104 
105 //------------------------------------------------------------------------------------
106 //------------------------------------------------------------------------------------
107 /// Class GlobalData (a singleton) encapsulates global static data as well as
108 /// command line definition and processing
109 class GlobalData : public Singleton<GlobalData> {
110 public:
111    //// Default and only constructor, sets defaults.
GlobalData()112    GlobalData() throw() { SetDefaults(); }
113 
114    // prgm housekeeping
115    static const string Version;  ///< version string - see below
116    string PrgmName;              ///< this program name
117    string Title;                 ///< name, version and runtime
118    string logfile;               ///< name of log file
119    ofstream oflog;               ///< output log file stream
120 
121    // command line input ----------------------------------------------------------
122    bool inputIsValid;            ///< if true, quit after Validate()
123 
124    /// strings used by GetCommandLine - usage, errors, etc
125    string cmdlineErrors,cmdlineDump,cmdlineUsage,cmdlineExtras;
126    vector<string> cmdlineUnrecognized;
127 
128    string logpath,obspath;       ///< paths for log and obs files
129    vector<string> obsfiles;      ///< input obs files - required
130    string obsout, cmdout;        ///< output obs and command files
131 
132    vector<string> wantedObsIDs;  ///< 4-char obs types wanted in the data store
133    unsigned int def_wanted_in;   ///< the number of default wantedObsIDs elements
134    vector<string> syscode_in;    ///< systems:codes input
135    unsigned int def_syscode_in;  ///< the number of default syscode_in elements
136 
137    Epoch startTime, stopTime;    ///< start and stop times for data
138    double decdt;                 ///< decimate data to this timestep (sec)
139    map<RinexSatID,int> GLOfreqCh;///< input freq channel - overrides eph input
140 
141    vector<string> DCcmds;        ///< GDC editing cmds - written to cmdout
142    vector<RinexSatID> exSat;     ///< ignore these sats
143    vector<RinexSatID> onlySat;   ///< process only these sats
144    vector<int> exPass;           ///< ignore these passes
145    vector<int> onlyPass;         ///< process only these passes
146    bool fixMS;
147 
148    // ephemeris and refpos input
149    vector<string> SP3files;      ///< SP3 ephemeris file names
150    vector<string> RNavfiles;     ///< RINEX nav file names
151    string ephpath;               ///< path for nav/ephemeris files
152    SP3EphemerisStore SP3EphList; ///< Store of SP3 ephemeris data
153    GPSEphemerisStore BCEphList;  ///< Store of NAV ephemeris data
154    XvtStore<SatID> *pEph;        ///< Pointer to chosen ephemeris store
155    Position Rx;                  ///< Receiver position provided by user
156    double elevLimit;             ///< Elevation angle lower limit (deg)
157    bool doElev;                  ///< set true if elevation screening is to be done
158 
159    vector<string> outlabels;     ///< tell GDC to output labels
160    bool typehelp;                ///< print all RINEX3 ObsIDs, then quit
161    bool DChelp;                  ///< help for the GDC
162    bool DChelpall;               ///< help for the GDC with advanced options
163    bool validate;                ///< validate the input, then quit
164    string timefmt;               ///< output format for GDC
165    // end command line input ------------------------------------------------------
166 
167    bool verbose;                 ///< flag handled by CommandLine
168    int debug;                    ///< int handled by CommandLine
169 
170    double nomdt;                 ///< nominal timestep of data
171    vector<SatPass> SPList;       ///< the SatPass list
172    vector<string> obstypes;      ///< list of 4 obstypes e.g. L1 L2 P1 P2
173    /// map<sys,vector<string>> of SatPass obstypes each system parallel to obstypes
174    map<char, vector<string> > SPsysobs;
175    /// map<sys,vector<string>> of R3 obsID each system parallel to SPsysobs & obstypes
176    map<char, vector<string> > R3sysobs;
177 
178    // these parallel : they come from --syscode s:codes,s
179    vector<string> Syss;          ///< systems, 1-char strings
180    vector<string> Codes;         ///< preferred codes, in order of "quality"
181 
182    Rinex3ObsHeader header;       ///< save for RINEX output
183 
184    gdc GDC;                      ///< the GDC object
185 
186    vector<string> EditCmds;      ///< editing commands returned by GDC - write cmdout
187    string longfmt;               ///< times in loader, error messages, etc.
188 
189 private:
190 
191    /// Define default values, called by c'tor
SetDefaults(void)192    void SetDefaults(void) throw()
193    {
194       PrgmName = string("dfix");
195       logfile = string();
196 
197       // command line input -------------------------------------------
198       // input control
199       startTime = CommonTime::BEGINNING_OF_TIME;
200       stopTime = CommonTime::END_OF_TIME;
201 
202       // default obs types - GPS only, ignored if user provides input
203       wantedObsIDs.push_back("GC1*");
204       wantedObsIDs.push_back("GC2*");
205       wantedObsIDs.push_back("GL1*");
206       wantedObsIDs.push_back("GL2*");
207       def_wanted_in = wantedObsIDs.size();
208 
209       // default tracking codes, ignored if user provides input
210       // NB since GPS L2C (code X,L) is not constellation-wide, suggest G:PYWCXL
211       syscode_in.push_back("G:PYWCXL");
212       syscode_in.push_back("R:PC");
213       def_syscode_in = syscode_in.size();
214 
215       // editing
216       decdt = -1.0;
217       fixMS = doElev = false;
218       elevLimit = 0.0;
219 
220       // output
221       DChelp = DChelpall = typehelp = validate = false;
222       timefmt = string("%4F %10.3g");
223       //timefmt = string("%.6Q");         // 1.5/86400 = 1.7e-5
224 
225       // end command line input ---------------------------------------
226 
227       longfmt = string("%04F %10.3g %04Y/%02m/%02d %02H:%02M:%06.3f %P");
228    }
229 
230 }; // end class GlobalData
231 
232 //------------------------------------------------------------------------------------
233 /// version string
234 const string GlobalData::Version(string("3.0 6/20/19"));
235 
236 //------------------------------------------------------------------------------------
237 //------------------------------------------------------------------------------------
main(int argc,char ** argv)238 int main(int argc, char **argv)
239 {
240    string PrgmName;        // for catch only
241 try {
242    // begin counting time
243    clock_t totaltime = clock();
244    Epoch wallbegin,wallend;
245    wallbegin.setLocalTime();
246 
247    // get (create) the global data object (a singleton);
248    // since this is the first instance, this will also set default values
249    GlobalData& GD=GlobalData::Instance();
250    PrgmName = GD.PrgmName;
251 
252    // Build title
253    Epoch ttag;
254    ttag.setLocalTime();
255    GD.Title = GD.PrgmName + " ver " + GD.Version
256                            + ttag.printf(", Run %04Y/%02m/%02d at %02H:%02M:%02S");
257 
258    // display title on screen
259    LOG(INFO) << GD.Title;
260 
261    // TEMP, for debugging CommandLine;
262    //LOGlevel = ConfigureLOG::Level("DEBUG");
263 
264    // process : loop once -----------------------------------------------------
265    int iret;
266    for(bool go=true; go; go=false)  {
267 
268       // process the command line ------------------------------------
269       // open log file, dump config
270       iret = GetCommandLine(argc,argv);
271       if(iret) break;
272 
273       // check input, add paths to filenames
274       iret = ValidateInput();
275       if(iret) break;
276 
277       // fill ephemeris store
278       iret = Initialize();
279       if(iret) break;
280 
281       // read files into SatPassList
282       iret = ReadRinexFiles();
283       if(iret) break;
284 
285       // preprocess the data - millisec fix,
286       iret = PreProcess();
287       if(iret) break;
288 
289       // do it
290       iret = Process();
291       if(iret) break;
292 
293    }  // end loop once
294 
295    // error condition ---------------------------------------------------------
296    // return codes: 0 ok, -3,1..6 from CommandLine - see below
297    //               6 decimation invalid
298    //               7 failed to read RINEX file(s)
299    //               8 no good data
300    //LOG(INFO) << "Return code is " << iret;
301    if(iret != 0) {
302       if(iret != 1) {
303          string msg;
304          msg = GD.PrgmName + string(" is terminating with code ")
305                            + StringUtils::asString(iret);
306          LOG(ERROR) << msg;
307       }
308 
309       if(iret == 1) {                           // help(s) handled in GetCommand
310          ;
311       }
312       else if(iret == 2) { LOG(INFO) << GD.cmdlineErrors; }    // cmd line errors
313       else if(iret == 3) { LOG(INFO) << "The user requested input validation."; }
314       else if(iret == 4) { LOG(INFO) << "The input is invalid."; }
315       else if(iret == 5) { LOG(INFO) << "The log file could not be opened."; }
316       else if(iret == 6) { LOG(INFO) << "Decimation was configured incorrectly."; }
317       else if(iret == 7) { LOG(INFO) << "Failed to read all RINEX files."; }
318       else if(iret == 8) { LOG(INFO) << "No good data was found."; }
319       //else if(iret == 9) { LOG(INFO) << "Input read configuration is invalid."; }
320       else if(iret == -3) { // cmd line definition invalid
321          LOG(INFO) << "The command line definition is invalid.\n" << GD.cmdlineErrors;
322       }
323       else
324          LOG(INFO) << "temp - Some other return code...fix this" << iret;
325    }
326 
327    // compute and print run time ----------------------------------------------
328    if(iret != 1) {
329       wallend.setLocalTime();
330       totaltime = clock()-totaltime;
331       ostringstream oss;
332       oss << PrgmName << " timing: " << fixed << setprecision(3)
333          << double(totaltime)/double(CLOCKS_PER_SEC)
334          << " seconds. (" << (wallend - wallbegin) << " sec)";
335       LOG(INFO) << oss.str();
336       if(pLOGstrm != &cout) cout << oss.str() << endl;
337    }
338 
339    return (iret == 0 ? 0 : 1);
340 }
341 catch(Exception& e) {
342    cerr << PrgmName << " caught Exception:\n" << e.what() << endl;
343    // don't use LOG here - causes hangup - don't know why
344 }
345 catch (...) {
346    cerr << "Unknown error in " << PrgmName << ".  Abort." << endl;
347 }
348    return -1;
349 }   // end main()
350 
351 //------------------------------------------------------------------------------------
352 //------------------------------------------------------------------------------------
GetCommandLine(int argc,char ** argv)353 int GetCommandLine(int argc, char **argv)
354 {
355 try {
356    unsigned int i,j,k;
357    GlobalData& GD=GlobalData::Instance();
358    vector<string> GLOstrs;                            // input strings GLOsat:n
359    const string defaultstartStr("[Beginning of dataset]");
360    const string defaultstopStr("[End of dataset]");
361    string startStr(defaultstartStr), stopStr(defaultstopStr);
362    string str,refPosStr;
363 
364    // create list of command line options, and fill it
365    // put required options first - they will get listed first anyway
366    CommandLine opts;
367 
368    // build the options list == syntax page
369    string PrgmDesc =
370       " Program " + GD.PrgmName + " will read input RINEX obs file(s) and ..."
371       "\n Input is on the command line, or of the same format in a file "
372       "(see --file below);\n lines in that file which begin with '#' are ignored. "
373       "Accepted options are \n shown below, followed by a description, with default "
374       "value, if any, in ().";
375 
376    // opts.Add(char, opt, arg, repeat?, required?, &target, pre-descript, descript.);
377    // required options
378    bool req(true);
379    opts.Add('i', "obs", "name", true, req, &GD.obsfiles, "\n# Required input",
380             "Name of input RINEX observation file(s)");
381 
382    // optional args
383    req = false;
384    string dummy("");         // dummy for --file
385    opts.Add('f', "file", "name", true, req, &dummy, "\n# File I/O:",
386             "Name of file containing more options [#-EOL = comment]");
387    opts.Add('l', "log", "name", false, req, &GD.logfile, "",
388             "Name of output log file");
389    opts.Add(0, "logpath", "path", false, req, &GD.logpath, "",
390             "Path for output log file");
391    opts.Add(0, "obspath", "path", false, req, &GD.obspath, "",
392             "Path for input RINEX observation file(s)");
393    // Flow control
394    opts.Add(0, "start", "time", false, req, &startStr, "\n# Flow control "
395             "(time = MJD OR GPSweek,SOW OR YYYY,Mon,D,H,Min,S:",
396             "Start processing the input data at this time");
397    opts.Add(0, "stop", "time", false, req, &stopStr, "",
398             "Stop processing the input data at this time");
399    // RINEX input obs IDs
400    opts.Add(0, "obsID", "ot", true, req, &GD.wantedObsIDs,
401             "\n# RINEX3 Data Input (NB ObsIDs for dual-freq PR+phase required;"
402             " defaults erased if obsID input is detected):",
403             "RINEX3 Obs types (4-char) to read from files");
404    opts.Add(0, "syscode", "s[:c]", true, false, &GD.syscode_in, "",
405             "Allowed system:tracking_codes s:c, (c's in order); cf --typehelp");
406    // Data input and config
407    opts.Add(0, "dt", "name", false, req, &GD.decdt, "\n# Data input and config:",
408             "Decimate timestep of the data to this in seconds");
409    opts.Add(0, "DC", "cmd=val", true, req, &GD.DCcmds,"",
410             "Set algorithm configuration parameter (see --DChelp)");
411    opts.Add(0, "exSat", "sat", true, req, &GD.exSat, "\n# Editing:",
412             "Exclude satellite(s) [e.g. G24 or R14 or R]");
413    opts.Add(0, "onlySat", "sat", true, req, &GD.onlySat, "",
414             "Process given satellite(s) only");
415    opts.Add(0, "exPass", "npass", true, req, &GD.exPass, "",
416             "Exclude satellite pass number(s)");
417    opts.Add(0, "onlyPass", "npass", true, req, &GD.onlyPass, "",
418             "Process given satellite pass number(s) only");
419    opts.Add(0, "GLOfreq", "sat:n", true, false, &GLOstrs, "",
420             "GLO channel #s for each sat [e.g. R17:-4]");
421    opts.Add(0, "fixMS", "", false, false, &GD.fixMS, "",
422             "Fix millisecond clock adjusts before processing");
423    // Editing
424    opts.Add(0, "elev", "deg", false, req, &GD.elevLimit,
425             "\n# Exclude low elevation (req's elev>0, ref, and one of eph/nav):",
426             "Minimum elevation angle (deg)");
427    opts.Add(0, "ref", "X,Y,Z", false, req, &refPosStr, "",
428             "Known position (ECEF,m), for computing residuals and ORDs");
429    opts.Add(0, "eph", "fn", true, req, &GD.SP3files, "",
430             "Input Ephemeris+clock (SP3 format) file name");
431    opts.Add(0, "nav", "fn", true, req, &GD.RNavfiles, "",
432             "Input RINEX nav file name(s)");
433    opts.Add(0, "ephpath", "path", false, req, &GD.ephpath, "",
434             "Path for input SP3 or RINEX ephemeris file(s)");
435 
436    // Output
437    opts.Add(0, "validate", "", false, req, &GD.validate, "\n# Output:",
438             "Read input and test its validity, then quit");
439    opts.Add('o', "obsout", "name", true, req, &GD.obsout, "",
440             "Name of output (edited) RINEX observation file");
441    opts.Add(0, "cmdout", "name", true, req, &GD.cmdout, "",
442             "Name of output file for RINEX editing commands");
443    opts.Add(0, "dump", "label", true, req, &GD.outlabels, "",
444             "Tell DC to output 'label' data (or 'all') to log - cf. DChelpall");
445    opts.Add(0, "timefmt", "fmt", false, req, &GD.timefmt, "",
446             "Output timetags with this format [cf. class Epoch]");
447    // Help
448    opts.Add(0, "DChelp", "", false, req, &GD.DChelp, "\n# Help",
449             "Print list of DC parameters and their defaults, then quit");
450    opts.Add(0, "DChelpall", "", false, req, &GD.DChelpall, "",
451             "DChelp with advanced options included");
452    opts.Add(0, "typehelp", "", false, false, &GD.typehelp, "",
453             "Print this syntax page and table of all RINEX3 ObsIDs, and quit");
454 
455    // add options that are ignored (true if it has an arg)
456    //opts.Add_ignore("--PRSoutput",true);
457 
458    // deprecated args
459    //opts.Add_deprecated("--HtOffset","--ht");
460 
461    // --------------------------------------------------------------------------
462    // declare it and parse it; write all errors to string GD.cmdlineErrors
463    int iret = opts.ProcessCommandLine(argc,argv,PrgmDesc,
464                          GD.cmdlineUsage,GD.cmdlineErrors,GD.cmdlineUnrecognized);
465    if(iret == -2) return iret;      // bad alloc
466    if(iret == -3) return iret;      // cmd line definition invalid
467 
468    // help, debug and verbose handled automatically by CommandLine
469    GD.verbose = (LOGlevel >= VERBOSE);
470    GD.debug = (LOGlevel - DEBUG);
471 
472    // --------------------------------------------------------------------------
473    // do extra parsing -- append errors to GD.cmdlineErrors
474    string msg;
475    vector<string> fields;
476    ostringstream oss,ossx;
477 
478    // unrecognized arguments are an error
479    if(GD.cmdlineUnrecognized.size() > 0) {
480       oss << " Error - unrecognized arguments:\n";
481       for(i=0; i<GD.cmdlineUnrecognized.size(); i++)
482          oss << GD.cmdlineUnrecognized[i] << "\n";
483       oss << " End of unrecognized arguments\n";
484    }
485 
486    // configure the DC
487    if(GD.debug > -1) GD.GDC.setParameter("debug",GD.debug);
488    if(GD.verbose) GD.GDC.setParameter("verbose",1);
489    //GD.GDC.setParameter("DT",GD.dt);    // no - dt comes from SatPass
490 
491    for(i=0; i<GD.DCcmds.size(); i++) {
492       msg = StringUtils::replaceAll(GD.DCcmds[i],string(" "),string(""));
493       if(GD.GDC.setParameter(msg)) {
494          if(GD.verbose) ossx << "Set GDC parameter with " << msg << endl;
495       }
496       else
497          ossx << "   Warning - failed to set GDC parameter " << msg << endl;
498    }
499    for(i=0; i<GD.outlabels.size(); i++) {
500       if(StringUtils::lowerCase(GD.outlabels[i]) == string("all")) {
501          // NB keep this list updated with gdc; 1 means do, 0 means don't
502          GD.GDC.setParameter("RAW=1"); // data (WL,GF) before any processing
503          GD.GDC.setParameter("WL1=1"); // results of 1st diff filter on WL
504          GD.GDC.setParameter("WLG=1"); // WL after fixing gross slips (after fdif)
505          GD.GDC.setParameter("WLW=1"); // results of window filter on WL
506          GD.GDC.setParameter("WLF=1"); // WL after fixing
507          GD.GDC.setParameter("GF1=1"); // results of 1st diff filter on GF
508          GD.GDC.setParameter("GFG=1"); // GF after fixing gross slips (after fdif)
509          GD.GDC.setParameter("GFW=1"); // results of window filter on GF
510          GD.GDC.setParameter("GFF=1"); // GF after fixing
511          LOG(VERBOSE) << "Set GDC output to all data types";
512       }
513       else {
514          msg = GD.outlabels[i]+"=1";
515          if(GD.GDC.setParameter(msg)) {
516             if(GD.verbose) ossx << "Set GDC output to include data type "
517                                  << GD.outlabels[i] << endl;
518          }
519          else
520             ossx << "   Warning - failed to set GDC output to include data type "
521                                  << GD.outlabels[i] << endl;
522       }
523    }
524 
525    // RINEX output requested
526    if(!GD.obsout.empty()) GD.GDC.setParameter("doFix=1");
527    // Editing cmd output requested
528    if(!GD.cmdout.empty()) GD.GDC.setParameter("doCmds=1");
529 
530    // start and stop times
531    for(i=0; i<2; i++) {
532       msg = (i==0 ? startStr : stopStr);
533       if(msg == (i==0 ? defaultstartStr : defaultstopStr)) continue;
534 
535       int n(StringUtils::numWords(msg,','));
536       if(n != 1 && n != 2 && n != 6) {
537          oss << "Error - invalid argument in --" << (i==0 ? "start" : "stop")
538             << " " << (i==0 ? startStr : stopStr) << endl;
539          continue;
540       }
541 
542       string fmtMJD("%Q"),fmtGPS("%F,%g"),fmtCAL("%Y,%m,%d,%H,%M,%S");
543       try {
544          (i==0 ? GD.startTime:GD.stopTime).scanf(msg,
545             (n==1 ? fmtMJD : (n==2 ? fmtGPS : fmtCAL)));
546       }
547       catch(Exception&) {
548          oss << "Error - invalid time in --" << (i==0 ? "start" : "stop")
549             << " " << (i==0 ? startStr : stopStr) << endl;
550       }
551    }
552 
553    // GLONASS frequency channels
554    for(i=0; i<GLOstrs.size(); i++) {
555       msg = GLOstrs[i];
556       fields = StringUtils::split(msg,':');
557       if(fields.size() != 2) {
558          oss << "Error - invalid input in --GLOfreq: " << msg << endl;
559       }
560       else {
561          RinexSatID sat(fields[0]);
562          int chan(StringUtils::asInt(fields[1]));      // for Solaris
563          GD.GLOfreqCh.insert(make_pair(sat,chan));
564       }
565    }
566 
567    // reference position
568    if(!refPosStr.empty()) {
569       msg = refPosStr;
570       fields.clear();
571       while(msg.size() > 0) fields.push_back(StringUtils::stripFirstWord(msg,','));
572       if(fields.size() != 3)
573          oss << "Error - invalid field in --refPos input: " << refPosStr << endl;
574       else {
575          try { GD.Rx.setECEF(StringUtils::asDouble(fields[0]),
576                              StringUtils::asDouble(fields[1]),
577                              StringUtils::asDouble(fields[2]));
578          }
579          catch(Exception&) {
580             oss << "Error - invalid position in --refPos input: "
581                << refPosStr << endl;
582          }
583       }
584    }
585 
586    // Obs types
587    // Rinex3ObsHeader.cpp line ~1820 - mapping R2 -> R3:
588    // C1->C1C       C2->C2X         C5->C5X
589    // P1->C1W/Y     P2->C2X/W/Y     P5->P5X
590    // L1->L1C/Y     L2->L2X/W/Y     L5->L5X
591    // D1->D1C/Y     D2->D2X/W/Y     D5->D5X
592    // S1->S1C/Y     S2->S2X/W/Y     S5->S5X
593    // Y if have P2 and P is really Y code - this can't come from RINEX
594    // W if have P2 but P is not Y
595    // X if don't have P2
596    // NB P[12] never -> tracking code P!
597 
598    // obs IDs ---------------------------------------------
599    // remove the defaults if user provided inputs
600    if(GD.wantedObsIDs.size() > GD.def_wanted_in) {
601       vector<string> vtemp(GD.wantedObsIDs);
602       GD.wantedObsIDs.clear();
603       for(i=4; i< vtemp.size(); i++)
604          GD.wantedObsIDs.push_back(vtemp[i]);
605    }
606 
607    // systems and codes -----------------------------------
608    bool skip;
609    string freqs(RinexObsID::validRinexFrequencies);
610    // loop over input syscode_in, but note it will start with def_syscode_in default
611    // elements, before any user input, and must see if those defaults were overridden
612    for(i=0; i<GD.syscode_in.size(); i++) {
613       fields = StringUtils::split(GD.syscode_in[i],':');       // system:codes
614       string sys(fields[0]);                                // 1-char sys
615 
616       // if defaults are over-ridden by user input, skip them;
617       // def_syscode_in is the number of def.s
618       if(i < GD.def_syscode_in) {
619          skip = false;
620          for(j=i+1; j<GD.syscode_in.size(); j++)
621             if(GD.syscode_in[j][0] == sys[0]) { skip=true; break; }  // user input sys
622          if(skip) continue;
623       }
624 
625       // if there are no "wanted" ObsIDs for this system, skip it
626       skip = true;
627       for(j=0; j<GD.wantedObsIDs.size(); j++) {
628          if(GD.wantedObsIDs[j][0] == '*' || GD.wantedObsIDs[j][0] == sys[0])
629             { skip = false; break; }
630       }
631       if(skip) {
632          if(i >= GD.def_syscode_in)       // don't warn about the defaults
633             ossx << "   Warning - system " << sys << " was specified (--syscode)"
634                << " but no ObsIDs for it were specified (--obsID) - skip this system."
635                << endl;
636          continue;
637       }
638 
639       // For now, allow only GPS and GLO
640       if(sys != "G" && sys != "R") {
641          ossx << "   Warning - only GPS and GLONASS are currently supported." << endl;
642          continue;
643       }
644 
645       // ok, save the system and codes
646       GD.Syss.push_back(sys);             // keep parallel to Codes
647       if(fields.size() > 1)
648          str = fields[1];                 // user-supplied codes
649       else {                              // default codes - combine all frequencies
650          str = string("");
651          for(j=0; j<freqs.size(); j++) {
652             // default codes for freq j
653             string strf(RinexObsID::validRinexTrackingCodes[sys[0]][freqs[j]]);
654             StringUtils::stripTrailing(strf,"* ");
655             if(str.empty() && !strf.empty()) str=strf;
656             else for(k=0; k<strf.size(); k++) {
657                if(str.find_first_of(strf.substr(k,1),0) == string::npos)
658                   str += string(1,strf[k]);
659             }
660          }
661       }
662       GD.Codes.push_back(str);               // keep parallel to GD.Syss
663       ossx << "   Include system:codes " << sys << ":" << str << endl;
664    }
665 
666    // check wantedObsIDs for invalid/no-system (NB ROFL::loadObsID also checks size)
667    for(i=0; i<GD.wantedObsIDs.size(); i++) {
668       if(GD.wantedObsIDs[i].size() != 4) {
669          oss << "Error : invalid RINEX3 obsID (not 4-char): "
670                         << GD.wantedObsIDs[i] << endl;
671          continue;
672       }
673       str = GD.wantedObsIDs[i].substr(0,1);
674       if(str != "*" && vectorindex(GD.Syss,str) == -1) {
675          oss << "Error : invalid RINEX3 obsID (system not found): "
676             << GD.wantedObsIDs[i] << endl;
677          continue;
678       }
679       // test using ROFL
680       Rinex3ObsFileLoader rofl;
681       if(!rofl.loadObsID(GD.wantedObsIDs[i])) {
682          oss << "Error : invalid or duplicate RINEX3 (3-char) obsID: "
683             << GD.wantedObsIDs[i] << endl;
684          continue;
685       }
686       ossx << "   Request ObsID " << GD.wantedObsIDs[i] << endl;
687    }
688 
689    // extra msg for debug<n>
690    ossx << "   NB. debug0/1/2/3 <=> --dump WLF,GFF,FIN / +RAW / +WL1,WLG,GF1,GFG"
691          << " / +WLW,GFW" << endl;
692 
693    // build list of desired obstypes - C may replace P within the loader
694    GD.obstypes.push_back(string("L1"));
695    GD.obstypes.push_back(string("L2"));
696    GD.obstypes.push_back(string("P1"));
697    GD.obstypes.push_back(string("P2"));
698 
699    // --------------------------------------------------------------------------
700    // append errors
701    GD.cmdlineErrors += oss.str();
702    GD.cmdlineExtras += ossx.str();
703 
704    LOG(DEBUG) << GD.cmdlineUsage;  // this will contain list of args
705 
706    // dump it
707    oss.str("");         // clear it
708    oss << "#------ Summary of " << GD.PrgmName
709       << " command line configuration --------" << endl;
710    opts.DumpConfiguration(oss);
711 
712    // dump the 'extra parsing' things
713    oss << "\n# Extra Processing:\n" << GD.cmdlineExtras;
714    if(GD.verbose && GD.GLOfreqCh.size() > 0) {
715       oss << "#\n# GLO frequency channels:";
716       map<RinexSatID,int>::const_iterator it(GD.GLOfreqCh.begin());
717       i = 0;
718       for( ; it != GD.GLOfreqCh.end(); ++it) {
719          oss << " " << it->first << ":" << it->second;
720          if((++i % 9)==0) { i=0; oss << endl << "#                        "; }
721       }
722       oss << endl;
723    }
724    oss << "#------ End configuration summary --------";
725 
726    GD.cmdlineDump = oss.str();
727 
728    if(opts.hasHelp() || GD.DChelp || GD.DChelpall || GD.typehelp) {
729       LOG(INFO) << GD.cmdlineUsage;
730       // handle DChelp
731       if(GD.DChelpall || GD.DChelp) {
732          LOG(INFO) << "";
733          GD.GDC.DisplayParameterUsage(LOGstrm,"#",GD.DChelpall);
734       }
735       if(GD.typehelp) dumpAllRinex3ObsTypes(LOGstrm);
736       return 1;
737    }
738    if(opts.hasErrors()) return 2;
739    if(!GD.cmdlineErrors.empty()) {
740       //LOG(INFO) << "RETURNING invalid b/c of errors:\n" << GD.cmdlineErrors;
741       return 2;
742    }
743 
744    // --------------------------------------------------------------------
745    // Open log file, if it exists
746    if(!GD.logfile.empty()) {
747       GD.oflog.open(GD.logfile.c_str(),ios_base::out);
748       if(!GD.oflog.is_open()) {
749          cerr << "Failed to open log file " << GD.logfile << endl;
750          return 5;
751       }
752       LOG(INFO) << "Output directed to log file " << GD.logfile;
753       pLOGstrm = &GD.oflog; // ConfigureLOG::Stream() = &GD.oflog;
754       LOG(INFO) << GD.Title;
755    }
756 
757    // configure log stream
758    ConfigureLOG::ReportLevels() = false;
759    ConfigureLOG::ReportTimeTags() = false;
760    // debug and verbose handled earlier in GetCommandLine/PreProcessArgs
761    if(GD.debug > -1)
762       ; // handled in CommandLine::PreProcessArgs()
763    else if(GD.verbose)
764       LOGlevel = ConfigureLOG::Level("VERBOSE");
765 
766    // dump configuration
767    if(GD.debug > -1) {
768       LOG(INFO) << "Found debug switch at level " << GD.debug;
769       LOG(INFO) << "\n" << GD.cmdlineUsage;  // this will contain list of args
770       // NB debug turns on verbose
771    }
772 
773    LOG(VERBOSE) << GD.cmdlineDump;
774 
775    return 0;
776 }
777 catch(Exception& e) { GPSTK_RETHROW(e); }
778 }
779 
780 //------------------------------------------------------------------------------------
781 // Check input: read RINEX and generate a complete header, saving the data.
782 // if not correct, set bool GD.inputIsValid
ValidateInput(void)783 int ValidateInput(void)
784 {
785 try {
786    int i;
787    string msg;
788    GlobalData& GD=GlobalData::Instance();
789    GD.inputIsValid = true;
790 
791    if(GD.validate) LOG(INFO) << " ---- Validate configuration ----";
792 
793    // where else to do this?
794    include_path(GD.logpath,GD.logfile);
795    expand_filename(GD.logfile);
796 
797    if(GD.obsfiles.size() == 0) {
798       LOG(ERROR) << "Error - No input file.";
799       GD.inputIsValid = false;
800    }
801    else {
802       for(i=0; i<GD.obsfiles.size(); i++) {
803          include_path(GD.obspath,GD.obsfiles[i]);
804          expand_filename(GD.obsfiles[i]);
805       }
806 
807       // sort filenames on time
808       if(GD.obsfiles.size() > 1) {
809          msg = sortRinexObsFiles(GD.obsfiles);
810          if(!msg.empty()) {
811             LOG(ERROR) << msg;
812             GD.inputIsValid = false;
813          }
814       }
815 
816       for(i=0; i<GD.SP3files.size(); i++) {
817          include_path(GD.ephpath,GD.SP3files[i]);
818          expand_filename(GD.SP3files[i]);
819       }
820       for(i=0; i<GD.RNavfiles.size(); i++) {
821          include_path(GD.ephpath,GD.RNavfiles[i]);
822          expand_filename(GD.RNavfiles[i]);
823       }
824    }
825 
826    if(GD.validate) {
827       LOG(INFO) << " ---- Input is "
828          << (GD.inputIsValid ? "" : "NOT ") << "valid ----";
829       return 3;
830    }
831 
832    if(!GD.inputIsValid) return 4;
833 
834    return 0;
835 }
836 catch(Exception& e) { GPSTK_RETHROW(e); }
837 }
838 
839 //------------------------------------------------------------------------------------
Initialize(void)840 int Initialize(void)
841 {
842 try {
843    int n;
844    GlobalData& GD=GlobalData::Instance();
845 
846    // exclude small elevation --------------------------------------
847    // get nav files and build EphemerisStore
848    if(GD.SP3files.size() > 0) {
849       n = FillEphemerisStore(GD.SP3files, GD.SP3EphList, GD.BCEphList);
850       if(GD.verbose) {
851          LOG(VERBOSE) << "Added " << n << " SP3 ephemeris files to store.";
852       }
853    }
854    else if(GD.RNavfiles.size() > 0) {
855       n = FillEphemerisStore(GD.RNavfiles, GD.SP3EphList, GD.BCEphList);
856       if(GD.verbose) {
857          LOG(VERBOSE) << "Added " << n << " nav ephemeris files to store.";
858       }
859    }
860 
861    if(GD.SP3files.size() > 0 && GD.RNavfiles.size() > 0)
862       LOG(WARNING) << " Warning - SP3 ephemeris used; RINEX nav ignored.";
863 
864    if(GD.SP3EphList.ndata() > 0) {
865       // set gap and interval checking, based on nominal timestep
866       // take default GD.SP3EphList.setPositionInterpOrder(order);
867       int order = GD.SP3EphList.getPositionInterpOrder();
868       GD.SP3EphList.setClockLinearInterp();
869 
870       vector<SatID> sats(GD.SP3EphList.getSatList());
871       double dt(GD.SP3EphList.getClockTimeStep(sats[0]));
872       GD.SP3EphList.setClockGapInterval(dt+1);
873       GD.SP3EphList.setClockMaxInterval((order-1)*dt+1);
874       dt = GD.SP3EphList.getPositionTimeStep(sats[0]);
875       GD.SP3EphList.setPosGapInterval(dt+1);
876       GD.SP3EphList.setPosMaxInterval((order-1)*dt+1);
877       if(GD.debug >= 0) GD.SP3EphList.dump(LOGstrm,1);
878       else if(GD.verbose) GD.SP3EphList.dump(LOGstrm,0);
879       GD.pEph = &GD.SP3EphList;
880    }
881    else if(GD.BCEphList.size() > 0) {
882       if(GD.verbose) GD.BCEphList.dump(LOGstrm,1);
883       GD.pEph = &GD.BCEphList;
884    }
885    else if(GD.elevLimit > 0.0) {
886       GD.elevLimit = 0.0;
887       LOG(WARNING) << " Warning - unable to build ephemeris store; ignore elevations";
888    }
889 
890    // is it there?
891    if(GD.pEph != NULL) {
892       if(GD.Rx.getCoordinateSystem() != Position::Unknown && GD.elevLimit > 0.0)
893          GD.doElev = true;
894       else if(GD.Rx.getCoordinateSystem() != Position::Unknown) {
895          LOG(WARNING) << " Warning - Excluding low elevation requires --elev";
896       }
897       else {
898          LOG(WARNING) << " Warning - Excluding low elevation requires --ref";
899       }
900    }
901 
902    return 0;
903 }
904 catch(Exception& e) { GPSTK_RETHROW(e); }
905 }
906 
907 //------------------------------------------------------------------------------------
908 /// Utility for use by ReadRinexFiles()
909 /// find the index in loader obs ids for the given system and obstype, accepting codes
910 /// from the string codes, in order. If totals for this code is zero, try for another.
911 /// return -2 if obstype is not one of L1,L2,P1,P2,
912 ///     or -1 if it is, but no R3ObsID was found
findIndex(const vector<string> & allR3ObsID,const char sys,const string & obs,const string & codes,const vector<int> & totals)913 int findIndex(const vector<string>& allR3ObsID, const char sys,
914                   const string& obs, const string& codes, const vector<int>& totals)
915 {
916    if(obs[0] != 'P' && obs[0] != 'L') return -2;
917    if(obs[1] != '1' && obs[1] != '2') return -2;
918    char type(obs[0]=='P' ? 'C' : 'L');
919    char freq(obs[1]);
920    //const string tkcodes(RinexObsID::validRinexTrackingCodes[sys][freq]);
921    string test3("123"); test3[0]=sys; test3[1]=type; test3[2]=freq;
922 
923    int ind(-1);
924    unsigned int i,j,bestj;
925    for(i=0; i<allR3ObsID.size(); i++) {
926       const string oid(allR3ObsID[i]);
927       if(test3 == oid.substr(0,3)) {
928          for(j=0; j<codes.size(); j++) {
929             if(oid[3] == codes[j]) {
930                if(totals[i] == 0) {
931                   LOG(WARNING) << " Warning - no data found for " << sys << " "
932                      << obs << " in " << allR3ObsID[i] << " - skip this R3ObsID.";
933                   continue;
934                }
935                if(ind == -1 || j < bestj) { ind = i; bestj = j; }
936                break;
937             }
938          }
939       }
940    }
941 
942    return ind;
943 }
944 
945 //------------------------------------------------------------------------------------
946 // Reads RINEX 2|3 Obs file(s) into SatPass list SPList
ReadRinexFiles(void)947 int ReadRinexFiles(void)
948 {
949 try {
950    int n,iret;
951    unsigned int i,j;
952    string str,msg;
953    GlobalData& GD=GlobalData::Instance();
954 
955    LOG(INFO) << "\nLoad the RINEX files using Rinex3ObsFileLoader -------";
956 
957    // declare the ROFL, giving it all the filenames
958    // NB could also loop over files here, defining an ROFL for each one...
959    Rinex3ObsFileLoader rofl(GD.obsfiles);
960 
961    // configure the loader
962    // NB this already done in ExtraProcessing but leave it here anyway
963    for(i=0; i<GD.wantedObsIDs.size(); i++) { // pass in desired obs IDs
964       if(!rofl.loadObsID(GD.wantedObsIDs[i])) {
965          LOG(WARNING) << " Warning - ignore invalid or duplicate ObsID request: "
966                      << GD.wantedObsIDs[i];
967       }
968    }
969 
970    // NB do this in Edit() so the pass can be marked as excluded
971    //for(i=0; i<GD.exSats.size(); i++)   // excluded sats (not systems)
972    //   rofl.excludeSat(GD.exSats[i]);
973    rofl.saveTheData(true);             // no sense to have wantedObsIDs without save
974    if(GD.decdt > 0.0)                  // decimate
975       rofl.setDecimation(GD.decdt);
976    rofl.setStartTime(GD.startTime);    // start and stop times
977    rofl.setStopTime(GD.stopTime);
978    rofl.setTimeFormat(GD.longfmt);     // time format
979 
980    // load the files
981    iret = rofl.loadFiles(str,msg);
982    if(iret < 0 || !str.empty()) {
983       LOG(ERROR) << " Error - Loader failed: returned " << iret
984                      << " with message " << str;
985       return -1;
986    }
987    if(!msg.empty()) LOG(INFO) << msg;
988    LOG(INFO) << "Loader read " << iret << " file" << (iret>1?"s":"")
989                << " successfully " << endl;
990 
991    // dump a summary of the whole thing, including the sat/obs counts table
992    LOG(INFO) << rofl.asString();
993 
994    // dump the headers
995    for(i=0; i<GD.obsfiles.size(); i++) {
996       LOG(INFO) << "\nHeader for file " << GD.obsfiles[i];
997       GD.header = rofl.getFullHeader(i);
998       GD.header.dump(LOGstrm);
999    }
1000 
1001    // dump the data - NB setTimeFormat()
1002    // could also get data store and dump each element
1003    //const vector<Rinex3ObsData>& datastore(rofl.getStore());
1004    //if(DUMP(DATA)) rofl.dumpStoreData(LOGstrm);
1005 
1006    // now write to SatPass -------------------------------------------
1007    LOG(INFO) << "\nWrite to SatPass -----------------------------";
1008 
1009    // ------------------------------------------------
1010    // get vector of ObsIDs actually stored by loader
1011    const vector<string> loadR3ObsIDs(rofl.getWantedObsTypes());
1012    // get vector of total epoch counts for loadR3ObsIDs
1013    const vector<int> totcounts(rofl.getTotalObsCounts());
1014    // indexes[sys][i] = index in loadR3ObsIDs for GD.obstypes[i] in system sys
1015    map<char, vector<int> > indexes;
1016 
1017    // loop over all systems, all obstypes: find corresponding R3 ObsID in loader's
1018    // output, and construct a new obstype for SatPass.
1019    for(i=0; i<GD.Syss.size(); i++) {
1020       char sys(GD.Syss[i][0]);         // Syss[i] is 1-char string
1021 
1022       string codes(GD.Codes[i]);       // users prioritized list
1023       vector<int> v;
1024       indexes[sys] = v;          // initialize indexes[sys] with empty vector
1025       vector<string> SPot;       // SatPass obstype P: P-code PR, C: C/A PR, L: phase
1026       vector<string> R3ot;       // R3 obstype from loader for SPot
1027       for(j=0; j<GD.obstypes.size(); j++) {
1028          // find the R3ObsID in loader
1029          n = findIndex(loadR3ObsIDs,sys,GD.obstypes[j],codes,totcounts);
1030          if(n == -1) {
1031             LOG(ERROR) << " Error - loader found no R3ObsID for system " << sys
1032                            << " obstype " << GD.obstypes[j] << ". Abort.";
1033             return -2;
1034          }
1035          indexes[sys].push_back(n);
1036 
1037          // add to map SPsysobs, use the tracking code to decide on pseudorange P|C
1038          // for purposes of determining when to apply differential code biases.
1039          string ot(GD.obstypes[j]);
1040          if(ot[0] == 'P') {                  // obstypes above and findIndex() use 'P'
1041             char tc(loadR3ObsIDs[n][3]);     // tracking code
1042             if(sys=='G') {                   // GPS
1043                switch(tc) {
1044                   case 'P': case 'Y': case 'W':
1045                   case 'I': case 'M': case 'Q': case 'D':      // all P-code, right?
1046                      ot[0] = 'P'; break;
1047                   case 'C': case 'L': case 'X': case 'S':      // all C/A
1048                      ot[0] = 'C'; break;
1049                   default: break;
1050                }
1051             }
1052             if(sys=='R') {                   // GLO
1053                ot[0] = tc;                   // P or C are only choices
1054             }
1055          }
1056          SPot.push_back(n >= 0 ? ot : GD.obstypes[j]);
1057          R3ot.push_back(n >= 0 ? loadR3ObsIDs[n] : "-NA-");
1058       }
1059 
1060       GD.SPsysobs[sys] = SPot;
1061       GD.R3sysobs[sys] = R3ot;
1062    }
1063 
1064    // print obs types assignment SP <=> R3
1065    LOG(INFO) << " Assign RINEX3-ObsIDs to SatPass obstypes for each system :";
1066    ostringstream oss;
1067 
1068    map<char, vector<string> >::const_iterator rit = GD.R3sysobs.begin();
1069    while(rit != GD.R3sysobs.end()) {
1070       oss.str("");
1071       oss << " System " << rit->first << " ("
1072         << RinexObsID::map1to3sys[string(1,rit->first)] << "): SatPass obstypes = [";
1073       for(i=0; i<rit->second.size(); i++)
1074          oss << (i==0 ? "":",") << rit->second[i];
1075       LOG(INFO) << oss.str() << "]";
1076       rit++;
1077    }
1078 
1079    // define dt
1080    GD.nomdt = rofl.getDT();
1081    LOG(VERBOSE) << fixed << setprecision(2)
1082       << " The input data interval is " << GD.nomdt << " seconds.";
1083 
1084    // -------------------------------------------------------------
1085    // NB all passes have the same SP obs types (per sys)
1086    n = rofl.WriteSatPassList(GD.SPsysobs, indexes, GD.SPList);
1087 
1088    LOG(INFO) << " WriteSatPassList returned " << n << " passes.";
1089    LOG(INFO) << " Dump the passes:";
1090    for(i=0; i<GD.SPList.size(); i++) {
1091       LOG(INFO) << "SPL " << setw(3) << i+1 << " " << GD.SPList[i];
1092    }
1093 
1094    rofl.reset();
1095 
1096    return 0;
1097 }
1098 catch(Exception& e) { GPSTK_RETHROW(e); }
1099 }  // end ReadRinexFiles()
1100 
1101 //------------------------------------------------------------------------------------
PreProcess(void)1102 int PreProcess(void)
1103 {
1104 try {
1105    unsigned int i,j;
1106    string msg;
1107    GlobalData& GD=GlobalData::Instance();
1108 
1109    msecHandler msh;
1110    i = FindMilliseconds(GD.SPList, msh);
1111    LOG(INFO) << "\n" << msh.getFindMessage(GD.fixMS);    // flag is verbose
1112 
1113    if(GD.fixMS && i) {
1114       RemoveMilliseconds(GD.SPList, msh);
1115       LOG(INFO) << msh.getFixMessage(GD.verbose);
1116    }
1117 
1118    // mark low elevation data bad
1119    if(GD.doElev) {
1120       CorrectedEphemerisRange CER;
1121       for(i=0; i<GD.SPList.size(); i++) {
1122          if(GD.SPList[i].status() == -1) continue;
1123 
1124          RinexSatID sat = GD.SPList[i].getSat();
1125          for(j=0; j<GD.SPList[i].size(); j++) {
1126             Epoch ttag = GD.SPList[i].time(j);
1127             try {
1128                //double ER =
1129                CER.ComputeAtReceiveTime(ttag, GD.Rx, sat, *GD.pEph);
1130                if(CER.elevation >= GD.elevLimit) continue;
1131             }
1132             catch(InvalidRequest&) {
1133                // do not exclude the sat here; PRSolution will...
1134                LOG(DEBUG) << "CER did not find ephemeris for "
1135                   << sat << " at time " << ttag.printf(GD.timefmt);
1136                // fall through
1137             }
1138 
1139             // mark it bad
1140             GD.SPList[i].setFlag(j,SatPass::BAD);
1141 
1142          }  // end loop over data in SPList[i]
1143       }  // end loop over SatPasses
1144    }  // end if elev mask
1145 
1146    return 0;
1147 }
1148 catch(Exception& e) { GPSTK_RETHROW(e); }
1149 }
1150 
1151 //------------------------------------------------------------------------------------
Process(void)1152 int Process(void)
1153 {
1154 try {
1155    int iret=-666,i=-666,GLOn=-666;
1156    string msg;
1157    ostringstream oss;
1158    map<RinexSatID,int>::const_iterator gloit;
1159    GlobalData& GD=GlobalData::Instance();
1160 
1161    // dump the configuration
1162    LOG(INFO) << "\n# GDC configuration:";
1163    GD.GDC.DisplayParameterUsage(LOGstrm, "#", true);
1164    LOG(INFO) << "# End of GDC configuration.\n";
1165 
1166    // call the GDC
1167    string retmsg;
1168    for(i=0; i<GD.SPList.size(); i++) {
1169       // configure SatPass SPList[i]
1170       GD.SPList[i].setOutputFormat(GD.timefmt);       // nround?
1171 
1172       RinexSatID sat(GD.SPList[i].getSat());
1173 
1174       // exclude sats
1175       if(vectorindex(GD.exSat,sat) != -1) {
1176          LOG(VERBOSE) << "DFX " << setw(3) << i+1 << " " << sat << " sat excluded.";
1177          continue;
1178       }
1179       if(GD.onlySat.size() > 0 && vectorindex(GD.onlySat,sat) == -1) {
1180          LOG(VERBOSE) << "DFX " << setw(3) << i+1 << " " << sat << " not only sat.";
1181          continue;
1182       }
1183 
1184       // exclude passes
1185       if(GD.onlyPass.size() > 0 && vectorindex(GD.onlyPass,i+1) == -1) {
1186          LOG(VERBOSE) << "DFX " << setw(3) << i+1 << " " << sat << " pass excluded.";
1187          continue;
1188       }
1189 
1190       // no good data
1191       if(GD.SPList[i].getNgood() == 0) {
1192          LOG(VERBOSE) << "DFX " << setw(3) << i+1 << " " << sat << " no good data.";
1193          continue;
1194       }
1195 
1196       // get the GLOn
1197       if(sat.system == SatelliteSystem::Glonass) {
1198          gloit = GD.GLOfreqCh.find(sat);
1199 
1200          // if GLONASS frequency channel not given, try to find it
1201          if(gloit != GD.GLOfreqCh.end()) {
1202             GLOn = gloit->second;
1203          }
1204          else {
1205             if(!GD.SPList[i].getGLOchannel(GLOn, msg)) {
1206                LOG(WARNING) << " Warning - unable to compute GLO channel for sat "
1207                   << sat << " - skip pass : " + msg;
1208             }
1209             else {
1210                LOG(VERBOSE) << "# GLO frequency channel for " << sat
1211                            << " was computed from data, = " << GLOn << "; " << msg;
1212                GD.GLOfreqCh[sat] = GLOn;
1213             }
1214          }
1215       }
1216 
1217       // make the unique number == pass number == i+1, always
1218       GD.GDC.ForceUniqueNumber(i);     // NB it will be incremented in DC call
1219 
1220       // call GDC
1221       iret = GD.GDC.DiscontinuityCorrector(GD.SPList[i], retmsg, GD.EditCmds, GLOn);
1222       // TD is iret<0 handled by retmsg?
1223       int unique(GD.GDC.getUniqueNumber());        // == i+1 here
1224 
1225       // save the GLO freq channel
1226       if(sat.system == SatelliteSystem::Glonass &&
1227          GD.GLOfreqCh.find(sat) == GD.GLOfreqCh.end())
1228             GD.GLOfreqCh[sat] = GLOn;
1229 
1230       // add tag to lines in the retmsg
1231       oss.str(""); oss << "DFX " << setw(3) << unique << " " << sat;
1232       msg = oss.str();
1233       // add tag == msg to all the lines in retmsg
1234       StringUtils::change(retmsg,"\n","\n"+msg+" ");
1235       retmsg = msg + " " + retmsg;
1236       LOG(INFO) << retmsg;
1237    }
1238 
1239    // write editing commands
1240    if(!GD.cmdout.empty()) {
1241       ofstream ofs;
1242       ofs.open(GD.cmdout.c_str(),ios_base::out);
1243       if(!GD.oflog.is_open()) {
1244          LOG(ERROR) << " Error - failed to open file " << GD.cmdout;
1245          GD.cmdout = string();
1246       }
1247       else {
1248          for(i=0; i<GD.EditCmds.size(); i++)
1249             ofs << GD.EditCmds[i] << endl;
1250          ofs.close();
1251       }
1252    }
1253 
1254    // write to RINEX
1255    if(!GD.obsout.empty()) {
1256       LOG(INFO) << "Write to RINEX file " << GD.obsout;
1257 
1258       // strict RINEX, and we may have computed them
1259       GD.header.glonassFreqNo.clear();
1260       for(gloit = GD.GLOfreqCh.begin(); gloit != GD.GLOfreqCh.end(); ++gloit)
1261          GD.header.glonassFreqNo[gloit->first] = gloit->second;
1262       GD.header.valid |= Rinex3ObsHeader::validGlonassSlotFreqNo;
1263       // strict RINEX
1264       GD.header.valid |= Rinex3ObsHeader::validGlonassCodPhsBias;
1265 
1266       GD.header.commentList.push_back(string("Edited by ")+GD.Title);
1267 
1268       i = SatPassToRinex3File(GD.obsout, GD.header, GD.R3sysobs, GD.SPList);
1269       LOG(VERBOSE) << "SatPassToRinex3File returned " << i;
1270    }
1271 
1272    return 0;
1273 }
1274 catch(Exception& e) { GPSTK_RETHROW(e); }
1275 }
1276 
1277 //------------------------------------------------------------------------------------
1278 //------------------------------------------------------------------------------------
1279