RapidDataAndPredictionColumnsLoader.java

  1. /* Copyright 2002-2024 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.List;
  26. import java.util.SortedSet;
  27. import java.util.function.Supplier;
  28. import java.util.regex.Matcher;
  29. import java.util.regex.Pattern;

  30. import org.orekit.data.DataProvidersManager;
  31. import org.orekit.errors.OrekitException;
  32. import org.orekit.errors.OrekitMessages;
  33. import org.orekit.time.AbsoluteDate;
  34. import org.orekit.time.DateComponents;
  35. import org.orekit.time.TimeScale;
  36. import org.orekit.utils.IERSConventions;
  37. import org.orekit.utils.IERSConventions.NutationCorrectionConverter;
  38. import org.orekit.utils.units.UnitsConverter;

  39. /** Loader for IERS rapid data and prediction files in columns format (finals file).
  40.  * <p>Rapid data and prediction files contain {@link EOPEntry
  41.  * Earth Orientation Parameters} for several years periods, in one file
  42.  * only that is updated regularly.</p>
  43.  * <p>
  44.  * These files contain both the data from IERS Bulletin A and IERS bulletin B.
  45.  * This class parses only the part from Bulletin A.
  46.  * </p>
  47.  * <p>The rapid data and prediction file is recognized thanks to its base name,
  48.  * which must match one of the the patterns <code>finals.*</code> or
  49.  * <code>finals2000A.*</code> (or the same ending with <code>.gz</code>
  50.  * for gzip-compressed files) where * stands for a word like "all", "daily",
  51.  * or "data". The file with 2000A in their name correspond to the
  52.  * IAU-2000 precession-nutation model whereas the files without any identifier
  53.  * correspond to the IAU-1980 precession-nutation model. The files with the all
  54.  * suffix start from 1973-01-01, the file with the data suffix start
  55.  * from 1992-01-01 and the files with the daily suffix.</p>
  56.  * <p>
  57.  * This class is immutable and hence thread-safe
  58.  * </p>
  59.  * @author Romain Di Costanzo
  60.  * @see <a href="http://maia.usno.navy.mil/ser7/readme.finals2000A">finals2000A file format description at USNO</a>
  61.  * @see <a href="http://maia.usno.navy.mil/ser7/readme.finals">finals file format description at USNO</a>
  62.  */
  63. class RapidDataAndPredictionColumnsLoader extends AbstractEopLoader
  64.         implements EopHistoryLoader {

  65.     /** Field for year, month and day parsing. */
  66.     private static final String  INTEGER2_FIELD               = "((?:\\p{Blank}|\\p{Digit})\\p{Digit})";

  67.     /** Field for modified Julian day parsing. */
  68.     private static final String  MJD_FIELD                    = "\\p{Blank}+(\\p{Digit}+)(?:\\.00*)";

  69.     /** Field for separator parsing. */
  70.     private static final String  SEPARATOR                    = "\\p{Blank}*[IP]";

  71.     /** Field for real parsing. */
  72.     private static final String  REAL_FIELD                   = "\\p{Blank}*(-?\\p{Digit}*\\.\\p{Digit}*)";

  73.     /** Start index of the date part of the line. */
  74.     private static int DATE_START = 0;

  75.     /** end index of the date part of the line. */
  76.     private static int DATE_END   = 15;

  77.     /** Pattern to match the date part of the line (always present). */
  78.     private static final Pattern DATE_PATTERN = Pattern.compile(INTEGER2_FIELD + INTEGER2_FIELD + INTEGER2_FIELD + MJD_FIELD);

  79.     /** Start index of the pole part of the line (from bulletin A). */
  80.     private static int POLE_START_A = 16;

  81.     /** end index of the pole part of the line (from bulletin A). */
  82.     private static int POLE_END_A   = 55;

  83.     /** Pattern to match the pole part of the line (from bulletin A). */
  84.     private static final Pattern POLE_PATTERN_A = Pattern.compile(SEPARATOR + REAL_FIELD + REAL_FIELD + REAL_FIELD + REAL_FIELD);

  85.     /** Start index of the pole part of the line (from bulletin B). */
  86.     private static int POLE_START_B = 134;

  87.     /** end index of the pole part of the line (from bulletin B). */
  88.     private static int POLE_END_B   = 154;

  89.     /** Pattern to match the pole part of the line (from bulletin B). */
  90.     private static final Pattern POLE_PATTERN_B = Pattern.compile(REAL_FIELD + REAL_FIELD);

  91.     /** Start index of the UT1-UTC part of the line (from bulletin A). */
  92.     private static int UT1_UTC_START_A = 57;

  93.     /** end index of the UT1-UTC part of the line (from bulletin A). */
  94.     private static int UT1_UTC_END_A   = 78;

  95.     /** Pattern to match the UT1-UTC part of the line (from bulletin A). */
  96.     private static final Pattern UT1_UTC_PATTERN_A = Pattern.compile(SEPARATOR + REAL_FIELD + REAL_FIELD);

  97.     /** Start index of the UT1-UTC part of the line (from bulletin B). */
  98.     private static int UT1_UTC_START_B = 154;

  99.     /** end index of the UT1-UTC part of the line (from bulletin B). */
  100.     private static int UT1_UTC_END_B   = 165;

  101.     /** Pattern to match the UT1-UTC part of the line (from bulletin B). */
  102.     private static final Pattern UT1_UTC_PATTERN_B = Pattern.compile(REAL_FIELD);

  103.     /** Start index of the LOD part of the line (from bulletin A). */
  104.     private static int LOD_START_A = 79;

  105.     /** end index of the LOD part of the line (from bulletin A). */
  106.     private static int LOD_END_A   = 93;

  107.     /** Pattern to match the LOD part of the line (from bulletin A). */
  108.     private static final Pattern LOD_PATTERN_A = Pattern.compile(REAL_FIELD + REAL_FIELD);

  109.     // there are no LOD part from bulletin B

  110.     /** Start index of the nutation part of the line (from bulletin A). */
  111.     private static int NUTATION_START_A = 95;

  112.     /** end index of the nutation part of the line (from bulletin A). */
  113.     private static int NUTATION_END_A   = 134;

  114.     /** Pattern to match the nutation part of the line (from bulletin A). */
  115.     private static final Pattern NUTATION_PATTERN_A = Pattern.compile(SEPARATOR + REAL_FIELD + REAL_FIELD + REAL_FIELD + REAL_FIELD);

  116.     /** Start index of the nutation part of the line (from bulletin B). */
  117.     private static int NUTATION_START_B = 165;

  118.     /** end index of the nutation part of the line (from bulletin B). */
  119.     private static int NUTATION_END_B   = 185;

  120.     /** Pattern to match the nutation part of the line (from bulletin B). */
  121.     private static final Pattern NUTATION_PATTERN_B = Pattern.compile(REAL_FIELD + REAL_FIELD);

  122.     /** Type of nutation corrections. */
  123.     private final boolean isNonRotatingOrigin;

  124.     /** Build a loader for IERS bulletins B files.
  125.      * @param isNonRotatingOrigin if true the supported files <em>must</em>
  126.      * contain δX/δY nutation corrections, otherwise they
  127.      * <em>must</em> contain δΔψ/δΔε nutation
  128.      * corrections
  129.      * @param supportedNames regular expression for supported files names
  130.      * @param manager provides access to EOP data files.
  131.      * @param utcSupplier UTC time scale.
  132.      */
  133.     RapidDataAndPredictionColumnsLoader(final boolean isNonRotatingOrigin,
  134.                                         final String supportedNames,
  135.                                         final DataProvidersManager manager,
  136.                                         final Supplier<TimeScale> utcSupplier) {
  137.         super(supportedNames, manager, utcSupplier);
  138.         this.isNonRotatingOrigin = isNonRotatingOrigin;
  139.     }

  140.     /** {@inheritDoc} */
  141.     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
  142.                             final SortedSet<EOPEntry> history) {
  143.         final ItrfVersionProvider itrfVersionProvider = new ITRFVersionLoader(
  144.                 ITRFVersionLoader.SUPPORTED_NAMES,
  145.                 getDataProvidersManager());
  146.         final Parser parser =
  147.                 new Parser(converter, itrfVersionProvider, getUtc(), isNonRotatingOrigin);
  148.         final EopParserLoader loader = new EopParserLoader(parser);
  149.         this.feed(loader);
  150.         history.addAll(loader.getEop());
  151.     }

  152.     /** Internal class performing the parsing. */
  153.     static class Parser extends AbstractEopParser {

  154.         /** Indicator for Non-Rotating Origin. */
  155.         private final boolean isNonRotatingOrigin;

  156.         /** Simple constructor.
  157.          * @param converter converter to use
  158.          * @param itrfVersionProvider to use for determining the ITRF version of the EOP.
  159.          * @param utc time scale for parsing dates.
  160.          * @param isNonRotatingOrigin type of nutation correction
  161.          */
  162.         Parser(final NutationCorrectionConverter converter,
  163.                final ItrfVersionProvider itrfVersionProvider,
  164.                final TimeScale utc,
  165.                final boolean isNonRotatingOrigin) {
  166.             super(converter, itrfVersionProvider, utc);
  167.             this.isNonRotatingOrigin = isNonRotatingOrigin;
  168.         }

  169.         /** {@inheritDoc} */
  170.         @Override
  171.         public Collection<EOPEntry> parse(final InputStream input, final String name)
  172.             throws IOException {

  173.             final List<EOPEntry> history = new ArrayList<>();
  174.             ITRFVersionLoader.ITRFVersionConfiguration configuration = null;

  175.             // reset parse info to start new file (do not clear history!)
  176.             int lineNumber = 0;

  177.             // set up a reader for line-oriented bulletin B files
  178.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

  179.                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {

  180.                     lineNumber++;

  181.                     // split the lines in its various columns (some of them can be blank)
  182.                     final String datePart       = getPart(line, DATE_START,       DATE_END);
  183.                     final String polePartA      = getPart(line, POLE_START_A,     POLE_END_A);
  184.                     final String ut1utcPartA    = getPart(line, UT1_UTC_START_A,  UT1_UTC_END_A);
  185.                     final String lodPartA       = getPart(line, LOD_START_A,      LOD_END_A);
  186.                     final String nutationPartA  = getPart(line, NUTATION_START_A, NUTATION_END_A);
  187.                     final String polePartB      = getPart(line, POLE_START_B,     POLE_END_B);
  188.                     final String ut1utcPartB    = getPart(line, UT1_UTC_START_B,  UT1_UTC_END_B);
  189.                     final String nutationPartB  = getPart(line, NUTATION_START_B, NUTATION_END_B);

  190.                     // parse the date part
  191.                     final Matcher dateMatcher = DATE_PATTERN.matcher(datePart);
  192.                     final int mjd;
  193.                     if (dateMatcher.matches()) {
  194.                         final int yy = Integer.parseInt(dateMatcher.group(1).trim());
  195.                         final int mm = Integer.parseInt(dateMatcher.group(2).trim());
  196.                         final int dd = Integer.parseInt(dateMatcher.group(3).trim());
  197.                         mjd = Integer.parseInt(dateMatcher.group(4).trim());
  198.                         final DateComponents reconstructedDate = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd);
  199.                         if ((reconstructedDate.getYear() % 100) != yy ||
  200.                              reconstructedDate.getMonth()       != mm ||
  201.                              reconstructedDate.getDay()         != dd) {
  202.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  203.                                                       lineNumber, name, line);
  204.                         }
  205.                     } else {
  206.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  207.                                                   lineNumber, name, line);
  208.                     }

  209.                     // parse the pole part
  210.                     final double x;
  211.                     final double y;
  212.                     if (polePartB.trim().length() == 0) {
  213.                         // pole part from bulletin B is blank
  214.                         if (polePartA.trim().length() == 0) {
  215.                             // pole part from bulletin A is blank
  216.                             x = 0;
  217.                             y = 0;
  218.                         } else {
  219.                             final Matcher poleAMatcher = POLE_PATTERN_A.matcher(polePartA);
  220.                             if (poleAMatcher.matches()) {
  221.                                 x = UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(poleAMatcher.group(1)));
  222.                                 y = UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(poleAMatcher.group(3)));
  223.                             } else {
  224.                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  225.                                                           lineNumber, name, line);
  226.                             }
  227.                         }
  228.                     } else {
  229.                         final Matcher poleBMatcher = POLE_PATTERN_B.matcher(polePartB);
  230.                         if (poleBMatcher.matches()) {
  231.                             x = UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(poleBMatcher.group(1)));
  232.                             y = UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(poleBMatcher.group(2)));
  233.                         } else {
  234.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  235.                                                       lineNumber, name, line);
  236.                         }
  237.                     }

  238.                     // parse the UT1-UTC part
  239.                     final double dtu1;
  240.                     if (ut1utcPartB.trim().length() == 0) {
  241.                         // UT1-UTC part from bulletin B is blank
  242.                         if (ut1utcPartA.trim().length() == 0) {
  243.                             // UT1-UTC part from bulletin A is blank
  244.                             dtu1 = 0;
  245.                         } else {
  246.                             final Matcher ut1utcAMatcher = UT1_UTC_PATTERN_A.matcher(ut1utcPartA);
  247.                             if (ut1utcAMatcher.matches()) {
  248.                                 dtu1 = Double.parseDouble(ut1utcAMatcher.group(1));
  249.                             } else {
  250.                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  251.                                                           lineNumber, name, line);
  252.                             }
  253.                         }
  254.                     } else {
  255.                         final Matcher ut1utcBMatcher = UT1_UTC_PATTERN_B.matcher(ut1utcPartB);
  256.                         if (ut1utcBMatcher.matches()) {
  257.                             dtu1 = Double.parseDouble(ut1utcBMatcher.group(1));
  258.                         } else {
  259.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  260.                                                       lineNumber, name, line);
  261.                         }
  262.                     }

  263.                     // parse the lod part
  264.                     final double lod;
  265.                     if (lodPartA.trim().length() == 0) {
  266.                         // lod part from bulletin A is blank
  267.                         lod = Double.NaN;
  268.                     } else {
  269.                         final Matcher lodAMatcher = LOD_PATTERN_A.matcher(lodPartA);
  270.                         if (lodAMatcher.matches()) {
  271.                             lod = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(lodAMatcher.group(1)));
  272.                         } else {
  273.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  274.                                                       lineNumber, name, line);
  275.                         }
  276.                     }

  277.                     // parse the nutation part
  278.                     final double[] nro;
  279.                     final double[] equinox;
  280.                     final AbsoluteDate mjdDate =
  281.                             new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  282.                                     getUtc());
  283.                     if (nutationPartB.trim().length() == 0) {
  284.                         // nutation part from bulletin B is blank
  285.                         if (nutationPartA.trim().length() == 0) {
  286.                             // nutation part from bulletin A is blank
  287.                             nro     = new double[2];
  288.                             equinox = new double[2];
  289.                         } else {
  290.                             final Matcher nutationAMatcher = NUTATION_PATTERN_A.matcher(nutationPartA);
  291.                             if (nutationAMatcher.matches()) {
  292.                                 if (isNonRotatingOrigin) {
  293.                                     nro = new double[] {
  294.                                         UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationAMatcher.group(1))),
  295.                                         UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationAMatcher.group(3)))
  296.                                     };
  297.                                     equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
  298.                                 } else {
  299.                                     equinox = new double[] {
  300.                                         UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationAMatcher.group(1))),
  301.                                         UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationAMatcher.group(3)))
  302.                                     };
  303.                                     nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
  304.                                 }
  305.                             } else {
  306.                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  307.                                                           lineNumber, name, line);
  308.                             }
  309.                         }
  310.                     } else {
  311.                         final Matcher nutationBMatcher = NUTATION_PATTERN_B.matcher(nutationPartB);
  312.                         if (nutationBMatcher.matches()) {
  313.                             if (isNonRotatingOrigin) {
  314.                                 nro = new double[] {
  315.                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationBMatcher.group(1))),
  316.                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationBMatcher.group(2)))
  317.                                 };
  318.                                 equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
  319.                             } else {
  320.                                 equinox = new double[] {
  321.                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationBMatcher.group(1))),
  322.                                     UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(nutationBMatcher.group(2)))
  323.                                 };
  324.                                 nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
  325.                             }
  326.                         } else {
  327.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  328.                                                       lineNumber, name, line);
  329.                         }
  330.                     }

  331.                     if (configuration == null || !configuration.isValid(mjd)) {
  332.                         // get a configuration for current name and date range
  333.                         configuration = getItrfVersionProvider().getConfiguration(name, mjd);
  334.                     }
  335.                     history.add(new EOPEntry(mjd, dtu1, lod, x, y, Double.NaN, Double.NaN,
  336.                                              equinox[0], equinox[1], nro[0], nro[1],
  337.                                              configuration.getVersion(), mjdDate));

  338.                 }

  339.             }

  340.             return history;
  341.         }

  342.     }

  343.     /** Get a part of a line.
  344.      * @param line line to analyze
  345.      * @param start start index of the part
  346.      * @param end end index of the part
  347.      * @return either the line part if present or an empty string if line is too short
  348.      * @since 11.1
  349.      */
  350.     private static String getPart(final String line, final int start, final int end) {
  351.         return (line.length() >= end) ? line.substring(start, end) : "";
  352.     }

  353. }