GroundStation.java

  1. /* Copyright 2002-2020 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.Field;
  20. import org.hipparchus.analysis.differentiation.DSFactory;
  21. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  22. import org.hipparchus.analysis.differentiation.Gradient;
  23. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  24. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  25. import org.hipparchus.geometry.euclidean.threed.Rotation;
  26. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  27. import org.hipparchus.util.FastMath;
  28. import org.orekit.bodies.BodyShape;
  29. import org.orekit.bodies.FieldGeodeticPoint;
  30. import org.orekit.bodies.GeodeticPoint;
  31. import org.orekit.data.BodiesElements;
  32. import org.orekit.data.FundamentalNutationArguments;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.frames.EOPHistory;
  36. import org.orekit.frames.FieldTransform;
  37. import org.orekit.frames.Frame;
  38. import org.orekit.frames.FramesFactory;
  39. import org.orekit.frames.TopocentricFrame;
  40. import org.orekit.frames.Transform;
  41. import org.orekit.models.earth.displacement.StationDisplacement;
  42. import org.orekit.time.AbsoluteDate;
  43. import org.orekit.time.FieldAbsoluteDate;
  44. import org.orekit.time.UT1Scale;
  45. import org.orekit.utils.ParameterDriver;

  46. /** Class modeling a ground station that can perform some measurements.
  47.  * <p>
  48.  * This class adds a position offset parameter to a base {@link TopocentricFrame
  49.  * topocentric frame}.
  50.  * </p>
  51.  * <p>
  52.  * Since 9.0, this class also adds parameters for an additional polar motion
  53.  * and an additional prime meridian orientation. Since these parameters will
  54.  * have the same name for all ground stations, they will be managed consistently
  55.  * and allow to estimate Earth orientation precisely (this is needed for precise
  56.  * orbit determination). The polar motion and prime meridian orientation will
  57.  * be applied <em>after</em> regular Earth orientation parameters, so the value
  58.  * of the estimated parameters will be correction to EOP, they will not be the
  59.  * complete EOP values by themselves. Basically, this means that for Earth, the
  60.  * following transforms are applied in order, between inertial frame and ground
  61.  * station frame (for non-Earth based ground stations, different precession nutation
  62.  * models and associated planet oritentation parameters would be applied, if available):
  63.  * </p>
  64.  * <p>
  65.  * Since 9.3, this class also adds a station clock offset parameter, which manages
  66.  * the value that must be subtracted from the observed measurement date to get the real
  67.  * physical date at which the measurement was performed (i.e. the offset is negative
  68.  * if the ground station clock is slow and positive if it is fast).
  69.  * </p>
  70.  * <ol>
  71.  *   <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
  72.  *   <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
  73.  *   <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
  74.  *   <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
  75.  *   <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  76.  *   {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
  77.  *   <li>station clock offset, controlled by {@link #getClockOffsetDriver()}</li>
  78.  *   <li>station position offset, controlled by {@link #getEastOffsetDriver()},
  79.  *   {@link #getNorthOffsetDriver()} and {@link #getZenithOffsetDriver()}</li>
  80.  * </ol>
  81.  * @author Luc Maisonobe
  82.  * @since 8.0
  83.  */
  84. public class GroundStation {

  85.     /** Suffix for ground station position and clock offset parameters names. */
  86.     public static final String OFFSET_SUFFIX = "-offset";

  87.     /** Suffix for ground station intermediate frame name. */
  88.     public static final String INTERMEDIATE_SUFFIX = "-intermediate";

  89.     /** Clock offset scaling factor.
  90.      * <p>
  91.      * We use a power of 2 to avoid numeric noise introduction
  92.      * in the multiplications/divisions sequences.
  93.      * </p>
  94.      */
  95.     private static final double CLOCK_OFFSET_SCALE = FastMath.scalb(1.0, -10);

  96.     /** Position offsets scaling factor.
  97.      * <p>
  98.      * We use a power of 2 (in fact really 1.0 here) to avoid numeric noise introduction
  99.      * in the multiplications/divisions sequences.
  100.      * </p>
  101.      */
  102.     private static final double POSITION_OFFSET_SCALE = FastMath.scalb(1.0, 0);

  103.     /** Provider for Earth frame whose EOP parameters can be estimated. */
  104.     private final EstimatedEarthFrameProvider estimatedEarthFrameProvider;

  105.     /** Earth frame whose EOP parameters can be estimated. */
  106.     private final Frame estimatedEarthFrame;

  107.     /** Base frame associated with the station. */
  108.     private final TopocentricFrame baseFrame;

  109.     /** Fundamental nutation arguments. */
  110.     private final FundamentalNutationArguments arguments;

  111.     /** Displacement models. */
  112.     private final StationDisplacement[] displacements;

  113.     /** Driver for clock offset. */
  114.     private final ParameterDriver clockOffsetDriver;

  115.     /** Driver for position offset along the East axis. */
  116.     private final ParameterDriver eastOffsetDriver;

  117.     /** Driver for position offset along the North axis. */
  118.     private final ParameterDriver northOffsetDriver;

  119.     /** Driver for position offset along the zenith axis. */
  120.     private final ParameterDriver zenithOffsetDriver;

  121.     /** Build a ground station ignoring {@link StationDisplacement station displacements}.
  122.      * <p>
  123.      * The initial values for the pole and prime meridian parametric linear models
  124.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  125.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  126.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  127.      * The initial values for the station offset model ({@link #getClockOffsetDriver()},
  128.      * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
  129.      * {@link #getZenithOffsetDriver()}) are set to 0.
  130.      * This implies that as long as these values are not changed, the offset frame is
  131.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  132.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  133.      * </p>
  134.      * @param baseFrame base frame associated with the station, without *any* parametric
  135.      * model (no station offset, no polar motion, no meridian shift)
  136.      * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
  137.      */
  138.     public GroundStation(final TopocentricFrame baseFrame) {
  139.         this(baseFrame, FramesFactory.findEOP(baseFrame), new StationDisplacement[0]);
  140.     }

  141.     /** Simple constructor.
  142.      * <p>
  143.      * The initial values for the pole and prime meridian parametric linear models
  144.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  145.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  146.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  147.      * The initial values for the station offset model ({@link #getClockOffsetDriver()},
  148.      * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
  149.      * {@link #getZenithOffsetDriver()}, {@link #getClockOffsetDriver()}) are set to 0.
  150.      * This implies that as long as these values are not changed, the offset frame is
  151.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  152.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  153.      * </p>
  154.      * @param baseFrame base frame associated with the station, without *any* parametric
  155.      * model (no station offset, no polar motion, no meridian shift)
  156.      * @param eopHistory EOP history associated with Earth frames
  157.      * @param displacements ground station displacement model (tides, ocean loading,
  158.      * atmospheric loading, thermal effects...)
  159.      * @since 9.1
  160.      */
  161.     public GroundStation(final TopocentricFrame baseFrame, final EOPHistory eopHistory,
  162.                          final StationDisplacement... displacements) {

  163.         this.baseFrame = baseFrame;

  164.         if (eopHistory == null) {
  165.             throw new OrekitException(OrekitMessages.NO_EARTH_ORIENTATION_PARAMETERS);
  166.         }

  167.         final UT1Scale baseUT1 = eopHistory.getTimeScales()
  168.                 .getUT1(eopHistory.getConventions(), eopHistory.isSimpleEop());
  169.         this.estimatedEarthFrameProvider = new EstimatedEarthFrameProvider(baseUT1);
  170.         this.estimatedEarthFrame = new Frame(baseFrame.getParent(), estimatedEarthFrameProvider,
  171.                                              baseFrame.getParent() + "-estimated");

  172.         if (displacements.length == 0) {
  173.             arguments = null;
  174.         } else {
  175.             arguments = eopHistory.getConventions().getNutationArguments(
  176.                     estimatedEarthFrameProvider.getEstimatedUT1(),
  177.                     eopHistory.getTimeScales());
  178.         }

  179.         this.displacements = displacements.clone();

  180.         this.clockOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-clock",
  181.                                                      0.0, CLOCK_OFFSET_SCALE,
  182.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  183.         this.eastOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-East",
  184.                                                     0.0, POSITION_OFFSET_SCALE,
  185.                                                     Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  186.         this.northOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-North",
  187.                                                      0.0, POSITION_OFFSET_SCALE,
  188.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  189.         this.zenithOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-Zenith",
  190.                                                       0.0, POSITION_OFFSET_SCALE,
  191.                                                       Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  192.     }

  193.     /** Get the displacement models.
  194.      * @return displacement models (empty if no model has been set up)
  195.      * @since 9.1
  196.      */
  197.     public StationDisplacement[] getDisplacements() {
  198.         return displacements.clone();
  199.     }

  200.     /** Get a driver allowing to change station clock (which is related to measurement date).
  201.      * @return driver for station clock offset
  202.      * @since 9.3
  203.      */
  204.     public ParameterDriver getClockOffsetDriver() {
  205.         return clockOffsetDriver;
  206.     }

  207.     /** Get a driver allowing to change station position along East axis.
  208.      * @return driver for station position offset along East axis
  209.      */
  210.     public ParameterDriver getEastOffsetDriver() {
  211.         return eastOffsetDriver;
  212.     }

  213.     /** Get a driver allowing to change station position along North axis.
  214.      * @return driver for station position offset along North axis
  215.      */
  216.     public ParameterDriver getNorthOffsetDriver() {
  217.         return northOffsetDriver;
  218.     }

  219.     /** Get a driver allowing to change station position along Zenith axis.
  220.      * @return driver for station position offset along Zenith axis
  221.      */
  222.     public ParameterDriver getZenithOffsetDriver() {
  223.         return zenithOffsetDriver;
  224.     }

  225.     /** Get a driver allowing to add a prime meridian rotation.
  226.      * <p>
  227.      * The parameter is an angle in radians. In order to convert this
  228.      * value to a DUT1 in seconds, the value must be divided by
  229.      * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
  230.      * of Earth from the TIRF model).
  231.      * </p>
  232.      * @return driver for prime meridian rotation
  233.      */
  234.     public ParameterDriver getPrimeMeridianOffsetDriver() {
  235.         return estimatedEarthFrameProvider.getPrimeMeridianOffsetDriver();
  236.     }

  237.     /** Get a driver allowing to add a prime meridian rotation rate.
  238.      * <p>
  239.      * The parameter is an angle rate in radians per second. In order to convert this
  240.      * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
  241.      * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
  242.      * of Earth from the TIRF model).
  243.      * </p>
  244.      * @return driver for prime meridian rotation rate
  245.      */
  246.     public ParameterDriver getPrimeMeridianDriftDriver() {
  247.         return estimatedEarthFrameProvider.getPrimeMeridianDriftDriver();
  248.     }

  249.     /** Get a driver allowing to add a polar offset along X.
  250.      * <p>
  251.      * The parameter is an angle in radians
  252.      * </p>
  253.      * @return driver for polar offset along X
  254.      */
  255.     public ParameterDriver getPolarOffsetXDriver() {
  256.         return estimatedEarthFrameProvider.getPolarOffsetXDriver();
  257.     }

  258.     /** Get a driver allowing to add a polar drift along X.
  259.      * <p>
  260.      * The parameter is an angle rate in radians per second
  261.      * </p>
  262.      * @return driver for polar drift along X
  263.      */
  264.     public ParameterDriver getPolarDriftXDriver() {
  265.         return estimatedEarthFrameProvider.getPolarDriftXDriver();
  266.     }

  267.     /** Get a driver allowing to add a polar offset along Y.
  268.      * <p>
  269.      * The parameter is an angle in radians
  270.      * </p>
  271.      * @return driver for polar offset along Y
  272.      */
  273.     public ParameterDriver getPolarOffsetYDriver() {
  274.         return estimatedEarthFrameProvider.getPolarOffsetYDriver();
  275.     }

  276.     /** Get a driver allowing to add a polar drift along Y.
  277.      * <p>
  278.      * The parameter is an angle rate in radians per second
  279.      * </p>
  280.      * @return driver for polar drift along Y
  281.      */
  282.     public ParameterDriver getPolarDriftYDriver() {
  283.         return estimatedEarthFrameProvider.getPolarDriftYDriver();
  284.     }

  285.     /** Get the base frame associated with the station.
  286.      * <p>
  287.      * The base frame corresponds to a null position offset, null
  288.      * polar motion, null meridian shift
  289.      * </p>
  290.      * @return base frame associated with the station
  291.      */
  292.     public TopocentricFrame getBaseFrame() {
  293.         return baseFrame;
  294.     }

  295.     /** Get the estimated Earth frame, including the estimated linear models for pole and prime meridian.
  296.      * <p>
  297.      * This frame is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
  298.      * {@link #getPrimeMeridianDriftDriver() driver prime meridian drift},
  299.      * {@link #getPolarOffsetXDriver() driver for polar offset along X},
  300.      * {@link #getPolarDriftXDriver() driver for polar drift along X},
  301.      * {@link #getPolarOffsetYDriver() driver for polar offset along Y},
  302.      * {@link #getPolarDriftYDriver() driver for polar drift along Y}, so its orientation changes when
  303.      * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
  304.      * </p>
  305.      * @return estimated Earth frame
  306.      * @since 9.1
  307.      */
  308.     public Frame getEstimatedEarthFrame() {
  309.         return estimatedEarthFrame;
  310.     }

  311.     /** Get the estimated UT1 scale, including the estimated linear models for prime meridian.
  312.      * <p>
  313.      * This time scale is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
  314.      * and {@link #getPrimeMeridianDriftDriver() driver prime meridian drift}, so its offset from UTC changes when
  315.      * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
  316.      * </p>
  317.      * @return estimated Earth frame
  318.      * @since 9.1
  319.      */
  320.     public UT1Scale getEstimatedUT1() {
  321.         return estimatedEarthFrameProvider.getEstimatedUT1();
  322.     }

  323.     /** Get the station displacement.
  324.      * @param date current date
  325.      * @param position raw position of the station in Earth frame
  326.      * before displacement is applied
  327.      * @return station displacement
  328.      * @since 9.1
  329.      */
  330.     private Vector3D computeDisplacement(final AbsoluteDate date, final Vector3D position) {
  331.         Vector3D displacement = Vector3D.ZERO;
  332.         if (arguments != null) {
  333.             final BodiesElements elements = arguments.evaluateAll(date);
  334.             for (final StationDisplacement sd : displacements) {
  335.                 // we consider all displacements apply to the same initial position,
  336.                 // i.e. they apply simultaneously, not according to some order
  337.                 displacement = displacement.add(sd.displacement(elements, estimatedEarthFrame, position));
  338.             }
  339.         }
  340.         return displacement;
  341.     }

  342.     /** Get the geodetic point at the center of the offset frame.
  343.      * @param date current date (may be null if displacements are ignored)
  344.      * @return geodetic point at the center of the offset frame
  345.      * @since 9.1
  346.      */
  347.     public GeodeticPoint getOffsetGeodeticPoint(final AbsoluteDate date) {

  348.         // take station offset into account
  349.         final double    x          = eastOffsetDriver.getValue();
  350.         final double    y          = northOffsetDriver.getValue();
  351.         final double    z          = zenithOffsetDriver.getValue();
  352.         final BodyShape baseShape  = baseFrame.getParentShape();
  353.         final Transform baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), date);
  354.         Vector3D        origin     = baseToBody.transformPosition(new Vector3D(x, y, z));

  355.         if (date != null) {
  356.             origin = origin.add(computeDisplacement(date, origin));
  357.         }

  358.         return baseShape.transform(origin, baseShape.getBodyFrame(), null);

  359.     }

  360.     /** Get the transform between offset frame and inertial frame.
  361.      * <p>
  362.      * The offset frame takes the <em>current</em> position offset,
  363.      * polar motion and the meridian shift into account. The frame
  364.      * returned is disconnected from later changes in the parameters.
  365.      * When the {@link ParameterDriver parameters} managing these
  366.      * offsets are changed, the method must be called again to retrieve
  367.      * a new offset frame.
  368.      * </p>
  369.      * @param inertial inertial frame to transform to
  370.      * @param clockDate date of the transform as read by the ground station clock (i.e. clock offset <em>not</em> compensated)
  371.      * @return transform between offset frame and inertial frame, at <em>real</em> measurement
  372.      * date (i.e. with clock, Earth and station offsets applied)
  373.      */
  374.     public Transform getOffsetToInertial(final Frame inertial, final AbsoluteDate clockDate) {

  375.         // take clock offset into account
  376.         final double offset = clockOffsetDriver.getValue();
  377.         final AbsoluteDate offsetCompensatedDate = new AbsoluteDate(clockDate, -offset);

  378.         // take Earth offsets into account
  379.         final Transform intermediateToBody = estimatedEarthFrameProvider.getTransform(offsetCompensatedDate).getInverse();

  380.         // take station offsets into account
  381.         final double    x          = eastOffsetDriver.getValue();
  382.         final double    y          = northOffsetDriver.getValue();
  383.         final double    z          = zenithOffsetDriver.getValue();
  384.         final BodyShape baseShape  = baseFrame.getParentShape();
  385.         final Transform baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), offsetCompensatedDate);
  386.         Vector3D        origin     = baseToBody.transformPosition(new Vector3D(x, y, z));
  387.         origin = origin.add(computeDisplacement(offsetCompensatedDate, origin));

  388.         final GeodeticPoint originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
  389.         final Transform offsetToIntermediate =
  390.                         new Transform(offsetCompensatedDate,
  391.                                       new Transform(offsetCompensatedDate,
  392.                                                     new Rotation(Vector3D.PLUS_I, Vector3D.PLUS_K,
  393.                                                                  originGP.getEast(), originGP.getZenith()),
  394.                                                     Vector3D.ZERO),
  395.                                       new Transform(offsetCompensatedDate, origin));

  396.         // combine all transforms together
  397.         final Transform bodyToInert        = baseFrame.getParent().getTransformTo(inertial, offsetCompensatedDate);

  398.         return new Transform(offsetCompensatedDate, offsetToIntermediate, new Transform(offsetCompensatedDate, intermediateToBody, bodyToInert));

  399.     }

  400.     /** Get the transform between offset frame and inertial frame with derivatives.
  401.      * <p>
  402.      * As the East and North vectors are not well defined at pole, the derivatives
  403.      * of these two vectors diverge to infinity as we get closer to the pole.
  404.      * So this method should not be used for stations less than 0.0001 degree from
  405.      * either poles.
  406.      * </p>
  407.      * @param inertial inertial frame to transform to
  408.      * @param clockDate date of the transform as read by the ground station clock (i.e. clock offset <em>not</em> compensated)
  409.      * @param factory factory for the derivatives
  410.      * @param indices indices of the estimated parameters in derivatives computations
  411.      * @return transform between offset frame and inertial frame, at <em>real</em> measurement
  412.      * date (i.e. with clock, Earth and station offsets applied)
  413.      * @see #getOffsetToInertial(Frame, FieldAbsoluteDate, DSFactory, Map)
  414.      * @since 9.3
  415.      * @deprecated as of 10.2, replaced by {@link #getOffsetToInertial(Frame, AbsoluteDate, int, Map)}
  416.      */
  417.     @Deprecated
  418.     public FieldTransform<DerivativeStructure> getOffsetToInertial(final Frame inertial,
  419.                                                                    final AbsoluteDate clockDate,
  420.                                                                    final DSFactory factory,
  421.                                                                    final Map<String, Integer> indices) {
  422.         // take clock offset into account
  423.         final DerivativeStructure offset = clockOffsetDriver.getValue(factory, indices);
  424.         final FieldAbsoluteDate<DerivativeStructure> offsetCompensatedDate =
  425.                         new FieldAbsoluteDate<DerivativeStructure>(clockDate, offset.negate());

  426.         return getOffsetToInertial(inertial, offsetCompensatedDate, factory, indices);
  427.     }

  428.     /** Get the transform between offset frame and inertial frame with derivatives.
  429.      * <p>
  430.      * As the East and North vectors are not well defined at pole, the derivatives
  431.      * of these two vectors diverge to infinity as we get closer to the pole.
  432.      * So this method should not be used for stations less than 0.0001 degree from
  433.      * either poles.
  434.      * </p>
  435.      * @param inertial inertial frame to transform to
  436.      * @param offsetCompensatedDate date of the transform, clock offset and its derivatives already compensated
  437.      * @param factory factory for the derivatives
  438.      * @param indices indices of the estimated parameters in derivatives computations
  439.      * @return transform between offset frame and inertial frame, at specified date
  440.      * @see #getOffsetToInertial(Frame, AbsoluteDate, DSFactory, Map)
  441.      * @since 9.0
  442.      * @deprecated as of 10.2, replaced by {@link #getOffsetToInertial(Frame, FieldAbsoluteDate, int, Map)}
  443.      */
  444.     @Deprecated
  445.     public FieldTransform<DerivativeStructure> getOffsetToInertial(final Frame inertial,
  446.                                                                    final FieldAbsoluteDate<DerivativeStructure> offsetCompensatedDate,
  447.                                                                    final DSFactory factory,
  448.                                                                    final Map<String, Integer> indices) {

  449.         final Field<DerivativeStructure>         field = factory.getDerivativeField();
  450.         final FieldVector3D<DerivativeStructure> zero  = FieldVector3D.getZero(field);
  451.         final FieldVector3D<DerivativeStructure> plusI = FieldVector3D.getPlusI(field);
  452.         final FieldVector3D<DerivativeStructure> plusK = FieldVector3D.getPlusK(field);

  453.         // take Earth offsets into account
  454.         final FieldTransform<DerivativeStructure> intermediateToBody =
  455.                         estimatedEarthFrameProvider.getTransform(offsetCompensatedDate, factory, indices).getInverse();

  456.         // take station offsets into account
  457.         final DerivativeStructure  x          = eastOffsetDriver.getValue(factory, indices);
  458.         final DerivativeStructure  y          = northOffsetDriver.getValue(factory, indices);
  459.         final DerivativeStructure  z          = zenithOffsetDriver.getValue(factory, indices);
  460.         final BodyShape            baseShape  = baseFrame.getParentShape();
  461.         final Transform            baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), (AbsoluteDate) null);

  462.         FieldVector3D<DerivativeStructure>            origin   = baseToBody.transformPosition(new FieldVector3D<>(x, y, z));
  463.         origin = origin.add(computeDisplacement(offsetCompensatedDate.toAbsoluteDate(), origin.toVector3D()));
  464.         final FieldGeodeticPoint<DerivativeStructure> originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
  465.         final FieldTransform<DerivativeStructure> offsetToIntermediate =
  466.                         new FieldTransform<>(offsetCompensatedDate,
  467.                                              new FieldTransform<>(offsetCompensatedDate,
  468.                                                                   new FieldRotation<>(plusI, plusK,
  469.                                                                                       originGP.getEast(), originGP.getZenith()),
  470.                                                                   zero),
  471.                                              new FieldTransform<>(offsetCompensatedDate, origin));

  472.         // combine all transforms together
  473.         final FieldTransform<DerivativeStructure> bodyToInert        = baseFrame.getParent().getTransformTo(inertial, offsetCompensatedDate);

  474.         return new FieldTransform<>(offsetCompensatedDate,
  475.                                     offsetToIntermediate,
  476.                                     new FieldTransform<>(offsetCompensatedDate, intermediateToBody, bodyToInert));

  477.     }

  478.     /** Get the transform between offset frame and inertial frame with derivatives.
  479.      * <p>
  480.      * As the East and North vectors are not well defined at pole, the derivatives
  481.      * of these two vectors diverge to infinity as we get closer to the pole.
  482.      * So this method should not be used for stations less than 0.0001 degree from
  483.      * either poles.
  484.      * </p>
  485.      * @param inertial inertial frame to transform to
  486.      * @param clockDate date of the transform as read by the ground station clock (i.e. clock offset <em>not</em> compensated)
  487.      * @param freeParameters total number of free parameters in the gradient
  488.      * @param indices indices of the estimated parameters in derivatives computations
  489.      * @return transform between offset frame and inertial frame, at <em>real</em> measurement
  490.      * date (i.e. with clock, Earth and station offsets applied)
  491.      * @see #getOffsetToInertial(Frame, FieldAbsoluteDate, DSFactory, Map)
  492.      * @since 10.2
  493.      */
  494.     public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
  495.                                                         final AbsoluteDate clockDate,
  496.                                                         final int freeParameters,
  497.                                                         final Map<String, Integer> indices) {
  498.         // take clock offset into account
  499.         final Gradient offset = clockOffsetDriver.getValue(freeParameters, indices);
  500.         final FieldAbsoluteDate<Gradient> offsetCompensatedDate =
  501.                         new FieldAbsoluteDate<>(clockDate, offset.negate());

  502.         return getOffsetToInertial(inertial, offsetCompensatedDate, freeParameters, indices);
  503.     }

  504.     /** Get the transform between offset frame and inertial frame with derivatives.
  505.      * <p>
  506.      * As the East and North vectors are not well defined at pole, the derivatives
  507.      * of these two vectors diverge to infinity as we get closer to the pole.
  508.      * So this method should not be used for stations less than 0.0001 degree from
  509.      * either poles.
  510.      * </p>
  511.      * @param inertial inertial frame to transform to
  512.      * @param offsetCompensatedDate date of the transform, clock offset and its derivatives already compensated
  513.      * @param freeParameters total number of free parameters in the gradient
  514.      * @param indices indices of the estimated parameters in derivatives computations
  515.      * @return transform between offset frame and inertial frame, at specified date
  516.      * @see #getOffsetToInertial(Frame, AbsoluteDate, DSFactory, Map)
  517.      * @since 10.2
  518.      */
  519.     public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
  520.                                                         final FieldAbsoluteDate<Gradient> offsetCompensatedDate,
  521.                                                         final int freeParameters,
  522.                                                         final Map<String, Integer> indices) {

  523.         final Field<Gradient>         field = offsetCompensatedDate.getField();
  524.         final FieldVector3D<Gradient> zero  = FieldVector3D.getZero(field);
  525.         final FieldVector3D<Gradient> plusI = FieldVector3D.getPlusI(field);
  526.         final FieldVector3D<Gradient> plusK = FieldVector3D.getPlusK(field);

  527.         // take Earth offsets into account
  528.         final FieldTransform<Gradient> intermediateToBody =
  529.                         estimatedEarthFrameProvider.getTransform(offsetCompensatedDate, freeParameters, indices).getInverse();

  530.         // take station offsets into account
  531.         final Gradient  x          = eastOffsetDriver.getValue(freeParameters, indices);
  532.         final Gradient  y          = northOffsetDriver.getValue(freeParameters, indices);
  533.         final Gradient  z          = zenithOffsetDriver.getValue(freeParameters, indices);
  534.         final BodyShape            baseShape  = baseFrame.getParentShape();
  535.         final Transform            baseToBody = baseFrame.getTransformTo(baseShape.getBodyFrame(), (AbsoluteDate) null);

  536.         FieldVector3D<Gradient> origin = baseToBody.transformPosition(new FieldVector3D<>(x, y, z));
  537.         origin = origin.add(computeDisplacement(offsetCompensatedDate.toAbsoluteDate(), origin.toVector3D()));
  538.         final FieldGeodeticPoint<Gradient> originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
  539.         final FieldTransform<Gradient> offsetToIntermediate =
  540.                         new FieldTransform<>(offsetCompensatedDate,
  541.                                              new FieldTransform<>(offsetCompensatedDate,
  542.                                                                   new FieldRotation<>(plusI, plusK,
  543.                                                                                       originGP.getEast(), originGP.getZenith()),
  544.                                                                   zero),
  545.                                              new FieldTransform<>(offsetCompensatedDate, origin));

  546.         // combine all transforms together
  547.         final FieldTransform<Gradient> bodyToInert = baseFrame.getParent().getTransformTo(inertial, offsetCompensatedDate);

  548.         return new FieldTransform<>(offsetCompensatedDate,
  549.                                     offsetToIntermediate,
  550.                                     new FieldTransform<>(offsetCompensatedDate, intermediateToBody, bodyToInert));

  551.     }

  552. }