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