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.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.util.FastMath;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.BeforeEach;
23  import org.junit.jupiter.api.Test;
24  import org.orekit.Utils;
25  import org.orekit.bodies.GeodeticPoint;
26  import org.orekit.bodies.OneAxisEllipsoid;
27  import org.orekit.estimation.measurements.EstimatedMeasurement;
28  import org.orekit.estimation.measurements.GroundStation;
29  import org.orekit.estimation.measurements.ObservableSatellite;
30  import org.orekit.estimation.measurements.Range;
31  import org.orekit.frames.FramesFactory;
32  import org.orekit.frames.TopocentricFrame;
33  import org.orekit.orbits.CartesianOrbit;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.propagation.analytical.tle.TLE;
36  import org.orekit.propagation.analytical.tle.TLEPropagator;
37  import org.orekit.utils.Constants;
38  import org.orekit.utils.IERSConventions;
39  import org.orekit.utils.ParameterDriver;
40  import org.orekit.utils.TimeStampedPVCoordinates;
41  
42  import java.util.Arrays;
43  
44  
45  /**
46   * Check against prediction in
47   *
48   * "Springer Handbook oƒ Global Navigation Satellite Systems, Teunissen, Montenbruck"
49   *
50   * An approximate value is given in terms of delay for Galileo satellites.
51   * As these satellites are close to GPS satellites, we consider the delays to be
52   * of the same order, namely around 62ps.
53   *
54   * The values produced by the modifiers are translated in terms of delay and checked against
55   * the approximate value.
56   */
57  
58  public class RelativisticJ2ClockRangeModifierTest {
59  
60  
61      @Test
62      public void testRelativisticJ2ClockCorrection() {
63  
64          // Station
65          final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
66                                                              Constants.WGS84_EARTH_FLATTENING,
67                                                              FramesFactory.getITRF(IERSConventions.IERS_2010, true));
68          final GeodeticPoint point    = new GeodeticPoint(FastMath.toRadians(42.0), FastMath.toRadians(1.0), 100.0);
69          final TopocentricFrame topo  = new TopocentricFrame(earth, point, "");
70          final GroundStation station  = new GroundStation(topo);
71  
72          // Satellite (GPS orbit from TLE)
73          final TLE tle = new TLE("1 28474U 04045A   20252.59334296 -.00000043  00000-0  00000-0 0  9998",
74                                  "2 28474  55.0265  49.5108 0200271 267.9106 149.0797  2.00552216116165");
75          final TimeStampedPVCoordinates satPV = TLEPropagator.selectExtrapolator(tle).getPVCoordinates(tle.getDate(), FramesFactory.getEME2000());
76          final SpacecraftState state = new SpacecraftState(new CartesianOrbit(satPV, FramesFactory.getEME2000(), Constants.WGS84_EARTH_MU));
77  
78          // Set reference date to station drivers
79          for (ParameterDriver driver : Arrays.asList(station.getClockOffsetDriver(),
80                                                      station.getEastOffsetDriver(),
81                                                      station.getNorthOffsetDriver(),
82                                                      station.getZenithOffsetDriver(),
83                                                      station.getPrimeMeridianOffsetDriver(),
84                                                      station.getPrimeMeridianDriftDriver(),
85                                                      station.getPolarOffsetXDriver(),
86                                                      station.getPolarDriftXDriver(),
87                                                      station.getPolarOffsetYDriver(),
88                                                      station.getPolarDriftYDriver())) {
89              if (driver.getReferenceDate() == null) {
90                  driver.setReferenceDate(state.getDate());
91              }
92          }
93  
94          // Station PV
95          final Vector3D zero = Vector3D.ZERO;
96          final TimeStampedPVCoordinates stationPV = station.getOffsetToInertial(state.getFrame(), state.getDate(), false).transformPVCoordinates(new TimeStampedPVCoordinates(state.getDate(), zero, zero, zero));
97  
98          // Range measurement
99          final Range range = new Range(station, false, state.getDate(), 26584264.45, 1.0, 1.0, new ObservableSatellite(0));
100         final EstimatedMeasurement<Range> estimated = new EstimatedMeasurement<>(range, 0, 0,
101                         new SpacecraftState[] {state},
102                         new TimeStampedPVCoordinates[] {state.getPVCoordinates(), stationPV});
103         estimated.setEstimatedValue(range.getObservedValue()[0]);
104         Assertions.assertEquals(0.0, estimated.getObservedValue()[0] - estimated.getEstimatedValue()[0], 1.0e-3);
105 
106         // Measurement modifier
107         final RelativisticJ2ClockRangeModifier modifier = new RelativisticJ2ClockRangeModifier(Constants.WGS84_EARTH_MU,
108                 Constants.WGS84_EARTH_C20, Constants.WGS84_EARTH_EQUATORIAL_RADIUS );
109         modifier.modify(estimated);
110         Assertions.assertEquals(0, modifier.getParametersDrivers().size());
111 
112         // Verify : According to Teunissen and Montenbruck, the delay is supposed to be around 60ps for Galileo.
113         //          The computed value is equal to 63.3 ps, therefore lying in the supposed range.
114         Assertions.assertEquals(-0.019414, estimated.getObservedValue()[0] - estimated.getEstimatedValue()[0], 1.0e-6);
115         Assertions.assertEquals(1,
116                                 estimated.getAppliedEffects().entrySet().stream().
117                                 filter(e -> e.getKey().getEffectName().equals("J₂ clock relativity")).count());
118 
119     }
120 
121     @Test
122     /**
123      * Testing if the 2 way case is taken into account in the computation of the delay.
124      * This has the effect of shifting the index from 0 to 1 for the selected PV coordinates
125      * to get the emitter's parameters and not the station's.
126      */
127     public void testRelativisticJ2ClockCorrectionTwoWay() {
128 
129         // Station
130         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
131                                                             Constants.WGS84_EARTH_FLATTENING,
132                                                             FramesFactory.getITRF(IERSConventions.IERS_2010, true));
133         final GeodeticPoint point    = new GeodeticPoint(FastMath.toRadians(42.0), FastMath.toRadians(1.0), 100.0);
134         final TopocentricFrame topo  = new TopocentricFrame(earth, point, "");
135         final GroundStation station  = new GroundStation(topo);
136 
137         // Satellite (GPS orbit from TLE)
138         final TLE tle = new TLE("1 28474U 04045A   20252.59334296 -.00000043  00000-0  00000-0 0  9998",
139                                 "2 28474  55.0265  49.5108 0200271 267.9106 149.0797  2.00552216116165");
140         final TimeStampedPVCoordinates satPV = TLEPropagator.selectExtrapolator(tle).getPVCoordinates(tle.getDate(), FramesFactory.getEME2000());
141         final SpacecraftState state = new SpacecraftState(new CartesianOrbit(satPV, FramesFactory.getEME2000(), Constants.WGS84_EARTH_MU));
142 
143         // Set reference date to station drivers
144         for (ParameterDriver driver : Arrays.asList(station.getClockOffsetDriver(),
145                                                     station.getEastOffsetDriver(),
146                                                     station.getNorthOffsetDriver(),
147                                                     station.getZenithOffsetDriver(),
148                                                     station.getPrimeMeridianOffsetDriver(),
149                                                     station.getPrimeMeridianDriftDriver(),
150                                                     station.getPolarOffsetXDriver(),
151                                                     station.getPolarDriftXDriver(),
152                                                     station.getPolarOffsetYDriver(),
153                                                     station.getPolarDriftYDriver())) {
154             if (driver.getReferenceDate() == null) {
155                 driver.setReferenceDate(state.getDate());
156             }
157         }
158 
159         // Station PV
160         final Vector3D zero = Vector3D.ZERO;
161         final TimeStampedPVCoordinates stationPV = station.getOffsetToInertial(state.getFrame(), state.getDate(), false).transformPVCoordinates(new TimeStampedPVCoordinates(state.getDate(), zero, zero, zero));
162 
163         // Range measurement : The two way boolean is set to true.
164         final Range range = new Range(station, true, state.getDate(), 26584264.45, 1.0, 1.0, new ObservableSatellite(0));
165         // The EstimatedMeasurement variable has now 3 TimeStampedPVCoordinates, as expected for the 2 way case.
166         final EstimatedMeasurement<Range> estimated = new EstimatedMeasurement<>(range, 0, 0,
167                         new SpacecraftState[] {state},
168                         new TimeStampedPVCoordinates[] {stationPV, state.getPVCoordinates(), stationPV});
169         estimated.setEstimatedValue(range.getObservedValue()[0]);
170         Assertions.assertEquals(0.0, estimated.getObservedValue()[0] - estimated.getEstimatedValue()[0], 1.0e-3);
171 
172         // Measurement modifier
173         final RelativisticJ2ClockRangeModifier modifier = new RelativisticJ2ClockRangeModifier(Constants.WGS84_EARTH_MU,
174                 Constants.WGS84_EARTH_C20, Constants.WGS84_EARTH_EQUATORIAL_RADIUS );
175         modifier.modify(estimated);
176         Assertions.assertEquals(0, modifier.getParametersDrivers().size());
177 
178         // Verify : According to Teunissen and Montenbruck, the delay is supposed to be around 60ps for Galileo.
179         //          The computed value is equal to 63.3 ps, therefore lying in the supposed range.
180         Assertions.assertEquals(-0.019414, estimated.getObservedValue()[0] - estimated.getEstimatedValue()[0], 1.0e-6);
181 
182     }
183 
184     @BeforeEach
185     public void setUp() {
186         Utils.setDataRoot("regular-data");
187     }
188 
189 }