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.Collection;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
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.orekit.data.DataProvidersManager;
32 import org.orekit.data.DataSource;
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.Month;
38 import org.orekit.time.TimeScale;
39 import org.orekit.utils.Constants;
40 import org.orekit.utils.IERSConventions;
41 import org.orekit.utils.IERSConventions.NutationCorrectionConverter;
42 import org.orekit.utils.units.UnitsConverter;
43
44 /** Loader for bulletin B files.
45 * <p>Bulletin B files contain {@link EOPEntry
46 * Earth Orientation Parameters} for a few months periods.
47 * They correspond to finalized data, suitable for long term
48 * a posteriori analysis.</p>
49 * <p>The bulletin B files are recognized thanks to their base names,
50 * which must match one of the patterns <code>bulletinb_IAU2000-###.txt</code>,
51 * <code>bulletinb_IAU2000.###</code>, <code>bulletinb-###.txt</code> or
52 * <code>bulletinb.###</code> (or the same ending with <code>.gz</code>
53 * for gzip-compressed files) where # stands for a digit character.</p>
54 * <p>
55 * Starting with bulletin B 252 published in February 2009, buletins B are
56 * written in a format containing nutation corrections for both the
57 * new IAU2000 nutation model as dx, dy entries in its section 1 and nutation
58 * corrections for the old IAU1976 nutation model as dPsi, dEpsilon entries in
59 * its section 2. These bulletins are available from IERS <a
60 * href="ftp://ftp.iers.org/products/eop/bulletinb/format_2009/">
61 * FTP site</a>. They are also available with exactly the same content
62 * (but a different naming convention) from <a
63 * href="https://hpiers.obspm.fr/eoppc/bul/bulb_new/">Paris-Meudon
64 * observatory site</a>.
65 * </p>
66 * <p>
67 * Ending with bulletin B 263 published in January 2010, bulletins B were
68 * written in a format containing only one type of nutation corrections in its
69 * section 1, either for new IAU2000 nutation model as dx, dy entries or the old
70 * IAU1976 nutation model as dPsi, dEpsilon entries, depending on the file (a pair of
71 * files with different name was published each month between March 2003 and January 2010).
72 * </p>
73 * <p>
74 * Bulletin B in csv format must be read using {@link EopCsvFilesLoader} rather
75 * than using this loader. Bulletin B in xml format must be read using {@link EopXmlLoader}
76 * rather than using this loader.
77 * </p>
78 * <p>
79 * This class handles both the old and the new format.
80 * </p>
81 * <p>
82 * This class is immutable and hence thread-safe
83 * </p>
84 * @author Luc Maisonobe
85 * @see EopCsvFilesLoader
86 * @see EopXmlLoader
87 */
88 class BulletinBFilesLoader extends AbstractEopLoader implements EopHistoryLoader {
89
90 /** Section 0 date pattern. */
91 private static final Pattern SECTION_0_DATE;
92
93 /** Section 1 header pattern. */
94 private static final Pattern SECTION_1_HEADER;
95
96 /** Section 2 header pattern for old format. */
97 private static final Pattern SECTION_2_HEADER_OLD;
98
99 /** Section 3 header pattern. */
100 private static final Pattern SECTION_3_HEADER;
101
102 /** Pattern for line introducing the final bulletin B values. */
103 private static final Pattern FINAL_VALUES_START;
104
105 /** Pattern for line introducing the bulletin B preliminary extension. */
106 private static final Pattern FINAL_VALUES_END;
107
108 /** Data line pattern in section 1 (old format). */
109 private static final Pattern SECTION_1_DATA_OLD_FORMAT;
110
111 /** Data line pattern in section 2. */
112 private static final Pattern SECTION_2_DATA_OLD_FORMAT;
113
114 /** Data line pattern in section 1 (new format). */
115 private static final Pattern SECTION_1_DATA_NEW_FORMAT;
116
117 /** Data line pattern in section 3 (new format). */
118 private static final Pattern SECTION_3_DATA_NEW_FORMAT;
119
120 static {
121
122 // the section headers lines in the old bulletin B monthly data files have
123 // the following form (the indentation discrepancy for section 6 is really
124 // present in the available files):
125 // BULLETIN B 220
126 // 1 june 2006
127 // 1 - EARTH ORIENTATION PARAMETERS (IERS evaluation).
128 // either
129 // 2 - SMOOTHED VALUES OF x, y, UT1, D, DPSI, DEPSILON (IERS EVALUATION)
130 // or
131 // 2 - SMOOTHED VALUES OF x, y, UT1, D, dX, dY (IERS EVALUATION)
132 // 3 - NORMAL VALUES OF THE EARTH ORIENTATION PARAMETERS AT FIVE-DAY INTERVALS
133 // 4 - DURATION OF THE DAY AND ANGULAR VELOCITY OF THE EARTH (IERS evaluation).
134 // 5 - INFORMATION ON TIME SCALES
135 // 6 - SUMMARY OF CONTRIBUTED EARTH ORIENTATION PARAMETERS SERIES
136 //
137 // the section headers lines in the new bulletin B monthly data files have
138 // the following form:
139 // BULLETIN B 216
140 // (IAU 2000)
141 // 31 january 2006
142 // 1 - DAILY FINAL VALUES OF x, y, UT1-UTC, dX, dY
143 // 2 - DAILY FINAL VALUES OF CELESTIAL POLE OFFSETS dPsi1980 & dEps1980
144 // 3 - EARTH ANGULAR VELOCITY : DAILY FINAL VALUES OF LOD, OMEGA AT 0hUTC
145 // 4 - INFORMATION ON TIME SCALES
146 // 5 - SUMMARY OF CONTRIBUTED EARTH ORIENTATION PARAMETERS SERIES
147 SECTION_0_DATE = Pattern.compile("^\\p{Blank}*(\\p{Digit}{1,2}) (\\p{Alpha}{3,9}) (\\p{Digit}{4}) *");
148 SECTION_1_HEADER = Pattern.compile("^ +1 - (\\p{Upper}+) \\p{Upper}+ \\p{Upper}+.*");
149 SECTION_2_HEADER_OLD = Pattern.compile("^ +2 - SMOOTHED \\p{Upper}+ \\p{Upper}+.*(DPSI, DEPSILON|dX, dY).*");
150 SECTION_3_HEADER = Pattern.compile("^ +3 - \\p{Upper}+ \\p{Upper}+ \\p{Upper}+.*");
151
152 // the markers bracketing the final values in section 1 in the old bulletin B
153 // monthly data files have the following form:
154 //
155 // Final Bulletin B values.
156 // ...
157 // Preliminary extension, to be updated weekly in Bulletin A and monthly
158 // in Bulletin B.
159 //
160 // the markers bracketing the final values in section 1 in the new bulletin B
161 // monthly data files have the following form:
162 //
163 // Final values
164 // ...
165 // Preliminary extension
166 //
167 FINAL_VALUES_START = Pattern.compile("^\\p{Blank}+Final( Bulletin B)? values.*");
168 FINAL_VALUES_END = Pattern.compile("^\\p{Blank}+Preliminary extension.*");
169
170 // the data lines in the old bulletin B monthly data files have the following form:
171 // in section 1:
172 // AUG 1 55044 0.22176 0.49302 0.231416 -33.768584 -69.1 -8.9
173 // AUG 6 55049 0.23202 0.48003 0.230263 -33.769737 -69.5 -8.5
174 // in section 2:
175 // AUG 1 55044 0.22176 0.49302 0.230581 -0.835 -0.310 -69.1 -8.9
176 // AUG 2 55045 0.22395 0.49041 0.230928 -0.296 -0.328 -69.5 -8.9
177 //
178 // the data lines in the new bulletin B monthly data files have the following form:
179 // in section 1:
180 // 2009 8 2 55045 223.954 490.410 230.9277 0.214 -0.056 0.008 0.009 0.0641 0.048 0.121
181 // 2009 8 3 55046 225.925 487.700 231.2186 0.300 -0.138 0.010 0.012 0.0466 0.099 0.248
182 // 2009 8 4 55047 227.931 485.078 231.3929 0.347 -0.231 0.019 0.023 0.0360 0.099 0.249
183 // 2009 8 5 55048 230.016 482.445 231.4601 0.321 -0.291 0.025 0.028 0.0441 0.095 0.240
184 // 2009 8 6 55049 232.017 480.026 231.3619 0.267 -0.273 0.025 0.029 0.0477 0.038 0.095
185 // in section 2:
186 // 2009 8 2 55045 -69.474 -8.929 0.199 0.121
187 // 2009 8 3 55046 -69.459 -9.016 0.250 0.248
188 // 2009 8 4 55047 -69.401 -9.039 0.250 0.249
189 // 2009 8 5 55048 -69.425 -8.864 0.247 0.240
190 // 2009 8 6 55049 -69.510 -8.539 0.153 0.095
191 // in section 3:
192 // 2009 8 2 55045 -0.3284 0.0013 15.04106723584 0.00000000023
193 // 2009 8 3 55046 -0.2438 0.0013 15.04106722111 0.00000000023
194 // 2009 8 4 55047 -0.1233 0.0013 15.04106720014 0.00000000023
195 // 2009 8 5 55048 0.0119 0.0013 15.04106717660 0.00000000023
196 // 2009 8 6 55049 0.1914 0.0013 15.04106714535 0.00000000023
197 final StringBuilder builder = new StringBuilder("^\\p{Blank}+(?:");
198 for (final Month month : Month.values()) {
199 builder.append(month.getUpperCaseAbbreviation());
200 builder.append('|');
201 }
202 builder.delete(builder.length() - 1, builder.length());
203 builder.append(")");
204 final String integerPattern = "[-+]?\\p{Digit}+";
205 final String realPattern = "[-+]?(?:\\p{Digit}+(?:\\.\\p{Digit}*)?|\\.\\p{Digit}+)(?:[eE][-+]?\\p{Digit}+)?";
206 final String monthNameField = builder.toString();
207 final String ignoredIntegerField = "\\p{Blank}*" + integerPattern;
208 final String storedIntegerField = "\\p{Blank}*(" + integerPattern + ")";
209 final String mjdField = "\\p{Blank}+(\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})";
210 final String storedRealField = "\\p{Blank}+(" + realPattern + ")";
211 final String ignoredRealField = "\\p{Blank}+" + realPattern;
212 final String finalBlanks = "\\p{Blank}*$";
213 SECTION_1_DATA_OLD_FORMAT = Pattern.compile(monthNameField + ignoredIntegerField + mjdField +
214 ignoredRealField + ignoredRealField + ignoredRealField +
215 ignoredRealField + ignoredRealField + ignoredRealField +
216 finalBlanks);
217 SECTION_2_DATA_OLD_FORMAT = Pattern.compile(monthNameField + ignoredIntegerField + mjdField +
218 storedRealField + storedRealField + storedRealField +
219 ignoredRealField +
220 storedRealField + storedRealField + storedRealField +
221 finalBlanks);
222 SECTION_1_DATA_NEW_FORMAT = Pattern.compile(storedIntegerField + storedIntegerField + storedIntegerField + mjdField +
223 storedRealField + storedRealField + storedRealField +
224 storedRealField + storedRealField + ignoredRealField + ignoredRealField +
225 ignoredRealField + ignoredRealField + ignoredRealField +
226 finalBlanks);
227 SECTION_3_DATA_NEW_FORMAT = Pattern.compile(ignoredIntegerField + ignoredIntegerField + ignoredIntegerField + mjdField +
228 storedRealField +
229 ignoredRealField + ignoredRealField + ignoredRealField +
230 finalBlanks);
231
232 }
233
234 /** Build a loader for IERS bulletins B files.
235 * @param supportedNames regular expression for supported files names
236 * @param manager provides access to the bulletin B files.
237 * @param utcSupplier UTC time scale.
238 */
239 BulletinBFilesLoader(final String supportedNames,
240 final DataProvidersManager manager,
241 final Supplier<TimeScale> utcSupplier) {
242 super(supportedNames, manager, utcSupplier);
243 }
244
245 /** {@inheritDoc} */
246 public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
247 final Collection<EOPEntry> history) {
248 final ItrfVersionProvider itrfVersionProvider = new ITRFVersionLoader(
249 ITRFVersionLoader.SUPPORTED_NAMES,
250 getDataProvidersManager());
251 final Parser parser = new Parser(converter, itrfVersionProvider, getUtc());
252 final EopParserLoader loader = new EopParserLoader(parser);
253 this.feed(loader);
254 history.addAll(loader.getEop());
255 }
256
257 /** Internal class performing the parsing. */
258 static class Parser extends AbstractEopParser {
259
260 /** ITRF version configuration. */
261 private ITRFVersionLoader.ITRFVersionConfiguration configuration;
262
263 /** History entries. */
264 private List<EOPEntry> history;
265
266 /** Map for fields read in different sections. */
267 private final Map<Integer, double[]> fieldsMap;
268
269 /** Publication date.
270 * @since 14.0
271 */
272 private DateComponents publicationDate;
273
274 /** Current line number. */
275 private int lineNumber;
276
277 /** Current line. */
278 private String line;
279
280 /** Start of final data. */
281 private int mjdMin;
282
283 /** End of final data. */
284 private int mjdMax;
285
286 /**
287 * Simple constructor.
288 *
289 * @param converter converter to use
290 * @param itrfVersionProvider to use for determining the ITRF version of the EOP.
291 * @param utc time scale for parsing dates.
292 */
293 Parser(final NutationCorrectionConverter converter,
294 final ItrfVersionProvider itrfVersionProvider,
295 final TimeScale utc) {
296 super(converter, itrfVersionProvider, utc);
297 this.fieldsMap = new HashMap<>();
298 this.lineNumber = 0;
299 this.mjdMin = Integer.MAX_VALUE;
300 this.mjdMax = Integer.MIN_VALUE;
301 }
302
303 /** {@inheritDoc} */
304 @Override
305 public Collection<EOPEntry> parse(final DataSource source)
306 throws IOException {
307
308 // set up a reader for line-oriented bulletin B files
309 try (BufferedReader reader = new BufferedReader(source.getOpener().openReaderOnce())) {
310 // reset parse info to start new file
311 fieldsMap.clear();
312 lineNumber = 0;
313 mjdMin = Integer.MAX_VALUE;
314 mjdMax = Integer.MIN_VALUE;
315 history = new ArrayList<>();
316 configuration = null;
317
318 // parse publication date
319 // for a long time, there was a typo in Bulletin B header files.
320 // files published in September, October, November and December
321 // had their month misspelled as "septembre", "octobre", "novembre", and "decembre".
322 // the last file published with the error was bulletin B 370 from 2018-12-01.
323 // we use only the month truncated to its standard 3-letter abbreviation here
324 // in order to allow parsing the publication date from these erroneous files and
325 // identify the proper month despite the spelling error.
326 final Matcher section0Matcher = seekToLine(SECTION_0_DATE, reader, source.getName());
327 publicationDate = new DateComponents(Integer.parseInt(section0Matcher.group(3)),
328 Month.parseMonth(section0Matcher.group(2).substring(0, 3)),
329 Integer.parseInt(section0Matcher.group(1)));
330
331 // skip header up to section 1 and check if we are parsing an old or new format file
332 final Matcher section1Matcher = seekToLine(SECTION_1_HEADER, reader, source.getName());
333 final boolean isOldFormat = "EARTH".equals(section1Matcher.group(1));
334
335 if (isOldFormat) {
336
337 // extract MJD bounds for final data from section 1
338 loadMJDBoundsOldFormat(reader, source.getName());
339
340 final Matcher section2Matcher = seekToLine(SECTION_2_HEADER_OLD, reader, source.getName());
341 final boolean isNonRotatingOrigin = section2Matcher.group(1).startsWith("dX");
342 loadEOPOldFormat(isNonRotatingOrigin, reader, source.getName());
343
344 } else {
345
346 // extract x, y, UT1-UTC, dx, dy from section 1
347 loadXYDTDxDyNewFormat(reader, source.getName());
348
349 // skip to section 3
350 seekToLine(SECTION_3_HEADER, reader, source.getName());
351
352 // extract LOD data from section 3
353 loadLODNewFormat(reader, source.getName());
354
355 // set up the EOP entries
356 for (Map.Entry<Integer, double[]> entry : fieldsMap.entrySet()) {
357 final int mjd = entry.getKey();
358 final double[] array = entry.getValue();
359 if (Double.isNaN(array[0] + array[1] + array[2] + array[3] + array[4] + array[5])) {
360 throw notifyUnexpectedErrorEncountered(source.getName());
361 }
362 final AbsoluteDate mjdDate =
363 new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
364 getUtc());
365 final double[] equinox = getConverter().toEquinox(mjdDate, array[4], array[5]);
366 if (configuration == null || !configuration.isValid(mjd)) {
367 // get a configuration for current name and date range
368 configuration = getItrfVersionProvider().getConfiguration(source.getName(), mjd);
369 }
370 history.add(new EOPEntry(mjd, array[0], array[1], array[2], array[3],
371 Double.NaN, Double.NaN,
372 equinox[0], equinox[1], array[4], array[5],
373 configuration.getVersion(), mjdDate, EopDataType.FINAL,
374 publicationDate.getMJD(),
375 publicationDate.getMJD(),
376 publicationDate.getMJD()));
377 }
378
379 }
380 }
381
382 return history;
383
384 }
385
386 /** Read until a line matching a pattern is found.
387 * @param pattern pattern to look for
388 * @param reader reader from where file content is obtained
389 * @param name name of the file (or zip entry)
390 * @return the matching matcher for the line
391 * @exception IOException if data can't be read
392 */
393 private Matcher seekToLine(final Pattern pattern, final BufferedReader reader, final String name)
394 throws IOException {
395
396 for (line = reader.readLine(); line != null; line = reader.readLine()) {
397 ++lineNumber;
398 final Matcher matcher = pattern.matcher(line);
399 if (matcher.matches()) {
400 return matcher;
401 }
402 }
403
404 // we have reached end of file and not found a matching line
405 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
406 name, lineNumber);
407
408 }
409
410 /** Read MJD bounds of the final data part from section 1 in the old bulletin B format.
411 * @param reader reader from where file content is obtained
412 * @param name name of the file (or zip entry)
413 * @exception IOException if data can't be read
414 */
415 private void loadMJDBoundsOldFormat(final BufferedReader reader, final String name)
416 throws IOException {
417
418 boolean inFinalValuesPart = false;
419 for (line = reader.readLine(); line != null; line = reader.readLine()) {
420 lineNumber++;
421 Matcher matcher = FINAL_VALUES_START.matcher(line);
422 if (matcher.matches()) {
423 // we are entering final values part (in section 1)
424 inFinalValuesPart = true;
425 } else if (inFinalValuesPart) {
426 matcher = SECTION_1_DATA_OLD_FORMAT.matcher(line);
427 if (matcher.matches()) {
428 // this is a data line, build an entry from the extracted fields
429 final int mjd = Integer.parseInt(matcher.group(1));
430 mjdMin = FastMath.min(mjdMin, mjd);
431 mjdMax = FastMath.max(mjdMax, mjd);
432 } else {
433 matcher = FINAL_VALUES_END.matcher(line);
434 if (matcher.matches()) {
435 // we leave final values part
436 return;
437 }
438 }
439 }
440 }
441
442 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
443 name, lineNumber);
444
445 }
446
447 /** Read EOP data from section 2 in the old bulletin B format.
448 * @param isNonRotatingOrigin if true, the file contain Non-Rotating Origin nutation corrections
449 * @param reader reader from where file content is obtained
450 * @param name name of the file (or zip entry)
451 * @exception IOException if data can't be read
452 */
453 private void loadEOPOldFormat(final boolean isNonRotatingOrigin,
454 final BufferedReader reader, final String name)
455 throws IOException {
456
457 // read the data lines in the final values part inside section 2
458 line = reader.readLine();
459 while (line != null) {
460 lineNumber++;
461 final Matcher matcher = SECTION_2_DATA_OLD_FORMAT.matcher(line);
462 if (matcher.matches()) {
463 // this is a data line, build an entry from the extracted fields
464 final int mjd = Integer.parseInt(matcher.group(1));
465 final double x = Double.parseDouble(matcher.group(2)) * Constants.ARC_SECONDS_TO_RADIANS;
466 final double y = Double.parseDouble(matcher.group(3)) * Constants.ARC_SECONDS_TO_RADIANS;
467 final double dtu1 = Double.parseDouble(matcher.group(4));
468 final double lod = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(matcher.group(5)));
469 if (mjd >= mjdMin) {
470 final AbsoluteDate mjdDate =
471 new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
472 getUtc());
473 final double[] equinox;
474 final double[] nro;
475 if (isNonRotatingOrigin) {
476 nro = new double[] {
477 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(6))),
478 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(7)))
479 };
480 equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
481 } else {
482 equinox = new double[] {
483 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(6))),
484 UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(7)))
485 };
486 nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
487 }
488 if (configuration == null || !configuration.isValid(mjd)) {
489 // get a configuration for current name and date range
490 configuration = getItrfVersionProvider().getConfiguration(name, mjd);
491 }
492 history.add(new EOPEntry(mjd, dtu1, lod, x, y, Double.NaN, Double.NaN,
493 equinox[0], equinox[1], nro[0], nro[1],
494 configuration.getVersion(), mjdDate, EopDataType.FINAL,
495 publicationDate.getMJD(),
496 publicationDate.getMJD(),
497 publicationDate.getMJD()));
498 line = mjd < mjdMax ? reader.readLine() : null;
499 } else {
500 line = reader.readLine();
501 }
502 } else {
503 line = reader.readLine();
504 }
505 }
506
507 }
508
509 /** Read X, Y, UT1-UTC, dx, dy from section 1 in the new bulletin B format.
510 * @param reader reader from where file content is obtained
511 * @param name name of the file (or zip entry)
512 * @exception IOException if data can't be read
513 */
514 private void loadXYDTDxDyNewFormat(final BufferedReader reader, final String name)
515 throws IOException {
516
517 boolean inFinalValuesPart = false;
518 line = reader.readLine();
519 while (line != null) {
520 lineNumber++;
521 Matcher matcher = FINAL_VALUES_START.matcher(line);
522 if (matcher.matches()) {
523 // we are entering final values part (in section 1)
524 inFinalValuesPart = true;
525 line = reader.readLine();
526 } else if (inFinalValuesPart) {
527 matcher = SECTION_1_DATA_NEW_FORMAT.matcher(line);
528 if (matcher.matches()) {
529 // this is a data line, build an entry from the extracted fields
530 final int year = Integer.parseInt(matcher.group(1));
531 final int month = Integer.parseInt(matcher.group(2));
532 final int day = Integer.parseInt(matcher.group(3));
533 final int mjd = Integer.parseInt(matcher.group(4));
534 if (new DateComponents(year, month, day).getMJD() != mjd) {
535 throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
536 name, year, month, day, mjd);
537 }
538 mjdMin = FastMath.min(mjdMin, mjd);
539 mjdMax = FastMath.max(mjdMax, mjd);
540 final double x = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(5)));
541 final double y = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(6)));
542 final double dtu1 = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(matcher.group(7)));
543 final double dx = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(8)));
544 final double dy = UnitsConverter.MILLI_ARC_SECONDS_TO_RADIANS.convert(Double.parseDouble(matcher.group(9)));
545 fieldsMap.put(mjd,
546 new double[] {
547 dtu1, Double.NaN, x, y, dx, dy
548 });
549 line = reader.readLine();
550 } else {
551 matcher = FINAL_VALUES_END.matcher(line);
552 line = matcher.matches() ? null : reader.readLine();
553 }
554 } else {
555 line = reader.readLine();
556 }
557 }
558 }
559
560 /** Read LOD from section 3 in the new bulletin B format.
561 * @param reader reader from where file content is obtained
562 * @param name name of the file (or zip entry)
563 * @exception IOException if data can't be read
564 */
565 private void loadLODNewFormat(final BufferedReader reader, final String name)
566 throws IOException {
567 line = reader.readLine();
568 while (line != null) {
569 lineNumber++;
570 final Matcher matcher = SECTION_3_DATA_NEW_FORMAT.matcher(line);
571 if (matcher.matches()) {
572 // this is a data line, build an entry from the extracted fields
573 final int mjd = Integer.parseInt(matcher.group(1));
574 if (mjd >= mjdMin) {
575 final double lod = UnitsConverter.MILLI_SECONDS_TO_SECONDS.convert(Double.parseDouble(matcher.group(2)));
576 final double[] array = fieldsMap.get(mjd);
577 if (array == null) {
578 throw notifyUnexpectedErrorEncountered(name);
579 }
580 array[1] = lod;
581 line = mjd >= mjdMax ? null : reader.readLine();
582 } else {
583 line = reader.readLine();
584 }
585 } else {
586 line = reader.readLine();
587 }
588 }
589 }
590
591 /** Create an exception to be thrown.
592 * @param name name of the file (or zip entry)
593 * @return OrekitException always thrown to notify an unexpected error has been
594 * encountered by the caller
595 */
596 private OrekitException notifyUnexpectedErrorEncountered(final String name) {
597 String loaderName = BulletinBFilesLoader.class.getName();
598 loaderName = loaderName.substring(loaderName.lastIndexOf('.') + 1);
599 return new OrekitException(OrekitMessages.UNEXPECTED_FILE_FORMAT_ERROR_FOR_LOADER,
600 name, loaderName);
601 }
602
603 }
604
605 }