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 //------------------------------------------------------------------------------------
40 // DiscFix.cpp Read a RINEX observation file containing dual frequency
41 //    pseudorange and phase, separate the data into satellite passes, and then
42 //    find and estimate discontinuities in the phase (using the GPSTk Discontinuity
43 //    Corrector (GDC) in DiscCorr.hpp).
44 //    The corrected data can be written out to another RINEX file, plus there is the
45 //    option to smooth the pseudorange and/or debias the phase (SatPass::smooth()).
46 //------------------------------------------------------------------------------------
47 
48 /// @file DiscFix.cpp
49 /// Correct phase discontinuities (cycle slips) in dual frequency data in a RINEX
50 /// observation file, plus optionally smooth the pseudoranges and/or debias the phases
51 
52 // system
53 #include <ctime>
54 #include <cstring>
55 #include <string>
56 #include <vector>
57 #include <iostream>
58 #include <fstream>
59 #include <algorithm>
60 // gpstk
61 #include "MathBase.hpp"
62 #include "RinexSatID.hpp"
63 #include "RinexObsBase.hpp"
64 #include "RinexObsData.hpp"
65 #include "RinexObsHeader.hpp"
66 #include "RinexObsStream.hpp"
67 #include "CommonTime.hpp"
68 #include "CivilTime.hpp"
69 #include "GPSWeekSecond.hpp"
70 #include "Epoch.hpp"
71 #include "TimeString.hpp"
72 #include "StringUtils.hpp"
73 // geomatics
74 #include "logstream.hpp"
75 #include "stl_helpers.hpp"
76 #include "expandtilde.hpp"
77 #include "CommandLine.hpp"
78 #include "SatPass.hpp"
79 #include "SatPassUtilities.hpp"
80 #include "DiscCorr.hpp"
81 
82 using namespace std;
83 using namespace gpstk;
84 using namespace StringUtils;
85 
86 //------------------------------------------------------------------------------------
87 //------------------------------------------------------------------------------------
88 // prgm data
89 static const string DiscFixVersion = string("6.3 2/4/16");
90 static const string PrgmName("DiscFix");
91 // a convenience
92 static const string L1("L1"),L2("L2"),P1("P1"),P2("P2"),C1("C1"),C2("C2");
93 
94 // all input and global data
95 typedef struct configuration {
96       // input
97    string inputPath;
98    vector<string> obsfiles;
99       // data flow
100    double decimate;
101    Epoch begTime, endTime;
102    double MaxGap;
103    //int MinPts;
104       // processing
105    double dt0,dt;          // data interval in input file, and final (decimated) dt
106    bool noCA1,noCA2,useCA1,forceCA1,useCA2,forceCA2,doGLO;
107    vector<RinexSatID> exSat;
108    RinexSatID SVonly;
109       // output files
110    string LogFile,OutFile;
111    ofstream oflog,ofout;
112    string format;
113    int round;
114       // output
115    string OutRinexObs;
116    string HDPrgm;         // header of output RINEX file
117    string HDRunby;
118    string HDObs;
119    string HDAgency;
120    string HDMarker;
121    string HDNumber;
122    int NrecOut;
123    Epoch FirstEpoch,LastEpoch;
124    bool smoothPR,smoothPH,smooth;
125    int debug;
126    bool verbose,DChelp;
127    vector<string> DCcmds;        // all the --DC... on the cmd line
128       // estimate dt from data
129    double estdt[9];
130    int ndt[9];
131    // input and/or compute GLONASS frequency channel for each GLO satellite
132    map<RinexSatID,int> GLOfreqChannel;
133 
134    // summary of cmd line input
135    string cmdlineSum;
136 
137    string Title,Date;
138    Epoch PrgmEpoch;
139    RinexObsStream irfstr; //, orfstr;   // input and output RINEX files
140    RinexObsHeader rhead;
141    int inP1,inP2,inL1,inL2;            // indexes in rhead of C1/P1, P2, L1 and L2
142    string P1C1,P2C2;                   // either P1 or C1
143 
144    // Data for an entire pass is stored in SatPass object
145    // This vector contains all the SatPass's defined so far
146    // The parallel vector holds an iterator for use in writing out the data
147    vector<SatPass> SPList;
148 
149    // list of observation types to be included in each SatPass
150    vector<string> obstypes;
151 
152    // this is a map relating a satellite to the index in SVPList of the current pass
153    vector<unsigned int> SPIndexList;
154    map<RinexSatID,int> SatToCurrentIndexMap;
155 
156    GDCconfiguration GDConfig;       // the discontinuity corrector configuration
157 } DFConfig;
158 
159 // declare (one only) global configuration object
160 DFConfig cfg;
161 
162 //------------------------------------------------------------------------------------
163 //------------------------------------------------------------------------------------
164 // prototypes
165 /**
166  * @throw Exception
167  */
168 int GetCommandLine(int argc, char **argv);
169 /**
170  * @throw Exception
171  */
172 void DumpConfiguration(void);
173 /**
174  * @throw Exception
175  */
176 int Initialize(void);
177 /**
178  * @throw Exception
179  */
180 int ShallowCheck(void);  // called by Initialize()
181 /**
182  * @throw Exception
183  */
184 int WriteToRINEX(void);
185 void PrintSPList(ostream&, string, vector<SatPass>&);
186 
187 //------------------------------------------------------------------------------------
188 //------------------------------------------------------------------------------------
main(int argc,char ** argv)189 int main(int argc, char **argv)
190 {
191    try {
192       clock_t totaltime = clock();
193       int i,nread,npass,iret;
194       Epoch ttag;
195       string msg;
196       vector<string> EditCmds;
197 
198       // Title and description
199       cfg.Title = PrgmName+", part of the GPS ToolKit, Ver "+DiscFixVersion+", Run ";
200       cfg.PrgmEpoch.setLocalTime();
201       cfg.Date = printTime(cfg.PrgmEpoch,"%04Y/%02m/%02d %02H:%02M:%02S");
202       cfg.Title += cfg.Date;
203       cout << cfg.Title << endl;
204 
205       for(;;) {                           // a convenience
206          // -------------------------------- get command line
207          iret = GetCommandLine(argc, argv);
208          if(iret) break;
209 
210          // -------------------------------- initialize
211          iret = Initialize();
212          if(iret) break;
213 
214          // -------------------------------- read in the data
215          try {
216             nread = SatPassFromRinexFiles(cfg.obsfiles, cfg.obstypes, cfg.dt0,
217                               cfg.SPList, cfg.exSat, true, cfg.begTime, cfg.endTime);
218             LOG(VERBOSE) << "Successfully read " << nread << " RINEX obs files.";
219          }
220          catch(Exception &e) {         // time tags out of order or a read error
221             string what(e.what());
222             string::size_type pos(what.find("Time tags out of order", 0));
223             if(pos != string::npos) {
224                pos = what.find("\n");
225                if(pos != string::npos) what.erase(pos);
226                LOG(ERROR) << "Error - " << what;
227             }
228             else { GPSTK_RETHROW(e); }
229          }
230 
231          if(nread != cfg.obsfiles.size()) {
232             iret = -7;
233             break;
234          }
235          if(cfg.SPList.size() <= 0) {
236             LOG(ERROR) << "Error - no data found.";
237             iret = -8;
238             break;
239          }
240 
241          // -------------------------------- exclude satellites
242          for(npass=0; npass<cfg.SPList.size(); npass++) {
243             RinexSatID sat(cfg.SPList[npass].getSat());
244 
245             if(cfg.SVonly.id != -1 && sat != cfg.SVonly) {
246                cfg.SPList[npass].status() = -1;
247                LOG(VERBOSE) << "Exclude pass #" << setw(2) << npass+1 << " (" << sat
248                   << ") as only one satellite is to be processed.";
249             }
250             // done in SatPassFromRinex()
251             //else if(vectorindex(cfg.exSat,sat) != -1) {
252             //   cfg.SPList[npass].status() = -1;
253             //   LOG(VERBOSE) << "Exclude pass #" << setw(2) << npass+1 << " (" << sat
254             //      << ") as satellite is excluded explicitly.";
255             //}
256             //else if((!cfg.doGLO && sat.system != SatelliteSystem::GPS) ||
257             //        ( cfg.doGLO && sat.system != SatelliteSystem::GPS
258             //                    && sat.system != SatelliteSystem::Glonass))
259             //{
260             //   cfg.SPList[npass].status() = -1;
261             //   LOG(VERBOSE) << "Exclude pass #" << setw(2) << npass+1 << " (" << sat
262             //      << ") as satellite system is excluded.";
263             //}
264             else if(cfg.SPList[npass].size()==0 || cfg.SPList[npass].getNgood()==0) {
265                cfg.SPList[npass].status() = -1;
266                LOG(VERBOSE) << "Exclude pass #" << setw(2) << npass+1 << " (" << sat
267                   << ") as it is empty.";
268             }
269             //else if(cfg.SPList[npass].getNgood() < minpass) {
270             //   cfg.SPList[npass].status() = -1;
271             //   LOG(VERBOSE) << "Exclude pass #" << setw(2) << npass+1 << " (" << sat
272             //      << ") as it is too small (" << cfg.SPList[npass].getNgood()
273             //      << " < " << minpass << ").";
274             //}
275 
276          }  // end for() over all passes
277 
278          // remove the invalid ones
279          vector<SatPass>::iterator it(cfg.SPList.begin());
280          while(it != cfg.SPList.end()) {
281             if(it->status() == -1)
282                it = cfg.SPList.erase(it);       // remove it; it points to next pass
283             else
284                ++it;                            // go to next pass
285          }
286 
287          // is there anything left?
288          if(cfg.SPList.size() <= 0) {
289             LOG(ERROR) << "Error - no data found.";
290             iret = -9;
291             break;
292          }
293 
294          // -------------------------------- decimate
295          // set the data interval, and decimate if the user input is N*raw interval
296          if(cfg.decimate < 0.0) {
297             LOG(INFO) << PrgmName << ": decimation timestep must be positive";
298             iret = -2;
299             break;
300          }
301          else if(cfg.decimate == 0.0) {
302             cfg.dt = cfg.dt0;                         // just go with raw interval
303          }
304          else if(fmod(cfg.decimate,cfg.dt0) < 0.01) { // decimate
305             int N(0.5+cfg.decimate/cfg.dt0);
306             ttag = cfg.SPList[0].getFirstTime();
307             int n(ttag.GPSsow()/cfg.decimate);
308             //ttag.setGPSfullweek(ttag.GPSfullweek(), n*cfg.decimate);
309             GPSWeekSecond gpst(ttag.GPSweek(), n*cfg.decimate);
310             ttag = static_cast<Epoch>(gpst);
311             for(npass=0; npass<cfg.SPList.size(); npass++)
312                cfg.SPList[npass].decimate(N, ttag);
313             cfg.dt = cfg.decimate;
314          }
315          else {                                       // can't decimate
316             LOG(ERROR) << "Error - cannot decimate; input time step ("
317                << asString(cfg.decimate,2)
318                << ") is not an even multiple of the data rate ("
319                << asString(cfg.dt0,2) << ")";
320             iret = -10;
321             break;
322          }
323 
324          cfg.GDConfig.setParameter(string("DT:")+asString(cfg.dt,2));
325          cfg.GDConfig.setParameter(string("MaxGap:")+asString(cfg.MaxGap,2));
326          LOG(INFO) << "\nHere is the current GPSTk DC configuration:";
327          cfg.GDConfig.DisplayParameterUsage(LOGstrm,(cfg.DChelp && cfg.verbose));
328          LOG(INFO) << "";
329 
330          // -------------------------------- call the GDC, output results and smooth
331          for(npass=0; npass<cfg.SPList.size(); npass++) {
332 
333             LOG(INFO) << "Proc " << setw(2) << npass+1 << " " << cfg.SPList[npass];
334             //cfg.SPList[npass].dump(*pLOGstrm,"RAW");      // temp
335 
336             msg = "";
337             iret=DiscontinuityCorrector(cfg.SPList[npass],cfg.GDConfig,EditCmds,msg);
338             if(iret != 0) {
339                cfg.SPList[npass].status() = -1;         // failed
340                LOG(ERROR) << "GDC failed (" << iret << " "
341                   << (iret==-1 ? "Singularity":
342                      (iret==-3 ? "DT not set, or memory":
343                      (iret==-4 ? "No data":"Bad input")))
344                   << ") for pass "
345                   << npass+1 << " :\n" << msg;
346                continue;
347             }
348             //if(cfg.verbose && LOGlevel < ConfigureLOG::Level("VERBOSE"))
349             LOG(INFO) << msg;
350 
351             ttag = cfg.SPList[npass].getFirstGoodTime();
352             if(ttag < cfg.FirstEpoch) cfg.FirstEpoch = ttag;
353             ttag = cfg.SPList[npass].getLastTime();
354             if(ttag > cfg.LastEpoch) cfg.LastEpoch = ttag;
355 
356             // output editing commands
357             for(i=0; i<EditCmds.size(); i++)
358                cfg.ofout << EditCmds[i] << " # pass " << npass+1 << endl;
359             EditCmds.clear();
360 
361             // smooth pseudorange and debias phase
362             if(cfg.smooth) {
363                cfg.SPList[npass].smooth(cfg.smoothPR, cfg.smoothPH, msg);
364                LOG(INFO) << msg;
365             }
366 
367          }  // end for() loop over passes
368 
369          // -------------------------------- write to RINEX
370          iret = WriteToRINEX();
371          if(iret) break;
372 
373          // -------------------------------- print a summary
374          PrintSPList(LOGstrm,"Fine",cfg.SPList);
375 
376          break;
377       }
378 
379       // timing
380       totaltime = clock()-totaltime;
381       LOG(INFO) << PrgmName << " timing: " << fixed << setprecision(3)
382          << double(totaltime)/double(CLOCKS_PER_SEC) << " seconds.\n";
383       cout << PrgmName << " timing: " << fixed << setprecision(3)
384          << double(totaltime)/double(CLOCKS_PER_SEC) << " seconds.\n";
385 
386       // clean up
387       cfg.ofout.close();
388       cfg.oflog.close();
389 
390       return iret;
391    }
392    catch(Exception& e) {
393       cfg.oflog << e.what();
394       cout << e.what();
395    }
396    catch (...) {
397       cfg.oflog << PrgmName << ": Unknown error.  Abort." << endl;
398       cout << PrgmName << ": Unknown error.  Abort." << endl;
399    }
400 
401    return -1;
402 
403 }   // end main()
404 
405 //------------------------------------------------------------------------------------
Initialize(void)406 int Initialize(void)
407 {
408 try {
409    int i;
410 
411    // open the log file
412    cfg.oflog.open(cfg.LogFile.c_str(),ios::out);
413    if(!cfg.oflog.is_open()) {
414       cerr << PrgmName << " failed to open log file " << cfg.LogFile << ".\n";
415       return -3;
416    }
417 
418    // last write to screen
419    LOG(INFO) << PrgmName << " is writing to log file " << cfg.LogFile;
420 
421    // attach LOG to the log file
422    pLOGstrm = &cfg.oflog;
423    ConfigureLOG::ReportLevels() = false;
424    ConfigureLOG::ReportTimeTags() = false;
425 
426    // set the DC commands now (setParameter may write to log file)
427    for(i=0; i<cfg.DCcmds.size(); i++) {
428       cfg.GDConfig.setParameter(cfg.DCcmds[i]);
429       if(cfg.DCcmds[i].substr(0,5) == string("Debug")) {
430          string msg("DEBUG");
431          msg += asString(cfg.DCcmds[i].substr(6));
432          LOGlevel = ConfigureLOG::Level(msg);
433       }
434       else if(cfg.DCcmds[i].substr(0,4) == string("--DC DT=<dt>")) {
435          LOG(WARNING) << "Warning - Input of the timestep with --DCDT is ignored.";
436       }
437    }
438 
439    if(cfg.verbose && LOGlevel < ConfigureLOG::Level("VERBOSE"))
440       LOGlevel = ConfigureLOG::Level("VERBOSE");
441 
442    LOG(INFO) << cfg.Title;
443    //LOG(INFO) << "LOG level is at " << ConfigureLOG::ToString(LOGlevel);
444 
445    // open input obs files, read header and some of the data
446    i = ShallowCheck();
447    if(i) return i;
448 
449    // allow GDC to output to log file
450    cfg.GDConfig.setDebugStream(cfg.oflog);
451    if(cfg.P1C1 == C1) cfg.GDConfig.setParameter("useCA1:1");   // Shallow sets P1C1
452    if(cfg.P2C2 == C2) cfg.GDConfig.setParameter("useCA2:1");   // Shallow sets P2C2
453 
454    cfg.SVonly.setfill('0');                     // set fill char in RinexSatID
455 
456    // catch input trap
457    if(!cfg.doGLO && cfg.SVonly.system == SatelliteSystem::Glonass) {
458       LOG(VERBOSE) << "SVonly is GLONASS - turn on processing of GLONASS";
459       cfg.doGLO = true;
460    }
461 
462    // write to log file
463    DumpConfiguration();
464 
465    cfg.FirstEpoch = CommonTime::END_OF_TIME;
466    cfg.LastEpoch = CommonTime::BEGINNING_OF_TIME;
467    // configure SatPass
468    {
469       cfg.obstypes.push_back(L1);    // DiscFix requires these 4 observables only
470       cfg.obstypes.push_back(L2);
471       cfg.obstypes.push_back(cfg.P1C1);
472       cfg.obstypes.push_back(P2);
473 
474       SatPass dummy(cfg.SVonly,cfg.dt0);
475       dummy.setMaxGap(cfg.MaxGap);
476       dummy.setOutputFormat(cfg.format,cfg.round);
477    }
478 
479    // open output file
480    // output for editing commands - write to this in ProcessSatPass()
481    cfg.ofout.open(cfg.OutFile.c_str());
482    if(!cfg.oflog.is_open()) {
483       cfg.oflog << "Error: " << PrgmName << " failed to open output file "
484          << cfg.OutFile << endl;
485       return -5;
486    }
487    else
488       LOG(INFO) << PrgmName << " is writing to output file " << cfg.OutFile;
489 
490    return 0;
491 }
492 catch(Exception& e) { GPSTK_RETHROW(e); }
493 }
494 
495 //------------------------------------------------------------------------------------
496 // open the input files, read the headers and some of the data. Determine dt0 & C1/P1.
ShallowCheck(void)497 int ShallowCheck(void)
498 {
499 try {
500    bool inputValid(true);
501    int i,j;
502    string msg;
503    vector<bool> fileValid;
504    vector<int> fileHasP1C1,fileHasP2C2;
505    vector<long> filesize;
506    vector<double> fileDT;
507    vector<Epoch> fileFirst;
508 
509    // open obs files and read few epochs; test validity and determine content
510    for(i=0; i<cfg.obsfiles.size(); i++) {
511       fileValid.push_back(false);
512       fileHasP1C1.push_back(0);           // 0: don't know, 1 C1, 2 P1, 3 both
513       fileHasP2C2.push_back(0);           // 0: don't know, 1 C2, 2 P2, 3 both
514       filesize.push_back(long(0));
515       fileDT.push_back(-1.0);
516       fileFirst.push_back(CommonTime::BEGINNING_OF_TIME);
517 
518       // open obs files
519       RinexObsStream rstrm;
520       rstrm.open(cfg.obsfiles[i].c_str(),ios_base::in);
521       if(!rstrm.is_open()) {
522          LOG(ERROR) << "  Error - Observation file " << cfg.obsfiles[i]
523              << " could not be opened.";
524          inputValid = false;
525       }
526       else {
527          LOG(DEBUG) << "Opened file " << cfg.obsfiles[i] << flush;
528          rstrm.exceptions(ios_base::failbit);
529 
530          // get file size
531          long begin = rstrm.tellg();
532          rstrm.seekg(0,ios::end);
533          long end = rstrm.tellg();
534          rstrm.seekg(0,ios::beg);
535          filesize[i] = end-begin;
536 
537          // read header
538          bool rinexok=true;
539          //RinexObsHeader head;
540          try { rstrm >> cfg.rhead; }
541          catch(Exception& e) { rinexok = false; }
542          catch(exception& e) { rinexok = false; }
543 
544          // is header valid?
545          LOG(DEBUG) << "Read header for " << cfg.obsfiles[i];
546          if(rinexok && !cfg.rhead.isValid()) { rinexok = false; }
547          if(!rinexok) {
548             LOG(ERROR) << "  Error - Observation file " << cfg.obsfiles[i]
549                 << " does not contain valid RINEX observations.";
550             inputValid = false;
551          }
552          // look for L1,L2,C1/P1,P2 observations, and antenna height
553          else {
554             unsigned int found=0;
555             for(j=0; j<cfg.rhead.obsTypeList.size(); j++) {
556                if(cfg.rhead.obsTypeList[j] == RinexObsHeader::L1) found +=32;
557                if(cfg.rhead.obsTypeList[j] == RinexObsHeader::L2) found +=16;  // 48
558                if(cfg.rhead.obsTypeList[j] == RinexObsHeader::P1) found += 8;  // 56
559                if(cfg.rhead.obsTypeList[j] == RinexObsHeader::P2) found += 4;  // 60
560                if(cfg.rhead.obsTypeList[j] == RinexObsHeader::C1) found += 2;  // 62
561                if(cfg.rhead.obsTypeList[j] == RinexObsHeader::C2) found += 1;  // 63
562             }
563             if(found & 8) fileHasP1C1[i] += 2;     // has P1
564             if(found & 2) fileHasP1C1[i] += 1;     // has C1
565             if(found & 4) fileHasP2C2[i] += 2;     // has P2
566             if(found & 1) fileHasP2C2[i] += 1;     // has C2
567             if(!(found & 32)) LOG(ERROR) << "  Error - Observation file "
568                   << cfg.obsfiles[i] << " has no L1 data.";
569             if(!(found & 16)) LOG(ERROR) << "  Error - Observation file "
570                   << cfg.obsfiles[i] << " has no L2 data.";
571             if(fileHasP1C1[i] == 0) LOG(ERROR) << "  Error - Observation file "
572                   << cfg.obsfiles[i] << " has no P1 or C1 data.";
573             if(fileHasP2C2[i] == 0) LOG(ERROR) << "  Error - Observation file "
574                   << cfg.obsfiles[i] << " has no P2 or C2 data.";
575 
576             if(!(found & 48) || fileHasP1C1[i]*fileHasP2C2[i] == 0) {
577                inputValid = false;
578             }
579             else {
580                fileFirst[i] = cfg.rhead.firstObs;
581 
582                // read a few obs to determine data interval
583                const int N=10;      // how many epochs to read?
584                begin = rstrm.tellg();
585                int jj,kk,nleast,nepochs=0,ndt[9]={-1,-1,-1, -1,-1,-1, -1,-1,-1};
586                double dt,bestdt[9];
587                Epoch first=CommonTime::END_OF_TIME,prev=CommonTime::END_OF_TIME;
588                RinexObsData robs;
589                while(1) {
590                   try { rstrm >> robs; }
591                   catch(Exception& e) { break; }   // simply quit if meet failure
592                   if(!rstrm) break;                // or EOF
593                   dt = robs.time - prev;
594                   if(dt > 0.0) {
595                      for(j=0; j<9; j++) {
596                         if(ndt[j] <= 0) { bestdt[j]=dt; ndt[j]=1; break; }
597                         if(fabs(dt-bestdt[j]) < 0.002) { ndt[j]++; break; }
598                         if(j == 8) {
599                            kk=0; nleast=ndt[kk];
600                            for(jj=1; jj<9; jj++) if(ndt[jj] <= nleast) {
601                               kk=jj; nleast=ndt[jj];
602                            }
603                            ndt[kk]=1; bestdt[kk]=dt;
604                         }
605                      }
606                   }
607                   if(++nepochs >= N) break;
608                   prev = robs.time;
609                   if(first == CommonTime::END_OF_TIME) first = robs.time;
610                }
611 
612                // save the results
613                for(jj=1,kk=0; jj<9; jj++) if(ndt[jj]>ndt[kk]) kk=jj;
614                // round to nearest 0.1 second
615                fileDT[i] = double(0.1*int(0.5+bestdt[kk]/0.1));
616                fileValid[i] = true;
617 
618                // dump the results
619                LOG(VERBOSE) << " RINEX observation file " << cfg.obsfiles[i]
620                   << " starts at "
621                   << printTime(first,"%04Y/%02m/%02d %02H:%02M:%02S = %F %10.3g");
622                LOG(VERBOSE) << " RINEX observation file " << cfg.obsfiles[i]
623                   << " has data interval " << asString(fileDT[i],2) << " sec,"
624                   << " size " << filesize[i] << " bytes, and types"
625                   << (found & 16 ?  " L1":"")
626                   << (found & 8 ?  " L2":"")
627                   << (found & 2 ?  " P1":"")
628                   << (found & 4 ?  " P2":"")
629                   << (found & 1 ?  " C1":"");
630             }
631          }
632          rstrm.clear();
633          rstrm.close();
634       }  // end reading file
635 
636       LOG(DEBUG) << "End reading file " << cfg.obsfiles[i] << flush;
637 
638    }  // end loop over cfg.obsfiles
639    cfg.dt0 = fileDT[0];
640 
641    LOG(VERBOSE)
642       << "The data interval in input file is " << fixed << setprecision(2) << cfg.dt0;
643 
644    // test that obs files agree on data interval, and look for C1/P1
645    bool P1missing(false), C1missing(false);
646    bool P2missing(false), C2missing(false);
647    for(j=0; j<cfg.obsfiles.size(); j++) {
648       if(fabs(cfg.dt0 - fileDT[j]) > 0.001) {
649          msg = string("  Error - RINEX Obs files data intervals differ: ")
650              + StringUtils::asString(fileDT[j],2) + string(" != ")
651              + StringUtils::asString(cfg.dt0,2);
652          LOG(ERROR) << msg;
653          inputValid = false;
654       }
655       if(!(fileHasP1C1[j] & 1)) C1missing = true;
656       if(!(fileHasP1C1[j] & 2)) P1missing = true;
657       if(!(fileHasP2C2[j] & 1)) C2missing = true;
658       if(!(fileHasP2C2[j] & 2)) P2missing = true;
659    }
660 
661    // handle C1 vs P1
662    if(C1missing && cfg.forceCA1) {
663       msg = string("  Error - Found '--forceCA1', but these files have no C1 data:");
664       for(j=0; j<cfg.obsfiles.size(); j++)
665          if(!(fileHasP1C1[j] & 1)) msg += string("\n    ") + cfg.obsfiles[j];
666       LOG(ERROR) << msg;
667       inputValid = false;
668    }
669    // NB 'else' and order of if()'s matter in the next stmt
670    else if(P1missing && (!cfg.useCA1 || C1missing)) {
671       if(C1missing) {
672          msg = string("  Error - Not all obs files have either P1 or C1 data.");
673       }
674       else {
675          msg = string("  Error - '--useCA1' not found, ")
676              + string("yet these obs files have no P1 data:");
677          for(j=0; j<cfg.obsfiles.size(); j++)
678             if(!(fileHasP1C1[j] & 2)) msg += string("\n    ") + cfg.obsfiles[j];
679       }
680       LOG(ERROR) << msg;
681       inputValid = false;
682    }
683    else {
684       if(P1missing || cfg.forceCA1) cfg.P1C1 = C1;
685       else                          cfg.P1C1 = P1;
686       //LOG(VERBOSE) << "Choose to use " << cfg.P1C1 << " for L1 pseudorange";
687    }
688    // handle C2 vs P2
689    if(C2missing && cfg.forceCA2) {
690       msg = string("  Error - Found '--forceCA2', but these files have no C2 data:");
691       for(j=0; j<cfg.obsfiles.size(); j++)
692          if(!(fileHasP2C2[j] & 1)) msg += string("\n    ") + cfg.obsfiles[j];
693       LOG(ERROR) << msg;
694       inputValid = false;
695    }
696    // NB 'else' and order of if()'s matter in the next stmt
697    else if(P2missing && (!cfg.useCA2 || C2missing)) {
698       if(C2missing) {
699          msg = string("  Error - Not all obs files have either P2 or C2 data.");
700       }
701       else {
702          msg = string("  Error - '--useCA2' not found, ")
703              + string("yet these obs files have no P2 data:");
704          for(j=0; j<cfg.obsfiles.size(); j++)
705             if(!(fileHasP2C2[j] & 2)) msg += string("\n    ") + cfg.obsfiles[j];
706       }
707       LOG(ERROR) << msg;
708       inputValid = false;
709    }
710    else {
711       if(P2missing || cfg.forceCA2) cfg.P2C2 = C2;
712       else                          cfg.P2C2 = P2;
713       //LOG(VERBOSE) << "Choose to use " << cfg.P2C2 << " for L2 pseudorange";
714    }
715 
716    if(inputValid) return 0;
717    return -6;
718 }
719 catch(Exception& e) { GPSTK_RETHROW(e); }
720 }
721 
722 //------------------------------------------------------------------------------------
WriteToRINEX(void)723 int WriteToRINEX(void)
724 {
725 try {
726    if(cfg.OutRinexObs.empty()) return 0;
727    LOG(VERBOSE) << "Write the output RINEX file " << cfg.OutRinexObs;
728 
729    // copy user input into the last input header
730    RinexObsHeader rheadout(cfg.rhead);
731 
732    // change the obs type list to include only P1(C1) P2 L1 L2
733    rheadout.obsTypeList.clear();
734 
735    rheadout.obsTypeList.push_back(RinexObsHeader::L1);
736    rheadout.obsTypeList.push_back(RinexObsHeader::L2);
737    if(cfg.P1C1 == C1)
738       rheadout.obsTypeList.push_back(RinexObsHeader::C1);
739    else
740       rheadout.obsTypeList.push_back(RinexObsHeader::P1);
741    rheadout.obsTypeList.push_back(RinexObsHeader::P2);
742 
743    // fill records in output header
744    rheadout.fileProgram = PrgmName + string(" v.") + DiscFixVersion.substr(0,4)
745                                  + string(",") + cfg.GDConfig.Version().substr(0,4);
746    if(!cfg.HDRunby.empty()) rheadout.fileAgency = cfg.HDRunby;
747    if(!cfg.HDObs.empty()) rheadout.observer = cfg.HDObs;
748    if(!cfg.HDAgency.empty()) rheadout.agency = cfg.HDAgency;
749    if(!cfg.HDMarker.empty()) rheadout.markerName = cfg.HDMarker;
750    if(!cfg.HDNumber.empty()) rheadout.markerNumber = cfg.HDNumber;
751    rheadout.version = 2.1;
752    rheadout.valid |= RinexObsHeader::versionValid;
753    rheadout.firstObs = cfg.FirstEpoch;
754    rheadout.valid |= RinexObsHeader::firstTimeValid;
755    rheadout.interval = cfg.dt;
756    rheadout.valid |= RinexObsHeader::intervalValid;
757    rheadout.lastObs = cfg.LastEpoch;
758    rheadout.valid |= RinexObsHeader::lastTimeValid;
759    if(cfg.smoothPR)
760       rheadout.commentList.push_back(string("Ranges smoothed by ") + PrgmName
761          + string(" v.") + DiscFixVersion.substr(0,4) + string(" ") + cfg.Date);
762    if(cfg.smoothPH)
763       rheadout.commentList.push_back(string("Phases debiased by ") + PrgmName
764          + string(" v.") + DiscFixVersion.substr(0,4) + string(" ") + cfg.Date);
765    if(cfg.smoothPR || cfg.smoothPH)
766       rheadout.valid |= RinexObsHeader::commentValid;
767       // invalidate the table
768    if(rheadout.valid & RinexObsHeader::numSatsValid)
769       rheadout.valid ^= RinexObsHeader::numSatsValid;
770    if(rheadout.valid & RinexObsHeader::prnObsValid)
771       rheadout.valid ^= RinexObsHeader::prnObsValid;
772 
773    int iret = SatPassToRinex2File(cfg.OutRinexObs,rheadout,cfg.SPList);
774    if(iret) return -4;
775 
776    return 0;
777 }
778 catch(Exception& e) { GPSTK_RETHROW(e); }
779 }
780 
781 //------------------------------------------------------------------------------------
PrintSPList(ostream & os,string msg,vector<SatPass> & v)782 void PrintSPList(ostream& os, string msg, vector<SatPass>& v)
783 {
784    int i,j,gap;
785    RinexSatID sat;
786    map<RinexSatID,int> lastSP;
787    map<RinexSatID,int>::const_iterator kt;
788 
789    os << "#" << leftJustify(msg,4)
790              << "  N gap  tot sat   ok  s      start time        end time   dt"
791              << " observation types\n";
792 
793    for(i=0; i<v.size(); i++) {
794       os << msg;
795       sat = v[i].getSat();
796       kt = lastSP.find(sat);
797       if(kt == lastSP.end())
798          gap = 0;
799       else {
800          j = kt->second;
801          gap = int((v[i].getFirstTime() - v[j].getLastTime()) / v[i].getDT() + 0.5);
802          lastSP.erase(sat);
803       }
804       lastSP[sat] = i;
805          // n,gap,sat,length,ngood,firstTime,lastTime
806       os << " " << setw(2) << i+1 << " " << setw(4) << gap << " " << v[i];
807       os << endl;
808    }
809 }
810 
811 //------------------------------------------------------------------------------------
812 //------------------------------------------------------------------------------------
GetCommandLine(int argc,char ** argv)813 int GetCommandLine(int argc, char **argv)
814 {
815 try {
816    size_t i;
817       // defaults
818    cfg.DChelp = false;
819    cfg.verbose = false;
820    cfg.decimate = 0.0;
821    cfg.begTime = Epoch(CommonTime::BEGINNING_OF_TIME);
822    cfg.endTime = Epoch(CommonTime::END_OF_TIME);
823    cfg.MaxGap = 600.0;
824 
825    cfg.LogFile = string("df.log");
826    cfg.OutFile = string("df.out");
827    cfg.format = string("%4F %10.3g");
828    cfg.round = 3;
829 
830    cfg.noCA1 = false;      // if false, use CA code on L1 (C1) if P1 absent
831    cfg.useCA1 = true;
832    cfg.forceCA1 = false;   // if true, use CA code on L1 even if P1 is present
833    cfg.noCA2 = false;      // if false, use CA code on L2 (C2) if P2 absent
834    cfg.useCA2 = true;
835    cfg.forceCA2 = false;   // if true, use CA code on L2 even if P2 is present
836    cfg.doGLO = false;      // if true, process GLONASS sats
837 
838    cfg.dt = -1.0;
839 
840    cfg.HDPrgm = PrgmName + string(" v.") + DiscFixVersion.substr(0,4);
841    cfg.HDRunby = string("ARL:UT/SGL/GPSTk");
842 
843    cfg.smoothPR = false;
844    cfg.smoothPH = false;
845    cfg.smooth = false;
846 
847    for(i=0; i<9; i++) cfg.ndt[i]=-1;
848 
849    cfg.inputPath = string(".");
850 
851    // -------------------------------------------------------
852    // create list of command line options, and fill it
853    // put required options first - they will get listed first anyway
854    CommandLine opts;
855    string cmdlineUsage, cmdlineErrors;
856    vector<string> cmdlineUnrecognized;
857 
858    // build the options list == syntax page
859    string PrgmDesc = "Prgm " + PrgmName +
860    " reads a RINEX observation data file containing GPS or GLO dual frequency\n"
861    "   pseudorange and carrier phase measurements, divides the data into\n"
862    "   'satellite passes', and finds and fixes discontinuities in the phases for\n"
863    "   each pass. Output is a list of editing commands for use with RinexEdit.\n"
864    "   " + PrgmName
865    + " will (optionally) write the corrected pseudorange and phase data\n"
866    "   to a new RINEX observation file. Other options will also smooth the\n"
867    "   pseudorange and/or debias the corrected phase.\n\n"
868    "   " + PrgmName + " calls the GPSTk Discontinuity Corrector (GDC vers "
869    + cfg.GDConfig.Version() + ").\n" +
870    "   GDC options (--DC below, and see --DChelp) are passed to GDC,\n"
871    "     except --DCDT is ignored; it is computed from the data.";
872 
873    // temp variables for input
874    bool help=false;
875    const string defaultstartStr("[Beginning of dataset]");
876    const string defaultstopStr("[End of dataset]");
877    string startStr(defaultstartStr);
878    string stopStr(defaultstopStr);
879    vector<string> GLOfreqStrs;
880 
881    // required
882    opts.Add(0,"obs","file", true, true, &cfg.obsfiles,"\n# File I/O:",
883       "Input RINEX obs file - may be repeated");
884 
885    // optional
886    // opts.Add(char, opt, arg, repeat?, required?, &target, pre-descript, descript.);
887    string dummy("");         // dummy for --file
888    opts.Add('f', "file", "name", true, false, &dummy, "",
889             "Name of file containing more options [#-EOL = comment]");
890    opts.Add(0, "obspath", "path", false, false, &cfg.inputPath, "",
891             "Path for input RINEX obs file(s)");
892 
893    opts.Add(0, "start", "time", false, false, &startStr,
894             "\n# Times (time = \"GPSweek,SOW\" OR \"YYYY,Mon,D,H,Min,S)\":",
895             "Start processing the input data at this time");
896    opts.Add(0, "stop", "time", false, false, &stopStr, "",
897             "Stop processing the input data at this time");
898 
899    opts.Add(0, "decimate", "dt", false, false, &cfg.decimate, "# Data config:",
900             "Decimate data to time interval (sec) dt");
901    opts.Add(0, "gap", "t", false, false, &cfg.MaxGap, "",
902             "Minimum gap (sec) between passes [same as --DCMaxGap] ("
903                + asString(int(cfg.MaxGap)) + ")");
904    opts.Add(0, "noCA1", "", false, false, &cfg.noCA1, "",
905             "Fail if L1 P-code is missing, even if L1 CA-code is present");
906    opts.Add(0, "noCA2", "", false, false, &cfg.noCA2, "",
907             "Fail if L2 P-code is missing, even if L2 CA-code is present");
908    opts.Add(0, "forceCA1", "", false, false, &cfg.forceCA1, "",
909             "Use C/A L1 range, even if L1 P-code is present");
910    opts.Add(0, "forceCA2", "", false, false, &cfg.forceCA2, "",
911             "Use C/A L2 range, even if L2 P-code is present");
912    opts.Add(0, "onlySat", "sat", false, false, &cfg.SVonly, "",
913             "Process only satellite <sat> (a SatID, e.g. G21 or R17)");
914    opts.Add(0, "exSat", "sat", true, false, &cfg.exSat, "",
915             "Exclude satellite(s) [e.g. --exSat G22,R]");
916    opts.Add(0, "doGLO", "", false, false, &cfg.doGLO, "",
917             "Process GLONASS satellites as well as GPS");
918    opts.Add(0, "GLOfreq", "sat:n", true, false, &GLOfreqStrs, "",
919             "GLO channel #s for each sat [e.g. R17:-4]");
920 
921    opts.Add(0, "smoothPR", "", false, false, &cfg.smoothPR,
922    "# Smoothing: [NB smoothed pseudorange and debiased phase are not identical.]",
923             "Smooth pseudorange and output in place of raw pseudorange");
924    opts.Add(0, "smoothPH", "", false, false, &cfg.smoothPH, "",
925             "Debias phase and output in place of raw phase");
926    opts.Add(0, "smooth", "", false, false, &cfg.smooth, "",
927             "Same as (--smoothPR AND --smoothPH)");
928 
929    opts.Add(0, "DC", "param=value", true, false, &cfg.DCcmds,
930             "# Discontinuity Corrector (DC) - cycle slip fixer - configuration:",
931             "Set DC parameter <param> to <value>");
932    opts.Add(0, "DChelp", "", false, false, &cfg.DChelp, "",
933             "Print list of DC parameters (all if -v) and their defaults, then quit");
934 
935    opts.Add(0, "log", "file", false, false, &cfg.LogFile, "# Output:",
936             "Output log file name (" + cfg.LogFile + ")");
937    opts.Add(0, "cmd", "file", false, false, &cfg.OutFile, "",
938             "Output file name (for editing commands) (" + cfg.OutFile + ")");
939    opts.Add(0, "format", "fmt", false, false, &cfg.format, "",
940             "Output time format (cf. gpstk::" "Epoch) (" + cfg.format + ")");
941    opts.Add(0, "round", "n", false, false, &cfg.round, "",
942             "Round output time format (--format) to n digits");
943 
944    opts.Add(0, "RinexFile", "file", false, false, &cfg.OutRinexObs, "# RINEX output:",
945             "RINEX (obs) file name for output of corrected data");
946    opts.Add(0, "Prgm", "str", false, false, &cfg.HDPrgm, "",
947             "RINEX header 'PROGRAM' string for output");
948    opts.Add(0, "RunBy", "str", false, false, &cfg.HDRunby, "",
949             "RINEX header 'RUNBY' string for output");
950    opts.Add(0, "Observer", "str", false, false, &cfg.HDObs, "",
951             "RINEX header 'OBSERVER' string for output");
952    opts.Add(0, "Agency", "str", false, false, &cfg.HDAgency, "",
953             "RINEX header 'AGENCY' string for output");
954    opts.Add(0, "Marker", "str", false, false, &cfg.HDMarker, "",
955             "RINEX header 'MARKER' string for output");
956    opts.Add(0, "Number", "str", false, false, &cfg.HDNumber, "",
957             "RINEX header 'NUMBER' string for output");
958 
959    opts.Add(0, "verbose", "", false, false, &cfg.verbose, "# Help:",
960             "print extended output information");
961    //opts.Add(0, "debug", "", false, false, &cfg.debug, "",
962    //         "print debug output at level 0 [debug<n> for level n=1-7]");
963    opts.Add(0, "help", "", false, false, &help, "",
964             "print this and quit");
965 
966    // declare it and parse it; write all errors to string GD.cmdlineErrors
967    int iret = opts.ProcessCommandLine(argc, argv, PrgmDesc,
968                          cmdlineUsage, cmdlineErrors, cmdlineUnrecognized);
969    if(iret == -2) {
970       LOG(ERROR) << " Error - command line failed (memory)";
971       return iret;
972    }
973 
974    // ---------------------------------------------------
975    // do extra parsing -- append errors to GD.cmdlineErrors
976    RinexSatID sat;
977    string msg;
978    vector<string> fields;
979    ostringstream oss;
980 
981    // unrecognized arguments are an error
982    if(cmdlineUnrecognized.size() > 0) {
983       oss << "Error - unrecognized arguments:\n";
984       for(i=0; i<cmdlineUnrecognized.size(); i++)
985          oss << cmdlineUnrecognized[i] << "\n";
986       oss << "End of unrecognized arguments\n";
987    }
988 
989    // if no GLO, add to exSat
990    if(!cfg.doGLO && cfg.SVonly.system != SatelliteSystem::Glonass) {
991       sat.fromString("R");
992       if(vectorindex(cfg.exSat,sat) == -1) cfg.exSat.push_back(sat);
993    }
994 
995    // parse GLO freq
996    for(i=0; i<GLOfreqStrs.size(); i++) {
997       fields = StringUtils::split(GLOfreqStrs[i],':');
998       if(fields.size() != 2) {
999          oss << "Error - invalid GLO sat:chan pair in --GLOfreq input: "
1000             << GLOfreqStrs[i] << endl;
1001       }
1002       else {
1003          sat.fromString(fields[0]);
1004          cfg.GLOfreqChannel.insert(
1005             map<RinexSatID,int>::value_type(sat,asInt(fields[1])));
1006       }
1007    }
1008 
1009    // start and stop times
1010    for(i=0; i<2; i++) {
1011       string msg = (i==0 ? startStr : stopStr);
1012       if(msg == (i==0 ? defaultstartStr : defaultstopStr)) continue;
1013 
1014       int n(StringUtils::numWords(msg,','));
1015       if(n != 2 && n != 6) {
1016          oss << "Error - invalid argument in --" << (i==0 ? "start" : "stop")
1017             << " " << (i==0 ? startStr : stopStr) << endl;
1018          continue;
1019       }
1020 
1021       string fmtGPS("%F,%g"),fmtCAL("%Y,%m,%d,%H,%M,%S");
1022       try {
1023          (i==0 ? cfg.begTime:cfg.endTime).scanf(msg,(n==2 ? fmtGPS:fmtCAL));
1024       }
1025       catch(Exception& e) {
1026          oss << "Error - invalid time in --" << (i==0 ? "start" : "stop")
1027             << " " << (i==0 ? startStr : stopStr) << endl;
1028       }
1029    }
1030 
1031    if(cfg.noCA1) cfg.useCA1 = false;
1032    if(cfg.noCA2) cfg.useCA2 = false;
1033 
1034    // append errors
1035    cmdlineErrors += oss.str();
1036    stripTrailing(cmdlineErrors,'\n');
1037 
1038    // --------------------------------------------------------------------
1039    // dump a summary of the command line configuration
1040    oss.str("");         // clear it
1041    oss << "------ Summary of " << PrgmName
1042       << " command line configuration --------" << endl;
1043    opts.DumpConfiguration(oss);
1044       // perhaps dump the 'extra parsing' things
1045    oss << "------ End configuration summary --------" << endl;
1046    cfg.cmdlineSum = oss.str();
1047 
1048    // --------------------------------------------------------------------
1049    // return
1050    if(opts.hasHelp() || cfg.DChelp) {
1051       stripTrailing(cmdlineUsage,'\n');
1052       LOG(INFO) << cmdlineUsage;
1053       if(cfg.DChelp) cfg.GDConfig.DisplayParameterUsage(LOGstrm,cfg.verbose);
1054       return 1;
1055    }
1056    if(opts.hasErrors()) {
1057       LOG(ERROR) << cmdlineErrors << endl;
1058       return -1;
1059    }
1060    if(!cmdlineErrors.empty()) {     // unrecognized or extra parsing produced an error
1061       LOG(ERROR) << cmdlineErrors;
1062       return -2;
1063    }
1064    return 0;
1065 
1066 } // end try
1067 catch(Exception& e) { GPSTK_RETHROW(e); }
1068 catch(exception& e) { Exception E("std except: "+string(e.what())); GPSTK_THROW(E); }
1069 catch(...) { Exception e("Unknown exception"); GPSTK_THROW(e); }
1070 }
1071 
1072 //------------------------------------------------------------------------------------
DumpConfiguration(void)1073 void DumpConfiguration(void)
1074 {
1075 try {
1076    int i,j;
1077       // print config to log, first DF
1078    LOG(INFO) << "\nHere is the " << PrgmName << " configuration:";
1079    LOG(INFO) << " Input RINEX obs files are:";
1080    for(i=0; i<cfg.obsfiles.size(); i++) {
1081       LOG(INFO) << "   " << cfg.obsfiles[i];
1082    }
1083    LOG(INFO) << " Input path for obs files is " << cfg.inputPath;
1084    if(cfg.decimate > 0.0)
1085       LOG(INFO) << " Decimate to time interval " << cfg.decimate;
1086    if(cfg.begTime > Epoch(CommonTime::BEGINNING_OF_TIME))
1087    LOG(INFO) << " Begin time is "
1088       << printTime(cfg.begTime,"%04Y/%02m/%02d %02H:%02M:%.3f")
1089       << " = " << printTime(cfg.begTime,"%04F/%10.3g");
1090    if(cfg.endTime < Epoch(CommonTime::END_OF_TIME))
1091       LOG(INFO) << " End time is "
1092          << printTime(cfg.endTime,"%04Y/%02m/%02d %02H:%02M:%.3f")
1093          << " = " << printTime(cfg.endTime,"%04F/%10.3g");
1094    if(cfg.useCA1)
1095       {
1096          LOG(INFO) << " Use the L1 C/A pseudorange if P-code is not found";
1097       }
1098    else
1099       {
1100          LOG(INFO) << " Do not use L1 C/A code range (C1)";
1101       }
1102    if(cfg.useCA2)
1103       {
1104          LOG(INFO) << " Use the L2 C/A pseudorange if P-code is not found";
1105       }
1106    else
1107       {
1108          LOG(INFO) << " Do not use L2 C/A code range (C2)";
1109       }
1110    if(cfg.forceCA1) LOG(INFO) <<" Use the L1 C/A pseudorange even if P-code is found";
1111    if(cfg.forceCA2) LOG(INFO) <<" Use the L2 C/A pseudorange even if P-code is found";
1112    if(cfg.dt0 > 0) LOG(INFO) << " dt is input as " << cfg.dt0 << " seconds.";
1113    LOG(INFO) << " Max gap is " << cfg.MaxGap << " seconds";
1114    if(cfg.exSat.size()) {
1115       LOGstrm << " Exclude satellites";
1116       for(i=0; i<cfg.exSat.size(); i++) {
1117          if(cfg.exSat[i].id == -1)
1118             LOGstrm << " (all " << cfg.exSat[i].systemString() << ")";
1119          else
1120             LOGstrm << " " << cfg.exSat[i];
1121       }
1122       LOGstrm << endl;
1123    }
1124    if(cfg.SVonly.id > 0)
1125       LOG(INFO) << " Process only satellite : " << cfg.SVonly;
1126    LOG(INFO) << (cfg.doGLO ? " P":" Do not p") << "rocess GLONASS satellites";
1127    if(cfg.GLOfreqChannel.size() > 0) {
1128       j = 0;
1129       LOGstrm << " GLO frequency channels:";
1130       map<RinexSatID,int>::const_iterator it(cfg.GLOfreqChannel.begin());
1131       while(it != cfg.GLOfreqChannel.end()) {
1132          LOGstrm << (j==0 ? " ":",") << it->first << ":" << it->second;
1133          ++j; ++it;
1134          if((j % 9)==0) { j=0; LOGstrm << endl << "                        "; }
1135       }
1136       LOGstrm << endl;
1137    }
1138    LOG(INFO) << " Log file is " << cfg.LogFile;
1139    LOG(INFO) << " Out file is " << cfg.OutFile;
1140    LOG(INFO) << " Output times in this format '" << cfg.format << "', rounding to "
1141       << cfg.round << " digits.";
1142    if(!cfg.OutRinexObs.empty())
1143       LOG(INFO) << " Output RINEX file name is " << cfg.OutRinexObs;
1144    if(!cfg.HDRunby.empty())
1145       LOG(INFO) << " Output RINEX 'RUN BY' is " << cfg.HDRunby;
1146    if(!cfg.HDObs.empty())
1147       LOG(INFO) << " Output RINEX 'OBSERVER' is " << cfg.HDObs;
1148    if(!cfg.HDAgency.empty())
1149       LOG(INFO) << " Output RINEX 'AGENCY' is " << cfg.HDAgency;
1150    if(!cfg.HDMarker.empty())
1151       LOG(INFO) << " Output RINEX 'MARKER' is " << cfg.HDMarker;
1152    if(!cfg.HDNumber.empty())
1153       LOG(INFO) << " Output RINEX 'NUMBER' is " << cfg.HDNumber;
1154    if(cfg.smoothPR) LOG(INFO) << " 'Smoothed range' option is on\n";
1155    if(cfg.smoothPH) LOG(INFO) << " 'Smoothed phase' option is on\n";
1156    if(!cfg.smooth) LOG(INFO) << " No smoothing.\n";
1157 
1158 } // end try
1159 catch(Exception& e) { GPSTK_RETHROW(e); }
1160 catch(exception& e) { Exception E("std except: "+string(e.what())); GPSTK_THROW(E); }
1161 catch(...) { Exception e("Unknown exception"); GPSTK_THROW(e); }
1162 }
1163 
1164 //------------------------------------------------------------------------------------
1165 //------------------------------------------------------------------------------------
1166