TDOA.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.Arrays;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.analysis.differentiation.Gradient;
  21. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  22. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.frames.FieldTransform;
  25. import org.orekit.frames.Transform;
  26. import org.orekit.propagation.SpacecraftState;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.time.FieldAbsoluteDate;
  29. import org.orekit.utils.Constants;
  30. import org.orekit.utils.ParameterDriver;
  31. import org.orekit.utils.TimeSpanMap.Span;
  32. import org.orekit.utils.TimeStampedFieldPVCoordinates;
  33. import org.orekit.utils.TimeStampedPVCoordinates;

  34. /** Class modeling a Time Difference of Arrival measurement with a satellite as emitter
  35.  * and two ground stations as receivers.
  36.  * <p>
  37.  * TDOA measures the difference in signal arrival time between the emitter and receivers,
  38.  * corresponding to a difference in ranges from the two receivers to the emitter.
  39.  * </p><p>
  40.  * The date of the measurement corresponds to the reception of the signal by the prime station.
  41.  * The measurement corresponds to the date of the measurement minus
  42.  * the date of reception of the signal by the second station:
  43.  * <code>tdoa = tr<sub>1</sub> - tr<sub>2</sub></code>
  44.  * </p><p>
  45.  * The motion of the stations and the satellite during the signal flight time are taken into account.
  46.  * </p>
  47.  * @author Pascal Parraud
  48.  * @since 11.2
  49.  */
  50. public class TDOA extends GroundReceiverMeasurement<TDOA> {

  51.     /** Type of the measurement. */
  52.     public static final String MEASUREMENT_TYPE = "TDOA";

  53.     /** Second ground station, the one that gives the measurement, i.e. the delay. */
  54.     private final GroundStation secondStation;

  55.     /** Simple constructor.
  56.      * @param primeStation ground station that gives the date of the measurement
  57.      * @param secondStation ground station that gives the measurement
  58.      * @param date date of the measurement
  59.      * @param tdoa observed value (s)
  60.      * @param sigma theoretical standard deviation
  61.      * @param baseWeight base weight
  62.      * @param satellite satellite related to this measurement
  63.      */
  64.     public TDOA(final GroundStation primeStation, final GroundStation secondStation,
  65.                 final AbsoluteDate date, final double tdoa, final double sigma,
  66.                 final double baseWeight, final ObservableSatellite satellite) {
  67.         super(primeStation, false, date, tdoa, sigma, baseWeight, satellite);

  68.         // add parameter drivers for the secondary station
  69.         addParameterDriver(secondStation.getClockOffsetDriver());
  70.         addParameterDriver(secondStation.getEastOffsetDriver());
  71.         addParameterDriver(secondStation.getNorthOffsetDriver());
  72.         addParameterDriver(secondStation.getZenithOffsetDriver());
  73.         addParameterDriver(secondStation.getPrimeMeridianOffsetDriver());
  74.         addParameterDriver(secondStation.getPrimeMeridianDriftDriver());
  75.         addParameterDriver(secondStation.getPolarOffsetXDriver());
  76.         addParameterDriver(secondStation.getPolarDriftXDriver());
  77.         addParameterDriver(secondStation.getPolarOffsetYDriver());
  78.         addParameterDriver(secondStation.getPolarDriftYDriver());
  79.         this.secondStation = secondStation;

  80.     }

  81.     /** Get the prime ground station, the one that gives the date of the measurement.
  82.      * @return prime ground station
  83.      */
  84.     public GroundStation getPrimeStation() {
  85.         return getStation();
  86.     }

  87.     /** Get the second ground station, the one that gives the measurement.
  88.      * @return second ground station
  89.      */
  90.     public GroundStation getSecondStation() {
  91.         return secondStation;
  92.     }

  93.     /** {@inheritDoc} */
  94.     @SuppressWarnings("checkstyle:WhitespaceAround")
  95.     @Override
  96.     protected EstimatedMeasurementBase<TDOA> theoreticalEvaluationWithoutDerivatives(final int iteration, final int evaluation,
  97.                                                                                      final SpacecraftState[] states) {

  98.         final GroundReceiverCommonParametersWithoutDerivatives common = computeCommonParametersWithout(states[0]);
  99.         final TimeStampedPVCoordinates emitterPV = common.getTransitPV();
  100.         final AbsoluteDate emitterDate = emitterPV.getDate();

  101.         // Approximate second location at transit time
  102.         final Transform secondToInertial =
  103.                 getSecondStation().getOffsetToInertial(common.getState().getFrame(), emitterDate, true);
  104.         final TimeStampedPVCoordinates secondApprox =
  105.                 secondToInertial.transformPVCoordinates(new TimeStampedPVCoordinates(emitterDate,
  106.                         Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));

  107.         // Time of flight from emitter to second station
  108.         final double tau2 = forwardSignalTimeOfFlight(secondApprox, emitterPV.getPosition(), emitterDate);

  109.         // Secondary station PV in inertial frame at receive at second station
  110.         final TimeStampedPVCoordinates secondPV = secondApprox.shiftedBy(tau2);

  111.         // The measured TDOA is (tau1 + clockOffset1) - (tau2 + clockOffset2)
  112.         final double offset1 = getPrimeStation().getClockOffsetDriver().getValue(emitterDate);
  113.         final double offset2 = getSecondStation().getClockOffsetDriver().getValue(emitterDate);
  114.         final double tdoa = (common.getTauD() + offset1) - (tau2 + offset2);

  115.         // Evaluate the TDOA value
  116.         // -------------------------------------------
  117.         final EstimatedMeasurement<TDOA> estimated =
  118.                 new EstimatedMeasurement<>(this, iteration, evaluation,
  119.                         new SpacecraftState[] {
  120.                             common.getTransitState()
  121.                         },
  122.                         new TimeStampedPVCoordinates[] {
  123.                             emitterPV,
  124.                             tdoa > 0.0 ? secondPV : common.getStationDownlink(),
  125.                             tdoa > 0.0 ? common.getStationDownlink() : secondPV
  126.                         });

  127.         // set TDOA value
  128.         estimated.setEstimatedValue(tdoa);

  129.         return estimated;
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     protected EstimatedMeasurement<TDOA> theoreticalEvaluation(final int iteration, final int evaluation,
  134.                                                                final SpacecraftState[] states) {

  135.         final SpacecraftState state = states[0];

  136.         // TDOA derivatives are computed with respect to spacecraft state in inertial frame
  137.         // and station parameters
  138.         // ----------------------
  139.         //
  140.         // Parameters:
  141.         //  - 0..2 - Position of the spacecraft in inertial frame
  142.         //  - 3..5 - Velocity of the spacecraft in inertial frame
  143.         //  - 6..n - measurements parameters (clock offset, station offsets, pole, prime meridian, sat clock offset...)
  144.         final GroundReceiverCommonParametersWithDerivatives common = computeCommonParametersWithDerivatives(state);
  145.         final int nbParams = common.getTauD().getFreeParameters();
  146.         final TimeStampedFieldPVCoordinates<Gradient> emitterPV = common.getTransitPV();
  147.         final FieldAbsoluteDate<Gradient> emitterDate = emitterPV.getDate();

  148.         // Approximate secondary location (at emission time)
  149.         final FieldVector3D<Gradient> zero = FieldVector3D.getZero(common.getTauD().getField());
  150.         final FieldTransform<Gradient> secondToInertial =
  151.                 getSecondStation().getOffsetToInertial(state.getFrame(), emitterDate, nbParams, common.getIndices());
  152.         final TimeStampedFieldPVCoordinates<Gradient> secondApprox =
  153.                 secondToInertial.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(emitterDate,
  154.                         zero, zero, zero));

  155.         // Time of flight from emitter to second station
  156.         final Gradient tau2 = forwardSignalTimeOfFlight(secondApprox, emitterPV.getPosition(), emitterDate);

  157.         // Second station coordinates at receive time
  158.         final TimeStampedFieldPVCoordinates<Gradient> secondPV = secondApprox.shiftedBy(tau2);

  159.         // The measured TDOA is (tau1 + clockOffset1) - (tau2 + clockOffset2)
  160.         final Gradient offset1 = getPrimeStation().getClockOffsetDriver()
  161.                 .getValue(nbParams, common.getIndices(), emitterDate.toAbsoluteDate());
  162.         final Gradient offset2 = getSecondStation().getClockOffsetDriver()
  163.                 .getValue(nbParams, common.getIndices(), emitterDate.toAbsoluteDate());
  164.         final Gradient tdoaG   = common.getTauD().add(offset1).subtract(tau2.add(offset2));
  165.         final double tdoa      = tdoaG.getValue();

  166.         // Evaluate the TDOA value and derivatives
  167.         // -------------------------------------------
  168.         final TimeStampedPVCoordinates pv1 = common.getStationDownlink().toTimeStampedPVCoordinates();
  169.         final TimeStampedPVCoordinates pv2 = secondPV.toTimeStampedPVCoordinates();
  170.         final EstimatedMeasurement<TDOA> estimated =
  171.                         new EstimatedMeasurement<>(this, iteration, evaluation,
  172.                                                    new SpacecraftState[] {
  173.                                                        common.getTransitState()
  174.                                                    },
  175.                                                    new TimeStampedPVCoordinates[] {
  176.                                                        emitterPV.toTimeStampedPVCoordinates(),
  177.                                                        tdoa > 0 ? pv2 : pv1,
  178.                                                        tdoa > 0 ? pv1 : pv2
  179.                                                    });

  180.         // set TDOA value
  181.         estimated.setEstimatedValue(tdoa);

  182.         // set first order derivatives with respect to state
  183.         final double[] derivatives = tdoaG.getGradient();
  184.         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));

  185.         // Set first order derivatives with respect to parameters
  186.         for (final ParameterDriver driver : getParametersDrivers()) {
  187.             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  188.                 final Integer index = common.getIndices().get(span.getData());
  189.                 if (index != null) {
  190.                     estimated.setParameterDerivatives(driver, span.getStart(), derivatives[index]);
  191.                 }
  192.             }
  193.         }

  194.         return estimated;
  195.     }


  196.     /** Compute propagation delay on a link leg (typically downlink or uplink).  This differs from signalTimeOfFlight
  197.      * through <em>advancing</em> rather than delaying the emitter.
  198.      *
  199.      * @param adjustableEmitterPV position/velocity of emitter that may be adjusted
  200.      * @param receiverPosition fixed position of receiver at {@code signalArrivalDate},
  201.      * in the same frame as {@code adjustableEmitterPV}
  202.      * @param signalArrivalDate date at which the signal arrives to receiver
  203.      * @return <em>positive</em> delay between signal emission and signal reception dates
  204.      */
  205.     public static double forwardSignalTimeOfFlight(final TimeStampedPVCoordinates adjustableEmitterPV,
  206.                                                    final Vector3D receiverPosition,
  207.                                                    final AbsoluteDate signalArrivalDate) {

  208.         // initialize emission date search loop assuming the state is already correct
  209.         // this will be true for all but the first orbit determination iteration,
  210.         // and even for the first iteration the loop will converge very fast
  211.         final double offset = signalArrivalDate.durationFrom(adjustableEmitterPV.getDate());
  212.         double delay = offset;

  213.         // search signal transit date, computing the signal travel in inertial frame
  214.         final double cReciprocal = 1.0 / Constants.SPEED_OF_LIGHT;
  215.         double delta;
  216.         int count = 0;
  217.         do {
  218.             final double previous   = delay;
  219.             final Vector3D transitP = adjustableEmitterPV.shiftedBy(delay - offset).getPosition();
  220.             delay                   = receiverPosition.distance(transitP) * cReciprocal;
  221.             delta                   = FastMath.abs(delay - previous);
  222.         } while (count++ < 10 && delta >= 2 * FastMath.ulp(delay));

  223.         return delay;

  224.     }

  225.     /** Compute propagation delay on a link leg (typically downlink or uplink).This differs from signalTimeOfFlight
  226.      * through <em>advancing</em> rather than delaying the emitter.
  227.      *
  228.      * @param adjustableEmitterPV position/velocity of emitter that may be adjusted
  229.      * @param receiverPosition fixed position of receiver at {@code signalArrivalDate},
  230.      * in the same frame as {@code adjustableEmitterPV}
  231.      * @param signalArrivalDate date at which the signal arrives to receiver
  232.      * @return <em>positive</em> delay between signal emission and signal reception dates
  233.      * @param <T> the type of the components
  234.      */
  235.     public static <T extends CalculusFieldElement<T>> T forwardSignalTimeOfFlight(final TimeStampedFieldPVCoordinates<T> adjustableEmitterPV,
  236.                                                                                   final FieldVector3D<T> receiverPosition,
  237.                                                                                   final FieldAbsoluteDate<T> signalArrivalDate) {

  238.         // Initialize emission date search loop assuming the emitter PV is almost correct
  239.         // this will be true for all but the first orbit determination iteration,
  240.         // and even for the first iteration the loop will converge extremely fast
  241.         final T offset = signalArrivalDate.durationFrom(adjustableEmitterPV.getDate());
  242.         T delay = offset;

  243.         // search signal transit date, computing the signal travel in the frame shared by emitter and receiver
  244.         final double cReciprocal = 1.0 / Constants.SPEED_OF_LIGHT;
  245.         double delta;
  246.         int count = 0;
  247.         do {
  248.             final double previous           = delay.getReal();
  249.             final FieldVector3D<T> transitP = adjustableEmitterPV.shiftedBy(delay.subtract(offset)).getPosition();
  250.             delay                           = receiverPosition.distance(transitP).multiply(cReciprocal);
  251.             delta                           = FastMath.abs(delay.getReal() - previous);
  252.         } while (count++ < 10 && delta >= 2 * FastMath.ulp(delay.getReal()));

  253.         return delay;
  254.     }

  255. }