1   /* Copyright 2002-2026 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.gnss;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.Map;
22  
23  import org.hipparchus.analysis.differentiation.Gradient;
24  import org.hipparchus.analysis.differentiation.GradientField;
25  import org.orekit.estimation.measurements.AbstractMeasurement;
26  import org.orekit.estimation.measurements.AbstractParticipant;
27  import org.orekit.estimation.measurements.EstimatedMeasurement;
28  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
29  import org.orekit.estimation.measurements.MeasurementQuality;
30  import org.orekit.estimation.measurements.ObservableSatellite;
31  import org.orekit.estimation.measurements.Observer;
32  import org.orekit.estimation.measurements.SignalBasedMeasurement;
33  import org.orekit.frames.FieldTransform;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.Transform;
36  import org.orekit.propagation.SpacecraftState;
37  import org.orekit.signal.AdjustableEmitterSignalTimer;
38  import org.orekit.signal.FieldAdjustableEmitterSignalTimer;
39  import org.orekit.signal.FieldSignalReceptionCondition;
40  import org.orekit.signal.SignalReceptionCondition;
41  import org.orekit.signal.SignalTravelTimeModel;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.FieldAbsoluteDate;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.FieldPVCoordinates;
46  import org.orekit.utils.FieldPVCoordinatesProvider;
47  import org.orekit.utils.PVCoordinates;
48  import org.orekit.utils.PVCoordinatesProvider;
49  import org.orekit.utils.ParameterDriver;
50  import org.orekit.utils.TimeSpanMap.Span;
51  import org.orekit.utils.TimeStampedFieldPVCoordinates;
52  import org.orekit.utils.TimeStampedPVCoordinates;
53  
54  /** Class modeling a phase measurement from a ground station.
55   * <p>
56   * The measurement is considered to be a signal emitted from
57   * a spacecraft and received on a ground station.
58   * Its value is the number of cycles between emission and
59   * reception. The motion of both the station and the
60   * spacecraft during the signal flight time are taken into
61   * account. The date of the measurement corresponds to the
62   * reception on ground of the emitted signal.
63   * </p>
64   * @author Thierry Ceolin
65   * @author Luc Maisonobe
66   * @author Maxime Journot
67   * @since 9.2
68   */
69  public class Phase extends SignalBasedMeasurement<Phase> {
70  
71      /** Type of the measurement. */
72      public static final String MEASUREMENT_TYPE = "Phase";
73  
74      /** Driver for ambiguity. */
75      private final AmbiguityDriver ambiguityDriver;
76  
77      /** Wavelength of the phase observed value [m]. */
78      private final double wavelength;
79  
80      /** Observer that receives signal from satellite. */
81      private final Observer observer;
82  
83      /** Simple constructor.
84       * @param observer observer that performs the measurement
85       * @param date date of the measurement
86       * @param phase observed value (cycles)
87       * @param wavelength phase observed value wavelength (m)
88       * @param sigma theoretical standard deviation
89       * @param baseWeight base weight
90       * @param satellite satellite related to this measurement
91       * @param cache from which ambiguity drive should come
92       * @since 12.1
93       */
94      public Phase(final Observer observer, final AbsoluteDate date,
95                   final double phase, final double wavelength, final double sigma,
96                   final double baseWeight, final ObservableSatellite satellite,
97                   final AmbiguityCache cache) {
98          this(observer, date, phase, wavelength, new MeasurementQuality(sigma, baseWeight), new SignalTravelTimeModel(),
99                  satellite, cache);
100     }
101 
102     /** Simple constructor.
103      * @param observer observer that performs the measurement
104      * @param date date of the measurement
105      * @param phase observed value (cycles)
106      * @param wavelength phase observed value wavelength (m)
107      * @param measurementQuality measurement quality data as used in orbit determination
108      * @param signalTravelTimeModel signal model
109      * @param satellite satellite related to this measurement
110      * @param cache from which ambiguity drive should come
111      * @since 14.0
112      */
113     public Phase(final Observer observer, final AbsoluteDate date,
114                  final double phase, final double wavelength, final MeasurementQuality measurementQuality,
115                  final SignalTravelTimeModel signalTravelTimeModel, final ObservableSatellite satellite,
116                  final AmbiguityCache cache) {
117         super(date, false, phase, measurementQuality, signalTravelTimeModel,
118                 Collections.singletonList(satellite));
119         ambiguityDriver = cache.getAmbiguity(satellite.getName(), observer.getName(), wavelength);
120         addParametersDrivers(observer.getParametersDrivers());
121         addParameterDriver(ambiguityDriver);
122         this.observer = observer;
123         this.wavelength = wavelength;
124     }
125 
126     /** Get receiving observer.
127      * @return observer
128      */
129     public final Observer getObserver() {
130         return observer;
131     }
132 
133     /** Get the wavelength.
134      * @return wavelength (m)
135      */
136     public double getWavelength() {
137         return wavelength;
138     }
139 
140     /** Get the driver for phase ambiguity.
141      * @return the driver for phase ambiguity
142      * @since 10.3
143      */
144     public AmbiguityDriver getAmbiguityDriver() {
145         return ambiguityDriver;
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     protected EstimatedMeasurementBase<Phase> theoreticalEvaluationWithoutDerivatives(final int iteration,
151                                                                                       final int evaluation,
152                                                                                       final SpacecraftState[] states) {
153         // Coordinates of the measured spacecraft
154         final SpacecraftState state = states[0];
155         final Frame frame = state.getFrame();
156         final TimeStampedPVCoordinates pva   = state.getPVCoordinates();
157 
158         // transform between remote observer frame and inertial frame
159         final AbsoluteDate measurementDate = getDate();
160         final Transform offsetToInertialDownlink = getObserver().getOffsetToInertial(frame, measurementDate, false);
161         final AbsoluteDate downlinkDate             = offsetToInertialDownlink.getDate();
162 
163         // Observer position in inertial frame at end of the downlink leg
164         final TimeStampedPVCoordinates origin = new TimeStampedPVCoordinates(downlinkDate, PVCoordinates.ZERO);
165         final TimeStampedPVCoordinates satelliteDownlink = offsetToInertialDownlink.transformPVCoordinates(origin);
166 
167         // Coordinates provider for emitting object (observed spacecraft)
168         final PVCoordinatesProvider pvCoordinatesProvider = AbstractParticipant.extractPVCoordinatesProvider(states[0], pva);
169 
170         // Downlink delay / determine time of emission of signal by ObservableSatellite
171         final AdjustableEmitterSignalTimer signalTimeOfFlight = getSignalTravelTimeModel()
172                 .getAdjustableEmitterComputer(pvCoordinatesProvider);
173         final SignalReceptionCondition receptionCondition = new SignalReceptionCondition(downlinkDate,
174                 satelliteDownlink.getPosition(), frame);
175         final double tauD = signalTimeOfFlight.computeDelay(receptionCondition, pva.getDate());
176 
177         // Transit state & Transit state (re)computed with gradients
178         final double          delta             = downlinkDate.durationFrom(state.getDate());
179         final double          deltaMTauD        = delta - tauD;
180         final SpacecraftState transitState      = states[0].shiftedBy(deltaMTauD);
181 
182         // prepare the evaluation
183         final EstimatedMeasurementBase<Phase> estimated =
184                         new EstimatedMeasurementBase<>(this, iteration, evaluation,
185                                                        new SpacecraftState[] {
186                                                            transitState
187                                                        }, new TimeStampedPVCoordinates[] {
188                                                            transitState.getPVCoordinates(), satelliteDownlink
189                                                        });
190 
191         // Clock offsets
192         final ObservableSatellite satellite = getSatellites().getFirst();
193 
194         final double dts = satellite.getOffsetValue(state.getDate());
195         final double dtg = getObserver().getOffsetValue(getDate());
196 
197         // Phase value
198         final double cOverLambda = Constants.SPEED_OF_LIGHT / wavelength;
199         final double ambiguity   = ambiguityDriver.getValue(state.getDate());
200         final double phase       = (tauD + dtg - dts) * cOverLambda + ambiguity;
201 
202         estimated.setEstimatedValue(phase);
203 
204         return estimated;
205 
206     }
207 
208     /** {@inheritDoc} */
209     @Override
210     protected EstimatedMeasurement<Phase> theoreticalEvaluation(final int iteration,
211                                                                 final int evaluation,
212                                                                 final SpacecraftState[] states) {
213         // Create the parameter indices map
214         final SpacecraftState state = states[0];
215         final Frame                frame        = state.getFrame();
216         final Map<String, Integer> paramIndices = getParameterIndices(states);
217         final int                  nbParams     = 6 * states.length + paramIndices.size();
218 
219         // Coordinates of the spacecraft expressed as a gradient
220         final TimeStampedFieldPVCoordinates<Gradient> pva = AbstractMeasurement.getCoordinates(states[0], 0, nbParams);
221 
222         // transform between Observer object and inertial frame, expressed as a gradient
223         // The components of the Observer's position in offset frame are the 3 last derivative parameters
224         final FieldTransform<Gradient> offsetToInertialDownlink = getObserver().
225                 getOffsetToInertial(frame, getDate(), nbParams, paramIndices);
226         final FieldAbsoluteDate<Gradient> downlinkDate = offsetToInertialDownlink.getFieldDate();
227 
228         // Observer position in inertial frame at end of the downlink leg
229         final GradientField field = GradientField.getField(nbParams);
230         final TimeStampedFieldPVCoordinates<Gradient> satelliteDownlink =
231                 offsetToInertialDownlink.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(downlinkDate,
232                         FieldPVCoordinates.getZero(field)));
233 
234         // Form coordinates provider
235         final FieldPVCoordinatesProvider<Gradient> fieldPVCoordinatesProvider = AbstractParticipant.extractFieldPVCoordinatesProvider(states[0], pva);
236 
237         // Downlink delay
238         final FieldAdjustableEmitterSignalTimer<Gradient> fieldComputer = getSignalTravelTimeModel()
239                 .getFieldAdjustableEmitterComputer(field, fieldPVCoordinatesProvider);
240         final FieldSignalReceptionCondition<Gradient> receptionCondition = new FieldSignalReceptionCondition<>(downlinkDate,
241                 satelliteDownlink.getPosition(), frame);
242         final Gradient tauD = fieldComputer.computeDelay(receptionCondition, pva.getDate());
243 
244         // Transit state & Transit state (re)computed with gradients
245         final Gradient        delta        = downlinkDate.durationFrom(states[0].getDate());
246         final Gradient        deltaMTauD   = tauD.negate().add(delta);
247         final SpacecraftState transitState = states[0].shiftedBy(deltaMTauD.getValue());
248         final FieldAbsoluteDate<Gradient> fieldDate = new FieldAbsoluteDate<>(field, states[0].getDate()).shiftedBy(deltaMTauD);
249         final TimeStampedFieldPVCoordinates<Gradient> transitPV = fieldPVCoordinatesProvider.getPVCoordinates(fieldDate, frame);
250 
251         // prepare the evaluation
252         final EstimatedMeasurement<Phase> estimated =
253                         new EstimatedMeasurement<>(this, iteration, evaluation,
254                                 new SpacecraftState[] { transitState},
255                                 new TimeStampedPVCoordinates[] {
256                                         transitPV.toTimeStampedPVCoordinates(),
257                                         satelliteDownlink.toTimeStampedPVCoordinates()});
258 
259         // Clock offsets
260         final ObservableSatellite satellite = getSatellites().getFirst();
261 
262         final Gradient dts = satellite.getFieldOffsetValue(nbParams, state.getDate(), paramIndices);
263         final Gradient dtg = getObserver().getFieldOffsetValue(nbParams, getDate(), paramIndices);
264 
265         // Phase value
266         final double   cOverLambda = Constants.SPEED_OF_LIGHT / wavelength;
267         final Gradient ambiguity   = ambiguityDriver.getValue(nbParams, paramIndices, state.getDate());
268         final Gradient phase       = tauD.add(dtg).subtract(dts).multiply(cOverLambda).add(ambiguity);
269 
270         estimated.setEstimatedValue(phase.getValue());
271 
272         // Phase first order derivatives with respect to state
273         final double[] derivatives = phase.getGradient();
274         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));
275 
276         // Set first order derivatives with respect to parameters
277         for (final ParameterDriver driver : getParametersDrivers()) {
278             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
279 
280                 final Integer index = paramIndices.get(span.getData());
281                 if (index != null) {
282                     estimated.setParameterDerivatives(driver, span.getStart(), derivatives[index]);
283                 }
284             }
285         }
286 
287         return estimated;
288 
289     }
290 
291 }