1 /* Copyright 2002-2025 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.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.SortedSet;
30 import java.util.function.Supplier;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import org.hipparchus.util.FastMath;
35 import org.orekit.data.DataLoader;
36 import org.orekit.data.DataProvidersManager;
37 import org.orekit.errors.OrekitException;
38 import org.orekit.errors.OrekitInternalError;
39 import org.orekit.errors.OrekitMessages;
40 import org.orekit.time.AbsoluteDate;
41 import org.orekit.time.DateComponents;
42 import org.orekit.time.TimeScale;
43 import org.orekit.utils.IERSConventions;
44 import org.orekit.utils.units.UnitsConverter;
45
46 /** Loader for bulletin A files.
47 * <p>Bulletin A files contain {@link EOPEntry
48 * Earth Orientation Parameters} for a few days periods, they
49 * correspond to rapid data estimations, suitable for near-real time
50 * and prediction purposes. Prediction series are only available for
51 * pole motion xp, yp and UT1-UTC, they are not available for
52 * pole offsets (Δδψ/Δδε and x/y).</p>
53 * <p>A bulletin A published on Modified Julian Day mjd (nominally a
54 * Thursday) will generally contain:
55 * </p>
56 * <ul>
57 * <li>rapid service xp, yp and UT1-UTC data from mjd-6 to mjd</li>
58 * <li>prediction xp, yp and UT1-UTC data from mjd+1 to mjd+365</li>
59 * <li>if it is first bulletin of month m, final values xp, yp and
60 * UT1-UTC data from day 2 of month m-2 to day 1 of month m-1</li>
61 * <li>rapid service pole offsets Δδψ/Δδε and x/y if available, for some
62 * varying period somewhere from mjd-30 to mjd-10 (see below)</li>
63 * <li>if it is first bulletin of month m, final values pole offsets
64 * Δδψ/Δδε and x/y data from day 2 of month m-2 to day 1 of month
65 * m-1</li>
66 * </ul>
67 * <p>
68 * There are some discrepancies in the rapid service time range above,
69 * mainly when the nominal publication Thursday corresponds to holidays.
70 * In this case a bulletin may be published the day before and have a 6
71 * days span only for rapid data, and a later bulletin will have an 8 days
72 * span to recover the normal schedule. This occurred for bulletin A Vol.
73 * XVIII No. 047, bulletin A Vol. XVIII No. 048, bulletin A Vol. XXI No.
74 * 052 and bulletin A Vol. XXII No. 001.
75 * </p>
76 * <p>Rapid service for pole offsets appears irregular. As extreme examples
77 * bulletin A Vol. XXVI No. 037 from 2013-09-12 contained 15 entries
78 * for pole offsets, from mjd-22 to mjd-8, bulletin A Vol. XXVI No. 039
79 * from 2013-09-26 contained only 3 entries for pole offsets, from mjd-15
80 * to mjd-13, and bulletin A Vol. XXVI No. 040 from 2013-10-03 contained no
81 * rapid service pole offsets at all, it contained only final values. Despite
82 * this irregularity, rapid service data is continuous over consecutive files,
83 * so the mean number of entries is 7 as the files are published on a weekly
84 * basis.
85 * </p>
86 * <p>
87 * There are no prediction data for pole offsets.
88 * </p>
89 * <p>
90 * This loader reads both the rapid service, the prediction and the final
91 * values parts. As successive files have overlaps between all these sections,
92 * values extracted from latest files (with respect to the covered dates)
93 * override values extracted from earlier files, regardless of the files
94 * reading order. If numerous bulletins A covering more than one year are read,
95 * one particular date will typically appear in the prediction section of
96 * 52 or 53 files, then in the rapid data section of one file, then it will
97 * be missing in a few files, and will finally appear a last time in the
98 * final values sections of a last file. In this case, the value retained
99 * will be the one extracted from the final values section in the more
100 * recent file.
101 * </p>
102 * <p>
103 * If only one bulletin A file is read and it correspond to the first bulletin
104 * of a month, it will have a roughly one month wide hole between the
105 * final data and the rapid data. This hole will trigger an error as EOP
106 * continuity is checked by default for at most 5 days holes. In this case,
107 * users should call something like {@link Frames#setEOPContinuityThreshold(double)
108 * FramesFactory.setEOPContinuityThreshold(Constants.JULIAN_YEAR)} to prevent
109 * the error to be triggered.
110 * </p>
111 * <p>The bulletin A files are recognized thanks to their base names,
112 * which must match the pattern <code>bulletina-xxxx-###.txt</code>,
113 * (or the same ending with <code>.gz</code> for gzip-compressed files)
114 * where x stands for a roman numeral character and # stands for a digit
115 * character.</p>
116 * <p>
117 * Bulletin A in csv format must be read using {@link EopCsvFilesLoader} rather
118 * than using this loader. Bulletin A in xml format must be read using {@link EopXmlLoader}
119 * rather than using this loader.
120 * </p>
121 * <p>
122 * This class is immutable and hence thread-safe
123 * </p>
124 * @author Luc Maisonobe
125 * @since 7.0
126 * @see EopCsvFilesLoader
127 * @see EopXmlLoader
128 */
129 class BulletinAFilesLoader extends AbstractEopLoader implements EopHistoryLoader {
130
131 /** Regular expression matching blanks at start of line. */
132 private static final String LINE_START_REGEXP = "^\\p{Blank}+";
133
134 /** Regular expression matching blanks at end of line. */
135 private static final String LINE_END_REGEXP = "\\p{Blank}*$";
136
137 /** Regular expression matching integers. */
138 private static final String INTEGER_REGEXP = "[-+]?\\p{Digit}+";
139
140 /** Regular expression matching real numbers. */
141 private static final String REAL_REGEXP = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
142
143 /** Regular expression matching an integer field to store. */
144 private static final String STORED_INTEGER_FIELD = "\\p{Blank}*(" + INTEGER_REGEXP + ")";
145
146 /** regular expression matching a Modified Julian Day field to store. */
147 private static final String STORED_MJD_FIELD = "\\p{Blank}+(\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})";
148
149 /** Regular expression matching a real field to store. */
150 private static final String STORED_REAL_FIELD = "\\p{Blank}+(" + REAL_REGEXP + ")";
151
152 /** Regular expression matching a real field to ignore. */
153 private static final String IGNORED_REAL_FIELD = "\\p{Blank}+" + REAL_REGEXP;
154
155 /** Enum for files sections, in expected order.
156 * <p>The bulletin A weekly data files contain several sections,
157 * each introduced with some fixed header text and followed by tabular data.
158 * </p>
159 */
160 private enum Section {
161
162 /** Earth Orientation Parameters rapid service. */
163 // section 2 always contain rapid service data including error fields
164 // COMBINED EARTH ORIENTATION PARAMETERS:
165 //
166 // IERS Rapid Service
167 // MJD x error y error UT1-UTC error
168 // " " " " s s
169 // 13 8 30 56534 0.16762 .00009 0.32705 .00009 0.038697 0.000019
170 // 13 8 31 56535 0.16669 .00010 0.32564 .00010 0.038471 0.000019
171 // 13 9 1 56536 0.16592 .00009 0.32410 .00010 0.038206 0.000024
172 // 13 9 2 56537 0.16557 .00009 0.32270 .00009 0.037834 0.000024
173 // 13 9 3 56538 0.16532 .00009 0.32147 .00010 0.037351 0.000024
174 // 13 9 4 56539 0.16488 .00009 0.32044 .00010 0.036756 0.000023
175 // 13 9 5 56540 0.16435 .00009 0.31948 .00009 0.036036 0.000024
176 EOP_RAPID_SERVICE("^ *COMBINED EARTH ORIENTATION PARAMETERS: *$",
177 LINE_START_REGEXP +
178 STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
179 STORED_MJD_FIELD +
180 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
181 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
182 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
183 LINE_END_REGEXP),
184
185 /** Earth Orientation Parameters final values. */
186 // the first bulletin A of each month also includes final values for the
187 // period covering from day 2 of month m-2 to day 1 of month m-1.
188 // IERS Final Values
189 // MJD x y UT1-UTC
190 // " " s
191 // 13 7 2 56475 0.1441 0.3901 0.05717
192 // 13 7 3 56476 0.1457 0.3895 0.05716
193 // 13 7 4 56477 0.1467 0.3887 0.05728
194 // 13 7 5 56478 0.1477 0.3875 0.05755
195 // 13 7 6 56479 0.1490 0.3862 0.05793
196 // 13 7 7 56480 0.1504 0.3849 0.05832
197 // 13 7 8 56481 0.1516 0.3835 0.05858
198 // 13 7 9 56482 0.1530 0.3822 0.05877
199 EOP_FINAL_VALUES("^ *IERS Final Values *$",
200 LINE_START_REGEXP +
201 STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
202 STORED_MJD_FIELD +
203 STORED_REAL_FIELD +
204 STORED_REAL_FIELD +
205 STORED_REAL_FIELD +
206 LINE_END_REGEXP),
207
208 /** Earth Orientation Parameters prediction. */
209 // section 3 always contain prediction data without error fields
210 //
211 // PREDICTIONS:
212 // The following formulas will not reproduce the predictions given below,
213 // but may be used to extend the predictions beyond the end of this table.
214 //
215 // x = 0.0969 + 0.1110 cos A - 0.0103 sin A - 0.0435 cos C - 0.0171 sin C
216 // y = 0.3457 - 0.0061 cos A - 0.1001 sin A - 0.0171 cos C + 0.0435 sin C
217 // UT1-UTC = -0.0052 - 0.00104 (MJD - 56548) - (UT2-UT1)
218 //
219 // where A = 2*pi*(MJD-56540)/365.25 and C = 2*pi*(MJD-56540)/435.
220 //
221 // TAI-UTC(MJD 56541) = 35.0
222 // The accuracy may be estimated from the expressions:
223 // S x,y = 0.00068 (MJD-56540)**0.80 S t = 0.00025 (MJD-56540)**0.75
224 // Estimated accuracies are: Predictions 10 d 20 d 30 d 40 d
225 // Polar coord's 0.004 0.007 0.010 0.013
226 // UT1-UTC 0.0014 0.0024 0.0032 0.0040
227 //
228 // MJD x(arcsec) y(arcsec) UT1-UTC(sec)
229 // 2013 9 6 56541 0.1638 0.3185 0.03517
230 // 2013 9 7 56542 0.1633 0.3175 0.03420
231 // 2013 9 8 56543 0.1628 0.3164 0.03322
232 // 2013 9 9 56544 0.1623 0.3153 0.03229
233 // 2013 9 10 56545 0.1618 0.3142 0.03144
234 // 2013 9 11 56546 0.1612 0.3131 0.03071
235 // 2013 9 12 56547 0.1607 0.3119 0.03008
236 EOP_PREDICTION("^ *PREDICTIONS: *$",
237 LINE_START_REGEXP +
238 STORED_INTEGER_FIELD + STORED_INTEGER_FIELD + STORED_INTEGER_FIELD +
239 STORED_MJD_FIELD +
240 STORED_REAL_FIELD +
241 STORED_REAL_FIELD +
242 STORED_REAL_FIELD +
243 LINE_END_REGEXP),
244
245 /** Pole offsets, IAU-1980. */
246 // section 4 may contain rapid service pole offset series including error fields
247 // CELESTIAL POLE OFFSET SERIES:
248 // NEOS Celestial Pole Offset Series
249 // MJD dpsi error deps error
250 // (msec. of arc)
251 // 56519 -87.47 0.13 -12.96 0.08
252 // 56520 -87.72 0.13 -13.20 0.08
253 // 56521 -87.79 0.19 -13.56 0.11
254 POLE_OFFSETS_IAU_1980_RAPID_SERVICE("^ *NEOS Celestial Pole Offset Series *$",
255 LINE_START_REGEXP +
256 STORED_MJD_FIELD +
257 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
258 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
259 LINE_END_REGEXP),
260
261 /** Pole offsets, IAU-1980 final values. */
262 // the first bulletin A of each month also includes final values for the
263 // period covering from day 2 of month m-2 to day 1 of month m-1.
264 // IERS Celestial Pole Offset Final Series
265 // MJD dpsi deps
266 // (msec. of arc)
267 // 56475 -81.0 -13.3
268 // 56476 -81.2 -13.4
269 // 56477 -81.6 -13.4
270 // 56478 -82.2 -13.5
271 // 56479 -82.5 -13.6
272 // 56480 -82.5 -13.7
273 POLE_OFFSETS_IAU_1980_FINAL_VALUES("^ *IERS Celestial Pole Offset Final Series *$",
274 LINE_START_REGEXP +
275 STORED_MJD_FIELD +
276 STORED_REAL_FIELD +
277 STORED_REAL_FIELD +
278 LINE_END_REGEXP),
279
280 /** Pole offsets, IAU-2000. */
281 // the format for the IAU-2000 series is similar, but the meanings of the fields
282 // are different
283 // IAU2000A Celestial Pole Offset Series
284 // MJD dX error dY error
285 // (msec. of arc)
286 // 56519 -0.246 0.052 -0.223 0.080
287 // 56520 -0.239 0.052 -0.248 0.080
288 // 56521 -0.224 0.076 -0.277 0.110
289 POLE_OFFSETS_IAU_2000_RAPID_SERVICE("^ *IAU2000A Celestial Pole Offset Series *$",
290 LINE_START_REGEXP +
291 STORED_MJD_FIELD +
292 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
293 STORED_REAL_FIELD + IGNORED_REAL_FIELD +
294 LINE_END_REGEXP),
295
296 /** Pole offsets, IAU-2000 final values. */
297 // the format for the IAU-2000 series is similar, but the meanings of the fields
298 // are different
299 // IAU2000A Celestial Pole Offset Final Series
300 // MJD dX dY
301 // (msec. of arc)
302 // 56475 0.00 -0.28
303 // 56476 -0.06 -0.29
304 // 56477 -0.07 -0.27
305 // 56478 -0.12 -0.33
306 // 56479 -0.12 -0.33
307 // 56480 -0.13 -0.36
308 POLE_OFFSETS_IAU_2000_FINAL_VALUES("^ *IAU2000A Celestial Pole Offset Final Series *$",
309 LINE_START_REGEXP +
310 STORED_MJD_FIELD +
311 STORED_REAL_FIELD +
312 STORED_REAL_FIELD +
313 LINE_END_REGEXP);
314
315 /** Header pattern. */
316 private final Pattern header;
317
318 /** Data pattern. */
319 private final Pattern data;
320
321 /** Simple constructor.
322 * @param headerRegExp regular expression for header
323 * @param dataRegExp regular expression for data
324 */
325 Section(final String headerRegExp, final String dataRegExp) {
326 this.header = Pattern.compile(headerRegExp);
327 this.data = Pattern.compile(dataRegExp);
328 }
329
330 /** Check if a line matches the section header.
331 * @param line line to check
332 * @return true if the line matches the header
333 */
334 public boolean matchesHeader(final String line) {
335 return header.matcher(line).matches();
336 }
337
338 /** Get the data fields from a line.
339 * @param line line to parse
340 * @return extracted fields, or null if line does not match data format
341 */
342 public String[] getFields(final String line) {
343 final Matcher matcher = data.matcher(line);
344 if (matcher.matches()) {
345 final String[] fields = new String[matcher.groupCount()];
346 for (int i = 0; i < fields.length; ++i) {
347 fields[i] = matcher.group(i + 1);
348 }
349 return fields;
350 } else {
351 return null;
352 }
353 }
354
355 }
356
357 /** Build a loader for IERS bulletins A files.
358 * @param supportedNames regular expression for supported files names
359 * @param manager provides access to the bulletin A files.
360 * @param utcSupplier UTC time scale.
361 */
362 BulletinAFilesLoader(final String supportedNames,
363 final DataProvidersManager manager,
364 final Supplier<TimeScale> utcSupplier) {
365 super(supportedNames, manager, utcSupplier);
366 }
367
368 /** {@inheritDoc} */
369 public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
370 final SortedSet<EOPEntry> history) {
371 final Parser parser = new Parser();
372 this.feed(parser);
373 parser.fill(history);
374 }
375
376 /** Internal class performing the parsing. */
377 private class Parser implements DataLoader {
378
379 /** Map for xp, yp, dut1 fields read in different sections. */
380 private final Map<Integer, double[]> eopFieldsMap;
381
382 /** Map for pole offsets fields read in different sections. */
383 private final Map<Integer, double[]> poleOffsetsFieldsMap;
384
385 /** Map for EOP data type. */
386 private final Map<Integer, EopDataType> eopDataTypeMap;
387
388 /** Configuration for ITRF versions. */
389 private final ItrfVersionProvider itrfVersionProvider;
390
391 /** ITRF version configuration. */
392 private ITRFVersionLoader.ITRFVersionConfiguration configuration;
393
394 /** File name. */
395 private String fileName;
396
397 /** Current line number. */
398 private int lineNumber;
399
400 /** Current line. */
401 private String line;
402
403 /** Earliest parsed data. */
404 private int mjdMin;
405
406 /** Latest parsed data. */
407 private int mjdMax;
408
409 /** First MJD parsed in current file. */
410 private int firstMJD;
411
412 /** Simple constructor.
413 */
414 Parser() {
415 this.eopFieldsMap = new HashMap<>();
416 this.poleOffsetsFieldsMap = new HashMap<>();
417 this.eopDataTypeMap = new HashMap<>();
418 this.itrfVersionProvider = new ITRFVersionLoader(
419 ITRFVersionLoader.SUPPORTED_NAMES,
420 getDataProvidersManager());
421 this.lineNumber = 0;
422 this.mjdMin = Integer.MAX_VALUE;
423 this.mjdMax = Integer.MIN_VALUE;
424 this.firstMJD = -1;
425 }
426
427 /** {@inheritDoc} */
428 public boolean stillAcceptsData() {
429 return true;
430 }
431
432 /** {@inheritDoc} */
433 public void loadData(final InputStream input, final String name)
434 throws IOException {
435
436 this.configuration = null;
437 this.fileName = name;
438
439 // set up a reader for line-oriented bulletin A files
440 try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
441 lineNumber = 0;
442 firstMJD = -1;
443
444 // loop over sections
445 final List<Section> remaining = new ArrayList<>(Arrays.asList(Section.values()));
446 for (Section section = nextSection(remaining, reader);
447 section != null;
448 section = nextSection(remaining, reader)) {
449
450 final EopDataType eopDataType;
451 switch (section) {
452 case EOP_FINAL_VALUES:
453 case POLE_OFFSETS_IAU_1980_FINAL_VALUES:
454 case POLE_OFFSETS_IAU_2000_FINAL_VALUES:
455 eopDataType = EopDataType.FINAL;
456 break;
457 case EOP_RAPID_SERVICE:
458 case POLE_OFFSETS_IAU_1980_RAPID_SERVICE:
459 case POLE_OFFSETS_IAU_2000_RAPID_SERVICE:
460 eopDataType = EopDataType.RAPID;
461 break;
462 case EOP_PREDICTION:
463 eopDataType = EopDataType.PREDICTED;
464 break;
465 default:
466 eopDataType = EopDataType.UNKNOWN;
467 }
468
469 switch (section) {
470 case EOP_RAPID_SERVICE :
471 case EOP_FINAL_VALUES :
472 case EOP_PREDICTION :
473 loadXYDT(section, reader, name, eopDataType);
474 break;
475 case POLE_OFFSETS_IAU_1980_RAPID_SERVICE :
476 case POLE_OFFSETS_IAU_1980_FINAL_VALUES :
477 loadPoleOffsets(section, false, reader, name, eopDataType);
478 break;
479 case POLE_OFFSETS_IAU_2000_RAPID_SERVICE :
480 case POLE_OFFSETS_IAU_2000_FINAL_VALUES :
481 loadPoleOffsets(section, true, reader, name, eopDataType);
482 break;
483 default :
484 // this should never happen
485 throw new OrekitInternalError(null);
486 }
487
488 // remove the already parsed section from the list
489 remaining.remove(section);
490
491 }
492
493 // check that the mandatory sections have been parsed
494 if (remaining.contains(Section.EOP_RAPID_SERVICE) ||
495 remaining.contains(Section.EOP_PREDICTION) ||
496 (remaining.contains(Section.POLE_OFFSETS_IAU_1980_RAPID_SERVICE) ^
497 remaining.contains(Section.POLE_OFFSETS_IAU_2000_RAPID_SERVICE)) ||
498 (remaining.contains(Section.POLE_OFFSETS_IAU_1980_FINAL_VALUES) ^
499 remaining.contains(Section.POLE_OFFSETS_IAU_2000_FINAL_VALUES))) {
500 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
501 }
502
503 }
504 }
505
506 /** Fill EOP history obtained after reading several files.
507 * @param history history to fill up
508 */
509 public void fill(final SortedSet<EOPEntry> history) {
510
511 double[] currentEOP = null;
512 double[] nextEOP = eopFieldsMap.get(mjdMin);
513 for (int mjd = mjdMin; mjd <= mjdMax; ++mjd) {
514
515 final AbsoluteDate mjdDate = AbsoluteDate.createMJDDate(mjd, 0, getUtc());
516 final double[] currentPole = poleOffsetsFieldsMap.get(mjd);
517 final EopDataType eopDataType = eopDataTypeMap.get(mjd);
518
519 currentEOP = nextEOP;
520 nextEOP = eopFieldsMap.get(mjd + 1);
521
522 if (currentEOP == null) {
523 if (currentPole != null) {
524 // we have only pole offsets for this date
525 if (configuration == null || !configuration.isValid(mjd)) {
526 // get a configuration for current name and date range
527 configuration = itrfVersionProvider.getConfiguration(fileName, mjd);
528 }
529 history.add(new EOPEntry(mjd,
530 0.0, Double.NaN, 0.0, 0.0, Double.NaN, Double.NaN,
531 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[1]),
532 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[2]),
533 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[3]),
534 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[4]),
535 configuration.getVersion(),
536 mjdDate, eopDataType));
537 }
538 } else {
539
540 if (configuration == null || !configuration.isValid(mjd)) {
541 // get a configuration for current name and date range
542 configuration = itrfVersionProvider.getConfiguration(fileName, mjd);
543 }
544 if (currentPole == null) {
545 // we have only EOP for this date
546 history.add(new EOPEntry(mjd,
547 currentEOP[3], Double.NaN,
548 UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[1]),
549 UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[2]),
550 Double.NaN, Double.NaN,
551 0.0, 0.0, 0.0, 0.0,
552 configuration.getVersion(),
553 mjdDate, eopDataType));
554 } else {
555 // we have complete data
556 history.add(new EOPEntry(mjd,
557 currentEOP[3], Double.NaN,
558 UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[1] ),
559 UnitsConverter.ARC_SECONDS_TO_RADIANS.convert(currentEOP[2] ),
560 Double.NaN, Double.NaN,
561 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[1]),
562 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[2]),
563 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[3]),
564 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(currentPole[4]),
565 configuration.getVersion(),
566 mjdDate, eopDataType));
567 }
568 }
569
570 }
571
572 }
573
574 /** Skip to next section header.
575 * @param sections sections to check for
576 * @param reader reader from where file content is obtained
577 * @return the next section or null if no section is found until end of file
578 * @exception IOException if data can't be read
579 */
580 private Section nextSection(final List<Section> sections,
581 final BufferedReader reader)
582 throws IOException {
583
584 for (line = reader.readLine(); line != null; line = reader.readLine()) {
585 ++lineNumber;
586 for (Section section : sections) {
587 if (section.matchesHeader(line)) {
588 return section;
589 }
590 }
591 }
592
593 // we have reached end of file and not found a matching section header
594 return null;
595
596 }
597
598 /** Read X, Y, UT1-UTC.
599 * @param section section to parse
600 * @param reader reader from where file content is obtained
601 * @param name name of the file (or zip entry)
602 * @param eopDataType EOP data type
603 * @exception IOException if data can't be read
604 */
605 private void loadXYDT(final Section section, final BufferedReader reader, final String name, final EopDataType eopDataType)
606 throws IOException {
607
608 boolean inValuesPart = false;
609 for (line = reader.readLine(); line != null; line = reader.readLine()) {
610 lineNumber++;
611 final String[] fields = section.getFields(line);
612 if (fields != null) {
613
614 // we are within the values part
615 inValuesPart = true;
616
617 // this is a data line, build an entry from the extracted fields
618 final int year = Integer.parseInt(fields[0]);
619 final int month = Integer.parseInt(fields[1]);
620 final int day = Integer.parseInt(fields[2]);
621 final int mjd = Integer.parseInt(fields[3]);
622 final DateComponents dc = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd);
623 if ((dc.getYear() % 100) != (year % 100) ||
624 dc.getMonth() != month ||
625 dc.getDay() != day) {
626 throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
627 name, year, month, day, mjd);
628 }
629 mjdMin = FastMath.min(mjdMin, mjd);
630 mjdMax = FastMath.max(mjdMax, mjd);
631 if (firstMJD < 0) {
632 // store the first mjd parsed
633 firstMJD = mjd;
634 }
635
636 // get the entry at the same date if it was already parsed
637 final double[] eop;
638 if (eopFieldsMap.containsKey(mjd)) {
639 eop = eopFieldsMap.get(mjd);
640 } else {
641 eop = new double[4];
642 eopFieldsMap.put(mjd, eop);
643 }
644
645 if (eop[0] <= firstMJD) {
646 // either it is the first time we parse this date (eop[0] = 0),
647 // or the new parsed data is from a more recent file
648 // in both case, we should update the array
649 eop[0] = firstMJD;
650 eop[1] = Double.parseDouble(fields[4]);
651 eop[2] = Double.parseDouble(fields[5]);
652 eop[3] = Double.parseDouble(fields[6]);
653 eopDataTypeMap.put(mjd, eopDataType);
654 }
655
656 } else if (inValuesPart) {
657 // we leave values part
658 return;
659 }
660 }
661
662 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
663 name, lineNumber);
664
665 }
666
667 /** Read EOP data.
668 * @param section section to parse
669 * @param isNonRotatingOrigin if true, the file contain Non-Rotating Origin nutation corrections
670 * @param reader reader from where file content is obtained
671 * @param name name of the file (or zip entry)
672 * @param eopDataType EOP data type
673 * @exception IOException if data can't be read
674 */
675 private void loadPoleOffsets(final Section section, final boolean isNonRotatingOrigin,
676 final BufferedReader reader, final String name, final EopDataType eopDataType)
677 throws IOException {
678
679 boolean inValuesPart = false;
680 for (line = reader.readLine(); line != null; line = reader.readLine()) {
681 lineNumber++;
682 final String[] fields = section.getFields(line);
683 if (fields != null) {
684
685 // we are within the values part
686 inValuesPart = true;
687
688 // this is a data line, build an entry from the extracted fields
689 final int mjd = Integer.parseInt(fields[0]);
690 mjdMin = FastMath.min(mjdMin, mjd);
691 mjdMax = FastMath.max(mjdMax, mjd);
692
693 // get the entry at the same date if it was already parsed
694 final double[] pole;
695 if (poleOffsetsFieldsMap.containsKey(mjd)) {
696 pole = poleOffsetsFieldsMap.get(mjd);
697 } else {
698 pole = new double[5];
699 poleOffsetsFieldsMap.put(mjd, pole);
700 }
701
702 if (pole[0] <= firstMJD) {
703 // either it is the first time we parse this date (pole[0] = 0),
704 // or the new parsed data is from a more recent file
705 // in both case, we should update the array
706 pole[0] = firstMJD;
707 if (isNonRotatingOrigin) {
708 pole[1] = Double.parseDouble(fields[1]);
709 pole[2] = Double.parseDouble(fields[2]);
710 } else {
711 pole[3] = Double.parseDouble(fields[1]);
712 pole[4] = Double.parseDouble(fields[2]);
713 }
714 eopDataTypeMap.put(mjd, eopDataType);
715 }
716
717 } else if (inValuesPart) {
718 // we leave values part
719 return;
720 }
721 }
722
723 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
724 name, lineNumber);
725
726 }
727
728 }
729
730 }