GPSPropagator.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.analytical.gnss;

  18. import org.hipparchus.analysis.differentiation.DSFactory;
  19. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.MathUtils;
  23. import org.hipparchus.util.Precision;
  24. import org.orekit.attitudes.AttitudeProvider;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.frames.Frame;
  28. import org.orekit.frames.FramesFactory;
  29. import org.orekit.orbits.CartesianOrbit;
  30. import org.orekit.orbits.Orbit;
  31. import org.orekit.propagation.SpacecraftState;
  32. import org.orekit.propagation.analytical.AbstractAnalyticalPropagator;
  33. import org.orekit.time.AbsoluteDate;
  34. import org.orekit.utils.IERSConventions;
  35. import org.orekit.utils.PVCoordinates;

  36. /**
  37.  * This class aims at propagating a GPS orbit from {@link GPSOrbitalElements}.
  38.  *
  39.  * @see <a href="http://www.gps.gov/technical/icwg/IS-GPS-200H.pdf">GPS Interface Specification</a>
  40.  * @author Pascal Parraud
  41.  * @since 8.0
  42.  */
  43. public class GPSPropagator extends AbstractAnalyticalPropagator {

  44.     // Constants
  45.     /** WGS 84 value of the earth's rotation rate in rad/s. */
  46.     private static final double GPS_AV = 7.2921151467e-5;

  47.     /** Duration of the GPS cycle in seconds. */
  48.     private static final double GPS_CYCLE_DURATION = GPSOrbitalElements.GPS_WEEK_IN_SECONDS *
  49.                                                      GPSOrbitalElements.GPS_WEEK_NB;

  50.     // Data used to solve Kepler's equation
  51.     /** First coefficient to compute Kepler equation solver starter. */
  52.     private static final double A;

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

  55.     static {
  56.         final double k1 = 3 * FastMath.PI + 2;
  57.         final double k2 = FastMath.PI - 1;
  58.         final double k3 = 6 * FastMath.PI - 1;
  59.         A  = 3 * k2 * k2 / k1;
  60.         B  = k3 * k3 / (6 * k1);
  61.     }

  62.     // Fields
  63.     /** The GPS orbital elements used. */
  64.     private final GPSOrbitalElements gpsOrbit;

  65.     /** The spacecraft mass (kg). */
  66.     private final double mass;

  67.     /** The ECI frame used for GPS propagation. */
  68.     private final Frame eci;

  69.     /** The ECEF frame used for GPS propagation. */
  70.     private final Frame ecef;

  71.     /** Factory for the DerivativeStructure instances. */
  72.     private final DSFactory factory;

  73.     /**
  74.      * This nested class aims at building a GPSPropagator.
  75.      * <p>It implements the classical builder pattern.</p>
  76.      *
  77.      */
  78.     public static class Builder {

  79.         // Required parameter
  80.         /** The GPS orbital elements. */
  81.         private final GPSOrbitalElements orbit;

  82.         // Optional parameters
  83.         /** The attitude provider. */
  84.         private AttitudeProvider attitudeProvider = DEFAULT_LAW;
  85.         /** The mass. */
  86.         private double mass = DEFAULT_MASS;
  87.         /** The ECI frame. */
  88.         private Frame eci  = null;
  89.         /** The ECEF frame. */
  90.         private Frame ecef = null;

  91.         /** Initializes the builder.
  92.          * <p>The GPS orbital elements is the only requested parameter to build a GPSPropagator.</p>
  93.          * <p>The attitude provider is set by default to the
  94.          *  {@link org.orekit.propagation.Propagator#DEFAULT_LAW DEFAULT_LAW}.<br>
  95.          * The mass is set by default to the
  96.          *  {@link org.orekit.propagation.Propagator#DEFAULT_MASS DEFAULT_MASS}.<br>
  97.          * The ECI frame is set by default to the
  98.          *  {@link org.orekit.frames.Predefined#EME2000 EME2000 frame}.<br>
  99.          * The ECEF frame is set by default to the
  100.          *  {@link org.orekit.frames.Predefined#ITRF_CIO_CONV_2010_SIMPLE_EOP CIO/2010-based ITRF simple EOP}.
  101.          * </p>
  102.          *
  103.          * @param gpsOrbElt the GPS orbital elements to be used by the GPSpropagator.
  104.          * @throws OrekitException if data embedded in the library cannot be read
  105.          * @see #attitudeProvider(AttitudeProvider provider)
  106.          * @see #mass(double mass)
  107.          * @see #eci(Frame inertial)
  108.          * @see #ecef(Frame bodyFixed)
  109.          */
  110.         public Builder(final GPSOrbitalElements gpsOrbElt) throws OrekitException {
  111.             this.orbit = gpsOrbElt;
  112.             this.eci   = FramesFactory.getEME2000();
  113.             this.ecef  = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
  114.         }

  115.         /** Sets the attitude provider.
  116.          *
  117.          * @param userProvider the attitude provider
  118.          * @return the updated builder
  119.          */
  120.         public Builder attitudeProvider(final AttitudeProvider userProvider) {
  121.             this.attitudeProvider = userProvider;
  122.             return this;
  123.         }

  124.         /** Sets the mass.
  125.          *
  126.          * @param userMass the mass (in kg)
  127.          * @return the updated builder
  128.          */
  129.         public Builder mass(final double userMass) {
  130.             this.mass = userMass;
  131.             return this;
  132.         }

  133.         /** Sets the Earth Centered Inertial frame used for propagation.
  134.          *
  135.          * @param inertial the ECI frame
  136.          * @return the updated builder
  137.          */
  138.         public Builder eci(final Frame inertial) {
  139.             this.eci = inertial;
  140.             return this;
  141.         }

  142.         /** Sets the Earth Centered Earth Fixed frame assimilated to the WGS84 ECEF.
  143.          *
  144.          * @param bodyFixed the ECEF frame
  145.          * @return the updated builder
  146.          */
  147.         public Builder ecef(final Frame bodyFixed) {
  148.             this.ecef = bodyFixed;
  149.             return this;
  150.         }

  151.         /** Finalizes the build.
  152.          *
  153.          * @return the built GPSPropagator
  154.          */
  155.         public GPSPropagator build() {
  156.             return new GPSPropagator(this);
  157.         }
  158.     }

  159.     /**
  160.      * Private constructor.
  161.      *
  162.      * @param builder the builder
  163.      */
  164.     private GPSPropagator(final Builder builder) {
  165.         super(builder.attitudeProvider);
  166.         // Stores the GPS orbital elements
  167.         this.gpsOrbit = builder.orbit;
  168.         // Sets the start date as the date of the orbital elements
  169.         setStartDate(gpsOrbit.getDate());
  170.         // Sets the mass
  171.         this.mass = builder.mass;
  172.         // Sets the Earth Centered Inertial frame
  173.         this.eci  = builder.eci;
  174.         // Sets the Earth Centered Earth Fixed frame
  175.         this.ecef = builder.ecef;

  176.         this.factory = new DSFactory(1, 2);

  177.     }

  178.     /**
  179.      * Gets the PVCoordinates of the GPS SV in {@link #getECEF() ECEF frame}.
  180.      *
  181.      * <p>The algorithm is defined at Table 20-IV from IS-GPS-200 document,
  182.      * with automatic differentiation added to compute velocity and
  183.      * acceleration.</p>
  184.      *
  185.      * @param date the computation date
  186.      * @return the GPS SV PVCoordinates in {@link #getECEF() ECEF frame}
  187.      */
  188.     public PVCoordinates propagateInEcef(final AbsoluteDate date) {
  189.         // Duration from GPS ephemeris Reference date
  190.         final DerivativeStructure tk = factory.variable(0, getTk(date));
  191.         // Mean anomaly
  192.         final DerivativeStructure mk = tk.multiply(gpsOrbit.getMeanMotion()).add(gpsOrbit.getM0());
  193.         // Eccentric Anomaly
  194.         final DerivativeStructure ek = getEccentricAnomaly(mk);
  195.         // True Anomaly
  196.         final DerivativeStructure vk =  getTrueAnomaly(ek);
  197.         // Argument of Latitude
  198.         final DerivativeStructure phik    = vk.add(gpsOrbit.getPa());
  199.         final DerivativeStructure twoPhik = phik.multiply(2);
  200.         final DerivativeStructure c2phi   = twoPhik.cos();
  201.         final DerivativeStructure s2phi   = twoPhik.sin();
  202.         // Argument of Latitude Correction
  203.         final DerivativeStructure dphik = c2phi.multiply(gpsOrbit.getCuc()).add(s2phi.multiply(gpsOrbit.getCus()));
  204.         // Radius Correction
  205.         final DerivativeStructure drk = c2phi.multiply(gpsOrbit.getCrc()).add(s2phi.multiply(gpsOrbit.getCrs()));
  206.         // Inclination Correction
  207.         final DerivativeStructure dik = c2phi.multiply(gpsOrbit.getCic()).add(s2phi.multiply(gpsOrbit.getCis()));
  208.         // Corrected Argument of Latitude
  209.         final DerivativeStructure uk = phik.add(dphik);
  210.         // Corrected Radius
  211.         final DerivativeStructure rk = ek.cos().multiply(-gpsOrbit.getE()).add(1).multiply(gpsOrbit.getSma()).add(drk);
  212.         // Corrected Inclination
  213.         final DerivativeStructure ik  = tk.multiply(gpsOrbit.getIDot()).add(gpsOrbit.getI0()).add(dik);
  214.         final DerivativeStructure cik = ik.cos();
  215.         // Positions in orbital plane
  216.         final DerivativeStructure xk = uk.cos().multiply(rk);
  217.         final DerivativeStructure yk = uk.sin().multiply(rk);
  218.         // Corrected longitude of ascending node
  219.         final DerivativeStructure omk = tk.multiply(gpsOrbit.getOmegaDot() - GPS_AV).
  220.                                         add(gpsOrbit.getOmega0() - GPS_AV * gpsOrbit.getTime());
  221.         final DerivativeStructure comk = omk.cos();
  222.         final DerivativeStructure somk = omk.sin();
  223.         // returns the Earth-fixed coordinates
  224.         final FieldVector3D<DerivativeStructure> positionwithDerivatives =
  225.                         new FieldVector3D<>(xk.multiply(comk).subtract(yk.multiply(somk).multiply(cik)),
  226.                                             xk.multiply(somk).add(yk.multiply(comk).multiply(cik)),
  227.                                             yk.multiply(ik.sin()));
  228.         return new PVCoordinates(positionwithDerivatives);
  229.     }

  230.     /**
  231.      * Get the duration from GPS Reference epoch.
  232.      * <p>This takes the GPS week roll-over into account.</p>
  233.      *
  234.      * @param date the considered date
  235.      * @return the duration from GPS orbit Reference epoch (s)
  236.      */
  237.     private double getTk(final AbsoluteDate date) {
  238.         // Time from ephemeris reference epoch
  239.         double tk = date.durationFrom(gpsOrbit.getDate());
  240.         // Adjusts the time to take roll over week into account
  241.         while (tk > 0.5 * GPS_CYCLE_DURATION) {
  242.             tk -= GPS_CYCLE_DURATION;
  243.         }
  244.         while (tk < -0.5 * GPS_CYCLE_DURATION) {
  245.             tk += GPS_CYCLE_DURATION;
  246.         }
  247.         // Returns the time from ephemeris reference epoch
  248.         return tk;
  249.     }

  250.     /**
  251.      * Gets eccentric anomaly from mean anomaly.
  252.      * <p>The algorithm used to solve the Kepler equation has been published in:
  253.      * "Procedures for  solving Kepler's Equation", A. W. Odell and R. H. Gooding,
  254.      * Celestial Mechanics 38 (1986) 307-334</p>
  255.      * <p>It has been copied from the OREKIT library (KeplerianOrbit class).</p>
  256.      *
  257.      * @param mk the mean anomaly (rad)
  258.      * @return the eccentric anomaly (rad)
  259.      */
  260.     private DerivativeStructure getEccentricAnomaly(final DerivativeStructure mk) {

  261.         // reduce M to [-PI PI] interval
  262.         final double[] mlDerivatives = mk.getAllDerivatives();
  263.         mlDerivatives[0] = MathUtils.normalizeAngle(mlDerivatives[0], 0.0);
  264.         final DerivativeStructure reducedM = mk.getFactory().build(mlDerivatives);

  265.         // compute start value according to A. W. Odell and R. H. Gooding S12 starter
  266.         DerivativeStructure ek;
  267.         if (FastMath.abs(reducedM.getValue()) < 1.0 / 6.0) {
  268.             if (FastMath.abs(reducedM.getValue()) < Precision.SAFE_MIN) {
  269.                 // this is an Orekit change to the S12 starter.
  270.                 // If reducedM is 0.0, the derivative of cbrt is infinite which induces NaN appearing later in
  271.                 // the computation. As in this case E and M are almost equal, we initialize ek with reducedM
  272.                 ek = reducedM;
  273.             } else {
  274.                 // this is the standard S12 starter
  275.                 ek = reducedM.add(reducedM.multiply(6).cbrt().subtract(reducedM).multiply(gpsOrbit.getE()));
  276.             }
  277.         } else {
  278.             if (reducedM.getValue() < 0) {
  279.                 final DerivativeStructure w = reducedM.add(FastMath.PI);
  280.                 ek = reducedM.add(w.multiply(-A).divide(w.subtract(B)).subtract(FastMath.PI).subtract(reducedM).multiply(gpsOrbit.getE()));
  281.             } else {
  282.                 final DerivativeStructure minusW = reducedM.subtract(FastMath.PI);
  283.                 ek = reducedM.add(minusW.multiply(A).divide(minusW.add(B)).add(FastMath.PI).subtract(reducedM).multiply(gpsOrbit.getE()));
  284.             }
  285.         }

  286.         final double e1 = 1 - gpsOrbit.getE();
  287.         final boolean noCancellationRisk = (e1 + ek.getValue() * ek.getValue() / 6) >= 0.1;

  288.         // perform two iterations, each consisting of one Halley step and one Newton-Raphson step
  289.         for (int j = 0; j < 2; ++j) {
  290.             final DerivativeStructure f;
  291.             DerivativeStructure fd;
  292.             final DerivativeStructure fdd  = ek.sin().multiply(gpsOrbit.getE());
  293.             final DerivativeStructure fddd = ek.cos().multiply(gpsOrbit.getE());
  294.             if (noCancellationRisk) {
  295.                 f  = ek.subtract(fdd).subtract(reducedM);
  296.                 fd = fddd.subtract(1).negate();
  297.             } else {
  298.                 f  = eMeSinE(ek).subtract(reducedM);
  299.                 final DerivativeStructure s = ek.multiply(0.5).sin();
  300.                 fd = s.multiply(s).multiply(2 * gpsOrbit.getE()).add(e1);
  301.             }
  302.             final DerivativeStructure dee = f.multiply(fd).divide(f.multiply(0.5).multiply(fdd).subtract(fd.multiply(fd)));

  303.             // update eccentric anomaly, using expressions that limit underflow problems
  304.             final DerivativeStructure w = fd.add(dee.multiply(0.5).multiply(fdd.add(dee.multiply(fdd).divide(3))));
  305.             fd = fd.add(dee.multiply(fdd.add(dee.multiply(0.5).multiply(fdd))));
  306.             ek = ek.subtract(f.subtract(dee.multiply(fd.subtract(w))).divide(fd));
  307.         }

  308.         // expand the result back to original range
  309.         ek = ek.add(mk.getValue() - reducedM.getValue());

  310.         // Returns the eccentric anomaly
  311.         return ek;
  312.     }

  313.     /**
  314.      * Accurate computation of E - e sin(E).
  315.      *
  316.      * @param E eccentric anomaly
  317.      * @return E - e sin(E)
  318.      */
  319.     private DerivativeStructure eMeSinE(final DerivativeStructure E) {
  320.         DerivativeStructure x = E.sin().multiply(1 - gpsOrbit.getE());
  321.         final DerivativeStructure mE2 = E.negate().multiply(E);
  322.         DerivativeStructure term = E;
  323.         DerivativeStructure d    = E.getField().getZero();
  324.         // the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
  325.         for (DerivativeStructure x0 = d.add(Double.NaN); x.getValue() != x0.getValue();) {
  326.             d = d.add(2);
  327.             term = term.multiply(mE2.divide(d.multiply(d.add(1))));
  328.             x0 = x;
  329.             x = x.subtract(term);
  330.         }
  331.         return x;
  332.     }

  333.     /** Gets true anomaly from eccentric anomaly.
  334.      *
  335.      * @param ek the eccentric anomaly (rad)
  336.      * @return the true anomaly (rad)
  337.      */
  338.     private DerivativeStructure getTrueAnomaly(final DerivativeStructure ek) {
  339.         final DerivativeStructure svk = ek.sin().multiply(FastMath.sqrt(1. - gpsOrbit.getE() * gpsOrbit.getE()));
  340.         final DerivativeStructure cvk = ek.cos().subtract(gpsOrbit.getE());
  341.         return svk.atan2(cvk);
  342.     }

  343.     /**
  344.      * Get the Earth gravity coefficient used for GPS propagation.
  345.      * @return the Earth gravity coefficient.
  346.      */
  347.     public static double getMU() {
  348.         return GPSOrbitalElements.GPS_MU;
  349.     }

  350.     /**
  351.      * Gets the underlying GPS orbital elements.
  352.      *
  353.      * @return the underlying GPS orbital elements
  354.      */
  355.     public GPSOrbitalElements getGPSOrbitalElements() {
  356.         return gpsOrbit;
  357.     }

  358.     /**
  359.      * Gets the Earth Centered Inertial frame used to propagate the orbit.
  360.      *
  361.      * @return the ECI frame
  362.      */
  363.     public Frame getECI() {
  364.         return eci;
  365.     }

  366.     /**
  367.      * Gets the Earth Centered Earth Fixed frame used to propagate GPS orbits according to the
  368.      * <a href="http://www.gps.gov/technical/icwg/IS-GPS-200H.pdf">GPS Interface Specification</a>.
  369.      * <p>This frame is assimilated to the WGS84 ECEF.</p>
  370.      *
  371.      * @return the ECEF frame
  372.      */
  373.     public Frame getECEF() {
  374.         return ecef;
  375.     }

  376.     /** {@inheritDoc} */
  377.     public Frame getFrame() {
  378.         return eci;
  379.     }

  380.     /** {@inheritDoc} */
  381.     public void resetInitialState(final SpacecraftState state) throws OrekitException {
  382.         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
  383.     }

  384.     /** {@inheritDoc} */
  385.     protected void resetIntermediateState(final SpacecraftState state, final boolean forward)
  386.         throws OrekitException {
  387.         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
  388.     }

  389.     /** {@inheritDoc} */
  390.     protected double getMass(final AbsoluteDate date) {
  391.         return mass;
  392.     }

  393.     /** {@inheritDoc} */
  394.     protected Orbit propagateOrbit(final AbsoluteDate date) throws OrekitException {
  395.         // Gets the PVCoordinates in ECEF frame
  396.         final PVCoordinates pvaInECEF = propagateInEcef(date);
  397.         // Transforms the PVCoordinates to ECI frame
  398.         final PVCoordinates pvaInECI = ecef.getTransformTo(eci, date).transformPVCoordinates(pvaInECEF);
  399.         // Returns the Cartesian orbit
  400.         return new CartesianOrbit(pvaInECI, eci, date, GPSOrbitalElements.GPS_MU);
  401.     }
  402. }