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 RinEdit.cpp
40 /// Read Rinex observation files (version 2 or 3) and edit them, writing the edited
41 /// data to a new RINEX file.
42 
43 // system
44 #include <string>
45 #include <vector>
46 #include <map>
47 #include <iostream>
48 #include <fstream>
49 #include <algorithm>
50 
51 // GPSTK
52 #include "Exception.hpp"
53 #include "StringUtils.hpp"
54 #include "GNSSconstants.hpp"
55 
56 #include "singleton.hpp"
57 #include "expandtilde.hpp"
58 #include "logstream.hpp"
59 #include "CommandLine.hpp"
60 
61 #include "CommonTime.hpp"
62 #include "Epoch.hpp"
63 #include "TimeString.hpp"
64 
65 #include "RinexSatID.hpp"
66 #include "RinexObsID.hpp"
67 #include "Rinex3ObsStream.hpp"
68 #include "Rinex3ObsHeader.hpp"
69 #include "Rinex3ObsData.hpp"
70 
71 //------------------------------------------------------------------------------
72 using namespace std;
73 using namespace gpstk;
74 using namespace StringUtils;
75 
76 //------------------------------------------------------------------------------
77 string version(string("2.4 9/23/15 rev"));
78 // TD
79 // if reading a R2 file, allow obs types in cmds to be R2 versions (C1,etc)
80 // option to replace input with output?
81 // include optional fmt input for t in edit cmds - is this feasible?
82 // if given a 4-char OT and SV, check their consistency
83 // OK - test it.  implement DO - how? copy and edit, or clear and copy?
84 // OK - test it.  edit header when DS (alone) or DO appear ... how?
85 // how to handle aux header data if its first - OF not yet opened
86 // END TD
87 
88 //------------------------------------------------------------------------------
89 //------------------------------------------------------------------------------
90 // class to encapsulate editing commands
91 class EditCmd
92 {
93 public:
94    enum CmdType
95    {
96       invalidCT=0,
97       ofCT,
98       daCT,
99       doCT,
100       dsCT,
101       ddCT,
102       sdCT,
103       ssCT,
104       slCT,
105       bdCT,
106       bsCT,
107       blCT,
108       bzCT,
109       countCT
110    };
111    CmdType type;     // the type of this command
112    RinexSatID sat;   // satellite
113    RinexObsID obs;   // observation type
114    CommonTime ttag;  // associated time tag
115    int sign;         // sign +1,0,-1 meaning start, one-time, stop
116    int idata;        // integer e.g. SSI or LLI
117    double data;      // data e.g. bias value
118    string field;     // OF file name
119 
EditCmd(void)120    EditCmd(void) : type(invalidCT) {}       // default constructor
~EditCmd(void)121    ~EditCmd(void) {}                      // destructor
122 
123       /** constructor from strings, i.e. parser e.g. "DA+","t" or
124        * "BDp","SV,OT,t,s"
125        * @throw Exception */
126    EditCmd(const string typestr, const string arg);
127       /// parse time from string
128    bool parseTime(const string arg, CommonTime& ttag) throw();
129 
130       /// is it valid?
isValid(void)131    inline bool isValid(void) throw()
132    { return (type != invalidCT); }
133 
134       /** dump, with optional message at front
135        * @throw Exception */
136    string asString(string msg=string());
137 
138 }; // end class EditCmd
139 
140 
141 // used for sorting
142 class EditCmdLessThan
143 {
144 public:
operator ()(const EditCmd & ec1,const EditCmd & ec2)145    bool operator()(const EditCmd& ec1, const EditCmd& ec2)
146    { return (ec1.ttag < ec2.ttag); }
147 };
148 
149 //------------------------------------------------------------------------------
150 //------------------------------------------------------------------------------
151 // Object for command line input and global data
152 class Configuration : public Singleton<Configuration>
153 {
154 public:
155 
156       // Default and only constructor
Configuration()157    Configuration() throw() { setDefaults(); }
158 
159       // Create, parse and process command line options and user input
160    int processUserInput(int argc, char **argv) throw();
161 
162       // Design the command line
163    string buildCommandLine(void) throw();
164 
165       // Open the output file, and parse the strings used on the command line
166       // return -4 if log file could not be opened
167    int extraProcessing(string& errors, string& extras) throw();
168 
169       // Parse one of the vector<string> of edit cmd options
170    void parseEditCmds(vector<string>& v, const string l, ostringstream& os) throw();
171 
172 private:
173 
174       // Define default values
setDefaults(void)175    void setDefaults(void) throw()
176    {
177       defaultstartStr = string("[Beginning of dataset]");
178       defaultstopStr = string("[End of dataset]");
179       beginTime = CivilTime(1980,1,6,0,0,0.0,TimeSystem::GPS).convertToCommonTime();
180       endTime = CommonTime::END_OF_TIME;
181       decimate = 0.0;
182 
183       help = verbose = outver2 = false;
184       debug = -1;
185 
186       messHDdc = messHDda = false;
187    }  // end Configuration::SetDefaults()
188 
189 public:
190 
191 // member data
192    CommandLine opts;             // command line options and syntax page
193    static const string prgmName; // program name
194    string Title;                 // id line printed to screen and log
195 
196       // start command line input
197    bool help, verbose, outver2;
198    int debug;
199    string cfgfile;
200 
201    vector<string> messIF, messOF; // RINEX obs file names - IF and OF args
202    string InObspath,OutObspath;  // obs file path
203 
204       // times derived from --start and --stop
205    string defaultstartStr,startStr;
206    string defaultstopStr,stopStr;
207    CommonTime beginTime,endTime,decTime;
208 
209    double decimate,timetol;      // decimate input data
210    string logfile;               // output log file
211 
212       // added "mess" to each of these variables because they were
213       // conflicting with precompiler macro definitions and enum names
214       // and I couldn't be bothered to do a more intelligent renaming.
215 
216       // editing commands
217    bool messHDdc,messHDda,messBZ;
218    string messHDp,messHDr,messHDo,messHDa,messHDx,messHDm,messHDn,messHDt,messHDh,messHDj,messHDk,messHDl,messHDs;
219    vector<string> messHDc,messDA,messDAm,messDAp,messDO,messDS,messDSp,messDSm,messDD,messDDp,messDDm;
220    vector<string> messSD,messSS,messSL,messSLp,messSLm,messBD,messBDp,messBDm,messBS,messBL;
221 
222       // end of command line input
223 
224    string msg;
225    ofstream logstrm;
226    static const string calfmt,gpsfmt,longfmt;
227 
228       // handle commands
229    vector<EditCmd> vecCmds, currCmds;
230    Rinex3ObsStream ostrm;        // RINEX output
231 
232 }; // end class Configuration
233 
234 //------------------------------------------------------------------------------
235 // const members of Configuration
236 const string Configuration::prgmName = string("RinEdit");
237 const string Configuration::calfmt = string("%04Y/%02m/%02d %02H:%02M:%02S");
238 const string Configuration::gpsfmt = string("%4F %10.3g");
239 const string Configuration::longfmt = calfmt + " = " + gpsfmt + " %P";
240 
241 //------------------------------------------------------------------------------
242 // prototypes
243 /**
244  * @throw Exception */
245 int initialize(string& errors);
246 void fixEditCmdList(void) throw();
247 /**
248  * @throw Exception */
249 int processFiles(void);
250 /**
251  * @throw Exception */
252 int processOneEpoch(Rinex3ObsHeader& Rhead, Rinex3ObsHeader& RHout,
253                     Rinex3ObsData& Rdata, Rinex3ObsData& RDout);
254 /**
255  * @throw Exception */
256 int executeEditCmd(const vector<EditCmd>::iterator& it, Rinex3ObsHeader& Rhead,
257                    Rinex3ObsData& Rdata);
258 
259 //------------------------------------------------------------------------------
260 //------------------------------------------------------------------------------
main(int argc,char ** argv)261 int main(int argc, char **argv)
262 {
263       // get the (single) instance of the configuration
264    Configuration& C(Configuration::Instance());
265 
266    try {
267       int iret;
268       clock_t totaltime(clock());
269       Epoch wallclkbeg;
270       wallclkbeg.setLocalTime();
271 
272          // build title = first line of output
273       C.Title = "# " + C.prgmName + ", part of the GPS Toolkit, Ver " + version
274          + ", Run " + printTime(wallclkbeg,C.calfmt);
275 
276       for(;;) {
277             // get information from the command line
278             // iret -2 -3 -4
279          if((iret = C.processUserInput(argc, argv)) != 0) break;
280 
281             // read stores, check input etc
282          string errs;
283          if((iret = initialize(errs)) != 0) {
284             LOG(ERROR) << "------- Input is not valid: ----------\n" << errs
285                        << "\n------- end errors -----------";
286             break;
287          }
288          if(!errs.empty()) LOG(INFO) << errs;         // Warnings are here too
289 
290          processFiles();
291             // iret = 0 = success
292          iret = 0;
293 
294          break;      // mandatory
295       }
296 
297       if(iret == 0) {
298             // print elapsed time
299          totaltime = clock()-totaltime;
300          Epoch wallclkend;
301          wallclkend.setLocalTime();
302          ostringstream oss;
303          oss << C.prgmName << " timing: processing " << fixed << setprecision(3)
304              << double(totaltime)/double(CLOCKS_PER_SEC) << " sec, wallclock: "
305              << setprecision(0) << (wallclkend-wallclkbeg) << " sec.";
306          LOG(INFO) << oss.str();
307       }
308 
309       return iret;
310    }
311    catch(FFStreamError& e) { cerr << "FFStreamError: " << e.what(); }
312    catch(Exception& e) { cerr << "Exception: " << e.what(); }
313    catch (...) { cerr << "Unknown exception.  Abort." << endl; }
314    return 1;
315 
316 }  // end main()
317 
318 //------------------------------------------------------------------------------
319 // return -5 if input is not valid
initialize(string & errors)320 int initialize(string& errors)
321 {
322    try
323    {
324       bool isValid(true);
325       int j;
326       size_t i;
327       Configuration& C(Configuration::Instance());
328       errors = string("");
329       ostringstream ossE;
330 
331          // must have an input file and an output file
332       if(C.messIF.size() == 0) {
333          ossE << "Error : No valid input files have been specified.";
334          isValid = false;
335       }
336       if(C.messOF.size() == 0) {
337          ossE << "Error : No valid output files have been specified.";
338          isValid = false;
339       }
340 
341          // add path to filenames, and expand tilde (~)
342       include_path(C.InObspath, C.messIF);
343 
344          // add path to all OF
345          // also if first OF command has a timetag, remove it and make that the start time
346       for(j=0, i=0; i<C.vecCmds.size(); ++i) {
347          if(C.vecCmds[i].type == EditCmd::ofCT) {
348             if(j == 0 && C.vecCmds[i].ttag != CommonTime::BEGINNING_OF_TIME) {
349                if(C.beginTime < C.vecCmds[i].ttag)
350                   C.beginTime = C.vecCmds[i].ttag;
351                C.vecCmds[i].ttag = CommonTime::BEGINNING_OF_TIME;
352             }
353             j++;
354             include_path(C.OutObspath, C.vecCmds[i].field);
355                //LOG(VERBOSE) << "Full output file name is " << C.vecCmds[i].field;
356          }
357       }
358 
359          // ------ compute and save a reference time for decimation
360       if(C.decimate > 0.0) {
361             // TD what if beginTime == BEGINNING_OF_TIME ?
362          C.decTime = C.beginTime;
363          double s,sow(static_cast<GPSWeekSecond>(C.decTime).sow);
364          s = int(C.decimate * int(sow/C.decimate));
365          if(::fabs(s-sow) > 1.0) LOG(WARNING) << "Warning : decimation reference time "
366                                               << "(--start) is not an even GPS-seconds-of-week mark.";
367          C.decTime = static_cast<CommonTime>(
368             GPSWeekSecond(static_cast<GPSWeekSecond>(C.decTime).week,0.0));
369          LOG(DEBUG) << "Decimate, with final decimate ref time "
370                     << printTime(C.decTime,C.longfmt) << " and step " << C.decimate;
371       }
372 
373          // -------- save errors and output
374       errors = ossE.str();
375       stripTrailing(errors,'\n');
376       replaceAll(errors,"\n","\n# ");
377 
378       if(!isValid) return -5;
379       return 0;
380    }
381    catch(Exception& e) { GPSTK_RETHROW(e); }
382 }  // end initialize()
383 
384 
385 //------------------------------------------------------------------------------
386 // Return 0 ok, >0 number of files successfully read, <0 fatal error
processFiles(void)387 int processFiles(void)
388 {
389    try
390    {
391       Configuration& C(Configuration::Instance());
392       C.beginTime.setTimeSystem(TimeSystem::GPS);
393       C.endTime.setTimeSystem(TimeSystem::GPS);
394       int iret,nfiles;
395       size_t i,nfile;
396       string tag;
397       RinexSatID sat;
398       ostringstream oss;
399 
400       if (C.debug > -1)
401          Rinex3ObsHeader::debug = 1;
402 
403       for(nfiles=0,nfile=0; nfile<C.messIF.size(); nfile++)
404       {
405          Rinex3ObsStream istrm;
406          Rinex3ObsHeader Rhead,RHout;  // use one header for input and output
407          Rinex3ObsData Rdata,RDout;
408          string filename(C.messIF[nfile]);
409 
410             // iret is set to 0 ok, or could not: 1 open file, 2 read header, 3 read data
411          iret = 0;
412 
413             // open the file ------------------------------------------------
414          istrm.open(filename.c_str(),ios::in);
415          if(!istrm.is_open())
416          {
417             LOG(WARNING) << "Warning : could not open file " << filename;
418             iret = 1;
419             continue;
420          }
421          else
422             LOG(DEBUG) << "Opened input file " << filename;
423          istrm.exceptions(ios::failbit);
424 
425             // read the header ----------------------------------------------
426          LOG(INFO) << "Reading header...";
427          try
428          {
429             istrm >> Rhead;
430          }
431          catch(Exception& e)
432          {
433             LOG(WARNING) << "Warning : Failed to read header: " << e.what()
434                          << "\n Header dump follows.";
435             Rhead.dump(LOGstrm);
436             istrm.close();
437             iret = 2;
438             continue;
439          }
440          if(C.debug > -1)
441          {
442             LOG(DEBUG) << "Input header for RINEX file " << filename;
443             Rhead.dump(LOGstrm);
444          }
445          // dump the obs types
446          map<string,vector<RinexObsID> >::const_iterator kt;
447          for (kt = Rhead.mapObsTypes.begin(); kt != Rhead.mapObsTypes.end(); kt++)
448          {
449             sat.fromString(kt->first);
450             oss.str("");
451             oss << "# Header ObsIDs " << sat.systemString3()
452                << " (" << kt->second.size() << "):";
453             for(i=0; i<kt->second.size(); i++)
454                oss << " " << kt->second[i].asString();
455             LOG(INFO) << oss.str();
456          }
457             // we have to set the time system of all the timetags using ttag from file
458          vector<EditCmd>::iterator jt;
459          for (jt=C.vecCmds.begin(); jt != C.vecCmds.end(); ++jt)
460             jt->ttag.setTimeSystem(Rhead.firstObs.getTimeSystem());
461             // -----------------------------------------------------------------
462             // generate output header from input header and DO,DS commands
463          bool mungeData(false);
464          map<string, map<int,int> > mapSysObsIDTranslate;
465 
466          RHout = Rhead;
467          vector<EditCmd>::iterator it;
468          for (it = C.vecCmds.begin(); it != C.vecCmds.end(); it++)
469          {
470             LOG(DEBUG) << "Killing " << it->asString() << " " << it->sat;
471 
472                // DO delete obs without sign
473             if (it->type == EditCmd::doCT)
474             {
475                   // if the system is defined, delete only for that system
476                string sys(asString(it->sat.systemChar()));
477 
478                   // loop over systems (short-circuit if sys is defined)
479                Rinex3ObsHeader::RinexObsMap::iterator jt;
480                for (jt=RHout.mapObsTypes.begin(); jt != RHout.mapObsTypes.end(); ++jt)
481                {
482                   if (sys != string("?") && sys != jt->first)
483                      continue;
484                   RinexObsID obsid(jt->first + it->obs.asString(),
485                                    Rhead.version);
486 
487                      // find the OT in the output header map, and delete it
488                   Rinex3ObsHeader::RinexObsVec::iterator kt;
489                   kt = find(jt->second.begin(), jt->second.end(), obsid);
490                   if(kt == jt->second.end())
491                      continue;
492                   jt->second.erase(kt);
493                      // flag the obs types have changed so the translations need to as well
494                   mungeData = true;
495                }
496             }
497 
498                // DS delete sat without sign and without time
499             else if (it->type == EditCmd::dsCT && it->sign >= 0
500                     && it->ttag == CommonTime::BEGINNING_OF_TIME)
501             {
502                if (it->sat.id == -1)
503                {
504                      // Delete all satellites with this system
505                   Rinex3ObsHeader::PRNNumObsMap::iterator i,j;
506                   for (i = RHout.numObsForSat.begin(); i != RHout.numObsForSat.end();)
507                   {
508                      j = i++;
509                      if (j->first.system == it->sat.system)
510                         RHout.numObsForSat.erase(j);
511                   }
512                   if (it->sat.system == SatelliteSystem::Glonass)
513                      RHout.glonassFreqNo.clear();
514 
515                      // Remove obs types for that system
516                   string sys(asString(it->sat.systemChar()));
517                   RHout.mapObsTypes.erase(sys);
518                }
519                else
520                {
521                      // Just delete a single satellite if its there
522                   Rinex3ObsHeader::PRNNumObsMap::iterator jt = RHout.numObsForSat.find(it->sat);
523                   if (jt != RHout.numObsForSat.end())
524                      RHout.numObsForSat.erase(jt);
525 
526                   Rinex3ObsHeader::GLOFreqNumMap::iterator kt = RHout.glonassFreqNo.find(it->sat);
527                   if (kt != RHout.glonassFreqNo.end())
528                      RHout.glonassFreqNo.erase(kt);
529                }
530             }
531          }  // end loop over edit commands
532 
533             // if mapObsTypes has changed, must make a map of indexes for translation
534             // mapSysObsIDTranslate[sys][input index] = output index
535          if (mungeData)
536          {
537             map<string, vector<RinexObsID> >::iterator jt;
538             for(jt = Rhead.mapObsTypes.begin(); jt != Rhead.mapObsTypes.end(); ++jt)
539             {
540                string sys(jt->first);
541                   // TD what if entire sys is deleted? RHout[sys] does not exist
542                vector<RinexObsID>::iterator kt;
543                for(i=0; i < jt->second.size(); i++)
544                {
545                   kt = find(RHout.mapObsTypes[sys].begin(),
546                             RHout.mapObsTypes[sys].end(), jt->second[i]);
547                   mapSysObsIDTranslate[sys][i]
548                      = (kt == RHout.mapObsTypes[sys].end()
549                         ?  -1                                     // not found
550                         : (kt - RHout.mapObsTypes[sys].begin())); // output index
551                }
552             }
553 
554                // dump it
555             if(C.debug > -1)
556             {
557                for(jt = Rhead.mapObsTypes.begin(); jt != Rhead.mapObsTypes.end(); ++jt)
558                {
559                   string sys(jt->first);
560                   oss.str("");
561                   oss << "Translation map for sys " << sys;
562                   for(i=0; i < jt->second.size(); i++)
563                      oss << " " << i << ":" << mapSysObsIDTranslate[sys][i];
564                   LOG(DEBUG) << oss.str();
565                }
566             }
567          }
568 
569 
570          if (C.outver2)
571          {
572             if (C.debug > -1)
573             {
574                LOG(DEBUG) << "Header pre prepareVer2Write";
575                RHout.dump(LOGstrm);
576             }
577             RHout.prepareVer2Write();
578          }
579 
580             // NB. header will be written by executeEditCmd
581             // -----------------------------------------------------------------
582 
583          if (C.debug > -1)
584          {
585             LOG(DEBUG) << "Output header";
586             RHout.dump(LOGstrm);
587          }
588 
589             // loop over epochs ---------------------------------------------
590          LOG(INFO) << "Reading observations...";
591          while(1)
592          {
593             try { istrm >> Rdata; }
594             catch(Exception& e)
595             {
596                LOG(WARNING) << " Warning : Failed to read obs data (Exception "
597                             << e.getText(0) << "); dump follows.";
598                Rdata.dump(LOGstrm,Rhead);
599                istrm.close();
600                iret = 3;
601                break;
602             }
603             catch(std::exception& e) {
604                Exception ge(string("Std excep: ") + e.what());
605                GPSTK_THROW(ge);
606             }
607             catch(...) {
608                Exception ue("Unknown exception while reading RINEX data.");
609                GPSTK_THROW(ue);
610             }
611 
612                // normal EOF
613             if(!istrm.good() || istrm.eof()) { iret = 0; break; }
614 
615             LOG(DEBUG) << "";
616             LOG(DEBUG) << " Read RINEX data: flag " << Rdata.epochFlag
617                        << ", timetag " << printTime(Rdata.time,C.longfmt);
618 
619                // stay within time limits
620             if(Rdata.time < C.beginTime) {
621                LOG(DEBUG) << " RINEX data timetag " << printTime(C.beginTime,C.longfmt)
622                           << " is before begin time.";
623                continue;
624             }
625             if(Rdata.time > C.endTime) {
626                LOG(DEBUG) << " RINEX data timetag " << printTime(C.endTime,C.longfmt)
627                           << " is after end time.";
628                break;
629             }
630 
631                // decimate
632             if (C.decimate > 0.0)
633             {
634                double dt(::fabs(Rdata.time - C.decTime));
635                dt -= C.decimate * long(0.5 + dt/C.decimate);
636                LOG(DEBUG) << "Decimate? dt = " << fixed << setprecision(2) << dt;
637                if (::fabs(dt) > 0.25)
638                {
639                   LOG(DEBUG) << " Decimation rejects RINEX data timetag "
640                              << printTime(Rdata.time,C.longfmt);
641                   continue;
642                }
643             }
644 
645                // copy data to output
646             RDout = Rdata;
647             if (mungeData)
648             {
649                   // must edit RDout.obs
650                RDout.obs.clear();
651                   // loop over satellites -----------------------------
652                Rinex3ObsData::DataMap::const_iterator kt;
653                for (kt=Rdata.obs.begin(); kt!=Rdata.obs.end(); ++kt)
654                {
655                   sat = kt->first;
656                   string sys(string(1,sat.systemChar()));
657                   for (i=0; i<Rdata.obs[sat].size(); i++)
658                      if (mapSysObsIDTranslate[sys][i] > -1)
659                         RDout.obs[sat].push_back(Rdata.obs[sat][i]);
660                }  // end loop over sats
661             }
662 
663                // apply editing commands, including open files, write out headers
664             iret = processOneEpoch(Rhead, RHout, Rdata, RDout);
665             if(iret < 0) break;
666             if(iret > 0) continue;
667 
668                // write data out
669             try { C.ostrm << RDout; }
670             catch(Exception& e) { GPSTK_RETHROW(e); }
671 
672                // debug: dump the RINEX data objects input and output
673             if (C.debug > -1)
674             {
675                LOG(DEBUG) << "INPUT data ---------------";
676                Rdata.dump(LOGstrm,Rhead);
677                LOG(DEBUG) << "OUTPUT data ---------------";
678                RDout.dump(LOGstrm,Rhead);
679             }
680 
681          }  // end while loop over epochs
682 
683             // clean up
684          istrm.close();
685 
686             // failure due to critical error
687          if(iret < 0) break;
688 
689          if(iret >= 0) nfiles++;
690 
691       }  // end loop over files
692 
693          // final clean up
694       LOG(INFO) << " Close output file.";
695       C.ostrm.close();
696 
697       if(iret < 0) return iret;
698 
699       return nfiles;
700    }
701    catch(Exception& e) { GPSTK_RETHROW(e); }
702 }  // end processFiles()
703 
704 //------------------------------------------------------------------------------
705 // return <0 fatal; >0 skip this epoch
processOneEpoch(Rinex3ObsHeader & Rhead,Rinex3ObsHeader & RHout,Rinex3ObsData & Rdata,Rinex3ObsData & RDout)706 int processOneEpoch(Rinex3ObsHeader& Rhead, Rinex3ObsHeader& RHout,
707                     Rinex3ObsData& Rdata, Rinex3ObsData& RDout)
708 {
709    try
710    {
711       Configuration& C(Configuration::Instance());
712       int iret(0);
713       RinexSatID sat;
714       CommonTime now(Rdata.time);         // TD what if its aux data w/o an epoch?
715 
716          // if aux header data, either output or skip
717       if (RDout.epochFlag > 1)
718       {           // aux header data
719          if (C.messHDda)
720             return 1;
721          return 0;
722       }
723 
724       else
725       {                              // regular data
726          vector<EditCmd>::iterator it, jt;
727          vector<EditCmd> toCurr;
728 
729             // for cmds with ttag <= now either execute and delete, or move to current
730          it = C.vecCmds.begin();
731          while(it != C.vecCmds.end())
732          {
733             if (it->ttag <= now || ::fabs(it->ttag - now) < C.timetol)
734             {
735                LOG(DEBUG) << "Execute vec cmd " << it->asString();
736                   // delete one-time cmds, move others to curr and delete
737                iret = executeEditCmd(it, RHout, RDout);
738                if(iret < 0) return iret;              // fatal error
739 
740                   // keep this command on the current list
741                if (iret > 0) toCurr.push_back(*it); // C.currCmds.push_back(*it);
742 
743                   // if this is a '-' cmd to be deleted, find matching '+' and delete
744                   // note fixEditCmdList() forced every - to have a corresponding +
745                if (iret == 0 && it->sign == -1)
746                {
747                   for (jt = C.currCmds.begin(); jt != C.currCmds.end(); ++jt)
748                      if (jt->type==it->type && jt->sat==it->sat && jt->obs==it->obs)
749                         break;
750                   if (jt == C.currCmds.end())
751                   {
752                      Exception e(string("Execute failed to find + cmd matching ")+it->asString());
753                      GPSTK_THROW(e);
754                   }
755                   C.currCmds.erase(jt);
756                }
757 
758                   // remove from vecCmds
759                it = C.vecCmds.erase(it);              // erase vector element
760             }
761             else
762                ++it;
763          }
764 
765             // apply current commands, deleting obsolete ones
766          it = C.currCmds.begin();
767          while(it != C.currCmds.end())
768          {
769             LOG(DEBUG) << "Execute current cmd " << it->asString();
770                // execute command; delete obsolete commands
771             iret = executeEditCmd(it, RHout, RDout);
772             if(iret < 0)
773                return iret;
774 
775             if(iret == 0)
776                it = C.currCmds.erase(it);              // erase vector element
777             else
778                ++it;
779          }
780 
781          for (it = toCurr.begin(); it != toCurr.end(); ++it)
782             C.currCmds.push_back(*it);
783       }
784 
785       return 0;
786    }
787    catch(Exception& e) { GPSTK_RETHROW(e); }
788 }  // end processOneEpoch()
789 
790 
791 //------------------------------------------------------------------------------
792 // return >0 to put/keep the command on the 'current' queue
793 // return <0 for fatal error
executeEditCmd(const vector<EditCmd>::iterator & it,Rinex3ObsHeader & Rhead,Rinex3ObsData & Rdata)794 int executeEditCmd(const vector<EditCmd>::iterator& it, Rinex3ObsHeader& Rhead,
795                                                         Rinex3ObsData& Rdata)
796 {
797    Configuration& C(Configuration::Instance());
798    size_t i,j;
799    string sys;
800    vector<string> flds;
801    Rinex3ObsData::DataMap::const_iterator kt;
802    vector<RinexObsID>::iterator jt;
803 
804    try
805    {
806       if(it->type == EditCmd::invalidCT)
807       {
808          LOG(DEBUG) << " Invalid command " << it->asString();
809          return 0;
810       }
811 
812          // OF output file --------------------------------------------------------
813       else if(it->type == EditCmd::ofCT)
814       {
815             // close the old file
816          if(C.ostrm.is_open()) { C.ostrm.close(); C.ostrm.clear(); }
817 
818             // open the new file
819          C.ostrm.open(it->field.c_str(),ios::out);
820          if(!C.ostrm.is_open())
821          {
822             LOG(ERROR) << "Error : could not open output file " << it->field;
823             return -1;
824          }
825          C.ostrm.exceptions(ios::failbit);
826 
827          LOG(INFO) << " Opened output file " << it->field << " at time "
828                    << printTime(Rdata.time,C.longfmt);
829 
830             // if this is the first file, apply the header commands
831          if(it->ttag == CommonTime::BEGINNING_OF_TIME)
832          {
833             Rhead.fileProgram = C.prgmName;
834             if(!C.messHDp.empty()) Rhead.fileProgram = C.messHDp;
835             if(!C.messHDr.empty()) Rhead.fileAgency = C.messHDr;
836             if(!C.messHDo.empty()) Rhead.observer = C.messHDo;
837             if(!C.messHDa.empty()) Rhead.agency = C.messHDa;
838             if(!C.messHDj.empty()) Rhead.recNo = C.messHDj;
839             if(!C.messHDk.empty()) Rhead.recType = C.messHDk;
840             if(!C.messHDl.empty()) Rhead.recVers = C.messHDl;
841             if(!C.messHDs.empty()) Rhead.antNo = C.messHDs;
842             if(!C.messHDx.empty())
843             {
844                flds = split(C.messHDx,',');
845                   // TD check n==3,doubles in initialize
846                for(i=0; i<3; i++) Rhead.antennaPosition[i] = asDouble(flds[i]);
847             }
848             if(!C.messHDm.empty()) Rhead.markerName = C.messHDm;
849             if(!C.messHDn.empty())
850             {
851                Rhead.markerNumber = C.messHDn;
852                Rhead.valid |= Rinex3ObsHeader::validMarkerNumber;
853             }
854             if(!C.messHDt.empty())
855                Rhead.antType = C.messHDt;
856             if(!C.messHDh.empty())
857             {
858                flds = split(C.messHDh,',');   // TD check n==3,doubles in initialize
859                for(i=0; i<3; i++) Rhead.antennaDeltaHEN[i] = asDouble(flds[i]);
860             }
861             if(C.messHDdc)
862             {
863                Rhead.commentList.clear();
864                Rhead.valid ^= Rinex3ObsHeader::validComment;
865             }
866             if(C.messHDc.size() > 0)
867             {
868                for(i=0; i<C.messHDc.size(); i++)
869                   Rhead.commentList.push_back(C.messHDc[i]);
870                Rhead.valid |= Rinex3ObsHeader::validComment;
871             }
872             if (C.decimate > 0.0)
873             {
874                Rhead.interval = C.decimate;
875                Rhead.valid |= Rinex3ObsHeader::validInterval;
876             }
877          }
878 
879          Rhead.firstObs = Rdata.time;
880          Rhead.valid.clear(Rinex3ObsHeader::validLastTime);    // turn off
881 
882             // write the header
883          C.ostrm << Rhead;
884          return 0;
885       }
886 
887          // DA delete all ---------------------------------------------------------------
888       else if(it->type == EditCmd::daCT)
889       {
890          switch(it->sign)
891          {
892             case 1: case 0:
893                Rdata.numSVs = 0;                   // clear this data, keep the cmd
894                Rdata.obs.clear();
895                if(it->sign == 0) return 0;
896                break;
897             case -1:
898                return 0;                           // delete the (-) command
899                break;
900          }
901       }
902 
903          // DO delete obs type ----------------------------------------------------------
904          // This is handled above where output header is created w/o the deleted
905          // obs type in it. The data isn't actually deleted from the obs data objects
906          // but just doesn't get written out
907       else if (it->type == EditCmd::doCT)
908          return 0;
909 
910          // DS delete satellite ---------------------------------------------------------
911       else if(it->type == EditCmd::dsCT)
912       {
913          vector<RinexSatID> sats;
914          if (it->sign == -1)
915             return 0;                 // delete the (-) command
916 
917          LOG(DEBUG) << " Delete sat " << it->asString();
918          if (it->sat.id > 0)
919          {
920                // Find a specific satellite
921             kt = Rdata.obs.find(it->sat);
922             if (kt != Rdata.obs.end())                // found the SV
923                sats.push_back(kt->first);
924             else
925                LOG(DEBUG) << " Execute: sat " << it->sat << " not found in data";
926          }
927          else
928          {
929                // Delete all with the specified system
930             for (kt=Rdata.obs.begin(); kt!=Rdata.obs.end(); ++kt)
931                if (kt->first.system == it->sat.system)
932                   sats.push_back(kt->first);
933          }
934 
935          LOG(DEBUG) << " sats.size() " << sats.size() << " Rdata.obs.size() " << Rdata.obs.size();
936 
937          for (j=0; j<sats.size(); j++)
938             Rdata.obs.erase(sats[j]);
939 
940          Rdata.numSVs = Rdata.obs.size();
941 
942          if (it->sign == 0)
943             return 0;                  // delete the one-time command
944       }
945 
946          // -----------------------------------------------------------------------------
947          // the rest require that we find satellite and obsid in Rdata.obs
948       else
949       {
950          vector<RinexSatID> sats;
951 
952          if(it->sign == -1) return 0;                 // delete the (-) command
953 
954          sys = asString(it->sat.systemChar());        // find the system
955 
956             // find the OT in the header map, and get index into vector
957          jt = find(Rhead.mapObsTypes[sys].begin(),
958                    Rhead.mapObsTypes[sys].end(), it->obs);
959          if (jt == Rhead.mapObsTypes[sys].end()) {     // ObsID not found
960                // TD message? user error: ask to delete one that's not there
961             LOG(DEBUG) << " Execute: obstype " << it->obs << " not found in header";
962             return 0;                                 // delete the cmd
963          }
964 
965          i = (jt - Rhead.mapObsTypes[sys].begin());   // index into vector
966 
967             // find the sat
968          if(it->sat.id > 0)
969          {
970             if(Rdata.obs.find(it->sat)==Rdata.obs.end())
971             { // sat not found
972                LOG(DEBUG) << " Execute: sat " << it->sat << " not found in data";
973             }
974             else
975                sats.push_back(it->sat);
976          }
977          else
978          {
979             for(kt=Rdata.obs.begin(); kt!=Rdata.obs.end(); ++kt)
980             {
981                if(kt->first.system == it->sat.system)
982                   sats.push_back(kt->first);
983             }
984          }
985 
986          for(j=0; j<sats.size(); j++)
987          {
988             switch(it->type)
989             {
990                   // DD delete data -----------------------------------------------------
991                case EditCmd::ddCT:
992                   Rdata.obs[sats[j]][i].data = 0.0;
993                   Rdata.obs[sats[j]][i].ssi = 0;
994                   Rdata.obs[sats[j]][i].lli = 0;
995                   break;
996                      // SD set data --------------------------------------------------------
997                case EditCmd::sdCT:
998                   Rdata.obs[sats[j]][i].data = it->data;
999                   break;
1000                      // SS set SSI ---------------------------------------------------------
1001                case EditCmd::ssCT:
1002                   Rdata.obs[sats[j]][i].ssi = it->idata;
1003                   break;
1004                      // SL set LLI ---------------------------------------------------------
1005                case EditCmd::slCT:
1006                   Rdata.obs[sats[j]][i].lli = it->idata;
1007                   break;
1008                      // BD bias data -------------------------------------------------------
1009                case EditCmd::bdCT:     // do not bias
1010                   if(Rdata.obs[sats[j]][i].data != 0.0 || C.messBZ)
1011                      Rdata.obs[sats[j]][i].data += it->data;
1012                   break;
1013                      // BS bias SSI --------------------------------------------------------
1014                case EditCmd::bsCT:
1015                   Rdata.obs[sats[j]][i].ssi += it->idata;
1016                   break;
1017                      // BL bias LLI --------------------------------------------------------
1018                case EditCmd::blCT:
1019                   Rdata.obs[sats[j]][i].lli += it->idata;
1020                   break;
1021                      // never reached ------------------------------------------------------
1022                default:
1023                      // message?
1024                   break;
1025             }
1026          }
1027 
1028          if(it->sign == 0)
1029             return 0;                  // delete the one-time command
1030       }
1031 
1032       return 1;
1033    }
1034    catch(Exception& e) { GPSTK_RETHROW(e); }
1035 }  // end executeEditCmd()
1036 
1037 
1038 //------------------------------------------------------------------------------
processUserInput(int argc,char ** argv)1039 int Configuration::processUserInput(int argc, char **argv) throw()
1040 {
1041    string PrgmDesc,cmdlineUsage, cmdlineErrors, cmdlineExtras;
1042    vector<string> cmdlineUnrecognized;
1043 
1044       // build the command line
1045    opts.DefineUsageString(prgmName + " [options]");
1046    PrgmDesc = buildCommandLine();
1047 
1048       // let CommandLine parse options; write all errors, etc to the passed strings
1049    int iret = opts.ProcessCommandLine(argc, argv, PrgmDesc,
1050                                       cmdlineUsage, cmdlineErrors, cmdlineUnrecognized);
1051 
1052       // handle return values
1053    if(iret == -2) return iret;      // bad alloc
1054    if(iret == -3) return iret;      // invalid command line
1055 
1056       // help: print syntax page and quit
1057    if(opts.hasHelp()) {
1058       LOG(INFO) << cmdlineUsage;
1059       return 1;
1060    }
1061 
1062       // extra parsing (perhaps add to cmdlineErrors, cmdlineExtras)
1063    iret = extraProcessing(cmdlineErrors, cmdlineExtras);
1064    if(iret == -4) return iret;      // log file could not be opened
1065 
1066       // output warning / error messages
1067    if(cmdlineUnrecognized.size() > 0) {
1068       LOG(WARNING) << "Warning - unrecognized arguments:";
1069       for(size_t i=0; i<cmdlineUnrecognized.size(); i++)
1070          LOG(WARNING) << "  " << cmdlineUnrecognized[i];
1071       LOG(WARNING) << "End of unrecognized arguments";
1072    }
1073 
1074    if(!cmdlineExtras.empty()) {
1075       stripTrailing(cmdlineExtras,'\n');
1076       LOG(INFO) << cmdlineExtras;
1077    }
1078 
1079       // fatal errors
1080    if(!cmdlineErrors.empty()) {
1081       stripTrailing(cmdlineErrors,'\n');
1082       replaceAll(cmdlineErrors,"\n","\n ");
1083       LOG(ERROR) << "Errors found on command line:\n " << cmdlineErrors
1084                  << "\nEnd of command line errors.";
1085       return 1;
1086    }
1087 
1088       // success: dump configuration summary
1089    if(debug > -1) {
1090       ostringstream oss;
1091       oss << "------ Summary of " << prgmName
1092           << " command line configuration ------\n";
1093       opts.DumpConfiguration(oss);
1094       if(!cmdlineExtras.empty()) oss << "# Extra Processing:\n" << cmdlineExtras;
1095       oss << "\n------ End configuration summary ------";
1096       LOG(DEBUG) << oss.str();
1097    }
1098 
1099    return 0;
1100 
1101 }  // end Configuration::CommandLine()
1102 
1103 //------------------------------------------------------------------------------
buildCommandLine(void)1104 string Configuration::buildCommandLine(void) throw()
1105 {
1106       // Program description will appear at the top of the syntax page
1107    string PrgmDesc = " Program " + prgmName +
1108       " will open and read RINEX observation files(s), apply editing\n"
1109       " commands, and write out the modified RINEX data to RINEX file(s).\n"
1110       " Input is on the command line, or of the same form in a file (--file).\n"
1111       " NB. Minimum required input is one input file (--IF) and one output file (--OF).\n"
1112       "  Usage: " + prgmName + " [options] [editing commands]\n"
1113       "  Options:";
1114 
1115       // options to appear on the syntax page, and to be accepted on command line
1116       //opts.Add(char, opt, arg, repeat?, required?, &target, pre-desc, desc);
1117       // NB cfgfile is a dummy, but it must exist when cmdline is processed.
1118    opts.Add(0, "IF", "fn", true, false, &messIF,
1119             "# RINEX input and output files",
1120             "Input RINEX observation file name");
1121    opts.Add(0, "ID", "p", false, false, &InObspath, "",
1122             "Path of input RINEX observation file(s)");
1123    opts.Add(0, "OF", "fn", true, false, &messOF, "",
1124             "Output RINEX obs files [also see --OF <f,t> below]");
1125    opts.Add(0, "OD", "p", false, false, &OutObspath, "",
1126             "Path of output RINEX observation file(s)");
1127 
1128    opts.Add('f', "file", "fn", true, false, &cfgfile, "# Other file I/O",
1129             "Name of file containing more options [#->EOL = comment]");
1130    opts.Add('l', "log", "fn", false, false, &logfile, "",
1131             "Output log file name");
1132    opts.Add(0, "ver2", "", false, false, &outver2, "",
1133             "Write out RINEX version 2");
1134 
1135    opts.Add(0, "verbose", "", false, false, &verbose, "# Help",
1136             "Print extra output information");
1137    opts.Add(0, "debug", "", false, false, &debug, "",
1138             "Print debug output at level 0 [debug<n> for level n=1-7]");
1139    opts.Add(0, "help", "", false, false, &help, "",
1140             "Print this syntax page, and quit");
1141 
1142    opts.Add(0, "HDp", "p", false, false, &messHDp, "# ------ Editing commands ------\n"
1143             "# RINEX header modifications (arguments with whitespace must be quoted)",
1144             "Set header 'PROGRAM' field to <p>");
1145    opts.Add(0, "HDr", "rb", false, false, &messHDr, "",
1146             "Set header 'RUN BY' field to <rb>");
1147    opts.Add(0, "HDo", "obs", false, false, &messHDo, "",
1148             "Set header 'OBSERVER' field to <obs>");
1149    opts.Add(0, "HDa", "a", false, false, &messHDa, "",
1150             "Set header 'AGENCY' field to <a>");
1151    opts.Add(0, "HDx", "x,y,z", false, false, &messHDx, "",
1152             "Set header 'POSITION' field to <x,y,z> (ECEF, m)");
1153    opts.Add(0, "HDm", "m", false, false, &messHDm, "",
1154             "Set header 'MARKER NAME' field to <m>");
1155    opts.Add(0, "HDn", "n", false, false, &messHDn, "",
1156             "Set header 'MARKER NUMBER' field to <n>");
1157    opts.Add(0, "HDj", "n", false, false, &messHDj, "",
1158             "Set header 'REC #' field to <n>");
1159    opts.Add(0, "HDk", "t", false, false, &messHDk, "",
1160             "Set header 'REC TYPE' field to <t>");
1161    opts.Add(0, "HDl", "v", false, false, &messHDl, "",
1162             "Set header 'REC VERS' field to <v>");
1163    opts.Add(0, "HDs", "n", false, false, &messHDs, "",
1164             "Set header 'ANT #' field to <n>");
1165    opts.Add(0, "HDt", "t", false, false, &messHDt, "",
1166             "Set header 'ANT TYPE' field to <t>");
1167    opts.Add(0, "HDh", "h,e,n", false, false, &messHDh, "",
1168             "Set header 'ANTENNA OFFSET' field to <h,e,n> (Ht,East,North)");
1169    opts.Add(0, "HDc", "c", true, false, &messHDc, "",
1170             "Add 'COMMENT' <c> to the output header");
1171    opts.Add(0, "HDdc", "", false, false, &messHDdc, "",
1172             "Delete all comments [not --HDc] from input header");
1173    opts.Add(0, "HDda", "", false, false, &messHDda, "",
1174             "Delete all auxiliary header data");
1175 
1176    startStr = defaultstartStr;
1177    stopStr = defaultstopStr;
1178    opts.Add(0, "TB", "t[:f]", false, false, &startStr,
1179             "# Time related [t,f are strings, time t conforms to format f;"
1180             " cf. gpstk::Epoch.]\n# Default t(f) is 'week,sec-of-week'(%F,%g)"
1181             " OR 'y,m,d,h,m,s'(%Y,%m,%d,%H,%M,%S)\n"
1182             " --OF <f,t>        At RINEX time <t>, close output file and open "
1183             "another named <f> ()",
1184             "Start time: Reject data before this time");
1185    opts.Add(0, "TE", "t[:f]", false, false, &stopStr, "",
1186             "Stop  time: Reject data after this time");
1187    opts.Add(0, "TT", "dt", false, false, &timetol, "",
1188             "Tolerance in comparing times, in seconds");
1189    opts.Add(0, "TN", "dt", false, false, &decimate, "",
1190             "If dt>0, decimate data to times = TB + N*dt [sec, w/in tol]");
1191 
1192    opts.Add(0, "DA", "t", true, false, &messDA,
1193             "# In the following <SV> is a RINEX satellite identifier, "
1194             "e.g. G17 R7 E22 R etc.\n"
1195             "#              and <OT> is a 3- or 4-char RINEX observation code "
1196             "e.g. C1C GL2X S2N\n"
1197             "# Delete cmds; for start(stop) cmds. stop(start) time defaults "
1198             "to end(begin) of data\n"
1199             "#     and 'deleting' data for a single OT means it is set to zero "
1200             "- as RINEX requires.",
1201             "Delete all data at a single time <t>");
1202    opts.Add(0, "DA+", "t", true, false, &messDAp, "",
1203             "Delete all data beginning at time <t>");
1204    opts.Add(0, "DA-", "t", true, false, &messDAm, "",
1205             "Stop deleting at time <t>");
1206 
1207    opts.Add(0, "DO", "OT", true, false, &messDO, "",
1208             "Delete RINEX obs type <OT> entirely (incl. header)");
1209 
1210    opts.Add(0, "DS", "SV,t", true, false, &messDS,
1211             " --DS <SV>         Delete all data for satellite <SV> [SV may be char]",
1212             "Delete all data for satellite <SV> at single time <t>");
1213    opts.Add(0, "DS+", "SV,t", true, false, &messDSp, "",
1214             "Delete data for satellite <SV> beginning at time <t>");
1215    opts.Add(0, "DS-", "SV,t", true, false, &messDSm, "",
1216             "Stop deleting data for sat <SV> beginning at time <t>");
1217 
1218    opts.Add(0, "DD", "SV,OT,t", true, false, &messDD, "",
1219             "Delete a single RINEX datum(SV,OT) at time <t>");
1220    opts.Add(0, "DD+", "SV,OT,t", true, false, &messDDp, "",
1221             "Delete all RINEX data(SV,OT) starting at time <t>");
1222    opts.Add(0, "DD-", "SV,OT,t", true, false, &messDDm, "",
1223             "Stop deleting RINEX data(SV,OT) at time <t>");
1224 
1225    opts.Add(0, "SD", "SV,OT,t,d", true, false, &messSD, "",
1226             "Set data(SV,OT) to value <d> at single time <t>");
1227    opts.Add(0, "SS", "SV,OT,t,s", true, false, &messSS, "",
1228             "Set SSI(SV,OT) to value <s> at single time <t>");
1229    opts.Add(0, "SL", "SV,OT,t,l", true, false, &messSL, "",
1230             "Set LLI(SV,OT) to value <l> at single time <t>");
1231    opts.Add(0, "SL+", "SV,OT,t,l", true, false, &messSLp, "",
1232             "Set all LLI(SV,OT) to value <l> starting at time <t>");
1233    opts.Add(0, "SL-", "SV,OT,t,l", true, false, &messSLm, "",
1234             "Stop setting LLI(SV,OT) to value <l> at time <t>");
1235 
1236    opts.Add(0, "BZ", "", false, false, &messBZ,
1237             "# Bias cmds: (BD cmds apply only when data is non-zero, unless --BZ)",
1238             "Apply BD command even when data is zero (i.e. 'missing')");
1239    opts.Add(0, "BS", "SV,OT,t,s", true, false, &messBS, "",
1240             "Add the value <s> to SSI(SV,OT) at single time <t>");
1241    opts.Add(0, "BL", "SV,OT,t,l", true, false, &messBL, "",
1242             "Add the value <l> to LLI(SV,OT) at single time <t>");
1243    opts.Add(0, "BD", "SV,OT,t,d", true, false, &messBD, "",
1244             "Add the value <d> to data(SV,OT) at single time <t>");
1245    opts.Add(0, "BD+", "SV,OT,t,d", true, false, &messBDp, "",
1246             "Add the value <d> to data(SV,OT) beginning at time <t>");
1247    opts.Add(0, "BD-", "SV,OT,t,d", true, false, &messBDm, "",
1248             "Stop adding the value <d> to data(SV,OT) at time <t>");
1249 
1250       // turn off argument expansion for the editing commands
1251    opts.noExpansion("HDc");
1252    opts.noExpansion("OF");
1253    opts.noExpansion("DA");
1254    opts.noExpansion("DA-");
1255    opts.noExpansion("DA+");
1256    opts.noExpansion("DO");
1257    opts.noExpansion("DS");
1258    opts.noExpansion("DS+");
1259    opts.noExpansion("DS-");
1260    opts.noExpansion("DD");
1261    opts.noExpansion("DD+");
1262    opts.noExpansion("DD-");
1263    opts.noExpansion("SD");
1264    opts.noExpansion("SS");
1265    opts.noExpansion("SL");
1266    opts.noExpansion("SL+");
1267    opts.noExpansion("SL-");
1268    opts.noExpansion("BD");
1269    opts.noExpansion("BD+");
1270    opts.noExpansion("BD-");
1271    opts.noExpansion("BS");
1272    opts.noExpansion("BL");
1273 
1274       // deprecated (old,new)
1275       //opts.Add_deprecated("--SP3","--eph");
1276 
1277    return PrgmDesc;
1278 
1279 }  // end Configuration::buildCommandLine()
1280 
1281 //------------------------------------------------------------------------------
extraProcessing(string & errors,string & extras)1282 int Configuration::extraProcessing(string& errors, string& extras) throw()
1283 {
1284    int n;
1285    size_t i;
1286    vector<string> fld;
1287    ostringstream oss,ossx;       // oss for Errors, ossx for Warnings and info
1288 
1289       // start and stop times
1290    for(i=0; i<2; i++) {
1291       static const string fmtGPS("%F,%g"),fmtCAL("%Y,%m,%d,%H,%M,%S");
1292       msg = (i==0 ? startStr : stopStr);
1293       if(msg == (i==0 ? defaultstartStr : defaultstopStr)) continue;
1294 
1295       bool ok(true);
1296       bool hasfmt(msg.find('%') != string::npos);
1297       n = numWords(msg,',');
1298       if(hasfmt) {
1299          fld = split(msg,':');
1300          if(fld.size() != 2) { ok = false; }
1301          else try {
1302                Epoch ep;
1303                stripLeading(fld[0]," \t");
1304                stripLeading(fld[1]," \t");
1305                ep.scanf(fld[0],fld[1]);
1306                (i==0 ? beginTime : endTime) = static_cast<CommonTime>(ep);
1307             }
1308             catch(Exception& e) { ok = false; LOG(INFO) << "excep " << e.what(); }
1309       }
1310       else if(n == 2 || n == 6) {        // try the defaults
1311          try {
1312             Epoch ep;
1313             ep.scanf(msg,(n==2 ? fmtGPS : fmtCAL));
1314             (i==0 ? beginTime : endTime) = static_cast<CommonTime>(ep);
1315          }
1316          catch(Exception& e) { ok = false; LOG(INFO) << "excep " << e.what(); }
1317       }
1318 
1319       if(ok) {
1320          msg = printTime((i==0 ? beginTime : endTime),fmtGPS+" = "+fmtCAL);
1321          if(msg.find("Error") != string::npos) ok = false;
1322       }
1323 
1324       if(!ok)
1325          oss << "Error : invalid time or format in --" << (i==0 ? "start" : "stop")
1326              << " " << (i==0 ? startStr : stopStr) << endl;
1327       else
1328          ossx << (i==0 ? "   Begin time --begin" : "   End time --end") << " is "
1329               << printTime((i==0 ? beginTime : endTime), fmtGPS+" = "+fmtCAL+"\n");
1330    }
1331 
1332       // parse the editing commands
1333    parseEditCmds(messOF, "OF", oss);
1334    parseEditCmds(messDA, "DA", oss);
1335    parseEditCmds(messDAp, "DA+", oss);
1336    parseEditCmds(messDAm, "DA-", oss);
1337    parseEditCmds(messDO, "DO", oss);
1338    parseEditCmds(messDS, "DS", oss);
1339    parseEditCmds(messDSp, "DS+", oss);
1340    parseEditCmds(messDSm, "DS-", oss);
1341    parseEditCmds(messDD, "DD", oss);
1342    parseEditCmds(messDDp, "DD+", oss);
1343    parseEditCmds(messDDm, "DD-", oss);
1344    parseEditCmds(messSD, "SD", oss);
1345    parseEditCmds(messSS, "SS", oss);
1346    parseEditCmds(messSL, "SL", oss);
1347    parseEditCmds(messSLp, "SL+", oss);
1348    parseEditCmds(messSLm, "SL-", oss);
1349    parseEditCmds(messBD, "BD", oss);
1350    parseEditCmds(messBDp, "BD+", oss);
1351    parseEditCmds(messBDm, "BD-", oss);
1352    parseEditCmds(messBS, "BS", oss);
1353    parseEditCmds(messBL, "BL", oss);
1354 
1355       // dump commands for debug
1356       //TEMP if(debug > -1)
1357       //for(i=0; i<vecCmds.size(); i++)
1358       //LOG(INFO) << vecCmds[i].asString(" Input Edit cmd:");
1359 
1360       // 'fix up' list of edit cmds: sort, add -(+) for unmatched +(-), find + > -
1361    fixEditCmdList();
1362 
1363       // dump final list of commands
1364    if(verbose)
1365       for(i=0; i<vecCmds.size(); i++)
1366          ossx << vecCmds[i].asString(" Edit cmd:") << endl;
1367 
1368       // open the log file (so warnings, configuration summary, etc can go there) -----
1369    if(!logfile.empty()) {
1370       logstrm.open(logfile.c_str(), ios::out);
1371       if(!logstrm.is_open()) {
1372          LOG(ERROR) << "Error : Failed to open log file " << logfile;
1373          return -4;
1374       }
1375       LOG(INFO) << "Output redirected to log file " << logfile;
1376       pLOGstrm = &logstrm;
1377    }
1378    LOG(INFO) << Title;
1379 
1380       // add new errors to the list
1381    msg = oss.str();
1382    if(!msg.empty()) errors += msg;
1383    msg = ossx.str();
1384    if(!msg.empty()) extras += msg;
1385 
1386    return 0;
1387 
1388 } // end Configuration::extraProcessing(string& errors) throw()
1389 
1390 //------------------------------------------------------------------------------
1391 // little helper routine for extraProcessing
parseEditCmds(vector<string> & vec,const string lab,ostringstream & os)1392 void Configuration::parseEditCmds(vector<string>& vec, const string lab,
1393                                                            ostringstream& os) throw()
1394 {
1395    for(size_t i=0; i<vec.size(); i++) {
1396       EditCmd ec(lab,vec[i]);
1397       if(ec.isValid()) vecCmds.push_back(ec);
1398       else os << "Error: invalid argument in " << lab << " cmd: >" << vec[i] << "<\n";
1399    }
1400 }
1401 
1402 //------------------------------------------------------------------------------
1403 //------------------------------------------------------------------------------
1404 // constructor from strings, i.e. parser e.g. "DA+","t" or "BDp","SV,OT,t,s"
EditCmd(const string intypestr,const string inarg)1405 EditCmd::EditCmd(const string intypestr, const string inarg)
1406 {
1407    try {
1408       string tag(upperCase(intypestr)), arg(inarg);
1409       vector<string> flds;
1410 
1411       type = invalidCT;                                 // defaults
1412       ttag = CommonTime::BEGINNING_OF_TIME;
1413       sign = idata = 0;
1414       data = 0.0;
1415 
1416       if(tag.size() == 2) sign = 0;                   // pull off sign
1417       else if(tag[2] == '+') sign = 1;
1418       else if(tag[2] == '-') sign = -1;
1419       else return;
1420       tag = tag.substr(0,2);
1421 
1422       flds = split(arg,',');                          // split arg
1423       const size_t n(flds.size());                       // number of args
1424 
1425       if(tag == "OF") {
1426          if(n != 1 && n != 3 && n != 7) return;
1427          field = flds[0];
1428          if(n != 1) {
1429             stripLeading(arg,field+",");
1430             if(!parseTime(arg,ttag)) return;
1431          }
1432          type = ofCT;
1433       }
1434       else if(tag == "DA") {
1435          if(!parseTime(arg,ttag)) return;
1436          type = daCT;
1437       }
1438       else if(tag == "DO") {
1439          if(sign != 0) return;                        // no DO+ or DO-
1440 
1441          if(arg.size() == 4)                          // get sys
1442             sat.fromString(string(1,arg[0]));
1443             // else sat sys is unknown
1444          if(isValidRinexObsID(arg))
1445          {
1446                /** @bug This is kind of ugly.  If someone tries to
1447                 * specify 3.02 codes, they'll be interpreted
1448                 * incorrectly. */
1449             obs = RinexObsID(arg, Rinex3ObsBase::currentVersion);
1450          }
1451          else
1452             return;
1453          type = doCT;
1454       }
1455       else if(tag == "DS") {
1456          if(n != 1 && n != 3 && n != 7) return;    // DS DS,w,sow and DS,y,m,d,h,m,s
1457          try { sat.fromString(flds[0]); } catch(Exception) { return; }
1458          if(n != 1) {                              // time for DS is BeginTime
1459             stripLeading(arg,flds[0]+",");
1460             if(!parseTime(arg,ttag)) return;
1461          }
1462          if(sign == 0 && n == 1) sign = 1;
1463          type = dsCT;
1464       }
1465       else {
1466             // args are SV,OT,t[,d or s or l]
1467          if(n < 4) return;                            // at minimum SV,OT,week,sow
1468 
1469          stripLeading(arg,flds[0]+","+flds[1]+",");   // remove 'SV,OT,' from arg
1470 
1471          string dat;
1472          if(tag != "DD") {                            // strip and save last arg (dsl)
1473             dat = flds[flds.size()-1];
1474             stripTrailing(arg,string(",")+dat);
1475          }
1476          if(!parseTime(arg,ttag)) return;             // get the time
1477 
1478             // parse satellite
1479          try { sat.fromString(flds[0]); } catch(Exception) { return; }
1480 
1481             // add system char to obs string
1482          if(flds[1].size() == 3 && sat.systemChar() != '?')
1483             flds[1] = string(1,sat.systemChar()) + flds[1];
1484             // parse obs type
1485          if(isValidRinexObsID(flds[1]))
1486          {
1487                /** @bug This is kind of ugly.  If someone tries to
1488                 * specify 3.02 codes, they'll be interpreted
1489                 * incorrectly. */
1490             obs = RinexObsID(flds[1], Rinex3ObsBase::currentVersion);
1491          }
1492          else
1493             return;
1494 
1495          if(tag == "DD") { type = ddCT; return; } // DD is done
1496 
1497          if(n != 5 && n != 9) return;           // rest have SV,OT,t,d = 5 or 9 args
1498 
1499          if(tag == "SD" || tag == "BD") {       // double data
1500             if(isScientificString(dat)) data = asDouble(dat); else return;
1501          }
1502          else {                                 // rest have int data
1503             if(isDigitString(dat)) idata = asInt(dat); else return;
1504          }
1505 
1506             // now just set type
1507          if(tag == "SD") type = sdCT;
1508          else if(tag == "SS") type = ssCT;
1509          else if(tag == "SL") type = slCT;
1510          else if(tag == "BS") type = bsCT;
1511          else if(tag == "BL") type = blCT;
1512          else if(tag == "BD") type = bdCT;
1513       }
1514    }
1515    catch(Exception& e) { GPSTK_RETHROW(e); }
1516 }
1517 
1518 //------------------------------------------------------------------------------
parseTime(const string arg,CommonTime & ttag)1519 bool EditCmd::parseTime(const string arg, CommonTime& ttag) throw()
1520 {
1521    static const string fmtGPS("%F,%g"),fmtCAL("%Y,%m,%d,%H,%M,%S");
1522    stripLeading(arg," \t");
1523    int n(numWords(arg,','));
1524    if(n == 2 || n == 6) {
1525       Epoch ep;
1526       try { ep.scanf(arg, (n==2 ? fmtGPS : fmtCAL)); }
1527       catch(StringException) { return false; }
1528       ttag = static_cast<CommonTime>(ep);
1529    }
1530    else return false;
1531 
1532    return true;
1533 }
1534 
1535 //------------------------------------------------------------------------------
1536 // dump, with optional message
asString(string msg)1537 string EditCmd::asString(string msg)
1538 {
1539    try {
1540       Configuration& C(Configuration::Instance());
1541       static map<CmdType,string> typeLabel;
1542       if(typeLabel.size() == 0) {
1543          typeLabel[invalidCT] = string("--invalidCT--   ");
1544          typeLabel[ofCT] = string("OF_Output_File");
1545          typeLabel[daCT] = string("DA_Delete_All ");
1546          typeLabel[doCT] = string("DO_Delete_Obs ");
1547          typeLabel[dsCT] = string("DS_Delete_Sat ");
1548          typeLabel[ddCT] = string("DD_Delete_Data");
1549          typeLabel[sdCT] = string("SD_Set_Data   ");
1550          typeLabel[ssCT] = string("SS_Set_SSI    ");
1551          typeLabel[slCT] = string("SL_Set_LLI    ");
1552          typeLabel[bdCT] = string("BD_Bias_Data  ");
1553          typeLabel[bsCT] = string("BS_Bias_SSI   ");
1554          typeLabel[blCT] = string("BL_Bias_LLI   ");
1555       }
1556 
1557       ostringstream os;
1558 
1559       if(msg.size()) os << msg;
1560       os << " " << typeLabel[type]
1561          << " " << (sign==0 ? "0" : (sign<0 ? "-":"+"))
1562          << " SV:" << sat.toString()
1563          << " OT:" << obs.asString()
1564          << " d:" << fixed << setprecision(4) << data
1565          << " i:" << idata
1566          << " t:" << (ttag == CommonTime::BEGINNING_OF_TIME
1567                       ? "BeginTime" : printTime(ttag,C.longfmt))
1568          << " >" << field << "<";
1569 
1570       return os.str();
1571    }
1572    catch(Exception& e) { GPSTK_RETHROW(e); }
1573    catch(std::exception& e) {
1574       Exception E(string("std::except: ") + e.what());
1575       GPSTK_THROW(E);
1576    }
1577 }
1578 
1579 //------------------------------------------------------------------------------
fixEditCmdList(void)1580 void fixEditCmdList(void) throw()
1581 {
1582    Configuration& C(Configuration::Instance());
1583    vector<EditCmd>::iterator it, jt;
1584    vector<EditCmd> newCmds;
1585 
1586       // sort on time
1587    sort(C.vecCmds.begin(), C.vecCmds.end(), EditCmdLessThan());
1588 
1589       // ensure each - command has a corresponding + command
1590       // (note that + cmds do not need a - cmd: they will just never be turned off)
1591    for(it = C.vecCmds.begin(); it != C.vecCmds.end(); ++it ) {
1592       if(it->sign == -1 && it->type != EditCmd::invalidCT) {
1593          bool havePair(false);
1594          if(it != C.vecCmds.begin()) {
1595             jt = it; --jt;  // --(jt = it);
1596             while(1) {                                // search backwards for match
1597                if(jt->type == it->type &&
1598                   jt->sat == it->sat &&
1599                   jt->obs == it->obs)
1600                {
1601                   if(jt->sign == 1) havePair=true;    // its a match
1602                   else if(jt->sign == -1) {           // this is an error
1603                      LOG(ERROR) << it->asString("Error: repeat '-'");
1604                      //LOG(ERROR) << jt->asString("Error: ref here  ");
1605                      it->type = EditCmd::invalidCT;
1606                   }
1607                   break;
1608                }
1609                if(jt == C.vecCmds.begin()) break;
1610                --jt;
1611             }
1612          }
1613          if(!havePair && it->type != EditCmd::invalidCT) {
1614             EditCmd ec(*it);
1615             ec.sign = 1;
1616             ec.ttag = CommonTime::BEGINNING_OF_TIME;
1617             newCmds.push_back(ec);
1618             LOG(VERBOSE) << ec.asString(" Add cmd:");
1619          }
1620       }
1621    }
1622 
1623    if(newCmds.size() > 0) {
1624       for(it = newCmds.begin(); it != newCmds.end(); ++it )
1625          C.vecCmds.push_back(*it);
1626       newCmds.clear();
1627       sort(C.vecCmds.begin(), C.vecCmds.end(), EditCmdLessThan());
1628    }
1629 
1630       // remove invalidCT commands
1631    it = C.vecCmds.begin();
1632    while(it != C.vecCmds.end()) {
1633       if(it->type == EditCmd::invalidCT)
1634          it = C.vecCmds.erase(it);              // erase vector element
1635       else
1636          ++it;
1637    }
1638 }
1639 
1640 //------------------------------------------------------------------------------
1641 //------------------------------------------------------------------------------
1642 //------------------------------------------------------------------------------
1643