1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.frames;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.List;
23 import java.util.function.Supplier;
24
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.parsers.SAXParser;
27 import javax.xml.parsers.SAXParserFactory;
28
29 import org.hipparchus.exception.LocalizedCoreFormats;
30 import org.orekit.data.DataProvidersManager;
31 import org.orekit.data.DataSource;
32 import org.orekit.errors.OrekitException;
33 import org.orekit.errors.OrekitMessages;
34 import org.orekit.time.AbsoluteDate;
35 import org.orekit.time.DateComponents;
36 import org.orekit.time.TimeScale;
37 import org.orekit.utils.IERSConventions;
38 import org.orekit.utils.units.Unit;
39 import org.xml.sax.Attributes;
40 import org.xml.sax.InputSource;
41 import org.xml.sax.SAXException;
42 import org.xml.sax.helpers.DefaultHandler;
43
44
45
46
47
48
49
50
51
52
53
54
55
56 class EopXmlLoader extends AbstractEopLoader implements EopHistoryLoader {
57
58
59 private static final Unit MILLI_SECOND = Unit.parse("ms");
60
61
62 private static final Unit MILLI_ARC_SECOND = Unit.parse("mas");
63
64
65
66
67 private static final Unit ARC_SECOND_PER_DAY = Unit.parse("as/day");
68
69
70
71
72
73
74
75
76 EopXmlLoader(final String supportedNames,
77 final DataProvidersManager manager,
78 final Supplier<TimeScale> utcSupplier) {
79 super(supportedNames, manager, utcSupplier);
80 }
81
82
83 public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
84 final Collection<EOPEntry> history) {
85 final ItrfVersionProvider itrfVersionProvider = new ITRFVersionLoader(
86 ITRFVersionLoader.SUPPORTED_NAMES,
87 getDataProvidersManager());
88 final Parser parser = new Parser(converter, itrfVersionProvider, getUtc());
89 final EopParserLoader loader = new EopParserLoader(parser);
90 this.feed(loader);
91 history.addAll(loader.getEop());
92 }
93
94
95 static class Parser extends AbstractEopParser {
96
97
98 private List<EOPEntry> history;
99
100
101
102
103
104
105
106
107 Parser(final IERSConventions.NutationCorrectionConverter converter,
108 final ItrfVersionProvider itrfVersionProvider,
109 final TimeScale utc) {
110 super(converter, itrfVersionProvider, utc);
111 }
112
113
114 @Override
115 public List<EOPEntry> parse(final DataSource source)
116 throws IOException, OrekitException {
117 try {
118 this.history = new ArrayList<>();
119
120 final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
121
122
123 parser.parse(new InputSource(source.getOpener().openStreamOnce()),
124 new EOPContentHandler(source.getName()));
125
126 return history;
127
128 } catch (SAXException | ParserConfigurationException e) {
129 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getMessage());
130 }
131 }
132
133
134 private class EOPContentHandler extends DefaultHandler {
135
136
137
138
139 private static final String MJD_ELT = "MJD";
140 private static final String LOD_ELT = "LOD";
141 private static final String X_ELT = "X";
142 private static final String Y_ELT = "Y";
143 private static final String X_RATE_ELT = "x_rate";
144 private static final String Y_RATE_ELT = "y_rate";
145 private static final String DPSI_ELT = "dPsi";
146 private static final String DEPSILON_ELT = "dEpsilon";
147 private static final String DX_ELT = "dX";
148 private static final String DY_ELT = "dY";
149
150
151 private static final String DATA_ELT = "data";
152 private static final String PRODUCT_ATTR = "product";
153 private static final String BULLETIN_A_PROD = "BulletinA";
154 private static final String BULLETIN_B_PROD = "BulletinB";
155 private static final String EOP_C04_PROD_PREFIX = "EOP";
156 private static final String EOP_C04_PROD_SUFFIX = "C04";
157
158
159 private static final String DATA_EOP_ELT = "dataEOP";
160 private static final String TIME_SERIES_ELT = "timeSeries";
161 private static final String DATE_YEAR_ELT = "dateYear";
162 private static final String DATE_MONTH_ELT = "dateMonth";
163 private static final String DATE_DAY_ELT = "dateDay";
164 private static final String POLE_ELT = "pole";
165 private static final String UT_ELT = "UT";
166 private static final String UT1_U_UTC_ELT = "UT1_UTC";
167 private static final String NUTATION_ELT = "nutation";
168 private static final String SOURCE_ATTR = "source";
169
170
171 private static final String FINALS_ELT = "Finals";
172 private static final String DATE_ELT = "date";
173 private static final String EOP_SET_ELT = "EOPSet";
174 private static final String BULLETIN_A_ELT = "bulletinA";
175 private static final String UT1_M_UTC_ELT = "UT1-UTC";
176
177 private boolean inBulletinA;
178 private int mjd;
179 private DateComponents dc;
180 private AbsoluteDate date;
181 private double dtu1;
182 private double lod;
183 private double x;
184 private double y;
185 private double xRate;
186 private double yRate;
187 private double dpsi;
188 private double deps;
189 private double dx;
190 private double dy;
191
192
193
194
195 private final String name;
196
197
198 private final StringBuilder buffer;
199
200
201 private DataFileContent content;
202
203
204 private ITRFVersionLoader.ITRFVersionConfiguration configuration;
205
206
207
208
209 EOPContentHandler(final String name) {
210 this.name = name;
211 this.buffer = new StringBuilder();
212 }
213
214
215 @Override
216 public void startDocument() {
217 content = DataFileContent.UNKNOWN;
218 configuration = null;
219 }
220
221
222 @Override
223 public void characters(final char[] ch, final int start, final int length) {
224 buffer.append(ch, start, length);
225 }
226
227
228 @Override
229 public void startElement(final String uri, final String localName,
230 final String qName, final Attributes atts) {
231
232
233 buffer.delete(0, buffer.length());
234
235 if (content == DataFileContent.UNKNOWN) {
236
237 switch (qName) {
238 case TIME_SERIES_ELT ->
239
240 content = DataFileContent.DAILY;
241 case FINALS_ELT ->
242
243 content = DataFileContent.FINAL;
244 case DATA_ELT -> {
245 final String product = atts.getValue(PRODUCT_ATTR);
246 if (product != null) {
247 if (product.startsWith(BULLETIN_A_PROD)) {
248
249 content = DataFileContent.BULLETIN_A;
250 inBulletinA = true;
251 } else if (product.startsWith(BULLETIN_B_PROD)) {
252
253 content = DataFileContent.BULLETIN_B;
254 } else if (product.startsWith(EOP_C04_PROD_PREFIX) && product.endsWith(
255 EOP_C04_PROD_SUFFIX)) {
256
257 content = DataFileContent.EOP_C04;
258 }
259 }
260 }
261 default ->
262 content = DataFileContent.UNKNOWN;
263 }
264 }
265
266 if (content == DataFileContent.DAILY || content == DataFileContent.BULLETIN_A ||
267 content == DataFileContent.BULLETIN_B || content == DataFileContent.EOP_C04) {
268 startDailyElement(qName, atts);
269 } else if (content == DataFileContent.FINAL) {
270 startFinalElement(qName);
271 }
272
273 }
274
275
276
277
278
279 private void startDailyElement(final String qName, final Attributes atts) {
280 if (qName.equals(TIME_SERIES_ELT)) {
281
282 resetEOPData();
283 } else if (qName.equals(POLE_ELT) || qName.equals(UT_ELT) || qName.equals(NUTATION_ELT)) {
284 final String source = atts.getValue(SOURCE_ATTR);
285 if (source != null) {
286 inBulletinA = source.equals(BULLETIN_A_PROD);
287 }
288 }
289 }
290
291
292
293
294 private void startFinalElement(final String qName) {
295 if (qName.equals(EOP_SET_ELT)) {
296
297 resetEOPData();
298 } else if (qName.equals(BULLETIN_A_ELT)) {
299 inBulletinA = true;
300 }
301 }
302
303
304
305 private void resetEOPData() {
306 inBulletinA = false;
307 dc = null;
308 mjd = -1;
309 date = null;
310 dtu1 = Double.NaN;
311 lod = Double.NaN;
312 x = Double.NaN;
313 y = Double.NaN;
314 xRate = Double.NaN;
315 yRate = Double.NaN;
316 dpsi = Double.NaN;
317 deps = Double.NaN;
318 dx = Double.NaN;
319 dy = Double.NaN;
320 }
321
322
323 @Override
324 public void endElement(final String uri, final String localName, final String qName) {
325 if (content == DataFileContent.DAILY || content == DataFileContent.BULLETIN_A ||
326 content == DataFileContent.BULLETIN_B || content == DataFileContent.EOP_C04) {
327 endDailyElement(qName);
328 } else if (content == DataFileContent.FINAL) {
329 endFinalElement(qName);
330 }
331 }
332
333
334
335
336 private void endDailyElement(final String qName) {
337 if (qName.equals(DATE_YEAR_ELT) && !buffer.isEmpty()) {
338 dc = new DateComponents(Integer.parseInt(buffer.toString()), 1, 1);
339 } else if (qName.equals(DATE_MONTH_ELT) && !buffer.isEmpty()) {
340 dc = new DateComponents(dc.getYear(), Integer.parseInt(buffer.toString()), 1);
341 } else if (qName.equals(DATE_DAY_ELT) && !buffer.isEmpty()) {
342 dc = new DateComponents(dc.getYear(), dc.getMonth(), Integer.parseInt(buffer.toString()));
343 } else if (qName.equals(MJD_ELT) && !buffer.isEmpty()) {
344 mjd = Integer.parseInt(buffer.toString());
345 date = new AbsoluteDate(dc, getUtc());
346 } else if (qName.equals(UT1_M_UTC_ELT)) {
347 dtu1 = overwrite(dtu1, Unit.SECOND);
348 } else if (qName.equals(LOD_ELT)) {
349 lod = overwrite(lod, MILLI_SECOND);
350 } else if (qName.equals(X_ELT)) {
351 x = overwrite(x, Unit.ARC_SECOND);
352 } else if (qName.equals(Y_ELT)) {
353 y = overwrite(y, Unit.ARC_SECOND);
354 } else if (qName.equals(X_RATE_ELT)) {
355 xRate = overwrite(xRate, ARC_SECOND_PER_DAY);
356 } else if (qName.equals(Y_RATE_ELT)) {
357 yRate = overwrite(yRate, ARC_SECOND_PER_DAY);
358 } else if (qName.equals(DPSI_ELT)) {
359 dpsi = overwrite(dpsi, MILLI_ARC_SECOND);
360 } else if (qName.equals(DEPSILON_ELT)) {
361 deps = overwrite(deps, MILLI_ARC_SECOND);
362 } else if (qName.equals(DX_ELT)) {
363 dx = overwrite(dx, MILLI_ARC_SECOND);
364 } else if (qName.equals(DY_ELT)) {
365 dy = overwrite(dy, MILLI_ARC_SECOND);
366 } else if (qName.equals(POLE_ELT) || qName.equals(UT_ELT) || qName.equals(NUTATION_ELT)) {
367 inBulletinA = false;
368 } else if (qName.equals(DATA_EOP_ELT)) {
369 checkDates();
370 if (!Double.isNaN(dtu1) && !Double.isNaN(x) && !Double.isNaN(y)) {
371 final double[] equinox;
372 final double[] nro;
373 if (Double.isNaN(dpsi)) {
374 nro = new double[] {
375 dx, dy
376 };
377 equinox = getConverter().toEquinox(date, nro[0], nro[1]);
378 } else {
379 equinox = new double[] {
380 dpsi, deps
381 };
382 nro = getConverter().toNonRotating(date, equinox[0], equinox[1]);
383 }
384 if (configuration == null || !configuration.isValid(mjd)) {
385
386 configuration = getItrfVersionProvider().getConfiguration(name, mjd);
387 }
388 history.add(new EOPEntry(mjd, dtu1, lod, x, y, Double.NaN, Double.NaN,
389 equinox[0], equinox[1], nro[0], nro[1],
390 configuration.getVersion(), date, EopDataType.UNKNOWN,
391 new EOPOrigin(dc, name), null, null));
392 }
393 }
394 }
395
396
397
398
399 private void endFinalElement(final String qName) {
400 if (qName.equals(DATE_ELT) && !buffer.isEmpty()) {
401 final String[] fields = buffer.toString().split("-");
402 if (fields.length == 3) {
403 dc = new DateComponents(Integer.parseInt(fields[0]),
404 Integer.parseInt(fields[1]),
405 Integer.parseInt(fields[2]));
406 }
407 } else if (qName.equals(MJD_ELT) && !buffer.isEmpty()) {
408 mjd = Integer.parseInt(buffer.toString());
409 date = new AbsoluteDate(dc, getUtc());
410 } else if (qName.equals(UT1_U_UTC_ELT)) {
411 dtu1 = overwrite(dtu1, Unit.SECOND);
412 } else if (qName.equals(LOD_ELT)) {
413 lod = overwrite(lod, MILLI_SECOND);
414 } else if (qName.equals(X_ELT)) {
415 x = overwrite(x, Unit.ARC_SECOND);
416 } else if (qName.equals(Y_ELT)) {
417 y = overwrite(y, Unit.ARC_SECOND);
418 } else if (qName.equals(X_RATE_ELT)) {
419 xRate = overwrite(xRate, ARC_SECOND_PER_DAY);
420 } else if (qName.equals(Y_RATE_ELT)) {
421 yRate = overwrite(yRate, ARC_SECOND_PER_DAY);
422 } else if (qName.equals(DPSI_ELT)) {
423 dpsi = overwrite(dpsi, MILLI_ARC_SECOND);
424 } else if (qName.equals(DEPSILON_ELT)) {
425 deps = overwrite(deps, MILLI_ARC_SECOND);
426 } else if (qName.equals(DX_ELT)) {
427 dx = overwrite(dx, MILLI_ARC_SECOND);
428 } else if (qName.equals(DY_ELT)) {
429 dy = overwrite(dy, MILLI_ARC_SECOND);
430 } else if (qName.equals(BULLETIN_A_ELT)) {
431 inBulletinA = false;
432 } else if (qName.equals(EOP_SET_ELT)) {
433 checkDates();
434 if (!Double.isNaN(dtu1) && !Double.isNaN(x) && !Double.isNaN(y)) {
435 final double[] equinox;
436 final double[] nro;
437 if (Double.isNaN(dpsi)) {
438 nro = new double[] {
439 dx, dy
440 };
441 equinox = getConverter().toEquinox(date, nro[0], nro[1]);
442 } else {
443 equinox = new double[] {
444 dpsi, deps
445 };
446 nro = getConverter().toNonRotating(date, equinox[0], equinox[1]);
447 }
448 if (configuration == null || !configuration.isValid(mjd)) {
449
450 configuration = getItrfVersionProvider().getConfiguration(name, mjd);
451 }
452 history.add(new EOPEntry(mjd, dtu1, lod, x, y, xRate, yRate,
453 equinox[0], equinox[1], nro[0], nro[1],
454 configuration.getVersion(), date, EopDataType.UNKNOWN,
455 new EOPOrigin(dc, name), null, null));
456 }
457 }
458 }
459
460
461
462
463
464
465 private double overwrite(final double oldValue, final Unit units) {
466 if (buffer.isEmpty()) {
467
468 return oldValue;
469 } else if (inBulletinA && !Double.isNaN(oldValue)) {
470
471 return oldValue;
472 } else {
473
474 return units.toSI(Double.parseDouble(buffer.toString()));
475 }
476 }
477
478
479
480 private void checkDates() {
481 if (dc.getMJD() != mjd) {
482 throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
483 name, dc.getYear(), dc.getMonth(), dc.getDay(), mjd);
484 }
485 }
486
487
488 @Override
489 public InputSource resolveEntity(final String publicId, final String systemId) {
490
491 return new InputSource();
492 }
493
494 }
495
496 }
497
498
499 private enum DataFileContent {
500
501
502 UNKNOWN,
503
504
505
506
507 BULLETIN_A,
508
509
510
511
512 BULLETIN_B,
513
514
515
516
517 EOP_C04,
518
519
520 DAILY,
521
522
523 FINAL
524
525 }
526
527 }