EstimatedEarthFrameProvider.java

  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.estimation.measurements;

  18. import java.util.Map;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.analysis.differentiation.Gradient;
  21. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Rotation;
  24. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.frames.FieldStaticTransform;
  30. import org.orekit.frames.FieldTransform;
  31. import org.orekit.frames.StaticTransform;
  32. import org.orekit.frames.Transform;
  33. import org.orekit.frames.TransformProvider;
  34. import org.orekit.time.AbsoluteDate;
  35. import org.orekit.time.FieldAbsoluteDate;
  36. import org.orekit.time.TimeOffset;
  37. import org.orekit.time.UT1Scale;
  38. import org.orekit.utils.IERSConventions;
  39. import org.orekit.utils.ParameterDriver;

  40. /** Class modeling an Earth frame whose Earth Orientation Parameters can be estimated.
  41.  * <p>
  42.  * This class adds parameters for an additional polar motion
  43.  * and an additional prime meridian orientation on top of an underlying regular Earth
  44.  * frame like {@link org.orekit.frames.FramesFactory#getITRF(IERSConventions, boolean) ITRF}.
  45.  * The polar motion and prime meridian orientation are applied <em>after</em> regular Earth
  46.  * orientation parameters, so the value of the estimated parameters will be correction to EOP,
  47.  * they will not be the complete EOP values by themselves. Basically, this means that for
  48.  * Earth, the following transforms are applied in order, between inertial frame and this frame:
  49.  * </p>
  50.  * <ol>
  51.  *   <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
  52.  *   <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
  53.  *   <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
  54.  *   <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
  55.  *   <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  56.  *   {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
  57.  * </ol>
  58.  * @author Luc Maisonobe
  59.  * @since 9.1
  60.  */
  61. public class EstimatedEarthFrameProvider implements TransformProvider {

  62.     /** Earth Angular Velocity, in rad/s, from TIRF model. */
  63.     public static final double EARTH_ANGULAR_VELOCITY = 7.292115146706979e-5;

  64.     /** Angular scaling factor.
  65.      * <p>
  66.      * We use a power of 2 to avoid numeric noise introduction
  67.      * in the multiplications/divisions sequences.
  68.      * </p>
  69.      */
  70.     private static final double ANGULAR_SCALE = FastMath.scalb(1.0, -22);

  71.     /** Underlying raw UT1. */
  72.     private final UT1Scale baseUT1;

  73.     /** Estimated UT1. */
  74.     private final transient UT1Scale estimatedUT1;

  75.     /** Driver for prime meridian offset. */
  76.     private final transient ParameterDriver primeMeridianOffsetDriver;

  77.     /** Driver for prime meridian drift. */
  78.     private final transient ParameterDriver primeMeridianDriftDriver;

  79.     /** Driver for pole offset along X. */
  80.     private final transient ParameterDriver polarOffsetXDriver;

  81.     /** Driver for pole drift along X. */
  82.     private final transient ParameterDriver polarDriftXDriver;

  83.     /** Driver for pole offset along Y. */
  84.     private final transient ParameterDriver polarOffsetYDriver;

  85.     /** Driver for pole drift along Y. */
  86.     private final transient ParameterDriver polarDriftYDriver;

  87.     /** Build an estimated Earth frame.
  88.      * <p>
  89.      * The initial values for the pole and prime meridian parametric linear models
  90.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  91.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  92.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  93.      * </p>
  94.      * @param baseUT1 underlying base UT1
  95.      * @since 9.1
  96.      */
  97.     public EstimatedEarthFrameProvider(final UT1Scale baseUT1) {

  98.         this.primeMeridianOffsetDriver = new ParameterDriver("prime-meridian-offset",
  99.                                                              0.0, ANGULAR_SCALE,
  100.                                                             -FastMath.PI, FastMath.PI);

  101.         this.primeMeridianDriftDriver = new ParameterDriver("prime-meridian-drift",
  102.                                                             0.0, ANGULAR_SCALE,
  103.                                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  104.         this.polarOffsetXDriver = new ParameterDriver("polar-offset-X",
  105.                                                       0.0, ANGULAR_SCALE,
  106.                                                       -FastMath.PI, FastMath.PI);

  107.         this.polarDriftXDriver = new ParameterDriver("polar-drift-X",
  108.                                                      0.0, ANGULAR_SCALE,
  109.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  110.         this.polarOffsetYDriver = new ParameterDriver("polar-offset-Y",
  111.                                                       0.0, ANGULAR_SCALE,
  112.                                                       -FastMath.PI, FastMath.PI);

  113.         this.polarDriftYDriver = new ParameterDriver("polar-drift-Y",
  114.                                                      0.0, ANGULAR_SCALE,
  115.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  116.         this.baseUT1      = baseUT1;
  117.         this.estimatedUT1 = new EstimatedUT1Scale();

  118.     }

  119.     /** Get a driver allowing to add a prime meridian rotation.
  120.      * <p>
  121.      * The parameter is an angle in radians. In order to convert this
  122.      * value to a DUT1 in seconds, the value must be divided by
  123.      * {@link #EARTH_ANGULAR_VELOCITY} (nominal Angular Velocity of Earth).
  124.      * </p>
  125.      * @return driver for prime meridian rotation
  126.      */
  127.     public ParameterDriver getPrimeMeridianOffsetDriver() {
  128.         return primeMeridianOffsetDriver;
  129.     }

  130.     /** Get a driver allowing to add a prime meridian rotation rate.
  131.      * <p>
  132.      * The parameter is an angle rate in radians per second. In order to convert this
  133.      * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
  134.      * {@link #EARTH_ANGULAR_VELOCITY} (nominal Angular Velocity of Earth).
  135.      * </p>
  136.      * @return driver for prime meridian rotation rate
  137.      */
  138.     public ParameterDriver getPrimeMeridianDriftDriver() {
  139.         return primeMeridianDriftDriver;
  140.     }

  141.     /** Get a driver allowing to add a polar offset along X.
  142.      * <p>
  143.      * The parameter is an angle in radians
  144.      * </p>
  145.      * @return driver for polar offset along X
  146.      */
  147.     public ParameterDriver getPolarOffsetXDriver() {
  148.         return polarOffsetXDriver;
  149.     }

  150.     /** Get a driver allowing to add a polar drift along X.
  151.      * <p>
  152.      * The parameter is an angle rate in radians per second
  153.      * </p>
  154.      * @return driver for polar drift along X
  155.      */
  156.     public ParameterDriver getPolarDriftXDriver() {
  157.         return polarDriftXDriver;
  158.     }

  159.     /** Get a driver allowing to add a polar offset along Y.
  160.      * <p>
  161.      * The parameter is an angle in radians
  162.      * </p>
  163.      * @return driver for polar offset along Y
  164.      */
  165.     public ParameterDriver getPolarOffsetYDriver() {
  166.         return polarOffsetYDriver;
  167.     }

  168.     /** Get a driver allowing to add a polar drift along Y.
  169.      * <p>
  170.      * The parameter is an angle rate in radians per second
  171.      * </p>
  172.      * @return driver for polar drift along Y
  173.      */
  174.     public ParameterDriver getPolarDriftYDriver() {
  175.         return polarDriftYDriver;
  176.     }

  177.     /** Get the estimated UT1 time scale.
  178.      * @return estimated UT1 time scale
  179.      */
  180.     public UT1Scale getEstimatedUT1() {
  181.         return estimatedUT1;
  182.     }

  183.     /** {@inheritDoc} */
  184.     @Override
  185.     public Transform getTransform(final AbsoluteDate date) {

  186.         // take parametric prime meridian shift into account
  187.         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
  188.         final double thetaDot = primeMeridianDriftDriver.getValue();
  189.         final Transform meridianShift =
  190.                         new Transform(date,
  191.                                       new Rotation(Vector3D.PLUS_K, theta, RotationConvention.FRAME_TRANSFORM),
  192.                                       new Vector3D(0, 0, thetaDot));

  193.         // take parametric pole shift into account
  194.         final double xpNeg     = -linearModel(date, polarOffsetXDriver, polarDriftXDriver);
  195.         final double ypNeg     = -linearModel(date, polarOffsetYDriver, polarDriftYDriver);
  196.         final double xpNegDot  = -polarDriftXDriver.getValue();
  197.         final double ypNegDot  = -polarDriftYDriver.getValue();
  198.         final Transform poleShift =
  199.                         new Transform(date,
  200.                                       new Transform(date,
  201.                                                     new Rotation(Vector3D.PLUS_J, xpNeg, RotationConvention.FRAME_TRANSFORM),
  202.                                                     new Vector3D(0.0, xpNegDot, 0.0)),
  203.                                       new Transform(date,
  204.                                                     new Rotation(Vector3D.PLUS_I, ypNeg, RotationConvention.FRAME_TRANSFORM),
  205.                                                     new Vector3D(ypNegDot, 0.0, 0.0)));

  206.         return new Transform(date, meridianShift, poleShift);

  207.     }

  208.     /** {@inheritDoc} */
  209.     @Override
  210.     public StaticTransform getStaticTransform(final AbsoluteDate date) {

  211.         // take parametric prime meridian shift into account
  212.         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
  213.         final StaticTransform meridianShift = StaticTransform.of(
  214.                 date,
  215.                 new Rotation(Vector3D.PLUS_K, theta, RotationConvention.FRAME_TRANSFORM)
  216.         );

  217.         // take parametric pole shift into account
  218.         final double xpNeg     = -linearModel(date, polarOffsetXDriver, polarDriftXDriver);
  219.         final double ypNeg     = -linearModel(date, polarOffsetYDriver, polarDriftYDriver);
  220.         final StaticTransform poleShift = StaticTransform.compose(
  221.                 date,
  222.                 StaticTransform.of(
  223.                         date,
  224.                         new Rotation(Vector3D.PLUS_J, xpNeg, RotationConvention.FRAME_TRANSFORM)),
  225.                 StaticTransform.of(
  226.                         date,
  227.                         new Rotation(Vector3D.PLUS_I, ypNeg, RotationConvention.FRAME_TRANSFORM)));

  228.         return StaticTransform.compose(date, meridianShift, poleShift);

  229.     }

  230.     /** {@inheritDoc} */
  231.     @Override
  232.     public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {

  233.         final T zero = date.getField().getZero();

  234.         // prime meridian shift parameters
  235.         final T theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
  236.         final T thetaDot = zero.newInstance(primeMeridianDriftDriver.getValue());

  237.         // pole shift parameters
  238.         final T xpNeg    = linearModel(date, polarOffsetXDriver, polarDriftXDriver).negate();
  239.         final T ypNeg    = linearModel(date, polarOffsetYDriver, polarDriftYDriver).negate();
  240.         final T xpNegDot = zero.subtract(polarDriftXDriver.getValue());
  241.         final T ypNegDot = zero.subtract(polarDriftYDriver.getValue());

  242.         return getTransform(date, theta, thetaDot, xpNeg, xpNegDot, ypNeg, ypNegDot);

  243.     }

  244.     /** {@inheritDoc} */
  245.     @Override
  246.     public <T extends CalculusFieldElement<T>> FieldStaticTransform<T> getStaticTransform(final FieldAbsoluteDate<T> date) {

  247.         // take parametric prime meridian shift into account
  248.         final T theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
  249.         final FieldStaticTransform<T> meridianShift = FieldStaticTransform.of(
  250.                 date,
  251.                 new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), theta, RotationConvention.FRAME_TRANSFORM)
  252.         );

  253.         // take parametric pole shift into account
  254.         final T xpNeg     = linearModel(date, polarOffsetXDriver, polarDriftXDriver).negate();
  255.         final T ypNeg     = linearModel(date, polarOffsetYDriver, polarDriftYDriver).negate();
  256.         final FieldStaticTransform<T> poleShift = FieldStaticTransform.compose(
  257.                 date,
  258.                 FieldStaticTransform.of(
  259.                         date,
  260.                         new FieldRotation<>(FieldVector3D.getPlusJ(date.getField()), xpNeg, RotationConvention.FRAME_TRANSFORM)),
  261.                 FieldStaticTransform.of(
  262.                         date,
  263.                         new FieldRotation<>(FieldVector3D.getPlusI(date.getField()), ypNeg, RotationConvention.FRAME_TRANSFORM)));

  264.         return FieldStaticTransform.compose(date, meridianShift, poleShift);

  265.     }

  266.     /** Get the transform with derivatives.
  267.      * @param date date of the transform
  268.      * @param freeParameters total number of free parameters in the gradient
  269.      * @param indices indices of the estimated parameters in derivatives computations
  270.      * @return computed transform with derivatives
  271.      * @since 10.2
  272.      */
  273.     public FieldTransform<Gradient> getTransform(final FieldAbsoluteDate<Gradient> date,
  274.                                                  final int freeParameters,
  275.                                                  final Map<String, Integer> indices) {

  276.         // prime meridian shift parameters
  277.         final Gradient theta    = linearModel(freeParameters, date,
  278.                                               primeMeridianOffsetDriver, primeMeridianDriftDriver,
  279.                                               indices);
  280.         final Gradient thetaDot = primeMeridianDriftDriver.getValue(freeParameters, indices, date.toAbsoluteDate());

  281.         // pole shift parameters
  282.         final Gradient xpNeg    = linearModel(freeParameters, date,
  283.                                                          polarOffsetXDriver, polarDriftXDriver, indices).negate();
  284.         final Gradient ypNeg    = linearModel(freeParameters, date,
  285.                                                          polarOffsetYDriver, polarDriftYDriver, indices).negate();
  286.         final Gradient xpNegDot = polarDriftXDriver.getValue(freeParameters, indices, date.toAbsoluteDate()).negate();
  287.         final Gradient ypNegDot = polarDriftYDriver.getValue(freeParameters, indices, date.toAbsoluteDate()).negate();

  288.         return getTransform(date, theta, thetaDot, xpNeg, xpNegDot, ypNeg, ypNegDot);

  289.     }

  290.     /** Get the transform with derivatives.
  291.      * @param date date of the transform
  292.      * @param theta angle of the prime meridian
  293.      * @param thetaDot angular rate of the prime meridian
  294.      * @param xpNeg opposite of the angle of the pole motion along X
  295.      * @param xpNegDot opposite of the angular rate of the pole motion along X
  296.      * @param ypNeg opposite of the angle of the pole motion along Y
  297.      * @param ypNegDot opposite of the angular rate of the pole motion along Y
  298.      * @param <T> type of the field elements
  299.      * @return computed transform with derivatives
  300.      */
  301.     private <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date,
  302.                                                                            final T theta, final T thetaDot,
  303.                                                                            final T xpNeg, final T xpNegDot,
  304.                                                                            final T ypNeg, final T ypNegDot) {

  305.         final T                zero  = date.getField().getZero();
  306.         final FieldVector3D<T> plusI = FieldVector3D.getPlusI(date.getField());
  307.         final FieldVector3D<T> plusJ = FieldVector3D.getPlusJ(date.getField());
  308.         final FieldVector3D<T> plusK = FieldVector3D.getPlusK(date.getField());

  309.         // take parametric prime meridian shift into account
  310.         final FieldTransform<T> meridianShift =
  311.                         new FieldTransform<>(date,
  312.                                              new FieldRotation<>(plusK, theta, RotationConvention.FRAME_TRANSFORM),
  313.                                              new FieldVector3D<>(zero, zero, thetaDot));

  314.         // take parametric pole shift into account
  315.         final FieldTransform<T> poleShift =
  316.                         new FieldTransform<>(date,
  317.                                       new FieldTransform<>(date,
  318.                                                            new FieldRotation<>(plusJ, xpNeg, RotationConvention.FRAME_TRANSFORM),
  319.                                                            new FieldVector3D<>(zero, xpNegDot, zero)),
  320.                                       new FieldTransform<>(date,
  321.                                                            new FieldRotation<>(plusI, ypNeg, RotationConvention.FRAME_TRANSFORM),
  322.                                                            new FieldVector3D<>(ypNegDot, zero, zero)));

  323.         return new FieldTransform<>(date, meridianShift, poleShift);

  324.     }

  325.     /** Evaluate a parametric linear model.
  326.      * @param date current date
  327.      * @param offsetDriver driver for the offset parameter
  328.      * @param driftDriver driver for the drift parameter
  329.      * @return current value of the linear model
  330.      */
  331.     private double linearModel(final AbsoluteDate date,
  332.                                final ParameterDriver offsetDriver, final ParameterDriver driftDriver) {
  333.         if (offsetDriver.getReferenceDate() == null) {
  334.             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
  335.                                       offsetDriver.getName());
  336.         }
  337.         final double dt     = date.durationFrom(offsetDriver.getReferenceDate());
  338.         final double offset = offsetDriver.getValue();
  339.         final double drift  = driftDriver.getValue();
  340.         return dt * drift + offset;
  341.     }

  342.     /** Evaluate a parametric linear model.
  343.      * @param date current date
  344.      * @param offsetDriver driver for the offset parameter
  345.      * @param driftDriver driver for the drift parameter
  346.      * @return current value of the linear model
  347.      * @param <T> type of the filed elements
  348.      */
  349.     private <T extends CalculusFieldElement<T>> T linearModel(final FieldAbsoluteDate<T> date,
  350.                                                           final ParameterDriver offsetDriver,
  351.                                                           final ParameterDriver driftDriver) {
  352.         if (offsetDriver.getReferenceDate() == null) {
  353.             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
  354.                                       offsetDriver.getName());
  355.         }
  356.         final T dt          = date.durationFrom(offsetDriver.getReferenceDate());
  357.         final double offset = offsetDriver.getValue();
  358.         final double drift  = driftDriver.getValue();
  359.         return dt.multiply(drift).add(offset);
  360.     }

  361.     /** Evaluate a parametric linear model.
  362.      * @param freeParameters total number of free parameters in the gradient
  363.      * @param date current date
  364.      * @param offsetDriver driver for the offset parameter
  365.      * @param driftDriver driver for the drift parameter
  366.      * @param indices indices of the estimated parameters in derivatives computations
  367.      * @return current value of the linear model
  368.      * @since 10.2
  369.      */
  370.     private Gradient linearModel(final int freeParameters, final FieldAbsoluteDate<Gradient> date,
  371.                                  final ParameterDriver offsetDriver, final ParameterDriver driftDriver,
  372.                                  final Map<String, Integer> indices) {
  373.         if (offsetDriver.getReferenceDate() == null) {
  374.             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
  375.                                       offsetDriver.getName());
  376.         }
  377.         final Gradient dt     = date.durationFrom(offsetDriver.getReferenceDate());
  378.         final Gradient offset = offsetDriver.getValue(freeParameters, indices, date.toAbsoluteDate());
  379.         final Gradient drift  = driftDriver.getValue(freeParameters, indices, date.toAbsoluteDate());
  380.         return dt.multiply(drift).add(offset);
  381.     }

  382.     /** Local time scale for estimated UT1. */
  383.     private class EstimatedUT1Scale extends UT1Scale {

  384.         /** Simple constructor.
  385.          */
  386.         EstimatedUT1Scale() {
  387.             super(baseUT1.getEOPHistory(), baseUT1.getUTCScale());
  388.         }

  389.         /** {@inheritDoc} */
  390.         @Override
  391.         public <T extends CalculusFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
  392.             final T dut1 = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver).divide(EARTH_ANGULAR_VELOCITY);
  393.             return baseUT1.offsetFromTAI(date).add(dut1);
  394.         }

  395.         /** {@inheritDoc} */
  396.         @Override
  397.         public TimeOffset offsetFromTAI(final AbsoluteDate date) {
  398.             final double dut1 = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver) / EARTH_ANGULAR_VELOCITY;
  399.             return baseUT1.offsetFromTAI(date).add(new TimeOffset(dut1));
  400.         }

  401.         /** {@inheritDoc} */
  402.         @Override
  403.         public String getName() {
  404.             return baseUT1.getName() + "/estimated";
  405.         }

  406.     }
  407. }