BulletinBFilesLoader.java

  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. 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.Collection;
  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.DataProvidersManager;
  34. import org.orekit.errors.OrekitException;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.DateComponents;
  38. import org.orekit.time.Month;
  39. import org.orekit.time.TimeScale;
  40. import org.orekit.utils.Constants;
  41. import org.orekit.utils.IERSConventions;
  42. import org.orekit.utils.IERSConventions.NutationCorrectionConverter;
  43. import org.orekit.utils.units.UnitsConverter;

  44. /** Loader for bulletin B files.
  45.  * <p>Bulletin B files contain {@link EOPEntry
  46.  * Earth Orientation Parameters} for a few months periods.
  47.  * They correspond to finalized data, suitable for long term
  48.  * a posteriori analysis.</p>
  49.  * <p>The bulletin B files are recognized thanks to their base names,
  50.  * which must match one of the patterns <code>bulletinb_IAU2000-###.txt</code>,
  51.  * <code>bulletinb_IAU2000.###</code>, <code>bulletinb-###.txt</code> or
  52.  * <code>bulletinb.###</code> (or the same ending with <code>.gz</code>
  53.  * for gzip-compressed files) where # stands for a digit character.</p>
  54.  * <p>
  55.  * Starting with bulletin B 252 published in February 2009, buletins B are
  56.  * written in a format containing nutation corrections for both the
  57.  * new IAU2000 nutation model as dx, dy entries in its section 1 and nutation
  58.  * corrections for the old IAU1976 nutation model as dPsi, dEpsilon entries in
  59.  * its section 2. These bulletins are available from IERS <a
  60.  * href="ftp://ftp.iers.org/products/eop/bulletinb/format_2009/">
  61.  *  FTP site</a>. They are also available with exactly the same content
  62.  * (but a different naming convention) from <a
  63.  * href="http://hpiers.obspm.fr/eoppc/bul/bulb_new/">Paris-Meudon
  64.  * observatory site</a>.
  65.  * </p>
  66.  * <p>
  67.  * Ending with bulletin B 263 published in January 2010, bulletins B were
  68.  * written in a format containing only one type of nutation corrections in its
  69.  * section 1, either for new IAU2000 nutation model as dx, dy entries or the old
  70.  * IAU1976 nutation model as dPsi, dEpsilon entries, depending on the file (a pair of
  71.  * files with different name was published each month between March 2003 and January 2010).
  72.  * </p>
  73.  * <p>
  74.  * Bulletin B in csv format must be read using {@link EopCsvFilesLoader} rather
  75.  * than using this loader. Bulletin B in xml format must be read using {@link EopXmlLoader}
  76.  * rather than using this loader.
  77.  * </p>
  78.  * <p>
  79.  * This class handles both the old and the new format.
  80.  * </p>
  81.  * <p>
  82.  * This class is immutable and hence thread-safe
  83.  * </p>
  84.  * @author Luc Maisonobe
  85.  * @see EopCsvFilesLoader
  86.  * @see EopXmlLoader
  87.  */
  88. class BulletinBFilesLoader extends AbstractEopLoader implements EopHistoryLoader {

  89.     /** Section 1 header pattern. */
  90.     private static final Pattern SECTION_1_HEADER;

  91.     /** Section 2 header pattern for old format. */
  92.     private static final Pattern SECTION_2_HEADER_OLD;

  93.     /** Section 3 header pattern. */
  94.     private static final Pattern SECTION_3_HEADER;

  95.     /** Pattern for line introducing the final bulletin B values. */
  96.     private static final Pattern FINAL_VALUES_START;

  97.     /** Pattern for line introducing the bulletin B preliminary extension. */
  98.     private static final Pattern FINAL_VALUES_END;

  99.     /** Data line pattern in section 1 (old format). */
  100.     private static final Pattern SECTION_1_DATA_OLD_FORMAT;

  101.     /** Data line pattern in section 2. */
  102.     private static final Pattern SECTION_2_DATA_OLD_FORMAT;

  103.     /** Data line pattern in section 1 (new format). */
  104.     private static final Pattern SECTION_1_DATA_NEW_FORMAT;

  105.     /** Data line pattern in section 3 (new format). */
  106.     private static final Pattern SECTION_3_DATA_NEW_FORMAT;

  107.     static {

  108.         // the section headers lines in the old bulletin B monthly data files have
  109.         // the following form (the indentation discrepancy for section 6 is really
  110.         // present in the available files):
  111.         // 1 - EARTH ORIENTATION PARAMETERS (IERS evaluation).
  112.         // either
  113.         // 2 - SMOOTHED VALUES OF x, y, UT1, D, DPSI, DEPSILON (IERS EVALUATION)
  114.         // or
  115.         // 2 - SMOOTHED VALUES OF x, y, UT1, D, dX, dY (IERS EVALUATION)
  116.         // 3 - NORMAL VALUES OF THE EARTH ORIENTATION PARAMETERS AT FIVE-DAY INTERVALS
  117.         // 4 - DURATION OF THE DAY AND ANGULAR VELOCITY OF THE EARTH (IERS evaluation).
  118.         // 5 - INFORMATION ON TIME SCALES
  119.         //       6 - SUMMARY OF CONTRIBUTED EARTH ORIENTATION PARAMETERS SERIES
  120.         //
  121.         // the section headers lines in the new bulletin B monthly data files have
  122.         // the following form:
  123.         // 1 - DAILY FINAL VALUES OF  x, y, UT1-UTC, dX, dY
  124.         // 2 - DAILY FINAL VALUES OF CELESTIAL POLE OFFSETS dPsi1980 & dEps1980
  125.         // 3 - EARTH ANGULAR VELOCITY : DAILY FINAL VALUES OF LOD, OMEGA AT 0hUTC
  126.         // 4 - INFORMATION ON TIME SCALES
  127.         // 5 - SUMMARY OF CONTRIBUTED EARTH ORIENTATION PARAMETERS SERIES
  128.         SECTION_1_HEADER     = Pattern.compile("^ +1 - (\\p{Upper}+) \\p{Upper}+ \\p{Upper}+.*");
  129.         SECTION_2_HEADER_OLD = Pattern.compile("^ +2 - SMOOTHED \\p{Upper}+ \\p{Upper}+.*((?:DPSI, DEPSILON)|(?:dX, dY)).*");
  130.         SECTION_3_HEADER     = Pattern.compile("^ +3 - \\p{Upper}+ \\p{Upper}+ \\p{Upper}+.*");

  131.         // the markers bracketing the final values in section 1 in the old bulletin B
  132.         // monthly data files have the following form:
  133.         //
  134.         //  Final Bulletin B values.
  135.         //   ...
  136.         //  Preliminary extension, to be updated weekly in Bulletin A and monthly
  137.         //  in Bulletin B.
  138.         //
  139.         // the markers bracketing the final values in section 1 in the new bulletin B
  140.         // monthly data files have the following form:
  141.         //
  142.         //  Final values
  143.         //   ...
  144.         //  Preliminary extension
  145.         //
  146.         FINAL_VALUES_START = Pattern.compile("^\\p{Blank}+Final( Bulletin B)? values.*");
  147.         FINAL_VALUES_END   = Pattern.compile("^\\p{Blank}+Preliminary extension.*");

  148.         // the data lines in the old bulletin B monthly data files have the following form:
  149.         // in section 1:
  150.         // AUG   1  55044  0.22176 0.49302  0.231416  -33.768584   -69.1    -8.9
  151.         // AUG   6  55049  0.23202 0.48003  0.230263  -33.769737   -69.5    -8.5
  152.         // in section 2:
  153.         // AUG   1   55044  0.22176  0.49302  0.230581 -0.835  -0.310  -69.1   -8.9
  154.         // AUG   2   55045  0.22395  0.49041  0.230928 -0.296  -0.328  -69.5   -8.9
  155.         //
  156.         // the data lines in the new bulletin B monthly data files have the following form:
  157.         // in section 1:
  158.         // 2009   8   2   55045  223.954  490.410  230.9277    0.214 -0.056    0.008    0.009    0.0641  0.048  0.121
  159.         // 2009   8   3   55046  225.925  487.700  231.2186    0.300 -0.138    0.010    0.012    0.0466  0.099  0.248
  160.         // 2009   8   4   55047  227.931  485.078  231.3929    0.347 -0.231    0.019    0.023    0.0360  0.099  0.249
  161.         // 2009   8   5   55048  230.016  482.445  231.4601    0.321 -0.291    0.025    0.028    0.0441  0.095  0.240
  162.         // 2009   8   6   55049  232.017  480.026  231.3619    0.267 -0.273    0.025    0.029    0.0477  0.038  0.095
  163.         // in section 2:
  164.         // 2009   8   2   55045   -69.474    -8.929     0.199     0.121
  165.         // 2009   8   3   55046   -69.459    -9.016     0.250     0.248
  166.         // 2009   8   4   55047   -69.401    -9.039     0.250     0.249
  167.         // 2009   8   5   55048   -69.425    -8.864     0.247     0.240
  168.         // 2009   8   6   55049   -69.510    -8.539     0.153     0.095
  169.         // in section 3:
  170.         // 2009   8   2   55045 -0.3284  0.0013  15.04106723584    0.00000000023
  171.         // 2009   8   3   55046 -0.2438  0.0013  15.04106722111    0.00000000023
  172.         // 2009   8   4   55047 -0.1233  0.0013  15.04106720014    0.00000000023
  173.         // 2009   8   5   55048  0.0119  0.0013  15.04106717660    0.00000000023
  174.         // 2009   8   6   55049  0.1914  0.0013  15.04106714535    0.00000000023
  175.         final StringBuilder builder = new StringBuilder("^\\p{Blank}+(?:");
  176.         for (final Month month : Month.values()) {
  177.             builder.append(month.getUpperCaseAbbreviation());
  178.             builder.append('|');
  179.         }
  180.         builder.delete(builder.length() - 1, builder.length());
  181.         builder.append(")");
  182.         final String integerPattern      = "[-+]?\\p{Digit}+";
  183.         final String realPattern         = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
  184.         final String monthNameField      = builder.toString();
  185.         final String ignoredIntegerField = "\\p{Blank}*" + integerPattern;
  186.         final String storedIntegerField  = "\\p{Blank}*(" + integerPattern + ")";
  187.         final String mjdField            = "\\p{Blank}+(\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})";
  188.         final String storedRealField     = "\\p{Blank}+(" + realPattern + ")";
  189.         final String ignoredRealField    = "\\p{Blank}+" + realPattern;
  190.         final String finalBlanks         = "\\p{Blank}*$";
  191.         SECTION_1_DATA_OLD_FORMAT = Pattern.compile(monthNameField + ignoredIntegerField + mjdField +
  192.                                                     ignoredRealField + ignoredRealField + ignoredRealField +
  193.                                                     ignoredRealField + ignoredRealField + ignoredRealField +
  194.                                                     finalBlanks);
  195.         SECTION_2_DATA_OLD_FORMAT = Pattern.compile(monthNameField + ignoredIntegerField + mjdField +
  196.                                                     storedRealField  + storedRealField  + storedRealField +
  197.                                                     ignoredRealField +
  198.                                                     storedRealField + storedRealField + storedRealField +
  199.                                                     finalBlanks);
  200.         SECTION_1_DATA_NEW_FORMAT = Pattern.compile(storedIntegerField + storedIntegerField + storedIntegerField + mjdField +
  201.                                                     storedRealField + storedRealField + storedRealField +
  202.                                                     storedRealField + storedRealField + ignoredRealField + ignoredRealField +
  203.                                                     ignoredRealField + ignoredRealField + ignoredRealField +
  204.                                                     finalBlanks);
  205.         SECTION_3_DATA_NEW_FORMAT = Pattern.compile(ignoredIntegerField + ignoredIntegerField + ignoredIntegerField + mjdField +
  206.                                                     storedRealField +
  207.                                                     ignoredRealField + ignoredRealField + ignoredRealField +
  208.                                                     finalBlanks);

  209.     }

  210.     /** Build a loader for IERS bulletins B files.
  211.      * @param supportedNames regular expression for supported files names
  212.      * @param manager provides access to the bulletin B files.
  213.      * @param utcSupplier UTC time scale.
  214.      */
  215.     BulletinBFilesLoader(final String supportedNames,
  216.                          final DataProvidersManager manager,
  217.                          final Supplier<TimeScale> utcSupplier) {
  218.         super(supportedNames, manager, utcSupplier);
  219.     }

  220.     /** {@inheritDoc} */
  221.     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
  222.                             final SortedSet<EOPEntry> history) {
  223.         final ItrfVersionProvider itrfVersionProvider = new ITRFVersionLoader(
  224.                 ITRFVersionLoader.SUPPORTED_NAMES,
  225.                 getDataProvidersManager());
  226.         final Parser parser = new Parser(converter, itrfVersionProvider, getUtc());
  227.         final EopParserLoader loader = new EopParserLoader(parser);
  228.         this.feed(loader);
  229.         history.addAll(loader.getEop());
  230.     }

  231.     /** Internal class performing the parsing. */
  232.     static class Parser extends AbstractEopParser {

  233.         /** ITRF version configuration. */
  234.         private ITRFVersionLoader.ITRFVersionConfiguration configuration;

  235.         /** History entries. */
  236.         private List<EOPEntry> history;

  237.         /** Map for fields read in different sections. */
  238.         private final Map<Integer, double[]> fieldsMap;

  239.         /** Current line number. */
  240.         private int lineNumber;

  241.         /** Current line. */
  242.         private String line;

  243.         /** Start of final data. */
  244.         private int mjdMin;

  245.         /** End of final data. */
  246.         private int mjdMax;

  247.         /**
  248.          * Simple constructor.
  249.          *
  250.          * @param converter           converter to use
  251.          * @param itrfVersionProvider to use for determining the ITRF version of the EOP.
  252.          * @param utc                 time scale for parsing dates.
  253.          */
  254.         Parser(final NutationCorrectionConverter converter,
  255.                final ItrfVersionProvider itrfVersionProvider,
  256.                final TimeScale utc) {
  257.             super(converter, itrfVersionProvider, utc);
  258.             this.fieldsMap         = new HashMap<>();
  259.             this.lineNumber        = 0;
  260.             this.mjdMin            = Integer.MAX_VALUE;
  261.             this.mjdMax            = Integer.MIN_VALUE;
  262.         }

  263.         /** {@inheritDoc} */
  264.         @Override
  265.         public Collection<EOPEntry> parse(final InputStream input, final String name)
  266.             throws IOException {

  267.             // set up a reader for line-oriented bulletin B files
  268.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  269.                 // reset parse info to start new file
  270.                 fieldsMap.clear();
  271.                 lineNumber = 0;
  272.                 mjdMin     = Integer.MAX_VALUE;
  273.                 mjdMax     = Integer.MIN_VALUE;
  274.                 history = new ArrayList<>();
  275.                 configuration = null;

  276.                 // skip header up to section 1 and check if we are parsing an old or new format file
  277.                 final Matcher section1Matcher = seekToLine(SECTION_1_HEADER, reader, name);
  278.                 final boolean isOldFormat = "EARTH".equals(section1Matcher.group(1));

  279.                 if (isOldFormat) {

  280.                     // extract MJD bounds for final data from section 1
  281.                     loadMJDBoundsOldFormat(reader, name);

  282.                     final Matcher section2Matcher = seekToLine(SECTION_2_HEADER_OLD, reader, name);
  283.                     final boolean isNonRotatingOrigin = section2Matcher.group(1).startsWith("dX");
  284.                     loadEOPOldFormat(isNonRotatingOrigin, reader, name);

  285.                 } else {

  286.                     // extract x, y, UT1-UTC, dx, dy from section 1
  287.                     loadXYDTDxDyNewFormat(reader, name);

  288.                     // skip to section 3
  289.                     seekToLine(SECTION_3_HEADER, reader, name);

  290.                     // extract LOD data from section 3
  291.                     loadLODNewFormat(reader, name);

  292.                     // set up the EOP entries
  293.                     for (Map.Entry<Integer, double[]> entry : fieldsMap.entrySet()) {
  294.                         final int mjd = entry.getKey();
  295.                         final double[] array = entry.getValue();
  296.                         if (Double.isNaN(array[0] + array[1] + array[2] + array[3] + array[4] + array[5])) {
  297.                             throw notifyUnexpectedErrorEncountered(name);
  298.                         }
  299.                         final AbsoluteDate mjdDate =
  300.                                 new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  301.                                                  getUtc());
  302.                         final double[] equinox = getConverter().toEquinox(mjdDate, array[4], array[5]);
  303.                         if (configuration == null || !configuration.isValid(mjd)) {
  304.                             // get a configuration for current name and date range
  305.                             configuration = getItrfVersionProvider().getConfiguration(name, mjd);
  306.                         }
  307.                         history.add(new EOPEntry(mjd, array[0], array[1], array[2], array[3],
  308.                                                  Double.NaN, Double.NaN,
  309.                                                  equinox[0], equinox[1], array[4], array[5],
  310.                                                  configuration.getVersion(), mjdDate));
  311.                     }

  312.                 }
  313.             }

  314.             return history;

  315.         }

  316.         /** Read until a line matching a pattern is found.
  317.          * @param pattern pattern to look for
  318.          * @param reader reader from where file content is obtained
  319.          * @param name name of the file (or zip entry)
  320.          * @return the matching matcher for the line
  321.          * @exception IOException if data can't be read
  322.          */
  323.         private Matcher seekToLine(final Pattern pattern, final BufferedReader reader, final String name)
  324.             throws IOException {

  325.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  326.                 ++lineNumber;
  327.                 final Matcher matcher = pattern.matcher(line);
  328.                 if (matcher.matches()) {
  329.                     return matcher;
  330.                 }
  331.             }

  332.             // we have reached end of file and not found a matching line
  333.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  334.                                       name, lineNumber);

  335.         }

  336.         /** Read MJD bounds of the final data part from section 1 in the old bulletin B format.
  337.          * @param reader reader from where file content is obtained
  338.          * @param name name of the file (or zip entry)
  339.          * @exception IOException if data can't be read
  340.          */
  341.         private void loadMJDBoundsOldFormat(final BufferedReader reader, final String name)
  342.             throws IOException {

  343.             boolean inFinalValuesPart = false;
  344.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  345.                 lineNumber++;
  346.                 Matcher matcher = FINAL_VALUES_START.matcher(line);
  347.                 if (matcher.matches()) {
  348.                     // we are entering final values part (in section 1)
  349.                     inFinalValuesPart = true;
  350.                 } else if (inFinalValuesPart) {
  351.                     matcher = SECTION_1_DATA_OLD_FORMAT.matcher(line);
  352.                     if (matcher.matches()) {
  353.                         // this is a data line, build an entry from the extracted fields
  354.                         final int mjd = Integer.parseInt(matcher.group(1));
  355.                         mjdMin = FastMath.min(mjdMin, mjd);
  356.                         mjdMax = FastMath.max(mjdMax, mjd);
  357.                     } else {
  358.                         matcher = FINAL_VALUES_END.matcher(line);
  359.                         if (matcher.matches()) {
  360.                             // we leave final values part
  361.                             return;
  362.                         }
  363.                     }
  364.                 }
  365.             }

  366.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  367.                                       name, lineNumber);

  368.         }

  369.         /** Read EOP data from section 2 in the old bulletin B format.
  370.          * @param isNonRotatingOrigin if true, the file contain Non-Rotating Origin nutation corrections
  371.          * @param reader reader from where file content is obtained
  372.          * @param name name of the file (or zip entry)
  373.          * @exception IOException if data can't be read
  374.          */
  375.         private void loadEOPOldFormat(final boolean isNonRotatingOrigin,
  376.                                       final BufferedReader reader, final String name)
  377.             throws IOException {

  378.             // read the data lines in the final values part inside section 2
  379.             line = reader.readLine();
  380.             while (line != null) {
  381.                 lineNumber++;
  382.                 final Matcher matcher = SECTION_2_DATA_OLD_FORMAT.matcher(line);
  383.                 if (matcher.matches()) {
  384.                     // this is a data line, build an entry from the extracted fields
  385.                     final int    mjd   = Integer.parseInt(matcher.group(1));
  386.                     final double x     = Double.parseDouble(matcher.group(2)) * Constants.ARC_SECONDS_TO_RADIANS;
  387.                     final double y     = Double.parseDouble(matcher.group(3)) * Constants.ARC_SECONDS_TO_RADIANS;
  388.                     final double dtu1  = Double.parseDouble(matcher.group(4));
  389.                     final double lod   = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(matcher.group(5)));
  390.                     if (mjd >= mjdMin) {
  391.                         final AbsoluteDate mjdDate =
  392.                                 new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  393.                                                  getUtc());
  394.                         final double[] equinox;
  395.                         final double[] nro;
  396.                         if (isNonRotatingOrigin) {
  397.                             nro = new double[] {
  398.                                 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(6))),
  399.                                 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(7)))
  400.                             };
  401.                             equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
  402.                         } else {
  403.                             equinox = new double[] {
  404.                                 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(6))),
  405.                                 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(7)))
  406.                             };
  407.                             nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
  408.                         }
  409.                         if (configuration == null || !configuration.isValid(mjd)) {
  410.                             // get a configuration for current name and date range
  411.                             configuration = getItrfVersionProvider().getConfiguration(name, mjd);
  412.                         }
  413.                         history.add(new EOPEntry(mjd, dtu1, lod, x, y, Double.NaN, Double.NaN,
  414.                                                  equinox[0], equinox[1], nro[0], nro[1],
  415.                                                  configuration.getVersion(), mjdDate));
  416.                         line = mjd < mjdMax ? reader.readLine() : null;
  417.                     } else {
  418.                         line = reader.readLine();
  419.                     }
  420.                 } else {
  421.                     line = reader.readLine();
  422.                 }
  423.             }

  424.         }

  425.         /** Read X, Y, UT1-UTC, dx, dy from section 1 in the new bulletin B format.
  426.          * @param reader reader from where file content is obtained
  427.          * @param name name of the file (or zip entry)
  428.          * @exception IOException if data can't be read
  429.          */
  430.         private void loadXYDTDxDyNewFormat(final BufferedReader reader, final String name)
  431.             throws IOException {

  432.             boolean inFinalValuesPart = false;
  433.             line = reader.readLine();
  434.             while (line != null) {
  435.                 lineNumber++;
  436.                 Matcher matcher = FINAL_VALUES_START.matcher(line);
  437.                 if (matcher.matches()) {
  438.                     // we are entering final values part (in section 1)
  439.                     inFinalValuesPart = true;
  440.                     line = reader.readLine();
  441.                 } else if (inFinalValuesPart) {
  442.                     matcher = SECTION_1_DATA_NEW_FORMAT.matcher(line);
  443.                     if (matcher.matches()) {
  444.                         // this is a data line, build an entry from the extracted fields
  445.                         final int year  = Integer.parseInt(matcher.group(1));
  446.                         final int month = Integer.parseInt(matcher.group(2));
  447.                         final int day   = Integer.parseInt(matcher.group(3));
  448.                         final int mjd   = Integer.parseInt(matcher.group(4));
  449.                         if (new DateComponents(year, month, day).getMJD() != mjd) {
  450.                             throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  451.                                                       name, year, month, day, mjd);
  452.                         }
  453.                         mjdMin = FastMath.min(mjdMin, mjd);
  454.                         mjdMax = FastMath.max(mjdMax, mjd);
  455.                         final double x    = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(5)));
  456.                         final double y    = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(6)));
  457.                         final double dtu1 = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(matcher.group(7)));
  458.                         final double dx   = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(8)));
  459.                         final double dy   = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(9)));
  460.                         fieldsMap.put(mjd,
  461.                                       new double[] {
  462.                                           dtu1, Double.NaN, x, y, dx, dy
  463.                                       });
  464.                         line = reader.readLine();
  465.                     } else {
  466.                         matcher = FINAL_VALUES_END.matcher(line);
  467.                         line = matcher.matches() ? null : reader.readLine();
  468.                     }
  469.                 } else {
  470.                     line = reader.readLine();
  471.                 }
  472.             }
  473.         }

  474.         /** Read LOD from section 3 in the new bulletin B format.
  475.          * @param reader reader from where file content is obtained
  476.          * @param name name of the file (or zip entry)
  477.          * @exception IOException if data can't be read
  478.          */
  479.         private void loadLODNewFormat(final BufferedReader reader, final String name)
  480.             throws IOException {
  481.             line = reader.readLine();
  482.             while (line != null) {
  483.                 lineNumber++;
  484.                 final Matcher matcher = SECTION_3_DATA_NEW_FORMAT.matcher(line);
  485.                 if (matcher.matches()) {
  486.                     // this is a data line, build an entry from the extracted fields
  487.                     final int    mjd = Integer.parseInt(matcher.group(1));
  488.                     if (mjd >= mjdMin) {
  489.                         final double lod = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(matcher.group(2)));
  490.                         final double[] array = fieldsMap.get(mjd);
  491.                         if (array == null) {
  492.                             throw notifyUnexpectedErrorEncountered(name);
  493.                         }
  494.                         array[1] = lod;
  495.                         line = mjd >= mjdMax ? null : reader.readLine();
  496.                     } else {
  497.                         line = reader.readLine();
  498.                     }
  499.                 } else {
  500.                     line = reader.readLine();
  501.                 }
  502.             }
  503.         }

  504.         /** Create an exception to be thrown.
  505.          * @param name name of the file (or zip entry)
  506.          * @return OrekitException always thrown to notify an unexpected error has been
  507.          * encountered by the caller
  508.          */
  509.         private OrekitException notifyUnexpectedErrorEncountered(final String name) {
  510.             String loaderName = BulletinBFilesLoader.class.getName();
  511.             loaderName = loaderName.substring(loaderName.lastIndexOf('.') + 1);
  512.             return new OrekitException(OrekitMessages.UNEXPECTED_FILE_FORMAT_ERROR_FOR_LOADER,
  513.                                        name, loaderName);
  514.         }

  515.     }

  516. }