1   /* Copyright 2022-2025 Luc Maisonobe
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.sp3;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.CharArrayWriter;
21  import java.io.IOException;
22  import java.nio.charset.StandardCharsets;
23  import java.util.List;
24  
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.util.FastMath;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.BeforeEach;
29  import org.junit.jupiter.api.Test;
30  import org.orekit.Utils;
31  import org.orekit.data.DataSource;
32  import org.orekit.data.UnixCompressFilter;
33  import org.orekit.frames.Frame;
34  import org.orekit.frames.FramesFactory;
35  import org.orekit.frames.ITRFVersion;
36  import org.orekit.time.TimeScalesFactory;
37  import org.orekit.utils.IERSConventions;
38  import org.orekit.utils.PVCoordinates;
39  
40  public class SP3WriterTest {
41  
42      @Test
43      public void testRoundtripExampleA1() {
44          doTestRoundtrip("/sp3/example-a-1.sp3");
45      }
46  
47      @Test
48      public void testRoundtripExampleA2() {
49          doTestRoundtrip("/sp3/example-a-2.sp3");
50      }
51  
52      @Test
53      public void testRoundtripExampleC1() {
54          doTestRoundtrip("/sp3/example-c-1.sp3");
55      }
56  
57      @Test
58      public void testRoundtripExampleC2() {
59          doTestRoundtrip("/sp3/example-c-2.sp3");
60      }
61  
62      @Test
63      public void testRoundtripExampleD1() {
64          doTestRoundtrip("/sp3/example-d-1.sp3");
65      }
66  
67      @Test
68      public void testRoundtripExampleD2() {
69          doTestRoundtrip("/sp3/example-d-2.sp3");
70      }
71  
72      @Test
73      public void testRoundtripExampleD3() {
74          doTestRoundtrip("/sp3/example-d-3.sp3");
75      }
76  
77      @Test
78      public void testRoundtripExampleD4() {
79          doTestRoundtrip("/sp3/example-d-4.sp3");
80      }
81  
82      @Test
83      public void testRoundtripEsaBHN() {
84          doTestRoundtrip("/sp3/esaBHN.sp3.Z");
85      }
86  
87      @Test
88      public void testRoundtripEsaPRO() {
89          doTestRoundtrip("/sp3/esaPRO.sp3.Z");
90      }
91  
92      @Test
93      public void testRoundtripGbm18432() {
94          doTestRoundtrip("/sp3/gbm18432.sp3.Z");
95      }
96  
97      @Test
98      public void testRoundtripGbm19500AfterDrop() {
99          doTestRoundtrip("/sp3/gbm19500_after_drop.sp3");
100     }
101 
102     @Test
103     public void testRoundtripGbm19500AfterNoDrop() {
104         doTestRoundtrip("/sp3/gbm19500_after_no_drop.sp3");
105     }
106 
107     @Test
108     public void testRoundtripGbm19500LargeGap() {
109         doTestRoundtrip("/sp3/gbm19500_large_gap.sp3");
110     }
111 
112     @Test
113     public void testRoundtripIssue895Clock() {
114         doTestRoundtrip("/sp3/issue895-clock-record.sp3");
115     }
116 
117     @Test
118     public void testRoundtripIssue895HEaderComment() {
119         doTestRoundtrip("/sp3/issue895-header-comment.sp3");
120     }
121 
122     @Test
123     public void testRoundtripIssue895HoursIncrement() {
124         doTestRoundtrip("/sp3/issue895-hours-increment.sp3");
125     }
126 
127     @Test
128     public void testRoundtripIssue895MinutesIncrement() {
129         doTestRoundtrip("/sp3/issue895-minutes-increment.sp3");
130     }
131 
132     @Test
133     public void testRoundtripIssue895NoEOF() {
134         doTestRoundtrip("/sp3/issue895-no-eof.sp3");
135     }
136 
137     @Test
138     public void testRoundtripIssue895SecondDigits() {
139         doTestRoundtrip("/sp3/issue895-second-digits.sp3");
140     }
141 
142     @Test
143     public void testRoundtripLageos() {
144         doTestRoundtrip("/sp3/truncated-nsgf.orb.lageos2.160305.v35.sp3");
145     }
146 
147     @Test
148     public void testRoundtripIssue1327FullLine() {
149         doTestRoundtrip("/sp3/issue1327-136-sats.sp3");
150     }
151 
152     @Test
153     public void testChangeFrameItrf96PositionOnly() {
154         doTestChangeFrame("/sp3/gbm18432.sp3.Z",
155                           FramesFactory.getITRF(ITRFVersion.ITRF_1996,
156                                                 IERSConventions.IERS_1996,
157                                                 false));
158     }
159 
160     @Test
161     public void testChangeFrameItrf05PositionOnly() {
162         doTestChangeFrame("/sp3/gbm18432.sp3.Z",
163                           FramesFactory.getITRF(ITRFVersion.ITRF_2005,
164                                                 IERSConventions.IERS_2003,
165                                                 false));
166     }
167 
168     @Test
169     public void testChangeFrameItrf20PositionOnly() {
170         doTestChangeFrame("/sp3/gbm18432.sp3.Z",
171                           FramesFactory.getITRF(ITRFVersion.ITRF_2020,
172                                                 IERSConventions.IERS_2010,
173                                                 false));
174     }
175 
176     @Test
177     public void testChangeFrameGcrfPositionOnly() {
178         doTestChangeFrame("/sp3/gbm18432.sp3.Z", FramesFactory.getGCRF());
179     }
180 
181     @Test
182     public void testChangeFrameEme2000PositionOnly() {
183         doTestChangeFrame("/sp3/gbm18432.sp3.Z", FramesFactory.getEME2000());
184     }
185 
186     @Test
187     public void testChangeFrameItrf96PositionVelocity() {
188         doTestChangeFrame("/sp3/example-a-2.sp3",
189                           FramesFactory.getITRF(ITRFVersion.ITRF_1996,
190                                                 IERSConventions.IERS_1996,
191                                                 false));
192     }
193 
194     @Test
195     public void testChangeFrameItrf05PositionVelocity() {
196         doTestChangeFrame("/sp3/example-a-2.sp3",
197                           FramesFactory.getITRF(ITRFVersion.ITRF_2005,
198                                                 IERSConventions.IERS_2003,
199                                                 false));
200     }
201 
202     @Test
203     public void testChangeFrameItrf20PositionVelocity() {
204         doTestChangeFrame("/sp3/example-a-2.sp3",
205                           FramesFactory.getITRF(ITRFVersion.ITRF_2020,
206                                                 IERSConventions.IERS_2010,
207                                                 false));
208     }
209 
210     @Test
211     public void testChangeFrameGcrfPositionVelocity() {
212         doTestChangeFrame("/sp3/example-a-2.sp3", FramesFactory.getGCRF());
213     }
214 
215     @Test
216     public void testChangeFrameEme2000PositionVelocity() {
217         doTestChangeFrame("/sp3/example-a-2.sp3", FramesFactory.getEME2000());
218     }
219 
220     private  void doTestRoundtrip(final String name) {
221         try {
222             DataSource source1 = new DataSource(name, () -> getClass().getResourceAsStream(name));
223             if (name.endsWith(".Z")) {
224                 source1 = new UnixCompressFilter().filter(source1);
225 
226             }
227             final SP3 original = new SP3Parser().parse(source1);
228 
229             // write the parsed file back to a characters array
230             final CharArrayWriter caw = new CharArrayWriter();
231             new SP3Writer(caw, "rebuilt-" + name, TimeScalesFactory.getTimeScales()).write(original);
232 
233             // reparse the written file
234             final byte[]     bytes   = caw.toString().getBytes(StandardCharsets.UTF_8);
235             final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
236             final SP3        rebuilt = new SP3Parser().parse(source2);
237 
238             SP3TestUtils.checkEquals(original, rebuilt);
239 
240         } catch (IOException ioe) {
241             Assertions.fail(ioe.getLocalizedMessage());
242         }
243     }
244 
245     private  void doTestChangeFrame(final String name, final Frame newFrame) {
246         try {
247             DataSource source1 = new DataSource(name, () -> getClass().getResourceAsStream(name));
248             if (name.endsWith(".Z")) {
249                 source1 = new UnixCompressFilter().filter(source1);
250 
251             }
252             final SP3 original = new SP3Parser().parse(source1);
253             final Frame originalFrame = original.getEphemeris(0).getFrame();
254 
255             // change frame
256             final SP3 changed = SP3.changeFrame(original, newFrame);
257 
258             // write the parsed file back to a characters array
259             final CharArrayWriter caw = new CharArrayWriter();
260             new SP3Writer(caw, name + "-changed", TimeScalesFactory.getTimeScales()).write(changed);
261 
262             // reparse the written file
263             final byte[]     bytes   = caw.toString().getBytes(StandardCharsets.UTF_8);
264             final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
265             final SP3        rebuilt = new SP3Parser().parse(source2);
266 
267             Assertions.assertEquals(newFrame.getName(), rebuilt.getEphemeris(0).getFrame().getName());
268             Assertions.assertEquals(original.getSatelliteCount(), rebuilt.getSatelliteCount());
269             double maxErrorP = 0;
270             double maxErrorV = 0;
271             for (int i = 0; i < original.getSatelliteCount(); ++i) {
272                 final List<SP3Segment> originalSegments = original.getEphemeris(i).getSegments();
273                 final List<SP3Segment> rebuiltSegments  = rebuilt.getEphemeris(i).getSegments();
274                 Assertions.assertEquals(originalSegments.size(), rebuiltSegments.size());
275                 for (int j = 0; j < originalSegments.size(); ++j) {
276                     final List<SP3Coordinate> originalCoordinates = originalSegments.get(j).getCoordinates();
277                     final List<SP3Coordinate> rebuiltCoordinates  = rebuiltSegments.get(j).getCoordinates();
278                     Assertions.assertEquals(originalCoordinates.size(), rebuiltCoordinates.size());
279                     for (int k = 0; k < originalCoordinates.size(); ++k) {
280                         final SP3Coordinate ok = originalCoordinates.get(k);
281                         final SP3Coordinate rk = rebuiltCoordinates.get(k);
282                         final PVCoordinates pv = newFrame.
283                                                  getTransformTo(originalFrame, ok.getDate()).
284                                                  transformPVCoordinates(rk);
285                         maxErrorP = FastMath.max(maxErrorP, Vector3D.distance(ok.getPosition(), pv.getPosition()));
286                         maxErrorV = FastMath.max(maxErrorV,
287                                                  Vector3D.distance(ok.getVelocity(),
288                                                                    ok.getVelocity().getNorm() < 1.0e-15 ?
289                                                                    rk.getVelocity() :
290                                                                    pv.getVelocity()));
291                     }
292                 }
293             }
294 
295             // tolerances are limited to SP3 file format accuracy
296             // as we have written and parsed again a file
297             Assertions.assertEquals(0, maxErrorP, 1.0e-3);
298             Assertions.assertEquals(0, maxErrorV, 1.0e-6);
299 
300         } catch (IOException ioe) {
301             Assertions.fail(ioe.getLocalizedMessage());
302         }
303     }
304 
305     @BeforeEach
306     public void setUp() {
307         Utils.setDataRoot("regular-data");
308     }
309 
310 }