1   /* Copyright 2002-2021 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.frames;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.SortedSet;
28  import java.util.function.Supplier;
29  import java.util.regex.Matcher;
30  import java.util.regex.Pattern;
31  
32  import org.orekit.data.DataProvidersManager;
33  import org.orekit.errors.OrekitException;
34  import org.orekit.errors.OrekitMessages;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.time.DateComponents;
37  import org.orekit.time.TimeScale;
38  import org.orekit.utils.IERSConventions;
39  import org.orekit.utils.IERSConventions.NutationCorrectionConverter;
40  import org.orekit.utils.units.UnitsConverter;
41  
42  /** Loader for IERS rapid data and prediction files in columns format (finals file).
43   * <p>Rapid data and prediction files contain {@link EOPEntry
44   * Earth Orientation Parameters} for several years periods, in one file
45   * only that is updated regularly.</p>
46   * <p>
47   * These files contain both the data from IERS Bulletin A and IERS bulletin B.
48   * This class parses only the part from Bulletin A.
49   * </p>
50   * <p>The rapid data and prediction file is recognized thanks to its base name,
51   * which must match one of the the patterns <code>finals.*</code> or
52   * <code>finals2000A.*</code> (or the same ending with <code>.gz</code>
53   * for gzip-compressed files) where * stands for a word like "all", "daily",
54   * or "data". The file with 2000A in their name correspond to the
55   * IAU-2000 precession-nutation model whereas the files without any identifier
56   * correspond to the IAU-1980 precession-nutation model. The files with the all
57   * suffix start from 1973-01-01, the file with the data suffix start
58   * from 1992-01-01 and the files with the daily suffix.</p>
59   * <p>
60   * This class is immutable and hence thread-safe
61   * </p>
62   * @author Romain Di Costanzo
63   * @see <a href="http://maia.usno.navy.mil/ser7/readme.finals2000A">finals2000A file format description at USNO</a>
64   * @see <a href="http://maia.usno.navy.mil/ser7/readme.finals">finals file format description at USNO</a>
65   */
66  class RapidDataAndPredictionColumnsLoader extends AbstractEopLoader
67          implements EOPHistoryLoader {
68  
69      /** Field for year, month and day parsing. */
70      private static final String  INTEGER2_FIELD               = "((?:\\p{Blank}|\\p{Digit})\\p{Digit})";
71  
72      /** Field for modified Julian day parsing. */
73      private static final String  MJD_FIELD                    = "\\p{Blank}+(\\p{Digit}+)(?:\\.00*)";
74  
75      /** Field for separator parsing. */
76      private static final String  SEPARATOR                    = "\\p{Blank}*[IP]";
77  
78      /** Field for real parsing. */
79      private static final String  REAL_FIELD                   = "\\p{Blank}*(-?\\p{Digit}*\\.\\p{Digit}*)";
80  
81      /** Start index of the date part of the line. */
82      private static int DATE_START = 0;
83  
84      /** end index of the date part of the line. */
85      private static int DATE_END   = 15;
86  
87      /** Pattern to match the date part of the line (always present). */
88      private static final Pattern DATE_PATTERN = Pattern.compile(INTEGER2_FIELD + INTEGER2_FIELD + INTEGER2_FIELD + MJD_FIELD);
89  
90      /** Start index of the pole part of the line. */
91      private static int POLE_START = 16;
92  
93      /** end index of the pole part of the line. */
94      private static int POLE_END   = 55;
95  
96      /** Pattern to match the pole part of the line. */
97      private static final Pattern POLE_PATTERN = Pattern.compile(SEPARATOR + REAL_FIELD + REAL_FIELD + REAL_FIELD + REAL_FIELD);
98  
99      /** Start index of the UT1-UTC part of the line. */
100     private static int UT1_UTC_START = 57;
101 
102     /** end index of the UT1-UTC part of the line. */
103     private static int UT1_UTC_END   = 78;
104 
105     /** Pattern to match the UT1-UTC part of the line. */
106     private static final Pattern UT1_UTC_PATTERN = Pattern.compile(SEPARATOR + REAL_FIELD + REAL_FIELD);
107 
108     /** Start index of the LOD part of the line. */
109     private static int LOD_START = 79;
110 
111     /** end index of the LOD part of the line. */
112     private static int LOD_END   = 93;
113 
114     /** Pattern to match the LOD part of the line. */
115     private static final Pattern LOD_PATTERN = Pattern.compile(REAL_FIELD + REAL_FIELD);
116 
117     /** Start index of the nutation part of the line. */
118     private static int NUTATION_START = 95;
119 
120     /** end index of the nutation part of the line. */
121     private static int NUTATION_END   = 134;
122 
123     /** Pattern to match the nutation part of the line. */
124     private static final Pattern NUTATION_PATTERN = Pattern.compile(SEPARATOR + REAL_FIELD + REAL_FIELD + REAL_FIELD + REAL_FIELD);
125 
126     /** Type of nutation corrections. */
127     private final boolean isNonRotatingOrigin;
128 
129     /** Build a loader for IERS bulletins B files.
130      * @param isNonRotatingOrigin if true the supported files <em>must</em>
131      * contain δX/δY nutation corrections, otherwise they
132      * <em>must</em> contain δΔψ/δΔε nutation
133      * corrections
134      * @param supportedNames regular expression for supported files names
135      * @param manager provides access to EOP data files.
136      * @param utcSupplier UTC time scale.
137      */
138     RapidDataAndPredictionColumnsLoader(final boolean isNonRotatingOrigin,
139                                         final String supportedNames,
140                                         final DataProvidersManager manager,
141                                         final Supplier<TimeScale> utcSupplier) {
142         super(supportedNames, manager, utcSupplier);
143         this.isNonRotatingOrigin = isNonRotatingOrigin;
144     }
145 
146     /** {@inheritDoc} */
147     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
148                             final SortedSet<EOPEntry> history) {
149         final ItrfVersionProvider itrfVersionProvider = new ITRFVersionLoader(
150                 ITRFVersionLoader.SUPPORTED_NAMES,
151                 getDataProvidersManager());
152         final Parser parser =
153                 new Parser(converter, itrfVersionProvider, getUtc(), isNonRotatingOrigin);
154         final EopParserLoader loader = new EopParserLoader(parser);
155         this.feed(loader);
156         history.addAll(loader.getEop());
157     }
158 
159     /** Internal class performing the parsing. */
160     static class Parser extends AbstractEopParser {
161 
162         /** Indicator for Non-Rotating Origin. */
163         private final boolean isNonRotatingOrigin;
164 
165         /** Simple constructor.
166          * @param converter converter to use
167          * @param itrfVersionProvider to use for determining the ITRF version of the EOP.
168          * @param utc time scale for parsing dates.
169          * @param isNonRotatingOrigin type of nutation correction
170          */
171         Parser(final NutationCorrectionConverter converter,
172                final ItrfVersionProvider itrfVersionProvider,
173                final TimeScale utc,
174                final boolean isNonRotatingOrigin) {
175             super(converter, itrfVersionProvider, utc);
176             this.isNonRotatingOrigin = isNonRotatingOrigin;
177         }
178 
179         /** {@inheritDoc} */
180         @Override
181         public Collection<EOPEntry> parse(final InputStream input, final String name)
182             throws IOException {
183 
184             final List<EOPEntry> history = new ArrayList<>();
185             ITRFVersionLoader.ITRFVersionConfiguration configuration = null;
186 
187             // reset parse info to start new file (do not clear history!)
188             int lineNumber = 0;
189 
190             // set up a reader for line-oriented bulletin B files
191             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
192 
193                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
194 
195                     lineNumber++;
196 
197                     // split the lines in its various columns (some of them can be blank)
198                     final String datePart      = (line.length() >= DATE_END)     ? line.substring(DATE_START,       DATE_END)     : "";
199                     final String polePart      = (line.length() >= POLE_END)     ? line.substring(POLE_START,       POLE_END)     : "";
200                     final String ut1utcPart    = (line.length() >= UT1_UTC_END ) ? line.substring(UT1_UTC_START,    UT1_UTC_END)  : "";
201                     final String lodPart       = (line.length() >= LOD_END)      ? line.substring(LOD_START,        LOD_END)      : "";
202                     final String nutationPart  = (line.length() >= NUTATION_END) ? line.substring(NUTATION_START,   NUTATION_END) : "";
203 
204                     // parse the date part
205                     final Matcher dateMatcher = DATE_PATTERN.matcher(datePart);
206                     final int mjd;
207                     if (dateMatcher.matches()) {
208                         final int yy = Integer.parseInt(dateMatcher.group(1).trim());
209                         final int mm = Integer.parseInt(dateMatcher.group(2).trim());
210                         final int dd = Integer.parseInt(dateMatcher.group(3).trim());
211                         mjd = Integer.parseInt(dateMatcher.group(4).trim());
212                         final DateComponents reconstructedDate = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd);
213                         if ((reconstructedDate.getYear() % 100) != yy ||
214                              reconstructedDate.getMonth()       != mm ||
215                              reconstructedDate.getDay()         != dd) {
216                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
217                                                       lineNumber, name, line);
218                         }
219                     } else {
220                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
221                                                   lineNumber, name, line);
222                     }
223 
224                     // parse the pole part
225                     final double x;
226                     final double y;
227                     if (polePart.trim().length() == 0) {
228                         // pole part is blank
229                         x = 0;
230                         y = 0;
231                     } else {
232                         final Matcher poleMatcher = POLE_PATTERN.matcher(polePart);
233                         if (poleMatcher.matches()) {
234                             x = UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(poleMatcher.group(1)));
235                             y = UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(poleMatcher.group(3)));
236                         } else {
237                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
238                                                       lineNumber, name, line);
239                         }
240                     }
241 
242                     // parse the UT1-UTC part
243                     final double dtu1;
244                     if (ut1utcPart.trim().length() == 0) {
245                         // UT1-UTC part is blank
246                         dtu1 = 0;
247                     } else {
248                         final Matcher ut1utcMatcher = UT1_UTC_PATTERN.matcher(ut1utcPart);
249                         if (ut1utcMatcher.matches()) {
250                             dtu1 = Double.parseDouble(ut1utcMatcher.group(1));
251                         } else {
252                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
253                                                       lineNumber, name, line);
254                         }
255                     }
256 
257                     // parse the lod part
258                     final double lod;
259                     if (lodPart.trim().length() == 0) {
260                         // lod part is blank
261                         lod = 0;
262                     } else {
263                         final Matcher lodMatcher = LOD_PATTERN.matcher(lodPart);
264                         if (lodMatcher.matches()) {
265                             lod = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(lodMatcher.group(1)));
266                         } else {
267                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
268                                                       lineNumber, name, line);
269                         }
270                     }
271 
272                     // parse the nutation part
273                     final double[] nro;
274                     final double[] equinox;
275                     final AbsoluteDate mjdDate =
276                             new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
277                                     getUtc());
278                     if (nutationPart.trim().length() == 0) {
279                         // nutation part is blank
280                         nro     = new double[2];
281                         equinox = new double[2];
282                     } else {
283                         final Matcher nutationMatcher = NUTATION_PATTERN.matcher(nutationPart);
284                         if (nutationMatcher.matches()) {
285                             if (isNonRotatingOrigin) {
286                                 nro = new double[] {
287                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationMatcher.group(1))),
288                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationMatcher.group(3)))
289                                 };
290                                 equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
291                             } else {
292                                 equinox = new double[] {
293                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationMatcher.group(1))),
294                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationMatcher.group(3)))
295                                 };
296                                 nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
297                             }
298                         } else {
299                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
300                                                       lineNumber, name, line);
301                         }
302                     }
303 
304                     if (configuration == null || !configuration.isValid(mjd)) {
305                         // get a configuration for current name and date range
306                         configuration = getItrfVersionProvider().getConfiguration(name, mjd);
307                     }
308                     history.add(new EOPEntry(mjd, dtu1, lod, x, y, equinox[0], equinox[1], nro[0], nro[1],
309                                              configuration.getVersion(), mjdDate));
310 
311                 }
312 
313             }
314 
315             return history;
316         }
317 
318     }
319 
320 }