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