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