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 }