1   /* Copyright 2024-2025 The Johns Hopkins University Applied Physics Laboratory
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.iirv;
18  
19  import org.hipparchus.exception.LocalizedCoreFormats;
20  import org.orekit.data.DataSource;
21  import org.orekit.errors.OrekitException;
22  import org.orekit.errors.OrekitIllegalArgumentException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.files.general.EphemerisFileParser;
25  import org.orekit.time.UTCScale;
26  import org.orekit.utils.Constants;
27  
28  import java.io.BufferedReader;
29  import java.io.IOException;
30  import java.io.Reader;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.List;
35  import java.util.regex.Pattern;
36  
37  /**
38   * Parser of {@link IIRVEphemerisFile}s.
39   *
40   * @author Nick LaFarge
41   * @since 13.0
42   */
43  public class IIRVParser implements EphemerisFileParser<IIRVEphemerisFile> {
44  
45      /** Default number of sample for interpolating data (See: reference documents). */
46      public static final int DEFAULT_INTERPOLATION_SAMPLE = 10;
47  
48      /** Line separator. */
49      private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile(IIRVVector.LINE_SEPARATOR);
50  
51      /** Standard gravitational parameter in m³/s². */
52      private final double mu;
53  
54      /** Number of data points to use in interpolation. */
55      private final int interpolationSamples;
56  
57      /** Year of the initial vector in the IIRV ephemeris file. */
58      private final int year;
59  
60      /** UTC time scale. */
61      private final UTCScale utc;
62  
63      /**
64       * Constructs a {@link IIRVParser} instance with default values.
65       * <p>
66       * Default gravitational parameter is {@link Constants#IERS96_EARTH_MU}. Default number of
67       * interpolation samples is 7.
68       *
69       * @param year year of the initial vector in the IIRV ephemeris file.
70       * @param utc  UTC time scale
71       */
72      public IIRVParser(final int year, final UTCScale utc) {
73          this(Constants.IERS96_EARTH_MU, DEFAULT_INTERPOLATION_SAMPLE, year, utc);
74      }
75  
76      /**
77       * Constructs a {@link IIRVParser} instance.
78       *
79       * @param mu                   gravitational parameter (m^3/s^2)
80       * @param interpolationSamples is the number of samples to use when interpolating.
81       * @param year                 year of the initial vector in the IIRV ephemeris file.
82       * @param utc                  UTC time scale
83       */
84      public IIRVParser(final double mu, final int interpolationSamples, final int year, final UTCScale utc) {
85          this.mu = mu;
86          this.interpolationSamples = interpolationSamples;
87          this.year = year;
88          this.utc = utc;
89      }
90  
91      /** {@inheritDoc} */
92      @Override
93      public IIRVEphemerisFile parse(final DataSource source) {
94          if (source == null) {
95              throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "source");
96          }
97  
98          final ArrayList<String> messageLines = new ArrayList<>();
99  
100         try (Reader reader = source.getOpener().openReaderOnce();
101              BufferedReader bufferedReader = (reader == null) ? null : new BufferedReader(reader)) {
102 
103             if (bufferedReader == null) {
104                 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
105             }
106 
107             // Message lines
108             final List<String> vectorLines = new ArrayList<>(Collections.nCopies(6, ""));
109             int currentIIRVLine = vectorLines.indexOf("");
110             String line = bufferedReader.readLine();  // Initialize on first line
111 
112             if (line == null) {
113                 throw new OrekitException(OrekitMessages.NO_DATA_IN_FILE, source.getName());
114             }
115 
116             while (line != null) {
117                 messageLines.add(line);
118                 vectorLines.set(currentIIRVLine, line);  // Set the line in the list
119 
120                 // If every line is set, create an IIRV vector and clear out the strings. Otherwise, increment
121                 // the line counter
122                 if (currentIIRVLine == 5) {
123                     for (int i = 0; i < 6; i++) { // Reset each line + line counter
124                         vectorLines.set(i, "");
125                     }
126                     currentIIRVLine = 0;
127                 } else {
128                     currentIIRVLine++;
129                 }
130 
131                 // Expect two line breaks here (except end of file
132                 final String linebreak1 = bufferedReader.readLine();
133                 final String linebreak2 = bufferedReader.readLine();
134                 if (linebreak1 == null || linebreak2 == null) {
135                     break;
136                 } else if (!linebreak1.isEmpty() || !linebreak2.isEmpty()) {
137                     throw new OrekitException(OrekitMessages.IIRV_MISSING_LINEBREAK_IN_FILE, currentIIRVLine, source.getName());
138                 }
139 
140                 line = bufferedReader.readLine();
141             }
142 
143         } catch (IOException ioe) {
144             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
145         }
146         return parse(messageLines);
147     }
148 
149 
150     /**
151      * Parses a string representing an IIRV message.
152      *
153      * @param iirv String representation of an IIRV message
154      * @return newly created {@link IIRVSegment} object populated with ephemeris data parsed from
155      * {@code iirvVectorStrings}
156      */
157     public IIRVEphemerisFile parse(final String iirv) {
158         return parse(Arrays.asList(LINE_SEPARATOR_PATTERN.split(iirv)));
159     }
160 
161     /**
162      * Parses a list of strings that comprise an {@link IIRVMessage}.
163      *
164      * @param iirvVectorStrings list of Strings that comprise an {@link IIRVMessage}
165      * @return newly created {@link IIRVSegment} object populated with ephemeris data parsed from
166      * {@code iirvVectorStrings}
167      */
168     public IIRVEphemerisFile parse(final List<String> iirvVectorStrings) {
169         final ArrayList<IIRVVector> vectors = new ArrayList<>();
170 
171         if (iirvVectorStrings == null) {
172             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "iirvVectorStrings");
173         }
174 
175         if (iirvVectorStrings.isEmpty()) {
176             throw new OrekitIllegalArgumentException(OrekitMessages.IIRV_INVALID_LINE_IN_VECTOR, 1, "");
177         }
178 
179         // The first vector in a message must *always* include metadata
180         Pattern line1Pattern = IIRVVector.LINE_1_PATTERN_METADATA_INCLUDED;
181 
182         // Message lines
183         final List<String> vectorLines = new ArrayList<>(Collections.nCopies(6, ""));
184         int currentIIRVLine = vectorLines.indexOf("");
185 
186         for (String line : iirvVectorStrings) {
187 
188             // The second vector tells us whether to expect metadata in line 1 for the
189             // remainder of the file
190             if (vectors.size() == 1 && currentIIRVLine == 0 && IIRVVector.LINE_1_PATTERN_METADATA_OMITTED.matcher(line).matches()) {
191                 line1Pattern = IIRVVector.LINE_1_PATTERN_METADATA_OMITTED;
192             }
193 
194             // Check if this line matches an IIRV pattern based on the current line index
195             final boolean line1Valid = currentIIRVLine == 0 && line1Pattern.matcher(line).matches();
196 
197             boolean isValidLine2to6 = false;
198             for (int i = currentIIRVLine; i < 6; i++) {
199                 final boolean isValidForLineI = IIRVVector.validateLine(i, line);
200                 if (isValidForLineI) {
201                     if (currentIIRVLine == i) {
202                         isValidLine2to6 = true;  // Only valid if the line is validated at the right location
203                         break;
204                     } else {
205                         // Valid for the wrong line-> invalid file
206                         throw new OrekitException(OrekitMessages.IIRV_INVALID_LINE_IN_VECTOR,
207                             currentIIRVLine, line);
208                     }
209                 }
210             }
211 
212             // Continue if this line matches a pattern
213             if (line1Valid || isValidLine2to6) {
214                 vectorLines.set(currentIIRVLine, line);  // Set the line in the list
215 
216                 // If every line is set, create an IIRV vector and clear out the strings. Otherwise, increment
217                 // the line counter
218                 if (currentIIRVLine == 5) {
219                     IIRVVector newVector = new IIRVVector(vectorLines, utc);
220 
221                     // Add metadata (if applicable)
222                     if (!vectors.isEmpty() && line1Pattern == IIRVVector.LINE_1_PATTERN_METADATA_OMITTED) {
223                         vectorLines.set(0, vectors.get(0).buildLine1(true));
224                         newVector = new IIRVVector(vectorLines, utc);
225                     }
226 
227                     vectors.add(newVector);
228 
229                     for (int i = 0; i < 6; i++) { // Reset each line + line counter
230                         vectorLines.set(i, "");
231                     }
232                     currentIIRVLine = 0;
233                 } else {
234                     currentIIRVLine++;
235                 }
236             }
237         }
238         return new IIRVEphemerisFile(mu, interpolationSamples, year, new IIRVMessage(vectors));
239     }
240 }