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 test_tides.cpp
40 /// Compute and dump solid earth tide, pole tide and ocean loading tide.
41 
42 // system includes
43 #include <string>
44 #include <vector>
45 #include <iostream>
46 #include <iomanip>
47 #include <fstream>
48 // gpstk
49 #include "Exception.hpp"
50 #include "EphTime.hpp"
51 #include "Position.hpp"
52 // geomatics
53 #include "CommandLine.hpp"
54 #include "logstream.hpp"
55 #include "singleton.hpp"
56 #include "expandtilde.hpp"
57 #include "SolarPosition.hpp"
58 #include "SolarSystem.hpp"
59 #include "SunEarthSatGeometry.hpp"
60 #include "OceanLoadTides.hpp"
61 #include "AtmLoadTides.hpp"
62 #include "logstream.hpp"
63 
64 using namespace std;
65 using namespace gpstk;
66 using namespace StringUtils;
67 
68 //------------------------------------------------------------------------------------
69 static const string tidesVersion("4.0 12/3/19");
70 
71 //------------------------------------------------------------------------------------
72 // data input from command line
73 class InputConfig : public Singleton<InputConfig> {
74 public:
75    // input data
76    bool doSimple,doOcean,doSolid,doPole,doAtm;
77    string logfile, SSEfile, earthfile, oceanfile, atmfile;
78    string fmtGPS,fmtCAL,fmt;
79    ofstream oflog;
80    EphTime beg, end;
81    int begmjd,endmjd;
82    bool help,verbose;
83    int prec, debug, iersyear;          // debug output - prints all the data
84    double dt;
85    string refPosstr;
86    vector<string> oceannames,atmnames;
87    Position posset,posotl,posatm;
88    SolarSystem SolSys;
89    OceanLoadTides oceanStore;
90    AtmLoadTides atmStore;
91 
92    // for CommandLine::ProcessCommandLine()
93    string cmdlineUsage, cmdlineErrors, cmdlineDump;
94    vector<string> cmdlineUnrecog;
95 
96    // ctor with defaults
InputConfig(void)97    InputConfig(void) throw() {
98       fmtGPS = string("%F,%g");
99       fmtCAL = string("%Y,%m,%d,%02H,%M,%f");
100       fmt = string("%4F %10.3g %4Y %2m %2d %2H %2M %6.3f");
101       prec = 5;
102       help = verbose = false;
103       debug = -1;
104       begmjd = 55007.0;
105       endmjd = 55008.0;
106       dt = 900.0;                // 15 minutes
107       refPosstr = string("-740289.9049,-5457071.7352,3207245.5544  #ARL.2012.0000");
108       iersyear = 2010;
109       // add options to make it look like three programs
110       doPole = doSolid = doOcean = doAtm = false;
111       // make default NOT to use SSE and earth
112       doSimple = true;
113       //SSEfile = string("/home/btolman/.grits/SolarSystem1960to2020.405.bin");
114       //iersyear = 2010;   // or 2003
115       //SSEfile = string("/home/btolman/.grits/SolarSystem1960to2020.405.bin");
116       //earthfile = string("/home/btolman/.grits/finals2000A.data");
117       //oceanfile = string("/home/btolman/.grits/ocean.blq");
118    }
119 
120 }; // end class InputConfig
121 
122 //------------------------------------------------------------------------------------
123 // prototypes
124 /**
125  * @throw Exception
126  */
127 int GetCommandLine(int argc, char **argv);
128 
129 //------------------------------------------------------------------------------------
main(int argc,char ** argv)130 int main(int argc, char **argv)
131 {
132 try {
133    InputConfig& C=InputConfig::Instance();
134    int i, iret;
135 
136       // Title and description
137    string Title =
138       "tides, a program to compute solid Earth, ocean loading and pole tides, Ver "
139       + tidesVersion;
140    LOG(INFO) << Title;
141 
142    // TEMP, for debugging CommandLine;
143    //LOGlevel = ConfigureLOG::Level("DEBUG");
144 
145    iret = GetCommandLine(argc, argv);
146    LOG(DEBUG) << "GetCommandLine returned " << iret;
147    //LOG(INFO) << "LOG level is " << LOGlevel;
148 
149    // return with help or errors - print to the screen - LOG is cout here
150    if(iret) {
151       if(iret == 1) {                    // help, else -1 == error
152          if(!C.cmdlineUsage.empty()) { LOG(INFO) << C.cmdlineUsage; }
153          if(!C.cmdlineDump.empty()) { LOG(INFO) << endl << C.cmdlineDump; }
154       }
155       else
156          LOG(ERROR) << C.cmdlineErrors;
157       return iret;
158    }
159 
160    // open output file
161    if(!C.logfile.empty()) {
162       C.oflog.open(C.logfile.c_str(),ios_base::out);
163       if(!C.oflog.is_open()) {
164          LOG(ERROR) << "Failed to open log file " << C.logfile;
165          return -1;
166       }
167       else {
168          C.oflog.exceptions(ios::failbit);
169          LOG(INFO) << "Output directed to file " << C.logfile;
170          pLOGstrm = &C.oflog; // ConfigureLOG::Stream() = &C.oflog;
171          ConfigureLOG::ReportLevels() = false;
172          ConfigureLOG::ReportTimeTags() = false;
173          // debug and verbose handled earlier in GetCommandLine
174          LOG(INFO) << Title;
175       }
176    }
177 
178    //// turn off screen if output is cout
179    //if(pLOGstrm == &cout) C.screen = false;
180 
181    // dump configuration
182    LOG(VERBOSE) << endl << C.cmdlineDump;
183 
184    // -------------------------------------------------------------------------
185    // initialize
186    bool isValid(true);
187 
188    if(!C.SSEfile.empty()) {
189       C.doSimple = false;
190 
191       // initialize solar system
192       if(C.iersyear == 1996) C.SolSys.setConvention(IERSConvention::IERS1996);
193       if(C.iersyear == 2003) C.SolSys.setConvention(IERSConvention::IERS2003);
194       if(C.iersyear == 2010) C.SolSys.setConvention(IERSConvention::IERS2010);
195 
196       // read solar system ephemeris file
197       expand_filename(C.SSEfile);
198       C.SolSys.initializeWithBinaryFile(C.SSEfile);
199       LOG(INFO) << "Solar System Ephemeris is DE"
200          << C.SolSys.EphNumber() << "; timespan "
201          << C.SolSys.startTime() << " to " << C.SolSys.endTime()
202          << " with " << C.SolSys.getConvention();
203 
204       // read EarthOP file and fill store
205       expand_filename(C.earthfile);
206       C.SolSys.addFile(C.earthfile);
207 
208       // trim the EOP list, as we might be using e.g. finals2000A.data (huge);
209       // trim generously so ephemeris limits the data, not EOP
210       if(C.beg.dMJD()-10 > C.SolSys.getFirstTimeMJD() ||
211          C.end.dMJD()+10 < C.SolSys.getLastTimeMJD()) {
212             //LOG(VERBOSE) << "Trim EOP store based on input time limits.";
213             C.SolSys.edit(C.beg.lMJD()-10, C.end.lMJD()+10);
214       }
215 
216       // dump the EOP summary
217       if(C.verbose) C.SolSys.dump(C.debug > -1 ? 1 : 0, LOGstrm);
218    }
219    else {
220       LOG(INFO) << "Solar System Ephemeris is simple Solar Position";
221    }
222 
223    // doSimple and doPole inconsistent
224    if(C.doSimple && C.doPole) {
225       LOG(ERROR) << "Error - pole option requires SSEfile and earthfile; abort.";
226       isValid = false;
227    }
228 
229    // fill ocean store
230    if(C.doOcean && (C.oceanfile.empty() || C.oceannames.size()==0)) {
231       // no ocean file and doOcean inconsistent
232       LOG(ERROR) << "Error - ocean option requires oceanfile and oceansite; abort.";
233       isValid = false;
234    }
235    else if(C.doOcean) {
236       vector<string> sites(C.oceannames);
237       // add the ocean file(s) and name(s) to the store
238       try {
239          C.oceanStore.initializeSites(sites,C.oceanfile);
240       }
241       catch(Exception& e) {
242          LOG(ERROR) << "Error - failed to open ocean loading file: "
243             << C.oceanfile << " :\n" << e.what();
244          isValid = false;
245       }
246       catch(exception& e) {
247          LOG(ERROR) << "Error - failed to open ocean loading file: "
248             << C.oceanfile << " :\n" << e.what();
249          isValid = false;
250       }
251 
252       // get the site
253       for(i=0; i<C.oceannames.size(); i++) {
254          if(C.oceannames[i].empty())
255             isValid = false;
256          else try {
257             Triple pos = C.oceanStore.getPosition(C.oceannames[i]);
258             if(pos[0] == 0.0 && pos[1] == 0.0) {
259                LOG(ERROR) << "Error - Failed to find ocean site name "
260                               << C.oceannames[i];
261                isValid = false;
262             }
263             else LOG(VERBOSE) << "Found ocean loading site " << C.oceannames[i]
264                      << " at position " << pos[0] << "N, " << pos[1] << "E";
265 
266             C.posotl.setGeodetic(pos[0],pos[1],0.0);
267 
268          }
269          catch(Exception& e) {
270             LOG(ERROR) << "Error - failed to get ocean loading site: "
271                << C.oceannames[i] << " from ocean loading files" << " :\n"<< e.what();
272             isValid = false;
273          }
274          catch(exception& e) {
275             LOG(ERROR) << "Error - failed to get ocean loading site: "
276                << C.oceannames[i] << " from ocean loading files\n" << e.what();
277             isValid = false;
278          }
279       }
280    }
281 
282    // fill atmospheric loading store
283    if(C.doAtm && (C.atmfile.empty() || C.atmnames.size()==0)) {
284       // no atm file and doAtm inconsistent
285       LOG(ERROR) << "Error - atm option requires atmfile and atmsite; abort.";
286       isValid = false;
287    }
288    else if(C.doAtm) {
289       vector<string> sites(C.atmnames);
290       // add the atm file(s) and name(s) to the store
291       try {
292          C.atmStore.initializeSites(sites,C.atmfile);
293       }
294       catch(Exception& e) {
295          LOG(ERROR) << "Error - failed to open atm loading file: "
296             << C.atmfile << " :\n" << e.what();
297          isValid = false;
298       }
299       catch(exception& e) {
300          LOG(ERROR) << "Error - failed to open atm loading file: "
301             << C.atmfile << " :\n" << e.what();
302          isValid = false;
303       }
304 
305       // get the site
306       for(i=0; i<C.atmnames.size(); i++) {
307          if(C.atmnames[i].empty())
308             isValid = false;
309          else try {
310             Triple pos = C.atmStore.getPosition(C.atmnames[i]);
311             if(pos[0] == 0.0 && pos[1] == 0.0) {
312                LOG(ERROR) << "Error - Failed to find atm site name "
313                               << C.atmnames[i];
314                isValid = false;
315             }
316             else LOG(VERBOSE) << "Found atm loading site " << C.atmnames[i]
317                      << " at position " << pos[0] << "N, " << pos[1] << "E";
318 
319             C.posatm.setGeodetic(pos[0],pos[1],0.0);
320 
321          }
322          catch(Exception& e) {
323             LOG(ERROR) << "Error - failed to get atm loading site: "
324                << C.atmnames[i] << " from atm loading files" << " :\n"<< e.what();
325             isValid = false;
326          }
327          catch(exception& e) {
328             LOG(ERROR) << "Error - failed to get atm loading site: "
329                << C.atmnames[i] << " from atm loading files\n" << e.what();
330             isValid = false;
331          }
332       }
333    }
334 
335    // get rotation matrix XYZ->NEU for pos
336    Matrix<double> Rotate,RotOTL,RotATM;
337    Rotate = NorthEastUp(C.posset);
338    RotOTL = NorthEastUp(C.posotl);
339    RotATM = NorthEastUp(C.posatm);
340 
341    // -------------------------------------------------------------------------
342    if(!isValid) {
343       if(C.oflog.is_open()) C.oflog.close();
344       return iret;
345    }
346 
347    // do it
348    if(C.doSolid) {
349    LOG(INFO) << "SET   MJD HH:MM:SS.sss "
350             << "SET_X_cm  SET_Y_cm  SET_Z_cm  SET_N_cm  SET_E_cm  SET_U_cm";
351    }
352    if(C.doOcean) {
353       LOG(INFO) << "OLT   MJD HH:MM:SS.sss "
354             << "OLT_X_cm  OLT_Y_cm  OLT_Z_cm  OLT_N_cm  OLT_E_cm  OLT_U_cm  site";
355    }
356    if(C.doPole) {
357       LOG(INFO) << "POT   MJD HH:MM:SS.sss "
358             << "POT_X_cm  POT_Y_cm  POT_Z_cm  POT_N_cm  POT_E_cm  POT_U_cm";
359    }
360    if(C.doAtm) {
361       LOG(INFO) << "ATL   MJD HH:MM:SS.sss "
362             << "ATL_X_cm  ATL_Y_cm  ATL_Z_cm  ATL_N_cm  ATL_E_cm  ATL_U_cm  site";
363    }
364 
365    // loop over times
366    int w(C.prec+3);
367    double arad;
368    double mjd;
369    EphTime ttag;
370    Vector<double> XYZ(3),NEU(3);
371    Triple dXYZ,dNEU;
372    for(mjd=static_cast<double>(C.begmjd);
373        mjd<static_cast<double>(C.endmjd); mjd += C.dt/SEC_PER_DAY)
374    {
375       ttag.setMJD(mjd);
376       ttag.setTimeSystem(TimeSystem::UTC);
377       if(C.doSolid) {
378          if(C.doSimple) {
379             Position Sun(SolarPosition(ttag, arad));
380             Position Moon(LunarPosition(ttag, arad));
381             dXYZ = computeSolidEarthTides(C.posset, ttag, Sun, Moon);
382          }
383          else {
384             dXYZ = C.SolSys.computeSolidEarthTides(C.posset, ttag);
385          }
386          for(i=0; i<3; i++) XYZ(i) = dXYZ[i];
387          NEU = Rotate * XYZ;
388 
389          LOG(INFO) << "SET " << ttag.asMJDString()
390             << fixed << setprecision(C.prec)
391             << " " << setw(w) << XYZ(0)*100.
392             << " " << setw(w) << XYZ(1)*100.
393             << " " << setw(w) << XYZ(2)*100.
394             << " " << setw(w) << NEU(0)*100.
395             << " " << setw(w) << NEU(1)*100.
396             << " " << setw(w) << NEU(2)*100.;
397       }
398 
399       if(C.doOcean) {
400          for(size_t j=0; j<C.oceannames.size(); j++) {
401             dNEU = C.oceanStore.computeDisplacement(C.oceannames[j], ttag);
402             for(i=0; i<3; i++) NEU(i) = dNEU[i];
403             XYZ = transpose(RotOTL) * NEU;
404 
405             LOG(INFO) << "OLT " << ttag.asMJDString()
406                << fixed << setprecision(C.prec)
407                << " " << setw(w) << XYZ(0)*100.
408                << " " << setw(w) << XYZ(1)*100.
409                << " " << setw(w) << XYZ(2)*100.
410                << " " << setw(w) << NEU(0)*100.
411                << " " << setw(w) << NEU(1)*100.
412                << " " << setw(w) << NEU(2)*100.
413                << "  " << C.oceannames[j];
414          }
415       }
416 
417       if(C.doPole) {
418          dXYZ = C.SolSys.computePolarTides(C.posset, ttag);
419          for(i=0; i<3; i++) XYZ(i) = dXYZ[i];
420          NEU = Rotate * XYZ;
421 
422          LOG(INFO) << "POT " << ttag.asMJDString()
423             << fixed << setprecision(C.prec)
424             << " " << setw(w) << XYZ(0)*100.
425             << " " << setw(w) << XYZ(1)*100.
426             << " " << setw(w) << XYZ(2)*100.
427             << " " << setw(w) << NEU(0)*100.
428             << " " << setw(w) << NEU(1)*100.
429             << " " << setw(w) << NEU(2)*100.;
430       }
431 
432       if(C.doAtm) {
433          for(size_t j=0; j<C.atmnames.size(); j++) {
434             dNEU = C.atmStore.computeDisplacement(C.atmnames[j], ttag);
435             for(i=0; i<3; i++) NEU(i) = dNEU[i];
436             XYZ = transpose(RotATM) * NEU;
437 
438             LOG(INFO) << "ATL " << ttag.asMJDString()
439                << fixed << setprecision(C.prec)
440                << " " << setw(w) << XYZ(0)*100.
441                << " " << setw(w) << XYZ(1)*100.
442                << " " << setw(w) << XYZ(2)*100.
443                << " " << setw(w) << NEU(0)*100.
444                << " " << setw(w) << NEU(1)*100.
445                << " " << setw(w) << NEU(2)*100.
446                << "  " << C.atmnames[j];
447          }
448       }
449 
450    }  // end loop over times
451 
452    if(C.oflog.is_open()) C.oflog.close();
453 
454    return iret;
455 }
456 catch(gpstk::Exception& e) { cerr << "Exception: " << e; }
457 catch (...) { cerr << "Unknown exception.  Abort." << endl; }
458    return 1;
459 }   // end main()
460 
461 //------------------------------------------------------------------------------------
GetCommandLine(int argc,char ** argv)462 int GetCommandLine(int argc, char **argv)
463 {
464 try {
465    InputConfig& C=InputConfig::Instance();
466    int i;
467 
468    // ---------------------------------------------------------------
469    // create list of command line options, and fill it
470    CommandLine opts;
471 
472    // build the command line == syntax page
473    opts.DefineUsageString("tides [options]");
474    string PrgmDesc =
475    "Prgm tides computes tides (solid earth, ocean loading, pole) for a given\n"
476    " time (UTC) and site, and dumps them to the screen.\n"
477    " NB One or more of options (solid ocean pole atm) must be provided.\n"
478    " NB ocean option requires oceanfile and oceansite.\n"
479    " NB atm option requires atmfile and atmsite.\n"
480    " NB pole requires SSEfile and earthfile.\n"
481    " NB SSEfile and earthfile are optional (unless pole); they are more accurate.\n"
482    " Input is on the command line, or of the same format in a file (see --file);\n"
483    " lines in that file which begin with '#' are ignored.\n"
484    " Options are shown below, with a description and default value, if any, in ().\n"
485    ;
486 
487    // opts.Add(char, opt, arg, repeat?, required?, &target, pre-descript, descript.);
488    bool req(false);  // obs is not req'd b/c filenames can appear alone (Unrecog)
489    // optional args
490    string dummy("");         // dummy for --file
491    opts.Add(0, "solid", "", false, req, &C.doSolid,
492             "# Computation: Require one or more of the following:",
493             "Output Solid Earth tide");
494    opts.Add(0, "ocean", "", false, req, &C.doOcean, "",
495             "Output Ocean loading [requires oceanfile and oceansite]");
496    opts.Add(0, "pole", "", false, req, &C.doPole, "",
497             "Output Polar tide [requires SSEfile and earthfile]");
498    opts.Add(0, "atm", "", false, req, &C.doAtm, "",
499             "Output Atmospheric loading [requires atmfile and atmsite]");
500    opts.Add('f', "file", "name", true, req, &dummy, "# File I/O:",
501             "Name of file containing more options [#-EOL = comment]");
502    opts.Add('o', "log", "fn", false, req, &C.logfile, "",
503             "Output the summary to a file named <fn>");
504    opts.Add(0, "start", "mjd", false, req, &C.begmjd, "",
505             "Start processing the input data at this MJD");
506    opts.Add(0, "stop", "mjd", false, req, &C.endmjd, "",
507             "Stop processing the input data at this MJD");
508    opts.Add(0, "dt", "sec", false, req, &C.dt, "",
509             "Timestep in seconds");
510    opts.Add(0, "refPos", "X,Y,Z", false, req, &C.refPosstr, "",
511             "Position for SET (ECEF XYZ)");
512    opts.Add(0, "IERS", "year", false, req, &C.iersyear, "",
513             "Year of IERS convention: 1996, 2003 or 2010");
514    opts.Add(0, "SSEfile", "fn", false, req, &C.SSEfile, "",
515             "Solar System ephemeris binary file name [else use simple ephem]");
516    opts.Add(0, "earthfile", "fn", false, req, &C.earthfile, "",
517             "Earth orientation parameter file name [if & only if --SSEfile]");
518    opts.Add(0, "oceanfile", "fn", true, req, &C.oceanfile, "",
519             "Ocean loading file name");
520    opts.Add(0, "oceansite", "name", true, req, &C.oceannames, "",
521             "Site name in ocean loading file");
522    opts.Add(0, "atmfile", "fn", true, req, &C.atmfile, "",
523             "Ocean loading file name");
524    opts.Add(0, "atmsite", "name", true, req, &C.atmnames, "",
525             "Site name in atmospheric loading file");
526    opts.Add(0, "timefmt", "f", false, req, &C.fmt, "# Output",
527             "Output format for time tag");
528    opts.Add('p', "prec", "n", false, req, &C.prec, "",
529             "Output precision for offsets");
530    opts.Add('d', "debug", "", false, req, &C.debug, "",
531             "Print debug output at level 0 [debug<n> for level n=1-7]");
532    opts.Add(0, "verbose", "", false, req, &C.verbose, "",
533             "print extended output information");
534    opts.Add('h', "help", "", false, req, &C.help, "",
535             "Print this syntax page and quit");
536 
537    // options to ignore
538    //opts.Add_ignore_off("--Scan");
539    // deprecated args
540    //opts.Add_deprecated("--dummy","--thisisatest");
541    // undocumented args
542    //opts.Add(0, "progress", "", false, req, &C.progress, "", "", false);
543 
544    // ---------------------------------------------------------------
545    // declare it and parse it; write all errors to string C.cmdlineErrors
546    int iret = opts.ProcessCommandLine(argc, argv, PrgmDesc,
547                                  C.cmdlineUsage, C.cmdlineErrors, C.cmdlineUnrecog);
548    if(iret == -2) return -1;     // bad alloc
549    if(iret == -3) return -1;     // invalid command line
550 
551    // ---------------------------------------------------------------
552    // do extra parsing - append errors to C.cmdlineErrors
553    string msg;
554    vector<string> fields;
555    ostringstream oss;
556 
557    // unrecognized arguments are an error
558    if(C.cmdlineUnrecog.size() > 0) {
559       oss << " Error - unrecognized arguments:\n";
560       for(i=0; i<C.cmdlineUnrecog.size(); i++)
561          oss << C.cmdlineUnrecog[i] << "\n";
562       oss << " End of unrecognized arguments\n";
563    }
564 
565    // start and stop times
566    C.beg.setMJD(C.begmjd);
567    C.end.setMJD(C.endmjd);
568 
569    // reference position
570    if(!C.refPosstr.empty()) {
571       fields = StringUtils::split(C.refPosstr,',');
572       if(fields.size() != 3)
573          oss << "Error - invalid field in --refPos input: " << C.refPosstr << endl;
574       else {
575          try { C.posset.setECEF(StringUtils::asDouble(fields[0]),
576                              StringUtils::asDouble(fields[1]),
577                              StringUtils::asDouble(fields[2]));
578          }
579          catch(Exception& e) {
580             oss << "Error - invalid position in --refPos input: "
581                << C.refPosstr << endl;
582          }
583       }
584    }
585 
586    if(C.iersyear != 1996 && C.iersyear != 2003 && C.iersyear != 2010) {
587       oss << "Error - invalid field in --IERS input: " << C.iersyear
588             << " - use 1996, 2003 or 2010." << endl;
589    }
590 
591    // append errors onto cmdlineErrors
592    C.cmdlineErrors += oss.str();
593    stripTrailing(C.cmdlineErrors,'\n');
594 
595    // ---------------------------------------------------------------
596    // dump a summary of command line configuration
597    if(C.verbose) {
598       oss.str("");                  // clear it
599       oss << "------ Summary of tides command line configuration --------" << endl;
600       opts.DumpConfiguration(oss);
601       oss << endl << "   Begin time is " << C.beg.asMJDString() << endl;
602       oss << "   End time is " << C.end.asMJDString() << endl;
603       oss << "   Position is "
604          << C.posset.printf("ECEF %.4x %.4y %.4z meters") << endl;
605       oss << "------ End configuration summary --------" << endl;
606       C.cmdlineDump = oss.str();
607       stripTrailing(C.cmdlineDump,'\n');
608    }
609 
610    // ---------------------------------------------------------------
611    // return
612    if(!C.cmdlineErrors.empty()) return -1;      // errors
613    if(C.help) return 1;                         // help
614 
615    return 0;                                    // ok
616 }
617 catch(Exception& e) { GPSTK_RETHROW(e); }
618 catch(exception& e) { Exception E("std except: "+string(e.what())); GPSTK_THROW(E); }
619 catch(...) { Exception e("Unknown exception"); GPSTK_THROW(e); }
620    return -1;
621 }
622 
623 //------------------------------------------------------------------------------------
624 //------------------------------------------------------------------------------------
625