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.gnss;
18  
19  import java.util.Arrays;
20  
21  import org.hipparchus.analysis.differentiation.Gradient;
22  import org.orekit.estimation.measurements.EstimatedMeasurement;
23  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
24  import org.orekit.estimation.measurements.ObservableSatellite;
25  import org.orekit.estimation.measurements.QuadraticClockModel;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.utils.Constants;
29  import org.orekit.utils.PVCoordinatesProvider;
30  import org.orekit.utils.ParameterDriver;
31  import org.orekit.utils.TimeSpanMap.Span;
32  import org.orekit.utils.TimeStampedPVCoordinates;
33  
34  /** One-way GNSS phase measurement.
35   * <p>
36   * This class can be used in precise orbit determination applications
37   * for modeling a phase measurement between a GNSS satellite (emitter)
38   * and a LEO satellite (receiver).
39   * <p>
40   * The one-way GNSS phase measurement assumes knowledge of the orbit and
41   * the clock offset of the emitting GNSS satellite. For instance, it is
42   * possible to use a SP3 file or a GNSS navigation message to recover
43   * the satellite's orbit and clock.
44   * <p>
45   * This class is very similar to {@link InterSatellitesPhase} measurement
46   * class. However, using the one-way GNSS phase measurement, the orbit and clock
47   * of the emitting GNSS satellite are <b>NOT</b> estimated simultaneously with
48   * LEO satellite coordinates.
49   *
50   * @author Bryan Cazabonne
51   * @since 10.3
52   */
53  public class OneWayGNSSPhase extends AbstractOneWayGNSSMeasurement<OneWayGNSSPhase> {
54  
55      /** Type of the measurement. */
56      public static final String MEASUREMENT_TYPE = "OneWayGNSSPhase";
57  
58      /** Driver for ambiguity. */
59      private final AmbiguityDriver ambiguityDriver;
60  
61      /** Wavelength of the phase observed value [m]. */
62      private final double wavelength;
63  
64      /** Simple constructor.
65       * @param remote provider for GNSS satellite which simply emits the signal
66       * @param remoteName name of the remote
67       * @param remoteClock clock offset of the GNSS satellite
68       * @param date date of the measurement
69       * @param phase observed value, in cycles
70       * @param wavelength phase observed value wavelength, in meters
71       * @param sigma theoretical standard deviation
72       * @param baseWeight base weight
73       * @param local satellite which receives the signal and perform the measurement
74       * @param cache from which ambiguity drive should come
75       * @since 12.1
76       */
77      public OneWayGNSSPhase(final PVCoordinatesProvider remote,
78                             final String remoteName,
79                             final QuadraticClockModel remoteClock,
80                             final AbsoluteDate date,
81                             final double phase, final double wavelength, final double sigma,
82                             final double baseWeight, final ObservableSatellite local,
83                             final AmbiguityCache cache) {
84          // Call super constructor
85          super(remote, remoteClock, date, phase, sigma, baseWeight, local);
86  
87          // Initialize phase ambiguity driver
88          ambiguityDriver = cache.getAmbiguity(remoteName, local.getName(), wavelength);
89  
90          // The local satellite clock offset affects the measurement
91          addParameterDriver(ambiguityDriver);
92  
93          // Initialise fields
94          this.wavelength = wavelength;
95      }
96  
97      /** Get the wavelength.
98       * @return wavelength (m)
99       */
100     public double getWavelength() {
101         return wavelength;
102     }
103 
104     /** Get the driver for phase ambiguity.
105      * @return the driver for phase ambiguity
106      */
107     public AmbiguityDriver getAmbiguityDriver() {
108         return ambiguityDriver;
109     }
110 
111     /** {@inheritDoc} */
112     @Override
113     protected EstimatedMeasurementBase<OneWayGNSSPhase> theoreticalEvaluationWithoutDerivatives(final int iteration,
114                                                                                                 final int evaluation,
115                                                                                                 final SpacecraftState[] states) {
116 
117         final OnBoardCommonParametersWithoutDerivatives common = computeCommonParametersWithout(states, false);
118 
119         // prepare the evaluation
120         final EstimatedMeasurementBase<OneWayGNSSPhase> estimatedPhase =
121                         new EstimatedMeasurementBase<>(this, iteration, evaluation,
122                                                        new SpacecraftState[] {
123                                                            common.getState()
124                                                        }, new TimeStampedPVCoordinates[] {
125                                                            common.getRemotePV(),
126                                                            common.getTransitPV()
127                                                        });
128 
129         // Phase value
130         final double   cOverLambda = Constants.SPEED_OF_LIGHT / wavelength;
131         final double   ambiguity   = ambiguityDriver.getValue(common.getState().getDate());
132         final double   phase       = (common.getTauD() + common.getLocalOffset() - common.getRemoteOffset()) * cOverLambda +
133                                      ambiguity;
134 
135         // Set value of the estimated measurement
136         estimatedPhase.setEstimatedValue(phase);
137 
138         // Return the estimated measurement
139         return estimatedPhase;
140 
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     protected EstimatedMeasurement<OneWayGNSSPhase> theoreticalEvaluation(final int iteration,
146                                                                           final int evaluation,
147                                                                           final SpacecraftState[] states) {
148 
149         final OnBoardCommonParametersWithDerivatives common = computeCommonParametersWith(states, false);
150 
151         // prepare the evaluation
152         final EstimatedMeasurement<OneWayGNSSPhase> estimatedPhase =
153                         new EstimatedMeasurement<>(this, iteration, evaluation,
154                                                    new SpacecraftState[] {
155                                                        common.getState()
156                                                    }, new TimeStampedPVCoordinates[] {
157                                                      common.getRemotePV().toTimeStampedPVCoordinates(),
158                                                      common.getTransitPV().toTimeStampedPVCoordinates()
159                                                    });
160 
161         // Phase value
162         final double   cOverLambda      = Constants.SPEED_OF_LIGHT / wavelength;
163         final Gradient ambiguity        = ambiguityDriver.getValue(common.getTauD().getFreeParameters(), common.getIndices(),
164                                                                    common.getState().getDate());
165         final Gradient phase            = common.getTauD().add(common.getLocalOffset()).subtract(common.getRemoteOffset()).
166                                           multiply(cOverLambda).
167                                           add(ambiguity);
168         final double[] phaseDerivatives = phase.getGradient();
169 
170         // Set value and state first order derivatives of the estimated measurement
171         estimatedPhase.setEstimatedValue(phase.getValue());
172         estimatedPhase.setStateDerivatives(0, Arrays.copyOfRange(phaseDerivatives, 0,  6));
173 
174         // Set first order derivatives with respect to parameters
175         for (final ParameterDriver phaseMeasurementDriver : getParametersDrivers()) {
176             for (Span<String> span = phaseMeasurementDriver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
177 
178                 final Integer index = common.getIndices().get(span.getData());
179                 if (index != null) {
180                     estimatedPhase.setParameterDerivatives(phaseMeasurementDriver, span.getStart(), phaseDerivatives[index]);
181                 }
182             }
183         }
184 
185         // Return the estimated measurement
186         return estimatedPhase;
187 
188     }
189 
190 }