GLONASSNumericalPropagator.java

  1. /* Copyright 2002-2024 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. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  19. import org.hipparchus.ode.ODEIntegrator;
  20. import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.MathUtils;
  23. import org.hipparchus.util.SinCos;
  24. import org.orekit.attitudes.Attitude;
  25. import org.orekit.attitudes.AttitudeProvider;
  26. import org.orekit.data.DataContext;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.frames.Frame;
  30. import org.orekit.orbits.CartesianOrbit;
  31. import org.orekit.orbits.Orbit;
  32. import org.orekit.orbits.OrbitType;
  33. import org.orekit.orbits.PositionAngleType;
  34. import org.orekit.propagation.PropagationType;
  35. import org.orekit.propagation.SpacecraftState;
  36. import org.orekit.propagation.analytical.gnss.data.GLONASSAlmanac;
  37. import org.orekit.propagation.analytical.gnss.data.GLONASSNavigationMessage;
  38. import org.orekit.propagation.analytical.gnss.data.GLONASSOrbitalElements;
  39. import org.orekit.propagation.analytical.gnss.data.GNSSConstants;
  40. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  41. import org.orekit.propagation.integration.StateMapper;
  42. import org.orekit.time.AbsoluteDate;
  43. import org.orekit.time.GLONASSDate;
  44. import org.orekit.utils.AbsolutePVCoordinates;
  45. import org.orekit.utils.Constants;
  46. import org.orekit.utils.IERSConventions;
  47. import org.orekit.utils.PVCoordinates;
  48. import org.orekit.utils.TimeStampedPVCoordinates;

  49. import java.util.Arrays;

  50. /**
  51.  * This class propagates GLONASS orbits using numerical integration.
  52.  * <p>
  53.  * As recommended by the GLONASS Interface Control Document (ICD),
  54.  * a {@link ClassicalRungeKuttaIntegrator  4th order Runge-Kutta technique}
  55.  * shall be used to integrate the equations.
  56.  * </p>
  57.  * <p>
  58.  * Classical used of this orbit propagator is to compute GLONASS satellite
  59.  * coordinates from the navigation message.
  60.  * </p>
  61.  * <p>
  62.  * If the projections of luni-solar accelerations to axes of
  63.  * Greenwich geocentric coordinates {@link GLONASSOrbitalElements#getXDotDot() X''(tb)},
  64.  * {@link GLONASSOrbitalElements#getYDotDot() Y''(tb)} and {@link GLONASSOrbitalElements#getZDotDot() Z''(tb)}
  65.  * are available in the navigation message; a transformation is performed to convert these
  66.  * accelerations into the correct coordinate system. In the case where they are not
  67.  * available into the navigation message, these accelerations are computed.
  68.  * </p>
  69.  * <p>
  70.  * <b>Caution:</b> The Glonass numerical propagator can only be used with {@link GLONASSNavigationMessage}.
  71.  * Using this propagator with a {@link GLONASSAlmanac} is prone to error.
  72.  * </p>
  73.  *
  74.  * @see <a href="http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf">
  75.  *       GLONASS Interface Control Document</a>
  76.  *
  77.  * @author Bryan Cazabonne
  78.  *
  79.  */
  80. public class GLONASSNumericalPropagator extends AbstractIntegratedPropagator {

  81.     /** Second degree coefficient of normal potential. */
  82.     private static final double GLONASS_J20 = 1.08262575e-3;

  83.     /** Equatorial radius of Earth (m). */
  84.     private static final double GLONASS_EARTH_EQUATORIAL_RADIUS = 6378136;

  85.     /** Value of the Earth's rotation rate in rad/s (See Ref). */
  86.     private static final double GLONASS_AV = 7.2921151467e-5;

  87.     // Data used to solve Kepler's equation
  88.     /** First coefficient to compute Kepler equation solver starter. */
  89.     private static final double A;

  90.     /** Second coefficient to compute Kepler equation solver starter. */
  91.     private static final double B;

  92.     static {
  93.         final double k1 = 3 * FastMath.PI + 2;
  94.         final double k2 = FastMath.PI - 1;
  95.         final double k3 = 6 * FastMath.PI - 1;
  96.         A  = 3 * k2 * k2 / k1;
  97.         B  = k3 * k3 / (6 * k1);
  98.     }

  99.     /** The GLONASS orbital elements used. */
  100.     private final GLONASSOrbitalElements glonassOrbit;

  101.     /** Initial date in GLONASS form. */
  102.     private final GLONASSDate initDate;

  103.     /** The spacecraft mass (kg). */
  104.     private final double mass;

  105.     /** The ECI frame used for GLONASS propagation. */
  106.     private final Frame eci;

  107.     /** Direction cosines and distance of perturbing body: Moon.
  108.      * <p>
  109.      * <ul>
  110.      * <li>double[0] = ξ<sub>m</sub></li>
  111.      * <li>double[1] = η<sub>m</sub></li>
  112.      * <li>double[2] = ψ<sub>m</sub></li>
  113.      * <li>double[3] = r<sub>m</sub></li>
  114.      * </ul>
  115.      * </p>
  116.      */
  117.     private double[] moonElements;

  118.     /** Direction cosines and distance of perturbing body: Sun.
  119.      * <p>
  120.      * <ul>
  121.      * <li>double[0] = ξ<sub>s</sub></li>
  122.      * <li>double[1] = η<sub>s</sub></li>
  123.      * <li>double[2] = ψ<sub>s</sub></li>
  124.      * <li>double[3] = r<sub>s</sub></li>
  125.      * </ul>
  126.      * </p>
  127.      */
  128.     private double[] sunElements;

  129.     /** Flag for availability of projections of acceleration transmitted within the navigation message. */
  130.     private final boolean isAccAvailable;

  131.     /** Data context used for propagation. */
  132.     private final DataContext dataContext;

  133.     /**
  134.      * Private constructor.
  135.      * @param integrator Runge-Kutta integrator
  136.      * @param glonassOrbit Glonass orbital elements
  137.      * @param eci Earth Centered Inertial frame
  138.      * @param provider Attitude provider
  139.      * @param mass Satellite mass (kg)
  140.      * @param context Data context
  141.      * @param isAccAvailable true if the acceleration  is transmitted within the navigation message
  142.      */
  143.     public GLONASSNumericalPropagator(final ClassicalRungeKuttaIntegrator integrator,
  144.                                       final GLONASSOrbitalElements glonassOrbit,
  145.                                       final Frame eci, final AttitudeProvider provider,
  146.                                       final double mass, final DataContext context,
  147.                                       final boolean isAccAvailable) {
  148.         super(integrator, PropagationType.OSCULATING);
  149.         this.dataContext = context;
  150.         this.isAccAvailable = isAccAvailable;
  151.         // Stores the GLONASS orbital elements
  152.         this.glonassOrbit = glonassOrbit;
  153.         // Sets the Earth Centered Inertial frame
  154.         this.eci = eci;
  155.         // Sets the mass
  156.         this.mass = mass;
  157.         this.initDate = new GLONASSDate(
  158.                 glonassOrbit.getDate(),
  159.                 dataContext.getTimeScales().getGLONASS());

  160.         // Initialize state mapper
  161.         initMapper();
  162.         setInitialState();
  163.         setAttitudeProvider(provider);
  164.         setOrbitType(OrbitType.CARTESIAN);
  165.         // It is not meaningful for propagation in Cartesian parameters
  166.         setPositionAngleType(PositionAngleType.TRUE);
  167.         setMu(GNSSConstants.GLONASS_MU);

  168.         // As recommended by GLONASS ICD (2016), the direction cosines and distance
  169.         // of perturbing body are calculated one time (at tb).
  170.         if (!isAccAvailable) {
  171.             computeMoonElements(initDate);
  172.             computeSunElements(initDate);
  173.         }
  174.     }

  175.     /**
  176.      * Gets the underlying GLONASS orbital elements.
  177.      *
  178.      * @return the underlying GLONASS orbital elements
  179.      */
  180.     public GLONASSOrbitalElements getGLONASSOrbitalElements() {
  181.         return glonassOrbit;
  182.     }

  183.     /** {@inheritDoc} */
  184.     @Override
  185.     public SpacecraftState propagate(final AbsoluteDate date) {
  186.         // Spacecraft state in inertial frame
  187.         final SpacecraftState stateInInertial = super.propagate(date);

  188.         // Build the spacecraft state in inertial frame
  189.         final PVCoordinates pvInPZ90 = getPVInPZ90(stateInInertial);
  190.         final AbsolutePVCoordinates absPV = new AbsolutePVCoordinates(
  191.                 dataContext.getFrames().getPZ9011(IERSConventions.IERS_2010, true),
  192.                 stateInInertial.getDate(), pvInPZ90);
  193.         final TimeStampedPVCoordinates pvInInertial = absPV.getPVCoordinates(eci);
  194.         return new SpacecraftState(new CartesianOrbit(pvInInertial, eci, pvInInertial.getDate(), GNSSConstants.GLONASS_MU),
  195.                                                       stateInInertial.getAttitude(),
  196.                                                       stateInInertial.getMass(),
  197.                                                       stateInInertial.getAdditionalStatesValues(),
  198.                                                       stateInInertial.getAdditionalStatesDerivatives());
  199.     }

  200.     /**
  201.      * Set the initial state.
  202.      * <p>
  203.      * The initial conditions on position and velocity are in the ECEF coordinate system PZ-90.
  204.      * Previous to orbit integration, they must be transformed to an absolute inertial coordinate system.
  205.      * </p>
  206.      */
  207.     private void setInitialState() {

  208.         // Transform initial PV coordinates to an absolute inertial coordinate system.
  209.         final PVCoordinates pvInInertial = getPVInInertial(initDate);

  210.         // Create a new orbit
  211.         final Orbit orbit = new CartesianOrbit(pvInInertial,
  212.                                                eci, initDate.getDate(),
  213.                                                GNSSConstants.GLONASS_MU);

  214.         // Reset the initial state to apply the transformation
  215.         resetInitialState(new SpacecraftState(orbit, mass));
  216.     }

  217.     /**
  218.      * This method computes the direction cosines and the distance used to
  219.      * compute the gravitational perturbations of the Moon.
  220.      *
  221.      * @param date the computation date in GLONASS scale
  222.      */
  223.     private void computeMoonElements(final GLONASSDate date) {

  224.         moonElements = new double[4];

  225.         // Constants
  226.         // Semi-major axis of the Moon's orbit (m)
  227.         final double am = 3.84385243e8;
  228.         // The Moon's orbit eccentricity
  229.         final double em = 0.054900489;
  230.         // Mean inclination of the Moon's orbit to the ecliptic (rad)
  231.         final double im = 0.0898041080;

  232.         // Computed parameters
  233.         // Time from epoch 2000 to the instant tb of GLONASS ephemeris broadcast
  234.         final double dtoJD = (glonassOrbit.getTime() - 10800.) / Constants.JULIAN_DAY;
  235.         final double t  = (date.getJD0() + dtoJD - 2451545.0) / 36525;
  236.         final double t2 = t * t;
  237.         // Mean inclination of Earth equator to ecliptic (rad)
  238.         final double eps = 0.4090926006 - 0.0002270711 * t;
  239.         // Mean longitude of the Moon's orbit perigee (rad)
  240.         final double gammaM = 1.4547885346 + 71.0176852437 * t - 0.0001801481 * t2;
  241.         // Mean longitude of the ascending node of the Moon (rad)
  242.         final double omegaM = 2.1824391966 - 33.7570459536 * t + 0.0000362262 * t2;
  243.         // Mean anomaly of the Moon (rad)
  244.         final double qm = 2.3555557435 + 8328.6914257190 * t + 0.0001545547 * t2;

  245.         // Commons parameters
  246.         final SinCos scOm  = FastMath.sinCos(omegaM);
  247.         final SinCos scIm  = FastMath.sinCos(im);
  248.         final SinCos scEs  = FastMath.sinCos(eps);
  249.         final SinCos scGm  = FastMath.sinCos(gammaM);
  250.         final double cosOm = scOm.cos();
  251.         final double sinOm = scOm.sin();
  252.         final double cosIm = scIm.cos();
  253.         final double sinIm = scIm.sin();
  254.         final double cosEs = scEs.cos();
  255.         final double sinEs = scEs.sin();
  256.         final double cosGm = scGm.cos();
  257.         final double sinGm = scGm.sin();

  258.         // Intermediate parameters
  259.         final double psiStar = cosOm * sinIm;
  260.         final double etaStar = sinOm * sinIm;
  261.         final double epsStar = 1. - cosOm * cosOm * (1. - cosIm);
  262.         final double eps11 = sinOm * cosOm * (1. - cosIm);
  263.         final double eps12 = 1. - sinOm * sinOm * (1. - cosIm);
  264.         final double eta11 = epsStar * cosEs - psiStar * sinEs;
  265.         final double eta12 = eps11 * cosEs + etaStar * sinEs;
  266.         final double psi11 = epsStar * sinEs + psiStar * cosEs;
  267.         final double psi12 = eps11 * sinEs - etaStar * cosEs;

  268.         // Eccentric Anomaly
  269.         final double ek = getEccentricAnomaly(qm, em);

  270.         // True Anomaly
  271.         final double vk    = getTrueAnomaly(ek, em);
  272.         final SinCos scVk  = FastMath.sinCos(vk);
  273.         final double sinVk = scVk.sin();
  274.         final double cosVk = scVk.cos();

  275.         // Direction cosine
  276.         final double epsM = eps11 * (sinVk * cosGm + cosVk * sinGm) + eps12 * (cosVk * cosGm - sinVk * sinGm);
  277.         final double etaM = eta11 * (sinVk * cosGm + cosVk * sinGm) + eta12 * (cosVk * cosGm - sinVk * sinGm);
  278.         final double psiM = psi11 * (sinVk * cosGm + cosVk * sinGm) + psi12 * (cosVk * cosGm - sinVk * sinGm);

  279.         // Distance
  280.         final double rm = am * (1. - em * FastMath.cos(ek));

  281.         moonElements[0] = epsM;
  282.         moonElements[1] = etaM;
  283.         moonElements[2] = psiM;
  284.         moonElements[3] = rm;

  285.     }

  286.     /**
  287.      * This method computes the direction cosines and the distance used to
  288.      * compute the gravitational perturbations of the Sun.
  289.      *
  290.      * @param date the computation date in GLONASS scale
  291.      */
  292.     private void computeSunElements(final GLONASSDate date) {

  293.         sunElements = new double[4];

  294.         // Constants
  295.         //  Major semi-axis of the Earth’s orbit around the Sun (m)
  296.         final double as = 1.49598e11;
  297.         // The eccentricity of the Earth’s orbit around the Sun
  298.         final double es = 0.016719;

  299.         // Computed parameters
  300.         // Time from epoch 2000 to the instant tb of GLONASS ephemeris broadcast
  301.         final double dtoJD = (glonassOrbit.getTime() - 10800.) / Constants.JULIAN_DAY;
  302.         final double t  = (date.getJD0() + dtoJD - 2451545.0) / 36525;
  303.         final double t2 = t * t;
  304.         // Mean inclination of Earth equator to ecliptic (rad)
  305.         final double eps = 0.4090926006 - 0.0002270711 * t;
  306.         // Mean tropic longitude of the Sun orbit perigee (rad)
  307.         final double ws = -7.6281824375 + 0.0300101976 * t + 0.0000079741 * t2;
  308.         // Mean anomaly of the Sun (rad)
  309.         final double qs = 6.2400601269 + 628.3019551714 * t - 0.0000026820 * t2;

  310.         // Eccentric Anomaly
  311.         final double ek = getEccentricAnomaly(qs, es);

  312.         // True Anomaly
  313.         final double vk    =  getTrueAnomaly(ek, es);
  314.         final SinCos scVk  = FastMath.sinCos(vk);
  315.         final double sinVk = scVk.sin();
  316.         final double cosVk = scVk.cos();

  317.         // Commons parameters
  318.         final SinCos scWs  = FastMath.sinCos(ws);
  319.         final SinCos scEs  = FastMath.sinCos(eps);
  320.         final double cosWs = scWs.cos();
  321.         final double sinWs = scWs.sin();
  322.         final double cosEs = scEs.cos();
  323.         final double sinEs = scEs.sin();

  324.         // Direction cosine
  325.         final double epsS = cosVk * cosWs - sinVk * sinWs;
  326.         final double etaS = cosEs * (sinVk * cosWs + cosVk * sinWs);
  327.         final double psiS = sinEs * (sinVk * cosWs + cosVk * sinWs);

  328.         // Distance
  329.         final double rs = as * (1. - es * FastMath.cos(ek));

  330.         sunElements[0] = epsS;
  331.         sunElements[1] = etaS;
  332.         sunElements[2] = psiS;
  333.         sunElements[3] = rs;

  334.     }

  335.     /**
  336.      * Computes the elliptic eccentric anomaly from the mean anomaly.
  337.      * <p>
  338.      * The algorithm used here for solving Kepler equation has been published
  339.      * in: "Procedures for  solving Kepler's Equation", A. W. Odell and
  340.      * R. H. Gooding, Celestial Mechanics 38 (1986) 307-334
  341.      * </p>
  342.      * <p>It has been copied from the OREKIT library (KeplerianOrbit class).</p>
  343.      *
  344.      * @param M mean anomaly (rad)
  345.      * @param e eccentricity
  346.      * @return E the eccentric anomaly
  347.      */
  348.     private double getEccentricAnomaly(final double M, final double e) {

  349.         // reduce M to [-PI PI) interval
  350.         final double reducedM = MathUtils.normalizeAngle(M, 0.0);

  351.         // compute start value according to A. W. Odell and R. H. Gooding S12 starter
  352.         double E;
  353.         if (FastMath.abs(reducedM) < 1.0 / 6.0) {
  354.             E = reducedM + e * (FastMath.cbrt(6 * reducedM) - reducedM);
  355.         } else {
  356.             if (reducedM < 0) {
  357.                 final double w = FastMath.PI + reducedM;
  358.                 E = reducedM + e * (A * w / (B - w) - FastMath.PI - reducedM);
  359.             } else {
  360.                 final double w = FastMath.PI - reducedM;
  361.                 E = reducedM + e * (FastMath.PI - A * w / (B - w) - reducedM);
  362.             }
  363.         }

  364.         final double e1 = 1 - e;
  365.         final boolean noCancellationRisk = (e1 + E * E / 6) >= 0.1;

  366.         // perform two iterations, each consisting of one Halley step and one Newton-Raphson step
  367.         for (int j = 0; j < 2; ++j) {
  368.             final double f;
  369.             double fd;
  370.             final SinCos scE  = FastMath.sinCos(E);
  371.             final double fdd  = e * scE.sin();
  372.             final double fddd = e * scE.cos();
  373.             if (noCancellationRisk) {
  374.                 f  = (E - fdd) - reducedM;
  375.                 fd = 1 - fddd;
  376.             } else {
  377.                 f  = eMeSinE(E, e) - reducedM;
  378.                 final double s = FastMath.sin(0.5 * E);
  379.                 fd = e1 + 2 * e * s * s;
  380.             }
  381.             final double dee = f * fd / (0.5 * f * fdd - fd * fd);

  382.             // update eccentric anomaly, using expressions that limit underflow problems
  383.             final double w = fd + 0.5 * dee * (fdd + dee * fddd / 3);
  384.             fd += dee * (fdd + 0.5 * dee * fddd);
  385.             E  -= (f - dee * (fd - w)) / fd;

  386.         }

  387.         // expand the result back to original range
  388.         E += M - reducedM;

  389.         return E;

  390.     }

  391.     /**
  392.      * Accurate computation of E - e sin(E).
  393.      *
  394.      * @param E eccentric anomaly
  395.      * @param e eccentricity
  396.      * @return E - e sin(E)
  397.      */
  398.     private static double eMeSinE(final double E, final double e) {
  399.         double x = (1 - e) * FastMath.sin(E);
  400.         final double mE2 = -E * E;
  401.         double term = E;
  402.         double d    = 0;
  403.         // the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
  404.         for (double x0 = Double.NaN; !Double.valueOf(x).equals(x0);) {
  405.             d += 2;
  406.             term *= mE2 / (d * (d + 1));
  407.             x0 = x;
  408.             x = x - term;
  409.         }
  410.         return x;
  411.     }

  412.     /**
  413.      * Get true anomaly from eccentric anomaly and eccentricity.
  414.      *
  415.      * @param ek the eccentric anomaly (rad)
  416.      * @param ecc the eccentricity
  417.      * @return the true anomaly (rad)
  418.      */
  419.     private double getTrueAnomaly(final double ek, final double ecc) {
  420.         final SinCos scek = FastMath.sinCos(ek);
  421.         final double svk  = FastMath.sqrt(1. - ecc * ecc) * scek.sin();
  422.         final double cvk  = scek.cos() - ecc;
  423.         return FastMath.atan2(svk, cvk);
  424.     }

  425.     /**
  426.      * This method transforms the PV coordinates obtained after the numerical
  427.      * integration in the ECEF PZ-90.
  428.      *
  429.      * @param state spacecraft state after integration
  430.      * @return the PV coordinates in the ECEF PZ-90.
  431.      */
  432.     private PVCoordinates getPVInPZ90(final SpacecraftState state) {

  433.         // Compute time difference between start date and end date
  434.         final double dt = state.getDate().durationFrom(initDate.getDate());

  435.         // Position and velocity vectors
  436.         final PVCoordinates pv = state.getPVCoordinates();
  437.         final Vector3D pos = pv.getPosition();
  438.         final Vector3D vel = pv.getVelocity();

  439.         // Components of position and velocity vectors
  440.         final double x0 = pos.getX();
  441.         final double y0 = pos.getY();
  442.         final double z0 = pos.getZ();
  443.         final double vx0 = vel.getX();
  444.         final double vy0 = vel.getY();
  445.         final double vz0 = vel.getZ();

  446.         // Greenwich Mean Sidereal Time (GMST)
  447.         final GLONASSDate gloDate = new GLONASSDate(
  448.                 state.getDate(),
  449.                 dataContext.getTimeScales().getGLONASS());
  450.         final double gmst = gloDate.getGMST();

  451.         final double ti = glonassOrbit.getTime() + dt;
  452.         // We use the GMST instead of the GMT as it is recommended into GLONASS ICD (2016)
  453.         final double s = gmst + GLONASS_AV * (ti - 10800.);

  454.         // Commons Parameters
  455.         final SinCos scS  = FastMath.sinCos(s);
  456.         final double cosS = scS.cos();
  457.         final double sinS = scS.sin();

  458.         // Transformed coordinates
  459.         final double x = x0 * cosS + y0 * sinS;
  460.         final double y = -x0 * sinS + y0 * cosS;
  461.         final double z = z0;
  462.         final double vx = vx0 * cosS + vy0 * sinS + GLONASS_AV * y;
  463.         final double vy = -vx0 * sinS + vy0 * cosS - GLONASS_AV * x;
  464.         final double vz = vz0;

  465.         // Transformed orbit
  466.         return new PVCoordinates(new Vector3D(x, y, z),
  467.                                  new Vector3D(vx, vy, vz));
  468.     }

  469.     /**
  470.      * This method computes the PV coordinates of the spacecraft center of mass.
  471.      * The returned PV are expressed in inertial coordinates system at the instant tb.
  472.      *
  473.      * @param date the computation date in GLONASS scale
  474.      * @return the PV Coordinates in inertial coordinates system
  475.      */
  476.     private PVCoordinates getPVInInertial(final GLONASSDate date) {

  477.         // Greenwich Mean Sidereal Time (GMST)
  478.         final double gmst = date.getGMST();

  479.         final double time = glonassOrbit.getTime();
  480.         final double dt   = time - 10800.;
  481.         // We use the GMST instead of the GMT as it is recommended into GLONASS ICD (2016)
  482.         final double s = gmst + GLONASS_AV * dt;

  483.         // Commons Parameters
  484.         final SinCos scS  = FastMath.sinCos(s);
  485.         final double cosS = scS.cos();
  486.         final double sinS = scS.sin();

  487.         // PV coordinates in inertial frame
  488.         final double x0  = glonassOrbit.getX() * cosS - glonassOrbit.getY() * sinS;
  489.         final double y0  = glonassOrbit.getX() * sinS + glonassOrbit.getY() * cosS;
  490.         final double z0  = glonassOrbit.getZ();
  491.         final double vx0 = glonassOrbit.getXDot() * cosS - glonassOrbit.getYDot() * sinS - GLONASS_AV * y0;
  492.         final double vy0 = glonassOrbit.getXDot() * sinS + glonassOrbit.getYDot() * cosS + GLONASS_AV * x0;
  493.         final double vz0 = glonassOrbit.getZDot();
  494.         return new PVCoordinates(new Vector3D(x0, y0, z0),
  495.                                  new Vector3D(vx0, vy0, vz0));
  496.     }

  497.     @Override
  498.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  499.                                        final OrbitType orbitType, final PositionAngleType positionAngleType,
  500.                                        final AttitudeProvider attitudeProvider, final Frame frame) {
  501.         return new Mapper(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
  502.     }

  503.     /** Internal mapper. */
  504.     private static class Mapper extends StateMapper {

  505.         /**
  506.          * Simple constructor.
  507.          *
  508.          * @param referenceDate reference date
  509.          * @param mu central attraction coefficient (m³/s²)
  510.          * @param orbitType orbit type to use for mapping
  511.          * @param positionAngleType angle type to use for propagation
  512.          * @param attitudeProvider attitude provider
  513.          * @param frame inertial frame
  514.          */
  515.         Mapper(final AbsoluteDate referenceDate, final double mu,
  516.                final OrbitType orbitType, final PositionAngleType positionAngleType,
  517.                final AttitudeProvider attitudeProvider, final Frame frame) {
  518.             super(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
  519.         }

  520.         @Override
  521.         public SpacecraftState mapArrayToState(final AbsoluteDate date, final double[] y,
  522.                                                final double[] yDot, final PropagationType type) {
  523.             // The parameter meanOnly is ignored for the GLONASS Propagator
  524.             final double mass = y[6];
  525.             if (mass <= 0.0) {
  526.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
  527.             }

  528.             final Orbit orbit       = getOrbitType().mapArrayToOrbit(y, yDot, getPositionAngleType(), date, getMu(), getFrame());
  529.             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());

  530.             return new SpacecraftState(orbit, attitude, mass);
  531.         }

  532.         @Override
  533.         public void mapStateToArray(final SpacecraftState state, final double[] y,
  534.                                     final double[] yDot) {
  535.             getOrbitType().mapOrbitToArray(state.getOrbit(), getPositionAngleType(), y, yDot);
  536.             y[6] = state.getMass();
  537.         }

  538.     }

  539.     @Override
  540.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integ) {
  541.         return new Main();
  542.     }

  543.     /** Internal class for orbital parameters integration. */
  544.     private class Main implements MainStateEquations {

  545.         /** Derivatives array. */
  546.         private final double[] yDot;

  547.         /**
  548.          * Simple constructor.
  549.          */
  550.         Main() {
  551.             yDot = new double[7];
  552.         }

  553.         @Override
  554.         public double[] computeDerivatives(final SpacecraftState state) {

  555.             // Date in Glonass form
  556.             final GLONASSDate gloDate = new GLONASSDate(
  557.                     state.getDate(),
  558.                     dataContext.getTimeScales().getGLONASS());

  559.             // Position and Velocity vectors
  560.             final Vector3D vel = state.getPVCoordinates().getVelocity();
  561.             final Vector3D pos = state.getPosition();

  562.             Arrays.fill(yDot, 0.0);

  563.             // dPos/dt = Vel
  564.             yDot[0] += vel.getX();
  565.             yDot[1] += vel.getY();
  566.             yDot[2] += vel.getZ();

  567.             // Components of position and velocity vectors
  568.             final double x0 = pos.getX();
  569.             final double y0 = pos.getY();
  570.             final double z0 = pos.getZ();

  571.             // Normalized values
  572.             final double r  = pos.getNorm();
  573.             final double r2 = r * r;
  574.             final double oor = 1. / r;
  575.             final double oor2 = 1. / r2;
  576.             final double x = x0 * oor;
  577.             final double y = y0 * oor;
  578.             final double z = z0 * oor;
  579.             final double g = GNSSConstants.GLONASS_MU * oor2;
  580.             final double ro = GLONASS_EARTH_EQUATORIAL_RADIUS * oor;

  581.             yDot[3] += x * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (1. - 5. * z * z)));
  582.             yDot[4] += y * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (1. - 5. * z * z)));
  583.             yDot[5] += z * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (3. - 5. * z * z)));

  584.             // Luni-Solar contribution
  585.             final Vector3D acc;
  586.             if (isAccAvailable) {
  587.                 acc = getLuniSolarPerturbations(gloDate);
  588.             } else {
  589.                 final Vector3D accMoon = computeLuniSolarPerturbations(
  590.                         state, moonElements[0], moonElements[1], moonElements[2],
  591.                         moonElements[3],
  592.                         dataContext.getCelestialBodies().getMoon().getGM());
  593.                 final Vector3D accSun = computeLuniSolarPerturbations(
  594.                         state,
  595.                         sunElements[0], sunElements[1], sunElements[2],
  596.                         sunElements[3],
  597.                         dataContext.getCelestialBodies().getSun().getGM());
  598.                 acc = accMoon.add(accSun);
  599.             }

  600.             yDot[3] += acc.getX();
  601.             yDot[4] += acc.getY();
  602.             yDot[5] += acc.getZ();

  603.             return yDot.clone();
  604.         }

  605.         /**
  606.          * This method computes the accelerations induced by gravitational
  607.          * perturbations of the Sun and the Moon if they are not available into
  608.          * the navigation message data.
  609.          *
  610.          * @param state current state
  611.          * @param eps first direction cosine
  612.          * @param eta second direction cosine
  613.          * @param psi third direction cosine
  614.          * @param r distance of perturbing body
  615.          * @param g body gravitational field constant
  616.          * @return a vector containing the accelerations
  617.          */
  618.         private Vector3D computeLuniSolarPerturbations(final SpacecraftState state, final double eps,
  619.                                                        final double eta, final double psi,
  620.                                                        final double r, final double g) {

  621.             // Current pv coordinates
  622.             final PVCoordinates pv = state.getPVCoordinates();

  623.             final double oor = 1. / r;
  624.             final double oor2 = oor * oor;

  625.             // Normalized variable
  626.             final double x = pv.getPosition().getX() * oor;
  627.             final double y = pv.getPosition().getY() * oor;
  628.             final double z = pv.getPosition().getZ() * oor;
  629.             final double gm = g * oor2;

  630.             final double epsmX  = eps - x;
  631.             final double etamY  = eta - y;
  632.             final double psimZ  = psi - z;
  633.             final Vector3D vector = new Vector3D(epsmX, etamY, psimZ);
  634.             final double d2 = vector.getNormSq();
  635.             final double deltaM = FastMath.sqrt(d2) * d2;

  636.             // Accelerations
  637.             final double accX = gm * ((epsmX / deltaM) - eps);
  638.             final double accY = gm * ((etamY / deltaM) - eta);
  639.             final double accZ = gm * ((psimZ / deltaM) - psi);

  640.             return new Vector3D(accX, accY, accZ);
  641.         }

  642.         /**
  643.          * Get the accelerations induced by gravitational
  644.          * perturbations of the Sun and the Moon in a geocentric
  645.          * coordinate system.
  646.          * <p>
  647.          * The accelerations are obtained using projections of accelerations
  648.          * transmitted within navigation message data.
  649.          * </p>
  650.          *
  651.          * @param date the computation date in GLONASS scale
  652.          * @return a vector containing the sum of both accelerations
  653.          */
  654.         private Vector3D getLuniSolarPerturbations(final GLONASSDate date) {

  655.             // Greenwich Mean Sidereal Time (GMST)
  656.             final double gmst = date.getGMST();

  657.             final double time = glonassOrbit.getTime();
  658.             final double dt   = time - 10800.;
  659.             // We use the GMST instead of the GMT as it is recommended into GLONASS ICD (see Ref)
  660.             final double s = gmst + GLONASS_AV * dt;

  661.             // Commons Parameters
  662.             final SinCos scS  = FastMath.sinCos(s);
  663.             final double cosS = scS.cos();
  664.             final double sinS = scS.sin();

  665.             // Accelerations
  666.             final double accX = glonassOrbit.getXDotDot() * cosS - glonassOrbit.getYDotDot() * sinS;
  667.             final double accY = glonassOrbit.getXDotDot() * sinS + glonassOrbit.getYDotDot() * cosS;
  668.             final double accZ = glonassOrbit.getZDotDot();

  669.             return new Vector3D(accX, accY, accZ);
  670.         }

  671.     }

  672. }