EOPC04FilesLoader.java

  1. /* Copyright 2002-2020 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 to match the columns header. */
  73.     private static final Pattern COLUMNS_HEADER_PATTERN;

  74.     /** Pattern for data lines. */
  75.     private static final Pattern DATA_LINE_PATTERN;

  76.     /** Year field. */
  77.     private static final int YEAR_FIELD;

  78.     /** Month field. */
  79.     private static final int MONTH_FIELD;

  80.     /** Day field. */
  81.     private static final int DAY_FIELD;

  82.     /** MJD field. */
  83.     private static final int MJD_FIELD;

  84.     /** X component of pole motion field. */
  85.     private static final int POLE_X_FIELD;

  86.     /** Y component of pole motion field. */
  87.     private static final int POLE_Y_FIELD;

  88.     /** UT1-UTC field. */
  89.     private static final int UT1_UTC_FIELD;

  90.     /** LoD field. */
  91.     private static final int LOD_FIELD;

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

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

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

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

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

  125.     }

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

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

  147.     /** Internal class performing the parsing. */
  148.     static class Parser extends AbstractEopParser {

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

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

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

  166.             // set up a reader for line-oriented bulletin B files
  167.             final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));

  168.             // reset parse info to start new file (do not clear history!)
  169.             int lineNumber              = 0;
  170.             boolean inHeader            = true;
  171.             boolean isNonRotatingOrigin = false;

  172.             // read all file
  173.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  174.                 ++lineNumber;
  175.                 boolean parsed = false;

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

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

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

  224.                 }
  225.                 if (!(inHeader || parsed)) {
  226.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  227.                             lineNumber, name, line);
  228.                 }
  229.             }

  230.             // check if we have read something
  231.             if (inHeader) {
  232.                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  233.             }

  234.             return history;
  235.         }

  236.     }

  237. }