1   /* Copyright 2002-2021 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.Collections;
21  import java.util.HashMap;
22  import java.util.Map;
23  
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.orekit.frames.FieldTransform;
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.TimeStampedFieldPVCoordinates;
34  import org.orekit.utils.TimeStampedPVCoordinates;
35  
36  /** Class modeling a range measurement from a ground station.
37   * <p>
38   * For one-way measurements, a signal is emitted by the satellite
39   * and received by the ground station. The measurement value is the
40   * elapsed time between emission and reception multiplied by c where
41   * c is the speed of light.
42   * </p>
43   * <p>
44   * For two-way measurements, the measurement is considered to be a signal
45   * emitted from a ground station, reflected on spacecraft, and received
46   * on the same ground station. Its value is the elapsed time between
47   * emission and reception multiplied by c/2 where c is the speed of light.
48   * </p>
49   * <p>
50   * The motion of both the station and the spacecraft during the signal
51   * flight time are taken into account. The date of the measurement
52   * corresponds to the reception on ground of the emitted or reflected signal.
53   * </p>
54   * <p>
55   * The clock offsets of both the ground station and the satellite are taken
56   * into account. These offsets correspond to the values that must be subtracted
57   * from station (resp. satellite) reading of time to compute the real physical
58   * date. These offsets have two effects:
59   * </p>
60   * <ul>
61   *   <li>as measurement date is evaluated at reception time, the real physical date
62   *   of the measurement is the observed date to which the receiving ground station
63   *   clock offset is subtracted</li>
64   *   <li>as range is evaluated using the total signal time of flight, for one-way
65   *   measurements the observed range is the real physical signal time of flight to
66   *   which (Δtg - Δts) ⨉ c is added, where Δtg (resp. Δts) is the clock offset for the
67   *   receiving ground station (resp. emitting satellite). A similar effect exists in
68   *   two-way measurements but it is computed as (Δtg - Δtg) ⨉ c / 2 as the same ground
69   *   station clock is used for initial emission and final reception and therefore it evaluates
70   *   to zero.</li>
71   * </ul>
72   * <p>
73   * @author Thierry Ceolin
74   * @author Luc Maisonobe
75   * @author Maxime Journot
76   * @since 8.0
77   */
78  public class Range extends AbstractMeasurement<Range> {
79  
80      /** Ground station from which measurement is performed. */
81      private final GroundStation station;
82  
83      /** Flag indicating whether it is a two-way measurement. */
84      private final boolean twoway;
85  
86      /** Simple constructor.
87       * @param station ground station from which measurement is performed
88       * @param twoWay flag indicating whether it is a two-way measurement
89       * @param date date of the measurement
90       * @param range observed value
91       * @param sigma theoretical standard deviation
92       * @param baseWeight base weight
93       * @param satellite satellite related to this measurement
94       * @since 9.3
95       */
96      public Range(final GroundStation station, final boolean twoWay, final AbsoluteDate date,
97                   final double range, final double sigma, final double baseWeight,
98                   final ObservableSatellite satellite) {
99          super(date, range, sigma, baseWeight, Collections.singletonList(satellite));
100         addParameterDriver(station.getClockOffsetDriver());
101         addParameterDriver(station.getEastOffsetDriver());
102         addParameterDriver(station.getNorthOffsetDriver());
103         addParameterDriver(station.getZenithOffsetDriver());
104         addParameterDriver(station.getPrimeMeridianOffsetDriver());
105         addParameterDriver(station.getPrimeMeridianDriftDriver());
106         addParameterDriver(station.getPolarOffsetXDriver());
107         addParameterDriver(station.getPolarDriftXDriver());
108         addParameterDriver(station.getPolarOffsetYDriver());
109         addParameterDriver(station.getPolarDriftYDriver());
110         if (!twoWay) {
111             // for one way measurements, the satellite clock offset affects the measurement
112             addParameterDriver(satellite.getClockOffsetDriver());
113         }
114         this.station = station;
115         this.twoway = twoWay;
116     }
117 
118     /** Get the ground station from which measurement is performed.
119      * @return ground station from which measurement is performed
120      */
121     public GroundStation getStation() {
122         return station;
123     }
124 
125     /** Check if the instance represents a two-way measurement.
126      * @return true if the instance represents a two-way measurement
127      */
128     public boolean isTwoWay() {
129         return twoway;
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     protected EstimatedMeasurement<Range> theoreticalEvaluation(final int iteration,
135                                                                 final int evaluation,
136                                                                 final SpacecraftState[] states) {
137 
138         final SpacecraftState state = states[0];
139 
140         // Range derivatives are computed with respect to spacecraft state in inertial frame
141         // and station parameters
142         // ----------------------
143         //
144         // Parameters:
145         //  - 0..2 - Position of the spacecraft in inertial frame
146         //  - 3..5 - Velocity of the spacecraft in inertial frame
147         //  - 6..n - measurements parameters (clock offset, station offsets, pole, prime meridian, sat clock offset...)
148         int nbParams = 6;
149         final Map<String, Integer> indices = new HashMap<>();
150         for (ParameterDriver driver : getParametersDrivers()) {
151             if (driver.isSelected()) {
152                 indices.put(driver.getName(), nbParams++);
153             }
154         }
155         final FieldVector3D<Gradient> zero = FieldVector3D.getZero(GradientField.getField(nbParams));
156 
157         // Coordinates of the spacecraft expressed as a gradient
158         final TimeStampedFieldPVCoordinates<Gradient> pvaDS = getCoordinates(state, 0, nbParams);
159 
160         // transform between station and inertial frame, expressed as a gradient
161         // The components of station's position in offset frame are the 3 last derivative parameters
162         final FieldTransform<Gradient> offsetToInertialDownlink =
163                         station.getOffsetToInertial(state.getFrame(), getDate(), nbParams, indices);
164         final FieldAbsoluteDate<Gradient> downlinkDateDS = offsetToInertialDownlink.getFieldDate();
165 
166         // Station position in inertial frame at end of the downlink leg
167         final TimeStampedFieldPVCoordinates<Gradient> stationDownlink =
168                         offsetToInertialDownlink.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(downlinkDateDS,
169                                                                                                             zero, zero, zero));
170 
171         // Compute propagation times
172         // (if state has already been set up to pre-compensate propagation delay,
173         //  we will have delta == tauD and transitState will be the same as state)
174 
175         // Downlink delay
176         final Gradient tauD = signalTimeOfFlight(pvaDS, stationDownlink.getPosition(), downlinkDateDS);
177 
178         // Transit state & Transit state (re)computed with gradients
179         final Gradient        delta        = downlinkDateDS.durationFrom(state.getDate());
180         final Gradient        deltaMTauD   = tauD.negate().add(delta);
181         final SpacecraftState transitState = state.shiftedBy(deltaMTauD.getValue());
182         final TimeStampedFieldPVCoordinates<Gradient> transitStateDS = pvaDS.shiftedBy(deltaMTauD);
183 
184         // prepare the evaluation
185         final EstimatedMeasurement<Range> estimated;
186         final Gradient range;
187 
188         if (twoway) {
189 
190             // Station at transit state date (derivatives of tauD taken into account)
191             final TimeStampedFieldPVCoordinates<Gradient> stationAtTransitDate =
192                             stationDownlink.shiftedBy(tauD.negate());
193             // Uplink delay
194             final Gradient tauU =
195                             signalTimeOfFlight(stationAtTransitDate, transitStateDS.getPosition(), transitStateDS.getDate());
196             final TimeStampedFieldPVCoordinates<Gradient> stationUplink =
197                             stationDownlink.shiftedBy(-tauD.getValue() - tauU.getValue());
198 
199             // Prepare the evaluation
200             estimated = new EstimatedMeasurement<Range>(this, iteration, evaluation,
201                                                             new SpacecraftState[] {
202                                                                 transitState
203                                                             }, new TimeStampedPVCoordinates[] {
204                                                                 stationUplink.toTimeStampedPVCoordinates(),
205                                                                 transitStateDS.toTimeStampedPVCoordinates(),
206                                                                 stationDownlink.toTimeStampedPVCoordinates()
207                                                             });
208 
209             // Range value
210             final double   cOver2 = 0.5 * Constants.SPEED_OF_LIGHT;
211             final Gradient tau    = tauD.add(tauU);
212             range                 = tau.multiply(cOver2);
213 
214         } else {
215 
216             estimated = new EstimatedMeasurement<Range>(this, iteration, evaluation,
217                             new SpacecraftState[] {
218                                 transitState
219                             }, new TimeStampedPVCoordinates[] {
220                                 transitStateDS.toTimeStampedPVCoordinates(),
221                                 stationDownlink.toTimeStampedPVCoordinates()
222                             });
223 
224             // Clock offsets
225             final ObservableSatellite satellite = getSatellites().get(0);
226             final Gradient            dts       = satellite.getClockOffsetDriver().getValue(nbParams, indices);
227             final Gradient            dtg       = station.getClockOffsetDriver().getValue(nbParams, indices);
228 
229             // Range value
230             range = tauD.add(dtg).subtract(dts).multiply(Constants.SPEED_OF_LIGHT);
231 
232         }
233 
234         estimated.setEstimatedValue(range.getValue());
235 
236         // Range partial derivatives with respect to state
237         final double[] derivatives = range.getGradient();
238         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));
239 
240         // set partial derivatives with respect to parameters
241         // (beware element at index 0 is the value, not a derivative)
242         for (final ParameterDriver driver : getParametersDrivers()) {
243             final Integer index = indices.get(driver.getName());
244             if (index != null) {
245                 estimated.setParameterDerivatives(driver, derivatives[index]);
246             }
247         }
248 
249         return estimated;
250 
251     }
252 
253 }