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  
19  import java.util.Arrays;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.analysis.differentiation.Gradient;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.geometry.euclidean.threed.Vector3D;
25  import org.hipparchus.util.FastMath;
26  import org.orekit.frames.FieldTransform;
27  import org.orekit.frames.Transform;
28  import org.orekit.propagation.SpacecraftState;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.FieldAbsoluteDate;
31  import org.orekit.utils.Constants;
32  import org.orekit.utils.ParameterDriver;
33  import org.orekit.utils.TimeSpanMap.Span;
34  import org.orekit.utils.TimeStampedFieldPVCoordinates;
35  import org.orekit.utils.TimeStampedPVCoordinates;
36  
37  /** Class modeling a Time Difference of Arrival measurement with a satellite as emitter
38   * and two ground stations as receivers.
39   * <p>
40   * TDOA measures the difference in signal arrival time between the emitter and receivers,
41   * corresponding to a difference in ranges from the two receivers to the emitter.
42   * </p><p>
43   * The date of the measurement corresponds to the reception of the signal by the prime station.
44   * The measurement corresponds to the date of the measurement minus
45   * the date of reception of the signal by the second station:
46   * <code>tdoa = tr<sub>1</sub> - tr<sub>2</sub></code>
47   * </p><p>
48   * The motion of the stations and the satellite during the signal flight time are taken into account.
49   * </p>
50   * @author Pascal Parraud
51   * @since 11.2
52   */
53  public class TDOA extends GroundReceiverMeasurement<TDOA> {
54  
55      /** Type of the measurement. */
56      public static final String MEASUREMENT_TYPE = "TDOA";
57  
58      /** Second ground station, the one that gives the measurement, i.e. the delay. */
59      private final GroundStation secondStation;
60  
61      /** Simple constructor.
62       * @param primeStation ground station that gives the date of the measurement
63       * @param secondStation ground station that gives the measurement
64       * @param date date of the measurement
65       * @param tdoa observed value (s)
66       * @param sigma theoretical standard deviation
67       * @param baseWeight base weight
68       * @param satellite satellite related to this measurement
69       */
70      public TDOA(final GroundStation primeStation, final GroundStation secondStation,
71                  final AbsoluteDate date, final double tdoa, final double sigma,
72                  final double baseWeight, final ObservableSatellite satellite) {
73          super(primeStation, false, date, tdoa, sigma, baseWeight, satellite);
74  
75          // add parameter drivers for the secondary station
76          addParameterDriver(secondStation.getClockOffsetDriver());
77          addParameterDriver(secondStation.getEastOffsetDriver());
78          addParameterDriver(secondStation.getNorthOffsetDriver());
79          addParameterDriver(secondStation.getZenithOffsetDriver());
80          addParameterDriver(secondStation.getPrimeMeridianOffsetDriver());
81          addParameterDriver(secondStation.getPrimeMeridianDriftDriver());
82          addParameterDriver(secondStation.getPolarOffsetXDriver());
83          addParameterDriver(secondStation.getPolarDriftXDriver());
84          addParameterDriver(secondStation.getPolarOffsetYDriver());
85          addParameterDriver(secondStation.getPolarDriftYDriver());
86          this.secondStation = secondStation;
87  
88      }
89  
90      /** Get the prime ground station, the one that gives the date of the measurement.
91       * @return prime ground station
92       */
93      public GroundStation getPrimeStation() {
94          return getStation();
95      }
96  
97      /** Get the second ground station, the one that gives the measurement.
98       * @return second ground station
99       */
100     public GroundStation getSecondStation() {
101         return secondStation;
102     }
103 
104     /** {@inheritDoc} */
105     @SuppressWarnings("checkstyle:WhitespaceAround")
106     @Override
107     protected EstimatedMeasurementBase<TDOA> theoreticalEvaluationWithoutDerivatives(final int iteration, final int evaluation,
108                                                                                      final SpacecraftState[] states) {
109 
110         final GroundReceiverCommonParametersWithoutDerivatives common = computeCommonParametersWithout(states[0]);
111         final TimeStampedPVCoordinates emitterPV = common.getTransitPV();
112         final AbsoluteDate emitterDate = emitterPV.getDate();
113 
114         // Approximate second location at transit time
115         final Transform secondToInertial =
116                 getSecondStation().getOffsetToInertial(common.getState().getFrame(), emitterDate, true);
117         final TimeStampedPVCoordinates secondApprox =
118                 secondToInertial.transformPVCoordinates(new TimeStampedPVCoordinates(emitterDate,
119                         Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));
120 
121         // Time of flight from emitter to second station
122         final double tau2 = forwardSignalTimeOfFlight(secondApprox, emitterPV.getPosition(), emitterDate);
123 
124         // Secondary station PV in inertial frame at receive at second station
125         final TimeStampedPVCoordinates secondPV = secondApprox.shiftedBy(tau2);
126 
127         // The measured TDOA is (tau1 + clockOffset1) - (tau2 + clockOffset2)
128         final double offset1 = getPrimeStation().getClockOffsetDriver().getValue(emitterDate);
129         final double offset2 = getSecondStation().getClockOffsetDriver().getValue(emitterDate);
130         final double tdoa = (common.getTauD() + offset1) - (tau2 + offset2);
131 
132         // Evaluate the TDOA value
133         // -------------------------------------------
134         final EstimatedMeasurement<TDOA> estimated =
135                 new EstimatedMeasurement<>(this, iteration, evaluation,
136                         new SpacecraftState[] {
137                             common.getTransitState()
138                         },
139                         new TimeStampedPVCoordinates[] {
140                             emitterPV,
141                             tdoa > 0.0 ? secondPV : common.getStationDownlink(),
142                             tdoa > 0.0 ? common.getStationDownlink() : secondPV
143                         });
144 
145         // set TDOA value
146         estimated.setEstimatedValue(tdoa);
147 
148         return estimated;
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     protected EstimatedMeasurement<TDOA> theoreticalEvaluation(final int iteration, final int evaluation,
154                                                                final SpacecraftState[] states) {
155 
156         final SpacecraftState state = states[0];
157 
158         // TDOA derivatives are computed with respect to spacecraft state in inertial frame
159         // and station parameters
160         // ----------------------
161         //
162         // Parameters:
163         //  - 0..2 - Position of the spacecraft in inertial frame
164         //  - 3..5 - Velocity of the spacecraft in inertial frame
165         //  - 6..n - measurements parameters (clock offset, station offsets, pole, prime meridian, sat clock offset...)
166         final GroundReceiverCommonParametersWithDerivatives common = computeCommonParametersWithDerivatives(state);
167         final int nbParams = common.getTauD().getFreeParameters();
168         final TimeStampedFieldPVCoordinates<Gradient> emitterPV = common.getTransitPV();
169         final FieldAbsoluteDate<Gradient> emitterDate = emitterPV.getDate();
170 
171         // Approximate secondary location (at emission time)
172         final FieldVector3D<Gradient> zero = FieldVector3D.getZero(common.getTauD().getField());
173         final FieldTransform<Gradient> secondToInertial =
174                 getSecondStation().getOffsetToInertial(state.getFrame(), emitterDate, nbParams, common.getIndices());
175         final TimeStampedFieldPVCoordinates<Gradient> secondApprox =
176                 secondToInertial.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(emitterDate,
177                         zero, zero, zero));
178 
179         // Time of flight from emitter to second station
180         final Gradient tau2 = forwardSignalTimeOfFlight(secondApprox, emitterPV.getPosition(), emitterDate);
181 
182         // Second station coordinates at receive time
183         final TimeStampedFieldPVCoordinates<Gradient> secondPV = secondApprox.shiftedBy(tau2);
184 
185         // The measured TDOA is (tau1 + clockOffset1) - (tau2 + clockOffset2)
186         final Gradient offset1 = getPrimeStation().getClockOffsetDriver()
187                 .getValue(nbParams, common.getIndices(), emitterDate.toAbsoluteDate());
188         final Gradient offset2 = getSecondStation().getClockOffsetDriver()
189                 .getValue(nbParams, common.getIndices(), emitterDate.toAbsoluteDate());
190         final Gradient tdoaG   = common.getTauD().add(offset1).subtract(tau2.add(offset2));
191         final double tdoa      = tdoaG.getValue();
192 
193         // Evaluate the TDOA value and derivatives
194         // -------------------------------------------
195         final TimeStampedPVCoordinates pv1 = common.getStationDownlink().toTimeStampedPVCoordinates();
196         final TimeStampedPVCoordinates pv2 = secondPV.toTimeStampedPVCoordinates();
197         final EstimatedMeasurement<TDOA> estimated =
198                         new EstimatedMeasurement<>(this, iteration, evaluation,
199                                                    new SpacecraftState[] {
200                                                        common.getTransitState()
201                                                    },
202                                                    new TimeStampedPVCoordinates[] {
203                                                        emitterPV.toTimeStampedPVCoordinates(),
204                                                        tdoa > 0 ? pv2 : pv1,
205                                                        tdoa > 0 ? pv1 : pv2
206                                                    });
207 
208         // set TDOA value
209         estimated.setEstimatedValue(tdoa);
210 
211         // set first order derivatives with respect to state
212         final double[] derivatives = tdoaG.getGradient();
213         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));
214 
215         // Set first order derivatives with respect to parameters
216         for (final ParameterDriver driver : getParametersDrivers()) {
217             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
218                 final Integer index = common.getIndices().get(span.getData());
219                 if (index != null) {
220                     estimated.setParameterDerivatives(driver, span.getStart(), derivatives[index]);
221                 }
222             }
223         }
224 
225         return estimated;
226     }
227 
228 
229     /** Compute propagation delay on a link leg (typically downlink or uplink).  This differs from signalTimeOfFlight
230      * through <em>advancing</em> rather than delaying the emitter.
231      *
232      * @param adjustableEmitterPV position/velocity of emitter that may be adjusted
233      * @param receiverPosition fixed position of receiver at {@code signalArrivalDate},
234      * in the same frame as {@code adjustableEmitterPV}
235      * @param signalArrivalDate date at which the signal arrives to receiver
236      * @return <em>positive</em> delay between signal emission and signal reception dates
237      */
238     public static double forwardSignalTimeOfFlight(final TimeStampedPVCoordinates adjustableEmitterPV,
239                                                    final Vector3D receiverPosition,
240                                                    final AbsoluteDate signalArrivalDate) {
241 
242         // initialize emission date search loop assuming the state is already correct
243         // this will be true for all but the first orbit determination iteration,
244         // and even for the first iteration the loop will converge very fast
245         final double offset = signalArrivalDate.durationFrom(adjustableEmitterPV.getDate());
246         double delay = offset;
247 
248         // search signal transit date, computing the signal travel in inertial frame
249         final double cReciprocal = 1.0 / Constants.SPEED_OF_LIGHT;
250         double delta;
251         int count = 0;
252         do {
253             final double previous   = delay;
254             final Vector3D transitP = adjustableEmitterPV.shiftedBy(delay - offset).getPosition();
255             delay                   = receiverPosition.distance(transitP) * cReciprocal;
256             delta                   = FastMath.abs(delay - previous);
257         } while (count++ < 10 && delta >= 2 * FastMath.ulp(delay));
258 
259         return delay;
260 
261     }
262 
263     /** Compute propagation delay on a link leg (typically downlink or uplink).This differs from signalTimeOfFlight
264      * through <em>advancing</em> rather than delaying the emitter.
265      *
266      * @param adjustableEmitterPV position/velocity of emitter that may be adjusted
267      * @param receiverPosition fixed position of receiver at {@code signalArrivalDate},
268      * in the same frame as {@code adjustableEmitterPV}
269      * @param signalArrivalDate date at which the signal arrives to receiver
270      * @return <em>positive</em> delay between signal emission and signal reception dates
271      * @param <T> the type of the components
272      */
273     public static <T extends CalculusFieldElement<T>> T forwardSignalTimeOfFlight(final TimeStampedFieldPVCoordinates<T> adjustableEmitterPV,
274                                                                                   final FieldVector3D<T> receiverPosition,
275                                                                                   final FieldAbsoluteDate<T> signalArrivalDate) {
276 
277         // Initialize emission date search loop assuming the emitter PV is almost correct
278         // this will be true for all but the first orbit determination iteration,
279         // and even for the first iteration the loop will converge extremely fast
280         final T offset = signalArrivalDate.durationFrom(adjustableEmitterPV.getDate());
281         T delay = offset;
282 
283         // search signal transit date, computing the signal travel in the frame shared by emitter and receiver
284         final double cReciprocal = 1.0 / Constants.SPEED_OF_LIGHT;
285         double delta;
286         int count = 0;
287         do {
288             final double previous           = delay.getReal();
289             final FieldVector3D<T> transitP = adjustableEmitterPV.shiftedBy(delay.subtract(offset)).getPosition();
290             delay                           = receiverPosition.distance(transitP).multiply(cReciprocal);
291             delta                           = FastMath.abs(delay.getReal() - previous);
292         } while (count++ < 10 && delta >= 2 * FastMath.ulp(delay.getReal()));
293 
294         return delay;
295     }
296 
297 }