EOPC04FilesLoader.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.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.Constants;
  37. import org.orekit.utils.IERSConventions;
  38. import org.orekit.utils.IERSConventions.NutationCorrectionConverter;

  39. /** Loader for EOP xx C04 files.
  40.  * <p>EOP xx C04 files contain {@link EOPEntry
  41.  * Earth Orientation Parameters} consistent with ITRF20xx for one year periods, with various
  42.  * xx (05, 08, 14) depending on the data source.</p>
  43.  * <p>The EOP xx C04 files retrieved from the ftp site
  44.  * <a href="ftp://ftp.iers.org/products/eop/long-term/">ftp://ftp.iers.org/products/eop/long-term/</a>
  45.  * are recognized thanks to their base names, which must match one of the patterns
  46.  * {@code eopc04_##_IAU2000.##} or {@code eopc04_##.##} (or the same ending with <code>.gz</code> for
  47.  * gzip-compressed files) where # stands for a digit character.</p>
  48.  * <p>
  49.  * Beware that files retrieved from the web site
  50.  * <a href="http://hpiers.obspm.fr/eoppc/eop/">http://hpiers.obspm.fr/eoppc/eop/</a> do
  51.  * <em>not</em> follow the same naming convention. They lack the _05, _08 or _14
  52.  * marker (and for EOP 14 C04 even the directory does not have this marker anymore...),
  53.  * so all the files in this site have the same name and can easily be confused. This is the reason
  54.  * why the default regular expression in {@link FramesFactory} does not recognize them and
  55.  * enforces the year marker presence.
  56.  * </p>
  57.  * <p>Between 2002 and 2007, another series of Earth Orientation Parameters was
  58.  * in use: EOPC04 (without the _##). These parameters were consistent with the
  59.  * previous ITRS realization: ITRF2000.</p>
  60.  * <p>Since 2008, several series of Earth Orientation Parameters have been available:
  61.  * EOP 05 C04 consistent with ITRF 2005, EOP 08 C04 consistent with ITRF 2008, and
  62.  * EOP 14 C04 consistent with ITRF 2014. As of mid 2017, only the EOP 08 C04 and
  63.  * EOP 14 C04 are updated for current years.</p>
  64.  * <p>Files are available at IERS FTP site: <a
  65.  * href="ftp://ftp.iers.org/products/eop/long-term/">ftp://ftp.iers.org/products/eop/long-term/</a>.</p>
  66.  * <p>
  67.  * This class is immutable and hence thread-safe
  68.  * </p>
  69.  * @author Luc Maisonobe
  70.  */
  71. class EOPC04FilesLoader extends AbstractEopLoader implements EOPHistoryLoader {

  72.     /** Pattern for delimiting regular expressions. */
  73.     private static final Pattern SEPARATOR;

  74.     /** Pattern to match the columns header. */
  75.     private static final Pattern COLUMNS_HEADER_PATTERN;

  76.     /** Pattern for data lines. */
  77.     private static final Pattern DATA_LINE_PATTERN;

  78.     /** Year field. */
  79.     private static final int YEAR_FIELD;

  80.     /** Month field. */
  81.     private static final int MONTH_FIELD;

  82.     /** Day field. */
  83.     private static final int DAY_FIELD;

  84.     /** MJD field. */
  85.     private static final int MJD_FIELD;

  86.     /** X component of pole motion field. */
  87.     private static final int POLE_X_FIELD;

  88.     /** Y component of pole motion field. */
  89.     private static final int POLE_Y_FIELD;

  90.     /** UT1-UTC field. */
  91.     private static final int UT1_UTC_FIELD;

  92.     /** LoD field. */
  93.     private static final int LOD_FIELD;

  94.     /** Correction for nutation first field (either dX or dPsi). */
  95.     private static final int NUT_0_FIELD;

  96.     /** Correction for nutation second field (either dY or dEps). */
  97.     private static final int NUT_1_FIELD;

  98.     static {

  99.         SEPARATOR = Pattern.compile(" +");

  100.         // Header have either the following form:
  101.         //       Date      MJD      x          y        UT1-UTC       LOD         dPsi      dEps       x Err     y Err   UT1-UTC Err  LOD Err    dPsi Err   dEpsilon Err
  102.         //                          "          "           s           s            "         "        "          "          s           s            "         "
  103.         //      (0h UTC)
  104.         // or the following form:
  105.         //       Date      MJD      x          y        UT1-UTC       LOD         dX        dY        x Err     y Err   UT1-UTC Err  LOD Err     dX Err       dY Err
  106.         //                          "          "           s           s          "         "           "          "          s         s            "           "
  107.         //      (0h UTC)
  108.         //
  109.         COLUMNS_HEADER_PATTERN = Pattern.compile("^ *Date +MJD +x +y +UT1-UTC +LOD +((?:dPsi +dEps)|(?:dX +dY)) .*");

  110.         // The data lines in the EOP C04 yearly data files have the following fixed form:
  111.         // year month day MJD ...12 floating values fields in decimal format...
  112.         // 2000   1   1  51544   0.043242   0.377915   0.3554777   ...
  113.         // 2000   1   2  51545   0.043515   0.377753   0.3546065   ...
  114.         // 2000   1   3  51546   0.043623   0.377452   0.3538444   ...
  115.         // the corresponding fortran format is:
  116.         // 3(I4),I7,2(F11.6),2(F12.7),2(F12.6),2(F11.6),2(F12.7),2F12.6
  117.         DATA_LINE_PATTERN = Pattern.compile("^\\d+ +\\d+ +\\d+ +\\d+(?: +-?\\d+\\.\\d+){12}$");

  118.         YEAR_FIELD    = 0;
  119.         MONTH_FIELD   = 1;
  120.         DAY_FIELD     = 2;
  121.         MJD_FIELD     = 3;
  122.         POLE_X_FIELD  = 4;
  123.         POLE_Y_FIELD  = 5;
  124.         UT1_UTC_FIELD = 6;
  125.         LOD_FIELD     = 7;
  126.         NUT_0_FIELD   = 8;
  127.         NUT_1_FIELD   = 9;

  128.     }

  129.     /** Build a loader for IERS EOP C04 files.
  130.      * @param supportedNames regular expression for supported files names
  131.      * @param manager provides access to the EOP C04 files.
  132.      * @param utcSupplier UTC time scale.
  133.      */
  134.     EOPC04FilesLoader(final String supportedNames,
  135.                       final DataProvidersManager manager,
  136.                       final Supplier<TimeScale> utcSupplier) {
  137.         super(supportedNames, manager, utcSupplier);
  138.     }

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

  150.     /** Internal class performing the parsing. */
  151.     static class Parser extends AbstractEopParser {

  152.         /**
  153.          * Simple constructor.
  154.          *
  155.          * @param converter           converter to use
  156.          * @param itrfVersionProvider to use for determining the ITRF version of the EOP.
  157.          * @param utc                 time scale for parsing dates.
  158.          */
  159.         Parser(final NutationCorrectionConverter converter,
  160.                final ItrfVersionProvider itrfVersionProvider,
  161.                final TimeScale utc) {
  162.             super(converter, itrfVersionProvider, utc);
  163.         }

  164.         /** {@inheritDoc} */
  165.         public Collection<EOPEntry> parse(final InputStream input, final String name)
  166.             throws IOException, OrekitException {

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

  169.             // set up a reader for line-oriented bulletin B files
  170.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  171.                 // reset parse info to start new file (do not clear history!)
  172.                 int lineNumber              = 0;
  173.                 boolean inHeader            = true;
  174.                 boolean isNonRotatingOrigin = false;

  175.                 // read all file
  176.                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  177.                     ++lineNumber;
  178.                     boolean parsed = false;

  179.                     if (inHeader) {
  180.                         final Matcher matcher = COLUMNS_HEADER_PATTERN.matcher(line);
  181.                         if (matcher.matches()) {
  182.                             if (matcher.group(1).startsWith("dX")) {
  183.                                 isNonRotatingOrigin = true;
  184.                             }
  185.                         }
  186.                     }

  187.                     if (DATA_LINE_PATTERN.matcher(line).matches()) {
  188.                         inHeader = false;
  189.                         // this is a data line, build an entry from the extracted fields
  190.                         final String[] fields = SEPARATOR.split(line);
  191.                         final DateComponents dc = new DateComponents(Integer.parseInt(fields[YEAR_FIELD]),
  192.                                                                      Integer.parseInt(fields[MONTH_FIELD]),
  193.                                                                      Integer.parseInt(fields[DAY_FIELD]));
  194.                         final int    mjd   = Integer.parseInt(fields[MJD_FIELD]);
  195.                         if (dc.getMJD() != mjd) {
  196.                             throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  197.                                                       name, dc.getYear(), dc.getMonth(), dc.getDay(), mjd);
  198.                         }
  199.                         final AbsoluteDate date = new AbsoluteDate(dc, getUtc());

  200.                         // the first six fields are consistent with the expected format
  201.                         final double x     = Double.parseDouble(fields[POLE_X_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS;
  202.                         final double y     = Double.parseDouble(fields[POLE_Y_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS;
  203.                         final double dtu1  = Double.parseDouble(fields[UT1_UTC_FIELD]);
  204.                         final double lod   = Double.parseDouble(fields[LOD_FIELD]);
  205.                         final double[] equinox;
  206.                         final double[] nro;
  207.                         if (isNonRotatingOrigin) {
  208.                             nro = new double[] {
  209.                                 Double.parseDouble(fields[NUT_0_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS,
  210.                                 Double.parseDouble(fields[NUT_1_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS
  211.                             };
  212.                             equinox = getConverter().toEquinox(date, nro[0], nro[1]);
  213.                         } else {
  214.                             equinox = new double[] {
  215.                                 Double.parseDouble(fields[NUT_0_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS,
  216.                                 Double.parseDouble(fields[NUT_1_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS
  217.                             };
  218.                             nro = getConverter().toNonRotating(date, equinox[0], equinox[1]);
  219.                         }
  220.                         if (configuration == null || !configuration.isValid(mjd)) {
  221.                             // get a configuration for current name and date range
  222.                             configuration = getItrfVersionProvider().getConfiguration(name, mjd);
  223.                         }
  224.                         history.add(new EOPEntry(mjd, dtu1, lod, x, y, equinox[0], equinox[1], nro[0], nro[1],
  225.                                                  configuration.getVersion(), date));
  226.                         parsed = true;

  227.                     }
  228.                     if (!(inHeader || parsed)) {
  229.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  230.                                 lineNumber, name, line);
  231.                     }
  232.                 }

  233.                 // check if we have read something
  234.                 if (inHeader) {
  235.                     throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  236.                 }
  237.             }

  238.             return history;
  239.         }

  240.     }

  241. }