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  import java.util.HashMap;
21  import java.util.Map;
22  
23  import org.hipparchus.Field;
24  import org.hipparchus.analysis.differentiation.Gradient;
25  import org.hipparchus.analysis.differentiation.GradientField;
26  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.orekit.frames.FieldTransform;
29  import org.orekit.frames.Transform;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.FieldAbsoluteDate;
33  import org.orekit.utils.Constants;
34  import org.orekit.utils.FieldPVCoordinates;
35  import org.orekit.utils.PVCoordinates;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.TimeSpanMap.Span;
38  import org.orekit.utils.TimeStampedFieldPVCoordinates;
39  import org.orekit.utils.TimeStampedPVCoordinates;
40  
41  /** Class modeling a turn-around range measurement using a primary ground station and a secondary ground station.
42   * <p>
43   * The measurement is considered to be a signal:
44   * - Emitted from the primary ground station
45   * - Reflected on the spacecraft
46   * - Reflected on the secondary ground station
47   * - Reflected on the spacecraft again
48   * - Received on the primary ground station
49   * Its value is the elapsed time between emission and reception
50   * divided by 2c were c is the speed of light.
51   * The motion of the stations and the spacecraft
52   * during the signal flight time are taken into account.
53   * The date of the measurement corresponds to the
54   * reception on ground of the reflected signal.
55   * </p>
56   * @author Thierry Ceolin
57   * @author Luc Maisonobe
58   * @author Maxime Journot
59   *
60   * @since 9.0
61   */
62  public class TurnAroundRange extends GroundReceiverMeasurement<TurnAroundRange> {
63  
64      /** Type of the measurement. */
65      public static final String MEASUREMENT_TYPE = "TurnAroundRange";
66  
67      /** Secondary ground station reflecting the signal. */
68      private final GroundStation secondaryStation;
69  
70      /** Simple constructor.
71       * @param primaryStation ground station from which measurement is performed
72       * @param secondaryStation ground station reflecting the signal
73       * @param date date of the measurement
74       * @param turnAroundRange observed value
75       * @param sigma theoretical standard deviation
76       * @param baseWeight base weight
77       * @param satellite satellite related to this measurement
78       * @since 9.3
79       */
80      public TurnAroundRange(final GroundStation primaryStation, final GroundStation secondaryStation,
81                             final AbsoluteDate date, final double turnAroundRange,
82                             final double sigma, final double baseWeight,
83                             final ObservableSatellite satellite) {
84          super(primaryStation, true, date, turnAroundRange, sigma, baseWeight, satellite);
85  
86          // the secondary station clock is not used at all, we ignore the corresponding parameter driver
87          addParameterDriver(secondaryStation.getEastOffsetDriver());
88          addParameterDriver(secondaryStation.getNorthOffsetDriver());
89          addParameterDriver(secondaryStation.getZenithOffsetDriver());
90          addParameterDriver(secondaryStation.getPrimeMeridianOffsetDriver());
91          addParameterDriver(secondaryStation.getPrimeMeridianDriftDriver());
92          addParameterDriver(secondaryStation.getPolarOffsetXDriver());
93          addParameterDriver(secondaryStation.getPolarDriftXDriver());
94          addParameterDriver(secondaryStation.getPolarOffsetYDriver());
95          addParameterDriver(secondaryStation.getPolarDriftYDriver());
96          this.secondaryStation = secondaryStation;
97  
98      }
99  
100     /** Get the primary ground station from which measurement is performed.
101      * @return primary ground station from which measurement is performed
102      */
103     public GroundStation getPrimaryStation() {
104         return getStation();
105     }
106 
107     /** Get the secondary ground station reflecting the signal.
108      * @return secondary ground station reflecting the signal
109      */
110     public GroundStation getSecondaryStation() {
111         return secondaryStation;
112     }
113 
114     /** {@inheritDoc} */
115     @Override
116     protected EstimatedMeasurementBase<TurnAroundRange> theoreticalEvaluationWithoutDerivatives(final int iteration,
117                                                                                                 final int evaluation,
118                                                                                                 final SpacecraftState[] states) {
119 
120         final SpacecraftState state = states[0];
121 
122         // Time-stamped PV
123         final TimeStampedPVCoordinates pva = state.getPVCoordinates();
124 
125         // The path of the signal is divided in two legs.
126         // Leg1: Emission from primary station to satellite in primaryTauU seconds
127         //     + Reflection from satellite to secondary station in secondaryTauD seconds
128         // Leg2: Reflection from secondary station to satellite in secondaryTauU seconds
129         //     + Reflection from satellite to primary station in primaryTaudD seconds
130         // The measurement is considered to be time stamped at reception on ground
131         // by the primary station. All times are therefore computed as backward offsets
132         // with respect to this reception time.
133         //
134         // Two intermediate spacecraft states are defined:
135         //  - transitStateLeg2: State of the satellite when it bounced back the signal
136         //                      from secondary station to primary station during the 2nd leg
137         //  - transitStateLeg1: State of the satellite when it bounced back the signal
138         //                      from primary station to secondary station during the 1st leg
139 
140         // Compute propagation time for the 2nd leg of the signal path
141         // --
142 
143         // Time difference between t (date of the measurement) and t' (date tagged in spacecraft state)
144         // (if state has already been set up to pre-compensate propagation delay,
145         // we will have delta = primaryTauD + secondaryTauU)
146         final double delta = getDate().durationFrom(state.getDate());
147 
148         // transform between primary station topocentric frame (east-north-zenith) and inertial frame expressed as gradients
149         final Transform primaryToInert =
150                         getStation().getOffsetToInertial(state.getFrame(), getDate(), false);
151         final AbsoluteDate measurementDate = primaryToInert.getDate();
152 
153         // Primary station PV in inertial frame at measurement date
154         final TimeStampedPVCoordinates primaryArrival =
155                         primaryToInert.transformPVCoordinates(new TimeStampedPVCoordinates(measurementDate,
156                                                                                            Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));
157 
158         // Compute propagation times
159         final double primaryTauD = signalTimeOfFlightAdjustableEmitter(pva, primaryArrival.getPosition(), measurementDate,
160                                                                        state.getFrame());
161 
162         // Elapsed time between state date t' and signal arrival to the transit state of the 2nd leg
163         final double dtLeg2 = delta - primaryTauD;
164 
165         // Transit state where the satellite reflected the signal from secondary to primary station
166         final SpacecraftState transitStateLeg2 = state.shiftedBy(dtLeg2);
167 
168         // Transit state pv of leg2 (re)computed with gradient
169         final TimeStampedPVCoordinates transitStateLeg2PV = pva.shiftedBy(dtLeg2);
170 
171         // transform between secondary station topocentric frame (east-north-zenith) and inertial frame expressed as gradients
172         // The components of secondary station's position in offset frame are the 3 last derivative parameters
173         final AbsoluteDate approxReboundDate = measurementDate.shiftedBy(-delta);
174         final Transform secondaryToInertApprox =
175                         secondaryStation.getOffsetToInertial(state.getFrame(), approxReboundDate, true);
176 
177         // Secondary station PV in inertial frame at approximate rebound date on secondary station
178         final TimeStampedPVCoordinates QSecondaryApprox =
179                         secondaryToInertApprox.transformPVCoordinates(new TimeStampedPVCoordinates(approxReboundDate,
180                                                                                                    Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));
181 
182         // Uplink time of flight from secondary station to transit state of leg2
183         final double secondaryTauU = signalTimeOfFlightAdjustableEmitter(QSecondaryApprox,
184                                                                          transitStateLeg2PV.getPosition(),
185                                                                          transitStateLeg2PV.getDate(),
186                                                                          state.getFrame());
187 
188         // Total time of flight for leg 2
189         final double tauLeg2 = primaryTauD + secondaryTauU;
190 
191         // Compute propagation time for the 1st leg of the signal path
192         // --
193 
194         // Absolute date of rebound of the signal to secondary station
195         final AbsoluteDate reboundDate   = measurementDate.shiftedBy(-tauLeg2);
196         final Transform secondaryToInert = secondaryStation.getOffsetToInertial(state.getFrame(), reboundDate, true);
197 
198         // Secondary station PV in inertial frame at rebound date on secondary station
199         final TimeStampedPVCoordinates secondaryRebound =
200                         secondaryToInert.transformPVCoordinates(new TimeStampedPVCoordinates(reboundDate,
201                                                                                              Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));
202 
203         // Downlink time of flight from transitStateLeg1 to secondary station at rebound date
204         final double secondaryTauD = signalTimeOfFlightAdjustableEmitter(transitStateLeg2PV,
205                                                                          secondaryRebound.getPosition(),
206                                                                          reboundDate,
207                                                                          state.getFrame());
208 
209 
210         // Elapsed time between state date t' and signal arrival to the transit state of the 1st leg
211         final double dtLeg1 = dtLeg2 - secondaryTauU - secondaryTauD;
212 
213         // Transit state pv of leg2 (re)computed
214         final TimeStampedPVCoordinates transitStateLeg1PV = pva.shiftedBy(dtLeg1);
215 
216         // transform between primary station topocentric frame (east-north-zenith) and inertial frame
217         final AbsoluteDate approxEmissionDate = measurementDate.shiftedBy(-2 * (secondaryTauU + primaryTauD));
218         final Transform primaryToInertApprox  = getStation().getOffsetToInertial(state.getFrame(), approxEmissionDate, true);
219 
220         // Primary station PV in inertial frame at approximate emission date
221         final TimeStampedPVCoordinates QPrimaryApprox =
222                         primaryToInertApprox.transformPVCoordinates(new TimeStampedPVCoordinates(approxEmissionDate,
223                                                                                                  Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));
224 
225         // Uplink time of flight from primary station to transit state of leg1
226         final double primaryTauU = signalTimeOfFlightAdjustableEmitter(QPrimaryApprox,
227                                                                        transitStateLeg1PV.getPosition(),
228                                                                        transitStateLeg1PV.getDate(),
229                                                                        state.getFrame());
230 
231         // Primary station PV in inertial frame at exact emission date
232         final AbsoluteDate emissionDate = transitStateLeg1PV.getDate().shiftedBy(-primaryTauU);
233         final TimeStampedPVCoordinates primaryDeparture =
234                         primaryToInertApprox.shiftedBy(emissionDate.durationFrom(primaryToInertApprox.getDate())).
235                         transformPVCoordinates(new TimeStampedPVCoordinates(emissionDate, PVCoordinates.ZERO));
236 
237         // Total time of flight for leg 1
238         final double tauLeg1 = secondaryTauD + primaryTauU;
239 
240 
241         // --
242         // Evaluate the turn-around range value and its derivatives
243         // --------------------------------------------------------
244 
245         // The state we use to define the estimated measurement is a middle ground between the two transit states
246         // This is done to avoid calling "SpacecraftState.shiftedBy" function on long duration
247         // Thus we define the state at the date t" = date of rebound of the signal at the secondary station
248         // Or t" = t -primaryTauD -secondaryTauU
249         // The iterative process in the estimation ensures that, after several iterations, the date stamped in the
250         // state S in input of this function will be close to t"
251         // Therefore we will shift state S by:
252         //  - +secondaryTauU to get transitStateLeg2
253         //  - -secondaryTauD to get transitStateLeg1
254         final EstimatedMeasurementBase<TurnAroundRange> estimated =
255                         new EstimatedMeasurementBase<>(this, iteration, evaluation,
256                                                    new SpacecraftState[] {
257                                                        transitStateLeg2.shiftedBy(-secondaryTauU)
258                                                    },
259                                                    new TimeStampedPVCoordinates[] {
260                                                        primaryDeparture,
261                                                        transitStateLeg1PV,
262                                                        secondaryRebound,
263                                                        transitStateLeg2.getPVCoordinates(),
264                                                        primaryArrival
265                                                    });
266 
267         // Turn-around range value = Total time of flight for the 2 legs divided by 2 and multiplied by c
268         final double cOver2 = 0.5 * Constants.SPEED_OF_LIGHT;
269         final double turnAroundRange = (tauLeg2 + tauLeg1) * cOver2;
270         estimated.setEstimatedValue(turnAroundRange);
271 
272         return estimated;
273 
274     }
275 
276     /** {@inheritDoc} */
277     @Override
278     protected EstimatedMeasurement<TurnAroundRange> theoreticalEvaluation(final int iteration, final int evaluation,
279                                                                           final SpacecraftState[] states) {
280 
281         final SpacecraftState state = states[0];
282 
283         // Turn around range derivatives are computed with respect to:
284         // - Spacecraft state in inertial frame
285         // - Primary station parameters
286         // - Secondary station parameters
287         // --------------------------
288         //
289         //  - 0..2 - Position of the spacecraft in inertial frame
290         //  - 3..5 - Velocity of the spacecraft in inertial frame
291         //  - 6..n - stations' parameters (clock offset, station offsets, pole, prime meridian...)
292         int nbParams = 6;
293         final Map<String, Integer> indices = new HashMap<>();
294         for (ParameterDriver driver : getParametersDrivers()) {
295             // we have to check for duplicate keys because primary and secondary station share
296             // pole and prime meridian parameters names that must be considered
297             // as one set only (they are combined together by the estimation engine)
298             if (driver.isSelected()) {
299                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
300 
301                     if (!indices.containsKey(span.getData())) {
302                         indices.put(span.getData(), nbParams++);
303                     }
304                 }
305             }
306         }
307         final Field<Gradient>         field   = GradientField.getField(nbParams);
308         final FieldVector3D<Gradient> zero    = FieldVector3D.getZero(field);
309 
310         // Place the gradient in a time-stamped PV
311         final TimeStampedFieldPVCoordinates<Gradient> pvaDS = getCoordinates(state, 0, nbParams);
312 
313         // The path of the signal is divided in two legs.
314         // Leg1: Emission from primary station to satellite in primaryTauU seconds
315         //     + Reflection from satellite to secondary station in secondaryTauD seconds
316         // Leg2: Reflection from secondary station to satellite in secondaryTauU seconds
317         //     + Reflection from satellite to primary station in primaryTaudD seconds
318         // The measurement is considered to be time stamped at reception on ground
319         // by the primary station. All times are therefore computed as backward offsets
320         // with respect to this reception time.
321         //
322         // Two intermediate spacecraft states are defined:
323         //  - transitStateLeg2: State of the satellite when it bounced back the signal
324         //                      from secondary station to primary station during the 2nd leg
325         //  - transitStateLeg1: State of the satellite when it bounced back the signal
326         //                      from primary station to secondary station during the 1st leg
327 
328         // Compute propagation time for the 2nd leg of the signal path
329         // --
330 
331         // Time difference between t (date of the measurement) and t' (date tagged in spacecraft state)
332         // (if state has already been set up to pre-compensate propagation delay,
333         // we will have delta = primaryTauD + secondaryTauU)
334         final double delta = getDate().durationFrom(state.getDate());
335 
336         // transform between primary station topocentric frame (east-north-zenith) and inertial frame expressed as gradients
337         final FieldTransform<Gradient> primaryToInert =
338                         getStation().getOffsetToInertial(state.getFrame(), getDate(), nbParams, indices);
339         final FieldAbsoluteDate<Gradient> measurementDateDS = primaryToInert.getFieldDate();
340 
341         // Primary station PV in inertial frame at measurement date
342         final TimeStampedFieldPVCoordinates<Gradient> primaryArrival =
343                         primaryToInert.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(measurementDateDS,
344                                                                                                   zero, zero, zero));
345 
346         // Compute propagation times
347         final Gradient primaryTauD = signalTimeOfFlightAdjustableEmitter(pvaDS, primaryArrival.getPosition(),
348                                                                          measurementDateDS, state.getFrame());
349 
350         // Elapsed time between state date t' and signal arrival to the transit state of the 2nd leg
351         final Gradient dtLeg2 = primaryTauD.negate().add(delta);
352 
353         // Transit state where the satellite reflected the signal from secondary to primary station
354         final SpacecraftState transitStateLeg2 = state.shiftedBy(dtLeg2.getValue());
355 
356         // Transit state pv of leg2 (re)computed with gradient
357         final TimeStampedFieldPVCoordinates<Gradient> transitStateLeg2PV = pvaDS.shiftedBy(dtLeg2);
358 
359         // transform between secondary station topocentric frame (east-north-zenith) and inertial frame expressed as gradients
360         // The components of secondary station's position in offset frame are the 3 last derivative parameters
361         final FieldAbsoluteDate<Gradient> approxReboundDate = measurementDateDS.shiftedBy(-delta);
362         final FieldTransform<Gradient> secondaryToInertApprox =
363                         secondaryStation.getOffsetToInertial(state.getFrame(), approxReboundDate, nbParams, indices);
364 
365         // Secondary station PV in inertial frame at approximate rebound date on secondary station
366         final TimeStampedFieldPVCoordinates<Gradient> QSecondaryApprox =
367                         secondaryToInertApprox.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(approxReboundDate,
368                                                                                                           zero, zero, zero));
369 
370         // Uplink time of flight from secondary station to transit state of leg2
371         final Gradient secondaryTauU = signalTimeOfFlightAdjustableEmitter(QSecondaryApprox,
372                                                                            transitStateLeg2PV.getPosition(),
373                                                                            transitStateLeg2PV.getDate(),
374                                                                            state.getFrame());
375 
376         // Total time of flight for leg 2
377         final Gradient tauLeg2 = primaryTauD.add(secondaryTauU);
378 
379         // Compute propagation time for the 1st leg of the signal path
380         // --
381 
382         // Absolute date of rebound of the signal to secondary station
383         final FieldAbsoluteDate<Gradient> reboundDateDS = measurementDateDS.shiftedBy(tauLeg2.negate());
384         final FieldTransform<Gradient> secondaryToInert =
385                         secondaryStation.getOffsetToInertial(state.getFrame(), reboundDateDS, nbParams, indices);
386 
387         // Secondary station PV in inertial frame at rebound date on secondary station
388         final TimeStampedFieldPVCoordinates<Gradient> secondaryRebound =
389                         secondaryToInert.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(reboundDateDS,
390                                                                                                     FieldPVCoordinates.getZero(field)));
391 
392         // Downlink time of flight from transitStateLeg1 to secondary station at rebound date
393         final Gradient secondaryTauD = signalTimeOfFlightAdjustableEmitter(transitStateLeg2PV,
394                                                                            secondaryRebound.getPosition(),
395                                                                            reboundDateDS,
396                                                                            state.getFrame());
397 
398 
399         // Elapsed time between state date t' and signal arrival to the transit state of the 1st leg
400         final Gradient dtLeg1 = dtLeg2.subtract(secondaryTauU).subtract(secondaryTauD);
401 
402         // Transit state pv of leg2 (re)computed with gradients
403         final TimeStampedFieldPVCoordinates<Gradient> transitStateLeg1PV = pvaDS.shiftedBy(dtLeg1);
404 
405         // transform between primary station topocentric frame (east-north-zenith) and inertial frame expressed as gradients
406         // The components of primary station's position in offset frame are the 3 third derivative parameters
407         final FieldAbsoluteDate<Gradient> approxEmissionDate =
408                         measurementDateDS.shiftedBy(-2 * (secondaryTauU.getValue() + primaryTauD.getValue()));
409         final FieldTransform<Gradient> primaryToInertApprox =
410                         getStation().getOffsetToInertial(state.getFrame(), approxEmissionDate, nbParams, indices);
411 
412         // Primary station PV in inertial frame at approximate emission date
413         final TimeStampedFieldPVCoordinates<Gradient> QPrimaryApprox =
414                         primaryToInertApprox.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(approxEmissionDate,
415                                                                                                         zero, zero, zero));
416 
417         // Uplink time of flight from primary station to transit state of leg1
418         final Gradient primaryTauU = signalTimeOfFlightAdjustableEmitter(QPrimaryApprox,
419                                                                          transitStateLeg1PV.getPosition(),
420                                                                          transitStateLeg1PV.getDate(),
421                                                                          state.getFrame());
422 
423         // Primary station PV in inertial frame at exact emission date
424         final AbsoluteDate emissionDate = transitStateLeg1PV.getDate().toAbsoluteDate().shiftedBy(-primaryTauU.getValue());
425         final TimeStampedPVCoordinates primaryDeparture =
426                         primaryToInertApprox.shiftedBy(emissionDate.durationFrom(primaryToInertApprox.getDate())).
427                         transformPVCoordinates(new TimeStampedPVCoordinates(emissionDate, PVCoordinates.ZERO)).
428                         toTimeStampedPVCoordinates();
429 
430         // Total time of flight for leg 1
431         final Gradient tauLeg1 = secondaryTauD.add(primaryTauU);
432 
433 
434         // --
435         // Evaluate the turn-around range value and its derivatives
436         // --------------------------------------------------------
437 
438         // The state we use to define the estimated measurement is a middle ground between the two transit states
439         // This is done to avoid calling "SpacecraftState.shiftedBy" function on long duration
440         // Thus we define the state at the date t" = date of rebound of the signal at the secondary station
441         // Or t" = t -primaryTauD -secondaryTauU
442         // The iterative process in the estimation ensures that, after several iterations, the date stamped in the
443         // state S in input of this function will be close to t"
444         // Therefore we will shift state S by:
445         //  - +secondaryTauU to get transitStateLeg2
446         //  - -secondaryTauD to get transitStateLeg1
447         final EstimatedMeasurement<TurnAroundRange> estimated =
448                         new EstimatedMeasurement<>(this, iteration, evaluation,
449                                                    new SpacecraftState[] {
450                                                        transitStateLeg2.shiftedBy(-secondaryTauU.getValue())
451                                                    },
452                                                    new TimeStampedPVCoordinates[] {
453                                                        primaryDeparture,
454                                                        transitStateLeg1PV.toTimeStampedPVCoordinates(),
455                                                        secondaryRebound.toTimeStampedPVCoordinates(),
456                                                        transitStateLeg2.getPVCoordinates(),
457                                                        primaryArrival.toTimeStampedPVCoordinates()
458                                                    });
459 
460         // Turn-around range value = Total time of flight for the 2 legs divided by 2 and multiplied by c
461         final double cOver2 = 0.5 * Constants.SPEED_OF_LIGHT;
462         final Gradient turnAroundRange = (tauLeg2.add(tauLeg1)).multiply(cOver2);
463         estimated.setEstimatedValue(turnAroundRange.getValue());
464 
465         // Turn-around range first order derivatives with respect to state
466         final double[] derivatives = turnAroundRange.getGradient();
467         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));
468 
469         // Set first order derivatives with respect to parameters
470         for (final ParameterDriver driver : getParametersDrivers()) {
471             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
472                 final Integer index = indices.get(span.getData());
473                 if (index != null) {
474                     estimated.setParameterDerivatives(driver, span.getStart(), derivatives[index]);
475                 }
476             }
477         }
478 
479         return estimated;
480 
481     }
482 
483 }