1   /* Copyright 2002-2021 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  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.SortedSet;
28  import java.util.function.Supplier;
29  import java.util.regex.Matcher;
30  import java.util.regex.Pattern;
31  
32  import org.orekit.data.DataProvidersManager;
33  import org.orekit.errors.OrekitException;
34  import org.orekit.errors.OrekitMessages;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.time.DateComponents;
37  import org.orekit.time.TimeScale;
38  import org.orekit.utils.Constants;
39  import org.orekit.utils.IERSConventions;
40  import org.orekit.utils.IERSConventions.NutationCorrectionConverter;
41  
42  /** Loader for EOP xx C04 files.
43   * <p>EOP xx C04 files contain {@link EOPEntry
44   * Earth Orientation Parameters} consistent with ITRF20xx for one year periods, with various
45   * xx (05, 08, 14) depending on the data source.</p>
46   * <p>The EOP xx C04 files retrieved from the ftp site
47   * <a href="ftp://ftp.iers.org/products/eop/long-term/">ftp://ftp.iers.org/products/eop/long-term/</a>
48   * are recognized thanks to their base names, which must match one of the patterns
49   * {@code eopc04_##_IAU2000.##} or {@code eopc04_##.##} (or the same ending with <code>.gz</code> for
50   * gzip-compressed files) where # stands for a digit character.</p>
51   * <p>
52   * Beware that files retrieved from the web site
53   * <a href="http://hpiers.obspm.fr/eoppc/eop/">http://hpiers.obspm.fr/eoppc/eop/</a> do
54   * <em>not</em> follow the same naming convention. They lack the _05, _08 or _14
55   * marker (and for EOP 14 C04 even the directory does not have this marker anymore...),
56   * so all the files in this site have the same name and can easily be confused. This is the reason
57   * why the default regular expression in {@link FramesFactory} does not recognize them and
58   * enforces the year marker presence.
59   * </p>
60   * <p>Between 2002 and 2007, another series of Earth Orientation Parameters was
61   * in use: EOPC04 (without the _##). These parameters were consistent with the
62   * previous ITRS realization: ITRF2000.</p>
63   * <p>Since 2008, several series of Earth Orientation Parameters have been available:
64   * EOP 05 C04 consistent with ITRF 2005, EOP 08 C04 consistent with ITRF 2008, and
65   * EOP 14 C04 consistent with ITRF 2014. As of mid 2017, only the EOP 08 C04 and
66   * EOP 14 C04 are updated for current years.</p>
67   * <p>Files are available at IERS FTP site: <a
68   * href="ftp://ftp.iers.org/products/eop/long-term/">ftp://ftp.iers.org/products/eop/long-term/</a>.</p>
69   * <p>
70   * This class is immutable and hence thread-safe
71   * </p>
72   * @author Luc Maisonobe
73   */
74  class EOPC04FilesLoader extends AbstractEopLoader implements EOPHistoryLoader {
75  
76      /** Pattern for delimiting regular expressions. */
77      private static final Pattern SEPARATOR;
78  
79      /** Pattern to match the columns header. */
80      private static final Pattern COLUMNS_HEADER_PATTERN;
81  
82      /** Pattern for data lines. */
83      private static final Pattern DATA_LINE_PATTERN;
84  
85      /** Year field. */
86      private static final int YEAR_FIELD;
87  
88      /** Month field. */
89      private static final int MONTH_FIELD;
90  
91      /** Day field. */
92      private static final int DAY_FIELD;
93  
94      /** MJD field. */
95      private static final int MJD_FIELD;
96  
97      /** X component of pole motion field. */
98      private static final int POLE_X_FIELD;
99  
100     /** Y component of pole motion field. */
101     private static final int POLE_Y_FIELD;
102 
103     /** UT1-UTC field. */
104     private static final int UT1_UTC_FIELD;
105 
106     /** LoD field. */
107     private static final int LOD_FIELD;
108 
109     /** Correction for nutation first field (either dX or dPsi). */
110     private static final int NUT_0_FIELD;
111 
112     /** Correction for nutation second field (either dY or dEps). */
113     private static final int NUT_1_FIELD;
114 
115     static {
116 
117         SEPARATOR = Pattern.compile(" +");
118 
119         // Header have either the following form:
120         //       Date      MJD      x          y        UT1-UTC       LOD         dPsi      dEps       x Err     y Err   UT1-UTC Err  LOD Err    dPsi Err   dEpsilon Err
121         //                          "          "           s           s            "         "        "          "          s           s            "         "
122         //      (0h UTC)
123         // or the following form:
124         //       Date      MJD      x          y        UT1-UTC       LOD         dX        dY        x Err     y Err   UT1-UTC Err  LOD Err     dX Err       dY Err
125         //                          "          "           s           s          "         "           "          "          s         s            "           "
126         //      (0h UTC)
127         //
128         COLUMNS_HEADER_PATTERN = Pattern.compile("^ *Date +MJD +x +y +UT1-UTC +LOD +((?:dPsi +dEps)|(?:dX +dY)) .*");
129 
130         // The data lines in the EOP C04 yearly data files have the following fixed form:
131         // year month day MJD ...12 floating values fields in decimal format...
132         // 2000   1   1  51544   0.043242   0.377915   0.3554777   ...
133         // 2000   1   2  51545   0.043515   0.377753   0.3546065   ...
134         // 2000   1   3  51546   0.043623   0.377452   0.3538444   ...
135         // the corresponding fortran format is:
136         // 3(I4),I7,2(F11.6),2(F12.7),2(F12.6),2(F11.6),2(F12.7),2F12.6
137         DATA_LINE_PATTERN = Pattern.compile("^\\d+ +\\d+ +\\d+ +\\d+(?: +-?\\d+\\.\\d+){12}$");
138 
139         YEAR_FIELD    = 0;
140         MONTH_FIELD   = 1;
141         DAY_FIELD     = 2;
142         MJD_FIELD     = 3;
143         POLE_X_FIELD  = 4;
144         POLE_Y_FIELD  = 5;
145         UT1_UTC_FIELD = 6;
146         LOD_FIELD     = 7;
147         NUT_0_FIELD   = 8;
148         NUT_1_FIELD   = 9;
149 
150     }
151 
152     /** Build a loader for IERS EOP C04 files.
153      * @param supportedNames regular expression for supported files names
154      * @param manager provides access to the EOP C04 files.
155      * @param utcSupplier UTC time scale.
156      */
157     EOPC04FilesLoader(final String supportedNames,
158                       final DataProvidersManager manager,
159                       final Supplier<TimeScale> utcSupplier) {
160         super(supportedNames, manager, utcSupplier);
161     }
162 
163     /** {@inheritDoc} */
164     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
165                             final SortedSet<EOPEntry> history) {
166         final ItrfVersionProvider itrfVersionProvider = new ITRFVersionLoader(
167                 ITRFVersionLoader.SUPPORTED_NAMES,
168                 getDataProvidersManager());
169         final Parser parser = new Parser(converter, itrfVersionProvider, getUtc());
170         final EopParserLoader loader = new EopParserLoader(parser);
171         this.feed(loader);
172         history.addAll(loader.getEop());
173     }
174 
175     /** Internal class performing the parsing. */
176     static class Parser extends AbstractEopParser {
177 
178         /**
179          * Simple constructor.
180          *
181          * @param converter           converter to use
182          * @param itrfVersionProvider to use for determining the ITRF version of the EOP.
183          * @param utc                 time scale for parsing dates.
184          */
185         Parser(final NutationCorrectionConverter converter,
186                final ItrfVersionProvider itrfVersionProvider,
187                final TimeScale utc) {
188             super(converter, itrfVersionProvider, utc);
189         }
190 
191         /** {@inheritDoc} */
192         public Collection<EOPEntry> parse(final InputStream input, final String name)
193             throws IOException, OrekitException {
194 
195             final List<EOPEntry> history = new ArrayList<>();
196             ITRFVersionLoader.ITRFVersionConfiguration configuration = null;
197 
198             // set up a reader for line-oriented bulletin B files
199             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
200                 // reset parse info to start new file (do not clear history!)
201                 int lineNumber              = 0;
202                 boolean inHeader            = true;
203                 boolean isNonRotatingOrigin = false;
204 
205                 // read all file
206                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
207                     ++lineNumber;
208                     boolean parsed = false;
209 
210                     if (inHeader) {
211                         final Matcher matcher = COLUMNS_HEADER_PATTERN.matcher(line);
212                         if (matcher.matches()) {
213                             if (matcher.group(1).startsWith("dX")) {
214                                 isNonRotatingOrigin = true;
215                             }
216                         }
217                     }
218 
219                     if (DATA_LINE_PATTERN.matcher(line).matches()) {
220                         inHeader = false;
221                         // this is a data line, build an entry from the extracted fields
222                         final String[] fields = SEPARATOR.split(line);
223                         final DateComponents dc = new DateComponents(Integer.parseInt(fields[YEAR_FIELD]),
224                                                                      Integer.parseInt(fields[MONTH_FIELD]),
225                                                                      Integer.parseInt(fields[DAY_FIELD]));
226                         final int    mjd   = Integer.parseInt(fields[MJD_FIELD]);
227                         if (dc.getMJD() != mjd) {
228                             throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
229                                                       name, dc.getYear(), dc.getMonth(), dc.getDay(), mjd);
230                         }
231                         final AbsoluteDate date = new AbsoluteDate(dc, getUtc());
232 
233                         // the first six fields are consistent with the expected format
234                         final double x     = Double.parseDouble(fields[POLE_X_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS;
235                         final double y     = Double.parseDouble(fields[POLE_Y_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS;
236                         final double dtu1  = Double.parseDouble(fields[UT1_UTC_FIELD]);
237                         final double lod   = Double.parseDouble(fields[LOD_FIELD]);
238                         final double[] equinox;
239                         final double[] nro;
240                         if (isNonRotatingOrigin) {
241                             nro = new double[] {
242                                 Double.parseDouble(fields[NUT_0_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS,
243                                 Double.parseDouble(fields[NUT_1_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS
244                             };
245                             equinox = getConverter().toEquinox(date, nro[0], nro[1]);
246                         } else {
247                             equinox = new double[] {
248                                 Double.parseDouble(fields[NUT_0_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS,
249                                 Double.parseDouble(fields[NUT_1_FIELD]) * Constants.ARC_SECONDS_TO_RADIANS
250                             };
251                             nro = getConverter().toNonRotating(date, equinox[0], equinox[1]);
252                         }
253                         if (configuration == null || !configuration.isValid(mjd)) {
254                             // get a configuration for current name and date range
255                             configuration = getItrfVersionProvider().getConfiguration(name, mjd);
256                         }
257                         history.add(new EOPEntry(mjd, dtu1, lod, x, y, equinox[0], equinox[1], nro[0], nro[1],
258                                                  configuration.getVersion(), date));
259                         parsed = true;
260 
261                     }
262                     if (!(inHeader || parsed)) {
263                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
264                                 lineNumber, name, line);
265                     }
266                 }
267 
268                 // check if we have read something
269                 if (inHeader) {
270                     throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
271                 }
272             }
273 
274             return history;
275         }
276 
277     }
278 
279 }