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.files.ilrs;
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.junit.jupiter.api.io.TempDir;
25  import org.orekit.Utils;
26  import org.orekit.data.DataSource;
27  import org.orekit.errors.OrekitIllegalArgumentException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.files.general.OrekitEphemerisFile;
30  import org.orekit.files.general.OrekitEphemerisFile.OrekitSatelliteEphemeris;
31  import org.orekit.files.ilrs.CPF.CPFCoordinate;
32  import org.orekit.files.ilrs.CPF.CPFEphemeris;
33  import org.orekit.frames.FramesFactory;
34  import org.orekit.orbits.KeplerianOrbit;
35  import org.orekit.orbits.Orbit;
36  import org.orekit.orbits.PositionAngleType;
37  import org.orekit.propagation.Propagator;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.propagation.analytical.KeplerianPropagator;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.time.DateComponents;
42  import org.orekit.time.TimeScale;
43  import org.orekit.time.TimeScalesFactory;
44  import org.orekit.utils.Constants;
45  
46  import java.io.BufferedReader;
47  import java.io.BufferedWriter;
48  import java.io.File;
49  import java.io.FileInputStream;
50  import java.io.IOException;
51  import java.io.InputStreamReader;
52  import java.io.Reader;
53  import java.net.URISyntaxException;
54  import java.nio.charset.StandardCharsets;
55  import java.nio.file.Path;
56  import java.util.ArrayList;
57  import java.util.List;
58  
59  public class CPFWriterTest {
60  
61      @TempDir
62      public Path temporaryFolderPath;
63      
64      @BeforeEach
65      public void setUp() throws Exception {
66          Utils.setDataRoot("regular-data");
67      }
68  
69      @Test
70      public void testWriteJason3Version2() throws IOException, URISyntaxException {
71  
72          // Simple test for version 2.0, only contains position entries
73          final String ex = "/ilrs/jason3_cpf_180613_16401.cne";
74          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
75          final CPF file = new CPFParser().parse(source);
76  
77          String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
78          CPFWriter writer = new CPFWriter(file.getHeader(), TimeScalesFactory.getUTC());
79          writer.write(tempCPFFilePath, file);
80  
81          final CPF generatedCpfFile = new CPFParser().parse(new DataSource(tempCPFFilePath));
82          compareCpfFiles(file, generatedCpfFile);
83  
84      }
85  
86      @Test
87      public void testWriteLageos1Version2() throws IOException, URISyntaxException {
88  
89          // Simple test for version 2.0, only contains position entries
90          final String ex = "/ilrs/lageos1_cpf_180613_16401.hts";
91          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
92          final CPF file = new CPFParser().parse(source);
93  
94          String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
95          CPFWriter writer = new CPFWriter(file.getHeader(), TimeScalesFactory.getUTC());
96          writer.write(tempCPFFilePath, file);
97  
98          final CPF generatedCpfFile = new CPFParser().parse(new DataSource(tempCPFFilePath));
99          compareCpfFiles(file, generatedCpfFile);
100 
101     }
102 
103     @Test
104     public void testWriteGalileoVersion1() throws IOException, URISyntaxException {
105 
106         // Simple test for version 1.0, only contains position entries
107         final String ex = "/ilrs/galileo212_cpf_180613_6641.esa";
108         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
109         final CPF file = new CPFParser().parse(source);
110 
111         String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
112         CPFWriter writer = new CPFWriter(file.getHeader(), TimeScalesFactory.getUTC());
113         writer.write(tempCPFFilePath, file);
114 
115         final CPF generatedCpfFile = new CPFParser().parse(new DataSource(tempCPFFilePath));
116         compareCpfFiles(file, generatedCpfFile);
117 
118     }
119 
120     @Test
121     public void testIssue868v1() throws IOException, URISyntaxException {
122 
123         // Load
124         final String ex = "/ilrs/galileo212_cpf_180613_6641.esa";
125         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
126         final CPF file = new CPFParser().parse(source);
127 
128         // Write
129         String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
130         CPFWriter writer = new CPFWriter(file.getHeader(), TimeScalesFactory.getUTC());
131         writer.write(tempCPFFilePath, file);
132 
133         // Verify
134         final DataSource tempSource = new DataSource(tempCPFFilePath);
135         try (Reader reader = tempSource.getOpener().openReaderOnce();
136                         BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {
137             // The testWriteGalileoVersion1() already verify the content of the file
138             // The objective here is just the verify the fix of issue #868
139             final String line1 = br.readLine();
140             Assertions.assertEquals(56, line1.length());
141             final String line2 = br.readLine();
142             Assertions.assertEquals(82, line2.length());
143         }
144     }
145 
146     @Test
147     public void testIssue868v2() throws IOException, URISyntaxException {
148 
149         // Load
150         final String ex = "/ilrs/lageos1_cpf_180613_16401.hts";
151         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
152         final CPF file = new CPFParser().parse(source);
153 
154         // Write
155         String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
156         CPFWriter writer = new CPFWriter(file.getHeader(), TimeScalesFactory.getUTC());
157         writer.write(tempCPFFilePath, file);
158 
159         // Verify
160         final DataSource tempSource = new DataSource(tempCPFFilePath);
161         try (Reader reader = tempSource.getOpener().openReaderOnce();
162                         BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {
163             // The testWriteLageos1Version2() already verify the content of the file
164             // The objective here is just the verify the fix of issue #868
165             final String line1 = br.readLine();
166             Assertions.assertEquals(58, line1.length());
167             final String line2 = br.readLine();
168             Assertions.assertEquals(85, line2.length());
169         }
170     }
171 
172     @Test
173     public void testNullFile() throws IOException {
174         final String    ex      = "/ilrs/lageos1_cpf_180613_16401.hts";
175         final DataSource source  = new DataSource(ex, () ->  getClass().getResourceAsStream(ex));
176         final CPF   cpfFile = new CPFParser().parse(source);
177         final CPFWriter writer  = new CPFWriter(cpfFile.getHeader(), TimeScalesFactory.getUTC());
178         try {
179             writer.write((BufferedWriter) null, cpfFile);
180             Assertions.fail("an exception should have been thrown");
181         } catch (OrekitIllegalArgumentException oiae) {
182             Assertions.assertEquals(OrekitMessages.NULL_ARGUMENT, oiae.getSpecifier());
183             Assertions.assertEquals("writer", oiae.getParts()[0]);
184         }
185     }
186 
187     @Test
188     public void testNullEphemeris() throws IOException {
189         File tempCPFFile = temporaryFolderPath.resolve("TestNullEphemeris.cpf").toFile();
190         CPFWriter writer = new CPFWriter(null, TimeScalesFactory.getUTC());
191         writer.write(tempCPFFile.toString(), null);
192         Assertions.assertTrue(tempCPFFile.exists());
193         try (FileInputStream   fis = new FileInputStream(tempCPFFile);
194              InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
195              BufferedReader    br  = new BufferedReader(isr)) {
196             int count = 0;
197             for (String line = br.readLine(); line != null; line = br.readLine()) {
198                 ++count;
199             }
200             Assertions.assertEquals(0, count);
201         }
202     }
203 
204     /** Test for issue #844 (https://gitlab.orekit.org/orekit/orekit/-/issues/844). */
205     @Test
206     public void testIssue844() throws IOException {
207 
208         // Create header
209         final CPFHeader header = new CPFHeader();
210         header.setSource("orekit");
211         header.setStep(300);
212         header.setStartEpoch(AbsoluteDate.J2000_EPOCH.shiftedBy(-300.0));
213         header.setEndEpoch(AbsoluteDate.J2000_EPOCH.shiftedBy(300.0));
214         header.setIlrsSatelliteId("070595");
215         header.setName("tag");
216         header.setNoradId("0705");
217         header.setProductionEpoch(new DateComponents(2000, 1, 2));
218         header.setProductionHour(12);
219         header.setSequenceNumber(0705);
220         header.setSic("0705");
221         final CPFHeader headerV1 = header;
222         headerV1.setVersion(1);
223 
224         // Writer
225         final CPFWriter writer = new CPFWriter(headerV1, TimeScalesFactory.getUTC());
226 
227         // Create an empty CPF file
228         final CPF cpf = new CPF();
229 
230         // Fast check
231         Assertions.assertEquals(0, cpf.getSatellites().size());
232 
233         // Add coordinates
234         final int leap = 0;
235         cpf.addSatelliteCoordinate(header.getIlrsSatelliteId(), new CPFCoordinate(AbsoluteDate.J2000_EPOCH.shiftedBy(-300.0), Vector3D.PLUS_I, leap));
236         cpf.addSatelliteCoordinate(header.getIlrsSatelliteId(), new CPFCoordinate(AbsoluteDate.J2000_EPOCH,                   Vector3D.PLUS_J, leap));
237         cpf.addSatelliteCoordinate(header.getIlrsSatelliteId(), new CPFCoordinate(AbsoluteDate.J2000_EPOCH.shiftedBy(300.0),  Vector3D.PLUS_K, leap));
238 
239         // Write the file
240         String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
241         writer.write(tempCPFFilePath, cpf);
242 
243         // Verify
244         final List<CPFCoordinate> coordinatesInFile = cpf.getSatellites().get(header.getIlrsSatelliteId()).getCoordinates();
245         Assertions.assertEquals(0.0, Vector3D.PLUS_I.distance(coordinatesInFile.get(0).getPosition()), 1.0e-10);
246         Assertions.assertEquals(0.0, Vector3D.PLUS_J.distance(coordinatesInFile.get(1).getPosition()), 1.0e-10);
247         Assertions.assertEquals(0.0, Vector3D.PLUS_K.distance(coordinatesInFile.get(2).getPosition()), 1.0e-10);
248 
249     }
250 
251     /** Test for issue #844 (https://gitlab.orekit.org/orekit/orekit/-/issues/844). */
252     @Test
253     public void testIssue844Bis() throws IOException {
254 
255         // Create header
256         final CPFHeader header = new CPFHeader();
257         header.setSource("orekit");
258         header.setStep(300);
259         header.setStartEpoch(AbsoluteDate.J2000_EPOCH.shiftedBy(-300.0));
260         header.setEndEpoch(AbsoluteDate.J2000_EPOCH.shiftedBy(300.0));
261         header.setIlrsSatelliteId("070595");
262         header.setName("tag");
263         header.setNoradId("0705");
264         header.setProductionEpoch(new DateComponents(2000, 1, 2));
265         header.setProductionHour(12);
266         header.setSequenceNumber(0705);
267         header.setSic("0705");
268         final CPFHeader headerV1 = header;
269         headerV1.setVersion(1);
270 
271         // Writer
272         final CPFWriter writer = new CPFWriter(headerV1, TimeScalesFactory.getUTC());
273 
274         // Create an empty CPF file
275         final CPF cpf = new CPF();
276 
277         // Fast check
278         Assertions.assertEquals(0, cpf.getSatellites().size());
279 
280         // Add coordinates
281         final int leap = 0;
282         final List<CPFCoordinate> coordinates = new ArrayList<>();
283         coordinates.add(new CPFCoordinate(AbsoluteDate.J2000_EPOCH.shiftedBy(-300.0), Vector3D.PLUS_I, leap));
284         coordinates.add(new CPFCoordinate(AbsoluteDate.J2000_EPOCH,                   Vector3D.PLUS_J, leap));
285         coordinates.add(new CPFCoordinate(AbsoluteDate.J2000_EPOCH.shiftedBy(300.0),  Vector3D.PLUS_K, leap));
286         cpf.addSatelliteCoordinates(header.getIlrsSatelliteId(), coordinates);
287 
288         // Write the file
289         String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
290         writer.write(tempCPFFilePath, cpf);
291 
292         // Verify
293         final List<CPFCoordinate> coordinatesInFile = cpf.getSatellites().get(header.getIlrsSatelliteId()).getCoordinates();
294         Assertions.assertEquals(0.0, Vector3D.PLUS_I.distance(coordinatesInFile.get(0).getPosition()), 1.0e-10);
295         Assertions.assertEquals(0.0, Vector3D.PLUS_J.distance(coordinatesInFile.get(1).getPosition()), 1.0e-10);
296         Assertions.assertEquals(0.0, Vector3D.PLUS_K.distance(coordinatesInFile.get(2).getPosition()), 1.0e-10);
297 
298     }
299 
300     /** Test for issue #790 (https://gitlab.orekit.org/orekit/orekit/-/issues/790).
301      * @throws URISyntaxException */
302     @Test
303     public void testIssue790() throws IOException, URISyntaxException {
304 
305          final TimeScale utc = TimeScalesFactory.getUTC();
306          final AbsoluteDate date = new AbsoluteDate(2022, 05, 10, 00, 00, 00.000, utc);
307 
308          // General orbit
309          double a = 24396159;                     // semi major axis in meters
310          double e = 0.72831215;                   // eccentricity
311          double i = FastMath.toRadians(7);        // inclination
312          double omega = FastMath.toRadians(180);  // perigee argument
313          double raan = FastMath.toRadians(261);   // right ascension of ascending node
314          double lM = 0;                           // mean anomaly
315 
316          Orbit orbit = new KeplerianOrbit(a, e, i, omega, raan, lM, PositionAngleType.MEAN,
317                                                 FramesFactory.getEME2000(), date,
318                                                 Constants.WGS84_EARTH_MU);
319 
320          // First, configure a propagation with the default event handler (expected to stop on event)
321          Propagator propagator = new KeplerianPropagator(orbit);
322 
323          final double propagationDurationSeconds = 86400.0;
324          final double stepSizeSeconds = 60.0;
325          List<SpacecraftState> states = new ArrayList<SpacecraftState>();
326 
327          for (double dt = 0.0; dt < propagationDurationSeconds; dt += stepSizeSeconds) {
328              states.add(propagator.propagate(date.shiftedBy(dt)));
329          }
330 
331          OrekitEphemerisFile ephemerisFile = new OrekitEphemerisFile();
332          OrekitSatelliteEphemeris satellite = ephemerisFile.addSatellite("070595");
333          satellite.addNewSegment(states);
334 
335         // Create header
336         final CPFHeader header = new CPFHeader();
337         header.setSource("orekit");
338         header.setStep(300);
339         header.setStartEpoch(date);
340         header.setEndEpoch(date.shiftedBy(86400.0));
341         header.setIlrsSatelliteId("070595");
342         header.setName("tag");
343         header.setNoradId("0705");
344         header.setProductionEpoch(date.getComponents(utc).getDate());
345         header.setProductionHour(12);
346         header.setSequenceNumber(0705);
347         header.setSic("0705");
348         final CPFHeader headerV1 = header;
349         headerV1.setVersion(1);
350 
351         // First launch the test with velocity flag enabled
352         boolean velocityFlag = true;
353 
354         // Write the CPF file from the generated ephemeris
355         String tempCPFFilePath = temporaryFolderPath.resolve("TestWriteCPF.cpf").toString();
356         CPFWriter writer = new CPFWriter(headerV1, TimeScalesFactory.getUTC(), velocityFlag);
357         writer.write(tempCPFFilePath, ephemerisFile);
358 
359         // Parse the generated CPF file
360         final CPF generatedCpfFile = new CPFParser().parse(new DataSource(tempCPFFilePath));
361 
362 
363         // Extract the coordinates from the generated CPF file
364         final CPFEphemeris ephemeris    = generatedCpfFile.getSatellites().get("070595");
365         final List<CPFCoordinate> coord = ephemeris.getCoordinates();
366 
367         // Verify first coordinate and that it includes the velocity components
368         final AbsoluteDate firstEpoch = AbsoluteDate.createMJDDate(59709, 0.0, generatedCpfFile.getTimeScale());
369         final Vector3D firstPos = new Vector3D(1036869.533, 6546536.585, 0.000);
370         final Vector3D firstVel = new Vector3D(-9994.355199, 1582.950355, -1242.449125);
371         Assertions.assertEquals(0, coord.get(0).getLeap());
372         Assertions.assertEquals(0.0, firstPos.distance(coord.get(0).getPosition()), 1.0e-15);
373         Assertions.assertEquals(0.0, firstVel.distance(coord.get(0).getVelocity()), 1.0e-15);
374         Assertions.assertEquals(0.0, firstEpoch.durationFrom(coord.get(0).getDate()), 1.0e-15);
375 
376 
377         // Repeat without velocity components for regression testing
378 
379         // Write the CPF file from the generated ephemeris
380         String tempCPFFilePathReg = temporaryFolderPath.resolve("TestWriteCPFReg.cpf").toString();
381         CPFWriter writerReg = new CPFWriter(headerV1, TimeScalesFactory.getUTC());
382         writerReg.write(tempCPFFilePathReg, ephemerisFile);
383 
384         // Parse the generated CPF file
385         final CPF generatedCpfFileReg = new CPFParser().parse(new DataSource(tempCPFFilePathReg));
386 
387 
388         // Extract the coordinates from the generated CPF file
389         final CPFEphemeris ephemerisReg    = generatedCpfFileReg.getSatellites().get("070595");
390         final List<CPFCoordinate> coordReg = ephemerisReg.getCoordinates();
391 
392         // Verify first coordinate and that the velocity components are zero
393         Assertions.assertEquals(0, coordReg.get(0).getLeap());
394         Assertions.assertEquals(0.0, firstPos.distance(coordReg.get(0).getPosition()), 1.0e-15);
395         Assertions.assertEquals(0.0, coordReg.get(0).getVelocity().getNorm(), 1.0e-15);
396         Assertions.assertEquals(0.0, firstEpoch.durationFrom(coordReg.get(0).getDate()), 1.0e-15);
397 
398     }
399 
400     public static void compareCpfFiles(CPF file1, CPF file2) {
401 
402         // Header
403         final CPFHeader header1 = file1.getHeader();
404         final CPFHeader header2 = file2.getHeader();
405         compareCpfHeader(header1, header2);
406 
407         // Ephemeris
408         final CPFEphemeris eph1 = file1.getSatellites().get(header1.getIlrsSatelliteId());
409         final CPFEphemeris eph2 = file2.getSatellites().get(header2.getIlrsSatelliteId());
410         Assertions.assertEquals(eph1.getId(), eph2.getId());
411         Assertions.assertEquals(eph1.getStart(), eph2.getStart());
412         Assertions.assertEquals(eph1.getStop(), eph2.getStop());
413 
414         // Coordinates
415         final List<CPFCoordinate> coord1 = eph1.getCoordinates();
416         final List<CPFCoordinate> coord2 = eph2.getCoordinates();
417         Assertions.assertEquals(coord1.size(), coord1.size());
418         verifyEphemerisLine(coord1.get(0), coord2.get(0));
419         verifyEphemerisLine(coord1.get(1), coord2.get(1));
420         verifyEphemerisLine(coord1.get(100), coord2.get(100));
421         verifyEphemerisLine(coord1.get(coord1.size() - 1), coord2.get(coord2.size() - 1));
422 
423     }
424 
425     public static void compareCpfHeader(CPFHeader header1, CPFHeader header2) {
426         Assertions.assertEquals(header1.getFormat(), header2.getFormat());
427         Assertions.assertEquals(header1.getVersion(), header2.getVersion());
428         Assertions.assertEquals(header1.getSource(), header2.getSource());
429         Assertions.assertEquals(header1.getProductionEpoch().getYear(), header2.getProductionEpoch().getYear());
430         Assertions.assertEquals(header1.getName(), header2.getName());
431         Assertions.assertEquals(header1.getIlrsSatelliteId(), header2.getIlrsSatelliteId());
432         Assertions.assertEquals(header1.getSic(), header2.getSic());
433         Assertions.assertEquals(0.0, header1.getStartEpoch().durationFrom(header2.getStartEpoch()), 1.0e-15);
434         Assertions.assertEquals(0.0, header1.getEndEpoch().durationFrom(header2.getEndEpoch()), 1.0e-15);
435     }
436 
437     public static void verifyEphemerisLine(CPFCoordinate coord1, CPFCoordinate coord2) {
438         Assertions.assertEquals(0.0, coord1.getDate().durationFrom(coord2.getDate()), 1.0e-10);
439         Assertions.assertEquals(0.0, coord1.getPosition().distance(coord2.getPosition()), 1.0e-10);
440     }
441 
442 }