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.modifiers;
18  
19  import org.hipparchus.util.MathUtils;
20  import org.hipparchus.util.Precision;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.Test;
23  import org.orekit.estimation.Context;
24  import org.orekit.estimation.EstimationTestUtils;
25  import org.orekit.estimation.measurements.AngularAzEl;
26  import org.orekit.estimation.measurements.AngularAzElMeasurementCreator;
27  import org.orekit.estimation.measurements.BistaticRange;
28  import org.orekit.estimation.measurements.BistaticRangeMeasurementCreator;
29  import org.orekit.estimation.measurements.BistaticRangeRate;
30  import org.orekit.estimation.measurements.BistaticRangeRateMeasurementCreator;
31  import org.orekit.estimation.measurements.EstimatedMeasurement;
32  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
33  import org.orekit.estimation.measurements.GroundStation;
34  import org.orekit.estimation.measurements.ObservedMeasurement;
35  import org.orekit.estimation.measurements.Range;
36  import org.orekit.estimation.measurements.RangeRate;
37  import org.orekit.estimation.measurements.RangeRateMeasurementCreator;
38  import org.orekit.estimation.measurements.TDOA;
39  import org.orekit.estimation.measurements.TDOAMeasurementCreator;
40  import org.orekit.estimation.measurements.TurnAroundRange;
41  import org.orekit.estimation.measurements.TurnAroundRangeMeasurementCreator;
42  import org.orekit.estimation.measurements.TwoWayRangeMeasurementCreator;
43  import org.orekit.estimation.measurements.gnss.Phase;
44  import org.orekit.estimation.measurements.gnss.PhaseMeasurementCreator;
45  import org.orekit.frames.TopocentricFrame;
46  import org.orekit.gnss.PredefinedGnssSignal;
47  import org.orekit.models.earth.ITURP834AtmosphericRefraction;
48  import org.orekit.models.earth.troposphere.EstimatedModel;
49  import org.orekit.models.earth.troposphere.ModifiedSaastamoinenModel;
50  import org.orekit.models.earth.troposphere.NiellMappingFunctionModel;
51  import org.orekit.models.earth.troposphere.TroposphericModelUtils;
52  import org.orekit.orbits.OrbitType;
53  import org.orekit.orbits.PositionAngleType;
54  import org.orekit.propagation.Propagator;
55  import org.orekit.propagation.SpacecraftState;
56  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
57  import org.orekit.time.AbsoluteDate;
58  import org.orekit.utils.ParameterDriver;
59  
60  import java.util.List;
61  import java.util.Map;
62  
63  public class TropoModifierTest {
64  
65      @Test
66      public void testRangeTropoModifier() {
67  
68          Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
69  
70          final NumericalPropagatorBuilder propagatorBuilder =
71                          context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
72                                                1.0e-6, 60.0, 0.001);
73  
74          // create perfect range measurements
75          for (final GroundStation station : context.stations) {
76              station.getClockOffsetDriver().setSelected(true);
77              station.getEastOffsetDriver().setSelected(true);
78              station.getNorthOffsetDriver().setSelected(true);
79              station.getZenithOffsetDriver().setSelected(true);
80          }
81          final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
82                                                                             propagatorBuilder);
83          final List<ObservedMeasurement<?>> measurements =
84                          EstimationTestUtils.createMeasurements(propagator,
85                                                                 new TwoWayRangeMeasurementCreator(context),
86                                                                 1.0, 3.0, 300.0);
87          propagator.clearStepHandlers();
88  
89          final RangeTroposphericDelayModifier modifier =
90              new RangeTroposphericDelayModifier(ModifiedSaastamoinenModel.getStandardModel());
91  
92          for (final ObservedMeasurement<?> measurement : measurements) {
93              final AbsoluteDate date = measurement.getDate();
94  
95              final SpacecraftState refState = propagator.propagate(date);
96  
97              Range range = (Range) measurement;
98              EstimatedMeasurementBase<Range> evalNoMod = range.estimateWithoutDerivatives(new SpacecraftState[] { refState });
99  
100 
101             // add modifier
102             range.addModifier(modifier);
103             EstimatedMeasurement<Range> eval = range.estimate(0, 0, new SpacecraftState[] { refState });
104 
105             final double diffMeters = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
106 
107             final double epsilon = 1e-6;
108             Assertions.assertTrue(Precision.compareTo(diffMeters, 12., epsilon) < 0);
109             Assertions.assertTrue(Precision.compareTo(diffMeters, 0., epsilon) > 0);
110             Assertions.assertEquals(evalNoMod.getEstimatedValue()[0],
111                                     eval.getOriginalEstimatedValue()[0],
112                                     3.0e-14 * evalNoMod.getEstimatedValue()[0]);
113             Assertions.assertEquals(eval.getEstimatedValue()[0] - eval.getOriginalEstimatedValue()[0],
114                                     eval.getAppliedEffects().get(modifier)[0],
115                                     1.0e-15);
116             Assertions.assertEquals(1,
117                                     eval.getAppliedEffects().entrySet().stream().
118                                     filter(e -> e.getKey().getEffectName().equals("troposphere")).count());
119         }
120     }
121 
122     @Test
123     public void testRangeEstimatedTropoModifier() {
124 
125         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
126 
127         final NumericalPropagatorBuilder propagatorBuilder =
128                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
129                                               1.0e-6, 60.0, 0.001);
130 
131         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
132                                                                            propagatorBuilder);
133         final List<ObservedMeasurement<?>> measurements =
134                         EstimationTestUtils.createMeasurements(propagator,
135                                                                new TwoWayRangeMeasurementCreator(context),
136                                                                1.0, 3.0, 300.0);
137         propagator.clearStepHandlers();
138 
139         for (final ObservedMeasurement<?> measurement : measurements) {
140             final AbsoluteDate date = measurement.getDate();
141 
142             final SpacecraftState refState = propagator.propagate(date);
143 
144             Range range = (Range) measurement;
145             EstimatedMeasurementBase<Range> evalNoMod = range.estimateWithoutDerivatives(new SpacecraftState[] { refState });
146 
147             // add modifier
148             final GroundStation                  stationParameter = ((Range) measurement).getStation();
149             final TopocentricFrame               baseFrame        = stationParameter.getBaseFrame();
150             final NiellMappingFunctionModel      mappingFunction  = new NiellMappingFunctionModel();
151             final EstimatedModel                 tropoModel       = new EstimatedModel(mappingFunction, 5.0);
152             final RangeTroposphericDelayModifier modifier         = new RangeTroposphericDelayModifier(tropoModel);
153 
154             final ParameterDriver parameterDriver = modifier.getParametersDrivers().get(0);
155             parameterDriver.setSelected(true);
156             parameterDriver.setName(baseFrame.getName() + EstimatedModel.TOTAL_ZENITH_DELAY);
157             range.addModifier(modifier);
158             EstimatedMeasurementBase<Range> eval = range.estimateWithoutDerivatives(new SpacecraftState[] { refState });
159 
160             final double diffMeters = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
161 
162             final double epsilon = 1e-6;
163             Assertions.assertTrue(Precision.compareTo(diffMeters, 12., epsilon) < 0);
164             Assertions.assertTrue(Precision.compareTo(diffMeters, 0., epsilon) > 0);
165         }
166     }
167 
168     @Test
169     public void testPhaseTropoModifier() {
170 
171         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
172 
173         final NumericalPropagatorBuilder propagatorBuilder =
174                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
175                                               1.0e-6, 60.0, 0.001);
176 
177         // create perfect range measurements
178         for (final GroundStation station : context.stations) {
179             station.getClockOffsetDriver().setSelected(true);
180             station.getEastOffsetDriver().setSelected(true);
181             station.getNorthOffsetDriver().setSelected(true);
182             station.getZenithOffsetDriver().setSelected(true);
183         }
184         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
185                                                                            propagatorBuilder);
186         final int    ambiguity         = 1234;
187         final double groundClockOffset =  12.0e-6;
188         for (final GroundStation station : context.stations) {
189             station.getClockOffsetDriver().setValue(groundClockOffset);
190         }
191         final double satClockOffset    = 345.0e-6;
192         final List<ObservedMeasurement<?>> measurements =
193                         EstimationTestUtils.createMeasurements(propagator,
194                                                                new PhaseMeasurementCreator(context,
195                                                                                            PredefinedGnssSignal.G01,
196                                                                                            ambiguity,
197                                                                                            satClockOffset),
198                                                                1.0, 3.0, 300.0);
199         propagator.clearStepHandlers();
200 
201         final PhaseTroposphericDelayModifier modifier =
202             new PhaseTroposphericDelayModifier(ModifiedSaastamoinenModel.getStandardModel());
203 
204         for (final ObservedMeasurement<?> measurement : measurements) {
205             final AbsoluteDate date = measurement.getDate();
206 
207             final SpacecraftState refState = propagator.propagate(date);
208 
209             Phase phase = (Phase) measurement;
210             EstimatedMeasurementBase<Phase> evalNoMod = phase.estimateWithoutDerivatives(new SpacecraftState[] { refState });
211 
212 
213             // add modifier
214             phase.addModifier(modifier);
215             EstimatedMeasurementBase<Phase> eval = phase.estimateWithoutDerivatives(new SpacecraftState[] { refState });
216 
217             final double diffMeters = (eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0]) * phase.getWavelength();
218 
219             final double epsilon = 1e-6;
220             Assertions.assertTrue(Precision.compareTo(diffMeters, 12., epsilon) < 0);
221             Assertions.assertTrue(Precision.compareTo(diffMeters, 0., epsilon) > 0);
222         }
223     }
224 
225     @Test
226     public void testPhaseEstimatedTropoModifier() {
227 
228         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
229 
230         final NumericalPropagatorBuilder propagatorBuilder =
231                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
232                                               1.0e-6, 60.0, 0.001);
233 
234         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
235                                                                            propagatorBuilder);
236         final int    ambiguity         = 1234;
237         final double groundClockOffset =  12.0e-6;
238         for (final GroundStation station : context.stations) {
239             station.getClockOffsetDriver().setValue(groundClockOffset);
240         }
241         final double satClockOffset    = 345.0e-6;
242         final List<ObservedMeasurement<?>> measurements =
243                         EstimationTestUtils.createMeasurements(propagator,
244                                                                new PhaseMeasurementCreator(context,
245                                                                                            PredefinedGnssSignal.G01,
246                                                                                            ambiguity,
247                                                                                            satClockOffset),
248                                                                1.0, 3.0, 300.0);
249         propagator.clearStepHandlers();
250 
251         for (final ObservedMeasurement<?> measurement : measurements) {
252             final AbsoluteDate date = measurement.getDate();
253 
254             final SpacecraftState refState = propagator.propagate(date);
255 
256             Phase phase = (Phase) measurement;
257             EstimatedMeasurementBase<Phase> evalNoMod = phase.estimateWithoutDerivatives(new SpacecraftState[] { refState });
258 
259 
260             // add modifier
261             final GroundStation                  stationParameter = phase.getStation();
262             final TopocentricFrame               baseFrame        = stationParameter.getBaseFrame();
263             final NiellMappingFunctionModel      mappingFunction  = new NiellMappingFunctionModel();
264             final EstimatedModel                 tropoModel       = new EstimatedModel(mappingFunction, 5.0);
265             final PhaseTroposphericDelayModifier modifier         = new PhaseTroposphericDelayModifier(tropoModel);
266 
267             final ParameterDriver parameterDriver = modifier.getParametersDrivers().get(0);
268             parameterDriver.setSelected(true);
269             parameterDriver.setName(baseFrame.getName() + EstimatedModel.TOTAL_ZENITH_DELAY);
270             phase.addModifier(modifier);
271             EstimatedMeasurementBase<Phase> eval = phase.estimateWithoutDerivatives(new SpacecraftState[] { refState });
272 
273             final double diffMeters = (eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0]) * phase.getWavelength();
274 
275             final double epsilon = 1e-6;
276             Assertions.assertTrue(Precision.compareTo(diffMeters, 12., epsilon) < 0);
277             Assertions.assertTrue(Precision.compareTo(diffMeters, 0., epsilon) > 0);
278         }
279     }
280 
281     @Test
282     public void testTurnAroundRangeTropoModifier() {
283 
284         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
285 
286         final NumericalPropagatorBuilder propagatorBuilder =
287                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
288                                               1.0e-6, 60.0, 0.001);
289 
290         // Create perfect turn-around measurements
291         for (Map.Entry<GroundStation, GroundStation> entry : context.TARstations.entrySet()) {
292             final GroundStation    primaryStation = entry.getKey();
293             final GroundStation    secondaryStation  = entry.getValue();
294             primaryStation.getClockOffsetDriver().setSelected(true);
295             primaryStation.getEastOffsetDriver().setSelected(true);
296             primaryStation.getNorthOffsetDriver().setSelected(true);
297             primaryStation.getZenithOffsetDriver().setSelected(true);
298             secondaryStation.getClockOffsetDriver().setSelected(false);
299             secondaryStation.getEastOffsetDriver().setSelected(true);
300             secondaryStation.getNorthOffsetDriver().setSelected(true);
301             secondaryStation.getZenithOffsetDriver().setSelected(true);
302         }
303         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
304                                                                            propagatorBuilder);
305         final List<ObservedMeasurement<?>> measurements =
306                         EstimationTestUtils.createMeasurements(propagator,
307                                                                new TurnAroundRangeMeasurementCreator(context),
308                                                                1.0, 3.0, 300.0);
309         propagator.clearStepHandlers();
310 
311         final TurnAroundRangeTroposphericDelayModifier modifier =
312             new TurnAroundRangeTroposphericDelayModifier(ModifiedSaastamoinenModel.getStandardModel());
313 
314         for (final ObservedMeasurement<?> measurement : measurements) {
315             final AbsoluteDate date = measurement.getDate();
316 
317             final SpacecraftState refState = propagator.propagate(date);
318 
319             TurnAroundRange turnAroundRange = (TurnAroundRange) measurement;
320             EstimatedMeasurementBase<TurnAroundRange> evalNoMod = turnAroundRange.estimateWithoutDerivatives(new SpacecraftState[] { refState });
321 
322             // add modifier
323             turnAroundRange.addModifier(modifier);
324             //
325             EstimatedMeasurement<TurnAroundRange> eval = turnAroundRange.estimate(0, 0, new SpacecraftState[] { refState });
326 
327             final double diffMeters = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
328 
329             final double epsilon = 1e-6;
330             Assertions.assertTrue(Precision.compareTo(diffMeters, 12., epsilon) < 0);
331             Assertions.assertTrue(Precision.compareTo(diffMeters, 0., epsilon) > 0);
332         }
333     }
334 
335     @Test
336     public void testBistaticRangeTropoModifier() {
337 
338         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
339 
340         final NumericalPropagatorBuilder propagatorBuilder =
341                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
342                                               1.0e-6, 60.0, 0.001);
343         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
344                                                                            propagatorBuilder);
345         // create perfect range measurements
346         final GroundStation emitter = context.BRRstations.getKey();
347         emitter.getClockOffsetDriver().setSelected(true);
348         emitter.getEastOffsetDriver().setSelected(true);
349         emitter.getNorthOffsetDriver().setSelected(true);
350         emitter.getZenithOffsetDriver().setSelected(true);
351         final GroundStation receiver = context.BRRstations.getValue();
352         receiver.getClockOffsetDriver().setSelected(true);
353         receiver.getEastOffsetDriver().setSelected(true);
354         receiver.getNorthOffsetDriver().setSelected(true);
355         receiver.getZenithOffsetDriver().setSelected(true);
356         final List<ObservedMeasurement<?>> measurements =
357                         EstimationTestUtils.createMeasurements(propagator,
358                                                                new BistaticRangeMeasurementCreator(context),
359                                                                1.0, 3.0, 300.0);
360         propagator.clearStepHandlers();
361 
362         final BistaticRangeTroposphericDelayModifier modifier =
363                         new BistaticRangeTroposphericDelayModifier(new ModifiedSaastamoinenModel(TroposphericModelUtils.STANDARD_ATMOSPHERE_PROVIDER));
364 
365         for (final ObservedMeasurement<?> measurement : measurements) {
366             BistaticRange biRange = (BistaticRange) measurement;
367             final SpacecraftState refState = propagator.propagate(biRange.getDate());
368 
369             // Estimate without modifier
370             EstimatedMeasurementBase<BistaticRange> evalNoMod = biRange.estimateWithoutDerivatives(new SpacecraftState[] { refState });
371 
372             // add modifier
373             biRange.addModifier(modifier);
374 
375             // Estimate with modifier
376             EstimatedMeasurementBase<BistaticRange> eval = biRange.estimateWithoutDerivatives(new SpacecraftState[] { refState });
377 
378             final double diffMeters = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
379 
380             Assertions.assertTrue(diffMeters < 9.1);
381             Assertions.assertTrue(diffMeters > 5.8);
382         }
383     }
384 
385     @Test
386     public void testBistaticRangeRateTropoModifier() {
387 
388         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
389 
390         final NumericalPropagatorBuilder propagatorBuilder =
391                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
392                                               1.0e-6, 60.0, 0.001);
393         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
394                                                                            propagatorBuilder);
395         // create perfect range-rate measurements
396         final GroundStation emitter = context.BRRstations.getKey();
397         emitter.getEastOffsetDriver().setSelected(true);
398         emitter.getNorthOffsetDriver().setSelected(true);
399         emitter.getZenithOffsetDriver().setSelected(true);
400         final GroundStation receiver = context.BRRstations.getValue();
401         receiver.getClockOffsetDriver().setSelected(true);
402         receiver.getEastOffsetDriver().setSelected(true);
403         receiver.getNorthOffsetDriver().setSelected(true);
404         receiver.getZenithOffsetDriver().setSelected(true);
405         final List<ObservedMeasurement<?>> measurements =
406                         EstimationTestUtils.createMeasurements(propagator,
407                                                                new BistaticRangeRateMeasurementCreator(context),
408                                                                1.0, 3.0, 300.0);
409         propagator.clearStepHandlers();
410 
411         final BistaticRangeRateTroposphericDelayModifier modifier =
412                         new BistaticRangeRateTroposphericDelayModifier(ModifiedSaastamoinenModel.getStandardModel());
413 
414         for (final ObservedMeasurement<?> measurement : measurements) {
415             BistaticRangeRate biRangeRate = (BistaticRangeRate) measurement;
416             final SpacecraftState refState = propagator.propagate(biRangeRate.getDate());
417 
418             // Estimate without modifier
419             EstimatedMeasurementBase<BistaticRangeRate> evalNoMod = biRangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
420 
421             // add modifier
422             biRangeRate.addModifier(modifier);
423 
424             // Estimate with modifier
425             EstimatedMeasurementBase<BistaticRangeRate> eval = biRangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
426 
427             final double diffMetersSec = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
428 
429             final double epsilon = 5e-5;
430             Assertions.assertTrue(Precision.compareTo(diffMetersSec,  0.005, epsilon) < 0);
431             Assertions.assertTrue(Precision.compareTo(diffMetersSec, -0.007, epsilon) > 0);
432         }
433     }
434 
435     @Test
436     public void testBistaticRangeRateEstimatedTropoModifier() {
437 
438         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
439 
440         final NumericalPropagatorBuilder propagatorBuilder =
441                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
442                                               1.0e-6, 60.0, 0.001);
443         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
444                                                                            propagatorBuilder);
445         // create perfect range-rate measurements
446         final GroundStation emitter = context.BRRstations.getKey();
447         emitter.getEastOffsetDriver().setSelected(true);
448         emitter.getNorthOffsetDriver().setSelected(true);
449         emitter.getZenithOffsetDriver().setSelected(true);
450         final GroundStation receiver = context.BRRstations.getValue();
451         receiver.getClockOffsetDriver().setSelected(true);
452         receiver.getEastOffsetDriver().setSelected(true);
453         receiver.getNorthOffsetDriver().setSelected(true);
454         receiver.getZenithOffsetDriver().setSelected(true);
455         final List<ObservedMeasurement<?>> measurements =
456                         EstimationTestUtils.createMeasurements(propagator,
457                                                                new BistaticRangeRateMeasurementCreator(context),
458                                                                1.0, 3.0, 300.0);
459         propagator.clearStepHandlers();
460 
461         for (final ObservedMeasurement<?> measurement : measurements) {
462             BistaticRangeRate biRangeRate = (BistaticRangeRate) measurement;
463             final SpacecraftState refState = propagator.propagate(biRangeRate.getDate());
464 
465             // Estimate without modifier
466             EstimatedMeasurementBase<BistaticRangeRate> evalNoMod = biRangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
467 
468             // add modifier
469             final NiellMappingFunctionModel mappingFunc = new NiellMappingFunctionModel();
470             final EstimatedModel            tropoModel  = new EstimatedModel(mappingFunc, 5.0);
471             final BistaticRangeRateTroposphericDelayModifier modifier =
472                             new BistaticRangeRateTroposphericDelayModifier(tropoModel);
473 
474             final TopocentricFrame baseFrame = biRangeRate.getReceiverStation().getBaseFrame();
475             final ParameterDriver parameterDriver = modifier.getParametersDrivers().get(0);
476             parameterDriver.setSelected(true);
477             parameterDriver.setName(baseFrame.getName() + EstimatedModel.TOTAL_ZENITH_DELAY);
478 
479             biRangeRate.addModifier(modifier);
480 
481             // Estimate with modifier
482             EstimatedMeasurementBase<BistaticRangeRate> eval = biRangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
483 
484             final double diffMetersSec = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
485 
486             final double epsilon = 1e-4;
487             Assertions.assertTrue(Precision.compareTo(diffMetersSec,  0.010, epsilon) < 0);
488             Assertions.assertTrue(Precision.compareTo(diffMetersSec, -0.014, epsilon) > 0);
489         }
490     }
491 
492     @Test
493     public void testTDOATropoModifier() {
494 
495         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
496 
497         final NumericalPropagatorBuilder propagatorBuilder =
498                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
499                                               1.0e-6, 60.0, 0.001);
500         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
501                                                                            propagatorBuilder);
502         // create perfect range measurements
503         final GroundStation emitter = context.TDOAstations.getKey();
504         emitter.getClockOffsetDriver().setSelected(true);
505         emitter.getEastOffsetDriver().setSelected(true);
506         emitter.getNorthOffsetDriver().setSelected(true);
507         emitter.getZenithOffsetDriver().setSelected(true);
508         final GroundStation receiver = context.TDOAstations.getValue();
509         receiver.getClockOffsetDriver().setSelected(true);
510         receiver.getEastOffsetDriver().setSelected(true);
511         receiver.getNorthOffsetDriver().setSelected(true);
512         receiver.getZenithOffsetDriver().setSelected(true);
513         final List<ObservedMeasurement<?>> measurements =
514                         EstimationTestUtils.createMeasurements(propagator,
515                                                                new TDOAMeasurementCreator(context),
516                                                                1.0, 3.0, 300.0);
517         propagator.clearStepHandlers();
518 
519         final TDOATroposphericDelayModifier modifier =
520                         new TDOATroposphericDelayModifier(ModifiedSaastamoinenModel.getStandardModel());
521 
522         for (final ObservedMeasurement<?> measurement : measurements) {
523             TDOA tdoa = (TDOA) measurement;
524             final SpacecraftState refState = propagator.propagate(tdoa.getDate());
525 
526             // Estimate without modifier
527             EstimatedMeasurementBase<TDOA> evalNoMod = tdoa.estimateWithoutDerivatives(new SpacecraftState[] { refState });
528 
529             // add modifier
530             tdoa.addModifier(modifier);
531 
532             // Estimate with modifier
533             EstimatedMeasurementBase<TDOA> eval = tdoa.estimateWithoutDerivatives(new SpacecraftState[] { refState });
534 
535             final double diffSec = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
536 
537             final double epsilon = 1.e-11;
538             Assertions.assertTrue(Precision.compareTo(diffSec,  2.35e-9, epsilon) < 0);
539             Assertions.assertTrue(Precision.compareTo(diffSec, -1.05e-9, epsilon) > 0);
540         }
541     }
542 
543     @Test
544     public void testTDOAEstimatedTropoModifier() {
545 
546         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
547 
548         final NumericalPropagatorBuilder propagatorBuilder =
549                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
550                                               1.0e-6, 60.0, 0.001);
551         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
552                                                                            propagatorBuilder);
553         // create perfect range measurements
554         final GroundStation emitter = context.TDOAstations.getKey();
555         emitter.getClockOffsetDriver().setSelected(true);
556         emitter.getEastOffsetDriver().setSelected(true);
557         emitter.getNorthOffsetDriver().setSelected(true);
558         emitter.getZenithOffsetDriver().setSelected(true);
559         final GroundStation receiver = context.TDOAstations.getValue();
560         receiver.getClockOffsetDriver().setSelected(true);
561         receiver.getEastOffsetDriver().setSelected(true);
562         receiver.getNorthOffsetDriver().setSelected(true);
563         receiver.getZenithOffsetDriver().setSelected(true);
564         final List<ObservedMeasurement<?>> measurements =
565                         EstimationTestUtils.createMeasurements(propagator,
566                                                                new TDOAMeasurementCreator(context),
567                                                                1.0, 3.0, 300.0);
568         propagator.clearStepHandlers();
569 
570         for (final ObservedMeasurement<?> measurement : measurements) {
571             TDOA tdoa = (TDOA) measurement;
572             final SpacecraftState refState = propagator.propagate(tdoa.getDate());
573 
574             // Estimate without modifier
575             EstimatedMeasurementBase<TDOA> evalNoMod = tdoa.estimateWithoutDerivatives(new SpacecraftState[] { refState });
576 
577             // add modifier
578             final NiellMappingFunctionModel     mappingFunct = new NiellMappingFunctionModel();
579             final EstimatedModel                tropoModel   = new EstimatedModel(mappingFunct, 5.0);
580             final TDOATroposphericDelayModifier modifier     = new TDOATroposphericDelayModifier(tropoModel);
581 
582             final TopocentricFrame baseFrame      = tdoa.getPrimeStation().getBaseFrame();
583             final ParameterDriver parameterDriver = modifier.getParametersDrivers().get(0);
584             parameterDriver.setSelected(true);
585             parameterDriver.setName(baseFrame.getName() + EstimatedModel.TOTAL_ZENITH_DELAY);
586 
587             tdoa.addModifier(modifier);
588 
589             // Estimate with modifier
590             EstimatedMeasurement<TDOA> eval = tdoa.estimate(0, 0, new SpacecraftState[] { refState });
591 
592             final double diffSec = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
593 
594             final double epsilon = 5.e-11;
595             Assertions.assertTrue(Precision.compareTo(diffSec,  4.90e-9, epsilon) < 0);
596             Assertions.assertTrue(Precision.compareTo(diffSec, -2.21e-9, epsilon) > 0);
597         }
598     }
599 
600     @Test
601     public void testRangeRateTropoModifier() {
602 
603         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
604 
605         final NumericalPropagatorBuilder propagatorBuilder =
606                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
607                                               1.0e-6, 60.0, 0.001);
608 
609         // create perfect range measurements
610         for (final GroundStation station : context.stations) {
611             station.getClockOffsetDriver().setSelected(true);
612             station.getEastOffsetDriver().setSelected(true);
613             station.getNorthOffsetDriver().setSelected(true);
614             station.getZenithOffsetDriver().setSelected(true);
615         }
616         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
617                                                                            propagatorBuilder);
618         final double satClkDrift = 3.2e-10;
619         final List<ObservedMeasurement<?>> measurements =
620                         EstimationTestUtils.createMeasurements(propagator,
621                                                                new RangeRateMeasurementCreator(context, false, satClkDrift),
622                                                                1.0, 3.0, 300.0);
623         propagator.clearStepHandlers();
624 
625         final RangeRateTroposphericDelayModifier modifier =
626             new RangeRateTroposphericDelayModifier(ModifiedSaastamoinenModel.getStandardModel(), false);
627 
628         for (final ObservedMeasurement<?> measurement : measurements) {
629             final AbsoluteDate date = measurement.getDate();
630 
631             final SpacecraftState refState = propagator.propagate(date);
632 
633             RangeRate rangeRate = (RangeRate) measurement;
634             EstimatedMeasurementBase<RangeRate> evalNoMod = rangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
635 
636             // add modifier
637             rangeRate.addModifier(modifier);
638 
639             //
640             EstimatedMeasurement<RangeRate> eval = rangeRate.estimate(0, 0, new SpacecraftState[] { refState });
641 
642             final double diffMetersSec = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
643 
644             final double epsilon = 1e-6;
645             Assertions.assertTrue(Precision.compareTo(diffMetersSec, 0.01, epsilon) < 0);
646             Assertions.assertTrue(Precision.compareTo(diffMetersSec, -0.01, epsilon) > 0);
647         }
648     }
649 
650     @Test
651     public void testRangeRateEstimatedTropoModifier() {
652 
653         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
654 
655         final NumericalPropagatorBuilder propagatorBuilder =
656                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
657                                               1.0e-6, 60.0, 0.001);
658 
659         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
660                                                                            propagatorBuilder);
661         final double satClkDrift = 3.2e-10;
662         final List<ObservedMeasurement<?>> measurements =
663                         EstimationTestUtils.createMeasurements(propagator,
664                                                                new RangeRateMeasurementCreator(context, false, satClkDrift),
665                                                                1.0, 3.0, 300.0);
666         propagator.clearStepHandlers();
667 
668         for (final ObservedMeasurement<?> measurement : measurements) {
669             final AbsoluteDate date = measurement.getDate();
670 
671             final SpacecraftState refState = propagator.propagate(date);
672 
673             RangeRate rangeRate = (RangeRate) measurement;
674             EstimatedMeasurementBase<RangeRate> evalNoMod = rangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
675 
676             // add modifier
677             final GroundStation                      stationParameter = ((RangeRate) measurement).getStation();
678             final TopocentricFrame                   baseFrame        = stationParameter.getBaseFrame();
679             final NiellMappingFunctionModel          mappingFunction  = new NiellMappingFunctionModel();
680             final EstimatedModel                     tropoModel       = new EstimatedModel(mappingFunction, 5.0);
681             final RangeRateTroposphericDelayModifier modifier         = new RangeRateTroposphericDelayModifier(tropoModel, false);
682 
683             final ParameterDriver parameterDriver = modifier.getParametersDrivers().get(0);
684             parameterDriver.setSelected(true);
685             parameterDriver.setName(baseFrame.getName() + EstimatedModel.TOTAL_ZENITH_DELAY);
686             rangeRate.addModifier(modifier);
687 
688             //
689             EstimatedMeasurementBase<RangeRate> eval = rangeRate.estimateWithoutDerivatives(new SpacecraftState[] { refState });
690 
691             final double diffMetersSec = eval.getEstimatedValue()[0] - evalNoMod.getEstimatedValue()[0];
692 
693             final double epsilon = 1e-6;
694             Assertions.assertTrue(Precision.compareTo(diffMetersSec, 0.01, epsilon) < 0);
695             Assertions.assertTrue(Precision.compareTo(diffMetersSec, -0.01, epsilon) > 0);
696         }
697     }
698 
699     @Test
700     public void testAngularRadioRefractionModifier() {
701 
702         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
703 
704         final NumericalPropagatorBuilder propagatorBuilder =
705                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
706                                               1.0e-6, 60.0, 0.001);
707 
708         // create perfect angular measurements
709         for (final GroundStation station : context.stations) {
710             station.getClockOffsetDriver().setSelected(true);
711             station.getEastOffsetDriver().setSelected(true);
712             station.getNorthOffsetDriver().setSelected(true);
713             station.getZenithOffsetDriver().setSelected(true);
714         }
715         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
716                                                                            propagatorBuilder);
717         final List<ObservedMeasurement<?>> measurements =
718                         EstimationTestUtils.createMeasurements(propagator,
719                                                                new AngularAzElMeasurementCreator(context),
720                                                                1.0, 3.0, 300.0);
721         propagator.clearStepHandlers();
722 
723 
724 
725         for (final ObservedMeasurement<?> measurement : measurements) {
726             final AbsoluteDate date = measurement.getDate();
727 
728             final SpacecraftState refState = propagator.propagate(date);
729 
730             AngularAzEl angular = (AngularAzEl) measurement;
731             EstimatedMeasurementBase<AngularAzEl> evalNoMod = angular.estimateWithoutDerivatives(new SpacecraftState[] { refState });
732 
733             // get the altitude of the station (in kilometers)
734             final double altitude = angular.getStation().getBaseFrame().getPoint().getAltitude() / 1000.;
735 
736             final AngularRadioRefractionModifier modifier = new AngularRadioRefractionModifier(new ITURP834AtmosphericRefraction(altitude));
737             // add modifier
738             angular.addModifier(modifier);
739             //
740             EstimatedMeasurementBase<AngularAzEl> eval = angular.estimateWithoutDerivatives(new SpacecraftState[] { refState });
741 
742             final double diffEl = MathUtils.normalizeAngle(eval.getEstimatedValue()[1], evalNoMod.getEstimatedValue()[1]) - evalNoMod.getEstimatedValue()[1];
743             // TODO: check threshold
744             Assertions.assertEquals(0.0, diffEl, 1.0e-3);
745         }
746     }
747 
748 }