1 //============================================================================== 2 // 3 // This file is part of GPSTk, the GPS Toolkit. 4 // 5 // The GPSTk is free software; you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published 7 // by the Free Software Foundation; either version 3.0 of the License, or 8 // any later version. 9 // 10 // The GPSTk is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with GPSTk; if not, write to the Free Software Foundation, 17 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA 18 // 19 // This software was developed by Applied Research Laboratories at the 20 // University of Texas at Austin. 21 // Copyright 2004-2020, The Board of Regents of The University of Texas System 22 // 23 //============================================================================== 24 25 //============================================================================== 26 // 27 // This software was developed by Applied Research Laboratories at the 28 // University of Texas at Austin, under contract to an agency or agencies 29 // within the U.S. Department of Defense. The U.S. Government retains all 30 // rights to use, duplicate, distribute, disclose, or release this software. 31 // 32 // Pursuant to DoD Directive 523024 33 // 34 // DISTRIBUTION STATEMENT A: This software has been approved for public 35 // release, distribution is unlimited. 36 // 37 //============================================================================== 38 39 /// @file GloEphemeris.cpp 40 /// Ephemeris data for GLONASS. 41 42 #include <iomanip> 43 #include "GloEphemeris.hpp" 44 #include "TimeString.hpp" 45 46 namespace gpstk 47 { 48 49 50 /* Compute satellite position & velocity at the given time 51 * using this ephemeris. 52 * 53 * @throw InvalidRequest if required data has not been stored. 54 */ svXvt(const CommonTime & epoch) const55 Xvt GloEphemeris::svXvt(const CommonTime& epoch) const 56 { 57 58 // Check that the given epoch is within the available time limits. 59 // We have to add a margin of 15 minutes (900 seconds). 60 if ( epoch < (ephTime - 900.0) || 61 epoch >= (ephTime + 900.0) ) 62 { 63 InvalidRequest e( "Requested time is out of ephemeris data" ); 64 GPSTK_THROW(e); 65 } 66 67 Xvt retVal = svXvtOverrideFit(epoch); 68 return retVal; 69 } 70 svXvtOverrideFit(const CommonTime & epoch) const71 Xvt GloEphemeris::svXvtOverrideFit(const CommonTime& epoch) const 72 { 73 // Values to be returned will be stored here 74 Xvt sv; 75 76 // If the exact epoch is found, let's return the values 77 if ( epoch == ephTime ) // exact match for epoch 78 { 79 80 sv.x[0] = x[0]*1.e3; // m 81 sv.x[1] = x[1]*1.e3; // m 82 sv.x[2] = x[2]*1.e3; // m 83 sv.v[0] = v[0]*1.e3; // m/sec 84 sv.v[1] = v[1]*1.e3; // m/sec 85 sv.v[2] = v[2]*1.e3; // m/sec 86 87 // In the GLONASS system, 'clkbias' already includes the 88 // relativistic correction, therefore we must substract the late 89 // from the former. 90 sv.relcorr = sv.computeRelativityCorrection(); 91 sv.clkbias = clkbias + clkdrift * (epoch - ephTime) - sv.relcorr; 92 sv.clkdrift = clkdrift; 93 sv.frame = ReferenceFrame::PZ90; 94 95 // We are done, let's return 96 return sv; 97 98 } 99 100 // Get the data out of the GloRecord structure 101 double px( x[0] ); // X coordinate (km) 102 double vx( v[0] ); // X velocity (km/s) 103 double ax( a[0] ); // X acceleration (km/s^2) 104 double py( x[1] ); // Y coordinate 105 double vy( v[1] ); // Y velocity 106 double ay( a[1] ); // Y acceleration 107 double pz( x[2] ); // Z coordinate 108 double vz( v[2] ); // Z velocity 109 double az( a[2] ); // Z acceleration 110 111 // We will need some PZ-90 ellipsoid parameters 112 PZ90Ellipsoid pz90; 113 double we( pz90.angVelocity() ); 114 115 // Get sidereal time at Greenwich at 0 hours UT 116 double gst( getSidTime( ephTime ) ); 117 double s0( gst*PI/12.0 ); 118 YDSTime ytime( ephTime ); 119 double numSeconds( ytime.sod ); 120 double s( s0 + we*numSeconds ); 121 double cs( std::cos(s) ); 122 double ss( std::sin(s) ); 123 124 // Initial state matrix 125 Vector<double> initialState(6), accel(3), dxt1(6), dxt2(6), dxt3(6), 126 dxt4(6), tempRes(6); 127 128 // Get the reference state out of GloEphemeris object data. Values 129 // must be rotated from PZ-90 to an absolute coordinate system 130 // Initial x coordinate (m) 131 initialState(0) = (px*cs - py*ss); 132 // Initial y coordinate 133 initialState(2) = (px*ss + py*cs); 134 // Initial z coordinate 135 initialState(4) = pz; 136 137 // Initial x velocity (m/s) 138 initialState(1) = (vx*cs - vy*ss - we*initialState(2) ); 139 // Initial y velocity 140 initialState(3) = (vx*ss + vy*cs + we*initialState(0) ); 141 // Initial z velocity 142 initialState(5) = vz; 143 144 145 // Integrate satellite state to desired epoch using the given step 146 double rkStep( step ); 147 148 if ( (epoch - ephTime) < 0.0 ) rkStep = step*(-1.0); 149 CommonTime workEpoch( ephTime ); 150 151 double tolerance( 1e-9 ); 152 bool done( false ); 153 while (!done) 154 { 155 156 // If we are about to overstep, change the stepsize appropriately 157 // to hit our target final time. 158 if( rkStep > 0.0 ) 159 { 160 if( (workEpoch + rkStep) > epoch ) 161 rkStep = (epoch - workEpoch); 162 } 163 else 164 { 165 if ( (workEpoch + rkStep) < epoch ) 166 rkStep = (epoch - workEpoch); 167 } 168 169 numSeconds += rkStep; 170 s = s0 + we*( numSeconds ); 171 cs = std::cos(s); 172 ss = std::sin(s); 173 174 // Accelerations are computed once per iteration 175 accel(0) = ax*cs - ay*ss; 176 accel(1) = ax*ss + ay*cs; 177 accel(2) = az; 178 179 dxt1 = derivative( initialState, accel ); 180 for( int j = 0; j < 6; ++j ) 181 tempRes(j) = initialState(j) + rkStep*dxt1(j)/2.0; 182 183 dxt2 = derivative( tempRes, accel ); 184 for( int j = 0; j < 6; ++j ) 185 tempRes(j) = initialState(j) + rkStep*dxt2(j)/2.0; 186 187 dxt3 = derivative( tempRes, accel ); 188 for( int j = 0; j < 6; ++j ) 189 tempRes(j) = initialState(j) + rkStep*dxt3(j); 190 191 dxt4 = derivative( tempRes, accel ); 192 for( int j = 0; j < 6; ++j ) 193 initialState(j) = initialState(j) + rkStep * ( dxt1(j) 194 + 2.0 * ( dxt2(j) + dxt3(j) ) + dxt4(j) ) / 6.0; 195 196 197 // If we are within tolerance of the target time, we are done. 198 workEpoch += rkStep; 199 if ( std::fabs(epoch - workEpoch ) < tolerance ) 200 done = true; 201 202 } // End of 'while (!done)...' 203 204 205 px = initialState(0); 206 py = initialState(2); 207 pz = initialState(4); 208 vx = initialState(1); 209 vy = initialState(3); 210 vz = initialState(5); 211 212 sv.x[0] = 1000.0*( px*cs + py*ss ); // X coordinate 213 sv.x[1] = 1000.0*(-px*ss + py*cs); // Y coordinate 214 sv.x[2] = 1000.0*pz; // Z coordinate 215 sv.v[0] = 1000.0*( vx*cs + vy*ss + we*(sv.x[1]/1000.0) ); // X velocity 216 sv.v[1] = 1000.0*(-vx*ss + vy*cs - we*(sv.x[0]/1000.0) ); // Y velocity 217 sv.v[2] = 1000.0*vz; // Z velocity 218 219 // In the GLONASS system, 'clkbias' already includes the relativistic 220 // correction, therefore we must substract the late from the former. 221 sv.relcorr = sv.computeRelativityCorrection(); 222 sv.clkbias = clkbias + clkdrift * (epoch - ephTime) - sv.relcorr; 223 sv.clkdrift = clkdrift; 224 sv.frame = ReferenceFrame::PZ90; 225 226 // We are done, let's return 227 return sv; 228 229 230 } // End of method 'GloEphemeris::svXvt(const CommonTime& t)' 231 232 233 // Get the epoch time for this ephemeris getEphemerisEpoch() const234 CommonTime GloEphemeris::getEphemerisEpoch() const 235 { 236 237 // First, let's check if there is valid data 238 if(!valid) 239 { 240 InvalidRequest exc("getEphemerisEpoch(): No valid data stored."); 241 GPSTK_THROW(exc); 242 } 243 244 return ephTime; 245 246 } // End of method 'GloEphemeris::getEphemerisEpoch()' 247 248 249 // This function returns the PRN ID of the SV. getPRNID() const250 short GloEphemeris::getPRNID() const 251 { 252 253 // First, let's check if there is valid data 254 if(!valid) 255 { 256 InvalidRequest exc("getPRNID(): No valid data stored."); 257 GPSTK_THROW(exc); 258 } 259 260 return PRNID; 261 262 } // End of method 'GloEphemeris::getPRNID()' 263 264 265 /* Compute the satellite clock bias (sec) at the given time 266 * 267 * @param epoch Epoch to compute satellite clock bias. 268 * 269 * @throw InvalidRequest if required data has not been stored. 270 */ svClockBias(const CommonTime & epoch) const271 double GloEphemeris::svClockBias(const CommonTime& epoch) const 272 { 273 274 // First, let's check if there is valid data 275 if(!valid) 276 { 277 InvalidRequest exc("svClockBias(): No valid data stored."); 278 GPSTK_THROW(exc); 279 } 280 281 // Auxiliar object 282 Xvt sv; 283 sv.x = x; 284 sv.v = v; 285 286 // In the GLONASS system, 'clkbias' already includes the relativistic 287 // correction, therefore we must substract the late from the former. 288 sv.relcorr = sv.computeRelativityCorrection(); 289 sv.clkbias = clkbias + clkdrift * (epoch - ephTime) - sv.relcorr; 290 291 return sv.clkbias; 292 293 } // End of method 'GloEphemeris::svClockBias(const CommonTime& epoch)' 294 295 296 /* Compute the satellite clock drift (sec/sec) at the given time 297 * 298 * @param epoch Epoch to compute satellite clock drift. 299 * 300 * @throw InvalidRequest if required data has not been stored. 301 */ svClockDrift(const CommonTime & epoch) const302 double GloEphemeris::svClockDrift(const CommonTime& epoch) const 303 { 304 305 // First, let's check if there is valid data 306 if(!valid) 307 { 308 InvalidRequest exc("svClockDrift(): No valid data stored."); 309 GPSTK_THROW(exc); 310 } 311 312 return clkdrift; 313 314 } // End of method 'GloEphemeris::svClockDrift(const CommonTime& epoch)' 315 316 317 318 319 // Output the contents of this ephemeris to the given stream as a single line. dump(std::ostream & s) const320 void GloEphemeris::dump(std::ostream& s) const 321 throw() 322 { 323 324 s << "Sys:" << satSys << ", PRN:" << PRNID 325 << ", Epoch:" << ephTime << ", pos:" << x 326 << ", vel:" << v << ", acc:" << a 327 << ", TauN:" << clkbias << ", GammaN:" << clkdrift 328 << ", MFTime:" << MFtime<< ", health:" << health 329 << ", freqNum:" << freqNum << ", ageOfInfo:" << ageOfInfo; 330 331 } // End of method 'GloEphemeris::dump(std::ostream& s)' 332 prettyDump(std::ostream & s) const333 void GloEphemeris::prettyDump(std::ostream& s) const 334 { 335 s << "**********************************************" << std::endl; 336 s << "Slot ID " << std::setw(12) << PRNID << std::endl; 337 s << "Epoch Time " << printTime(ephTime,"%03j, %02m/%02d/%02y %02H:%02M:%02S") 338 << std::endl; 339 s << "MFTime " << std::setw(12) << MFtime << " sec of Week" << std::endl; 340 s << "Health " << std::setw(12) << health << std::endl; 341 s << "Freq. Offset" << std::setw(12) << freqNum << std::endl; 342 s << "Age of Info " << std::setw(12) << ageOfInfo << " days" << std::endl; 343 344 s << "Position " << std::setw(12) << x << "m" << std::endl; 345 s << "Velocity " << std::setw(12) << v << "m/sec" << std::endl; 346 s << "Acceleration" << std::setw(12) << a << "m/sec**2" << std::endl; 347 s << "TauN " << std::setw(12) << clkbias << "units" << std::endl; 348 s << "GammaN " << std::setw(12) << clkdrift << "units" << std::endl; 349 } 350 terseDump(std::ostream & s) const351 void GloEphemeris::terseDump(std::ostream& s) const 352 { 353 s << " " << std::setw(2) << PRNID << " "; 354 s << printTime(ephTime,"%03j, %02m/%02d/%02y %02H:%02M:%02S") << " "; 355 s << std::setw(6) << MFtime << " "; 356 s << std::setw(5) << health << " "; 357 s << std::setw(2) << freqNum << std::endl; 358 } 359 terseHeader(std::ostream & s) const360 void GloEphemeris::terseHeader(std::ostream& s) const 361 { 362 s << " Epoch time Msg Time Freq." << std::endl; 363 s << "Slot DOY mm/dd/yy HH:MM:SS (SOW) Health Offset" << std::endl; 364 } 365 366 // Set the parameters for this ephemeris object. setRecord(std::string svSys,short prn,const CommonTime & epoch,Triple pos,Triple vel,Triple acc,double tau,double gamma,long mftime,short h,short freqnum,double age,double rkStep)367 GloEphemeris& GloEphemeris::setRecord( std::string svSys, 368 short prn, 369 const CommonTime& epoch, 370 Triple pos, 371 Triple vel, 372 Triple acc, 373 double tau, 374 double gamma, 375 long mftime, 376 short h, 377 short freqnum, 378 double age, 379 double rkStep ) 380 { 381 satSys = svSys; 382 PRNID = prn; 383 ephTime = epoch; 384 x = pos; 385 v = vel; 386 a = acc; 387 clkbias = tau; 388 clkdrift = gamma; 389 MFtime = mftime; 390 health = h; 391 freqNum = freqnum; 392 ageOfInfo = age; 393 394 step = rkStep; 395 396 // Set this object as valid 397 valid = true; 398 399 return *this; 400 401 } // End of method 'GloEphemeris::setRecord()' 402 403 404 // Compute true sidereal time (in hours) at Greenwich at 0 hours UT. getSidTime(const CommonTime & time) const405 double GloEphemeris::getSidTime( const CommonTime& time ) const 406 { 407 408 // The following algorithm is based on the paper: 409 // Aoki, S., Guinot,B., Kaplan, G. H., Kinoshita, H., McCarthy, D. D. 410 // and P.K. Seidelmann. 'The New Definition of Universal Time'. 411 // Astronomy and Astrophysics, 105, 359-361, 1982. 412 413 // Get the Julian Day at 0 hours UT (jd) 414 YDSTime ytime( time ); 415 double year( static_cast<double>(ytime.year) ); 416 int doy( ytime.doy ); 417 int temp( static_cast<int>(floor(365.25 * (year - 1.0))) + doy ); 418 419 double jd( static_cast<double>(temp)+ 1721409.5 ); 420 421 // Compute the Julian centuries (36525 days) 422 double jc( (jd - 2451545.0)/36525.0 ); 423 424 // Compute the sidereal time, in seconds 425 double sid( 24110.54841 + jc * ( 8640184.812866 426 + jc * ( 0.093104 - jc * 0.0000062 ) ) ); 427 428 sid = sid / 3600.0; 429 sid = fmod(sid, 24.0); 430 if( sid < 0.0 ) sid = sid + 24.0; 431 432 return sid; 433 434 }; // End of method 'GloEphemeris::getSidTime()' 435 436 437 // Function implementing the derivative of GLONASS orbital model. derivative(const Vector<double> & inState,const Vector<double> & accel) const438 Vector<double> GloEphemeris::derivative( const Vector<double>& inState, 439 const Vector<double>& accel ) 440 const 441 { 442 443 // We will need some important PZ90 ellipsoid values 444 PZ90Ellipsoid pz90; 445 const double j20( pz90.j20() ); 446 const double mu( pz90.gm_km() ); 447 const double ae( pz90.a_km() ); 448 449 // Let's start getting the current satellite position and velocity 450 double x( inState(0) ); // X coordinate 451 //double vx( inState(1) ); // X velocity 452 double y( inState(2) ); // Y coordinate 453 //double vy( inState(3) ); // Y velocity 454 double z( inState(4) ); // Z coordinate 455 //double vz( inState(5) ); // Z velocity 456 457 double r2( x*x + y*y + z*z ); 458 double r( std::sqrt(r2) ); 459 double xmu( mu/r2 ); 460 double rho( ae/r ); 461 double xr( x/r ); 462 double yr( y/r ); 463 double zr( z/r ); 464 double zr2( zr*zr ); 465 double k1(j20*xmu*1.5*rho*rho); 466 double cm( k1*(1.0-5.0*zr2) ); 467 double cmz( k1*(3.0-5.0*zr2) ); 468 double k2(cm-xmu); 469 470 double gloAx( k2*xr + accel(0) ); 471 double gloAy( k2*yr + accel(1) ); 472 double gloAz( (cmz-xmu)*zr + accel(2) ); 473 474 Vector<double> dxt(6, 0.0); 475 476 // Let's insert data related to X coordinates 477 dxt(0) = inState(1); // Set X' = Vx 478 dxt(1) = gloAx; // Set Vx' = gloAx 479 480 // Let's insert data related to Y coordinates 481 dxt(2) = inState(3); // Set Y' = Vy 482 dxt(3) = gloAy; // Set Vy' = gloAy 483 484 // Let's insert data related to Z coordinates 485 dxt(4) = inState(5); // Set Z' = Vz 486 dxt(5) = gloAz; // Set Vz' = gloAz 487 488 return dxt; 489 490 } // End of method 'GloEphemeris::derivative()' 491 492 493 // Output the contents of this ephemeris to the given stream. operator <<(std::ostream & s,const GloEphemeris & glo)494 std::ostream& operator<<( std::ostream& s, const GloEphemeris& glo ) 495 { 496 497 glo.dump(s); 498 return s; 499 500 } // End of 'std::ostream& operator<<()' 501 502 503 } // End of namespace gpstk 504