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