1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.propagation.numerical;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.ode.ODEIntegrator;
21  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
22  import org.hipparchus.util.FastMath;
23  import org.hipparchus.util.MathUtils;
24  import org.hipparchus.util.SinCos;
25  import org.orekit.attitudes.Attitude;
26  import org.orekit.attitudes.AttitudeProvider;
27  import org.orekit.data.DataContext;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.frames.Frame;
31  import org.orekit.orbits.CartesianOrbit;
32  import org.orekit.orbits.Orbit;
33  import org.orekit.orbits.OrbitType;
34  import org.orekit.orbits.PositionAngleType;
35  import org.orekit.propagation.PropagationType;
36  import org.orekit.propagation.SpacecraftState;
37  import org.orekit.propagation.analytical.gnss.data.GLONASSAlmanac;
38  import org.orekit.propagation.analytical.gnss.data.GLONASSFdmaNavigationMessage;
39  import org.orekit.propagation.analytical.gnss.data.GLONASSOrbitalElements;
40  import org.orekit.propagation.analytical.gnss.data.GNSSConstants;
41  import org.orekit.propagation.integration.AbstractIntegratedPropagator;
42  import org.orekit.propagation.integration.StateMapper;
43  import org.orekit.time.AbsoluteDate;
44  import org.orekit.time.GLONASSDate;
45  import org.orekit.utils.AbsolutePVCoordinates;
46  import org.orekit.utils.Constants;
47  import org.orekit.utils.IERSConventions;
48  import org.orekit.utils.PVCoordinates;
49  import org.orekit.utils.TimeStampedPVCoordinates;
50  
51  import java.util.Arrays;
52  
53  /**
54   * This class propagates GLONASS orbits using numerical integration.
55   * <p>
56   * As recommended by the GLONASS Interface Control Document (ICD),
57   * a {@link ClassicalRungeKuttaIntegrator  4th order Runge-Kutta technique}
58   * shall be used to integrate the equations.
59   * </p>
60   * <p>
61   * Classical used of this orbit propagator is to compute GLONASS satellite
62   * coordinates from the navigation message.
63   * </p>
64   * <p>
65   * If the projections of luni-solar accelerations to axes of
66   * Greenwich geocentric coordinates {@link GLONASSOrbitalElements#getXDotDot() X''(tb)},
67   * {@link GLONASSOrbitalElements#getYDotDot() Y''(tb)} and {@link GLONASSOrbitalElements#getZDotDot() Z''(tb)}
68   * are available in the navigation message; a transformation is performed to convert these
69   * accelerations into the correct coordinate system. In the case where they are not
70   * available into the navigation message, these accelerations are computed.
71   * </p>
72   * <p>
73   * <b>Caution:</b> The Glonass numerical propagator can only be used with {@link GLONASSFdmaNavigationMessage}.
74   * Using this propagator with a {@link GLONASSAlmanac} is prone to error.
75   * </p>
76   *
77   * @see <a href="http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf">
78   *       GLONASS Interface Control Document</a>
79   *
80   * @author Bryan Cazabonne
81   *
82   */
83  public class GLONASSNumericalPropagator extends AbstractIntegratedPropagator {
84  
85      /** Second degree coefficient of normal potential. */
86      private static final double GLONASS_J20 = 1.08262575e-3;
87  
88      /** Equatorial radius of Earth (m). */
89      private static final double GLONASS_EARTH_EQUATORIAL_RADIUS = 6378136;
90  
91      /** Value of the Earth's rotation rate in rad/s (See Ref). */
92      private static final double GLONASS_AV = 7.2921151467e-5;
93  
94      // Data used to solve Kepler's equation
95      /** First coefficient to compute Kepler equation solver starter. */
96      private static final double A;
97  
98      /** Second coefficient to compute Kepler equation solver starter. */
99      private static final double B;
100 
101     static {
102         final double k1 = 3 * FastMath.PI + 2;
103         final double k2 = FastMath.PI - 1;
104         final double k3 = 6 * FastMath.PI - 1;
105         A  = 3 * k2 * k2 / k1;
106         B  = k3 * k3 / (6 * k1);
107     }
108 
109     /** The GLONASS orbital elements used. */
110     private final GLONASSOrbitalElements glonassOrbit;
111 
112     /** Initial date in GLONASS form. */
113     private final GLONASSDate initDate;
114 
115     /** The spacecraft mass (kg). */
116     private final double mass;
117 
118     /** The ECI frame used for GLONASS propagation. */
119     private final Frame eci;
120 
121     /** Direction cosines and distance of perturbing body: Moon.
122      * <p>
123      * <ul>
124      * <li>double[0] = ξ<sub>m</sub></li>
125      * <li>double[1] = η<sub>m</sub></li>
126      * <li>double[2] = ψ<sub>m</sub></li>
127      * <li>double[3] = r<sub>m</sub></li>
128      * </ul>
129      * </p>
130      */
131     private double[] moonElements;
132 
133     /** Direction cosines and distance of perturbing body: Sun.
134      * <p>
135      * <ul>
136      * <li>double[0] = ξ<sub>s</sub></li>
137      * <li>double[1] = η<sub>s</sub></li>
138      * <li>double[2] = ψ<sub>s</sub></li>
139      * <li>double[3] = r<sub>s</sub></li>
140      * </ul>
141      * </p>
142      */
143     private double[] sunElements;
144 
145     /** Flag for availability of projections of acceleration transmitted within the navigation message. */
146     private final boolean isAccAvailable;
147 
148     /** Data context used for propagation. */
149     private final DataContext dataContext;
150 
151     /**
152      * Private constructor.
153      * @param integrator Runge-Kutta integrator
154      * @param glonassOrbit Glonass orbital elements
155      * @param eci Earth Centered Inertial frame
156      * @param provider Attitude provider
157      * @param mass Satellite mass (kg)
158      * @param context Data context
159      * @param isAccAvailable true if the acceleration  is transmitted within the navigation message
160      */
161     public GLONASSNumericalPropagator(final ClassicalRungeKuttaIntegrator integrator,
162                                       final GLONASSOrbitalElements glonassOrbit,
163                                       final Frame eci, final AttitudeProvider provider,
164                                       final double mass, final DataContext context,
165                                       final boolean isAccAvailable) {
166         super(integrator, PropagationType.OSCULATING);
167         this.dataContext = context;
168         this.isAccAvailable = isAccAvailable;
169         // Stores the GLONASS orbital elements
170         this.glonassOrbit = glonassOrbit;
171         // Sets the Earth Centered Inertial frame
172         this.eci = eci;
173         // Sets the mass
174         this.mass = mass;
175         this.initDate = new GLONASSDate(
176                 glonassOrbit.getDate(),
177                 dataContext.getTimeScales().getGLONASS());
178 
179         // Initialize state mapper
180         initMapper();
181         setInitialState();
182         setAttitudeProvider(provider);
183         setOrbitType(OrbitType.CARTESIAN);
184         // It is not meaningful for propagation in Cartesian parameters
185         setPositionAngleType(PositionAngleType.TRUE);
186         setMu(GNSSConstants.GLONASS_MU);
187 
188         // As recommended by GLONASS ICD (2016), the direction cosines and distance
189         // of perturbing body are calculated one time (at tb).
190         if (!isAccAvailable) {
191             computeMoonElements(initDate);
192             computeSunElements(initDate);
193         }
194     }
195 
196     /**
197      * Gets the underlying GLONASS orbital elements.
198      *
199      * @return the underlying GLONASS orbital elements
200      */
201     public GLONASSOrbitalElements getGLONASSOrbitalElements() {
202         return glonassOrbit;
203     }
204 
205     /** {@inheritDoc} */
206     @Override
207     public SpacecraftState propagate(final AbsoluteDate date) {
208         // Spacecraft state in inertial frame
209         final SpacecraftState stateInInertial = super.propagate(date);
210 
211         // Build the spacecraft state in inertial frame
212         final PVCoordinates pvInPZ90 = getPVInPZ90(stateInInertial);
213         final AbsolutePVCoordinates absPV = new AbsolutePVCoordinates(
214                 dataContext.getFrames().getPZ9011(IERSConventions.IERS_2010, true),
215                 stateInInertial.getDate(), pvInPZ90);
216         final TimeStampedPVCoordinates pvInInertial = absPV.getPVCoordinates(eci);
217         return new SpacecraftState(new CartesianOrbit(pvInInertial, eci, pvInInertial.getDate(), GNSSConstants.GLONASS_MU),
218                                                       stateInInertial.getAttitude(),
219                                                       stateInInertial.getMass(),
220                                                       stateInInertial.getAdditionalDataValues(),
221                                                       stateInInertial.getAdditionalStatesDerivatives());
222     }
223 
224     /**
225      * Set the initial state.
226      * <p>
227      * The initial conditions on position and velocity are in the ECEF coordinate system PZ-90.
228      * Previous to orbit integration, they must be transformed to an absolute inertial coordinate system.
229      * </p>
230      */
231     private void setInitialState() {
232 
233         // Transform initial PV coordinates to an absolute inertial coordinate system.
234         final PVCoordinates pvInInertial = getPVInInertial(initDate);
235 
236         // Create a new orbit
237         final Orbit orbit = new CartesianOrbit(pvInInertial,
238                                                eci, initDate.getDate(),
239                                                GNSSConstants.GLONASS_MU);
240 
241         // Reset the initial state to apply the transformation
242         resetInitialState(new SpacecraftState(orbit).withMass(mass));
243     }
244 
245     /**
246      * This method computes the direction cosines and the distance used to
247      * compute the gravitational perturbations of the Moon.
248      *
249      * @param date the computation date in GLONASS scale
250      */
251     private void computeMoonElements(final GLONASSDate date) {
252 
253         moonElements = new double[4];
254 
255         // Constants
256         // Semi-major axis of the Moon's orbit (m)
257         final double am = 3.84385243e8;
258         // The Moon's orbit eccentricity
259         final double em = 0.054900489;
260         // Mean inclination of the Moon's orbit to the ecliptic (rad)
261         final double im = 0.0898041080;
262 
263         // Computed parameters
264         // Time from epoch 2000 to the instant tb of GLONASS ephemeris broadcast
265         final double dtoJD = (glonassOrbit.getTime() - 10800.) / Constants.JULIAN_DAY;
266         final double t  = (date.getJD0() + dtoJD - 2451545.0) / 36525;
267         final double t2 = t * t;
268         // Mean inclination of Earth equator to ecliptic (rad)
269         final double eps = 0.4090926006 - 0.0002270711 * t;
270         // Mean longitude of the Moon's orbit perigee (rad)
271         final double gammaM = 1.4547885346 + 71.0176852437 * t - 0.0001801481 * t2;
272         // Mean longitude of the ascending node of the Moon (rad)
273         final double omegaM = 2.1824391966 - 33.7570459536 * t + 0.0000362262 * t2;
274         // Mean anomaly of the Moon (rad)
275         final double qm = 2.3555557435 + 8328.6914257190 * t + 0.0001545547 * t2;
276 
277         // Commons parameters
278         final SinCos scOm  = FastMath.sinCos(omegaM);
279         final SinCos scIm  = FastMath.sinCos(im);
280         final SinCos scEs  = FastMath.sinCos(eps);
281         final SinCos scGm  = FastMath.sinCos(gammaM);
282         final double cosOm = scOm.cos();
283         final double sinOm = scOm.sin();
284         final double cosIm = scIm.cos();
285         final double sinIm = scIm.sin();
286         final double cosEs = scEs.cos();
287         final double sinEs = scEs.sin();
288         final double cosGm = scGm.cos();
289         final double sinGm = scGm.sin();
290 
291         // Intermediate parameters
292         final double psiStar = cosOm * sinIm;
293         final double etaStar = sinOm * sinIm;
294         final double epsStar = 1. - cosOm * cosOm * (1. - cosIm);
295         final double eps11 = sinOm * cosOm * (1. - cosIm);
296         final double eps12 = 1. - sinOm * sinOm * (1. - cosIm);
297         final double eta11 = epsStar * cosEs - psiStar * sinEs;
298         final double eta12 = eps11 * cosEs + etaStar * sinEs;
299         final double psi11 = epsStar * sinEs + psiStar * cosEs;
300         final double psi12 = eps11 * sinEs - etaStar * cosEs;
301 
302         // Eccentric Anomaly
303         final double ek = getEccentricAnomaly(qm, em);
304 
305         // True Anomaly
306         final double vk    = getTrueAnomaly(ek, em);
307         final SinCos scVk  = FastMath.sinCos(vk);
308         final double sinVk = scVk.sin();
309         final double cosVk = scVk.cos();
310 
311         // Direction cosine
312         final double epsM = eps11 * (sinVk * cosGm + cosVk * sinGm) + eps12 * (cosVk * cosGm - sinVk * sinGm);
313         final double etaM = eta11 * (sinVk * cosGm + cosVk * sinGm) + eta12 * (cosVk * cosGm - sinVk * sinGm);
314         final double psiM = psi11 * (sinVk * cosGm + cosVk * sinGm) + psi12 * (cosVk * cosGm - sinVk * sinGm);
315 
316         // Distance
317         final double rm = am * (1. - em * FastMath.cos(ek));
318 
319         moonElements[0] = epsM;
320         moonElements[1] = etaM;
321         moonElements[2] = psiM;
322         moonElements[3] = rm;
323 
324     }
325 
326     /**
327      * This method computes the direction cosines and the distance used to
328      * compute the gravitational perturbations of the Sun.
329      *
330      * @param date the computation date in GLONASS scale
331      */
332     private void computeSunElements(final GLONASSDate date) {
333 
334         sunElements = new double[4];
335 
336         // Constants
337         //  Major semi-axis of the Earth’s orbit around the Sun (m)
338         final double as = 1.49598e11;
339         // The eccentricity of the Earth’s orbit around the Sun
340         final double es = 0.016719;
341 
342         // Computed parameters
343         // Time from epoch 2000 to the instant tb of GLONASS ephemeris broadcast
344         final double dtoJD = (glonassOrbit.getTime() - 10800.) / Constants.JULIAN_DAY;
345         final double t  = (date.getJD0() + dtoJD - 2451545.0) / 36525;
346         final double t2 = t * t;
347         // Mean inclination of Earth equator to ecliptic (rad)
348         final double eps = 0.4090926006 - 0.0002270711 * t;
349         // Mean tropic longitude of the Sun orbit perigee (rad)
350         final double ws = -7.6281824375 + 0.0300101976 * t + 0.0000079741 * t2;
351         // Mean anomaly of the Sun (rad)
352         final double qs = 6.2400601269 + 628.3019551714 * t - 0.0000026820 * t2;
353 
354         // Eccentric Anomaly
355         final double ek = getEccentricAnomaly(qs, es);
356 
357         // True Anomaly
358         final double vk    =  getTrueAnomaly(ek, es);
359         final SinCos scVk  = FastMath.sinCos(vk);
360         final double sinVk = scVk.sin();
361         final double cosVk = scVk.cos();
362 
363         // Commons parameters
364         final SinCos scWs  = FastMath.sinCos(ws);
365         final SinCos scEs  = FastMath.sinCos(eps);
366         final double cosWs = scWs.cos();
367         final double sinWs = scWs.sin();
368         final double cosEs = scEs.cos();
369         final double sinEs = scEs.sin();
370 
371         // Direction cosine
372         final double epsS = cosVk * cosWs - sinVk * sinWs;
373         final double etaS = cosEs * (sinVk * cosWs + cosVk * sinWs);
374         final double psiS = sinEs * (sinVk * cosWs + cosVk * sinWs);
375 
376         // Distance
377         final double rs = as * (1. - es * FastMath.cos(ek));
378 
379         sunElements[0] = epsS;
380         sunElements[1] = etaS;
381         sunElements[2] = psiS;
382         sunElements[3] = rs;
383 
384     }
385 
386     /**
387      * Computes the elliptic eccentric anomaly from the mean anomaly.
388      * <p>
389      * The algorithm used here for solving Kepler equation has been published
390      * in: "Procedures for  solving Kepler's Equation", A. W. Odell and
391      * R. H. Gooding, Celestial Mechanics 38 (1986) 307-334
392      * </p>
393      * <p>It has been copied from the OREKIT library (KeplerianOrbit class).</p>
394      *
395      * @param M mean anomaly (rad)
396      * @param e eccentricity
397      * @return E the eccentric anomaly
398      */
399     private double getEccentricAnomaly(final double M, final double e) {
400 
401         // reduce M to [-PI PI) interval
402         final double reducedM = MathUtils.normalizeAngle(M, 0.0);
403 
404         // compute start value according to A. W. Odell and R. H. Gooding S12 starter
405         double E;
406         if (FastMath.abs(reducedM) < 1.0 / 6.0) {
407             E = reducedM + e * (FastMath.cbrt(6 * reducedM) - reducedM);
408         } else {
409             if (reducedM < 0) {
410                 final double w = FastMath.PI + reducedM;
411                 E = reducedM + e * (A * w / (B - w) - FastMath.PI - reducedM);
412             } else {
413                 final double w = FastMath.PI - reducedM;
414                 E = reducedM + e * (FastMath.PI - A * w / (B - w) - reducedM);
415             }
416         }
417 
418         final double e1 = 1 - e;
419         final boolean noCancellationRisk = (e1 + E * E / 6) >= 0.1;
420 
421         // perform two iterations, each consisting of one Halley step and one Newton-Raphson step
422         for (int j = 0; j < 2; ++j) {
423             final double f;
424             double fd;
425             final SinCos scE  = FastMath.sinCos(E);
426             final double fdd  = e * scE.sin();
427             final double fddd = e * scE.cos();
428             if (noCancellationRisk) {
429                 f  = (E - fdd) - reducedM;
430                 fd = 1 - fddd;
431             } else {
432                 f  = eMeSinE(E, e) - reducedM;
433                 final double s = FastMath.sin(0.5 * E);
434                 fd = e1 + 2 * e * s * s;
435             }
436             final double dee = f * fd / (0.5 * f * fdd - fd * fd);
437 
438             // update eccentric anomaly, using expressions that limit underflow problems
439             final double w = fd + 0.5 * dee * (fdd + dee * fddd / 3);
440             fd += dee * (fdd + 0.5 * dee * fddd);
441             E  -= (f - dee * (fd - w)) / fd;
442 
443         }
444 
445         // expand the result back to original range
446         E += M - reducedM;
447 
448         return E;
449 
450     }
451 
452     /**
453      * Accurate computation of E - e sin(E).
454      *
455      * @param E eccentric anomaly
456      * @param e eccentricity
457      * @return E - e sin(E)
458      */
459     private static double eMeSinE(final double E, final double e) {
460         double x = (1 - e) * FastMath.sin(E);
461         final double mE2 = -E * E;
462         double term = E;
463         double d    = 0;
464         // the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
465         for (double x0 = Double.NaN; !Double.valueOf(x).equals(x0);) {
466             d += 2;
467             term *= mE2 / (d * (d + 1));
468             x0 = x;
469             x = x - term;
470         }
471         return x;
472     }
473 
474     /**
475      * Get true anomaly from eccentric anomaly and eccentricity.
476      *
477      * @param ek the eccentric anomaly (rad)
478      * @param ecc the eccentricity
479      * @return the true anomaly (rad)
480      */
481     private double getTrueAnomaly(final double ek, final double ecc) {
482         final SinCos scek = FastMath.sinCos(ek);
483         final double svk  = FastMath.sqrt(1. - ecc * ecc) * scek.sin();
484         final double cvk  = scek.cos() - ecc;
485         return FastMath.atan2(svk, cvk);
486     }
487 
488     /**
489      * This method transforms the PV coordinates obtained after the numerical
490      * integration in the ECEF PZ-90.
491      *
492      * @param state spacecraft state after integration
493      * @return the PV coordinates in the ECEF PZ-90.
494      */
495     private PVCoordinates getPVInPZ90(final SpacecraftState state) {
496 
497         // Compute time difference between start date and end date
498         final double dt = state.getDate().durationFrom(initDate.getDate());
499 
500         // Position and velocity vectors
501         final PVCoordinates pv = state.getPVCoordinates();
502         final Vector3D pos = pv.getPosition();
503         final Vector3D vel = pv.getVelocity();
504 
505         // Components of position and velocity vectors
506         final double x0 = pos.getX();
507         final double y0 = pos.getY();
508         final double z0 = pos.getZ();
509         final double vx0 = vel.getX();
510         final double vy0 = vel.getY();
511         final double vz0 = vel.getZ();
512 
513         // Greenwich Mean Sidereal Time (GMST)
514         final GLONASSDate gloDate = new GLONASSDate(
515                 state.getDate(),
516                 dataContext.getTimeScales().getGLONASS());
517         final double gmst = gloDate.getGMST();
518 
519         final double ti = glonassOrbit.getTime() + dt;
520         // We use the GMST instead of the GMT as it is recommended into GLONASS ICD (2016)
521         final double s = gmst + GLONASS_AV * (ti - 10800.);
522 
523         // Commons Parameters
524         final SinCos scS  = FastMath.sinCos(s);
525         final double cosS = scS.cos();
526         final double sinS = scS.sin();
527 
528         // Transformed coordinates
529         final double x = x0 * cosS + y0 * sinS;
530         final double y = -x0 * sinS + y0 * cosS;
531         final double z = z0;
532         final double vx = vx0 * cosS + vy0 * sinS + GLONASS_AV * y;
533         final double vy = -vx0 * sinS + vy0 * cosS - GLONASS_AV * x;
534         final double vz = vz0;
535 
536         // Transformed orbit
537         return new PVCoordinates(new Vector3D(x, y, z),
538                                  new Vector3D(vx, vy, vz));
539     }
540 
541     /**
542      * This method computes the PV coordinates of the spacecraft center of mass.
543      * The returned PV are expressed in inertial coordinates system at the instant tb.
544      *
545      * @param date the computation date in GLONASS scale
546      * @return the PV Coordinates in inertial coordinates system
547      */
548     private PVCoordinates getPVInInertial(final GLONASSDate date) {
549 
550         // Greenwich Mean Sidereal Time (GMST)
551         final double gmst = date.getGMST();
552 
553         final double time = glonassOrbit.getTime();
554         final double dt   = time - 10800.;
555         // We use the GMST instead of the GMT as it is recommended into GLONASS ICD (2016)
556         final double s = gmst + GLONASS_AV * dt;
557 
558         // Commons Parameters
559         final SinCos scS  = FastMath.sinCos(s);
560         final double cosS = scS.cos();
561         final double sinS = scS.sin();
562 
563         // PV coordinates in inertial frame
564         final double x0  = glonassOrbit.getX() * cosS - glonassOrbit.getY() * sinS;
565         final double y0  = glonassOrbit.getX() * sinS + glonassOrbit.getY() * cosS;
566         final double z0  = glonassOrbit.getZ();
567         final double vx0 = glonassOrbit.getXDot() * cosS - glonassOrbit.getYDot() * sinS - GLONASS_AV * y0;
568         final double vy0 = glonassOrbit.getXDot() * sinS + glonassOrbit.getYDot() * cosS + GLONASS_AV * x0;
569         final double vz0 = glonassOrbit.getZDot();
570         return new PVCoordinates(new Vector3D(x0, y0, z0),
571                                  new Vector3D(vx0, vy0, vz0));
572     }
573 
574     @Override
575     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
576                                        final OrbitType orbitType, final PositionAngleType positionAngleType,
577                                        final AttitudeProvider attitudeProvider, final Frame frame) {
578         return new Mapper(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
579     }
580 
581     /** Internal mapper. */
582     private static class Mapper extends StateMapper {
583 
584         /**
585          * Simple constructor.
586          *
587          * @param referenceDate reference date
588          * @param mu central attraction coefficient (m³/s²)
589          * @param orbitType orbit type to use for mapping
590          * @param positionAngleType angle type to use for propagation
591          * @param attitudeProvider attitude provider
592          * @param frame inertial frame
593          */
594         Mapper(final AbsoluteDate referenceDate, final double mu,
595                final OrbitType orbitType, final PositionAngleType positionAngleType,
596                final AttitudeProvider attitudeProvider, final Frame frame) {
597             super(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
598         }
599 
600         @Override
601         public SpacecraftState mapArrayToState(final AbsoluteDate date, final double[] y,
602                                                final double[] yDot, final PropagationType type) {
603             // The parameter meanOnly is ignored for the GLONASS Propagator
604             final double mass = y[6];
605             if (mass <= 0.0) {
606                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
607             }
608 
609             final Orbit orbit       = getOrbitType().mapArrayToOrbit(y, yDot, getPositionAngleType(), date, getMu(), getFrame());
610             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());
611 
612             return new SpacecraftState(orbit, attitude).withMass(mass);
613         }
614 
615         @Override
616         public void mapStateToArray(final SpacecraftState state, final double[] y,
617                                     final double[] yDot) {
618             getOrbitType().mapOrbitToArray(state.getOrbit(), getPositionAngleType(), y, yDot);
619             y[6] = state.getMass();
620         }
621 
622     }
623 
624     @Override
625     protected MainStateEquations getMainStateEquations(final ODEIntegrator integ) {
626         return new Main();
627     }
628 
629     /** Internal class for orbital parameters integration. */
630     private class Main implements MainStateEquations {
631 
632         /** Derivatives array. */
633         private final double[] yDot;
634 
635         /**
636          * Simple constructor.
637          */
638         Main() {
639             yDot = new double[7];
640         }
641 
642         @Override
643         public double[] computeDerivatives(final SpacecraftState state) {
644 
645             // Date in Glonass form
646             final GLONASSDate gloDate = new GLONASSDate(
647                     state.getDate(),
648                     dataContext.getTimeScales().getGLONASS());
649 
650             // Position and Velocity vectors
651             final Vector3D vel = state.getVelocity();
652             final Vector3D pos = state.getPosition();
653 
654             Arrays.fill(yDot, 0.0);
655 
656             // dPos/dt = Vel
657             yDot[0] += vel.getX();
658             yDot[1] += vel.getY();
659             yDot[2] += vel.getZ();
660 
661             // Components of position and velocity vectors
662             final double x0 = pos.getX();
663             final double y0 = pos.getY();
664             final double z0 = pos.getZ();
665 
666             // Normalized values
667             final double r  = pos.getNorm();
668             final double r2 = r * r;
669             final double oor = 1. / r;
670             final double oor2 = 1. / r2;
671             final double x = x0 * oor;
672             final double y = y0 * oor;
673             final double z = z0 * oor;
674             final double g = GNSSConstants.GLONASS_MU * oor2;
675             final double ro = GLONASS_EARTH_EQUATORIAL_RADIUS * oor;
676 
677             yDot[3] += x * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (1. - 5. * z * z)));
678             yDot[4] += y * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (1. - 5. * z * z)));
679             yDot[5] += z * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (3. - 5. * z * z)));
680 
681             // Luni-Solar contribution
682             final Vector3D acc;
683             if (isAccAvailable) {
684                 acc = getLuniSolarPerturbations(gloDate);
685             } else {
686                 final Vector3D accMoon = computeLuniSolarPerturbations(
687                         state, moonElements[0], moonElements[1], moonElements[2],
688                         moonElements[3],
689                         dataContext.getCelestialBodies().getMoon().getGM());
690                 final Vector3D accSun = computeLuniSolarPerturbations(
691                         state,
692                         sunElements[0], sunElements[1], sunElements[2],
693                         sunElements[3],
694                         dataContext.getCelestialBodies().getSun().getGM());
695                 acc = accMoon.add(accSun);
696             }
697 
698             yDot[3] += acc.getX();
699             yDot[4] += acc.getY();
700             yDot[5] += acc.getZ();
701 
702             return yDot.clone();
703         }
704 
705         /**
706          * This method computes the accelerations induced by gravitational
707          * perturbations of the Sun and the Moon if they are not available into
708          * the navigation message data.
709          *
710          * @param state current state
711          * @param eps first direction cosine
712          * @param eta second direction cosine
713          * @param psi third direction cosine
714          * @param r distance of perturbing body
715          * @param g body gravitational field constant
716          * @return a vector containing the accelerations
717          */
718         private Vector3D computeLuniSolarPerturbations(final SpacecraftState state, final double eps,
719                                                        final double eta, final double psi,
720                                                        final double r, final double g) {
721 
722             // Current pv coordinates
723             final PVCoordinates pv = state.getPVCoordinates();
724 
725             final double oor = 1. / r;
726             final double oor2 = oor * oor;
727 
728             // Normalized variable
729             final double x = pv.getPosition().getX() * oor;
730             final double y = pv.getPosition().getY() * oor;
731             final double z = pv.getPosition().getZ() * oor;
732             final double gm = g * oor2;
733 
734             final double epsmX  = eps - x;
735             final double etamY  = eta - y;
736             final double psimZ  = psi - z;
737             final Vector3D vector = new Vector3D(epsmX, etamY, psimZ);
738             final double d2 = vector.getNorm2Sq();
739             final double deltaM = FastMath.sqrt(d2) * d2;
740 
741             // Accelerations
742             final double accX = gm * ((epsmX / deltaM) - eps);
743             final double accY = gm * ((etamY / deltaM) - eta);
744             final double accZ = gm * ((psimZ / deltaM) - psi);
745 
746             return new Vector3D(accX, accY, accZ);
747         }
748 
749         /**
750          * Get the accelerations induced by gravitational
751          * perturbations of the Sun and the Moon in a geocentric
752          * coordinate system.
753          * <p>
754          * The accelerations are obtained using projections of accelerations
755          * transmitted within navigation message data.
756          * </p>
757          *
758          * @param date the computation date in GLONASS scale
759          * @return a vector containing the sum of both accelerations
760          */
761         private Vector3D getLuniSolarPerturbations(final GLONASSDate date) {
762 
763             // Greenwich Mean Sidereal Time (GMST)
764             final double gmst = date.getGMST();
765 
766             final double time = glonassOrbit.getTime();
767             final double dt   = time - 10800.;
768             // We use the GMST instead of the GMT as it is recommended into GLONASS ICD (see Ref)
769             final double s = gmst + GLONASS_AV * dt;
770 
771             // Commons Parameters
772             final SinCos scS  = FastMath.sinCos(s);
773             final double cosS = scS.cos();
774             final double sinS = scS.sin();
775 
776             // Accelerations
777             final double accX = glonassOrbit.getXDotDot() * cosS - glonassOrbit.getYDotDot() * sinS;
778             final double accY = glonassOrbit.getXDotDot() * sinS + glonassOrbit.getYDotDot() * cosS;
779             final double accZ = glonassOrbit.getZDotDot();
780 
781             return new Vector3D(accX, accY, accZ);
782         }
783 
784     }
785 
786 }