BulletinAFilesLoader.java

  1. /* Copyright 2002-2022 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. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.SortedSet;
  29. import java.util.function.Supplier;
  30. import java.util.regex.Matcher;
  31. import java.util.regex.Pattern;

  32. import org.hipparchus.util.FastMath;
  33. import org.orekit.data.DataLoader;
  34. import org.orekit.data.DataProvidersManager;
  35. import org.orekit.errors.OrekitException;
  36. import org.orekit.errors.OrekitInternalError;
  37. import org.orekit.errors.OrekitMessages;
  38. import org.orekit.time.AbsoluteDate;
  39. import org.orekit.time.DateComponents;
  40. import org.orekit.time.TimeScale;
  41. import org.orekit.utils.IERSConventions;
  42. import org.orekit.utils.units.UnitsConverter;

  43. /** Loader for bulletin A files.
  44.  * <p>Bulletin A files contain {@link EOPEntry
  45.  * Earth Orientation Parameters} for a few days periods, they
  46.  * correspond to rapid data estimations, suitable for near-real time
  47.  * and prediction purposes. Prediction series are only available for
  48.  * pole motion xp, yp and UT1-UTC, they are not available for
  49.  * pole offsets (Δδψ/Δδε and x/y).</p>
  50.  * <p>A bulletin A published on Modified Julian Day mjd (nominally a
  51.  * Thursday) will generally contain:
  52.  * </p>
  53.  * <ul>
  54.  *   <li>rapid service xp, yp and UT1-UTC data from mjd-6 to mjd</li>
  55.  *   <li>prediction xp, yp and UT1-UTC data from mjd+1 to mjd+365</li>
  56.  *   <li>if it is first bulletin of month m, final values xp, yp and
  57.  *       UT1-UTC data from day 2 of month m-2 to day 1 of month m-1</li>
  58.  *   <li>rapid service pole offsets Δδψ/Δδε and x/y if available, for some
  59.  *       varying period somewhere from mjd-30 to mjd-10 (see below)</li>
  60.  *   <li>if it is first bulletin of month m, final values pole offsets
  61.  *       Δδψ/Δδε and x/y data from day 2 of month m-2 to day 1 of month
  62.  *       m-1</li>
  63.  * </ul>
  64.  * <p>
  65.  * There are some discrepancies in the rapid service time range above,
  66.  * mainly when the nominal publication Thursday corresponds to holidays.
  67.  * In this case a bulletin may be published the day before and have a 6
  68.  * days span only for rapid data, and a later bulletin will have an 8 days
  69.  * span to recover the normal schedule. This occurred for bulletin A Vol.
  70.  * XVIII No. 047, bulletin A Vol. XVIII No. 048, bulletin A Vol. XXI No.
  71.  * 052 and bulletin A Vol. XXII No. 001.
  72.  * </p>
  73.  * <p>Rapid service for pole offsets appears irregular. As extreme examples
  74.  * bulletin A Vol. XXVI No. 037 from 2013-09-12 contained 15 entries
  75.  * for pole offsets, from mjd-22 to mjd-8, bulletin A Vol. XXVI No. 039
  76.  * from 2013-09-26 contained only 3 entries for pole offsets, from mjd-15
  77.  * to mjd-13, and bulletin A Vol. XXVI No. 040 from 2013-10-03 contained no
  78.  * rapid service pole offsets at all, it contained only final values. Despite
  79.  * this irregularity, rapid service data is continuous over consecutive files,
  80.  * so the mean number of entries is 7 as the files are published on a weekly
  81.  * basis.
  82.  * </p>
  83.  * <p>
  84.  * There are no prediction data for pole offsets.
  85.  * </p>
  86.  * <p>
  87.  * This loader reads both the rapid service, the prediction and the final
  88.  * values parts. As successive files have overlaps between all these sections,
  89.  * values extracted from latest files (with respect to the covered dates)
  90.  * override values extracted from earlier files, regardless of the files
  91.  * reading order. If numerous bulletins A covering more than one year are read,
  92.  * one particular date will typically appear in the prediction section of
  93.  * 52 or 53 files, then in the rapid data section of one file, then it will
  94.  * be missing in a few files, and will finally appear a last time in the
  95.  * final values sections of a last file. In this case, the value retained
  96.  * will be the one extracted from the final values section in the more
  97.  * recent file.
  98.  * </p>
  99.  * <p>
  100.  * If only one bulletin A file is read and it correspond to the first bulletin
  101.  * of a month, it will have a roughly one month wide hole between the
  102.  * final data and the rapid data. This hole will trigger an error as EOP
  103.  * continuity is checked by default for at most 5 days holes. In this case,
  104.  * users should call something like {@link Frames#setEOPContinuityThreshold(double)
  105.  * FramesFactory.setEOPContinuityThreshold(Constants.JULIAN_YEAR)} to prevent
  106.  * the error to be triggered.
  107.  * </p>
  108.  * <p>The bulletin A files are recognized thanks to their base names,
  109.  * which must match the pattern <code>bulletina-xxxx-###.txt</code>,
  110.  * (or the same ending with <code>.gz</code> for gzip-compressed files)
  111.  * where x stands for a roman numeral character and # stands for a digit
  112.  * character.</p>
  113.  * <p>
  114.  * This class is immutable and hence thread-safe
  115.  * </p>
  116.  * @author Luc Maisonobe
  117.  * @since 7.0
  118.  */
  119. class BulletinAFilesLoader extends AbstractEopLoader implements EOPHistoryLoader {

  120.     /** Regular expression matching blanks at start of line. */
  121.     private static final String LINE_START_REGEXP     = "^\\p{Blank}+";

  122.     /** Regular expression matching blanks at end of line. */
  123.     private static final String LINE_END_REGEXP       = "\\p{Blank}*$";

  124.     /** Regular expression matching integers. */
  125.     private static final String INTEGER_REGEXP        = "[-+]?\\p{Digit}+";

  126.     /** Regular expression matching real numbers. */
  127.     private static final String REAL_REGEXP           = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";

  128.     /** Regular expression matching an integer field to store. */
  129.     private static final String STORED_INTEGER_FIELD  = "\\p{Blank}*(" + INTEGER_REGEXP + ")";

  130.     /** regular expression matching a Modified Julian Day field to store. */
  131.     private static final String STORED_MJD_FIELD      = "\\p{Blank}+(\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})";

  132.     /** Regular expression matching a real field to store. */
  133.     private static final String STORED_REAL_FIELD     = "\\p{Blank}+(" + REAL_REGEXP + ")";

  134.     /** Regular expression matching a real field to ignore. */
  135.     private static final String IGNORED_REAL_FIELD    = "\\p{Blank}+" + REAL_REGEXP;

  136.     /** Enum for files sections, in expected order.
  137.      * <p>The bulletin A weekly data files contain several sections,
  138.      * each introduced with some fixed header text and followed by tabular data.
  139.      * </p>
  140.      */
  141.     private enum Section {

  142.         /** Earth Orientation Parameters rapid service. */
  143.         // section 2 always contain rapid service data including error fields
  144.         //      COMBINED EARTH ORIENTATION PARAMETERS:
  145.         //
  146.         //                              IERS Rapid Service
  147.         //              MJD      x    error     y    error   UT1-UTC   error
  148.         //                       "      "       "      "        s        s
  149.         //   13  8 30  56534 0.16762 .00009 0.32705 .00009  0.038697 0.000019
  150.         //   13  8 31  56535 0.16669 .00010 0.32564 .00010  0.038471 0.000019
  151.         //   13  9  1  56536 0.16592 .00009 0.32410 .00010  0.038206 0.000024
  152.         //   13  9  2  56537 0.16557 .00009 0.32270 .00009  0.037834 0.000024
  153.         //   13  9  3  56538 0.16532 .00009 0.32147 .00010  0.037351 0.000024
  154.         //   13  9  4  56539 0.16488 .00009 0.32044 .00010  0.036756 0.000023
  155.         //   13  9  5  56540 0.16435 .00009 0.31948 .00009  0.036036 0.000024
  156.         EOP_RAPID_SERVICE("^ *COMBINED EARTH ORIENTATION PARAMETERS: *$",
  157.                           LINE_START_REGEXP +
  158.                           STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
  159.                           STORED_MJD_FIELD +
  160.                           STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  161.                           STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  162.                           STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  163.                           LINE_END_REGEXP),

  164.         /** Earth Orientation Parameters final values. */
  165.         // the first bulletin A of each month also includes final values for the
  166.         // period covering from day 2 of month m-2 to day 1 of month m-1.
  167.         //                                IERS Final Values
  168.         //                                 MJD        x        y      UT1-UTC
  169.         //                                            "        "         s
  170.         //             13  7  2           56475    0.1441   0.3901   0.05717
  171.         //             13  7  3           56476    0.1457   0.3895   0.05716
  172.         //             13  7  4           56477    0.1467   0.3887   0.05728
  173.         //             13  7  5           56478    0.1477   0.3875   0.05755
  174.         //             13  7  6           56479    0.1490   0.3862   0.05793
  175.         //             13  7  7           56480    0.1504   0.3849   0.05832
  176.         //             13  7  8           56481    0.1516   0.3835   0.05858
  177.         //             13  7  9           56482    0.1530   0.3822   0.05877
  178.         EOP_FINAL_VALUES("^ *IERS Final Values *$",
  179.                          LINE_START_REGEXP +
  180.                          STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
  181.                          STORED_MJD_FIELD +
  182.                          STORED_REAL_FIELD +
  183.                          STORED_REAL_FIELD +
  184.                          STORED_REAL_FIELD +
  185.                          LINE_END_REGEXP),

  186.         /** Earth Orientation Parameters prediction. */
  187.         // section 3 always contain prediction data without error fields
  188.         //
  189.         //         PREDICTIONS:
  190.         //         The following formulas will not reproduce the predictions given below,
  191.         //         but may be used to extend the predictions beyond the end of this table.
  192.         //
  193.         //         x =  0.0969 + 0.1110 cos A - 0.0103 sin A - 0.0435 cos C - 0.0171 sin C
  194.         //         y =  0.3457 - 0.0061 cos A - 0.1001 sin A - 0.0171 cos C + 0.0435 sin C
  195.         //            UT1-UTC = -0.0052 - 0.00104 (MJD - 56548) - (UT2-UT1)
  196.         //
  197.         //         where A = 2*pi*(MJD-56540)/365.25 and C = 2*pi*(MJD-56540)/435.
  198.         //
  199.         //            TAI-UTC(MJD 56541) = 35.0
  200.         //         The accuracy may be estimated from the expressions:
  201.         //         S x,y = 0.00068 (MJD-56540)**0.80   S t = 0.00025 (MJD-56540)**0.75
  202.         //         Estimated accuracies are:  Predictions     10 d   20 d   30 d   40 d
  203.         //                                    Polar coord's  0.004  0.007  0.010  0.013
  204.         //                                    UT1-UTC        0.0014 0.0024 0.0032 0.0040
  205.         //
  206.         //                       MJD      x(arcsec)   y(arcsec)   UT1-UTC(sec)
  207.         //          2013  9  6  56541       0.1638      0.3185      0.03517
  208.         //          2013  9  7  56542       0.1633      0.3175      0.03420
  209.         //          2013  9  8  56543       0.1628      0.3164      0.03322
  210.         //          2013  9  9  56544       0.1623      0.3153      0.03229
  211.         //          2013  9 10  56545       0.1618      0.3142      0.03144
  212.         //          2013  9 11  56546       0.1612      0.3131      0.03071
  213.         //          2013  9 12  56547       0.1607      0.3119      0.03008
  214.         EOP_PREDICTION("^ *PREDICTIONS: *$",
  215.                        LINE_START_REGEXP +
  216.                        STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
  217.                        STORED_MJD_FIELD +
  218.                        STORED_REAL_FIELD +
  219.                        STORED_REAL_FIELD +
  220.                        STORED_REAL_FIELD +
  221.                        LINE_END_REGEXP),

  222.         /** Pole offsets, IAU-1980. */
  223.         // section 4 may contain rapid service pole offset series including error fields
  224.         //        CELESTIAL POLE OFFSET SERIES:
  225.         //                             NEOS Celestial Pole Offset Series
  226.         //                         MJD      dpsi    error     deps    error
  227.         //                                          (msec. of arc)
  228.         //                        56519   -87.47     0.13   -12.96     0.08
  229.         //                        56520   -87.72     0.13   -13.20     0.08
  230.         //                        56521   -87.79     0.19   -13.56     0.11
  231.         POLE_OFFSETS_IAU_1980_RAPID_SERVICE("^ *NEOS Celestial Pole Offset Series *$",
  232.                                             LINE_START_REGEXP +
  233.                                             STORED_MJD_FIELD +
  234.                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  235.                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  236.                                             LINE_END_REGEXP),

  237.         /** Pole offsets, IAU-1980 final values. */
  238.         // the first bulletin A of each month also includes final values for the
  239.         // period covering from day 2 of month m-2 to day 1 of month m-1.
  240.         //                    IERS Celestial Pole Offset Final Series
  241.         //                          MJD          dpsi      deps
  242.         //                                       (msec. of arc)
  243.         //                         56475       -81.0     -13.3
  244.         //                         56476       -81.2     -13.4
  245.         //                         56477       -81.6     -13.4
  246.         //                         56478       -82.2     -13.5
  247.         //                         56479       -82.5     -13.6
  248.         //                         56480       -82.5     -13.7
  249.         POLE_OFFSETS_IAU_1980_FINAL_VALUES("^ *IERS Celestial Pole Offset Final Series *$",
  250.                                            LINE_START_REGEXP +
  251.                                            STORED_MJD_FIELD +
  252.                                            STORED_REAL_FIELD +
  253.                                            STORED_REAL_FIELD +
  254.                                            LINE_END_REGEXP),

  255.         /** Pole offsets, IAU-2000. */
  256.         // the format for the IAU-2000 series is similar, but the meanings of the fields
  257.         // are different
  258.         //                       IAU2000A Celestial Pole Offset Series
  259.         //                        MJD      dX     error     dY     error
  260.         //                                      (msec. of arc)
  261.         //                       56519   -0.246   0.052   -0.223   0.080
  262.         //                       56520   -0.239   0.052   -0.248   0.080
  263.         //                       56521   -0.224   0.076   -0.277   0.110
  264.         POLE_OFFSETS_IAU_2000_RAPID_SERVICE("^ *IAU2000A Celestial Pole Offset Series *$",
  265.                                             LINE_START_REGEXP +
  266.                                             STORED_MJD_FIELD +
  267.                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  268.                                             STORED_REAL_FIELD + IGNORED_REAL_FIELD +
  269.                                             LINE_END_REGEXP),

  270.         /** Pole offsets, IAU-2000 final values. */
  271.         // the format for the IAU-2000 series is similar, but the meanings of the fields
  272.         // are different
  273.         //                   IAU2000A Celestial Pole Offset Final Series
  274.         //                            MJD     dX         dY
  275.         //                            (msec. of arc)
  276.         //                          56475     0.00      -0.28
  277.         //                          56476    -0.06      -0.29
  278.         //                          56477    -0.07      -0.27
  279.         //                          56478    -0.12      -0.33
  280.         //                          56479    -0.12      -0.33
  281.         //                          56480    -0.13      -0.36
  282.         POLE_OFFSETS_IAU_2000_FINAL_VALUES("^ *IAU2000A Celestial Pole Offset Final Series *$",
  283.                                            LINE_START_REGEXP +
  284.                                            STORED_MJD_FIELD +
  285.                                            STORED_REAL_FIELD +
  286.                                            STORED_REAL_FIELD +
  287.                                            LINE_END_REGEXP);

  288.         /** Header pattern. */
  289.         private final Pattern header;

  290.         /** Data pattern. */
  291.         private final Pattern data;

  292.         /** Simple constructor.
  293.          * @param headerRegExp regular expression for header
  294.          * @param dataRegExp regular expression for data
  295.          */
  296.         Section(final String headerRegExp, final String dataRegExp) {
  297.             this.header = Pattern.compile(headerRegExp);
  298.             this.data   = Pattern.compile(dataRegExp);
  299.         }

  300.         /** Check if a line matches the section header.
  301.          * @param line line to check
  302.          * @return true if the line matches the header
  303.          */
  304.         public boolean matchesHeader(final String line) {
  305.             return header.matcher(line).matches();
  306.         }

  307.         /** Get the data fields from a line.
  308.          * @param line line to parse
  309.          * @return extracted fields, or null if line does not match data format
  310.          */
  311.         public String[] getFields(final String line) {
  312.             final Matcher matcher = data.matcher(line);
  313.             if (matcher.matches()) {
  314.                 final String[] fields = new String[matcher.groupCount()];
  315.                 for (int i = 0; i < fields.length; ++i) {
  316.                     fields[i] = matcher.group(i + 1);
  317.                 }
  318.                 return fields;
  319.             } else {
  320.                 return null;
  321.             }
  322.         }

  323.     }

  324.     /** Build a loader for IERS bulletins A files.
  325.      * @param supportedNames regular expression for supported files names
  326.      * @param manager provides access to the bulletin A files.
  327.      * @param utcSupplier UTC time scale.
  328.      */
  329.     BulletinAFilesLoader(final String supportedNames,
  330.                          final DataProvidersManager manager,
  331.                          final Supplier<TimeScale> utcSupplier) {
  332.         super(supportedNames, manager, utcSupplier);
  333.     }

  334.     /** {@inheritDoc} */
  335.     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
  336.                             final SortedSet<EOPEntry> history) {
  337.         final Parser parser = new Parser();
  338.         this.feed(parser);
  339.         parser.fill(history);
  340.     }

  341.     /** Internal class performing the parsing. */
  342.     private class Parser implements DataLoader {

  343.         /** Map for xp, yp, dut1 fields read in different sections. */
  344.         private final Map<Integer, double[]> eopFieldsMap;

  345.         /** Map for pole offsets fields read in different sections. */
  346.         private final Map<Integer, double[]> poleOffsetsFieldsMap;

  347.         /** Configuration for ITRF versions. */
  348.         private final ItrfVersionProvider itrfVersionProvider;

  349.         /** ITRF version configuration. */
  350.         private ITRFVersionLoader.ITRFVersionConfiguration configuration;

  351.         /** File name. */
  352.         private String fileName;

  353.         /** Current line number. */
  354.         private int lineNumber;

  355.         /** Current line. */
  356.         private String line;

  357.         /** Earliest parsed data. */
  358.         private int mjdMin;

  359.         /** Latest parsed data. */
  360.         private int mjdMax;

  361.         /** First MJD parsed in current file. */
  362.         private int firstMJD;

  363.         /** Simple constructor.
  364.          */
  365.         Parser() {
  366.             this.eopFieldsMap         = new HashMap<>();
  367.             this.poleOffsetsFieldsMap = new HashMap<>();
  368.             this.itrfVersionProvider = new ITRFVersionLoader(
  369.                     ITRFVersionLoader.SUPPORTED_NAMES,
  370.                     getDataProvidersManager());
  371.             this.lineNumber           = 0;
  372.             this.mjdMin               = Integer.MAX_VALUE;
  373.             this.mjdMax               = Integer.MIN_VALUE;
  374.             this.firstMJD             = -1;
  375.         }

  376.         /** {@inheritDoc} */
  377.         public boolean stillAcceptsData() {
  378.             return true;
  379.         }

  380.         /** {@inheritDoc} */
  381.         public void loadData(final InputStream input, final String name)
  382.             throws IOException {

  383.             this.configuration = null;
  384.             this.fileName      = name;

  385.             // set up a reader for line-oriented bulletin A files
  386.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  387.                 lineNumber =  0;
  388.                 firstMJD   = -1;

  389.                 // loop over sections
  390.                 final List<Section> remaining = new ArrayList<>(Arrays.asList(Section.values()));
  391.                 for (Section section = nextSection(remaining, reader);
  392.                      section != null;
  393.                      section = nextSection(remaining, reader)) {

  394.                     switch (section) {
  395.                         case EOP_RAPID_SERVICE :
  396.                         case EOP_FINAL_VALUES  :
  397.                         case EOP_PREDICTION    :
  398.                             loadXYDT(section, reader, name);
  399.                             break;
  400.                         case POLE_OFFSETS_IAU_1980_RAPID_SERVICE :
  401.                         case POLE_OFFSETS_IAU_1980_FINAL_VALUES  :
  402.                             loadPoleOffsets(section, false, reader, name);
  403.                             break;
  404.                         case POLE_OFFSETS_IAU_2000_RAPID_SERVICE :
  405.                         case POLE_OFFSETS_IAU_2000_FINAL_VALUES  :
  406.                             loadPoleOffsets(section, true, reader, name);
  407.                             break;
  408.                         default :
  409.                             // this should never happen
  410.                             throw new OrekitInternalError(null);
  411.                     }

  412.                     // remove the already parsed section from the list
  413.                     remaining.remove(section);

  414.                 }

  415.                 // check that the mandatory sections have been parsed
  416.                 if (remaining.contains(Section.EOP_RAPID_SERVICE) ||
  417.                     remaining.contains(Section.EOP_PREDICTION) ||
  418.                     (remaining.contains(Section.POLE_OFFSETS_IAU_1980_RAPID_SERVICE) ^
  419.                      remaining.contains(Section.POLE_OFFSETS_IAU_2000_RAPID_SERVICE)) ||
  420.                     (remaining.contains(Section.POLE_OFFSETS_IAU_1980_FINAL_VALUES) ^
  421.                      remaining.contains(Section.POLE_OFFSETS_IAU_2000_FINAL_VALUES))) {
  422.                     throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  423.                 }

  424.             }
  425.         }

  426.         /** Fill EOP history obtained after reading several files.
  427.          * @param history history to fill up
  428.          */
  429.         public void fill(final SortedSet<EOPEntry> history) {

  430.             double[] currentEOP = null;
  431.             double[] nextEOP    = eopFieldsMap.get(mjdMin);
  432.             for (int mjd = mjdMin; mjd <= mjdMax; ++mjd) {

  433.                 final AbsoluteDate mjdDate = AbsoluteDate.createMJDDate(mjd, 0, getUtc());
  434.                 final double[] currentPole = poleOffsetsFieldsMap.get(mjd);

  435.                 final double[] previousEOP = currentEOP;
  436.                 currentEOP = nextEOP;
  437.                 nextEOP    = eopFieldsMap.get(mjd + 1);

  438.                 if (currentEOP == null) {
  439.                     if (currentPole != null) {
  440.                         // we have only pole offsets for this date
  441.                         if (configuration == null || !configuration.isValid(mjd)) {
  442.                             // get a configuration for current name and date range
  443.                             configuration = itrfVersionProvider.getConfiguration(fileName, mjd);
  444.                         }
  445.                         history.add(new EOPEntry(mjd,
  446.                                                  0.0, 0.0, 0.0, 0.0,
  447.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[1]),
  448.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[2]),
  449.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[3]),
  450.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[4]),
  451.                                                  configuration.getVersion(),
  452.                                                  mjdDate));
  453.                     }
  454.                 } else {

  455.                     // compute LOD as the opposite of the time derivative of UT1-UTC
  456.                     final double lod;
  457.                     if (previousEOP == null) {
  458.                         if (nextEOP == null) {
  459.                             // isolated point
  460.                             lod = 0;
  461.                         } else {
  462.                             // first entry, we use a forward difference
  463.                             lod = currentEOP[3] - nextEOP[3];
  464.                         }
  465.                     } else {
  466.                         if (nextEOP == null) {
  467.                             // last entry, we use a backward difference
  468.                             lod = previousEOP[3] - currentEOP[3];
  469.                         } else {
  470.                             // regular entry, we use a centered difference
  471.                             lod = 0.5 * (previousEOP[3] - nextEOP[3]);
  472.                         }
  473.                     }

  474.                     if (configuration == null || !configuration.isValid(mjd)) {
  475.                         // get a configuration for current name and date range
  476.                         configuration = itrfVersionProvider.getConfiguration(fileName, mjd);
  477.                     }
  478.                     if (currentPole == null) {
  479.                         // we have only EOP for this date
  480.                         history.add(new EOPEntry(mjd,
  481.                                                  currentEOP[3], lod,
  482.                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[1]),
  483.                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[2]),
  484.                                                  0.0, 0.0, 0.0, 0.0,
  485.                                                  configuration.getVersion(),
  486.                                                  mjdDate));
  487.                     } else {
  488.                         // we have complete data
  489.                         history.add(new EOPEntry(mjd,
  490.                                                  currentEOP[3], lod,
  491.                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[1] ),
  492.                                                  UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[2] ),
  493.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[1]),
  494.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[2]),
  495.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[3]),
  496.                                                  UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[4]),
  497.                                                  configuration.getVersion(),
  498.                                                  mjdDate));
  499.                     }
  500.                 }

  501.             }

  502.         }

  503.         /** Skip to next section header.
  504.          * @param sections sections to check for
  505.          * @param reader reader from where file content is obtained
  506.          * @return the next section or null if no section is found until end of file
  507.          * @exception IOException if data can't be read
  508.          */
  509.         private Section nextSection(final List<Section> sections,
  510.                                     final BufferedReader reader)
  511.             throws IOException {

  512.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  513.                 ++lineNumber;
  514.                 for (Section section : sections) {
  515.                     if (section.matchesHeader(line)) {
  516.                         return section;
  517.                     }
  518.                 }
  519.             }

  520.             // we have reached end of file and not found a matching section header
  521.             return null;

  522.         }

  523.         /** Read X, Y, UT1-UTC.
  524.          * @param section section to parse
  525.          * @param reader reader from where file content is obtained
  526.          * @param name name of the file (or zip entry)
  527.          * @exception IOException if data can't be read
  528.          */
  529.         private void loadXYDT(final Section section, final BufferedReader reader, final String name)
  530.             throws IOException {

  531.             boolean inValuesPart = false;
  532.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  533.                 lineNumber++;
  534.                 final String[] fields = section.getFields(line);
  535.                 if (fields != null) {

  536.                     // we are within the values part
  537.                     inValuesPart = true;

  538.                     // this is a data line, build an entry from the extracted fields
  539.                     final int year  = Integer.parseInt(fields[0]);
  540.                     final int month = Integer.parseInt(fields[1]);
  541.                     final int day   = Integer.parseInt(fields[2]);
  542.                     final int mjd   = Integer.parseInt(fields[3]);
  543.                     final DateComponents dc = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd);
  544.                     if ((dc.getYear() % 100) != (year % 100) ||
  545.                          dc.getMonth() != month ||
  546.                          dc.getDay() != day) {
  547.                         throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  548.                                                   name, year, month, day, mjd);
  549.                     }
  550.                     mjdMin = FastMath.min(mjdMin, mjd);
  551.                     mjdMax = FastMath.max(mjdMax, mjd);
  552.                     if (firstMJD < 0) {
  553.                         // store the first mjd parsed
  554.                         firstMJD = mjd;
  555.                     }

  556.                     // get the entry at the same date if it was already parsed
  557.                     final double[] eop;
  558.                     if (eopFieldsMap.containsKey(mjd)) {
  559.                         eop = eopFieldsMap.get(mjd);
  560.                     } else {
  561.                         eop = new double[4];
  562.                         eopFieldsMap.put(mjd, eop);
  563.                     }

  564.                     if (eop[0] <= firstMJD) {
  565.                         // either it is the first time we parse this date (eop[0] = 0),
  566.                         // or the new parsed data is from a more recent file
  567.                         // in both case, we should update the array
  568.                         eop[0] = firstMJD;
  569.                         eop[1] = Double.parseDouble(fields[4]);
  570.                         eop[2] = Double.parseDouble(fields[5]);
  571.                         eop[3] = Double.parseDouble(fields[6]);
  572.                     }

  573.                 } else if (inValuesPart) {
  574.                     // we leave values part
  575.                     return;
  576.                 }
  577.             }

  578.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  579.                                       name, lineNumber);

  580.         }

  581.         /** Read EOP data.
  582.          * @param section section to parse
  583.          * @param isNonRotatingOrigin if true, the file contain Non-Rotating Origin nutation corrections
  584.          * @param reader reader from where file content is obtained
  585.          * @param name name of the file (or zip entry)
  586.          * @exception IOException if data can't be read
  587.          */
  588.         private void loadPoleOffsets(final Section section, final boolean isNonRotatingOrigin,
  589.                                      final BufferedReader reader, final String name)
  590.             throws IOException {

  591.             boolean inValuesPart = false;
  592.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  593.                 lineNumber++;
  594.                 final String[] fields = section.getFields(line);
  595.                 if (fields != null) {

  596.                     // we are within the values part
  597.                     inValuesPart = true;

  598.                     // this is a data line, build an entry from the extracted fields
  599.                     final int mjd = Integer.parseInt(fields[0]);
  600.                     mjdMin = FastMath.min(mjdMin, mjd);
  601.                     mjdMax = FastMath.max(mjdMax, mjd);

  602.                     // get the entry at the same date if it was already parsed
  603.                     final double[] pole;
  604.                     if (poleOffsetsFieldsMap.containsKey(mjd)) {
  605.                         pole = poleOffsetsFieldsMap.get(mjd);
  606.                     } else {
  607.                         pole = new double[5];
  608.                         poleOffsetsFieldsMap.put(mjd, pole);
  609.                     }

  610.                     if (pole[0] <= firstMJD) {
  611.                         // either it is the first time we parse this date (pole[0] = 0),
  612.                         // or the new parsed data is from a more recent file
  613.                         // in both case, we should update the array
  614.                         pole[0] = firstMJD;
  615.                         if (isNonRotatingOrigin) {
  616.                             pole[1] = Double.parseDouble(fields[1]);
  617.                             pole[2] = Double.parseDouble(fields[2]);
  618.                         } else {
  619.                             pole[3] = Double.parseDouble(fields[1]);
  620.                             pole[4] = Double.parseDouble(fields[2]);
  621.                         }
  622.                     }

  623.                 } else if (inValuesPart) {
  624.                     // we leave values part
  625.                     return;
  626.                 }
  627.             }

  628.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  629.                                       name, lineNumber);

  630.         }

  631.     }

  632. }