BulletinBFilesLoader.java

  1. /* Copyright 2002-2013 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.util.ArrayList;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.SortedSet;
  27. import java.util.regex.Matcher;
  28. import java.util.regex.Pattern;

  29. import org.apache.commons.math3.util.FastMath;
  30. import org.orekit.data.DataLoader;
  31. import org.orekit.data.DataProvidersManager;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;
  34. import org.orekit.time.AbsoluteDate;
  35. import org.orekit.time.DateComponents;
  36. import org.orekit.time.Month;
  37. import org.orekit.time.TimeScalesFactory;
  38. import org.orekit.utils.Constants;
  39. import org.orekit.utils.IERSConventions;

  40. /** Loader for bulletin B files.
  41.  * <p>Bulletin B files contain {@link EOPEntry
  42.  * Earth Orientation Parameters} for a few months periods.</p>
  43.  * <p>The bulletin B files are recognized thanks to their base names,
  44.  * which must match one of the the patterns <code>bulletinb_IAU2000-###.txt</code>,
  45.  * <code>bulletinb_IAU2000.###</code>, <code>bulletinb-###.txt</code> or
  46.  * <code>bulletinb.###</code> (or the same ending with <code>.gz</code>
  47.  * for gzip-compressed files) where # stands for a digit character.</p>
  48.  * <p>
  49.  * Starting with bulletin B 252 published in February 2009, buletins B are
  50.  * written in a format containing nutation corrections for both the
  51.  * new IAU2000 nutation model as dx, dy entries in its section 1 and nutation
  52.  * corrections for the old IAU1976 nutation model as dPsi, dEpsilon entries in
  53.  * its sections 2. These buletins are available from IERS main site <a
  54.  * href="http://www.iers.org/IERS/EN/DataProducts/EarthOrientationData/eop.html">
  55.  * Earth Orientation Parameters</a> section. They are also available with exactly the same content
  56.  * (but a different naming convention) from <a
  57.  * href="http://hpiers.obspm.fr/eoppc/bul/bulb_new/">Paris-Meudon
  58.  * observatory site</a>.
  59.  * </p>
  60.  * <p>
  61.  * Ending with bulletin B 263 published in January 2010, bulletins B were
  62.  * written in a format containing only one type of nutation corrections in its
  63.  * section 1, either for new IAU2000 nutation model as dx, dy entries or the old
  64.  * IAU1976 nutation model as dPsi, dEpsilon entries, depending on the file (a pair of
  65.  * files with different name was published each month between March 2003 and January 2010).
  66.  * </p>
  67.  * <p>
  68.  * This class handles both the old and the new format.
  69.  * </p>
  70.  * <p>
  71.  * This class is immutable and hence thread-safe
  72.  * </p>
  73.  * @author Luc Maisonobe
  74.  */
  75. class BulletinBFilesLoader implements EOPHistoryLoader {

  76.     /** Conversion factor. */
  77.     private static final double MILLI_ARC_SECONDS_TO_RADIANS = Constants.ARC_SECONDS_TO_RADIANS / 1000;

  78.     /** Conversion factor. */
  79.     private static final double MILLI_SECONDS_TO_SECONDS = 1.e-3;

  80.     /** Section 1 header pattern. */
  81.     private static final Pattern SECTION_1_HEADER;

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

  84.     /** Section 3 header pattern. */
  85.     private static final Pattern SECTION_3_HEADER;

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

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

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

  92.     /** Data line pattern in section 2. */
  93.     private static final Pattern SECTION_2_DATA_OLD_FORMAT;

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

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

  98.     static {

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

  122.         // the markers bracketing the final values in section 1 in the old bulletin B
  123.         // monthly data files have the following form:
  124.         //
  125.         //  Final Bulletin B values.
  126.         //   ...
  127.         //  Preliminary extension, to be updated weekly in Bulletin A and monthly
  128.         //  in Bulletin B.
  129.         //
  130.         // the markers bracketing the final values in section 1 in the new bulletin B
  131.         // monthly data files have the following form:
  132.         //
  133.         //  Final values
  134.         //   ...
  135.         //  Preliminary extension
  136.         //
  137.         FINAL_VALUES_START = Pattern.compile("^\\p{Blank}+Final( Bulletin B)? values.*");
  138.         FINAL_VALUES_END   = Pattern.compile("^\\p{Blank}+Preliminary extension.*");

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

  198.     }

  199.     /** Regular expression for supported files names. */
  200.     private final String supportedNames;

  201.     /** Build a loader for IERS bulletins B files.
  202.     * @param supportedNames regular expression for supported files names
  203.     */
  204.     public BulletinBFilesLoader(final String supportedNames) {
  205.         this.supportedNames = supportedNames;
  206.     }

  207.     /** {@inheritDoc} */
  208.     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
  209.                             final SortedSet<EOPEntry> history)
  210.         throws OrekitException {
  211.         final Parser parser = new Parser(converter);
  212.         DataProvidersManager.getInstance().feed(supportedNames, parser);
  213.         history.addAll(parser.history);
  214.     }

  215.     /** Internal class performing the parsing. */
  216.     private static class Parser implements DataLoader {

  217.         /** Converter for nutation corrections. */
  218.         private final IERSConventions.NutationCorrectionConverter converter;

  219.         /** History entries. */
  220.         private final List<EOPEntry> history;

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

  223.         /** Current line number. */
  224.         private int lineNumber;

  225.         /** Current line. */
  226.         private String line;

  227.         /** Start of final data. */
  228.         private int mjdMin;

  229.         /** End of final data. */
  230.         private int mjdMax;

  231.         /** Simple constructor.
  232.          * @param converter converter to use
  233.          */
  234.         public Parser(final IERSConventions.NutationCorrectionConverter converter) {
  235.             this.converter  = converter;
  236.             this.history    = new ArrayList<EOPEntry>();
  237.             this.fieldsMap  = new HashMap<Integer, double[]>();
  238.             this.lineNumber = 0;
  239.             this.mjdMin     = Integer.MAX_VALUE;
  240.             this.mjdMax     = Integer.MIN_VALUE;
  241.         }

  242.         /** {@inheritDoc} */
  243.         public boolean stillAcceptsData() {
  244.             return true;
  245.         }

  246.         /** {@inheritDoc} */
  247.         public void loadData(final InputStream input, final String name)
  248.             throws OrekitException, IOException {

  249.             // set up a reader for line-oriented bulletin B files
  250.             final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));

  251.             // reset parse info to start new file (do not clear history!)
  252.             fieldsMap.clear();
  253.             lineNumber = 0;
  254.             mjdMin     = Integer.MAX_VALUE;
  255.             mjdMax     = Integer.MIN_VALUE;

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

  259.             if (isOldFormat) {

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

  262.                 final Matcher section2Matcher = seekToLine(SECTION_2_HEADER_OLD, reader, name);
  263.                 final boolean isNonRotatingOrigin = section2Matcher.group(1).startsWith("dX");
  264.                 loadEOPOldFormat(isNonRotatingOrigin, reader, name);

  265.             } else {

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

  268.                 // skip to section 3
  269.                 seekToLine(SECTION_3_HEADER, reader, name);

  270.                 // extract LOD data from section 3
  271.                 loadLODNewFormat(reader, name);

  272.                 // set up the EOP entries
  273.                 for (Map.Entry<Integer, double[]> entry : fieldsMap.entrySet()) {
  274.                     final int mjd = entry.getKey();
  275.                     final double[] array = entry.getValue();
  276.                     if (Double.isNaN(array[0]) || Double.isNaN(array[1]) ||
  277.                             Double.isNaN(array[2]) || Double.isNaN(array[3]) ||
  278.                             Double.isNaN(array[4]) || Double.isNaN(array[5])) {
  279.                         throw notifyUnexpectedErrorEncountered(name);
  280.                     }
  281.                     final AbsoluteDate mjdDate =
  282.                             new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  283.                                              TimeScalesFactory.getUTC());
  284.                     final double[] equinox = converter.toEquinox(mjdDate, array[4], array[5]);
  285.                     history.add(new EOPEntry(mjd, array[0], array[1], array[2], array[3],
  286.                                              equinox[0], equinox[1], array[4], array[5]));
  287.                 }

  288.             }

  289.         }

  290.         /** Read until a line matching a pattern is found.
  291.          * @param pattern pattern to look for
  292.          * @param reader reader from where file content is obtained
  293.          * @param name name of the file (or zip entry)
  294.          * @return the matching matcher for the line
  295.          * @exception IOException if data can't be read
  296.          * @exception OrekitException if end of file is reached before line has been found
  297.          */
  298.         private Matcher seekToLine(final Pattern pattern, final BufferedReader reader, final String name)
  299.             throws IOException, OrekitException {

  300.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  301.                 ++lineNumber;
  302.                 final Matcher matcher = pattern.matcher(line);
  303.                 if (matcher.matches()) {
  304.                     return matcher;
  305.                 }
  306.             }

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

  310.         }

  311.         /** Read MJD bounds of the final data part from section 1 in the old bulletin B format.
  312.          * @param reader reader from where file content is obtained
  313.          * @param name name of the file (or zip entry)
  314.          * @exception IOException if data can't be read
  315.          * @exception OrekitException if some data is missing or if some loader specific error occurs
  316.          */
  317.         private void loadMJDBoundsOldFormat(final BufferedReader reader, final String name)
  318.             throws OrekitException, IOException {

  319.             boolean inFinalValuesPart = false;
  320.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  321.                 lineNumber++;
  322.                 Matcher matcher = FINAL_VALUES_START.matcher(line);
  323.                 if (matcher.matches()) {
  324.                     // we are entering final values part (in section 1)
  325.                     inFinalValuesPart = true;
  326.                 } else if (inFinalValuesPart) {
  327.                     matcher = SECTION_1_DATA_OLD_FORMAT.matcher(line);
  328.                     if (matcher.matches()) {
  329.                         // this is a data line, build an entry from the extracted fields
  330.                         final int mjd = Integer.parseInt(matcher.group(1));
  331.                         mjdMin = FastMath.min(mjdMin, mjd);
  332.                         mjdMax = FastMath.max(mjdMax, mjd);
  333.                     } else {
  334.                         matcher = FINAL_VALUES_END.matcher(line);
  335.                         if (matcher.matches()) {
  336.                             // we leave final values part
  337.                             return;
  338.                         }
  339.                     }
  340.                 }
  341.             }

  342.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  343.                                       name, lineNumber);

  344.         }

  345.         /** Read EOP data from section 2 in the old bulletin B format.
  346.          * @param isNonRotatingOrigin if true, the file contain Non-Rotating Origin nutation corrections
  347.          * @param reader reader from where file content is obtained
  348.          * @param name name of the file (or zip entry)
  349.          * @exception IOException if data can't be read
  350.          * @exception OrekitException if some data is missing or if some loader specific error occurs
  351.          */
  352.         private void loadEOPOldFormat(final boolean isNonRotatingOrigin,
  353.                                       final BufferedReader reader, final String name)
  354.             throws OrekitException, IOException {

  355.             // read the data lines in the final values part inside section 2
  356.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  357.                 lineNumber++;
  358.                 final Matcher matcher = SECTION_2_DATA_OLD_FORMAT.matcher(line);
  359.                 if (matcher.matches()) {
  360.                     // this is a data line, build an entry from the extracted fields
  361.                     final int    mjd   = Integer.parseInt(matcher.group(1));
  362.                     final double x     = Double.parseDouble(matcher.group(2)) * Constants.ARC_SECONDS_TO_RADIANS;
  363.                     final double y     = Double.parseDouble(matcher.group(3)) * Constants.ARC_SECONDS_TO_RADIANS;
  364.                     final double dtu1  = Double.parseDouble(matcher.group(4));
  365.                     final double lod   = Double.parseDouble(matcher.group(5)) * MILLI_SECONDS_TO_SECONDS;
  366.                     if (mjd >= mjdMin) {
  367.                         final AbsoluteDate mjdDate =
  368.                                 new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  369.                                                  TimeScalesFactory.getUTC());
  370.                         final double[] equinox;
  371.                         final double[] nro;
  372.                         if (isNonRotatingOrigin) {
  373.                             nro = new double[] {
  374.                                 Double.parseDouble(matcher.group(6)) * MILLI_ARC_SECONDS_TO_RADIANS,
  375.                                 Double.parseDouble(matcher.group(7)) * MILLI_ARC_SECONDS_TO_RADIANS
  376.                             };
  377.                             equinox = converter.toEquinox(mjdDate, nro[0], nro[1]);
  378.                         } else {
  379.                             equinox = new double[] {
  380.                                 Double.parseDouble(matcher.group(6)) * MILLI_ARC_SECONDS_TO_RADIANS,
  381.                                 Double.parseDouble(matcher.group(7)) * MILLI_ARC_SECONDS_TO_RADIANS
  382.                             };
  383.                             nro = converter.toNonRotating(mjdDate, equinox[0], equinox[1]);
  384.                         }
  385.                         history.add(new EOPEntry(mjd, dtu1, lod, x, y, equinox[0], equinox[1], nro[0], nro[1]));
  386.                         if (mjd >= mjdMax) {
  387.                             // don't bother reading the rest of the file
  388.                             return;
  389.                         }
  390.                     }
  391.                 }
  392.             }

  393.         }

  394.         /** Read X, Y, UT1-UTC, dx, dy from section 1 in the new bulletin B format.
  395.          * @param reader reader from where file content is obtained
  396.          * @param name name of the file (or zip entry)
  397.          * @exception IOException if data can't be read
  398.          * @exception OrekitException if some data is missing or if some loader specific error occurs
  399.          */
  400.         private void loadXYDTDxDyNewFormat(final BufferedReader reader, final String name)
  401.             throws OrekitException, IOException {

  402.             boolean inFinalValuesPart = false;
  403.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  404.                 lineNumber++;
  405.                 Matcher matcher = FINAL_VALUES_START.matcher(line);
  406.                 if (matcher.matches()) {
  407.                     // we are entering final values part (in section 1)
  408.                     inFinalValuesPart = true;
  409.                 } else if (inFinalValuesPart) {
  410.                     matcher = SECTION_1_DATA_NEW_FORMAT.matcher(line);
  411.                     if (matcher.matches()) {
  412.                         // this is a data line, build an entry from the extracted fields
  413.                         final int year  = Integer.parseInt(matcher.group(1));
  414.                         final int month = Integer.parseInt(matcher.group(2));
  415.                         final int day   = Integer.parseInt(matcher.group(3));
  416.                         final int mjd   = Integer.parseInt(matcher.group(4));
  417.                         if (new DateComponents(year, month, day).getMJD() != mjd) {
  418.                             throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  419.                                                       name, year, month, day, mjd);
  420.                         }
  421.                         mjdMin = FastMath.min(mjdMin, mjd);
  422.                         mjdMax = FastMath.max(mjdMax, mjd);
  423.                         final double x    = Double.parseDouble(matcher.group(5)) * MILLI_ARC_SECONDS_TO_RADIANS;
  424.                         final double y    = Double.parseDouble(matcher.group(6)) * MILLI_ARC_SECONDS_TO_RADIANS;
  425.                         final double dtu1 = Double.parseDouble(matcher.group(7)) * MILLI_SECONDS_TO_SECONDS;
  426.                         final double dx   = Double.parseDouble(matcher.group(8)) * MILLI_ARC_SECONDS_TO_RADIANS;
  427.                         final double dy   = Double.parseDouble(matcher.group(9)) * MILLI_ARC_SECONDS_TO_RADIANS;
  428.                         fieldsMap.put(mjd,
  429.                                       new double[] {
  430.                                           dtu1, Double.NaN, x, y, dx, dy
  431.                                       });
  432.                     } else {
  433.                         matcher = FINAL_VALUES_END.matcher(line);
  434.                         if (matcher.matches()) {
  435.                             // we leave final values part
  436.                             return;
  437.                         }
  438.                     }
  439.                 }
  440.             }
  441.         }

  442.         /** Read LOD from section 3 in the new bulletin B format.
  443.          * @param reader reader from where file content is obtained
  444.          * @param name name of the file (or zip entry)
  445.          * @exception IOException if data can't be read
  446.          * @exception OrekitException if some data is missing or if some loader specific error occurs
  447.          */
  448.         private void loadLODNewFormat(final BufferedReader reader, final String name)
  449.             throws OrekitException, IOException {
  450.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  451.                 lineNumber++;
  452.                 final Matcher matcher = SECTION_3_DATA_NEW_FORMAT.matcher(line);
  453.                 if (matcher.matches()) {
  454.                     // this is a data line, build an entry from the extracted fields
  455.                     final int    mjd = Integer.parseInt(matcher.group(1));
  456.                     if (mjd >= mjdMin) {
  457.                         final double lod = Double.parseDouble(matcher.group(2)) * MILLI_SECONDS_TO_SECONDS;
  458.                         final double[] array = fieldsMap.get(mjd);
  459.                         if (array == null) {
  460.                             throw notifyUnexpectedErrorEncountered(name);
  461.                         }
  462.                         array[1] = lod;
  463.                         if (mjd >= mjdMax) {
  464.                             // don't bother reading the rest of the file
  465.                             return;
  466.                         }
  467.                     }
  468.                 }
  469.             }
  470.         }

  471.         /** Create an exception to be thrown.
  472.          * @param name name of the file (or zip entry)
  473.          * @return OrekitException always thrown to notify an unexpected error has been
  474.          * encountered by the caller
  475.          */
  476.         private OrekitException notifyUnexpectedErrorEncountered(final String name) {
  477.             String loaderName = BulletinBFilesLoader.class.getName();
  478.             loaderName = loaderName.substring(loaderName.lastIndexOf('.') + 1);
  479.             return new OrekitException(OrekitMessages.UNEXPECTED_FILE_FORMAT_ERROR_FOR_LOADER,
  480.                                        name, loaderName);
  481.         }

  482.     }

  483. }