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.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.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.util.Binary64;
25  import org.hipparchus.util.Binary64Field;
26  import org.hipparchus.util.CombinatoricsUtils;
27  import org.hipparchus.util.FastMath;
28  import org.hipparchus.util.MathArrays;
29  import org.junit.jupiter.api.Assertions;
30  import org.junit.jupiter.api.BeforeEach;
31  import org.junit.jupiter.api.DisplayName;
32  import org.junit.jupiter.api.Test;
33  import org.orekit.Utils;
34  import org.orekit.errors.OrekitException;
35  import org.orekit.errors.OrekitMessages;
36  import org.orekit.propagation.FieldSpacecraftState;
37  import org.orekit.propagation.conversion.osc2mean.FixedPointConverter;
38  import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.DateComponents;
41  import org.orekit.time.FieldAbsoluteDate;
42  import org.orekit.time.TimeComponents;
43  import org.orekit.time.TimeOffset;
44  import org.orekit.time.TimeScalesFactory;
45  import org.orekit.utils.Constants;
46  import org.orekit.utils.FieldPVCoordinates;
47  
48  import java.io.BufferedReader;
49  import java.io.IOException;
50  import java.io.InputStream;
51  import java.io.InputStreamReader;
52  import java.text.ParseException;
53  
54  public class FieldTLETest {
55  
56  
57  
58      @Test
59      public void testTLEFormat() {
60          doTestTLEFormat(Binary64Field.getInstance());
61      }
62  
63      @Test
64      public void TestIssue196() {
65          doTestIssue196(Binary64Field.getInstance());
66      }
67  
68      @Test
69      public void testSymmetry() {
70          doTestSymmetry(Binary64Field.getInstance());
71      }
72  
73      @Test
74      public void testBug74() {
75          doTestBug74(Binary64Field.getInstance());
76      }
77  
78      @Test
79      public void testBug77() {
80          doTestBug77(Binary64Field.getInstance());
81      }
82  
83      @Test
84      public void testDirectConstruction() {
85          doTestDirectConstruction(Binary64Field.getInstance());
86      }
87  
88      @Test
89      public void testGenerateAlpha5() {
90          doTestGenerateAlpha5(Binary64Field.getInstance());
91      }
92  
93      @Test
94      public void testBug77TooLargeSecondDerivative() {
95          doTestBug77TooLargeSecondDerivative(Binary64Field.getInstance());
96      }
97  
98      @Test
99      public void testBug77TooLargeBStar() {
100         doTestBug77TooLargeBStar(Binary64Field.getInstance());
101     }
102 
103     @Test
104     public void testBug77TooLargeEccentricity() {
105         doTestBug77TooLargeEccentricity(Binary64Field.getInstance());
106     }
107 
108     @Test
109     public void testBug77TooLargeSatelliteNumber1() {
110         doTestBug77TooLargeSatelliteNumber1(Binary64Field.getInstance());
111     }
112 
113     @Test
114     public void testBug77TooLargeSatelliteNumber2() {
115         doTestBug77TooLargeSatelliteNumber2(Binary64Field.getInstance());
116     }
117 
118     @Test
119     public void testDifferentSatNumbers() {
120         Assertions.assertThrows(OrekitException.class, () -> {
121             doTestDifferentSatNumbers(Binary64Field.getInstance());
122         });
123     }
124 
125     @Test
126     public void testChecksumOK() {
127         doTestChecksumOK();
128     }
129 
130     @Test
131     public void testWrongChecksum1() {
132         doTestWrongChecksum1();
133     }
134 
135     @Test
136     public void testWrongChecksum2() {
137         doTestWrongChecksum2();
138     }
139 
140     @Test
141     public void testSatCodeCompliance() throws IOException, OrekitException, ParseException {
142         doTestSatCodeCompliance(Binary64Field.getInstance());
143     }
144 
145     @Test
146     public void testZeroInclination() {
147         doTestZeroInclination(Binary64Field.getInstance());
148     }
149 
150     @Test
151     public void testSymmetryAfterLeapSecondIntroduction() {
152         doTestSymmetryAfterLeapSecondIntroduction(Binary64Field.getInstance());
153     }
154 
155     @Test
156     public void testOldTLE() {
157         doTestOldTLE(Binary64Field.getInstance());
158     }
159 
160     @Test
161     public void testEqualTLE() {
162         doTestEqualTLE(Binary64Field.getInstance());
163     }
164 
165     @Test
166     public void testNonEqualTLE() {
167         doTestNonEqualTLE(Binary64Field.getInstance());
168     }
169 
170     @Test
171     public void testIssue388() {
172         doTestIssue388(Binary64Field.getInstance());
173     }
174 
175     @Test
176     public void testIssue664NegativeRaanPa() {
177         doTestIssue664NegativeRaanPa(Binary64Field.getInstance());
178     }
179 
180     @Test
181     public void testDifferentFields() {
182         String line1 = "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20";
183         String line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62";
184         final DSFactory factory = new DSFactory(1, 1);
185         FieldTLE<DerivativeStructure> tleA = new FieldTLE<>(factory.getDerivativeField(), line1, line2);
186         FieldTLE<Binary64> tleB = new FieldTLE<>(Binary64Field.getInstance(), line1, line2);
187         Assertions.assertNotEquals(tleA, tleB);
188     }
189 
190     public <T extends CalculusFieldElement<T>> void doTestTLEFormat(Field<T> field) {
191 
192         String line1 = "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20";
193         String line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62";
194 
195         Assertions.assertTrue(TLE.isFormatOK(line1, line2));
196 
197         FieldTLE<T> tle = new FieldTLE<T>(field, line1, line2);
198         Assertions.assertEquals(27421, tle.getSatelliteNumber(), 0);
199         Assertions.assertEquals(2002, tle.getLaunchYear());
200         Assertions.assertEquals(21, tle.getLaunchNumber());
201         Assertions.assertEquals("A", tle.getLaunchPiece());
202         Assertions.assertEquals(-0.0089879, tle.getBStar(), 0);
203         Assertions.assertEquals(0, tle.getEphemerisType());
204         Assertions.assertEquals(98.749, FastMath.toDegrees(tle.getI().getReal()), 1e-10);
205         Assertions.assertEquals(199.5121, FastMath.toDegrees(tle.getRaan().getReal()), 1e-10);
206         Assertions.assertEquals(0.0001333, tle.getE().getReal(), 1e-10);
207         Assertions.assertEquals(133.9522, FastMath.toDegrees(tle.getPerigeeArgument().getReal()), 1e-10);
208         Assertions.assertEquals(226.1918, FastMath.toDegrees(tle.getMeanAnomaly().getReal()), 1e-10);
209         Assertions.assertEquals(14.26113993, tle.getMeanMotion().getReal() * Constants.JULIAN_DAY / (2 * FastMath.PI), 0);
210         Assertions.assertEquals(7182888.814633288, tle.computeSemiMajorAxis().getReal(), 1e-10);
211         Assertions.assertEquals(tle.getRevolutionNumberAtEpoch(), 6, 0);
212         Assertions.assertEquals(tle.getElementNumber(), 2 , 0);
213 
214         line1 = "1 T7421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    28";
215         line2 = "2 T7421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    60";
216         Assertions.assertTrue(TLE.isFormatOK(line1, line2));
217 
218         tle = new FieldTLE<T>(field, line1, line2);
219         Assertions.assertEquals(277421, tle.getSatelliteNumber(), 0);
220 
221         line1 = "1 I7421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    28";
222         line2 = "2 I7421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    60";
223         Assertions.assertFalse(TLE.isFormatOK(line1, line2));
224         try {
225             new FieldTLE<T>(field, line1, line2);
226             Assertions.fail("an exception should have been thrown");
227         } catch (NumberFormatException nfe) {
228             // expected
229         }
230 
231         line1 = "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20";
232         line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14*26113993    62";
233         Assertions.assertFalse(TLE.isFormatOK(line1, line2));
234 
235         line1 = "1 27421 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20";
236         line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62";
237         Assertions.assertFalse(TLE.isFormatOK(line1, line2));
238 
239         line1 = "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20";
240         line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 10006113993    62";
241         Assertions.assertFalse(TLE.isFormatOK(line1, line2));
242 
243         line1 = "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879 2 0    20";
244         line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62";
245         Assertions.assertFalse(TLE.isFormatOK(line1, line2));
246     }
247 
248 
249     public <T extends CalculusFieldElement<T>> void doTestIssue196(Field<T> field) {
250 
251         String line1A = "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20";
252         String line1B = "1 27421U 02021A   02124.48976499  -.0002147  00000-0 -89879-2 0    20";
253         String line2 = "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62";
254 
255         Assertions.assertTrue(TLE.isFormatOK(line1A, line2));
256         FieldTLE<T> tleA = new FieldTLE<T>(field, line1A, line2);
257         Assertions.assertTrue(TLE.isFormatOK(line1B, line2));
258         TLE tleB = new TLE(line1B, line2);
259         Assertions.assertEquals(tleA.getSatelliteNumber(),           tleB.getSatelliteNumber(), 0);
260         Assertions.assertEquals(tleA.getLaunchYear(),                tleB.getLaunchYear());
261         Assertions.assertEquals(tleA.getLaunchNumber(),              tleB.getLaunchNumber());
262         Assertions.assertEquals(tleA.getLaunchPiece(),               tleB.getLaunchPiece());
263         Assertions.assertEquals(tleA.getBStar(),           tleB.getBStar(), 0);
264         Assertions.assertEquals(tleA.getEphemerisType(),             tleB.getEphemerisType());
265         Assertions.assertEquals(tleA.getI().getReal(),               tleB.getI(), 1e-10);
266         Assertions.assertEquals(tleA.getRaan().getReal(),            tleB.getRaan(), 1e-10);
267         Assertions.assertEquals(tleA.getE().getReal(),               tleB.getE(), 1e-10);
268         Assertions.assertEquals(tleA.getPerigeeArgument().getReal(), tleB.getPerigeeArgument(), 1e-10);
269         Assertions.assertEquals(tleA.getMeanAnomaly().getReal(),     tleB.getMeanAnomaly(), 1e-10);
270         Assertions.assertEquals(tleA.getMeanMotion().getReal(),      tleB.getMeanMotion(), 0);
271         Assertions.assertEquals(tleA.getRevolutionNumberAtEpoch(),   tleB.getRevolutionNumberAtEpoch(), 0);
272         Assertions.assertEquals(tleA.getElementNumber(),             tleB.getElementNumber(), 0);
273 
274     }
275 
276     public <T extends CalculusFieldElement<T>>void doTestSymmetry(Field<T> field) {
277         checkSymmetry(field, "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
278                       "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
279         checkSymmetry(field, "1 31928U 98067BA  08269.84884916  .00114257  17652-4  13615-3 0  4412",
280                       "2 31928  51.6257 175.4142 0001703  41.9031 318.2112 16.08175249 68368");
281         checkSymmetry(field, "1 T7421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    28",
282                       "2 T7421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    60");
283     }
284 
285     private <T extends CalculusFieldElement<T>> void checkSymmetry(Field<T> field, String line1, String line2) {
286         FieldTLE<T> tleRef = new FieldTLE<T>(field, line1, line2);
287         FieldTLE<T> tle = new FieldTLE<T>(tleRef.getSatelliteNumber(), tleRef.getClassification(),
288                           tleRef.getLaunchYear(), tleRef.getLaunchNumber(), tleRef.getLaunchPiece(),
289                           tleRef.getEphemerisType(), tleRef.getElementNumber(), tleRef.getDate(),
290                           tleRef.getMeanMotion(), tleRef.getMeanMotionFirstDerivative(),
291                           tleRef.getMeanMotionSecondDerivative(), tleRef.getE(), tleRef.getI(),
292                           tleRef.getPerigeeArgument(), tleRef.getRaan(), tleRef.getMeanAnomaly(),
293                           tleRef.getRevolutionNumberAtEpoch(), tleRef.getBStar());
294         Assertions.assertEquals(line1, tle.getLine1());
295         Assertions.assertEquals(line2, tle.getLine2());
296     }
297 
298     public <T extends CalculusFieldElement<T>> void doTestBug74(Field<T> field) {
299         checkSymmetry(field, "1 00001U 00001A   12026.45833333 2.94600864  39565-9  16165-7 1    12",
300                       "2 00001 127.0796 254.4522 0000000 224.9662   0.4817  0.00000000    11");
301     }
302 
303     public <T extends CalculusFieldElement<T> >void doTestBug77(Field<T> field) {
304         checkSymmetry(field, "1 05555U 71086J   12026.96078249 -.00000004  00001-9  01234-9 0  9082",
305                       "2 05555  74.0161 228.9750 0075476 328.9888  30.6709 12.26882470804545");
306     }
307 
308     public <T extends CalculusFieldElement<T>> void doTestDirectConstruction(Field<T> field) {
309         final T T_zero = field.getZero();
310         FieldTLE<T> tleA = new FieldTLE<T>(5555, 'U', 1971, 86, "J", 0, 908,
311                            new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
312                                             new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
313                                             TimeScalesFactory.getUTC()),
314                            T_zero.add(taylorConvert(12.26882470, 1)), T_zero.add(taylorConvert(-0.00000004, 2)), T_zero.add(taylorConvert(0.00001e-9, 3)),
315                            T_zero.add(0.0075476), T_zero.add(FastMath.toRadians(74.0161)), T_zero.add(FastMath.toRadians(328.9888)),
316                            T_zero.add(FastMath.toRadians(228.9750)), T_zero.add(FastMath.toRadians(30.6709)), 80454, 0.01234e-9);
317         FieldTLE<T> tleB =  new FieldTLE<T>(field, "1 05555U 71086J   12026.96078249 -.00000004  00001-9  01234-9 0  9082",
318                             "2 05555  74.0161 228.9750 0075476 328.9888  30.6709 12.26882470804545");
319         Assertions.assertEquals(tleA.getSatelliteNumber(),           tleB.getSatelliteNumber(), 0);
320         Assertions.assertEquals(tleA.getLaunchYear(),                tleB.getLaunchYear());
321         Assertions.assertEquals(tleA.getLaunchNumber(),              tleB.getLaunchNumber());
322         Assertions.assertEquals(tleA.getLaunchPiece(),               tleB.getLaunchPiece());
323         Assertions.assertEquals(tleA.getBStar()          ,           tleB.getBStar(), 0);
324         Assertions.assertEquals(tleA.getEphemerisType(),             tleB.getEphemerisType());
325         Assertions.assertEquals(tleA.getI().getReal(),               tleB.getI().getReal(), 1e-10);
326         Assertions.assertEquals(tleA.getRaan().getReal(),            tleB.getRaan().getReal(), 1e-10);
327         Assertions.assertEquals(tleA.getE().getReal(),               tleB.getE().getReal(), 1e-10);
328         Assertions.assertEquals(tleA.getPerigeeArgument().getReal(), tleB.getPerigeeArgument().getReal(), 1e-10);
329         Assertions.assertEquals(tleA.getMeanAnomaly().getReal(),     tleB.getMeanAnomaly().getReal(), 1e-10);
330         Assertions.assertEquals(tleA.getMeanMotion().getReal(),      tleB.getMeanMotion().getReal(), 0);
331         Assertions.assertEquals(tleA.getRevolutionNumberAtEpoch(),   tleB.getRevolutionNumberAtEpoch(), 0);
332         Assertions.assertEquals(tleA.getElementNumber(),             tleB.getElementNumber(), 0);
333     }
334 
335     public <T extends CalculusFieldElement<T>> void doTestGenerateAlpha5(Field<T> field) {
336         final T T_zero = field.getZero();
337         FieldTLE<T> tle = new FieldTLE<T>(339999, 'U', 1971, 86, "J", 0, 908,
338                           new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
339                                                    new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
340                                                    TimeScalesFactory.getUTC()),
341                           T_zero.add(taylorConvert(12.26882470, 1)),
342                           T_zero.add(taylorConvert(-0.00000004, 2)),
343                           T_zero.add(taylorConvert(0.00001e-9, 3)),
344                           T_zero.add(0.0075476), T_zero.add(FastMath.toRadians(74.0161)),
345                           T_zero.add(FastMath.toRadians(328.9888)),
346                           T_zero.add(FastMath.toRadians(228.9750)),
347                           T_zero.add(FastMath.toRadians(30.6709)),
348                           80454, 0.01234e-9);
349         Assertions.assertEquals("1 Z9999U 71086J   12026.96078249 -.00000004  00001-9  01234-9 0  9088", tle.getLine1());
350         Assertions.assertEquals("2 Z9999  74.0161 228.9750 0075476 328.9888  30.6709 12.26882470804541", tle.getLine2());
351     }
352 
353     public <T extends CalculusFieldElement<T>> void doTestBug77TooLargeSecondDerivative(Field<T> field) {
354         try {
355             final T T_zero = field.getZero();
356             FieldTLE<T> tle = new FieldTLE<T>(5555, 'U', 1971, 86, "J", 0, 908,
357                               new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
358                                                new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
359                                                TimeScalesFactory.getUTC()),
360                               T_zero.add(taylorConvert(12.26882470, 1)), T_zero.add(taylorConvert(-0.00000004, 2)), T_zero.add(taylorConvert(0.99999e11, 3)),
361                               T_zero.add(0.0075476), T_zero.add(FastMath.toRadians(74.0161)), T_zero.add(FastMath.toRadians(328.9888)),
362                               T_zero.add(FastMath.toRadians(228.9750)), T_zero.add(FastMath.toRadians(30.6709)), 80454, 0.01234e-9);
363             tle.getLine1();
364             Assertions.fail("an exception should have been thrown");
365         } catch (OrekitException oe) {
366             Assertions.assertEquals(OrekitMessages.TLE_INVALID_PARAMETER, oe.getSpecifier());
367             Assertions.assertEquals(5555, ((Integer) oe.getParts()[0]).intValue());
368             Assertions.assertEquals("meanMotionSecondDerivative", oe.getParts()[1]);
369         }
370     }
371 
372     public <T extends CalculusFieldElement<T>> void doTestBug77TooLargeBStar(Field<T> field) {
373         try {
374             final T T_zero = field.getZero();
375             FieldTLE<T> tle = new FieldTLE<T>(5555, 'U', 1971, 86, "J", 0, 908,
376                               new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
377                                                new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
378                                                TimeScalesFactory.getUTC()),
379                               T_zero.add(taylorConvert(12.26882470, 1)), T_zero.add(taylorConvert(-0.00000004, 2)), T_zero.add(taylorConvert(0.00001e-9, 3)),
380                               T_zero.add(0.0075476), T_zero.add(FastMath.toRadians(74.0161)), T_zero.add(FastMath.toRadians(328.9888)),
381                               T_zero.add(FastMath.toRadians(228.9750)), T_zero.add(FastMath.toRadians(30.6709)), 80454, 0.99999e11);
382             tle.getLine1();
383             Assertions.fail("an exception should have been thrown");
384         } catch (OrekitException oe) {
385             Assertions.assertEquals(OrekitMessages.TLE_INVALID_PARAMETER, oe.getSpecifier());
386             Assertions.assertEquals(5555, ((Integer) oe.getParts()[0]).intValue());
387             Assertions.assertEquals("B*", oe.getParts()[1]);
388         }
389     }
390 
391     public <T extends CalculusFieldElement<T>> void doTestBug77TooLargeEccentricity(Field<T> field) {
392         try {
393             final T T_zero = field.getZero();
394             FieldTLE<T> tle = new FieldTLE<T>(5555, 'U', 1971, 86, "J", 0, 908,
395                               new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
396                                                new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
397                                                TimeScalesFactory.getUTC()),
398                               T_zero.add(taylorConvert(12.26882470, 1)), T_zero.add(taylorConvert(-0.00000004, 2)), T_zero.add(taylorConvert(0.00001e-9, 3)),
399                               T_zero.add(1.0075476), T_zero.add(FastMath.toRadians(74.0161)), T_zero.add(FastMath.toRadians(328.9888)),
400                               T_zero.add(FastMath.toRadians(228.9750)), T_zero.add(FastMath.toRadians(30.6709)), 80454, 0.01234e-9);
401             tle.getLine2();
402             Assertions.fail("an exception should have been thrown");
403         } catch (OrekitException oe) {
404             Assertions.assertEquals(OrekitMessages.TLE_INVALID_PARAMETER, oe.getSpecifier());
405             Assertions.assertEquals(5555, ((Integer) oe.getParts()[0]).intValue());
406             Assertions.assertEquals("eccentricity", oe.getParts()[1]);
407         }
408     }
409 
410     public <T extends CalculusFieldElement<T>> void doTestBug77TooLargeSatelliteNumber1(Field<T> field) {
411         try {
412             final T T_zero = field.getZero();
413             FieldTLE<T> tle = new FieldTLE<T>(1000000, 'U', 1971, 86, "J", 0, 908,
414                               new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
415                                                new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
416                                                TimeScalesFactory.getUTC()),
417                               T_zero.add(taylorConvert(12.26882470, 1)),  T_zero.add(taylorConvert(-0.00000004, 2)),  T_zero.add(taylorConvert(0.00001e-9, 3)),
418                               T_zero.add(0.0075476),  T_zero.add(FastMath.toRadians(74.0161)),  T_zero.add(FastMath.toRadians(328.9888)),
419                               T_zero.add(FastMath.toRadians(228.9750)),  T_zero.add(FastMath.toRadians(30.6709)), 80454, 0.01234e-9);
420             tle.getLine1();
421             Assertions.fail("an exception should have been thrown");
422         } catch (OrekitException oe) {
423             Assertions.assertEquals(OrekitMessages.TLE_INVALID_PARAMETER, oe.getSpecifier());
424             Assertions.assertEquals(1000000, ((Integer) oe.getParts()[0]).intValue());
425             Assertions.assertEquals("satelliteNumber-1", oe.getParts()[1]);
426         }
427     }
428 
429     public <T extends CalculusFieldElement<T>> void doTestBug77TooLargeSatelliteNumber2(Field<T> field) {
430         try {
431             final T T_zero = field.getZero();
432             FieldTLE<T> tle = new FieldTLE<T>(1000000, 'U', 1971, 86, "J", 0, 908,
433                               new FieldAbsoluteDate<T>(field, new DateComponents(2012, 26),
434                                                new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
435                                                TimeScalesFactory.getUTC()),
436                               T_zero.add(taylorConvert(12.26882470, 1)), T_zero.add(taylorConvert(-0.00000004, 2)), T_zero.add(taylorConvert(0.00001e-9, 3)),
437                               T_zero.add(0.0075476), T_zero.add(FastMath.toRadians(74.0161)), T_zero.add(FastMath.toRadians(328.9888)),
438                               T_zero.add(FastMath.toRadians(228.9750)), T_zero.add(FastMath.toRadians(30.6709)), 80454, 0.01234e-9);
439             tle.getLine2();
440             Assertions.fail("an exception should have been thrown");
441         } catch (OrekitException oe) {
442             Assertions.assertEquals(OrekitMessages.TLE_INVALID_PARAMETER, oe.getSpecifier());
443             Assertions.assertEquals(1000000, ((Integer) oe.getParts()[0]).intValue());
444             Assertions.assertEquals("satelliteNumber-2", oe.getParts()[1]);
445         }
446     }
447 
448     final double taylorConvert(final double m, final int n) {
449         // convert one term of TLE mean motion Taylor series
450         return  m * 2 * FastMath.PI * CombinatoricsUtils.factorial(n) / FastMath.pow(Constants.JULIAN_DAY, n);
451     }
452 
453     public <T extends CalculusFieldElement<T>> void doTestDifferentSatNumbers(Field<T> field) {
454         new FieldTLE<T>(field, "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
455                                "2 27422  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
456     }
457 
458     public void doTestChecksumOK() {
459         FieldTLE.isFormatOK("1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
460                             "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
461     }
462 
463     public void doTestWrongChecksum1() {
464         try {
465             FieldTLE.isFormatOK("1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    21",
466                                 "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
467             Assertions.fail("an exception should have been thrown");
468         } catch (OrekitException oe) {
469             Assertions.assertEquals(OrekitMessages.TLE_CHECKSUM_ERROR, oe.getSpecifier());
470             Assertions.assertEquals(1, ((Integer) oe.getParts()[0]).intValue());
471             Assertions.assertEquals("0", oe.getParts()[1]);
472             Assertions.assertEquals("1", oe.getParts()[2]);
473             Assertions.assertEquals("1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    21",
474                                 oe.getParts()[3]);
475         }
476     }
477 
478     public void doTestWrongChecksum2() {
479         try {
480             FieldTLE.isFormatOK("1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
481                                 "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    61");
482             Assertions.fail("an exception should have been thrown");
483         } catch (OrekitException oe) {
484             Assertions.assertEquals(OrekitMessages.TLE_CHECKSUM_ERROR, oe.getSpecifier());
485             Assertions.assertEquals(2, ((Integer) oe.getParts()[0]).intValue());
486             Assertions.assertEquals("2", oe.getParts()[1]);
487             Assertions.assertEquals("1", oe.getParts()[2]);
488             Assertions.assertEquals("2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    61",
489                                 oe.getParts()[3]);
490         }
491     }
492 
493     public <T extends CalculusFieldElement<T>>void doTestSatCodeCompliance(Field<T> field) throws IOException, OrekitException, ParseException {
494 
495         BufferedReader rEntry = null;
496         BufferedReader rResults = null;
497         final T T_zero = field.getZero();
498 
499         InputStream inEntry =
500             FieldTLETest.class.getResourceAsStream("/tle/extrapolationTest-data/SatCode-entry");
501         rEntry = new BufferedReader(new InputStreamReader(inEntry));
502 
503         try {
504             InputStream inResults =
505                 FieldTLETest.class.getResourceAsStream("/tle/extrapolationTest-data/SatCode-results");
506             rResults = new BufferedReader(new InputStreamReader(inResults));
507 
508             try {
509                 double cumulated = 0; // sum of all differences between test cases and OREKIT results
510                 boolean stop = false;
511 
512                 String rline = rResults.readLine();
513 
514                 while (!stop) {
515                     if (rline == null) break;
516 
517                     String[] title = rline.split(" ");
518 
519                     if (title[0].matches("r")) {
520 
521                         String eline;
522                         int count = 0;
523                         String[] header = new String[4];
524                         for (eline = rEntry.readLine(); (eline != null) && (eline.charAt(0)=='#'); eline = rEntry.readLine()) {
525                             header[count++] = eline;
526                         }
527                         String line1 = eline;
528                         String line2 = rEntry.readLine();
529                         Assertions.assertTrue(TLE.isFormatOK(line1, line2));
530 
531                         FieldTLE<T> tle = new FieldTLE<T>(field, line1, line2);
532 
533                         int satNum = Integer.parseInt(title[1]);
534                         Assertions.assertEquals(satNum, tle.getSatelliteNumber());
535                         final T[] parameters;
536                         parameters = MathArrays.buildArray(field, 1);
537                         parameters[0] = field.getZero().add(tle.getBStar());
538                         FieldTLEPropagator<T> ex = FieldTLEPropagator.selectExtrapolator(tle, parameters);
539                         for (rline = rResults.readLine(); (rline!=null)&&(rline.charAt(0)!='r'); rline = rResults.readLine()) {
540 
541                             String[] data = rline.split(" ");
542                             double minFromStart = Double.parseDouble(data[0]);
543                             T pX = T_zero.add(1000*Double.parseDouble(data[1]));
544                             T pY = T_zero.add(1000*Double.parseDouble(data[2]));
545                             T pZ = T_zero.add(1000*Double.parseDouble(data[3]));
546                             T vX = T_zero.add(1000*Double.parseDouble(data[4]));
547                             T vY = T_zero.add(1000*Double.parseDouble(data[5]));
548                             T vZ = T_zero.add(1000*Double.parseDouble(data[6]));
549                             FieldVector3D<T> testPos = new FieldVector3D<T>(pX, pY, pZ);
550                             FieldVector3D<T> testVel = new FieldVector3D<T>(vX, vY, vZ);
551 
552                             FieldAbsoluteDate<T> date = tle.getDate().shiftedBy(minFromStart * 60);
553                             FieldPVCoordinates<T> results = ex.getPVCoordinates(date, parameters);
554                             double normDifPos = testPos.subtract(results.getPosition()).getNorm().getReal();
555                             double normDifVel = testVel.subtract(results.getVelocity()).getNorm().getReal();
556 
557                             cumulated += normDifPos;
558                             Assertions.assertEquals(0, normDifPos, 2e-3);
559                             Assertions.assertEquals(0, normDifVel, 7e-4);
560 
561                         }
562                     }
563                 }
564                 Assertions.assertEquals(0, cumulated, 0.026);
565             } finally {
566                 if (rResults != null) {
567                     rResults.close();
568                 }
569             }
570         } finally {
571             if (rEntry != null) {
572                 rEntry.close();
573             }
574         }
575     }
576 
577     public <T extends CalculusFieldElement<T>> void doTestZeroInclination(Field<T> field) {
578         FieldTLE<T> tle = new FieldTLE<T>(field,"1 26451U 00043A   10130.13784012 -.00000276  00000-0  10000-3 0  3866",
579                                                 "2 26451 000.0000 266.1044 0001893 160.7642 152.5985 01.00271160 35865");
580         final T[] parameters;
581         parameters = MathArrays.buildArray(field, 1);
582         parameters[0].add(tle.getBStar());
583         FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(tle, parameters);
584         FieldPVCoordinates<T> pv = propagator.propagate(tle.getDate().shiftedBy(100)).getPVCoordinates();
585         Assertions.assertEquals(42171546.979560345, pv.getPosition().getNorm().getReal(), 1.0e-3);
586         Assertions.assertEquals(3074.1890089357994, pv.getVelocity().getNorm().getReal(), 1.0e-6);
587     }
588 
589     public <T extends CalculusFieldElement<T>> void doTestSymmetryAfterLeapSecondIntroduction(Field<T> field) {
590         checkSymmetry(field, "1 34602U 09013A   12187.35117436  .00002472  18981-5  42406-5 0  9995",
591                              "2 34602  96.5991 210.0210 0006808 112.8142 247.3865 16.06008103193411");
592     }
593 
594     public <T extends CalculusFieldElement<T>> void doTestOldTLE(Field<T> field) {
595         String line1 = "1 15427U          85091.94293084 0.00000051  00000+0  32913-4 0   179";
596         String line2 = "2 15427  98.9385  46.0219 0015502 321.4354  38.5705 14.11363211 15580";
597         Assertions.assertTrue(TLE.isFormatOK(line1, line2));
598         FieldTLE<T> tle = new FieldTLE<T>(field, line1, line2);
599         Assertions.assertEquals(15427, tle.getSatelliteNumber());
600         Assertions.assertEquals(0.00000051,
601                             tle.getMeanMotionFirstDerivative().getReal() * Constants.JULIAN_DAY * Constants.JULIAN_DAY / (4 * FastMath.PI),
602                             1.0e-15);
603     }
604 
605     public <T extends CalculusFieldElement<T>> void doTestEqualTLE(Field<T> field) {
606         FieldTLE<T> tleA = new FieldTLE<T>(field, "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
607                                                   "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
608         FieldTLE<T> tleB = new FieldTLE<T>(field, "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
609                                                   "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
610         Assertions.assertEquals(tleA, tleB);
611     }
612 
613     public <T extends CalculusFieldElement<T>> void doTestNonEqualTLE(Field<T> field) {
614         FieldTLE<T> tleA = new FieldTLE<T>(field, "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
615                                                   "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
616         FieldTLE<T> tleB = new FieldTLE<T>(field, "1 05555U 71086J   12026.96078249 -.00000004  00001-9  01234-9 0  9082",
617                                                   "2 05555  74.0161 228.9750 0075476 328.9888  30.6709 12.26882470804545");
618         Assertions.assertNotEquals(tleA, tleB);
619     }
620 
621     public <T extends CalculusFieldElement<T>> void doTestIssue388(Field<T> field) {
622         final T T_zero = field.getZero();
623         FieldTLE<T> tleRef = new FieldTLE<T>(field, "1 27421U 02021A   02124.48976499 -.00021470  00000-0 -89879-2 0    20",
624                                                     "2 27421  98.7490 199.5121 0001333 133.9522 226.1918 14.26113993    62");
625         FieldTLE<T> tleOriginal = new FieldTLE<T>(27421, 'U', 2002, 21, "A", TLE.DEFAULT, 2,
626                                   new FieldAbsoluteDate<T>(field, "2002-05-04T11:45:15.695", TimeScalesFactory.getUTC()),
627                                   T_zero.add(FastMath.toRadians(14.26113993 * 360 / Constants.JULIAN_DAY)),
628                                   T_zero.add(FastMath.toRadians(-.00021470 * 360 * 2 / (Constants.JULIAN_DAY * Constants.JULIAN_DAY))),
629                                   T_zero.add(FastMath.toRadians(0.0)),
630                                   T_zero.add(1.333E-4), T_zero.add(FastMath.toRadians(98.7490)),
631                                   T_zero.add(FastMath.toRadians(133.9522)), T_zero.add(FastMath.toRadians(199.5121)), T_zero.add(FastMath.toRadians(226.1918)),
632                                   6, -0.0089879);
633         Assertions.assertEquals(tleRef.getLine1(), tleOriginal.getLine1());
634         Assertions.assertEquals(tleRef.getLine2(), tleOriginal.getLine2());
635         FieldTLE<T> changedBStar = new FieldTLE<T>(27421, 'U', 2002, 21, "A", TLE.DEFAULT, 2,
636                                    new FieldAbsoluteDate<T>(field, "2002-05-04T11:45:15.695", TimeScalesFactory.getUTC()),
637                                    T_zero.add(FastMath.toRadians(14.26113993 * 360 / Constants.JULIAN_DAY)),
638                                    T_zero.add(FastMath.toRadians(-.00021470 * 360 * 2 / (Constants.JULIAN_DAY * Constants.JULIAN_DAY))),
639                                    T_zero.add(FastMath.toRadians(0.0)),
640                                    T_zero.add(1.333E-4), T_zero.add(FastMath.toRadians(98.7490)),
641                                    T_zero.add(FastMath.toRadians(133.9522)), T_zero.add(FastMath.toRadians(199.5121)), T_zero.add(FastMath.toRadians(226.1918)),
642                                    6, 1.0e-4);
643         Assertions.assertEquals(tleRef.getLine1().replace("-89879-2", " 10000-3"), changedBStar.getLine1());
644         Assertions.assertEquals(tleRef.getLine2(), changedBStar.getLine2());
645         Assertions.assertEquals(1.0e-4, new FieldTLE<T>(field, changedBStar.getLine1(), changedBStar.getLine2()).getBStar(), 1.0e-15);
646     }
647 
648     public <T extends CalculusFieldElement<T>> void doTestIssue664NegativeRaanPa(Field<T> field) {
649         final T T_zero = field.getZero();
650         FieldTLE<T> tle = new FieldTLE<T>(99999, 'X', 2020, 42, "F", 0, 999,
651                 new FieldAbsoluteDate<T>(field, "2020-01-01T01:00:00.000", TimeScalesFactory.getUTC()), T_zero.add(0.0011010400252833312), T_zero.add(0.0),
652                 T_zero.add(0.0), T_zero.add(0.0016310523359516962), T_zero.add(1.6999188604164899),
653                 T_zero.add(-3.219351286726724), T_zero.add(-2.096689019811356),
654                 T_zero.add(2.157567545975006), 1, 1e-05);
655         // Comparing with TLE strings generated in Orekit Python after forcing the RAAN
656         // and PA to the [0, 2*Pi] range
657         Assertions.assertEquals(tle.getLine1(), "1 99999X 20042F   20001.04166667  .00000000  00000-0  10000-4 0  9997");
658         Assertions.assertEquals(tle.getLine2(), "2 99999  97.3982 239.8686 0016311 175.5448 123.6195 15.14038717    18");
659     }
660 
661     @Test
662     public void testStateToTleISS() {
663         doTestStateToTleISS(Binary64Field.getInstance());
664     }
665 
666     private <T extends CalculusFieldElement<T>> void doTestStateToTleISS(final Field<T> field) {
667 
668         // Initialize TLE
669         final FieldTLE<T> tleISS = new FieldTLE<>(field, "1 25544U 98067A   21035.14486477  .00001026  00000-0  26816-4 0  9998",
670                                                          "2 25544  51.6455 280.7636 0002243 335.6496 186.1723 15.48938788267977");
671 
672         // TLE propagator
673         final FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(tleISS, tleISS.getParameters(field));
674 
675         // State at TLE epoch
676         final FieldSpacecraftState<T> state = propagator.propagate(tleISS.getDate());
677 
678         // Osculating to mean orbit converter
679         final OsculatingToMeanConverter converter = new FixedPointConverter();
680 
681         // Convert to TLE
682         final FieldTLE<T> rebuilt = FieldTLE.stateToTLE(state, tleISS, converter);
683 
684         // Verify
685         final double eps = 1.0e-7;
686         Assertions.assertEquals(tleISS.getSatelliteNumber(),           rebuilt.getSatelliteNumber());
687         Assertions.assertEquals(tleISS.getClassification(),            rebuilt.getClassification());
688         Assertions.assertEquals(tleISS.getLaunchYear(),                rebuilt.getLaunchYear());
689         Assertions.assertEquals(tleISS.getLaunchNumber(),              rebuilt.getLaunchNumber());
690         Assertions.assertEquals(tleISS.getLaunchPiece(),               rebuilt.getLaunchPiece());
691         Assertions.assertEquals(tleISS.getElementNumber(),             rebuilt.getElementNumber());
692         Assertions.assertEquals(tleISS.getRevolutionNumberAtEpoch(),   rebuilt.getRevolutionNumberAtEpoch());
693         Assertions.assertEquals(tleISS.getMeanMotion().getReal(),      rebuilt.getMeanMotion().getReal(),      eps * tleISS.getMeanMotion().getReal());
694         Assertions.assertEquals(tleISS.getE().getReal(),               rebuilt.getE().getReal(),               eps * tleISS.getE().getReal());
695         Assertions.assertEquals(tleISS.getI().getReal(),               rebuilt.getI().getReal(),               eps * tleISS.getI().getReal());
696         Assertions.assertEquals(tleISS.getPerigeeArgument().getReal(), rebuilt.getPerigeeArgument().getReal(), eps * tleISS.getPerigeeArgument().getReal());
697         Assertions.assertEquals(tleISS.getRaan().getReal(),            rebuilt.getRaan().getReal(),            eps * tleISS.getRaan().getReal());
698         Assertions.assertEquals(tleISS.getMeanAnomaly().getReal(),     rebuilt.getMeanAnomaly().getReal(),     eps * tleISS.getMeanAnomaly().getReal());
699         Assertions.assertEquals(tleISS.getMeanAnomaly().getReal(),     rebuilt.getMeanAnomaly().getReal(),     eps * tleISS.getMeanAnomaly().getReal());
700         Assertions.assertEquals(tleISS.getBStar(),                     rebuilt.getBStar(),                     eps * tleISS.getBStar());
701     }
702 
703     @Test
704     public void testToTLE() {
705         doTestToTLE(Binary64Field.getInstance());
706     }
707 
708     private <T extends CalculusFieldElement<T>> void doTestToTLE(final Field<T> field) {
709         final TLE tle = new TLE("1 25544U 98067A   21035.14486477  .00001026  00000-0  26816-4 0  9998",
710                                 "2 25544  51.6455 280.7636 0002243 335.6496 186.1723 15.48938788267977");
711         final FieldTLE<T> fieldTle = new FieldTLE<T>(field, tle.getLine1(), tle.getLine2());
712         final TLE rebuilt = fieldTle.toTLE();
713         Assertions.assertEquals(rebuilt, tle);
714         Assertions.assertEquals(tle.toString(), rebuilt.toString());
715     }
716 
717     @Test
718     void roundToNextDayError() {
719         //Given
720         final Field<Binary64> field = Binary64Field.getInstance();
721         final Binary64        zero  = field.getZero();
722 
723         final FieldAbsoluteDate<Binary64> tleDate =
724                 new FieldAbsoluteDate<>(field, new AbsoluteDate("2022-01-01T23:59:59.99999999", TimeScalesFactory.getUTC()));
725 
726         final FieldTLE<Binary64> tle =
727                 new FieldTLE<>(99999, 'U', 2022, 999, "A", 0, 1, tleDate, zero, zero, zero, zero, zero, zero, zero, zero, 99,
728                                11606 * 1e-4, TimeScalesFactory.getUTC());
729 
730         //When
731         final FieldAbsoluteDate<Binary64> returnedDate = tle.getDate();
732 
733         //Then
734         // Assert that TLE class did not round the date to the next day
735         Assertions.assertEquals(tleDate, returnedDate);
736     }
737 
738     @Test
739     @DisplayName("fix issue 1773 :  with Orekit 13 new date representation, TLE parsing from lines may introduce " +
740       "some attoseconds of error")
741     void testFixIssue1773() {
742         // Given
743         final Field<Binary64> field = Binary64Field.getInstance();
744         final String line1WithDateParsingError = "1 00005U 58002B   25047.63247525. .00000268  00000-0  35695-3 0  9999";
745         final String line2WithDateParsingError = "2 00005  34.2494  12.9875 1841261 109.2290 271.5928 10.85874828390491";
746 
747         final DateComponents dateComponents = new DateComponents(2025, 2, 16);
748         final TimeOffset timeOffset = new TimeOffset(45, TimeOffset.MICROSECOND.multiply(861600).getAttoSeconds());
749         final TimeComponents timeComponents = new TimeComponents(15, 10, timeOffset);
750         final FieldAbsoluteDate<Binary64> reconstructedExactDate =
751           new FieldAbsoluteDate<>(field, dateComponents, timeComponents, TimeScalesFactory.getUTC());
752 
753         // When
754         final FieldTLE<Binary64> parsedTle = new FieldTLE<>(field, line1WithDateParsingError, line2WithDateParsingError);
755 
756         // Then
757         Assertions.assertEquals(reconstructedExactDate, parsedTle.getDate());
758     }
759 
760     @BeforeEach
761     public void setUp() {
762         Utils.setDataRoot("regular-data");
763     }
764 
765 }