1 //==============================================================================
2 //
3 //  This file is part of GPSTk, the GPS Toolkit.
4 //
5 //  The GPSTk is free software; you can redistribute it and/or modify
6 //  it under the terms of the GNU Lesser General Public License as published
7 //  by the Free Software Foundation; either version 3.0 of the License, or
8 //  any later version.
9 //
10 //  The GPSTk is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU Lesser General Public License for more details.
14 //
15 //  You should have received a copy of the GNU Lesser General Public
16 //  License along with GPSTk; if not, write to the Free Software Foundation,
17 //  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
18 //
19 //  This software was developed by Applied Research Laboratories at the
20 //  University of Texas at Austin.
21 //  Copyright 2004-2020, The Board of Regents of The University of Texas System
22 //
23 //==============================================================================
24 
25 //==============================================================================
26 //
27 //  This software was developed by Applied Research Laboratories at the
28 //  University of Texas at Austin, under contract to an agency or agencies
29 //  within the U.S. Department of Defense. The U.S. Government retains all
30 //  rights to use, duplicate, distribute, disclose, or release this software.
31 //
32 //  Pursuant to DoD Directive 523024
33 //
34 //  DISTRIBUTION STATEMENT A: This software has been approved for public
35 //                            release, distribution is unlimited.
36 //
37 //==============================================================================
38 
39 /**
40  * @file Position.cpp
41  * class gpstk::Position encapsulates 3-D positions, both geographic positions,
42  *    expressed as geodetic (with respect to any geoid), geocentric or
43  *    Earth-centered, Earth-fixed (cartesian) coordinates, as well as ordinary
44  *    positions defined by spherical or cartesian coordinates. Position inherits
45  *    from class Triple.
46  */
47 
48 #include "Position.hpp"
49 #include "WGS84Ellipsoid.hpp"
50 #include "GNSSconstants.hpp"    // for TWO_PI, etc
51 #include "GNSSconstants.hpp"             // for RAD_TO_DEG, etc
52 #include "MiscMath.hpp"             // for RSS, SQRT
53 
54 namespace gpstk
55 {
56 
57    using namespace std;
58    using namespace StringUtils;
59 
60    // ----------- Part  1: coordinate systems --------------------------------
61       // Labels for coordinate systems supported by Position
62    static const char *SystemNames[] = {
63       "Unknown",
64       "Geodetic",
65       "Geocentric",
66       "Cartesian",
67       "Spherical"};
68 
69       // return string giving name of coordinate system
getSystemName()70    string Position::getSystemName()
71       throw()
72    { return SystemNames[system]; }
73 
74    // ----------- Part  2: tolerance -----------------------------------------
75       // One millimeter tolerance.
76    const double Position::ONE_MM_TOLERANCE = 0.001;
77       // One centimeter tolerance.
78    const double Position::ONE_CM_TOLERANCE = 0.01;
79       // One micron tolerance.
80    const double Position::ONE_UM_TOLERANCE = 0.000001;
81 
82       // Default tolerance for time equality in meters.
83    double Position::POSITION_TOLERANCE = Position::ONE_MM_TOLERANCE;
84 
85       // Sets the tolerance for output and comparisons, for this object only.
86       // See the constants in this file (e.g. ONE_MM_TOLERANCE)
87       // for some easy to use tolerance values.
88       // @param tol Tolerance in meters to be used by comparison operators.
setTolerance(const double tol)89    Position& Position::setTolerance(const double tol)
90       throw()
91    {
92       tolerance = tol;
93       return *this;
94    }
95 
96    // ----------- Part  3: member functions: constructors --------------------
97    //
98       // Default constructor.
Position()99    Position::Position()
100       throw()
101    {
102       WGS84Ellipsoid WGS84;
103       initialize(0.0,0.0,0.0,Unknown,&WGS84);
104    }
105 
Position(const double & a,const double & b,const double & c,Position::CoordinateSystem s,EllipsoidModel * ell,ReferenceFrame frame)106    Position::Position(const double& a,
107                       const double& b,
108                       const double& c,
109                       Position::CoordinateSystem s,
110                       EllipsoidModel *ell,
111                       ReferenceFrame frame)
112    {
113       try {
114          initialize(a,b,c,s,ell,frame);
115       }
116       catch(GeometryException& ge) {
117          GPSTK_RETHROW(ge);
118       }
119    }
120 
Position(const double ABC[3],CoordinateSystem s,EllipsoidModel * ell,ReferenceFrame frame)121    Position::Position(const double ABC[3],
122                       CoordinateSystem s,
123                       EllipsoidModel *ell,
124                       ReferenceFrame frame)
125    {
126       double a=ABC[0];
127       double b=ABC[1];
128       double c=ABC[2];
129       try {
130          initialize(a,b,c,s,ell,frame);
131       }
132       catch(GeometryException& ge) {
133          GPSTK_RETHROW(ge);
134       }
135    }
136 
Position(const Triple & ABC,CoordinateSystem s,EllipsoidModel * ell,ReferenceFrame frame)137    Position::Position(const Triple& ABC,
138                       CoordinateSystem s,
139                       EllipsoidModel *ell,
140                       ReferenceFrame frame)
141    {
142       double a=ABC[0];
143       double b=ABC[1];
144       double c=ABC[2];
145       try {
146          initialize(a,b,c,s,ell,frame);
147       }
148       catch(GeometryException& ge) {
149       }
150    }
151 
Position(const Xvt & xvt)152    Position::Position(const Xvt& xvt)
153       throw()
154    {
155       double a=xvt.x[0];
156       double b=xvt.x[1];
157       double c=xvt.x[2];
158       initialize(a,b,c,Cartesian, NULL, xvt.frame);
159    }
160 
161    // ----------- Part  4: member functions: arithmetic ----------------------
162    //
operator -=(const Position & right)163    Position& Position::operator-=(const Position& right)
164       throw()
165    {
166       Position r(right);
167       CoordinateSystem savesys=system;    // save the original system
168 
169          // convert to cartestian and difference there
170       transformTo(Cartesian);
171       r.transformTo(Cartesian);
172 
173       for(int i=0; i<3; i++)
174          theArray[i] -= r.theArray[i];
175 
176       transformTo(savesys);               // transform back to the original system
177       return *this;
178    }
179 
operator +=(const Position & right)180    Position& Position::operator+=(const Position& right)
181       throw()
182    {
183       Position r(right);
184       CoordinateSystem savesys=system;    // save the original system
185 
186          // convert to cartestian and difference there
187       transformTo(Cartesian);
188       r.transformTo(Cartesian);
189 
190       for(int i=0; i<3; i++)
191          theArray[i] += r.theArray[i];
192 
193       transformTo(savesys);               // transform back to the original system
194       return *this;
195    }
196 
operator -(const Position & left,const Position & right)197    Position operator-(const Position& left,
198                             const Position& right)
199       throw()
200    {
201       Position l(left),r(right);
202          // convert both to Cartesian
203       l.transformTo(Position::Cartesian);
204       r.transformTo(Position::Cartesian);
205          // difference
206       l -= r;
207 
208       return l;
209    }
210 
operator +(const Position & left,const Position & right)211    Position operator+(const Position& left,
212                             const Position& right)
213       throw()
214    {
215       Position l(left),r(right);
216          // convert both to Cartesian
217       l.transformTo(Position::Cartesian);
218       r.transformTo(Position::Cartesian);
219          // add
220       l += r;
221 
222       return l;
223    }
224 
225    // ----------- Part  5: member functions: comparisons ---------------------
226    //
227       // Equality operator. Returns false if ell values differ.
operator ==(const Position & right) const228    bool Position::operator==(const Position &right) const
229       throw()
230    {
231       if(AEarth != right.AEarth || eccSquared != right.eccSquared)
232          return false;
233       if(right.getReferenceFrame() != refFrame)
234          return false;   //Unknown frames are considered the same.
235       if(range(*this,right) < tolerance)
236          return true;
237       else
238          return false;
239    }
240 
241       // Inequality operator. Returns true if ell values differ.
operator !=(const Position & right) const242    bool Position::operator!=(const Position &right) const
243       throw()
244    {
245       return !(operator==(right));
246    }
247 
248    // ----------- Part  6: member functions: coordinate transformations ------
249    //
250       // Transform coordinate system. Does nothing if sys already matches the
251       // current value of member CoordinateSystem 'system'.
252       // @param sys coordinate system into which *this is to be transformed.
253       // @return *this
transformTo(CoordinateSystem sys)254    Position& Position::transformTo(CoordinateSystem sys)
255       throw()
256    {
257       if(sys == Unknown || sys == system) return *this;
258 
259       // this copies geoid information and tolerance
260       Position target(*this);
261 
262       // transform target.theArray and set target.system
263       switch(system) {
264          case Unknown:
265             return *this;
266          case Geodetic:
267             // --------------- Geodetic to ... ------------------------
268             switch(sys) {
269                case Unknown: case Geodetic: return *this;
270                case Geocentric:
271                   convertGeodeticToGeocentric(*this,target,AEarth,eccSquared);
272                   target.system = Geocentric;
273                   break;
274                case Cartesian:
275                   convertGeodeticToCartesian(*this,target,AEarth,eccSquared);
276                   target.system = Cartesian;
277                   break;
278                case Spherical:
279                   convertGeodeticToGeocentric(*this,target,AEarth,eccSquared);
280                   target.theArray[0] = 90 - target.theArray[0];   // geocen -> sph
281                   target.system = Spherical;
282                   break;
283             }
284             break;
285          case Geocentric:
286             // --------------- Geocentric to ... ----------------------
287             switch(sys) {
288                case Unknown: case Geocentric: return *this;
289                case Geodetic:
290                   convertGeocentricToGeodetic(*this,target,AEarth,eccSquared);
291                   target.system = Geodetic;
292                   break;
293                case Cartesian:
294                   convertGeocentricToCartesian(*this,target);
295                   target.system = Cartesian;
296                   break;
297                case Spherical:
298                   target.theArray[0] = 90 - target.theArray[0];   // geocen -> sph
299                   target.system = Spherical;
300                   break;
301             }
302             break;
303          case Cartesian:
304             // --------------- Cartesian to ... -----------------------
305             switch(sys) {
306                case Unknown: case Cartesian: return *this;
307                case Geodetic:
308                   convertCartesianToGeodetic(*this,target,AEarth,eccSquared);
309                   target.system = Geodetic;
310                   break;
311                case Geocentric:
312                   convertCartesianToGeocentric(*this,target);
313                   target.system = Geocentric;
314                   break;
315                case Spherical:
316                   convertCartesianToSpherical(*this,target);
317                   target.system = Spherical;
318                   break;
319             }
320             break;
321          case Spherical:
322             // --------------- Spherical to ... -----------------------
323             switch(sys) {
324                case Unknown: case Spherical: return *this;
325                case Geodetic:
326                   theArray[0] = 90 - theArray[0];   // sph -> geocen
327                   convertGeocentricToGeodetic(*this,target,AEarth,eccSquared);
328                   target.system = Geodetic;
329                   break;
330                case Geocentric:
331                   target.theArray[0] = 90 - target.theArray[0];   // sph -> geocen
332                   target.system = Geocentric;
333                   break;
334                case Cartesian:
335                   convertSphericalToCartesian(*this,target);
336                   target.system = Cartesian;
337                   break;
338             }
339             break;
340       }  // end switch(system)
341 
342       *this = target;
343       return *this;
344    }
345 
346    // ----------- Part  7: member functions: get -----------------------------
347    //
348    // These routines retrieve coordinate values in all coordinate systems.
349    // Note that calling these will transform the Position to another coordinate
350    // system if that is required.
351    //
352 
getReferenceFrame() const353    const ReferenceFrame& Position::getReferenceFrame() const
354       throw()
355    {
356       return refFrame;
357    }
358 
359       // Get X coordinate (meters)
X() const360    double Position::X() const
361       throw()
362    {
363       if(system == Cartesian)
364          return theArray[0];
365       Position t(*this);
366       t.transformTo(Cartesian);
367       return t.theArray[0];
368    }
369 
370       // Get Y coordinate (meters)
Y() const371    double Position::Y() const
372       throw()
373    {
374       if(system == Cartesian)
375          return theArray[1];
376       Position t(*this);
377       t.transformTo(Cartesian);
378       return t.theArray[1];
379    }
380 
381       // Get Z coordinate (meters)
Z() const382    double Position::Z() const
383       throw()
384    {
385       if(system == Cartesian)
386          return theArray[2];
387       Position t(*this);
388       t.transformTo(Cartesian);
389       return t.theArray[2];
390    }
391 
392       // Get geodetic latitude (degrees North).
geodeticLatitude() const393    double Position::geodeticLatitude() const
394       throw()
395    {
396       if(system == Geodetic)
397          return theArray[0];
398       Position t(*this);
399       t.transformTo(Geodetic);
400       return t.theArray[0];
401    }
402 
403       // Get geocentric latitude (degrees North),
404       // equal to 90 degress - theta in regular spherical coordinates.
geocentricLatitude() const405    double Position::geocentricLatitude() const
406       throw()
407    {
408       if(system == Geocentric)
409          return theArray[0];
410       Position t(*this);
411       t.transformTo(Geocentric);
412       return t.theArray[0];
413    }
414 
415       // Get spherical coordinate theta in degrees
theta() const416    double Position::theta() const
417       throw()
418    {
419       if(system == Spherical)
420          return theArray[0];
421       Position t(*this);
422       t.transformTo(Spherical);
423       return t.theArray[0];
424    }
425 
426       // Get spherical coordinate phi in degrees
phi() const427    double Position::phi() const
428       throw()
429    {
430       if(system == Spherical)
431          return theArray[1];
432       Position t(*this);
433       t.transformTo(Spherical);
434       return t.theArray[1];
435    }
436 
437       // Get longitude (degrees East),
438       // equal to phi in regular spherical coordinates.
longitude() const439    double Position::longitude() const
440       throw()
441    {
442       if(system != Cartesian)
443          return theArray[1];
444       Position t(*this);
445       t.transformTo(Spherical);
446       return t.theArray[1];
447    }
448 
449       // Get radius or distance from the center of Earth (meters),
450       // Same as radius in spherical coordinates.
radius() const451    double Position::radius() const
452       throw()
453    {
454       if(system == Spherical || system == Geocentric)
455          return theArray[2];
456       Position t(*this);
457       t.transformTo(Spherical);
458       return t.theArray[2];
459    }
460 
461       // Get height above ellipsoid (meters) (Geodetic).
height() const462    double Position::height() const
463       throw()
464    {
465       if(system == Geodetic)
466          return theArray[2];
467       Position t(*this);
468       t.transformTo(Geodetic);
469       return t.theArray[2];
470    }
471 
472    // ----------- Part  8: member functions: set -----------------------------
473    //
setReferenceFrame(const ReferenceFrame & frame)474    void Position::setReferenceFrame(const ReferenceFrame& frame)
475       throw()
476    {
477       refFrame = frame;
478    }
479 
480       /**
481       * Set the ellipsoid values for this Position given a ellipsoid.
482       * @param ell  Pointer to the EllipsoidModel.
483       * @throw      GeometryException if input is NULL.
484       */
setEllipsoidModel(const EllipsoidModel * ell)485    void Position::setEllipsoidModel(const EllipsoidModel *ell)
486    {
487       if(!ell)
488       {
489          GeometryException ge("Given EllipsoidModel pointer is NULL.");
490          GPSTK_THROW(ge);
491       }
492       AEarth = ell->a();
493       eccSquared = ell->eccSquared();
494    }
495 
496       // Set the Position given geodetic coordinates, system is set to Geodetic.
497       // @param lat geodetic latitude in degrees North
498       // @param lon geodetic longitude in degrees East
499       // @param ht height above the ellipsoid in meters
500       // @return a reference to this object.
501       // @throw GeometryException on invalid input
setGeodetic(const double lat,const double lon,const double ht,const EllipsoidModel * ell)502    Position& Position::setGeodetic(const double lat,
503                                    const double lon,
504                                    const double ht,
505                                    const EllipsoidModel *ell)
506    {
507       if(lat > 90 || lat < -90)
508       {
509          GeometryException ge("Invalid latitude in setGeodetic: "
510                                  + StringUtils::asString(lat));
511          GPSTK_THROW(ge);
512       }
513       theArray[0] = lat;
514 
515       theArray[1] = lon;
516       if(theArray[1] < 0)
517          theArray[1] += 360*(1+(unsigned long)(theArray[1]/360));
518       else if(theArray[1] >= 360)
519          theArray[1] -= 360*(unsigned long)(theArray[1]/360);
520 
521       theArray[2] = ht;
522 
523       if(ell) {
524          AEarth = ell->a();
525          eccSquared = ell->eccSquared();
526       }
527       system = Geodetic;
528 
529       return *this;
530    }
531 
532       // Set the Position given geocentric coordinates, system is set to Geocentric
533       // @param lat geocentric latitude in degrees North
534       // @param lon geocentric longitude in degrees East
535       // @param rad radius from the Earth's center in meters
536       // @return a reference to this object.
537       // @throw GeometryException on invalid input
setGeocentric(const double lat,const double lon,const double rad)538    Position& Position::setGeocentric(const double lat,
539                                      const double lon,
540                                      const double rad)
541    {
542       if(lat > 90 || lat < -90)
543       {
544          GeometryException ge("Invalid latitude in setGeocentric: "
545                                  + StringUtils::asString(lat));
546          GPSTK_THROW(ge);
547       }
548       if(rad < 0)
549       {
550          GeometryException ge("Invalid radius in setGeocentric: "
551                                           + StringUtils::asString(rad));
552          GPSTK_THROW(ge);
553       }
554       theArray[0] = lat;
555       theArray[1] = lon;
556       theArray[2] = rad;
557 
558       if(theArray[1] < 0)
559          theArray[1] += 360*(1+(unsigned long)(theArray[1]/360));
560       else if(theArray[1] >= 360)
561          theArray[1] -= 360*(unsigned long)(theArray[1]/360);
562       system = Geocentric;
563 
564       return *this;
565    }
566 
567       // Set the Position given spherical coordinates, system is set to Spherical
568       // @param theta angle from the Z-axis (degrees)
569       // @param phi angle from the X-axis in the XY plane (degrees)
570       // @param rad radius from the center in meters
571       // @return a reference to this object.
572       // @throw GeometryException on invalid input
setSpherical(const double theta,const double phi,const double rad)573    Position& Position::setSpherical(const double theta,
574                                     const double phi,
575                                     const double rad)
576    {
577       if(theta < 0 || theta > 180)
578       {
579          GeometryException ge("Invalid theta in setSpherical: "
580                                  + StringUtils::asString(theta));
581          GPSTK_THROW(ge);
582       }
583       if(rad < 0)
584       {
585          GeometryException ge("Invalid radius in setSpherical: "
586                                           + StringUtils::asString(rad));
587          GPSTK_THROW(ge);
588       }
589 
590       theArray[0] = theta;
591       theArray[1] = phi;
592       theArray[2] = rad;
593 
594       if(theArray[1] < 0)
595          theArray[1] += 360*(1+(unsigned long)(theArray[1]/360));
596       else if(theArray[1] >= 360)
597          theArray[1] -= 360*(unsigned long)(theArray[1]/360);
598       system = Spherical;
599 
600       return *this;
601    }
602 
603       // Set the Position given ECEF coordinates, system is set to Cartesian.
604       // @param X ECEF X coordinate in meters.
605       // @param Y ECEF Y coordinate in meters.
606       // @param Z ECEF Z coordinate in meters.
607       // @return a reference to this object.
setECEF(const double X,const double Y,const double Z)608    Position& Position::setECEF(const double X,
609                                const double Y,
610                                const double Z)
611       throw()
612    {
613       theArray[0] = X;
614       theArray[1] = Y;
615       theArray[2] = Z;
616       system = Cartesian;
617       return *this;
618    }
619 
620    // ----------- Part 9: member functions: setToString, printf -------------
621    //
622       // setToString, similar to scanf, this function takes a string and a
623       // format describing string in order to define Position
624       // values.  The parameters it can take are listed below and
625       // described above with the printf() function.
626       //
627       // The specification must be sufficient to define a Position.
628       // The following table lists combinations that give valid
629       // Positions. Anything more or other combinations will give
630       // unknown (read as: "bad") results so don't try it.  Anything
631       // less will throw an exception.
632       //
633       // @code
634       //  %X %Y %Z  (cartesian or ECEF)
635       //  %x %y %z  (cartesian or ECEF)
636       //  %a %l %r  (geocentric)
637       //  %A %L %h  (geodetic)
638       //  %t %p %r  (spherical)
639       // @endcode
640       //
641       // So
642       // @code
643       // pos.setToString("123.4342,9328.1982,-128987.399", "%X,%Y,%Z");
644       // @endcode
645       //
646       // works but
647       //
648       // @code
649       // pos.setToString("123.4342,9328.1982", "%X,%Y");
650       // @endcode
651       // doesn't work (incomplete specification because it doesn't specify
652       // a Position).
653       //
654       // Whitespace is unimportant here, the function will handle it.
655       // The caller must ensure that that the extra characters in
656       // the format string (ie '.' ',') are in the same relative
657       // location as they are in the actual string, see the example above.
658       //
659       // @param str string from which to get the Position coordinates
660       // @param fmt format to use to parse \c str.
661       // @throw GeometryException if \c fmt is an incomplete or invalid specification
662       // @throw StringException if an error occurs manipulating the
663       // \c str or \c fmt strings.
664       // @return a reference to this object.
setToString(const std::string & str,const std::string & fmt)665    Position& Position::setToString(const std::string& str,
666                                    const std::string& fmt)
667    {
668       try {
669             // make an object to return (so we don't fiddle with *this
670             // until it's necessary)
671          Position toReturn;
672 
673             // flags indicated these defined
674          bool hX=false, hY=false, hZ=false;
675          bool hglat=false, hlon=false, hht=false;
676          bool hclat=false, hrad=false;
677          bool htheta=false, hphi=false;
678             // store input values
679          double x=0.0, y=0.0, z=0.0, glat=0.0, lon=0.0, ht=0.0, clat=0.0,
680                 rad=0.0, theta=0.0, phi=0.0;
681             // copy format and input string to parse
682          string f = fmt;
683          string s = str;
684          stripLeading(s);
685          stripTrailing(f);
686 
687             // parse strings...  As we process each part, it's removed from both
688             // strings so when we reach 0, we're done
689          while ( (s.size() > 0) && (f.size() > 0) )
690          {
691                // remove everything in f and s up to the first % in f
692                // (these parts of the strings must be identical or this will break
693                // after it tries to remove it!)
694             while ( (s.length() != 0) && (f.length() != 0) && (f[0] != '%') )
695             {
696                   // remove that character now and other whitespace
697                s.erase(0,1);
698                f.erase(0,1);
699                stripLeading(s);
700                stripLeading(f);
701             }
702 
703                // check just in case we hit the end of either string...
704             if ( (s.length() == 0) || (f.length() == 0) )
705                break;
706 
707                // lose the '%' in f...
708             f.erase(0,1);
709 
710                // if the format string is like %03f, get '3' as the field
711                // length.
712             string::size_type fieldLength = string::npos;
713 
714             if (!isalpha(f[0]))
715             {
716                fieldLength = asInt(f);
717 
718                   // remove everything else up to the next character
719                   // (in "%03f", that would be 'f')
720                while ((!f.empty()) && (!isalpha(f[0])))
721                   f.erase(0,1);
722                if (f.empty())
723                   break;
724             }
725 
726                // finally, get the character that should end this field, if any
727             char delimiter = 0;
728             if (f.size() > 1)
729             {
730                if (f[1] != '%')
731                {
732                   delimiter = f[1];
733 
734                   if (fieldLength == string::npos)
735                      fieldLength = s.find(delimiter,0);
736                }
737                   // if the there is no delimiter character and the next field
738                   // is another part of the time to parse, assume the length
739                   // of this field is 1
740                else if (fieldLength == string::npos)
741                {
742                   fieldLength = 1;
743                }
744             }
745 
746                // figure out the next string to be removed.  if there is a field
747                // length, use that first
748             string toBeRemoved = s.substr(0, fieldLength);
749 
750                // based on char at f[0], we know what to do...
751             switch (f[0])
752             {
753           //%x   X() (meters)
754           //%y   Y() (meters)
755           //%z   Z() (meters)
756           //%X   X()/1000 (kilometers)
757           //%Y   Y()/1000 (kilometers)
758           //%Z   Z()/1000 (kilometers)
759                case 'X':
760                   x = asDouble(toBeRemoved) * 1000;
761                   hX = true;
762                   break;
763                case 'x':
764                   x = asDouble(toBeRemoved);
765                   hX = true;
766                   break;
767                case 'Y':
768                   y = asDouble(toBeRemoved) * 1000;
769                   hY = true;
770                   break;
771                case 'y':
772                   y = asDouble(toBeRemoved);
773                   hY = true;
774                   break;
775                case 'Z':
776                   z = asDouble(toBeRemoved) * 1000;
777                   hZ = true;
778                   break;
779                case 'z':
780                   z = asDouble(toBeRemoved);
781                   hZ = true;
782                   break;
783           //%A   geodeticLatitude() (degrees North)
784           //%a   geocentricLatitude() (degrees North)
785                case 'A':
786                   glat = asDouble(toBeRemoved);
787                   if(glat > 90. || glat < -90.) {
788                      InvalidRequest f(
789                            "Invalid geodetic latitude for setTostring: "
790                            + toBeRemoved);
791                      GPSTK_THROW(f);
792                   }
793                   hglat = true;
794                   break;
795                case 'a':
796                   clat = asDouble(toBeRemoved);
797                   if(clat > 90. || clat < -90.) {
798                      InvalidRequest f(
799                            "Invalid geocentric latitude for setTostring: "
800                            + toBeRemoved);
801                      GPSTK_THROW(f);
802                   }
803                   hclat = true;
804                   break;
805           //%L   longitude() (degrees East)
806           //%l   longitude() (degrees East)
807           //%w   longitude() (degrees West)
808           //%W   longitude() (degrees West)
809                case 'L':
810                case 'l':
811                   lon = asDouble(toBeRemoved);
812                   if(lon < 0)
813                      lon += 360*(1+(unsigned long)(lon/360));
814                   else if(lon >= 360)
815                      lon -= 360*(unsigned long)(lon/360);
816                   hlon = true;
817                   break;
818                case 'w':
819                case 'W':
820                   lon = 360.0 - asDouble(toBeRemoved);
821                   if(lon < 0)
822                      lon += 360*(1+(unsigned long)(lon/360));
823                   else if(lon >= 360)
824                      lon -= 360*(unsigned long)(lon/360);
825                   hlon = true;
826                   break;
827           //%t   theta() (degrees)
828           //%T   theta() (radians)
829                case 't':
830                   theta = asDouble(toBeRemoved);
831                   if(theta > 180. || theta < 0.) {
832                      InvalidRequest f("Invalid theta for setTostring: "
833                                                 + toBeRemoved);
834                      GPSTK_THROW(f);
835                   }
836                   htheta = true;
837                   break;
838                case 'T':
839                   theta = asDouble(toBeRemoved) * RAD_TO_DEG;
840                   if(theta > 90. || theta < -90.) {
841                      InvalidRequest f("Invalid theta for setTostring: "
842                                                 + toBeRemoved);
843                      GPSTK_THROW(f);
844                   }
845                   htheta = true;
846                   break;
847           //%p   phi() (degrees)
848           //%P   phi() (radians)
849                case 'p':
850                   phi = asDouble(toBeRemoved);
851                   if(phi < 0)
852                      phi += 360*(1+(unsigned long)(phi/360));
853                   else if(phi >= 360)
854                      phi -= 360*(unsigned long)(phi/360);
855                   hphi = true;
856                   break;
857                case 'P':
858                   phi = asDouble(toBeRemoved) * RAD_TO_DEG;
859                   if(phi < 0)
860                      phi += 360*(1+(unsigned long)(phi/360));
861                   else if(phi >= 360)
862                      phi -= 360*(unsigned long)(phi/360);
863                   hphi = true;
864                   break;
865           //%r   radius() meters
866           //%R   radius()/1000 kilometers
867           //%h   height() meters
868           //%H   height()/1000 kilometers
869                case 'r':
870                   rad = asDouble(toBeRemoved);
871                   if(rad < 0.0) {
872                      InvalidRequest f("Invalid radius for setTostring: "
873                                                 + toBeRemoved);
874                      GPSTK_THROW(f);
875                   }
876                   hrad = true;
877                   break;
878                case 'R':
879                   rad = asDouble(toBeRemoved) * 1000;
880                   if(rad < 0.0) {
881                      InvalidRequest f("Invalid radius for setTostring: "
882                                                 + toBeRemoved);
883                      GPSTK_THROW(f);
884                   }
885                   hrad = true;
886                   break;
887                case 'h':
888                   ht = asDouble(toBeRemoved);
889                   hht = true;
890                   break;
891                case 'H':
892                   ht = asDouble(toBeRemoved) * 1000;
893                   hht = true;
894                   break;
895                default: // do nothing
896                   break;
897             }
898                // remove the part of s that we processed
899             stripLeading(s,toBeRemoved,1);
900 
901                // remove the character we processed from f
902             f.erase(0,1);
903 
904                // check for whitespace again...
905             stripLeading(f);
906             stripLeading(s);
907 
908          }
909 
910          if ( s.length() != 0  )
911          {
912                // throw an error - something didn't get processed in the strings
913             InvalidRequest fe(
914                "Processing error - parts of strings left unread - " + s);
915             GPSTK_THROW(fe);
916          }
917 
918          if (f.length() != 0)
919          {
920                // throw an error - something didn't get processed in the strings
921             InvalidRequest fe(
922                "Processing error - parts of strings left unread - " + f);
923             GPSTK_THROW(fe);
924          }
925 
926             // throw if the specification is incomplete
927          if ( !(hX && hY && hZ) && !(hglat && hlon && hht) &&
928               !(hclat && hlon && hrad) && !(htheta && hphi && hrad)) {
929             InvalidRequest fe("Incomplete specification for setToString");
930             GPSTK_THROW(fe);
931          }
932 
933             // define the Position toReturn
934          if(hX && hY && hZ)
935             toReturn.setECEF(x,y,z);
936          else if(hglat && hlon && hht)
937             toReturn.setGeodetic(glat,lon,ht);
938          else if(hclat && hlon && hrad)
939             toReturn.setGeocentric(clat,lon,rad);
940          else if(htheta && hphi && hrad)
941             toReturn.setSpherical(theta,phi,rad);
942 
943          *this = toReturn;
944          return *this;
945       }
946       catch(gpstk::Exception& exc)
947       {
948          GeometryException ge(exc);
949          ge.addText("Failed to convert string to Position");
950          GPSTK_THROW(ge);
951       }
952       catch(std::exception& exc)
953       {
954          GeometryException ge(exc.what());
955          ge.addText("Failed to convert string to Position");
956          GPSTK_THROW(ge);
957       }
958    }
959 
960       // Format this Position into a string.
961       //
962       // Generate and return a string containing formatted
963       // Position coordinates, formatted by the specification \c fmt.
964       //
965       // \li \%x   X() (meters)
966       // \li \%y   Y() (meters)
967       // \li \%z   Z() (meters)
968       // \li \%X   X()/1000 (kilometers)
969       // \li \%Y   Y()/1000 (kilometers)
970       // \li \%Z   Z()/1000 (kilometers)
971       // \li \%A   geodeticLatitude() (degrees North)
972       // \li \%a   geocentricLatitude() (degrees North)
973       // \li \%L   longitude() (degrees East)
974       // \li \%l   longitude() (degrees East)
975       // \li \%w   longitude() (degrees West)
976       // \li \%W   longitude() (degrees West)
977       // \li \%t   theta() (degrees)
978       // \li \%T   theta() (radians)
979       // \li \%p   phi() (degrees)
980       // \li \%P   phi() (radians)
981       // \li \%r   radius() meters
982       // \li \%R   radius()/1000 kilometers
983       // \li \%h   height() meters
984       // \li \%H   height()/1000 kilometers
985       //
986       // @param fmt format to use for this time.
987       // @return a string containing this Position in the
988       // representation specified by \c fmt.
printf(const char * fmt) const989    std::string Position::printf(const char *fmt) const
990    {
991       string rv = fmt;
992       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?x"),
993                           string("xf"), X());
994       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?y"),
995                           string("yf"), Y());
996       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?z"),
997                           string("zf"), Z());
998       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?X"),
999                           string("Xf"), X()/1000);
1000       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?Y"),
1001                           string("Yf"), Y()/1000);
1002       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?Z"),
1003                           string("Zf"), Z()/1000);
1004 
1005       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?A"),
1006                           string("Af"), geodeticLatitude());
1007       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?a"),
1008                           string("af"), geocentricLatitude());
1009       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?L"),
1010                           string("Lf"), longitude());
1011       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?l"),
1012                           string("lf"), longitude());
1013       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?w"),
1014                           string("wf"), 360-longitude());
1015       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?W"),
1016                           string("Wf"), 360-longitude());
1017 
1018       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?t"),
1019                           string("tf"), theta());
1020       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?T"),
1021                           string("Tf"), theta()*DEG_TO_RAD);
1022       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?p"),
1023                           string("pf"), phi());
1024       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?P"),
1025                           string("Pf"), phi()*DEG_TO_RAD);
1026       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?r"),
1027                           string("rf"), radius());
1028       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?R"),
1029                           string("Rf"), radius()/1000);
1030       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?h"),
1031                           string("hf"), height());
1032       rv = formattedPrint(rv, string("%[ 0-]?[[:digit:]]*(\\.[[:digit:]]+)?H"),
1033                           string("Hf"), height()/1000);
1034       return rv;
1035    }
1036 
1037       // Returns the string that operator<<() would print.
asString() const1038    string Position::asString() const
1039    {
1040       ostringstream o;
1041       o << *this;
1042       return o.str();
1043    }
1044 
1045    // ----------- Part 10: functions: fundamental conversions ---------------
1046    //
1047       // Fundamental conversion from spherical to cartesian coordinates.
1048       // @param trp (input): theta, phi, radius
1049       // @param xyz (output): X,Y,Z in units of radius
1050       // Algorithm references: standard geometry.
convertSphericalToCartesian(const Triple & tpr,Triple & xyz)1051    void Position::convertSphericalToCartesian(const Triple& tpr,
1052                                               Triple& xyz)
1053       throw()
1054    {
1055       double st=::sin(tpr[0]*DEG_TO_RAD);
1056       xyz[0] = tpr[2]*st*::cos(tpr[1]*DEG_TO_RAD);
1057       xyz[1] = tpr[2]*st*::sin(tpr[1]*DEG_TO_RAD);
1058       xyz[2] = tpr[2]*::cos(tpr[0]*DEG_TO_RAD);
1059    }
1060 
1061       // Fundamental routine to convert cartesian to spherical coordinates.
1062       // @param xyz (input): X,Y,Z
1063       // @param trp (output): theta, phi (deg), radius in units of input
1064       // Algorithm references: standard geometry.
convertCartesianToSpherical(const Triple & xyz,Triple & tpr)1065    void Position::convertCartesianToSpherical(const Triple& xyz,
1066                                               Triple& tpr)
1067       throw()
1068    {
1069       tpr[2] = RSS(xyz[0],xyz[1],xyz[2]);
1070       if(tpr[2] <= Position::POSITION_TOLERANCE/5) { // zero-length Cartesian vector
1071          tpr[0] = 90;
1072          tpr[1] = 0;
1073          return;
1074       }
1075       tpr[0] = ::acos(xyz[2]/tpr[2]);
1076       tpr[0] *= RAD_TO_DEG;
1077       if(RSS(xyz[0],xyz[1]) < Position::POSITION_TOLERANCE/5) {       // pole
1078          tpr[1] = 0;
1079          return;
1080       }
1081       tpr[1] = ::atan2(xyz[1],xyz[0]);
1082       tpr[1] *= RAD_TO_DEG;
1083       if(tpr[1] < 0) tpr[1] += 360;
1084    }
1085 
1086       // Fundamental routine to convert cartesian (ECEF) to geodetic coordinates,
1087       // (Geoid specified by semi-major axis and eccentricity squared).
1088       // @param xyz (input): X,Y,Z in meters
1089       // @param llh (output): geodetic lat(deg N), lon(deg E),
1090       //                             height above ellipsoid (meters)
1091       // @param A (input) Earth semi-major axis
1092       // @param eccSq (input) square of Earth eccentricity
1093       // Algorithm references:
convertCartesianToGeodetic(const Triple & xyz,Triple & llh,const double A,const double eccSq)1094    void Position::convertCartesianToGeodetic(const Triple& xyz,
1095                                              Triple& llh,
1096                                              const double A,
1097                                              const double eccSq)
1098       throw()
1099    {
1100       double p,slat,N,htold,latold;
1101       p = SQRT(xyz[0]*xyz[0]+xyz[1]*xyz[1]);
1102       if(p < Position::POSITION_TOLERANCE/5) {  // pole or origin
1103          llh[0] = (xyz[2] > 0 ? 90.0: -90.0);
1104          llh[1] = 0;                            // lon undefined, really
1105          llh[2] = ::fabs(xyz[2]) - A*SQRT(1.0-eccSq);
1106          return;
1107       }
1108       llh[0] = ::atan2(xyz[2], p*(1.0-eccSq));
1109       llh[2] = 0;
1110       for(int i=0; i<5; i++) {
1111          slat = ::sin(llh[0]);
1112          N = A / SQRT(1.0 - eccSq*slat*slat);
1113          htold = llh[2];
1114          llh[2] = p/::cos(llh[0]) - N;
1115          latold = llh[0];
1116          llh[0] = ::atan2(xyz[2], p*(1.0-eccSq*(N/(N+llh[2]))));
1117          if(::fabs(llh[0]-latold) < 1.0e-9 && fabs(llh[2]-htold) < 1.0e-9 * A) break;
1118       }
1119       llh[1] = ::atan2(xyz[1],xyz[0]);
1120       if(llh[1] < 0.0) llh[1] += TWO_PI;
1121       llh[0] *= RAD_TO_DEG;
1122       llh[1] *= RAD_TO_DEG;
1123    }
1124 
1125       // Fundamental routine to convert geodetic to cartesian (ECEF) coordinates,
1126       // (Geoid specified by semi-major axis and eccentricity squared).
1127       // @param llh (input): geodetic lat(deg N), lon(deg E),
1128       //            height above ellipsoid (meters)
1129       // @param xyz (output): X,Y,Z in meters
1130       // @param A (input) Earth semi-major axis
1131       // @param eccSq (input) square of Earth eccentricity
1132       // Algorithm references:
convertGeodeticToCartesian(const Triple & llh,Triple & xyz,const double A,const double eccSq)1133    void Position::convertGeodeticToCartesian(const Triple& llh,
1134                                              Triple& xyz,
1135                                              const double A,
1136                                              const double eccSq)
1137       throw()
1138    {
1139       double slat = ::sin(llh[0]*DEG_TO_RAD);
1140       double clat = ::cos(llh[0]*DEG_TO_RAD);
1141       double N = A/SQRT(1.0-eccSq*slat*slat);
1142       xyz[0] = (N+llh[2])*clat*::cos(llh[1]*DEG_TO_RAD);
1143       xyz[1] = (N+llh[2])*clat*::sin(llh[1]*DEG_TO_RAD);
1144       xyz[2] = (N*(1.0-eccSq)+llh[2])*slat;
1145    }
1146 
1147       // Fundamental routine to convert cartesian (ECEF) to geocentric coordinates.
1148       // @param xyz (input): X,Y,Z in meters
1149       // @param llr (output):
1150       //            geocentric lat(deg N),lon(deg E),radius (units of input)
convertCartesianToGeocentric(const Triple & xyz,Triple & llr)1151    void Position::convertCartesianToGeocentric(const Triple& xyz,
1152                                                Triple& llr)
1153       throw()
1154    {
1155       convertCartesianToSpherical(xyz, llr);
1156       llr[0] = 90 - llr[0];         // convert theta to latitude
1157    }
1158 
1159       // Fundamental routine to convert geocentric to cartesian (ECEF) coordinates.
1160       // @param llr (input): geocentric lat(deg N),lon(deg E),radius
1161       // @param xyz (output): X,Y,Z (units of radius)
convertGeocentricToCartesian(const Triple & llr,Triple & xyz)1162    void Position::convertGeocentricToCartesian(const Triple& llr,
1163                                                Triple& xyz)
1164       throw()
1165    {
1166       Triple llh(llr);
1167       llh[0] = 90 - llh[0];         // convert latitude to theta
1168       convertSphericalToCartesian(llh, xyz);
1169    }
1170 
1171       // Fundamental routine to convert geocentric to geodetic coordinates.
1172       // @param llr (input): geocentric Triple: lat(deg N),lon(deg E),radius (meters)
1173       // @param llh (output): geodetic latitude (deg N),
1174       //            longitude (deg E), and height above ellipsoid (meters)
1175       // @param A (input) Earth semi-major axis
1176       // @param eccSq (input) square of Earth eccentricity
convertGeocentricToGeodetic(const Triple & llr,Triple & llh,const double A,const double eccSq)1177    void Position::convertGeocentricToGeodetic(const Triple& llr,
1178                                                Triple& llh,
1179                                                const double A,
1180                                                const double eccSq)
1181       throw()
1182    {
1183       double cl,p,sl,slat,N,htold,latold;
1184       llh[1] = llr[1];   // longitude is unchanged
1185       cl = ::sin((90-llr[0])*DEG_TO_RAD);
1186       sl = ::cos((90-llr[0])*DEG_TO_RAD);
1187       if(llr[2] <= Position::POSITION_TOLERANCE/5) {
1188          // radius is below tolerance, hence assign zero-length
1189          // arbitrarily set latitude = longitude = 0
1190          llh[0] = llh[1] = 0;
1191          llh[2] = -A;
1192          return;
1193       }
1194       else if(cl < 1.e-10) {
1195          // near pole ... note that 1mm/radius(Earth) = 1.5e-10
1196          if(llr[0] < 0) llh[0] = -90;
1197          else           llh[0] =  90;
1198          llh[1] = 0;
1199          llh[2] = llr[2] - A*SQRT(1-eccSq);
1200          return;
1201       }
1202       llh[0] = ::atan2(sl, cl*(1.0-eccSq));
1203       p = cl*llr[2];
1204       llh[2] = 0;
1205       for(int i=0; i<5; i++) {
1206          slat = ::sin(llh[0]);
1207          N = A / SQRT(1.0 - eccSq*slat*slat);
1208          htold = llh[2];
1209          llh[2] = p/::cos(llh[0]) - N;
1210          latold = llh[0];
1211          llh[0] = ::atan2(sl, cl*(1.0-eccSq*(N/(N+llh[2]))));
1212          if(fabs(llh[0]-latold) < 1.0e-9 && ::fabs(llh[2]-htold) < 1.0e-9 * A) break;
1213       }
1214       llh[0] *= RAD_TO_DEG;
1215    }
1216 
1217       // Fundamental routine to convert geodetic to geocentric coordinates.
1218       // @param geodeticllh (input): geodetic latitude (deg N),
1219       //            longitude (deg E), and height above ellipsoid (meters)
1220       // @param llr (output): geocentric lat (deg N),lon (deg E),radius (meters)
1221       // @param A (input) Earth semi-major axis
1222       // @param eccSq (input) square of Earth eccentricity
convertGeodeticToGeocentric(const Triple & llh,Triple & llr,const double A,const double eccSq)1223    void Position::convertGeodeticToGeocentric(const Triple& llh,
1224                                               Triple& llr,
1225                                               const double A,
1226                                               const double eccSq)
1227       throw()
1228    {
1229       double slat = ::sin(llh[0]*DEG_TO_RAD);
1230       double N = A/SQRT(1.0-eccSq*slat*slat);
1231       // longitude is unchanged
1232       llr[1] = llh[1];
1233       // radius
1234       llr[2] = SQRT((N+llh[2])*(N+llh[2]) + N*eccSq*(N*eccSq-2*(N+llh[2]))*slat*slat);
1235       if(llr[2] <= Position::POSITION_TOLERANCE/5) {
1236          // radius is below tolerance, hence assign zero-length
1237          // arbitrarily set latitude = longitude = 0
1238          llr[0] = llr[1] = llr[2] = 0;
1239          return;
1240       }
1241       if(1-::fabs(slat) < 1.e-10) {             // at the pole
1242          if(slat < 0) llr[0] = -90;
1243          else         llr[0] =  90;
1244          llr[1] = 0.0;
1245          return;
1246       }
1247       // theta
1248       llr[0] = ::acos((N*(1-eccSq)+llh[2])*slat/llr[2]);
1249       llr[0] *= RAD_TO_DEG;
1250       llr[0] = 90 - llr[0];
1251    }
1252 
1253    // ----------- Part 11: operator<< and other useful functions -------------
1254    //
1255      // Stream output for Position objects.
1256      // @param s stream to append formatted Position to.
1257      // @param t Position to append to stream \c s.
1258      // @return reference to \c s.
operator <<(ostream & s,const Position & p)1259    ostream& operator<<(ostream& s, const Position& p)
1260    {
1261       if(p.system == Position::Cartesian)
1262          s << p.printf("%.4x m %.4y m %.4z m");
1263       else if(p.system == Position::Geodetic)
1264          s << p.printf("%.8A degN %.8L degE %.4h m");
1265       else if(p.system == Position::Geocentric)
1266          s << p.printf("%.8a degN %.8L degE %.4r m");
1267       else if(p.system == Position::Spherical)
1268          s << p.printf("%.8t deg %.8p deg %.4r m");
1269       else
1270          s << " Unknown system! : " << p[0] << " " << p[1] << " " << p[2];
1271 
1272       return s;
1273    }
1274 
1275       // Compute the range in meters between this Position and
1276       // the Position passed as input.
1277       // @param right Position to which to find the range
1278       // @return the range (in meters)
1279       // @throw GeometryException if ell values differ
range(const Position & A,const Position & B)1280    double range(const Position& A,
1281                 const Position& B)
1282    {
1283       if(A.AEarth != B.AEarth || A.eccSquared != B.eccSquared)
1284       {
1285          GeometryException ge("Unequal geoids");
1286          GPSTK_THROW(ge);
1287       }
1288 
1289          Position L(A),R(B);
1290          L.transformTo(Position::Cartesian);
1291          R.transformTo(Position::Cartesian);
1292          double dif = RSS(L.X()-R.X(),L.Y()-R.Y(),L.Z()-R.Z());
1293          return dif;
1294       }
1295 
1296      // Compute the radius of the ellipsoidal Earth, given the geodetic latitude.
1297      // @param geolat geodetic latitude in degrees
1298      // @return the Earth radius (in meters)
radiusEarth(const double geolat,const double A,const double eccSq)1299    double Position::radiusEarth(const double geolat,
1300                                 const double A,
1301                                 const double eccSq)
1302       throw()
1303    {
1304       double slat=::sin(DEG_TO_RAD*geolat);
1305       double e=(1.0-eccSq);
1306       double f=(1.0+(e*e-1.0)*slat*slat)/(1.0-eccSq*slat*slat);
1307       return (A * SQRT(f));
1308    }
1309 
1310       // A member function that computes the elevation of the input
1311       // (Target) position as seen from this Position.
1312       // @param Target the Position which is observed to have the
1313       //        computed elevation, as seen from this Position.
1314       // @return the elevation in degrees
elevation(const Position & Target) const1315    double Position::elevation(const Position& Target) const
1316    {
1317       Position R(*this),S(Target);
1318       R.transformTo(Cartesian);
1319       S.transformTo(Cartesian);
1320       // use Triple:: functions in cartesian coordinates (only)
1321       double elevation;
1322       try {
1323          elevation = R.elvAngle(S);
1324       }
1325       catch(GeometryException& ge)
1326       {
1327          GPSTK_RETHROW(ge);
1328       }
1329       return elevation;
1330    }
1331 
1332       // A member function that computes the elevation of the input
1333       // (Target) position as seen from this Position, using a Geodetic
1334       // (i.e. ellipsoidal) system.
1335       // @param Target the Position which is observed to have the
1336       //        computed elevation, as seen from this Position.
1337       // @return the elevation in degrees
elevationGeodetic(const Position & Target) const1338    double Position::elevationGeodetic(const Position& Target) const
1339    {
1340       Position R(*this),S(Target);
1341       double latGeodetic = R.getGeodeticLatitude()*DEG_TO_RAD;
1342       double longGeodetic = R.getLongitude()*DEG_TO_RAD;
1343       double localUp;
1344       double cosUp;
1345       R.transformTo(Cartesian);
1346       S.transformTo(Cartesian);
1347       Triple z;
1348       // Let's get the slant vector
1349       z = S.theArray - R.theArray;
1350 
1351       if (z.mag()<=1e-4) // if the positions are within .1 millimeter
1352       {
1353          GeometryException ge("Positions are within .1 millimeter");
1354          GPSTK_THROW(ge);
1355       }
1356 
1357       // Compute k vector in local North-East-Up (NEU) system
1358       Triple kVector(::cos(latGeodetic)*::cos(longGeodetic), ::cos(latGeodetic)*::sin(longGeodetic), ::sin(latGeodetic));
1359       // Take advantage of dot method to get Up coordinate in local NEU system
1360       localUp = z.dot(kVector);
1361       // Let's get cos(z), being z the angle with respect to local vertical (Up);
1362       cosUp = localUp/z.mag();
1363 
1364       return 90.0 - ((::acos(cosUp))*RAD_TO_DEG);
1365    }
1366 
1367       // A member function that computes the azimuth of the input
1368       // (Target) position as seen from this Position.
1369       // @param Target the Position which is observed to have the
1370       //        computed azimuth, as seen from this Position.
1371       // @return the azimuth in degrees
azimuth(const Position & Target) const1372    double Position::azimuth(const Position& Target) const
1373    {
1374       Position R(*this),S(Target);
1375       R.transformTo(Cartesian);
1376       S.transformTo(Cartesian);
1377       // use Triple:: functions in cartesian coordinates (only)
1378       double az;
1379       try
1380       {
1381          az = R.azAngle(S);
1382 
1383       }
1384       catch(GeometryException& ge)
1385       {
1386          GPSTK_RETHROW(ge);
1387       }
1388 
1389       return az;
1390    }
1391 
1392       // A member function that computes the azimuth of the input
1393       // (Target) position as seen from this Position, using a Geodetic
1394       // (i.e. ellipsoidal) system.
1395       // @param Target the Position which is observed to have the
1396       //        computed azimuth, as seen from this Position.
1397       // @return the azimuth in degrees
azimuthGeodetic(const Position & Target) const1398    double Position::azimuthGeodetic(const Position& Target) const
1399    {
1400       Position R(*this),S(Target);
1401       double latGeodetic = R.getGeodeticLatitude()*DEG_TO_RAD;
1402       double longGeodetic = R.getLongitude()*DEG_TO_RAD;
1403       double localN, localE;
1404       R.transformTo(Cartesian);
1405       S.transformTo(Cartesian);
1406       Triple z;
1407       // Let's get the slant vector
1408       z = S.theArray - R.theArray;
1409 
1410       if (z.mag()<=1e-4) // if the positions are within .1 millimeter
1411       {
1412          GeometryException ge("Positions are within .1 millimeter");
1413          GPSTK_THROW(ge);
1414       }
1415 
1416       // Compute i vector in local North-East-Up (NEU) system
1417       Triple iVector(-::sin(latGeodetic)*::cos(longGeodetic), -::sin(latGeodetic)*::sin(longGeodetic), ::cos(latGeodetic));
1418       // Compute j vector in local North-East-Up (NEU) system
1419       Triple jVector(-::sin(longGeodetic), ::cos(longGeodetic), 0);
1420 
1421       // Now, let's use dot product to get localN and localE unitary vectors
1422       localN = (z.dot(iVector))/z.mag();
1423       localE = (z.dot(jVector))/z.mag();
1424 
1425       // Let's test if computing azimuth has any sense
1426       double test = fabs(localN) + fabs(localE);
1427 
1428       // Warning: If elevation is very close to 90 degrees, we will return azimuth = 0.0
1429       if (test < 1.0e-16) return 0.0;
1430 
1431       double alpha = ((::atan2(localE, localN)) * RAD_TO_DEG);
1432       if (alpha < 0.0)
1433       {
1434          return alpha + 360.0;
1435       }
1436       else
1437       {
1438          return alpha;
1439       }
1440    }
1441 
1442      // A member function that computes the point at which a signal, which
1443      // is received at *this Position and there is observed at the input
1444      // azimuth and elevation, crosses a model ionosphere that is taken to
1445      // be a uniform thin shell at the input height. This algorithm is done
1446      // in geocentric coordinates.
1447      // A member function that computes the point at which a signal, which
1448      // is received at *this Position and there is observed at the input
1449      // azimuth and elevation, crosses a model ionosphere that is taken to
1450      // be a uniform thin shell at the input height. This algorithm is done
1451      // in geocentric coordinates.
1452      // @param elev elevation angle of the signal at reception, in degrees
1453      // @param azim azimuth angle of the signal at reception, in degrees
1454      // @param ionoht height of the ionosphere, in meters
1455      // @return Position IPP the position of the ionospheric pierce point,
1456      //     in the same coordinate system as *this; *this is not modified.
getIonosphericPiercePoint(const double elev,const double azim,const double ionoht) const1457    Position Position::getIonosphericPiercePoint(const double elev,
1458                                                 const double azim,
1459                                                 const double ionoht) const
1460       throw()
1461    {
1462       Position Rx(*this);
1463 
1464       // convert to Geocentric
1465       Rx.transformTo(Geocentric);
1466 
1467       // compute the geographic pierce point
1468       Position IPP(Rx);                   // copy system and geoid
1469       double el = elev * DEG_TO_RAD;
1470       // p is the angle subtended at Earth center by Rx and the IPP
1471       double p = PI/2.0 - el - ::asin(AEarth*::cos(el)/(AEarth+ionoht));
1472       double lat = Rx.theArray[0] * DEG_TO_RAD;
1473       double az = azim * DEG_TO_RAD;
1474       IPP.theArray[0] = std::asin(std::sin(lat)*std::cos(p) + std::cos(lat)*std::sin(p)*std::cos(az));
1475       IPP.theArray[1] = Rx.theArray[1]*DEG_TO_RAD
1476          + ::asin(::sin(p)*::sin(az)/::cos(IPP.theArray[0]));
1477 
1478       IPP.theArray[0] *= RAD_TO_DEG;
1479       IPP.theArray[1] *= RAD_TO_DEG;
1480       IPP.theArray[2] = AEarth + ionoht;
1481 
1482       // transform back
1483       IPP.transformTo(system);
1484 
1485       return IPP;
1486    }
1487 
1488 
1489         /**
1490         * A member function that computes the radius of curvature of the
1491         * meridian (Rm) corresponding to this Position.
1492         * @return radius of curvature of the meridian (in meters)
1493         */
getCurvMeridian() const1494     double Position::getCurvMeridian() const
1495         throw()
1496     {
1497 
1498         double slat = ::sin(geodeticLatitude()*DEG_TO_RAD);
1499         double W = 1.0/SQRT(1.0-eccSquared*slat*slat);
1500 
1501         return AEarth*(1.0-eccSquared)*W*W*W;
1502 
1503     }
1504 
1505         /**
1506         * A member function that computes the radius of curvature in the
1507         * prime vertical (Rn) corresponding to this Position.
1508         * @return radius of curvature in the prime vertical (in meters)
1509         */
getCurvPrimeVertical() const1510     double Position::getCurvPrimeVertical() const
1511         throw()
1512     {
1513 
1514         double slat = ::sin(geodeticLatitude()*DEG_TO_RAD);
1515 
1516         return AEarth/SQRT(1.0-eccSquared*slat*slat);
1517 
1518     }
1519 
1520    // ----------- Part 12: private functions and member data -----------------
1521    //
1522       // Initialization function, used by the constructors.
1523       // @param a coordinate [ X(m), or latitude (degrees N) ]
1524       // @param b coordinate [ Y(m), or longitude (degrees E) ]
1525       // @param c coordinate [ Z, height above ellipsoid or radius, in m ]
1526       // @param s CoordinateSystem, defaults to Cartesian
1527       // @param geiod pointer to a GeoidModel, default NULL (WGS84)
1528       // @throw GeometryException on invalid input.
initialize(const double a,const double b,const double c,Position::CoordinateSystem s,EllipsoidModel * ell,ReferenceFrame frame)1529    void Position::initialize(const double a,
1530                   const double b,
1531                   const double c,
1532                   Position::CoordinateSystem s,
1533                   EllipsoidModel *ell,
1534                   ReferenceFrame frame)
1535    {
1536       double bb(b);
1537       if(s == Geodetic || s==Geocentric)
1538       {
1539          if(a > 90 || a < -90)
1540          {
1541             GeometryException ge("Invalid latitude in constructor: "
1542                                     + StringUtils::asString(a));
1543             GPSTK_THROW(ge);
1544          }
1545          if(bb < 0)
1546             bb += 360*(1+(unsigned long)(bb/360));
1547          else if(bb >= 360)
1548             bb -= 360*(unsigned long)(bb/360);
1549       }
1550       if(s==Geocentric || s==Spherical)
1551       {
1552          if(c < 0)
1553          {
1554             GeometryException ge("Invalid radius in constructor: "
1555                                            + StringUtils::asString(c));
1556             GPSTK_THROW(ge);
1557          }
1558       }
1559       if(s==Spherical)
1560       {
1561          if(a < 0 || a > 180)
1562          {
1563             GeometryException ge("Invalid theta in constructor: "
1564                                     + StringUtils::asString(a));
1565             GPSTK_THROW(ge);
1566          }
1567          if(bb < 0)
1568             bb += 360*(1+(unsigned long)(bb/360));
1569          else if(bb >= 360)
1570             bb -= 360*(unsigned long)(bb/360);
1571       }
1572 
1573       theArray[0] = a;
1574       theArray[1] = bb;
1575       theArray[2] = c;
1576 
1577       if(ell) {
1578          AEarth = ell->a();
1579          eccSquared = ell->eccSquared();
1580       }
1581       else {
1582          WGS84Ellipsoid WGS84;
1583          AEarth = WGS84.a();
1584          eccSquared = WGS84.eccSquared();
1585       }
1586       system = s;
1587       tolerance = POSITION_TOLERANCE;
1588       refFrame = frame;
1589    }
1590 
1591 }  // namespace gpstk
1592