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

  18. import java.util.Map;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.Field;
  21. import org.hipparchus.analysis.differentiation.Gradient;
  22. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.geometry.euclidean.threed.Rotation;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.bodies.BodyShape;
  28. import org.orekit.bodies.FieldGeodeticPoint;
  29. import org.orekit.bodies.GeodeticPoint;
  30. import org.orekit.data.BodiesElements;
  31. import org.orekit.data.FundamentalNutationArguments;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;
  34. import org.orekit.frames.EOPHistory;
  35. import org.orekit.frames.FieldStaticTransform;
  36. import org.orekit.frames.FieldTransform;
  37. import org.orekit.frames.Frame;
  38. import org.orekit.frames.FramesFactory;
  39. import org.orekit.frames.StaticTransform;
  40. import org.orekit.frames.TopocentricFrame;
  41. import org.orekit.frames.Transform;
  42. import org.orekit.models.earth.displacement.StationDisplacement;
  43. import org.orekit.models.earth.troposphere.TroposphericModelUtils;
  44. import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
  45. import org.orekit.models.earth.weather.PressureTemperatureHumidity;
  46. import org.orekit.models.earth.weather.PressureTemperatureHumidityProvider;
  47. import org.orekit.time.AbsoluteDate;
  48. import org.orekit.time.FieldAbsoluteDate;
  49. import org.orekit.time.UT1Scale;
  50. import org.orekit.utils.ParameterDriver;

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

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

  92.     /** Suffix for ground clock drift parameters name. */
  93.     public static final String DRIFT_SUFFIX = "-drift-clock";

  94.     /** Suffix for ground clock drift parameters name.
  95.      * @since 12.1
  96.      */
  97.     public static final String ACCELERATION_SUFFIX = "-acceleration-clock";

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

  100.     /** Clock offset scaling factor.
  101.      * <p>
  102.      * We use a power of 2 to avoid numeric noise introduction
  103.      * in the multiplications/divisions sequences.
  104.      * </p>
  105.      */
  106.     private static final double CLOCK_OFFSET_SCALE = FastMath.scalb(1.0, -10);

  107.     /** Position offsets scaling factor.
  108.      * <p>
  109.      * We use a power of 2 (in fact really 1.0 here) to avoid numeric noise introduction
  110.      * in the multiplications/divisions sequences.
  111.      * </p>
  112.      */
  113.     private static final double POSITION_OFFSET_SCALE = FastMath.scalb(1.0, 0);

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

  116.     /** Provider for weather parameters.
  117.      * @since 12.1
  118.      */
  119.     private final PressureTemperatureHumidityProvider pthProvider;

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

  122.     /** Base frame associated with the station. */
  123.     private final TopocentricFrame baseFrame;

  124.     /** Fundamental nutation arguments. */
  125.     private final FundamentalNutationArguments arguments;

  126.     /** Displacement models. */
  127.     private final StationDisplacement[] displacements;

  128.     /** Driver for clock offset. */
  129.     private final ParameterDriver clockOffsetDriver;

  130.     /** Driver for clock drift. */
  131.     private final ParameterDriver clockDriftDriver;

  132.     /** Driver for clock acceleration.
  133.      * @since 12.1
  134.      */
  135.     private final ParameterDriver clockAccelerationDriver;

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

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

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

  142.     /** Build a ground station ignoring {@link StationDisplacement station displacements}.
  143.      * <p>
  144.      * Calls {@link #GroundStation(TopocentricFrame, PressureTemperatureHumidityProvider)}
  145.      * with {@link TroposphericModelUtils#STANDARD_ATMOSPHERE_PROVIDER} as the provider.
  146.      * </p>
  147.      * <p>
  148.      * The initial values for the pole and prime meridian parametric linear models
  149.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  150.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  151.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  152.      * The initial values for the station offset model ({@link #getClockOffsetDriver()},
  153.      * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
  154.      * {@link #getZenithOffsetDriver()}) are set to 0.
  155.      * This implies that as long as these values are not changed, the offset frame is
  156.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  157.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  158.      * </p>
  159.      * @param baseFrame base frame associated with the station, without *any* parametric
  160.      * model (no station offset, no polar motion, no meridian shift)
  161.      * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
  162.      */
  163.     public GroundStation(final TopocentricFrame baseFrame) {
  164.         this(baseFrame, TroposphericModelUtils.STANDARD_ATMOSPHERE_PROVIDER,
  165.              FramesFactory.findEOP(baseFrame), new StationDisplacement[0]);
  166.     }

  167.     /** Build a ground station ignoring {@link StationDisplacement station displacements}.
  168.      * <p>
  169.      * The initial values for the pole and prime meridian parametric linear models
  170.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  171.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  172.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  173.      * The initial values for the station offset model ({@link #getClockOffsetDriver()},
  174.      * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
  175.      * {@link #getZenithOffsetDriver()}) are set to 0.
  176.      * This implies that as long as these values are not changed, the offset frame is
  177.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  178.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  179.      * </p>
  180.      * @param baseFrame base frame associated with the station, without *any* parametric
  181.      * model (no station offset, no polar motion, no meridian shift)
  182.      * @param pthProvider provider for weather parameters
  183.      * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
  184.      * @since 12.1
  185.      */
  186.     public GroundStation(final TopocentricFrame baseFrame, final PressureTemperatureHumidityProvider pthProvider) {
  187.         this(baseFrame, pthProvider, FramesFactory.findEOP(baseFrame), new StationDisplacement[0]);
  188.     }

  189.     /** Simple constructor.
  190.      * <p>
  191.      * The initial values for the pole and prime meridian parametric linear models
  192.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  193.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  194.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  195.      * The initial values for the station offset model ({@link #getClockOffsetDriver()},
  196.      * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
  197.      * {@link #getZenithOffsetDriver()}, {@link #getClockOffsetDriver()}) are set to 0.
  198.      * This implies that as long as these values are not changed, the offset frame is
  199.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  200.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  201.      * </p>
  202.      * @param baseFrame base frame associated with the station, without *any* parametric
  203.      * model (no station offset, no polar motion, no meridian shift)
  204.      * @param eopHistory EOP history associated with Earth frames
  205.      * @param displacements ground station displacement model (tides, ocean loading,
  206.      * atmospheric loading, thermal effects...)
  207.      * @since 9.1
  208.      * @deprecated as of 12.1, replaced by {@link #GroundStation(TopocentricFrame,
  209.      * PressureTemperatureHumidityProvider, EOPHistory, StationDisplacement...)}
  210.      */
  211.     @Deprecated
  212.     public GroundStation(final TopocentricFrame baseFrame, final EOPHistory eopHistory,
  213.                          final StationDisplacement... displacements) {
  214.         this(baseFrame, TroposphericModelUtils.STANDARD_ATMOSPHERE_PROVIDER, eopHistory, displacements);
  215.     }

  216.     /** Simple constructor.
  217.      * <p>
  218.      * The initial values for the pole and prime meridian parametric linear models
  219.      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
  220.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
  221.      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
  222.      * The initial values for the station offset model ({@link #getClockOffsetDriver()},
  223.      * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
  224.      * {@link #getZenithOffsetDriver()}, {@link #getClockOffsetDriver()}) are set to 0.
  225.      * This implies that as long as these values are not changed, the offset frame is
  226.      * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
  227.      * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
  228.      * </p>
  229.      * @param baseFrame base frame associated with the station, without *any* parametric
  230.      * model (no station offset, no polar motion, no meridian shift)
  231.      * @param pthProvider provider for weather parameters
  232.      * @param eopHistory EOP history associated with Earth frames
  233.      * @param displacements ground station displacement model (tides, ocean loading,
  234.      * atmospheric loading, thermal effects...)
  235.      * @since 12.1
  236.      */
  237.     public GroundStation(final TopocentricFrame baseFrame,
  238.                          final PressureTemperatureHumidityProvider pthProvider,
  239.                          final EOPHistory eopHistory,
  240.                          final StationDisplacement... displacements) {

  241.         this.baseFrame   = baseFrame;
  242.         this.pthProvider = pthProvider;

  243.         if (eopHistory == null) {
  244.             throw new OrekitException(OrekitMessages.NO_EARTH_ORIENTATION_PARAMETERS);
  245.         }

  246.         final UT1Scale baseUT1 = eopHistory.getTimeScales()
  247.                 .getUT1(eopHistory.getConventions(), eopHistory.isSimpleEop());
  248.         this.estimatedEarthFrameProvider = new EstimatedEarthFrameProvider(baseUT1);
  249.         this.estimatedEarthFrame = new Frame(baseFrame.getParent(), estimatedEarthFrameProvider,
  250.                                              baseFrame.getParent() + "-estimated");

  251.         if (displacements.length == 0) {
  252.             arguments = null;
  253.         } else {
  254.             arguments = eopHistory.getConventions().getNutationArguments(
  255.                     estimatedEarthFrameProvider.getEstimatedUT1(),
  256.                     eopHistory.getTimeScales());
  257.         }

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

  259.         this.clockOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-clock",
  260.                                                      0.0, CLOCK_OFFSET_SCALE,
  261.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  262.         this.clockDriftDriver = new ParameterDriver(baseFrame.getName() + DRIFT_SUFFIX,
  263.                                                     0.0, CLOCK_OFFSET_SCALE,
  264.                                                     Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  265.         this.clockAccelerationDriver = new ParameterDriver(baseFrame.getName() + ACCELERATION_SUFFIX,
  266.                                                     0.0, CLOCK_OFFSET_SCALE,
  267.                                                     Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  268.         this.eastOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-East",
  269.                                                     0.0, POSITION_OFFSET_SCALE,
  270.                                                     Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  271.         this.northOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-North",
  272.                                                      0.0, POSITION_OFFSET_SCALE,
  273.                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  274.         this.zenithOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-Zenith",
  275.                                                       0.0, POSITION_OFFSET_SCALE,
  276.                                                       Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

  277.     }

  278.     /** Get the weather parameters.
  279.      * @param date date at which weather parameters are requested
  280.      * @return weather parameters
  281.      * @since 12.1
  282.      */
  283.     public PressureTemperatureHumidity getPressureTemperatureHumidity(final AbsoluteDate date) {
  284.         return pthProvider.getWeatherParamerers(getOffsetGeodeticPoint(date), date);
  285.     }

  286.     /** Get the weather parameters.
  287.      * @param <T> type of the field elements
  288.      * @param date date at which weather parameters are requested
  289.      * @return weather parameters
  290.      * @since 12.1
  291.      */
  292.     public <T extends CalculusFieldElement<T>> FieldPressureTemperatureHumidity<T> getPressureTemperatureHumidity(final FieldAbsoluteDate<T> date) {
  293.         return pthProvider.getWeatherParamerers(getOffsetGeodeticPoint(date), date);
  294.     }

  295.     /** Get the displacement models.
  296.      * @return displacement models (empty if no model has been set up)
  297.      * @since 9.1
  298.      */
  299.     public StationDisplacement[] getDisplacements() {
  300.         return displacements.clone();
  301.     }

  302.     /** Get a driver allowing to change station clock (which is related to measurement date).
  303.      * @return driver for station clock offset
  304.      * @since 9.3
  305.      */
  306.     public ParameterDriver getClockOffsetDriver() {
  307.         return clockOffsetDriver;
  308.     }

  309.     /** Get a driver allowing to change station clock drift (which is related to measurement date).
  310.      * @return driver for station clock drift
  311.      * @since 10.3
  312.      */
  313.     public ParameterDriver getClockDriftDriver() {
  314.         return clockDriftDriver;
  315.     }

  316.     /** Get a driver allowing to change station clock acceleration (which is related to measurement date).
  317.      * @return driver for station clock acceleration
  318.      * @since 12.1
  319.      */
  320.     public ParameterDriver getClockAccelerationDriver() {
  321.         return clockAccelerationDriver;
  322.     }

  323.     /** Get a driver allowing to change station position along East axis.
  324.      * @return driver for station position offset along East axis
  325.      */
  326.     public ParameterDriver getEastOffsetDriver() {
  327.         return eastOffsetDriver;
  328.     }

  329.     /** Get a driver allowing to change station position along North axis.
  330.      * @return driver for station position offset along North axis
  331.      */
  332.     public ParameterDriver getNorthOffsetDriver() {
  333.         return northOffsetDriver;
  334.     }

  335.     /** Get a driver allowing to change station position along Zenith axis.
  336.      * @return driver for station position offset along Zenith axis
  337.      */
  338.     public ParameterDriver getZenithOffsetDriver() {
  339.         return zenithOffsetDriver;
  340.     }

  341.     /** Get a driver allowing to add a prime meridian rotation.
  342.      * <p>
  343.      * The parameter is an angle in radians. In order to convert this
  344.      * value to a DUT1 in seconds, the value must be divided by
  345.      * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
  346.      * of Earth from the TIRF model).
  347.      * </p>
  348.      * @return driver for prime meridian rotation
  349.      */
  350.     public ParameterDriver getPrimeMeridianOffsetDriver() {
  351.         return estimatedEarthFrameProvider.getPrimeMeridianOffsetDriver();
  352.     }

  353.     /** Get a driver allowing to add a prime meridian rotation rate.
  354.      * <p>
  355.      * The parameter is an angle rate in radians per second. In order to convert this
  356.      * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
  357.      * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
  358.      * of Earth from the TIRF model).
  359.      * </p>
  360.      * @return driver for prime meridian rotation rate
  361.      */
  362.     public ParameterDriver getPrimeMeridianDriftDriver() {
  363.         return estimatedEarthFrameProvider.getPrimeMeridianDriftDriver();
  364.     }

  365.     /** Get a driver allowing to add a polar offset along X.
  366.      * <p>
  367.      * The parameter is an angle in radians
  368.      * </p>
  369.      * @return driver for polar offset along X
  370.      */
  371.     public ParameterDriver getPolarOffsetXDriver() {
  372.         return estimatedEarthFrameProvider.getPolarOffsetXDriver();
  373.     }

  374.     /** Get a driver allowing to add a polar drift along X.
  375.      * <p>
  376.      * The parameter is an angle rate in radians per second
  377.      * </p>
  378.      * @return driver for polar drift along X
  379.      */
  380.     public ParameterDriver getPolarDriftXDriver() {
  381.         return estimatedEarthFrameProvider.getPolarDriftXDriver();
  382.     }

  383.     /** Get a driver allowing to add a polar offset along Y.
  384.      * <p>
  385.      * The parameter is an angle in radians
  386.      * </p>
  387.      * @return driver for polar offset along Y
  388.      */
  389.     public ParameterDriver getPolarOffsetYDriver() {
  390.         return estimatedEarthFrameProvider.getPolarOffsetYDriver();
  391.     }

  392.     /** Get a driver allowing to add a polar drift along Y.
  393.      * <p>
  394.      * The parameter is an angle rate in radians per second
  395.      * </p>
  396.      * @return driver for polar drift along Y
  397.      */
  398.     public ParameterDriver getPolarDriftYDriver() {
  399.         return estimatedEarthFrameProvider.getPolarDriftYDriver();
  400.     }

  401.     /** Get the base frame associated with the station.
  402.      * <p>
  403.      * The base frame corresponds to a null position offset, null
  404.      * polar motion, null meridian shift
  405.      * </p>
  406.      * @return base frame associated with the station
  407.      */
  408.     public TopocentricFrame getBaseFrame() {
  409.         return baseFrame;
  410.     }

  411.     /** Get the estimated Earth frame, including the estimated linear models for pole and prime meridian.
  412.      * <p>
  413.      * This frame is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
  414.      * {@link #getPrimeMeridianDriftDriver() driver prime meridian drift},
  415.      * {@link #getPolarOffsetXDriver() driver for polar offset along X},
  416.      * {@link #getPolarDriftXDriver() driver for polar drift along X},
  417.      * {@link #getPolarOffsetYDriver() driver for polar offset along Y},
  418.      * {@link #getPolarDriftYDriver() driver for polar drift along Y}, so its orientation changes when
  419.      * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
  420.      * </p>
  421.      * @return estimated Earth frame
  422.      * @since 9.1
  423.      */
  424.     public Frame getEstimatedEarthFrame() {
  425.         return estimatedEarthFrame;
  426.     }

  427.     /** Get the estimated UT1 scale, including the estimated linear models for prime meridian.
  428.      * <p>
  429.      * This time scale is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
  430.      * and {@link #getPrimeMeridianDriftDriver() driver prime meridian drift}, so its offset from UTC changes when
  431.      * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
  432.      * </p>
  433.      * @return estimated Earth frame
  434.      * @since 9.1
  435.      */
  436.     public UT1Scale getEstimatedUT1() {
  437.         return estimatedEarthFrameProvider.getEstimatedUT1();
  438.     }

  439.     /** Get the station displacement.
  440.      * @param date current date
  441.      * @param position raw position of the station in Earth frame
  442.      * before displacement is applied
  443.      * @return station displacement
  444.      * @since 9.1
  445.      */
  446.     private Vector3D computeDisplacement(final AbsoluteDate date, final Vector3D position) {
  447.         Vector3D displacement = Vector3D.ZERO;
  448.         if (arguments != null) {
  449.             final BodiesElements elements = arguments.evaluateAll(date);
  450.             for (final StationDisplacement sd : displacements) {
  451.                 // we consider all displacements apply to the same initial position,
  452.                 // i.e. they apply simultaneously, not according to some order
  453.                 displacement = displacement.add(sd.displacement(elements, estimatedEarthFrame, position));
  454.             }
  455.         }
  456.         return displacement;
  457.     }

  458.     /** Get the geodetic point at the center of the offset frame.
  459.      * @param date current date (may be null if displacements are ignored)
  460.      * @return geodetic point at the center of the offset frame
  461.      * @since 9.1
  462.      */
  463.     public GeodeticPoint getOffsetGeodeticPoint(final AbsoluteDate date) {

  464.         // take station offset into account
  465.         final double    x          = eastOffsetDriver.getValue();
  466.         final double    y          = northOffsetDriver.getValue();
  467.         final double    z          = zenithOffsetDriver.getValue();
  468.         final BodyShape baseShape  = baseFrame.getParentShape();
  469.         final StaticTransform baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), date);
  470.         Vector3D        origin     = baseToBody.transformPosition(new Vector3D(x, y, z));

  471.         if (date != null) {
  472.             origin = origin.add(computeDisplacement(date, origin));
  473.         }

  474.         return baseShape.transform(origin, baseShape.getBodyFrame(), date);

  475.     }

  476.     /** Get the geodetic point at the center of the offset frame.
  477.      * @param <T> type of the field elements
  478.      * @param date current date(<em>must</em> be non-null, which is a more stringent condition
  479.      *      *                    than in {@link #getOffsetGeodeticPoint(AbsoluteDate)}
  480.      * @return geodetic point at the center of the offset frame
  481.      * @since 12.1
  482.      */
  483.     public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> getOffsetGeodeticPoint(final FieldAbsoluteDate<T> date) {

  484.         // take station offset into account
  485.         final double    x          = eastOffsetDriver.getValue();
  486.         final double    y          = northOffsetDriver.getValue();
  487.         final double    z          = zenithOffsetDriver.getValue();
  488.         final BodyShape baseShape  = baseFrame.getParentShape();
  489.         final FieldStaticTransform<T> baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), date);
  490.         FieldVector3D<T> origin    = baseToBody.transformPosition(new Vector3D(x, y, z));
  491.         origin = origin.add(computeDisplacement(date.toAbsoluteDate(), origin.toVector3D()));

  492.         return baseShape.transform(origin, baseShape.getBodyFrame(), date);

  493.     }

  494.     /** Get the transform between offset frame and inertial frame.
  495.      * <p>
  496.      * The offset frame takes the <em>current</em> position offset,
  497.      * polar motion and the meridian shift into account. The frame
  498.      * returned is disconnected from later changes in the parameters.
  499.      * When the {@link ParameterDriver parameters} managing these
  500.      * offsets are changed, the method must be called again to retrieve
  501.      * a new offset frame.
  502.      * </p>
  503.      * @param inertial inertial frame to transform to
  504.      * @param date date of the transform
  505.      * @param clockOffsetAlreadyApplied if true, the specified {@code date} is as read
  506.      * by the ground station clock (i.e. clock offset <em>not</em> compensated), if false,
  507.      * the specified {@code date} was already compensated and is a physical absolute date
  508.      * @return transform between offset frame and inertial frame, at <em>real</em> measurement
  509.      * date (i.e. with clock, Earth and station offsets applied)
  510.      */
  511.     public Transform getOffsetToInertial(final Frame inertial,
  512.                                          final AbsoluteDate date, final boolean clockOffsetAlreadyApplied) {

  513.         // take clock offset into account
  514.         final AbsoluteDate offsetCompensatedDate = clockOffsetAlreadyApplied ?
  515.                                                    date :
  516.                                                    new AbsoluteDate(date, -clockOffsetDriver.getValue());

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

  519.         // take station offsets into account
  520.         final double    x          = eastOffsetDriver.getValue();
  521.         final double    y          = northOffsetDriver.getValue();
  522.         final double    z          = zenithOffsetDriver.getValue();
  523.         final BodyShape baseShape  = baseFrame.getParentShape();
  524.         final StaticTransform baseToBody = baseFrame
  525.                 .getStaticTransformTo(baseShape.getBodyFrame(), offsetCompensatedDate);
  526.         Vector3D        origin     = baseToBody.transformPosition(new Vector3D(x, y, z));
  527.         origin = origin.add(computeDisplacement(offsetCompensatedDate, origin));

  528.         final GeodeticPoint originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
  529.         final Transform offsetToIntermediate =
  530.                         new Transform(offsetCompensatedDate,
  531.                                       new Transform(offsetCompensatedDate,
  532.                                                     new Rotation(Vector3D.PLUS_I, Vector3D.PLUS_K,
  533.                                                                  originGP.getEast(), originGP.getZenith()),
  534.                                                     Vector3D.ZERO),
  535.                                       new Transform(offsetCompensatedDate, origin));

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

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

  539.     }

  540.     /** Get the transform between offset frame and inertial frame with derivatives.
  541.      * <p>
  542.      * As the East and North vectors are not well defined at pole, the derivatives
  543.      * of these two vectors diverge to infinity as we get closer to the pole.
  544.      * So this method should not be used for stations less than 0.0001 degree from
  545.      * either poles.
  546.      * </p>
  547.      * @param inertial inertial frame to transform to
  548.      * @param clockDate date of the transform as read by the ground station clock (i.e. clock offset <em>not</em> compensated)
  549.      * @param freeParameters total number of free parameters in the gradient
  550.      * @param indices indices of the estimated parameters in derivatives computations, must be driver
  551.      * span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
  552.      * @return transform between offset frame and inertial frame, at <em>real</em> measurement
  553.      * date (i.e. with clock, Earth and station offsets applied)
  554.      * @see #getOffsetToInertial(Frame, FieldAbsoluteDate, int, Map)
  555.      * @since 10.2
  556.      */
  557.     public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
  558.                                                         final AbsoluteDate clockDate,
  559.                                                         final int freeParameters,
  560.                                                         final Map<String, Integer> indices) {
  561.         // take clock offset into account
  562.         final Gradient offset = clockOffsetDriver.getValue(freeParameters, indices, clockDate);
  563.         final FieldAbsoluteDate<Gradient> offsetCompensatedDate =
  564.                         new FieldAbsoluteDate<>(clockDate, offset.negate());

  565.         return getOffsetToInertial(inertial, offsetCompensatedDate, freeParameters, indices);
  566.     }

  567.     /** Get the transform between offset frame and inertial frame with derivatives.
  568.      * <p>
  569.      * As the East and North vectors are not well defined at pole, the derivatives
  570.      * of these two vectors diverge to infinity as we get closer to the pole.
  571.      * So this method should not be used for stations less than 0.0001 degree from
  572.      * either poles.
  573.      * </p>
  574.      * @param inertial inertial frame to transform to
  575.      * @param offsetCompensatedDate date of the transform, clock offset and its derivatives already compensated
  576.      * @param freeParameters total number of free parameters in the gradient
  577.      * @param indices indices of the estimated parameters in derivatives computations, must be driver
  578.      * span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
  579.      * @return transform between offset frame and inertial frame, at specified date
  580.      * @since 10.2
  581.      */
  582.     public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
  583.                                                         final FieldAbsoluteDate<Gradient> offsetCompensatedDate,
  584.                                                         final int freeParameters,
  585.                                                         final Map<String, Integer> indices) {

  586.         final Field<Gradient>         field = offsetCompensatedDate.getField();
  587.         final FieldVector3D<Gradient> zero  = FieldVector3D.getZero(field);
  588.         final FieldVector3D<Gradient> plusI = FieldVector3D.getPlusI(field);
  589.         final FieldVector3D<Gradient> plusK = FieldVector3D.getPlusK(field);

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

  593.         // take station offsets into account
  594.         final Gradient                       x          = eastOffsetDriver.getValue(freeParameters, indices);
  595.         final Gradient                       y          = northOffsetDriver.getValue(freeParameters, indices);
  596.         final Gradient                       z          = zenithOffsetDriver.getValue(freeParameters, indices);
  597.         final BodyShape                      baseShape  = baseFrame.getParentShape();
  598.         final FieldStaticTransform<Gradient> baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), offsetCompensatedDate);

  599.         FieldVector3D<Gradient> origin = baseToBody.transformPosition(new FieldVector3D<>(x, y, z));
  600.         origin = origin.add(computeDisplacement(offsetCompensatedDate.toAbsoluteDate(), origin.toVector3D()));
  601.         final FieldGeodeticPoint<Gradient> originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
  602.         final FieldTransform<Gradient> offsetToIntermediate =
  603.                         new FieldTransform<>(offsetCompensatedDate,
  604.                                              new FieldTransform<>(offsetCompensatedDate,
  605.                                                                   new FieldRotation<>(plusI, plusK,
  606.                                                                                       originGP.getEast(), originGP.getZenith()),
  607.                                                                   zero),
  608.                                              new FieldTransform<>(offsetCompensatedDate, origin));

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

  611.         return new FieldTransform<>(offsetCompensatedDate,
  612.                                     offsetToIntermediate,
  613.                                     new FieldTransform<>(offsetCompensatedDate, intermediateToBody, bodyToInert));

  614.     }

  615. }