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.propagation.analytical.tle;
18  
19  import org.hamcrest.MatcherAssert;
20  import org.hipparchus.geometry.euclidean.threed.Line;
21  import org.hipparchus.geometry.euclidean.threed.Rotation;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.ode.events.Action;
24  import org.hipparchus.util.FastMath;
25  import org.junit.jupiter.api.Assertions;
26  import org.junit.jupiter.api.BeforeEach;
27  import org.junit.jupiter.api.Test;
28  import org.orekit.OrekitMatchers;
29  import org.orekit.Utils;
30  import org.orekit.attitudes.BodyCenterPointing;
31  import org.orekit.bodies.OneAxisEllipsoid;
32  import org.orekit.frames.Frame;
33  import org.orekit.frames.FramesFactory;
34  import org.orekit.frames.StaticTransform;
35  import org.orekit.orbits.Orbit;
36  import org.orekit.propagation.BoundedPropagator;
37  import org.orekit.propagation.EphemerisGenerator;
38  import org.orekit.propagation.Propagator;
39  import org.orekit.propagation.SpacecraftState;
40  import org.orekit.propagation.events.DateDetector;
41  import org.orekit.propagation.events.handlers.EventHandler;
42  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
43  import org.orekit.time.AbsoluteDate;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.IERSConventions;
46  
47  
48  public class TLEPropagatorTest {
49  
50      private TLE tle;
51      private double period;
52  
53      @Test
54      public void testsecondaryMode() {
55  
56          TLEPropagator propagator = TLEPropagator.selectExtrapolator(tle);
57          AbsoluteDate initDate = tle.getDate();
58          SpacecraftState initialState = propagator.getInitialState();
59  
60          // Simulate a full period of a GPS satellite
61          // -----------------------------------------
62          SpacecraftState finalState = propagator.propagate(initDate.shiftedBy(period));
63  
64          // Check results
65          final Orbit initialOrbit = initialState.getOrbit();
66          final Orbit finalOrbit = finalState.getOrbit();
67          Assertions.assertEquals(initialOrbit.getA(), finalOrbit.getA(), 1e-1);
68          Assertions.assertEquals(initialOrbit.getEquinoctialEx(), finalOrbit.getEquinoctialEx(), 1e-1);
69          Assertions.assertEquals(initialOrbit.getEquinoctialEy(), finalOrbit.getEquinoctialEy(), 1e-1);
70          Assertions.assertEquals(initialOrbit.getHx(), finalOrbit.getHx(), 1e-3);
71          Assertions.assertEquals(initialOrbit.getHy(), finalOrbit.getHy(), 1e-3);
72          Assertions.assertEquals(initialOrbit.getLM(), finalOrbit.getLM(), 1e-3);
73  
74      }
75  
76      @Test
77      public void testEphemerisMode() {
78  
79          TLEPropagator propagator = TLEPropagator.selectExtrapolator(tle);
80          final EphemerisGenerator generator = propagator.getEphemerisGenerator();
81  
82          AbsoluteDate initDate = tle.getDate();
83          SpacecraftState initialState = propagator.getInitialState();
84  
85          // Simulate a full period of a GPS satellite
86          // -----------------------------------------
87          AbsoluteDate endDate = initDate.shiftedBy(period);
88          propagator.propagate(endDate);
89  
90          // get the ephemeris
91          BoundedPropagator boundedProp = generator.getGeneratedEphemeris();
92  
93          // get the initial state from the ephemeris and check if it is the same as
94          // the initial state from the TLE
95          SpacecraftState boundedState = boundedProp.propagate(initDate);
96  
97          // Check results
98          final Orbit initialOrbit = initialState.getOrbit();
99          final Orbit boundedOrbit = boundedState.getOrbit();
100         Assertions.assertEquals(initialOrbit.getA(), boundedOrbit.getA(), 0.);
101         Assertions.assertEquals(initialOrbit.getEquinoctialEx(), boundedOrbit.getEquinoctialEx(), 0.);
102         Assertions.assertEquals(initialOrbit.getEquinoctialEy(), boundedOrbit.getEquinoctialEy(), 0.);
103         Assertions.assertEquals(initialOrbit.getHx(), boundedOrbit.getHx(), 0.);
104         Assertions.assertEquals(initialOrbit.getHy(), boundedOrbit.getHy(), 0.);
105         Assertions.assertEquals(initialOrbit.getLM(), boundedOrbit.getLM(), 1e-14);
106 
107         SpacecraftState finalState = boundedProp.propagate(endDate);
108 
109         // Check results
110         final Orbit finalOrbit = finalState.getOrbit();
111         Assertions.assertEquals(initialOrbit.getA(), finalOrbit.getA(), 1e-1);
112         Assertions.assertEquals(initialOrbit.getEquinoctialEx(), finalOrbit.getEquinoctialEx(), 1e-1);
113         Assertions.assertEquals(initialOrbit.getEquinoctialEy(), finalOrbit.getEquinoctialEy(), 1e-1);
114         Assertions.assertEquals(initialOrbit.getHx(), finalOrbit.getHx(), 1e-3);
115         Assertions.assertEquals(initialOrbit.getHy(), finalOrbit.getHy(), 1e-3);
116         Assertions.assertEquals(initialOrbit.getLM(), finalOrbit.getLM(), 1e-3);
117 
118     }
119 
120     /** Test if body center belongs to the direction pointed by the satellite
121      */
122     @Test
123     public void testBodyCenterInPointingDirection() {
124 
125         final Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
126         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
127                                                             Constants.WGS84_EARTH_FLATTENING,
128                                                             itrf);
129         DistanceChecker checker = new DistanceChecker(itrf);
130 
131         // with Earth pointing attitude, distance should be small
132         TLEPropagator propagator =
133                 TLEPropagator.selectExtrapolator(tle,
134                                                  new BodyCenterPointing(FramesFactory.getTEME(), earth),
135                                                  Propagator.DEFAULT_MASS);
136         propagator.setStepHandler(900.0, checker);
137         propagator.propagate(tle.getDate().shiftedBy(period));
138         Assertions.assertEquals(0.0, checker.getMaxDistance(), 2.0e-7);
139 
140         // with default attitude mode, distance should be large
141         propagator = TLEPropagator.selectExtrapolator(tle);
142         propagator.setStepHandler(900.0, checker);
143         propagator.propagate(tle.getDate().shiftedBy(period));
144         MatcherAssert.assertThat(checker.getMinDistance(),
145                 OrekitMatchers.greaterThan(1.5218e7));
146         Assertions.assertEquals(2.6572e7, checker.getMaxDistance(), 1000.0);
147 
148     }
149 
150     private static class DistanceChecker implements OrekitFixedStepHandler {
151 
152         private final Frame itrf;
153         private double minDistance;
154         private double maxDistance;
155 
156         public DistanceChecker(Frame itrf) {
157             this.itrf = itrf;
158         }
159 
160         public double getMinDistance() {
161             return minDistance;
162         }
163 
164         public double getMaxDistance() {
165             return maxDistance;
166         }
167 
168         public void init(SpacecraftState s0, AbsoluteDate t, double step) {
169             minDistance = Double.POSITIVE_INFINITY;
170             maxDistance = Double.NEGATIVE_INFINITY;
171         }
172 
173         public void handleStep(SpacecraftState currentState) {
174             // Get satellite attitude rotation, i.e rotation from inertial frame to satellite frame
175             Rotation rotSat = currentState.getAttitude().getRotation();
176 
177             // Transform Z axis from satellite frame to inertial frame
178             Vector3D zSat = rotSat.applyInverseTo(Vector3D.PLUS_K);
179 
180             // Transform Z axis from inertial frame to ITRF
181             StaticTransform transform = currentState.getFrame().getStaticTransformTo(itrf, currentState.getDate());
182             Vector3D zSatITRF = transform.transformVector(zSat);
183 
184             // Transform satellite position/velocity from inertial frame to ITRF
185             Vector3D posSatITRF = transform.transformPosition(currentState.getPosition());
186 
187             // Line containing satellite point and following pointing direction
188             Line pointingLine = new Line(posSatITRF,
189                                          posSatITRF.add(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
190                                                         zSatITRF),
191                                          1.0e-10);
192 
193             double distance = pointingLine.distance(Vector3D.ZERO);
194             minDistance = FastMath.min(minDistance, distance);
195             maxDistance = FastMath.max(maxDistance, distance);
196         }
197 
198     }
199 
200     @Test
201     void testResetInitialState() {
202         // GIVEN
203         final TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(tle);
204         final SpacecraftState initialState = tlePropagator.getInitialState();
205         final double unexpectedMass = initialState.getMass();
206         final double expectedMass = 2. * unexpectedMass;
207         final SpacecraftState newState = new SpacecraftState(initialState.getOrbit(), initialState.getAttitude(),
208                 expectedMass);
209 
210         // WHEN
211         tlePropagator.resetInitialState(newState);
212 
213         // THEN
214         final SpacecraftState actualState = tlePropagator.getInitialState();
215         Assertions.assertEquals(expectedMass, tlePropagator.getMass(actualState.getDate()));
216         Assertions.assertEquals(expectedMass, actualState.getMass());
217         Assertions.assertNotEquals(unexpectedMass, actualState.getMass());
218     }
219 
220     @Test
221     void testResetIntermediateStateForward() {
222         testResetIntermediateStateTemplate(true);
223     }
224 
225     @Test
226     void testResetIntermediateStateBackward() {
227         testResetIntermediateStateTemplate(false);
228     }
229 
230     void testResetIntermediateStateTemplate(final boolean isForward) {
231         // GIVEN
232         final TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(tle);
233         final double expectedMass = 2000.;
234         final SpacecraftState propagatedState = tlePropagator.propagate(tle.getDate().shiftedBy(1));
235         final SpacecraftState modifiedState = new SpacecraftState(propagatedState.getOrbit(), expectedMass);
236 
237         // WHEN
238         tlePropagator.resetIntermediateState(modifiedState, isForward);
239 
240         // THEN
241         final double tinyTimeShift = (isForward) ? 1e-3 : -1e-3;
242         final double actualMass = tlePropagator.getMass(modifiedState.getDate().shiftedBy(tinyTimeShift));
243         Assertions.assertEquals(expectedMass, actualMass);
244     }
245 
246     @Test
247     void testResetIntermediateStateHighLevelForward() {
248         testResetIntermediateStateHighLevelTemplate(true);
249     }
250 
251     @Test
252     void testResetIntermediateStateHighLevelBackward() {
253         testResetIntermediateStateHighLevelTemplate(false);
254     }
255 
256     void testResetIntermediateStateHighLevelTemplate(final boolean isForward) {
257         // GIVEN
258         final TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(tle);
259         final AbsoluteDate epoch = tlePropagator.getInitialState().getDate();
260         final double totalShift = 1e4;
261         final double shiftSign = isForward ? 1 : -1;
262         final AbsoluteDate targetDate = epoch.shiftedBy(totalShift * shiftSign);
263         final EventHandler stateResetter = (s, detector, increasing) -> Action.RESET_STATE;
264         final DateDetector detector = new DateDetector(targetDate)
265                 .withThreshold(1e-8).withHandler(stateResetter);
266         tlePropagator.addEventDetector(detector);
267 
268         // WHEN
269         final SpacecraftState actualState = tlePropagator.propagate(targetDate);
270 
271         // THEN
272         final SpacecraftState expectedState = TLEPropagator.selectExtrapolator(tle).propagate(targetDate);
273         final Vector3D expectedPosition = expectedState.getPosition();
274         final Vector3D actualPosition = actualState.getPosition();
275         final double tolerance = 1e-1;
276         Assertions.assertEquals(expectedPosition.getX(), actualPosition.getX(), tolerance);
277         Assertions.assertEquals(expectedPosition.getY(), actualPosition.getY(), tolerance);
278         Assertions.assertEquals(expectedPosition.getZ(), actualPosition.getZ(), tolerance);
279     }
280 
281     @BeforeEach
282     public void setUp() {
283         Utils.setDataRoot("regular-data");
284 
285         // setup a TLE for a GPS satellite
286         String line1 = "1 37753U 11036A   12090.13205652 -.00000006  00000-0  00000+0 0  2272";
287         String line2 = "2 37753  55.0032 176.5796 0004733  13.2285 346.8266  2.00565440  5153";
288 
289         tle = new TLE(line1, line2);
290 
291         // the period of the GPS satellite
292         period = 717.97 * 60.0;
293     }
294 
295 }
296