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.generation;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.analysis.differentiation.DSFactory;
22  import org.hipparchus.analysis.differentiation.DerivativeStructure;
23  import org.hipparchus.util.Binary64Field;
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.TestUtils;
29  import org.orekit.Utils;
30  import org.orekit.frames.Frame;
31  import org.orekit.frames.FramesFactory;
32  import org.orekit.orbits.CartesianOrbit;
33  import org.orekit.orbits.FieldCartesianOrbit;
34  import org.orekit.propagation.FieldPropagator;
35  import org.orekit.propagation.FieldSpacecraftState;
36  import org.orekit.propagation.Propagator;
37  import org.orekit.propagation.SpacecraftState;
38  import org.orekit.propagation.analytical.tle.FieldTLE;
39  import org.orekit.propagation.analytical.tle.FieldTLEPropagator;
40  import org.orekit.propagation.analytical.tle.TLE;
41  import org.orekit.propagation.analytical.tle.TLEPropagator;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.utils.Constants;
44  import org.orekit.utils.TimeStampedFieldPVCoordinates;
45  import org.orekit.utils.TimeStampedPVCoordinates;
46  
47  
48  public class FixedPointTleGenerationAlgorithmTest {
49  
50      private TLE geoTLE;
51      private TLE leoTLE;
52  
53      @BeforeEach
54      public void setUp() {
55          Utils.setDataRoot("regular-data");
56          geoTLE = new TLE("1 27508U 02040A   12021.25695307 -.00000113  00000-0  10000-3 0  7326",
57                           "2 27508   0.0571 356.7800 0005033 344.4621 218.7816  1.00271798 34501");
58          leoTLE = new TLE("1 31135U 07013A   11003.00000000  .00000816  00000+0  47577-4 0    11",
59                           "2 31135   2.4656 183.9084 0021119 236.4164  60.4567 15.10546832    15");
60      }
61  
62      @Test
63      public void testOneMoreRevolution() {
64          final TLEPropagator propagator = TLEPropagator.selectExtrapolator(leoTLE);
65          final int initRevolutionNumber = leoTLE.getRevolutionNumberAtEpoch();
66          final double dt =  2 * FastMath.PI / leoTLE.getMeanMotion();
67          final AbsoluteDate target = leoTLE.getDate().shiftedBy(dt);
68          final SpacecraftState endState = propagator.propagate(target);
69          final TLE endLEOTLE = new FixedPointTleGenerationAlgorithm().generate(endState, leoTLE);
70          final int endRevolutionNumber = endLEOTLE.getRevolutionNumberAtEpoch();
71          Assertions.assertEquals(initRevolutionNumber + 1 , endRevolutionNumber);
72      }
73  
74      @Test
75      public void testOneLessRevolution() {
76          final TLEPropagator propagator = TLEPropagator.selectExtrapolator(leoTLE);
77          final int initRevolutionNumber = leoTLE.getRevolutionNumberAtEpoch();
78          final double dt =  - 2 * FastMath.PI / leoTLE.getMeanMotion();
79          final AbsoluteDate target = leoTLE.getDate().shiftedBy(dt);
80          final SpacecraftState endState = propagator.propagate(target);
81          final TLE endLEOTLE = new FixedPointTleGenerationAlgorithm().generate(endState, leoTLE);
82          final int endRevolutionNumber = endLEOTLE.getRevolutionNumberAtEpoch();
83          Assertions.assertEquals(initRevolutionNumber - 1 , endRevolutionNumber);
84      }
85  
86      @Test
87      public void testIssue781() {
88  
89          final DSFactory factory = new DSFactory(6, 3);
90          final String line1 = "1 05709U 71116A   21105.62692147  .00000088  00000-0  00000-0 0  9999";
91          final String line2 = "2 05709  10.8207 310.3659 0014139  71.9531 277.0561  0.99618926100056";
92          Assertions.assertTrue(TLE.isFormatOK(line1, line2));
93  
94          final FieldTLE<DerivativeStructure> fieldTLE = new FieldTLE<>(factory.getDerivativeField(), line1, line2);
95          final FieldTLEPropagator<DerivativeStructure> tlePropagator = FieldTLEPropagator.selectExtrapolator(fieldTLE, fieldTLE.getParameters(factory.getDerivativeField()));
96          final FieldTLE<DerivativeStructure> fieldTLE1 = new FixedPointTleGenerationAlgorithm().generate(tlePropagator.getInitialState(), fieldTLE);
97          Assertions.assertEquals(line2, fieldTLE1.getLine2());
98  
99      }
100 
101     @Test
102     public void testIssue802() {
103 
104         // Initialize TLE
105         final TLE tleISS = new TLE("1 25544U 98067A   21035.14486477  .00001026  00000-0  26816-4 0  9998",
106                                    "2 25544  51.6455 280.7636 0002243 335.6496 186.1723 15.48938788267977");
107 
108         // TLE propagator
109         final TLEPropagator propagator = TLEPropagator.selectExtrapolator(tleISS);
110 
111         // State at TLE epoch
112         final SpacecraftState state = propagator.propagate(tleISS.getDate());
113 
114         // Changes frame
115         final Frame eme2000 = FramesFactory.getEME2000();
116         final TimeStampedPVCoordinates pv = state.getPVCoordinates(eme2000);
117         final CartesianOrbit orbit = new CartesianOrbit(pv, eme2000, state.getOrbit().getMu());
118 
119         // Convert to TLE
120         final TLE rebuilt = new FixedPointTleGenerationAlgorithm().generate(new SpacecraftState(orbit), tleISS);
121 
122         // Verify
123         Assertions.assertEquals(tleISS.getLine1(), rebuilt.getLine1());
124         Assertions.assertEquals(tleISS.getLine2(), rebuilt.getLine2());
125     }
126 
127     @Test
128     public void testIssue802Field() {
129         doTestIssue802Field(Binary64Field.getInstance());
130     }
131 
132     private <T extends CalculusFieldElement<T>> void doTestIssue802Field(final Field<T> field) {
133 
134         // Initialize TLE
135         final FieldTLE<T> tleISS = new FieldTLE<>(field, "1 25544U 98067A   21035.14486477  .00001026  00000-0  26816-4 0  9998",
136                                                          "2 25544  51.6455 280.7636 0002243 335.6496 186.1723 15.48938788267977");
137 
138         // TLE propagator
139         final FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(tleISS, tleISS.getParameters(field));
140 
141         // State at TLE epoch
142         final FieldSpacecraftState<T> state = propagator.propagate(tleISS.getDate());
143 
144         // Changes frame
145         final Frame eme2000 = FramesFactory.getEME2000();
146         final TimeStampedFieldPVCoordinates<T> pv = state.getPVCoordinates(eme2000);
147         final FieldCartesianOrbit<T> orbit = new FieldCartesianOrbit<T>(pv, eme2000, state.getOrbit().getMu());
148 
149         // Convert to TLE
150         final FieldTLE<T> rebuilt = new FixedPointTleGenerationAlgorithm().generate(new FieldSpacecraftState<T>(orbit), tleISS);
151 
152         // Verify
153         Assertions.assertEquals(tleISS.getLine1(), rebuilt.getLine1());
154         Assertions.assertEquals(tleISS.getLine2(), rebuilt.getLine2());
155     }
156 
157     @Test
158     public void testIssue864() {
159 
160         // Initialize TLE
161         final TLE tleISS = new TLE("1 25544U 98067A   21035.14486477  .00001026  00000-0  26816-4 0  9998",
162                                    "2 25544  51.6455 280.7636 0002243 335.6496 186.1723 15.48938788267977");
163 
164         // TLE propagator
165         final TLEPropagator propagator = TLEPropagator.selectExtrapolator(tleISS);
166 
167         // State at TLE epoch
168         final SpacecraftState state = propagator.propagate(tleISS.getDate());
169 
170         // Set the BStar driver to selected
171         tleISS.getParametersDrivers().forEach(driver -> driver.setSelected(true));
172 
173         // Convert to TLE
174         final TLE rebuilt = new FixedPointTleGenerationAlgorithm().generate(state, tleISS);
175 
176         // Verify if driver is still selected
177         rebuilt.getParametersDrivers().forEach(driver -> Assertions.assertTrue(driver.isSelected()));
178 
179     }
180 
181     @Test
182     public void testIssue864Field() {
183         doTestIssue864Field(Binary64Field.getInstance());
184     }
185 
186     private <T extends CalculusFieldElement<T>>  void doTestIssue864Field(final Field<T> field) {
187 
188         // Initialize TLE
189         final FieldTLE<T>  tleISS = new FieldTLE<> (field, "1 25544U 98067A   21035.14486477  .00001026  00000-0  26816-4 0  9998",
190                                                            "2 25544  51.6455 280.7636 0002243 335.6496 186.1723 15.48938788267977");
191 
192         // TLE propagator
193         final FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(tleISS, tleISS.getParameters(field));
194 
195         // State at TLE epoch
196         final FieldSpacecraftState<T> state = propagator.propagate(tleISS.getDate());
197 
198         // Set the BStar driver to selected
199         tleISS.getParametersDrivers().forEach(driver -> driver.setSelected(true));
200 
201         // Convert to TLE
202         final FieldTLE<T> rebuilt = new FixedPointTleGenerationAlgorithm().generate(state, tleISS);
203 
204         // Verify if driver is still selected
205         rebuilt.getParametersDrivers().forEach(driver -> Assertions.assertTrue(driver.isSelected()));
206 
207     }
208 
209     @Test
210     public void testIssue859() {
211 
212         // INTELSAT 25 TLE taken from Celestrak the 2021-11-24T07:45:00.000
213         // Because the satellite eccentricity and inclination are closed to zero, this satellite
214         // reach convergence issues when converting the spacecraft's state to TLE using default
215         // parameters (i.e., epsilon = 1.0e-10, maxIterations = 100, and scale = 1.0).
216         final TLE tle = new TLE("1 33153U 08034A   21327.46310733 -.00000207  00000+0  00000+0 0  9990",
217                                 "2 33153   0.0042  20.7353 0003042 213.9370 323.2156  1.00270917 48929");
218 
219         // The purpose here is to verify that reducing the scale value (from 1.0 to 0.5) while keeping
220         // 1.0e-10 for epsilon value  solve the issue. In other words, keeping epsilon value to its
221         // default value show that the accuracy of the generated TLE is acceptable
222         Propagator p = TLEPropagator.selectExtrapolator(tle);
223         FixedPointTleGenerationAlgorithm algorithm =
224                         new FixedPointTleGenerationAlgorithm(FixedPointTleGenerationAlgorithm.EPSILON_DEFAULT,
225                                                              400, 0.5);
226         final TLE converted = algorithm.generate(p.getInitialState(), tle);
227 
228         // Verify
229         Assertions.assertEquals(tle.getLine2(),                   converted.getLine2());
230         Assertions.assertEquals(tle.getBStar(),                   converted.getBStar());
231         Assertions.assertEquals(0.,                               converted.getDate().durationFrom(tle.getDate()));
232         Assertions.assertEquals(tle.getSatelliteNumber(),         converted.getSatelliteNumber());
233         Assertions.assertEquals(tle.getClassification(),          converted.getClassification());
234         Assertions.assertEquals(tle.getLaunchYear(),              converted.getLaunchYear());
235         Assertions.assertEquals(tle.getLaunchNumber(),            converted.getLaunchNumber());
236         Assertions.assertEquals(tle.getLaunchPiece(),             converted.getLaunchPiece());
237         Assertions.assertEquals(tle.getElementNumber(),           converted.getElementNumber());
238         Assertions.assertEquals(tle.getRevolutionNumberAtEpoch(), converted.getRevolutionNumberAtEpoch());
239 
240     }
241 
242     @Test
243     public void testIssue859Field() {
244         dotestIssue859Field(Binary64Field.getInstance());
245     }
246 
247     private <T extends CalculusFieldElement<T>> void dotestIssue859Field(Field<T> field) {
248 
249         // INTELSAT 25 TLE taken from Celestrak the 2021-11-24T07:45:00.000
250         // Because the satellite eccentricity and inclination are closed to zero, this satellite
251         // reach convergence issues when converting the spacecraft's state to TLE using default
252         // parameters (i.e., epsilon = 1.0e-10, maxIterations = 100, and scale = 1.0).
253         final FieldTLE<T> tle = new FieldTLE<>(field, "1 33153U 08034A   21327.46310733 -.00000207  00000+0  00000+0 0  9990",
254                                                       "2 33153   0.0042  20.7353 0003042 213.9370 323.2156  1.00270917 48929");
255 
256         // The purpose here is to verify that reducing the scale value (from 1.0 to 0.5) while keeping
257         // 1.0e-10 for epsilon value  solve the issue. In other words, keeping epsilon value to its
258         // default value show that the accuracy of the generated TLE is acceptable
259         FieldPropagator<T> p = FieldTLEPropagator.selectExtrapolator(tle, tle.getParameters(field, tle.getDate()));
260         FixedPointTleGenerationAlgorithm algorithm =
261                         new FixedPointTleGenerationAlgorithm(FixedPointTleGenerationAlgorithm.EPSILON_DEFAULT,
262                                                              400, 0.5);
263         final FieldTLE<T> converted = algorithm.generate(p.getInitialState(), tle);
264 
265         // Verify
266         Assertions.assertEquals(tle.getLine2(),                   converted.getLine2());
267         Assertions.assertEquals(tle.getBStar(),                   converted.getBStar());
268         Assertions.assertEquals(0.,                               converted.getDate().durationFrom(tle.getDate()).getReal());
269         Assertions.assertEquals(tle.getSatelliteNumber(),         converted.getSatelliteNumber());
270         Assertions.assertEquals(tle.getClassification(),          converted.getClassification());
271         Assertions.assertEquals(tle.getLaunchYear(),              converted.getLaunchYear());
272         Assertions.assertEquals(tle.getLaunchNumber(),            converted.getLaunchNumber());
273         Assertions.assertEquals(tle.getLaunchPiece(),             converted.getLaunchPiece());
274         Assertions.assertEquals(tle.getElementNumber(),           converted.getElementNumber());
275         Assertions.assertEquals(tle.getRevolutionNumberAtEpoch(), converted.getRevolutionNumberAtEpoch());
276 
277     }
278 
279     @Test
280     public void testConversionLeo() {
281         checkConversion(leoTLE, 5.2e-9);
282     }
283 
284     @Test
285     public void testConversionGeo() {
286         checkConversion(geoTLE, 9.2e-8);
287     }
288 
289     /** Check the State to TLE conversion. */
290     private void checkConversion(final TLE tle, final double threshold) {
291 
292         Propagator p = TLEPropagator.selectExtrapolator(tle);
293         final TLE converted = new FixedPointTleGenerationAlgorithm().generate(p.getInitialState(), tle);
294 
295         Assertions.assertEquals(tle.getSatelliteNumber(),         converted.getSatelliteNumber());
296         Assertions.assertEquals(tle.getClassification(),          converted.getClassification());
297         Assertions.assertEquals(tle.getLaunchYear(),              converted.getLaunchYear());
298         Assertions.assertEquals(tle.getLaunchNumber(),            converted.getLaunchNumber());
299         Assertions.assertEquals(tle.getLaunchPiece(),             converted.getLaunchPiece());
300         Assertions.assertEquals(tle.getElementNumber(),           converted.getElementNumber());
301         Assertions.assertEquals(tle.getRevolutionNumberAtEpoch(), converted.getRevolutionNumberAtEpoch());
302 
303         Assertions.assertEquals(tle.getMeanMotion(), converted.getMeanMotion(), threshold * tle.getMeanMotion());
304         Assertions.assertEquals(tle.getE(), converted.getE(), threshold * tle.getE());
305         Assertions.assertEquals(tle.getI(), converted.getI(), threshold * tle.getI());
306         Assertions.assertEquals(tle.getPerigeeArgument(), converted.getPerigeeArgument(), threshold * tle.getPerigeeArgument());
307         Assertions.assertEquals(tle.getRaan(), converted.getRaan(), threshold * tle.getRaan());
308         Assertions.assertEquals(tle.getMeanAnomaly(), converted.getMeanAnomaly(), threshold * tle.getMeanAnomaly());
309         Assertions.assertEquals(tle.getBStar(), converted.getBStar(), threshold * tle.getBStar());
310 
311     }
312 
313     @Test
314     public void testConversionLeoField() {
315         doTestConversionLeoField(Binary64Field.getInstance());
316     }
317 
318     private <T extends CalculusFieldElement<T>> void doTestConversionLeoField(final Field<T> field) {
319         final FieldTLE<T> leoTLE = new FieldTLE<>(field, "1 31135U 07013A   11003.00000000  .00000816  00000+0  47577-4 0    11",
320                                                          "2 31135   2.4656 183.9084 0021119 236.4164  60.4567 15.10546832    15");
321         checkConversion(leoTLE, field, 5.2e-9);
322     }
323 
324     @Test
325     public void testConversionGeoField() {
326         doConversionGeoField(Binary64Field.getInstance());
327     }
328 
329     private <T extends CalculusFieldElement<T>> void doConversionGeoField(final Field<T> field) {
330         final FieldTLE<T> geoTLE = new FieldTLE<>(field, "1 27508U 02040A   12021.25695307 -.00000113  00000-0  10000-3 0  7326",
331                                                          "2 27508   0.0571 356.7800 0005033 344.4621 218.7816  1.00271798 34501");
332         checkConversion(geoTLE, field, 9.2e-8);
333     }
334 
335     private <T extends CalculusFieldElement<T>> void checkConversion(final FieldTLE<T> tle, final Field<T> field,
336                                                                      final double threshold) {
337 
338         FieldPropagator<T> p = FieldTLEPropagator.selectExtrapolator(tle, tle.getParameters(field, tle.getDate()));
339         final FieldTLE<T> converted = new FixedPointTleGenerationAlgorithm().generate(p.getInitialState(), tle);
340         
341         Assertions.assertEquals(tle.getSatelliteNumber(),         converted.getSatelliteNumber());
342         Assertions.assertEquals(tle.getClassification(),          converted.getClassification());
343         Assertions.assertEquals(tle.getLaunchYear(),              converted.getLaunchYear());
344         Assertions.assertEquals(tle.getLaunchNumber(),            converted.getLaunchNumber());
345         Assertions.assertEquals(tle.getLaunchPiece(),             converted.getLaunchPiece());
346         Assertions.assertEquals(tle.getElementNumber(),           converted.getElementNumber());
347         Assertions.assertEquals(tle.getRevolutionNumberAtEpoch(), converted.getRevolutionNumberAtEpoch());
348         
349         Assertions.assertEquals(tle.getMeanMotion().getReal(), converted.getMeanMotion().getReal(),threshold * tle.getMeanMotion().getReal());
350         Assertions.assertEquals(tle.getE().getReal(), converted.getE().getReal(), threshold * tle.getE().getReal());
351         Assertions.assertEquals(tle.getI().getReal(), converted.getI().getReal(), threshold * tle.getI().getReal());
352         Assertions.assertEquals(tle.getPerigeeArgument().getReal(), converted.getPerigeeArgument().getReal(), threshold * tle.getPerigeeArgument().getReal());
353         Assertions.assertEquals(tle.getRaan().getReal(), converted.getRaan().getReal(), threshold * tle.getRaan().getReal());
354         Assertions.assertEquals(tle.getMeanAnomaly().getReal(), converted.getMeanAnomaly().getReal(), threshold * tle.getMeanAnomaly().getReal());
355         Assertions.assertEquals(tle.getBStar(), converted.getBStar(), threshold * tle.getBStar());
356     }
357 
358     @Test
359     public void testIssue1408() {
360         // The result of the TLE generation shall not be affected by the value of the gravitational
361         // parameter of the input orbit.
362 
363         final TleGenerationAlgorithm algorithm = new FixedPointTleGenerationAlgorithm();
364         
365         // Initial TLE
366         final TLE initialTle = new TLE("1 31135U 07013A   11003.00000000  .00000816  00000-0  47577-4 0    12",
367                                        "2 31135   2.4656 183.9084 0021119 236.4164  60.4567 15.10546832    15");
368         final SpacecraftState expectedState = TLEPropagator.selectExtrapolator(initialTle).getInitialState();
369         final TimeStampedPVCoordinates expectedPV = expectedState.getPVCoordinates();
370 
371         // Create a new orbit using the position and velocity taken from the TLE, but using
372         // a gravitational parameter mu different from the TLE mu.
373         final CartesianOrbit orbit = new CartesianOrbit(expectedState.getOrbit().getPVCoordinates(), expectedState.getFrame(), Constants.EGM96_EARTH_MU);
374 
375         // Generate a TLE based on the orbit and check that the generated TLE is the same as the
376         // original one.
377         final TLE generatedTle = TLE.stateToTLE(new SpacecraftState(orbit), initialTle, algorithm);
378         Assertions.assertEquals(initialTle.getLine1(), generatedTle.getLine1());
379         Assertions.assertEquals(initialTle.getLine2(), generatedTle.getLine2());
380         final TimeStampedPVCoordinates actualPvCoordinates = TLEPropagator.selectExtrapolator(generatedTle).getInitialState().getPVCoordinates();
381         TestUtils.validateVector3D(expectedPV.getPosition(), actualPvCoordinates.getPosition(), 1.0e-4);
382         TestUtils.validateVector3D(expectedPV.getVelocity(), actualPvCoordinates.getVelocity(), 1.0e-4);
383     }
384 }