1   /* Copyright 2002-2025 Mark Rutten
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    * Mark Rutten 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.analysis.differentiation.Gradient;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.orekit.frames.FieldTransform;
25  import org.orekit.frames.Transform;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.time.FieldAbsoluteDate;
29  import org.orekit.utils.Constants;
30  import org.orekit.utils.ParameterDriver;
31  import org.orekit.utils.TimeSpanMap.Span;
32  import org.orekit.utils.TimeStampedFieldPVCoordinates;
33  import org.orekit.utils.TimeStampedPVCoordinates;
34  
35  /**
36   * Class modeling a bistatic range measurement using
37   * an emitter ground station and a receiver ground station.
38   * <p>
39   * The measurement is considered to be a signal:
40   * <ul>
41   * <li>Emitted from the emitter ground station</li>
42   * <li>Reflected on the spacecraft</li>
43   * <li>Received on the receiver ground station</li>
44   * </ul>
45   * The date of the measurement corresponds to the reception on ground of the reflected signal.
46   * <p>
47   * The motion of the stations and the spacecraft during the signal flight time are taken into account.
48   * </p>
49   *
50   * @author Mark Rutten
51   * @since 11.2
52   */
53  public class BistaticRange extends GroundReceiverMeasurement<BistaticRange> {
54  
55      /** Type of the measurement. */
56      public static final String MEASUREMENT_TYPE = "BistaticRange";
57  
58      /**
59       * Ground station from which transmission is made.
60       */
61      private final GroundStation emitter;
62  
63      /**
64       * Simple constructor.
65       *
66       * @param emitter     ground station from which transmission is performed
67       * @param receiver    ground station from which measurement is performed
68       * @param date        date of the measurement
69       * @param range       observed value
70       * @param sigma       theoretical standard deviation
71       * @param baseWeight  base weight
72       * @param satellite   satellite related to this measurement
73       * @since 11.2
74       */
75      public BistaticRange(final GroundStation emitter, final GroundStation receiver, final AbsoluteDate date,
76                           final double range, final double sigma, final double baseWeight,
77                           final ObservableSatellite satellite) {
78          super(receiver, true, date, range, sigma, baseWeight, satellite);
79  
80          addParameterDriver(emitter.getClockOffsetDriver());
81          addParameterDriver(emitter.getEastOffsetDriver());
82          addParameterDriver(emitter.getNorthOffsetDriver());
83          addParameterDriver(emitter.getZenithOffsetDriver());
84          addParameterDriver(emitter.getPrimeMeridianOffsetDriver());
85          addParameterDriver(emitter.getPrimeMeridianDriftDriver());
86          addParameterDriver(emitter.getPolarOffsetXDriver());
87          addParameterDriver(emitter.getPolarDriftXDriver());
88          addParameterDriver(emitter.getPolarOffsetYDriver());
89          addParameterDriver(emitter.getPolarDriftYDriver());
90  
91          this.emitter = emitter;
92  
93      }
94  
95      /** Get the emitter ground station.
96       * @return emitter ground station
97       */
98      public GroundStation getEmitterStation() {
99          return emitter;
100     }
101 
102     /** Get the receiver ground station.
103      * @return receiver ground station
104      */
105     public GroundStation getReceiverStation() {
106         return getStation();
107     }
108 
109     /**
110      * {@inheritDoc}
111      */
112     @Override
113     protected EstimatedMeasurementBase<BistaticRange> theoreticalEvaluationWithoutDerivatives(final int iteration,
114                                                                                               final int evaluation,
115                                                                                               final SpacecraftState[] states) {
116 
117         final GroundReceiverCommonParametersWithoutDerivatives common = computeCommonParametersWithout(states[0]);
118         final TimeStampedPVCoordinates transitPV = common.getTransitPV();
119         final AbsoluteDate transitDate = transitPV.getDate();
120 
121         // Approximate emitter location at transit time
122         final Transform emitterToInertial =
123                 getEmitterStation().getOffsetToInertial(common.getState().getFrame(), transitDate, true);
124         final TimeStampedPVCoordinates emitterApprox =
125                 emitterToInertial.transformPVCoordinates(new TimeStampedPVCoordinates(transitDate,
126                                                                                       Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));
127 
128         // Uplink time of flight from emitter station to transit state
129         final double tauU = signalTimeOfFlightAdjustableEmitter(emitterApprox, transitPV.getPosition(), transitDate,
130                                                                 common.getState().getFrame());
131 
132         // Secondary station PV in inertial frame at rebound date on secondary station
133         final TimeStampedPVCoordinates emitterPV = emitterApprox.shiftedBy(-tauU);
134 
135         // Prepare the evaluation
136         final EstimatedMeasurementBase<BistaticRange> estimated =
137                         new EstimatedMeasurementBase<>(this,
138                                                        iteration, evaluation,
139                                                        new SpacecraftState[] {
140                                                            common.getTransitState()
141                                                        },
142                                                        new TimeStampedPVCoordinates[] {
143                                                            common.getStationDownlink(),
144                                                            transitPV,
145                                                            emitterPV
146                                                        });
147 
148         // Clock offsets
149         final double dte = getEmitterStation().getClockOffsetDriver().getValue(common.getState().getDate());
150         final double dtr = getReceiverStation().getClockOffsetDriver().getValue(common.getState().getDate());
151 
152         // Range value
153         final double tau = common.getTauD() + tauU + dtr - dte;
154         final double range = tau * Constants.SPEED_OF_LIGHT;
155 
156         estimated.setEstimatedValue(range);
157 
158         return estimated;
159     }
160 
161     /**
162      * {@inheritDoc}
163      */
164     @Override
165     protected EstimatedMeasurement<BistaticRange> theoreticalEvaluation(final int iteration,
166                                                                         final int evaluation,
167                                                                         final SpacecraftState[] states) {
168         final SpacecraftState state = states[0];
169 
170         // Bistatic range derivatives are computed with respect to spacecraft state in inertial frame
171         // and station parameters
172         // ----------------------
173         //
174         // Parameters:
175         //  - 0..2 - Position of the spacecraft in inertial frame
176         //  - 3..5 - Velocity of the spacecraft in inertial frame
177         //  - 6..n - measurements parameters (clock offset, station offsets, pole, prime meridian, sat clock offset...)
178         final GroundReceiverCommonParametersWithDerivatives common = computeCommonParametersWithDerivatives(state);
179         final int nbParams = common.getTauD().getFreeParameters();
180         final TimeStampedFieldPVCoordinates<Gradient> transitPV = common.getTransitPV();
181         final FieldAbsoluteDate<Gradient> transitDate = transitPV.getDate();
182 
183         // Approximate emitter location (at transit time)
184         final FieldVector3D<Gradient> zero = FieldVector3D.getZero(common.getTauD().getField());
185         final FieldTransform<Gradient> emitterToInertial =
186                 getEmitterStation().getOffsetToInertial(state.getFrame(), transitDate, nbParams, common.getIndices());
187         final TimeStampedFieldPVCoordinates<Gradient> emitterApprox =
188                 emitterToInertial.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(transitDate,
189                                                                                              zero, zero, zero));
190 
191         // Uplink time of flight from emiiter to transit state
192         final Gradient tauU = signalTimeOfFlightAdjustableEmitter(emitterApprox, transitPV.getPosition(),
193                                                                   transitPV.getDate(), state.getFrame());
194 
195         // Emitter coordinates at transmit time
196         final TimeStampedFieldPVCoordinates<Gradient> emitterPV = emitterApprox.shiftedBy(tauU.negate());
197 
198         // Prepare the evaluation
199         final EstimatedMeasurement<BistaticRange> estimated = new EstimatedMeasurement<>(this,
200                 iteration, evaluation,
201                 new SpacecraftState[] {
202                     common.getTransitState()
203                 },
204                 new TimeStampedPVCoordinates[] {
205                     common.getStationDownlink().toTimeStampedPVCoordinates(),
206                     common.getTransitPV().toTimeStampedPVCoordinates(),
207                     emitterPV.toTimeStampedPVCoordinates()
208                 });
209 
210         // Clock offsets
211         final Gradient dte = getEmitterStation().getClockOffsetDriver().getValue(nbParams, common.getIndices(), state.getDate());
212         final Gradient dtr = getReceiverStation().getClockOffsetDriver().getValue(nbParams, common.getIndices(), state.getDate());
213 
214         // Range value
215         final Gradient tau = common.getTauD().add(tauU).add(dtr).subtract(dte);
216         final Gradient range = tau.multiply(Constants.SPEED_OF_LIGHT);
217 
218         estimated.setEstimatedValue(range.getValue());
219 
220         // Range first order derivatives with respect to state
221         final double[] derivatives = range.getGradient();
222         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));
223 
224         // Set first order derivatives with respect to parameters
225         for (final ParameterDriver driver : getParametersDrivers()) {
226             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
227                 final Integer index = common.getIndices().get(span.getData());
228                 if (index != null) {
229                     estimated.setParameterDerivatives(driver, span.getStart(), derivatives[index]);
230                 }
231             }
232         }
233 
234         return estimated;
235     }
236 
237 }