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 Collection<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 year;
179 private int month;
180 private int day;
181 private int mjd;
182 private AbsoluteDate mjdDate;
183 private double dtu1;
184 private double lod;
185 private double x;
186 private double y;
187 private double xRate;
188 private double yRate;
189 private double dpsi;
190 private double deps;
191 private double dx;
192 private double dy;
193
194
195
196
197 private final String name;
198
199
200 private final StringBuilder buffer;
201
202
203 private DataFileContent content;
204
205
206 private ITRFVersionLoader.ITRFVersionConfiguration configuration;
207
208
209
210
211 EOPContentHandler(final String name) {
212 this.name = name;
213 this.buffer = new StringBuilder();
214 }
215
216
217 @Override
218 public void startDocument() {
219 content = DataFileContent.UNKNOWN;
220 configuration = null;
221 }
222
223
224 @Override
225 public void characters(final char[] ch, final int start, final int length) {
226 buffer.append(ch, start, length);
227 }
228
229
230 @Override
231 public void startElement(final String uri, final String localName,
232 final String qName, final Attributes atts) {
233
234
235 buffer.delete(0, buffer.length());
236
237 if (content == DataFileContent.UNKNOWN) {
238
239 switch (qName) {
240 case TIME_SERIES_ELT ->
241
242 content = DataFileContent.DAILY;
243 case FINALS_ELT ->
244
245 content = DataFileContent.FINAL;
246 case DATA_ELT -> {
247 final String product = atts.getValue(PRODUCT_ATTR);
248 if (product != null) {
249 if (product.startsWith(BULLETIN_A_PROD)) {
250
251 content = DataFileContent.BULLETIN_A;
252 inBulletinA = true;
253 } else if (product.startsWith(BULLETIN_B_PROD)) {
254
255 content = DataFileContent.BULLETIN_B;
256 } else if (product.startsWith(EOP_C04_PROD_PREFIX) && product.endsWith(
257 EOP_C04_PROD_SUFFIX)) {
258
259 content = DataFileContent.EOP_C04;
260 }
261 }
262 }
263 default ->
264 content = DataFileContent.UNKNOWN;
265 }
266 }
267
268 if (content == DataFileContent.DAILY || content == DataFileContent.BULLETIN_A ||
269 content == DataFileContent.BULLETIN_B || content == DataFileContent.EOP_C04) {
270 startDailyElement(qName, atts);
271 } else if (content == DataFileContent.FINAL) {
272 startFinalElement(qName);
273 }
274
275 }
276
277
278
279
280
281 private void startDailyElement(final String qName, final Attributes atts) {
282 if (qName.equals(TIME_SERIES_ELT)) {
283
284 resetEOPData();
285 } else if (qName.equals(POLE_ELT) || qName.equals(UT_ELT) || qName.equals(NUTATION_ELT)) {
286 final String source = atts.getValue(SOURCE_ATTR);
287 if (source != null) {
288 inBulletinA = source.equals(BULLETIN_A_PROD);
289 }
290 }
291 }
292
293
294
295
296 private void startFinalElement(final String qName) {
297 if (qName.equals(EOP_SET_ELT)) {
298
299 resetEOPData();
300 } else if (qName.equals(BULLETIN_A_ELT)) {
301 inBulletinA = true;
302 }
303 }
304
305
306
307 private void resetEOPData() {
308 inBulletinA = false;
309 year = -1;
310 month = -1;
311 day = -1;
312 mjd = -1;
313 mjdDate = null;
314 dtu1 = Double.NaN;
315 lod = Double.NaN;
316 x = Double.NaN;
317 y = Double.NaN;
318 xRate = Double.NaN;
319 yRate = Double.NaN;
320 dpsi = Double.NaN;
321 deps = Double.NaN;
322 dx = Double.NaN;
323 dy = Double.NaN;
324 }
325
326
327 @Override
328 public void endElement(final String uri, final String localName, final String qName) {
329 if (content == DataFileContent.DAILY || content == DataFileContent.BULLETIN_A ||
330 content == DataFileContent.BULLETIN_B || content == DataFileContent.EOP_C04) {
331 endDailyElement(qName);
332 } else if (content == DataFileContent.FINAL) {
333 endFinalElement(qName);
334 }
335 }
336
337
338
339
340 private void endDailyElement(final String qName) {
341 if (qName.equals(DATE_YEAR_ELT) && !buffer.isEmpty()) {
342 year = Integer.parseInt(buffer.toString());
343 } else if (qName.equals(DATE_MONTH_ELT) && !buffer.isEmpty()) {
344 month = Integer.parseInt(buffer.toString());
345 } else if (qName.equals(DATE_DAY_ELT) && !buffer.isEmpty()) {
346 day = Integer.parseInt(buffer.toString());
347 } else if (qName.equals(MJD_ELT) && !buffer.isEmpty()) {
348 mjd = Integer.parseInt(buffer.toString());
349 mjdDate = new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
350 getUtc());
351 } else if (qName.equals(UT1_M_UTC_ELT)) {
352 dtu1 = overwrite(dtu1, Unit.SECOND);
353 } else if (qName.equals(LOD_ELT)) {
354 lod = overwrite(lod, MILLI_SECOND);
355 } else if (qName.equals(X_ELT)) {
356 x = overwrite(x, Unit.ARC_SECOND);
357 } else if (qName.equals(Y_ELT)) {
358 y = overwrite(y, Unit.ARC_SECOND);
359 } else if (qName.equals(X_RATE_ELT)) {
360 xRate = overwrite(xRate, ARC_SECOND_PER_DAY);
361 } else if (qName.equals(Y_RATE_ELT)) {
362 yRate = overwrite(yRate, ARC_SECOND_PER_DAY);
363 } else if (qName.equals(DPSI_ELT)) {
364 dpsi = overwrite(dpsi, MILLI_ARC_SECOND);
365 } else if (qName.equals(DEPSILON_ELT)) {
366 deps = overwrite(deps, MILLI_ARC_SECOND);
367 } else if (qName.equals(DX_ELT)) {
368 dx = overwrite(dx, MILLI_ARC_SECOND);
369 } else if (qName.equals(DY_ELT)) {
370 dy = overwrite(dy, MILLI_ARC_SECOND);
371 } else if (qName.equals(POLE_ELT) || qName.equals(UT_ELT) || qName.equals(NUTATION_ELT)) {
372 inBulletinA = false;
373 } else if (qName.equals(DATA_EOP_ELT)) {
374 checkDates();
375 if (!Double.isNaN(dtu1) && !Double.isNaN(x) && !Double.isNaN(y)) {
376 final double[] equinox;
377 final double[] nro;
378 if (Double.isNaN(dpsi)) {
379 nro = new double[] {
380 dx, dy
381 };
382 equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
383 } else {
384 equinox = new double[] {
385 dpsi, deps
386 };
387 nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
388 }
389 if (configuration == null || !configuration.isValid(mjd)) {
390
391 configuration = getItrfVersionProvider().getConfiguration(name, mjd);
392 }
393 history.add(new EOPEntry(mjd, dtu1, lod, x, y, Double.NaN, Double.NaN,
394 equinox[0], equinox[1], nro[0], nro[1],
395 configuration.getVersion(), mjdDate, EopDataType.UNKNOWN));
396 }
397 }
398 }
399
400
401
402
403 private void endFinalElement(final String qName) {
404 if (qName.equals(DATE_ELT) && !buffer.isEmpty()) {
405 final String[] fields = buffer.toString().split("-");
406 if (fields.length == 3) {
407 year = Integer.parseInt(fields[0]);
408 month = Integer.parseInt(fields[1]);
409 day = Integer.parseInt(fields[2]);
410 }
411 } else if (qName.equals(MJD_ELT) && !buffer.isEmpty()) {
412 mjd = Integer.parseInt(buffer.toString());
413 mjdDate = new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
414 getUtc());
415 } else if (qName.equals(UT1_U_UTC_ELT)) {
416 dtu1 = overwrite(dtu1, Unit.SECOND);
417 } else if (qName.equals(LOD_ELT)) {
418 lod = overwrite(lod, MILLI_SECOND);
419 } else if (qName.equals(X_ELT)) {
420 x = overwrite(x, Unit.ARC_SECOND);
421 } else if (qName.equals(Y_ELT)) {
422 y = overwrite(y, Unit.ARC_SECOND);
423 } else if (qName.equals(X_RATE_ELT)) {
424 xRate = overwrite(xRate, ARC_SECOND_PER_DAY);
425 } else if (qName.equals(Y_RATE_ELT)) {
426 yRate = overwrite(yRate, ARC_SECOND_PER_DAY);
427 } else if (qName.equals(DPSI_ELT)) {
428 dpsi = overwrite(dpsi, MILLI_ARC_SECOND);
429 } else if (qName.equals(DEPSILON_ELT)) {
430 deps = overwrite(deps, MILLI_ARC_SECOND);
431 } else if (qName.equals(DX_ELT)) {
432 dx = overwrite(dx, MILLI_ARC_SECOND);
433 } else if (qName.equals(DY_ELT)) {
434 dy = overwrite(dy, MILLI_ARC_SECOND);
435 } else if (qName.equals(BULLETIN_A_ELT)) {
436 inBulletinA = false;
437 } else if (qName.equals(EOP_SET_ELT)) {
438 checkDates();
439 if (!Double.isNaN(dtu1) && !Double.isNaN(x) && !Double.isNaN(y)) {
440 final double[] equinox;
441 final double[] nro;
442 if (Double.isNaN(dpsi)) {
443 nro = new double[] {
444 dx, dy
445 };
446 equinox = getConverter().toEquinox(mjdDate, nro[0], nro[1]);
447 } else {
448 equinox = new double[] {
449 dpsi, deps
450 };
451 nro = getConverter().toNonRotating(mjdDate, equinox[0], equinox[1]);
452 }
453 if (configuration == null || !configuration.isValid(mjd)) {
454
455 configuration = getItrfVersionProvider().getConfiguration(name, mjd);
456 }
457 history.add(new EOPEntry(mjd, dtu1, lod, x, y, xRate, yRate,
458 equinox[0], equinox[1], nro[0], nro[1],
459 configuration.getVersion(), mjdDate,
460 EopDataType.UNKNOWN));
461 }
462 }
463 }
464
465
466
467
468
469
470 private double overwrite(final double oldValue, final Unit units) {
471 if (buffer.isEmpty()) {
472
473 return oldValue;
474 } else if (inBulletinA && !Double.isNaN(oldValue)) {
475
476 return oldValue;
477 } else {
478
479 return units.toSI(Double.parseDouble(buffer.toString()));
480 }
481 }
482
483
484
485 private void checkDates() {
486 if (new DateComponents(year, month, day).getMJD() != mjd) {
487 throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
488 name, year, month, day, mjd);
489 }
490 }
491
492
493 @Override
494 public InputSource resolveEntity(final String publicId, final String systemId) {
495
496 return new InputSource();
497 }
498
499 }
500
501 }
502
503
504 private enum DataFileContent {
505
506
507 UNKNOWN,
508
509
510
511
512 BULLETIN_A,
513
514
515
516
517 BULLETIN_B,
518
519
520
521
522 EOP_C04,
523
524
525 DAILY,
526
527
528 FINAL
529
530 }
531
532 }