EOPC04FilesLoader.java

  1. /* Copyright 2002-2018 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.List;
  24. import java.util.SortedSet;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.Pattern;

  27. import org.orekit.data.DataLoader;
  28. import org.orekit.data.DataProvidersManager;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.time.DateComponents;
  33. import org.orekit.time.TimeScalesFactory;
  34. import org.orekit.utils.Constants;
  35. import org.orekit.utils.IERSConventions;

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

  69.     /** Pattern to match the columns header. */
  70.     private static final Pattern COLUMNS_HEADER_PATTERN;

  71.     /** Pattern for data lines. */
  72.     private static final Pattern DATA_LINE_PATTERN;

  73.     /** Year field. */
  74.     private static final int YEAR_FIELD;

  75.     /** Month field. */
  76.     private static final int MONTH_FIELD;

  77.     /** Day field. */
  78.     private static final int DAY_FIELD;

  79.     /** MJD field. */
  80.     private static final int MJD_FIELD;

  81.     /** X component of pole motion field. */
  82.     private static final int POLE_X_FIELD;

  83.     /** Y component of pole motion field. */
  84.     private static final int POLE_Y_FIELD;

  85.     /** UT1-UTC field. */
  86.     private static final int UT1_UTC_FIELD;

  87.     /** LoD field. */
  88.     private static final int LOD_FIELD;

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

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

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

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

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

  122.     }

  123.     /** Regular expression for supported files names. */
  124.     private final String supportedNames;

  125.     /** Build a loader for IERS EOP C04 files.
  126.      * @param supportedNames regular expression for supported files names
  127.      */
  128.     EOPC04FilesLoader(final String supportedNames) {
  129.         this.supportedNames = supportedNames;
  130.     }

  131.     /** {@inheritDoc} */
  132.     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
  133.                             final SortedSet<EOPEntry> history)
  134.         throws OrekitException {
  135.         final Parser parser = new Parser(converter);
  136.         DataProvidersManager.getInstance().feed(supportedNames, parser);
  137.         history.addAll(parser.history);
  138.     }

  139.     /** Internal class performing the parsing. */
  140.     private static class Parser implements DataLoader {

  141.         /** Converter for nutation corrections. */
  142.         private final IERSConventions.NutationCorrectionConverter converter;

  143.         /** Configuration for ITRF versions. */
  144.         private final ITRFVersionLoader itrfVersionLoader;

  145.         /** History entries. */
  146.         private final List<EOPEntry> history;

  147.         /** Current line number. */
  148.         private int lineNumber;

  149.         /** Current line. */
  150.         private String line;

  151.         /** Indicator for header parsing. */
  152.         private boolean inHeader;

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

  155.         /** Simple constructor.
  156.          * @param converter converter to use
  157.          * @exception OrekitException if ITRF version loader cannot be parsed
  158.          */
  159.         Parser(final IERSConventions.NutationCorrectionConverter converter)
  160.             throws OrekitException {
  161.             this.converter           = converter;
  162.             this.itrfVersionLoader   = new ITRFVersionLoader(ITRFVersionLoader.SUPPORTED_NAMES);
  163.             this.history             = new ArrayList<EOPEntry>();
  164.             this.lineNumber          = 0;
  165.             this.inHeader            = true;
  166.             this.isNonRotatingOrigin = false;
  167.         }

  168.         /** {@inheritDoc} */
  169.         public boolean stillAcceptsData() {
  170.             return true;
  171.         }

  172.         /** {@inheritDoc} */
  173.         public void loadData(final InputStream input, final String name)
  174.             throws IOException, OrekitException {

  175.             ITRFVersionLoader.ITRFVersionConfiguration configuration = null;

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

  178.             // reset parse info to start new file (do not clear history!)
  179.             lineNumber          = 0;
  180.             inHeader            = true;
  181.             isNonRotatingOrigin = false;

  182.             // read all file
  183.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  184.                 ++lineNumber;
  185.                 boolean parsed = false;

  186.                 if (inHeader) {
  187.                     final Matcher matcher = COLUMNS_HEADER_PATTERN.matcher(line);
  188.                     if (matcher.matches()) {
  189.                         if (matcher.group(1).startsWith("dX")) {
  190.                             isNonRotatingOrigin = true;
  191.                         }
  192.                     }
  193.                 }

  194.                 if (DATA_LINE_PATTERN.matcher(line).matches()) {
  195.                     inHeader = false;
  196.                     // this is a data line, build an entry from the extracted fields
  197.                     final String[] fields = line.split(" +");
  198.                     final DateComponents dc = new DateComponents(Integer.parseInt(fields[YEAR_FIELD]),
  199.                                                                  Integer.parseInt(fields[MONTH_FIELD]),
  200.                                                                  Integer.parseInt(fields[DAY_FIELD]));
  201.                     final int    mjd   = Integer.parseInt(fields[MJD_FIELD]);
  202.                     if (dc.getMJD() != mjd) {
  203.                         throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  204.                                                   name, dc.getYear(), dc.getMonth(), dc.getDay(), mjd);
  205.                     }
  206.                     final AbsoluteDate date = new AbsoluteDate(dc, TimeScalesFactory.getUTC());

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

  234.                 }
  235.                 if (!(inHeader || parsed)) {
  236.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  237.                                               lineNumber, name, line);
  238.                 }
  239.             }

  240.             // check if we have read something
  241.             if (inHeader) {
  242.                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  243.             }

  244.         }

  245.     }

  246. }