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.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.Arrays;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.SortedSet;
30  import java.util.function.Supplier;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import org.hipparchus.util.FastMath;
35  import org.orekit.data.DataLoader;
36  import org.orekit.data.DataProvidersManager;
37  import org.orekit.errors.OrekitException;
38  import org.orekit.errors.OrekitInternalError;
39  import org.orekit.errors.OrekitMessages;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.time.DateComponents;
42  import org.orekit.time.TimeScale;
43  import org.orekit.utils.IERSConventions;
44  import org.orekit.utils.units.UnitsConverter;
45  
46  /** Loader for bulletin A files.
47   * <p>Bulletin A files contain {@link EOPEntry
48   * Earth Orientation Parameters} for a few days periods, they
49   * correspond to rapid data estimations, suitable for near-real time
50   * and prediction purposes. Prediction series are only available for
51   * pole motion xp, yp and UT1-UTC, they are not available for
52   * pole offsets (Δδψ/Δδε and x/y).</p>
53   * <p>A bulletin A published on Modified Julian Day mjd (nominally a
54   * Thursday) will generally contain:
55   * </p>
56   * <ul>
57   *   <li>rapid service xp, yp and UT1-UTC data from mjd-6 to mjd</li>
58   *   <li>prediction xp, yp and UT1-UTC data from mjd+1 to mjd+365</li>
59   *   <li>if it is first bulletin of month m, final values xp, yp and
60   *       UT1-UTC data from day 2 of month m-2 to day 1 of month m-1</li>
61   *   <li>rapid service pole offsets Δδψ/Δδε and x/y if available, for some
62   *       varying period somewhere from mjd-30 to mjd-10 (see below)</li>
63   *   <li>if it is first bulletin of month m, final values pole offsets
64   *       Δδψ/Δδε and x/y data from day 2 of month m-2 to day 1 of month
65   *       m-1</li>
66   * </ul>
67   * <p>
68   * There are some discrepancies in the rapid service time range above,
69   * mainly when the nominal publication Thursday corresponds to holidays.
70   * In this case a bulletin may be published the day before and have a 6
71   * days span only for rapid data, and a later bulletin will have an 8 days
72   * span to recover the normal schedule. This occurred for bulletin A Vol.
73   * XVIII No. 047, bulletin A Vol. XVIII No. 048, bulletin A Vol. XXI No.
74   * 052 and bulletin A Vol. XXII No. 001.
75   * </p>
76   * <p>Rapid service for pole offsets appears irregular. As extreme examples
77   * bulletin A Vol. XXVI No. 037 from 2013-09-12 contained 15 entries
78   * for pole offsets, from mjd-22 to mjd-8, bulletin A Vol. XXVI No. 039
79   * from 2013-09-26 contained only 3 entries for pole offsets, from mjd-15
80   * to mjd-13, and bulletin A Vol. XXVI No. 040 from 2013-10-03 contained no
81   * rapid service pole offsets at all, it contained only final values. Despite
82   * this irregularity, rapid service data is continuous over consecutive files,
83   * so the mean number of entries is 7 as the files are published on a weekly
84   * basis.
85   * </p>
86   * <p>
87   * There are no prediction data for pole offsets.
88   * </p>
89   * <p>
90   * This loader reads both the rapid service, the prediction and the final
91   * values parts. As successive files have overlaps between all these sections,
92   * values extracted from latest files (with respect to the covered dates)
93   * override values extracted from earlier files, regardless of the files
94   * reading order. If numerous bulletins A covering more than one year are read,
95   * one particular date will typically appear in the prediction section of
96   * 52 or 53 files, then in the rapid data section of one file, then it will
97   * be missing in a few files, and will finally appear a last time in the
98   * final values sections of a last file. In this case, the value retained
99   * will be the one extracted from the final values section in the more
100  * recent file.
101  * </p>
102  * <p>
103  * If only one bulletin A file is read and it correspond to the first bulletin
104  * of a month, it will have a roughly one month wide hole between the
105  * final data and the rapid data. This hole will trigger an error as EOP
106  * continuity is checked by default for at most 5 days holes. In this case,
107  * users should call something like {@link Frames#setEOPContinuityThreshold(double)
108  * FramesFactory.setEOPContinuityThreshold(Constants.JULIAN_YEAR)} to prevent
109  * the error to be triggered.
110  * </p>
111  * <p>The bulletin A files are recognized thanks to their base names,
112  * which must match the pattern <code>bulletina-xxxx-###.txt</code>,
113  * (or the same ending with <code>.gz</code> for gzip-compressed files)
114  * where x stands for a roman numeral character and # stands for a digit
115  * character.</p>
116  * <p>
117  * Bulletin A in csv format must be read using {@link EopCsvFilesLoader} rather
118  * than using this loader. Bulletin A in xml format must be read using {@link EopXmlLoader}
119  * rather than using this loader.
120  * </p>
121  * <p>
122  * This class is immutable and hence thread-safe
123  * </p>
124  * @author Luc Maisonobe
125  * @since 7.0
126  * @see EopCsvFilesLoader
127  * @see EopXmlLoader
128  */
129 class BulletinAFilesLoader extends AbstractEopLoader implements EopHistoryLoader {
130 
131     /** Regular expression matching blanks at start of line. */
132     private static final String LINE_START_REGEXP     = "^\\p{Blank}+";
133 
134     /** Regular expression matching blanks at end of line. */
135     private static final String LINE_END_REGEXP       = "\\p{Blank}*$";
136 
137     /** Regular expression matching integers. */
138     private static final String INTEGER_REGEXP        = "[-+]?\\p{Digit}+";
139 
140     /** Regular expression matching real numbers. */
141     private static final String REAL_REGEXP           = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
142 
143     /** Regular expression matching an integer field to store. */
144     private static final String STORED_INTEGER_FIELD  = "\\p{Blank}*(" + INTEGER_REGEXP + ")";
145 
146     /** regular expression matching a Modified Julian Day field to store. */
147     private static final String STORED_MJD_FIELD      = "\\p{Blank}+(\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})";
148 
149     /** Regular expression matching a real field to store. */
150     private static final String STORED_REAL_FIELD     = "\\p{Blank}+(" + REAL_REGEXP + ")";
151 
152     /** Regular expression matching a real field to ignore. */
153     private static final String IGNORED_REAL_FIELD    = "\\p{Blank}+" + REAL_REGEXP;
154 
155     /** Enum for files sections, in expected order.
156      * <p>The bulletin A weekly data files contain several sections,
157      * each introduced with some fixed header text and followed by tabular data.
158      * </p>
159      */
160     private enum Section {
161 
162         /** Earth Orientation Parameters rapid service. */
163         // section 2 always contain rapid service data including error fields
164         //      COMBINED EARTH ORIENTATION PARAMETERS:
165         //
166         //                              IERS Rapid Service
167         //              MJD      x    error     y    error   UT1-UTC   error
168         //                       "      "       "      "        s        s
169         //   13  8 30  56534 0.16762 .00009 0.32705 .00009  0.038697 0.000019
170         //   13  8 31  56535 0.16669 .00010 0.32564 .00010  0.038471 0.000019
171         //   13  9  1  56536 0.16592 .00009 0.32410 .00010  0.038206 0.000024
172         //   13  9  2  56537 0.16557 .00009 0.32270 .00009  0.037834 0.000024
173         //   13  9  3  56538 0.16532 .00009 0.32147 .00010  0.037351 0.000024
174         //   13  9  4  56539 0.16488 .00009 0.32044 .00010  0.036756 0.000023
175         //   13  9  5  56540 0.16435 .00009 0.31948 .00009  0.036036 0.000024
176         EOP_RAPID_SERVICE("^ *COMBINED EARTH ORIENTATION PARAMETERS: *$",
177                           LINE_START_REGEXP +
178                           STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
179                           STORED_MJD_FIELD +
180                           STORED_REAL_FIELD + IGNORED_REAL_FIELD +
181                           STORED_REAL_FIELD + IGNORED_REAL_FIELD +
182                           STORED_REAL_FIELD + IGNORED_REAL_FIELD +
183                           LINE_END_REGEXP),
184 
185         /** Earth Orientation Parameters final values. */
186         // the first bulletin A of each month also includes final values for the
187         // period covering from day 2 of month m-2 to day 1 of month m-1.
188         //                                IERS Final Values
189         //                                 MJD        x        y      UT1-UTC
190         //                                            "        "         s
191         //             13  7  2           56475    0.1441   0.3901   0.05717
192         //             13  7  3           56476    0.1457   0.3895   0.05716
193         //             13  7  4           56477    0.1467   0.3887   0.05728
194         //             13  7  5           56478    0.1477   0.3875   0.05755
195         //             13  7  6           56479    0.1490   0.3862   0.05793
196         //             13  7  7           56480    0.1504   0.3849   0.05832
197         //             13  7  8           56481    0.1516   0.3835   0.05858
198         //             13  7  9           56482    0.1530   0.3822   0.05877
199         EOP_FINAL_VALUES("^ *IERS Final Values *$",
200                          LINE_START_REGEXP +
201                          STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
202                          STORED_MJD_FIELD +
203                          STORED_REAL_FIELD +
204                          STORED_REAL_FIELD +
205                          STORED_REAL_FIELD +
206                          LINE_END_REGEXP),
207 
208         /** Earth Orientation Parameters prediction. */
209         // section 3 always contain prediction data without error fields
210         //
211         //         PREDICTIONS:
212         //         The following formulas will not reproduce the predictions given below,
213         //         but may be used to extend the predictions beyond the end of this table.
214         //
215         //         x =  0.0969 + 0.1110 cos A - 0.0103 sin A - 0.0435 cos C - 0.0171 sin C
216         //         y =  0.3457 - 0.0061 cos A - 0.1001 sin A - 0.0171 cos C + 0.0435 sin C
217         //            UT1-UTC = -0.0052 - 0.00104 (MJD - 56548) - (UT2-UT1)
218         //
219         //         where A = 2*pi*(MJD-56540)/365.25 and C = 2*pi*(MJD-56540)/435.
220         //
221         //            TAI-UTC(MJD 56541) = 35.0
222         //         The accuracy may be estimated from the expressions:
223         //         S x,y = 0.00068 (MJD-56540)**0.80   S t = 0.00025 (MJD-56540)**0.75
224         //         Estimated accuracies are:  Predictions     10 d   20 d   30 d   40 d
225         //                                    Polar coord's  0.004  0.007  0.010  0.013
226         //                                    UT1-UTC        0.0014 0.0024 0.0032 0.0040
227         //
228         //                       MJD      x(arcsec)   y(arcsec)   UT1-UTC(sec)
229         //          2013  9  6  56541       0.1638      0.3185      0.03517
230         //          2013  9  7  56542       0.1633      0.3175      0.03420
231         //          2013  9  8  56543       0.1628      0.3164      0.03322
232         //          2013  9  9  56544       0.1623      0.3153      0.03229
233         //          2013  9 10  56545       0.1618      0.3142      0.03144
234         //          2013  9 11  56546       0.1612      0.3131      0.03071
235         //          2013  9 12  56547       0.1607      0.3119      0.03008
236         EOP_PREDICTION("^ *PREDICTIONS: *$",
237                        LINE_START_REGEXP +
238                        STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
239                        STORED_MJD_FIELD +
240                        STORED_REAL_FIELD +
241                        STORED_REAL_FIELD +
242                        STORED_REAL_FIELD +
243                        LINE_END_REGEXP),
244 
245         /** Pole offsets, IAU-1980. */
246         // section 4 may contain rapid service pole offset series including error fields
247         //        CELESTIAL POLE OFFSET SERIES:
248         //                             NEOS Celestial Pole Offset Series
249         //                         MJD      dpsi    error     deps    error
250         //                                          (msec. of arc)
251         //                        56519   -87.47     0.13   -12.96     0.08
252         //                        56520   -87.72     0.13   -13.20     0.08
253         //                        56521   -87.79     0.19   -13.56     0.11
254         POLE_OFFSETS_IAU_1980_RAPID_SERVICE("^ *NEOS Celestial Pole Offset Series *$",
255                                             LINE_START_REGEXP +
256                                             STORED_MJD_FIELD +
257                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
258                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
259                                             LINE_END_REGEXP),
260 
261         /** Pole offsets, IAU-1980 final values. */
262         // the first bulletin A of each month also includes final values for the
263         // period covering from day 2 of month m-2 to day 1 of month m-1.
264         //                    IERS Celestial Pole Offset Final Series
265         //                          MJD          dpsi      deps
266         //                                       (msec. of arc)
267         //                         56475       -81.0     -13.3
268         //                         56476       -81.2     -13.4
269         //                         56477       -81.6     -13.4
270         //                         56478       -82.2     -13.5
271         //                         56479       -82.5     -13.6
272         //                         56480       -82.5     -13.7
273         POLE_OFFSETS_IAU_1980_FINAL_VALUES("^ *IERS Celestial Pole Offset Final Series *$",
274                                            LINE_START_REGEXP +
275                                            STORED_MJD_FIELD +
276                                            STORED_REAL_FIELD +
277                                            STORED_REAL_FIELD +
278                                            LINE_END_REGEXP),
279 
280         /** Pole offsets, IAU-2000. */
281         // the format for the IAU-2000 series is similar, but the meanings of the fields
282         // are different
283         //                       IAU2000A Celestial Pole Offset Series
284         //                        MJD      dX     error     dY     error
285         //                                      (msec. of arc)
286         //                       56519   -0.246   0.052   -0.223   0.080
287         //                       56520   -0.239   0.052   -0.248   0.080
288         //                       56521   -0.224   0.076   -0.277   0.110
289         POLE_OFFSETS_IAU_2000_RAPID_SERVICE("^ *IAU2000A Celestial Pole Offset Series *$",
290                                             LINE_START_REGEXP +
291                                             STORED_MJD_FIELD +
292                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
293                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
294                                             LINE_END_REGEXP),
295 
296         /** Pole offsets, IAU-2000 final values. */
297         // the format for the IAU-2000 series is similar, but the meanings of the fields
298         // are different
299         //                   IAU2000A Celestial Pole Offset Final Series
300         //                            MJD     dX         dY
301         //                            (msec. of arc)
302         //                          56475     0.00      -0.28
303         //                          56476    -0.06      -0.29
304         //                          56477    -0.07      -0.27
305         //                          56478    -0.12      -0.33
306         //                          56479    -0.12      -0.33
307         //                          56480    -0.13      -0.36
308         POLE_OFFSETS_IAU_2000_FINAL_VALUES("^ *IAU2000A Celestial Pole Offset Final Series *$",
309                                            LINE_START_REGEXP +
310                                            STORED_MJD_FIELD +
311                                            STORED_REAL_FIELD +
312                                            STORED_REAL_FIELD +
313                                            LINE_END_REGEXP);
314 
315         /** Header pattern. */
316         private final Pattern header;
317 
318         /** Data pattern. */
319         private final Pattern data;
320 
321         /** Simple constructor.
322          * @param headerRegExp regular expression for header
323          * @param dataRegExp regular expression for data
324          */
325         Section(final String headerRegExp, final String dataRegExp) {
326             this.header = Pattern.compile(headerRegExp);
327             this.data   = Pattern.compile(dataRegExp);
328         }
329 
330         /** Check if a line matches the section header.
331          * @param line line to check
332          * @return true if the line matches the header
333          */
334         public boolean matchesHeader(final String line) {
335             return header.matcher(line).matches();
336         }
337 
338         /** Get the data fields from a line.
339          * @param line line to parse
340          * @return extracted fields, or null if line does not match data format
341          */
342         public String[] getFields(final String line) {
343             final Matcher matcher = data.matcher(line);
344             if (matcher.matches()) {
345                 final String[] fields = new String[matcher.groupCount()];
346                 for (int i = 0; i < fields.length; ++i) {
347                     fields[i] = matcher.group(i + 1);
348                 }
349                 return fields;
350             } else {
351                 return null;
352             }
353         }
354 
355     }
356 
357     /** Build a loader for IERS bulletins A files.
358      * @param supportedNames regular expression for supported files names
359      * @param manager provides access to the bulletin A files.
360      * @param utcSupplier UTC time scale.
361      */
362     BulletinAFilesLoader(final String supportedNames,
363                          final DataProvidersManager manager,
364                          final Supplier<TimeScale> utcSupplier) {
365         super(supportedNames, manager, utcSupplier);
366     }
367 
368     /** {@inheritDoc} */
369     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
370                             final SortedSet<EOPEntry> history) {
371         final Parser parser = new Parser();
372         this.feed(parser);
373         parser.fill(history);
374     }
375 
376     /** Internal class performing the parsing. */
377     private class Parser implements DataLoader {
378 
379         /** Map for xp, yp, dut1 fields read in different sections. */
380         private final Map<Integer, double[]> eopFieldsMap;
381 
382         /** Map for pole offsets fields read in different sections. */
383         private final Map<Integer, double[]> poleOffsetsFieldsMap;
384 
385         /** Map for EOP data type. */
386         private final Map<Integer, EopDataType> eopDataTypeMap;
387 
388         /** Configuration for ITRF versions. */
389         private final ItrfVersionProvider itrfVersionProvider;
390 
391         /** ITRF version configuration. */
392         private ITRFVersionLoader.ITRFVersionConfiguration configuration;
393 
394         /** File name. */
395         private String fileName;
396 
397         /** Current line number. */
398         private int lineNumber;
399 
400         /** Current line. */
401         private String line;
402 
403         /** Earliest parsed data. */
404         private int mjdMin;
405 
406         /** Latest parsed data. */
407         private int mjdMax;
408 
409         /** First MJD parsed in current file. */
410         private int firstMJD;
411 
412         /** Simple constructor.
413          */
414         Parser() {
415             this.eopFieldsMap         = new HashMap<>();
416             this.poleOffsetsFieldsMap = new HashMap<>();
417             this.eopDataTypeMap       = new HashMap<>();
418             this.itrfVersionProvider  = new ITRFVersionLoader(
419                     ITRFVersionLoader.SUPPORTED_NAMES,
420                     getDataProvidersManager());
421             this.lineNumber           = 0;
422             this.mjdMin               = Integer.MAX_VALUE;
423             this.mjdMax               = Integer.MIN_VALUE;
424             this.firstMJD             = -1;
425         }
426 
427         /** {@inheritDoc} */
428         public boolean stillAcceptsData() {
429             return true;
430         }
431 
432         /** {@inheritDoc} */
433         public void loadData(final InputStream input, final String name)
434             throws IOException {
435 
436             this.configuration = null;
437             this.fileName      = name;
438 
439             // set up a reader for line-oriented bulletin A files
440             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
441                 lineNumber =  0;
442                 firstMJD   = -1;
443 
444                 // loop over sections
445                 final List<Section> remaining = new ArrayList<>(Arrays.asList(Section.values()));
446                 for (Section section = nextSection(remaining, reader);
447                      section != null;
448                      section = nextSection(remaining, reader)) {
449 
450                     final EopDataType eopDataType;
451                     switch (section) {
452                         case EOP_FINAL_VALUES:
453                         case POLE_OFFSETS_IAU_1980_FINAL_VALUES:
454                         case POLE_OFFSETS_IAU_2000_FINAL_VALUES:
455                             eopDataType = EopDataType.FINAL;
456                             break;
457                         case EOP_RAPID_SERVICE:
458                         case POLE_OFFSETS_IAU_1980_RAPID_SERVICE:
459                         case POLE_OFFSETS_IAU_2000_RAPID_SERVICE:
460                             eopDataType = EopDataType.RAPID;
461                             break;
462                         case EOP_PREDICTION:
463                             eopDataType = EopDataType.PREDICTED;
464                             break;
465                         default:
466                             eopDataType = EopDataType.UNKNOWN;
467                     }
468 
469                     switch (section) {
470                         case EOP_RAPID_SERVICE :
471                         case EOP_FINAL_VALUES  :
472                         case EOP_PREDICTION    :
473                             loadXYDT(section, reader, name, eopDataType);
474                             break;
475                         case POLE_OFFSETS_IAU_1980_RAPID_SERVICE :
476                         case POLE_OFFSETS_IAU_1980_FINAL_VALUES  :
477                             loadPoleOffsets(section, false, reader, name, eopDataType);
478                             break;
479                         case POLE_OFFSETS_IAU_2000_RAPID_SERVICE :
480                         case POLE_OFFSETS_IAU_2000_FINAL_VALUES  :
481                             loadPoleOffsets(section, true, reader, name, eopDataType);
482                             break;
483                         default :
484                             // this should never happen
485                             throw new OrekitInternalError(null);
486                     }
487 
488                     // remove the already parsed section from the list
489                     remaining.remove(section);
490 
491                 }
492 
493                 // check that the mandatory sections have been parsed
494                 if (remaining.contains(Section.EOP_RAPID_SERVICE) ||
495                     remaining.contains(Section.EOP_PREDICTION) ||
496                     (remaining.contains(Section.POLE_OFFSETS_IAU_1980_RAPID_SERVICE) ^
497                      remaining.contains(Section.POLE_OFFSETS_IAU_2000_RAPID_SERVICE)) ||
498                     (remaining.contains(Section.POLE_OFFSETS_IAU_1980_FINAL_VALUES) ^
499                      remaining.contains(Section.POLE_OFFSETS_IAU_2000_FINAL_VALUES))) {
500                     throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
501                 }
502 
503             }
504         }
505 
506         /** Fill EOP history obtained after reading several files.
507          * @param history history to fill up
508          */
509         public void fill(final SortedSet<EOPEntry> history) {
510 
511             double[] currentEOP = null;
512             double[] nextEOP    = eopFieldsMap.get(mjdMin);
513             for (int mjd = mjdMin; mjd <= mjdMax; ++mjd) {
514 
515                 final AbsoluteDate mjdDate    = AbsoluteDate.createMJDDate(mjd, 0, getUtc());
516                 final double[] currentPole    = poleOffsetsFieldsMap.get(mjd);
517                 final EopDataType eopDataType = eopDataTypeMap.get(mjd);
518 
519                 currentEOP = nextEOP;
520                 nextEOP    = eopFieldsMap.get(mjd + 1);
521 
522                 if (currentEOP == null) {
523                     if (currentPole != null) {
524                         // we have only pole offsets for this date
525                         if (configuration == null || !configuration.isValid(mjd)) {
526                             // get a configuration for current name and date range
527                             configuration = itrfVersionProvider.getConfiguration(fileName, mjd);
528                         }
529                         history.add(new EOPEntry(mjd,
530                                                  0.0, Double.NaN, 0.0, 0.0, Double.NaN, Double.NaN,
531                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[1]),
532                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[2]),
533                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[3]),
534                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[4]),
535                                                  configuration.getVersion(),
536                                                  mjdDate, eopDataType));
537                     }
538                 } else {
539 
540                     if (configuration == null || !configuration.isValid(mjd)) {
541                         // get a configuration for current name and date range
542                         configuration = itrfVersionProvider.getConfiguration(fileName, mjd);
543                     }
544                     if (currentPole == null) {
545                         // we have only EOP for this date
546                         history.add(new EOPEntry(mjd,
547                                                  currentEOP[3], Double.NaN,
548                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[1]),
549                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[2]),
550                                                  Double.NaN, Double.NaN,
551                                                  0.0, 0.0, 0.0, 0.0,
552                                                  configuration.getVersion(),
553                                                  mjdDate, eopDataType));
554                     } else {
555                         // we have complete data
556                         history.add(new EOPEntry(mjd,
557                                                  currentEOP[3], Double.NaN,
558                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[1] ),
559                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[2] ),
560                                                  Double.NaN, Double.NaN,
561                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[1]),
562                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[2]),
563                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[3]),
564                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[4]),
565                                                  configuration.getVersion(),
566                                                  mjdDate, eopDataType));
567                     }
568                 }
569 
570             }
571 
572         }
573 
574         /** Skip to next section header.
575          * @param sections sections to check for
576          * @param reader reader from where file content is obtained
577          * @return the next section or null if no section is found until end of file
578          * @exception IOException if data can't be read
579          */
580         private Section nextSection(final List<Section> sections,
581                                     final BufferedReader reader)
582             throws IOException {
583 
584             for (line = reader.readLine(); line != null; line = reader.readLine()) {
585                 ++lineNumber;
586                 for (Section section : sections) {
587                     if (section.matchesHeader(line)) {
588                         return section;
589                     }
590                 }
591             }
592 
593             // we have reached end of file and not found a matching section header
594             return null;
595 
596         }
597 
598         /** Read X, Y, UT1-UTC.
599          * @param section section to parse
600          * @param reader reader from where file content is obtained
601          * @param name name of the file (or zip entry)
602          * @param eopDataType EOP data type
603          * @exception IOException if data can't be read
604          */
605         private void loadXYDT(final Section section, final BufferedReader reader, final String name, final EopDataType eopDataType)
606             throws IOException {
607 
608             boolean inValuesPart = false;
609             for (line = reader.readLine(); line != null; line = reader.readLine()) {
610                 lineNumber++;
611                 final String[] fields = section.getFields(line);
612                 if (fields != null) {
613 
614                     // we are within the values part
615                     inValuesPart = true;
616 
617                     // this is a data line, build an entry from the extracted fields
618                     final int year  = Integer.parseInt(fields[0]);
619                     final int month = Integer.parseInt(fields[1]);
620                     final int day   = Integer.parseInt(fields[2]);
621                     final int mjd   = Integer.parseInt(fields[3]);
622                     final DateComponents dc = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd);
623                     if ((dc.getYear() % 100) != (year % 100) ||
624                          dc.getMonth() != month ||
625                          dc.getDay() != day) {
626                         throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
627                                                   name, year, month, day, mjd);
628                     }
629                     mjdMin = FastMath.min(mjdMin, mjd);
630                     mjdMax = FastMath.max(mjdMax, mjd);
631                     if (firstMJD < 0) {
632                         // store the first mjd parsed
633                         firstMJD = mjd;
634                     }
635 
636                     // get the entry at the same date if it was already parsed
637                     final double[] eop;
638                     if (eopFieldsMap.containsKey(mjd)) {
639                         eop = eopFieldsMap.get(mjd);
640                     } else {
641                         eop = new double[4];
642                         eopFieldsMap.put(mjd, eop);
643                     }
644 
645                     if (eop[0] <= firstMJD) {
646                         // either it is the first time we parse this date (eop[0] = 0),
647                         // or the new parsed data is from a more recent file
648                         // in both case, we should update the array
649                         eop[0] = firstMJD;
650                         eop[1] = Double.parseDouble(fields[4]);
651                         eop[2] = Double.parseDouble(fields[5]);
652                         eop[3] = Double.parseDouble(fields[6]);
653                         eopDataTypeMap.put(mjd, eopDataType);
654                     }
655 
656                 } else if (inValuesPart) {
657                     // we leave values part
658                     return;
659                 }
660             }
661 
662             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
663                                       name, lineNumber);
664 
665         }
666 
667         /** Read EOP data.
668          * @param section section to parse
669          * @param isNonRotatingOrigin if true, the file contain Non-Rotating Origin nutation corrections
670          * @param reader reader from where file content is obtained
671          * @param name name of the file (or zip entry)
672          * @param eopDataType EOP data type
673          * @exception IOException if data can't be read
674          */
675         private void loadPoleOffsets(final Section section, final boolean isNonRotatingOrigin,
676                                      final BufferedReader reader, final String name, final EopDataType eopDataType)
677             throws IOException {
678 
679             boolean inValuesPart = false;
680             for (line = reader.readLine(); line != null; line = reader.readLine()) {
681                 lineNumber++;
682                 final String[] fields = section.getFields(line);
683                 if (fields != null) {
684 
685                     // we are within the values part
686                     inValuesPart = true;
687 
688                     // this is a data line, build an entry from the extracted fields
689                     final int mjd = Integer.parseInt(fields[0]);
690                     mjdMin = FastMath.min(mjdMin, mjd);
691                     mjdMax = FastMath.max(mjdMax, mjd);
692 
693                     // get the entry at the same date if it was already parsed
694                     final double[] pole;
695                     if (poleOffsetsFieldsMap.containsKey(mjd)) {
696                         pole = poleOffsetsFieldsMap.get(mjd);
697                     } else {
698                         pole = new double[5];
699                         poleOffsetsFieldsMap.put(mjd, pole);
700                     }
701 
702                     if (pole[0] <= firstMJD) {
703                         // either it is the first time we parse this date (pole[0] = 0),
704                         // or the new parsed data is from a more recent file
705                         // in both case, we should update the array
706                         pole[0] = firstMJD;
707                         if (isNonRotatingOrigin) {
708                             pole[1] = Double.parseDouble(fields[1]);
709                             pole[2] = Double.parseDouble(fields[2]);
710                         } else {
711                             pole[3] = Double.parseDouble(fields[1]);
712                             pole[4] = Double.parseDouble(fields[2]);
713                         }
714                         eopDataTypeMap.put(mjd, eopDataType);
715                     }
716 
717                 } else if (inValuesPart) {
718                     // we leave values part
719                     return;
720                 }
721             }
722 
723             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
724                                       name, lineNumber);
725 
726         }
727 
728     }
729 
730 }