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.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.gnss.TimeSystem;
26  import org.orekit.time.AbsoluteDate;
27  import org.orekit.utils.CartesianDerivativesFilter;
28  
29  /** Header for SP3 files.
30   * @author Luc Maisonobe
31   * @since 12.0
32   */
33  public class SP3Header {
34  
35      /** String representation of the center of ephemeris coordinate system. **/
36      public static final String SP3_FRAME_CENTER_STRING = "EARTH";
37  
38      /** Name for pos/vel accuracy base header entry. */
39      private static final String POS_VEL_ACCURACY_BASE = "pos/vel accuracy base";
40  
41      /** Name for clock accuracy base header entry. */
42      private static final String CLOCK_ACCURACY_BASE = "clock accuracy base";
43  
44      /** Name for comments header entry. */
45      private static final String COMMENTS = "comments";
46  
47      /** File version. */
48      private char version;
49  
50      /** File type. */
51      private SP3FileType type;
52  
53      /** Time system. */
54      private TimeSystem timeSystem;
55  
56      /** Epoch of the file. */
57      private AbsoluteDate epoch;
58  
59      /** GPS week. */
60      private int gpsWeek;
61  
62      /** Seconds of the current GPS week. */
63      private double secondsOfWeek;
64  
65      /** Julian day. */
66      private int modifiedJulianDay;
67  
68      /** Day fraction. */
69      private double dayFraction;
70  
71      /** Time-interval between epochs. */
72      private double epochInterval;
73  
74      /** Number of epochs. */
75      private int numberOfEpochs;
76  
77      /** Coordinate system. */
78      private String coordinateSystem;
79  
80      /** Data used indicator. */
81      private List<DataUsed> dataUsed;
82  
83      /** Orbit type. */
84      private SP3OrbitType orbitType;
85  
86      /** Key for orbit type. */
87      private String orbitTypeKey;
88  
89      /** Agency providing the file. */
90      private String agency;
91  
92      /** Indicates if data contains velocity or not. */
93      private CartesianDerivativesFilter filter;
94  
95      /** Base for position/velocity accuracy. */
96      private double posVelBase;
97  
98      /** Base for clock/clock-rate accuracy. */
99      private double clockBase;
100 
101     /** Satellite identifiers. */
102     private List<String> satIds;
103 
104     /** Satellite accuracies. */
105     private double[] accuracies;
106 
107     /** Comments. */
108     private final List<String> comments;
109 
110     /** Create a new SP3 header.
111      */
112     public SP3Header() {
113         this.version    = '?';
114         this.satIds     = new ArrayList<>();
115         this.accuracies = null;
116         this.comments   = new ArrayList<>();
117     }
118 
119     /** Check header is valid.
120      * @param parsing if true, we are parsing an existing file, and are more lenient
121      * in order to accept some common errors (like between 86 and 99 satellites
122      * in SP3a, SP3b or SP3c files)
123      * @param hasAccuracy if true, there are accuracy data in the file
124      * @param fileName file name to generate the error message
125      * @exception OrekitException if file is not valid
126      */
127     void validate(final boolean parsing, final boolean hasAccuracy, final String fileName) throws OrekitException {
128 
129         // check version
130         if ("abcd".indexOf(getVersion()) < 0) {
131             throw new OrekitException(OrekitMessages.SP3_UNSUPPORTED_VERSION, getVersion());
132         }
133 
134         if (getVersion() == 'a') {
135             // in SP3 version a, the base accuracy must be set to 0
136             if (getPosVelBase() != 0.0) {
137                 throw new OrekitException(OrekitMessages.SP3_INVALID_HEADER_ENTRY,
138                                           POS_VEL_ACCURACY_BASE, getPosVelBase(), fileName, getVersion());
139             }
140             if (getClockBase() != 0.0) {
141                 throw new OrekitException(OrekitMessages.SP3_INVALID_HEADER_ENTRY,
142                                           CLOCK_ACCURACY_BASE, getClockBase(), fileName, getVersion());
143             }
144         } else if (hasAccuracy) {
145             // in SP3 versions after version a, the base accuracy must be set if entries specify accuracy
146             if (getPosVelBase() <= 0.0) {
147                 throw new OrekitException(OrekitMessages.SP3_INVALID_HEADER_ENTRY,
148                                           POS_VEL_ACCURACY_BASE, getPosVelBase(), fileName, getVersion());
149             }
150             if (getClockBase() <= 0.0) {
151                 throw new OrekitException(OrekitMessages.SP3_INVALID_HEADER_ENTRY,
152                                           CLOCK_ACCURACY_BASE, getClockBase(), fileName, getVersion());
153             }
154         }
155         if (getVersion() < 'd') {
156             // in SP3 versions a, b, and c, there are exactly 4 comments with max length 57
157             // (60 minus first three characters)
158             if (comments.size() != 4 ||
159                 comments.get(0).length() > 57 ||
160                 comments.get(1).length() > 57 ||
161                 comments.get(2).length() > 57 ||
162                 comments.get(3).length() > 57) {
163                 throw new OrekitException(OrekitMessages.SP3_INVALID_HEADER_ENTRY,
164                                           COMMENTS, "/* …", fileName, getVersion());
165             }
166         } else {
167             // starting with SP3 version d, there is an unspecified number of comments with max length 77
168             // (80 minus first three characters)
169             for (final String c : comments) {
170                 if (c.length() > 77) {
171                     throw new OrekitException(OrekitMessages.SP3_INVALID_HEADER_ENTRY,
172                                               COMMENTS, c, fileName, getVersion());
173                 }
174             }
175         }
176 
177     }
178 
179     /** Set the file version.
180      * @param version file version
181      */
182     public void setVersion(final char version) {
183         this.version = version;
184     }
185 
186     /** Get the file version.
187      * @return file version
188      */
189     public char getVersion() {
190         return version;
191     }
192 
193     /** Set the derivatives filter.
194      * @param filter that indicates which derivatives of position are available.
195      */
196     public void setFilter(final CartesianDerivativesFilter filter) {
197         this.filter = filter;
198     }
199 
200     /** Get the derivatives filter.
201      * @return filter with available derivatives
202      */
203     public CartesianDerivativesFilter getFilter() {
204         return filter;
205     }
206 
207     /** Returns the {@link SP3FileType} associated with this SP3 file.
208      * @return the file type for this SP3 file
209      */
210     public SP3FileType getType() {
211         return type;
212     }
213 
214     /** Set the file type for this SP3 file.
215      * @param fileType the file type to be set
216      */
217     public void setType(final SP3FileType fileType) {
218         this.type = fileType;
219     }
220 
221     /** Returns the {@link TimeSystem} used to time-stamp position entries.
222      * @return the {@link TimeSystem} of the orbit file
223      */
224     public TimeSystem getTimeSystem() {
225         return timeSystem;
226     }
227 
228     /** Set the time system used in this SP3 file.
229      * @param system the time system to be set
230      */
231     public void setTimeSystem(final TimeSystem system) {
232         this.timeSystem = system;
233     }
234 
235     /** Returns the data used indicator from the SP3 file.
236      * @return the data used indicator
237      */
238     public List<DataUsed> getDataUsed() {
239         return dataUsed;
240     }
241 
242     /** Set the data used indicator for this SP3 file.
243      * @param dataUsed the data used indicator to be set
244      */
245     public void setDataUsed(final List<DataUsed> dataUsed) {
246         this.dataUsed = dataUsed;
247     }
248 
249     /** Returns the start epoch of the orbit file.
250      * @return the start epoch
251      */
252     public AbsoluteDate getEpoch() {
253         return epoch;
254     }
255 
256     /** Set the epoch of the SP3 file.
257      * @param time the epoch to be set
258      */
259     public void setEpoch(final AbsoluteDate time) {
260         this.epoch = time;
261     }
262 
263     /** Returns the GPS week as contained in the SP3 file.
264      * @return the GPS week of the SP3 file
265      */
266     public int getGpsWeek() {
267         return gpsWeek;
268     }
269 
270     /** Set the GPS week of the SP3 file.
271      * @param week the GPS week to be set
272      */
273     public void setGpsWeek(final int week) {
274         this.gpsWeek = week;
275     }
276 
277     /** Returns the seconds of the GPS week as contained in the SP3 file.
278      * @return the seconds of the GPS week
279      */
280     public double getSecondsOfWeek() {
281         return secondsOfWeek;
282     }
283 
284     /** Set the seconds of the GPS week for this SP3 file.
285      * @param seconds the seconds to be set
286      */
287     public void setSecondsOfWeek(final double seconds) {
288         this.secondsOfWeek = seconds;
289     }
290 
291     /** Returns the modified julian day for this SP3 file.
292      * @return the modified julian day
293      */
294     public int getModifiedJulianDay() {
295         return modifiedJulianDay;
296     }
297 
298     /** Set the modified julian day for this SP3 file.
299      * @param day the modified julian day to be set
300      */
301     public void setModifiedJulianDay(final int day) {
302         this.modifiedJulianDay = day;
303     }
304 
305     /** Returns the day fraction for this SP3 file.
306      * @return the day fraction
307      */
308     public double getDayFraction() {
309         return dayFraction;
310     }
311 
312     /** Set the day fraction for this SP3 file.
313      * @param fraction the day fraction to be set
314      */
315     public void setDayFraction(final double fraction) {
316         this.dayFraction = fraction;
317     }
318 
319     /** Returns the time interval between epochs (in seconds).
320      * @return the time interval between epochs
321      */
322     public double getEpochInterval() {
323         return epochInterval;
324     }
325 
326     /** Set the epoch interval for this SP3 file.
327      * @param interval the interval between orbit entries
328      */
329     public void setEpochInterval(final double interval) {
330         this.epochInterval = interval;
331     }
332 
333     /** Returns the number of epochs contained in this orbit file.
334      * @return the number of epochs
335      */
336     public int getNumberOfEpochs() {
337         return numberOfEpochs;
338     }
339 
340     /** Set the number of epochs as contained in the SP3 file.
341      * @param epochCount the number of epochs to be set
342      */
343     public void setNumberOfEpochs(final int epochCount) {
344         this.numberOfEpochs = epochCount;
345     }
346 
347     /** Returns the coordinate system of the entries in this orbit file.
348      * @return the coordinate system
349      */
350     public String getCoordinateSystem() {
351         return coordinateSystem;
352     }
353 
354     /** Set the coordinate system used for the orbit entries.
355      * @param system the coordinate system to be set
356      */
357     public void setCoordinateSystem(final String system) {
358         this.coordinateSystem = system;
359     }
360 
361     /** Returns the {@link SP3OrbitType} for this SP3 file.
362      * @return the orbit type
363      */
364     public SP3OrbitType getOrbitType() {
365         return orbitType;
366     }
367 
368     /** Returns the orbit type key for this SP3 file.
369      * @return the orbit type key
370      */
371     public String getOrbitTypeKey() {
372         return orbitTypeKey;
373     }
374 
375     /** Set the orbit type key for this SP3 file.
376      * @param oTypeKey the orbit type key to be set
377      */
378     public void setOrbitTypeKey(final String oTypeKey) {
379         this.orbitTypeKey = oTypeKey;
380         this.orbitType    = SP3OrbitType.parseType(oTypeKey);
381     }
382 
383     /** Returns the agency that prepared this SP3 file.
384      * @return the agency
385      */
386     public String getAgency() {
387         return agency;
388     }
389 
390     /** Set the agency string for this SP3 file.
391      * @param agencyStr the agency string to be set
392      */
393     public void setAgency(final String agencyStr) {
394         this.agency = agencyStr;
395     }
396 
397     /** Set the base for position/velocity accuracy.
398      * @param posVelBase base for position/velocity accuracy
399      */
400     public void setPosVelBase(final double posVelBase) {
401         this.posVelBase = posVelBase;
402     }
403 
404     /** Get the base for position/velocity accuracy.
405      * @return base for position/velocity accuracy
406      */
407     public double getPosVelBase() {
408         return posVelBase;
409     }
410 
411     /** Set the base for clock/clock-rate accuracy.
412      * @param clockBase base for clock/clock-rate accuracy
413      */
414     public void setClockBase(final double clockBase) {
415         this.clockBase = clockBase;
416     }
417 
418     /** Get the base for clock/clock-rate accuracy.
419      * @return base for clock/clock-rate accuracy
420      */
421     public double getClockBase() {
422         return clockBase;
423     }
424 
425     /** Add a satellite identifier.
426      * @param satId satellite identifier
427      */
428     public void addSatId(final String satId) {
429         satIds.add(satId);
430     }
431 
432     /** Get the satellite identifiers.
433      * @return satellites identifiers
434      */
435     public List<String> getSatIds() {
436         return Collections.unmodifiableList(satIds);
437     }
438 
439     /** Set the accuracy.
440      * @param index satellite index in {@link #getSatIds()}
441      * @param accuracy in m
442      */
443     public void setAccuracy(final int index, final double accuracy) {
444         if (accuracies == null) {
445             // lazy allocation of the array
446             accuracies = new double[satIds.size()];
447         }
448         accuracies[index] = accuracy;
449     }
450 
451     /** Get the formal accuracy.
452      * <p>
453      * The accuracy is limited by the SP3 standard to be a power of 2 in mm.
454      * The value returned here is in meters.
455      * </p>
456      * @param satId satellite identifier
457      * @return magnitude of one standard deviation, in m.
458      */
459     public double getAccuracy(final String satId) {
460         for (int i = 0; i < satIds.size(); ++i) {
461             if (satIds.get(i).equals(satId)) {
462                 return accuracies[i];
463             }
464         }
465         return Double.NaN;
466     }
467 
468     /** Get the comments.
469      * @return an unmodifiable view of comments
470      */
471     public List<String> getComments() {
472         return Collections.unmodifiableList(comments);
473     }
474 
475     /** Add a comment.
476      * @param comment comment to add
477      */
478     public void addComment(final String comment) {
479         comments.add(comment);
480     }
481 
482 }