RapidDataAndPredictionXMLLoader.java

  1. /* Copyright 2002-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.InputStreamReader;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. import java.util.SortedSet;

  24. import javax.xml.parsers.ParserConfigurationException;
  25. import javax.xml.parsers.SAXParserFactory;

  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.orekit.data.DataLoader;
  28. import org.orekit.data.DataProvidersManager;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.time.DateComponents;
  33. import org.orekit.time.TimeScalesFactory;
  34. import org.orekit.utils.Constants;
  35. import org.orekit.utils.IERSConventions;
  36. import org.xml.sax.Attributes;
  37. import org.xml.sax.InputSource;
  38. import org.xml.sax.SAXException;
  39. import org.xml.sax.XMLReader;
  40. import org.xml.sax.helpers.DefaultHandler;

  41. /** Loader for IERS rapid data and prediction file in XML format (finals file).
  42.  * <p>Rapid data and prediction file contain {@link EOPEntry
  43.  * Earth Orientation Parameters} for several years periods, in one file
  44.  * only that is updated regularly.</p>
  45.  * <p>The XML EOP files are recognized thanks to their base names, which
  46.  * must match one of the the patterns <code>finals.2000A.*.xml</code> or
  47.  * <code>finals.*.xml</code> (or the same ending with <code>.gz</code> for
  48.  * gzip-compressed files) where * stands for a word like "all", "daily",
  49.  * or "data".</p>
  50.  * <p>Files containing data (back to 1973) are available at IERS web site: <a
  51.  * href="http://www.iers.org/IERS/EN/DataProducts/EarthOrientationData/eop.html">Earth orientation data</a>.</p>
  52.  * <p>
  53.  * This class is immutable and hence thread-safe
  54.  * </p>
  55.  * @author Luc Maisonobe
  56.  */
  57. class RapidDataAndPredictionXMLLoader implements EOPHistoryLoader {

  58.     /** Conversion factor for milli-arc seconds entries. */
  59.     private static final double MILLI_ARC_SECONDS_TO_RADIANS = Constants.ARC_SECONDS_TO_RADIANS / 1000.0;

  60.     /** Conversion factor for milli seconds entries. */
  61.     private static final double MILLI_SECONDS_TO_SECONDS = 1.0 / 1000.0;

  62.     /** Regular expression for supported files names. */
  63.     private final String supportedNames;

  64.     /** Build a loader for IERS XML EOP files.
  65.      * @param supportedNames regular expression for supported files names
  66.      */
  67.     RapidDataAndPredictionXMLLoader(final String supportedNames) {
  68.         this.supportedNames = supportedNames;
  69.     }

  70.     /** {@inheritDoc} */
  71.     public void fillHistory(final IERSConventions.NutationCorrectionConverter converter,
  72.                             final SortedSet<EOPEntry> history) {
  73.         final Parser parser = new Parser(converter);
  74.         DataProvidersManager.getInstance().feed(supportedNames, parser);
  75.         history.addAll(parser.history);
  76.     }

  77.     /** Internal class performing the parsing. */
  78.     private static class Parser implements DataLoader {

  79.         /** Converter for nutation corrections. */
  80.         private final IERSConventions.NutationCorrectionConverter converter;

  81.         /** Configuration for ITRF versions. */
  82.         private final ITRFVersionLoader itrfVersionLoader;

  83.         /** History entries. */
  84.         private final List<EOPEntry> history;

  85.         /** Simple constructor.
  86.          * @param converter converter to use
  87.          */
  88.         Parser(final IERSConventions.NutationCorrectionConverter converter) {
  89.             this.converter         = converter;
  90.             this.itrfVersionLoader = new ITRFVersionLoader(ITRFVersionLoader.SUPPORTED_NAMES);
  91.             this.history           = new ArrayList<EOPEntry>();
  92.         }

  93.         /** {@inheritDoc} */
  94.         public boolean stillAcceptsData() {
  95.             return true;
  96.         }

  97.         /** {@inheritDoc} */
  98.         public void loadData(final InputStream input, final String name)
  99.             throws IOException, OrekitException {
  100.             try {
  101.                 // set up a reader for line-oriented bulletin B files
  102.                 final XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
  103.                 reader.setContentHandler(new EOPContentHandler(name));
  104.                 // disable external entities
  105.                 reader.setEntityResolver((publicId, systemId) -> new InputSource());

  106.                 // read all file, ignoring header
  107.                 reader.parse(new InputSource(new InputStreamReader(input, "UTF-8")));

  108.             } catch (SAXException se) {
  109.                 if ((se.getCause() != null) && (se.getCause() instanceof OrekitException)) {
  110.                     throw (OrekitException) se.getCause();
  111.                 }
  112.                 throw new OrekitException(se, LocalizedCoreFormats.SIMPLE_MESSAGE, se.getMessage());
  113.             } catch (ParserConfigurationException pce) {
  114.                 throw new OrekitException(pce, LocalizedCoreFormats.SIMPLE_MESSAGE, pce.getMessage());
  115.             }
  116.         }

  117.         /** Local content handler for XML EOP files. */
  118.         private class EOPContentHandler extends DefaultHandler {

  119.             // CHECKSTYLE: stop JavadocVariable check

  120.             // elements and attributes used in both daily and finals data files
  121.             private static final String MJD_ELT           = "MJD";
  122.             private static final String LOD_ELT           = "LOD";
  123.             private static final String X_ELT             = "X";
  124.             private static final String Y_ELT             = "Y";
  125.             private static final String DPSI_ELT          = "dPsi";
  126.             private static final String DEPSILON_ELT      = "dEpsilon";
  127.             private static final String DX_ELT            = "dX";
  128.             private static final String DY_ELT            = "dY";

  129.             // elements and attributes specific to daily data files
  130.             private static final String DATA_EOP_ELT      = "dataEOP";
  131.             private static final String TIME_SERIES_ELT   = "timeSeries";
  132.             private static final String DATE_YEAR_ELT     = "dateYear";
  133.             private static final String DATE_MONTH_ELT    = "dateMonth";
  134.             private static final String DATE_DAY_ELT      = "dateDay";
  135.             private static final String POLE_ELT          = "pole";
  136.             private static final String UT_ELT            = "UT";
  137.             private static final String UT1_U_UTC_ELT     = "UT1_UTC";
  138.             private static final String NUTATION_ELT      = "nutation";
  139.             private static final String SOURCE_ATTR       = "source";
  140.             private static final String BULLETIN_A_SOURCE = "BulletinA";

  141.             // elements and attributes specific to finals data files
  142.             private static final String FINALS_ELT        = "Finals";
  143.             private static final String DATE_ELT          = "date";
  144.             private static final String EOP_SET_ELT       = "EOPSet";
  145.             private static final String BULLETIN_A_ELT    = "bulletinA";
  146.             private static final String UT1_M_UTC_ELT     = "UT1-UTC";

  147.             private boolean inBulletinA;
  148.             private int     year;
  149.             private int     month;
  150.             private int     day;
  151.             private int     mjd;
  152.             private AbsoluteDate mjdDate;
  153.             private double  dtu1;
  154.             private double  lod;
  155.             private double  x;
  156.             private double  y;
  157.             private double  dpsi;
  158.             private double  deps;
  159.             private double  dx;
  160.             private double  dy;

  161.             // CHECKSTYLE: resume JavadocVariable check

  162.             /** File name. */
  163.             private final String name;

  164.             /** Buffer for read characters. */
  165.             private final StringBuffer buffer;

  166.             /** Indicator for daily data XML format or final data XML format. */
  167.             private DataFileContent content;

  168.             /** ITRF version configuration. */
  169.             private ITRFVersionLoader.ITRFVersionConfiguration configuration;

  170.             /** Simple constructor.
  171.              * @param name file name
  172.              */
  173.             EOPContentHandler(final String name) {
  174.                 this.name   = name;
  175.                 this.buffer = new StringBuffer();
  176.             }

  177.             /** {@inheritDoc} */
  178.             @Override
  179.             public void startDocument() {
  180.                 content       = DataFileContent.UNKNOWN;
  181.                 configuration = null;
  182.             }

  183.             /** {@inheritDoc} */
  184.             @Override
  185.             public void characters(final char[] ch, final int start, final int length) {
  186.                 buffer.append(ch, start, length);
  187.             }

  188.             /** {@inheritDoc} */
  189.             @Override
  190.             public void startElement(final String uri, final String localName,
  191.                                      final String qName, final Attributes atts) {

  192.                 // reset the buffer to empty
  193.                 buffer.delete(0, buffer.length());

  194.                 if (content == DataFileContent.UNKNOWN) {
  195.                     // try to identify file content
  196.                     if (qName.equals(TIME_SERIES_ELT)) {
  197.                         // the file contains final data
  198.                         content = DataFileContent.DAILY;
  199.                     } else if (qName.equals(FINALS_ELT)) {
  200.                         // the file contains final data
  201.                         content = DataFileContent.FINAL;
  202.                     }
  203.                 }

  204.                 if (content == DataFileContent.DAILY) {
  205.                     startDailyElement(qName, atts);
  206.                 } else if (content == DataFileContent.FINAL) {
  207.                     startFinalElement(qName, atts);
  208.                 }

  209.             }

  210.             /** Handle end of an element in a daily data file.
  211.              * @param qName name of the element
  212.              * @param atts element attributes
  213.              */
  214.             private void startDailyElement(final String qName, final Attributes atts) {
  215.                 if (qName.equals(TIME_SERIES_ELT)) {
  216.                     // reset EOP data
  217.                     resetEOPData();
  218.                 } else if (qName.equals(POLE_ELT) || qName.equals(UT_ELT) || qName.equals(NUTATION_ELT)) {
  219.                     final String source = atts.getValue(SOURCE_ATTR);
  220.                     if (source != null) {
  221.                         inBulletinA = source.equals(BULLETIN_A_SOURCE);
  222.                     }
  223.                 }
  224.             }

  225.             /** Handle end of an element in a final data file.
  226.              * @param qName name of the element
  227.              * @param atts element attributes
  228.              */
  229.             private void startFinalElement(final String qName, final Attributes atts) {
  230.                 if (qName.equals(EOP_SET_ELT)) {
  231.                     // reset EOP data
  232.                     resetEOPData();
  233.                 } else if (qName.equals(BULLETIN_A_ELT)) {
  234.                     inBulletinA = true;
  235.                 }
  236.             }

  237.             /** Reset EOP data.
  238.              */
  239.             private void resetEOPData() {
  240.                 inBulletinA = false;
  241.                 year        = -1;
  242.                 month       = -1;
  243.                 day         = -1;
  244.                 mjd         = -1;
  245.                 mjdDate     = null;
  246.                 dtu1        = Double.NaN;
  247.                 lod         = Double.NaN;
  248.                 x           = Double.NaN;
  249.                 y           = Double.NaN;
  250.                 dpsi        = Double.NaN;
  251.                 deps        = Double.NaN;
  252.                 dx          = Double.NaN;
  253.                 dy          = Double.NaN;
  254.             }

  255.             /** {@inheritDoc} */
  256.             @Override
  257.             public void endElement(final String uri, final String localName, final String qName)
  258.                 throws SAXException {
  259.                 try {
  260.                     if (content == DataFileContent.DAILY) {
  261.                         endDailyElement(qName);
  262.                     } else if (content == DataFileContent.FINAL) {
  263.                         endFinalElement(qName);
  264.                     }
  265.                 } catch (OrekitException oe) {
  266.                     throw new SAXException(oe);
  267.                 }
  268.             }

  269.             /** Handle end of an element in a daily data file.
  270.              * @param qName name of the element
  271.              */
  272.             private void endDailyElement(final String qName) {
  273.                 if (qName.equals(DATE_YEAR_ELT) && (buffer.length() > 0)) {
  274.                     year = Integer.parseInt(buffer.toString());
  275.                 } else if (qName.equals(DATE_MONTH_ELT) && (buffer.length() > 0)) {
  276.                     month = Integer.parseInt(buffer.toString());
  277.                 } else if (qName.equals(DATE_DAY_ELT) && (buffer.length() > 0)) {
  278.                     day = Integer.parseInt(buffer.toString());
  279.                 } else if (qName.equals(MJD_ELT) && (buffer.length() > 0)) {
  280.                     mjd     = Integer.parseInt(buffer.toString());
  281.                     mjdDate = new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  282.                                                TimeScalesFactory.getUTC());
  283.                 } else if (qName.equals(UT1_M_UTC_ELT)) {
  284.                     dtu1 = overwrite(dtu1, 1.0);
  285.                 } else if (qName.equals(LOD_ELT)) {
  286.                     lod = overwrite(lod, MILLI_SECONDS_TO_SECONDS);
  287.                 } else if (qName.equals(X_ELT)) {
  288.                     x = overwrite(x, Constants.ARC_SECONDS_TO_RADIANS);
  289.                 } else if (qName.equals(Y_ELT)) {
  290.                     y = overwrite(y, Constants.ARC_SECONDS_TO_RADIANS);
  291.                 } else if (qName.equals(DPSI_ELT)) {
  292.                     dpsi = overwrite(dpsi, MILLI_ARC_SECONDS_TO_RADIANS);
  293.                 } else if (qName.equals(DEPSILON_ELT)) {
  294.                     deps = overwrite(deps, MILLI_ARC_SECONDS_TO_RADIANS);
  295.                 } else if (qName.equals(DX_ELT)) {
  296.                     dx   = overwrite(dx, MILLI_ARC_SECONDS_TO_RADIANS);
  297.                 } else if (qName.equals(DY_ELT)) {
  298.                     dy   = overwrite(dy, MILLI_ARC_SECONDS_TO_RADIANS);
  299.                 } else if (qName.equals(POLE_ELT) || qName.equals(UT_ELT) || qName.equals(NUTATION_ELT)) {
  300.                     inBulletinA = false;
  301.                 } else if (qName.equals(DATA_EOP_ELT)) {
  302.                     checkDates();
  303.                     if ((!Double.isNaN(dtu1)) && (!Double.isNaN(lod)) && (!Double.isNaN(x)) && (!Double.isNaN(y))) {
  304.                         final double[] equinox;
  305.                         final double[] nro;
  306.                         if (Double.isNaN(dpsi)) {
  307.                             nro = new double[] {
  308.                                 dx, dy
  309.                             };
  310.                             equinox = converter.toEquinox(mjdDate, nro[0], nro[1]);
  311.                         } else {
  312.                             equinox = new double[] {
  313.                                 dpsi, deps
  314.                             };
  315.                             nro = converter.toNonRotating(mjdDate, equinox[0], equinox[1]);
  316.                         }
  317.                         if (configuration == null || !configuration.isValid(mjd)) {
  318.                             // get a configuration for current name and date range
  319.                             configuration = itrfVersionLoader.getConfiguration(name, mjd);
  320.                         }
  321.                         history.add(new EOPEntry(mjd, dtu1, lod, x, y, equinox[0], equinox[1], nro[0], nro[1],
  322.                                                  configuration.getVersion()));
  323.                     }
  324.                 }
  325.             }

  326.             /** Handle end of an element in a final data file.
  327.              * @param qName name of the element
  328.              */
  329.             private void endFinalElement(final String qName) {
  330.                 if (qName.equals(DATE_ELT) && (buffer.length() > 0)) {
  331.                     final String[] fields = buffer.toString().split("-");
  332.                     if (fields.length == 3) {
  333.                         year  = Integer.parseInt(fields[0]);
  334.                         month = Integer.parseInt(fields[1]);
  335.                         day   = Integer.parseInt(fields[2]);
  336.                     }
  337.                 } else if (qName.equals(MJD_ELT) && (buffer.length() > 0)) {
  338.                     mjd     = Integer.parseInt(buffer.toString());
  339.                     mjdDate = new AbsoluteDate(new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, mjd),
  340.                                                TimeScalesFactory.getUTC());
  341.                 } else if (qName.equals(UT1_U_UTC_ELT)) {
  342.                     dtu1 = overwrite(dtu1, 1.0);
  343.                 } else if (qName.equals(LOD_ELT)) {
  344.                     lod = overwrite(lod, MILLI_SECONDS_TO_SECONDS);
  345.                 } else if (qName.equals(X_ELT)) {
  346.                     x = overwrite(x, Constants.ARC_SECONDS_TO_RADIANS);
  347.                 } else if (qName.equals(Y_ELT)) {
  348.                     y = overwrite(y, Constants.ARC_SECONDS_TO_RADIANS);
  349.                 } else if (qName.equals(DPSI_ELT)) {
  350.                     dpsi = overwrite(dpsi, MILLI_ARC_SECONDS_TO_RADIANS);
  351.                 } else if (qName.equals(DEPSILON_ELT)) {
  352.                     deps = overwrite(deps, MILLI_ARC_SECONDS_TO_RADIANS);
  353.                 } else if (qName.equals(DX_ELT)) {
  354.                     dx   = overwrite(dx, MILLI_ARC_SECONDS_TO_RADIANS);
  355.                 } else if (qName.equals(DY_ELT)) {
  356.                     dy   = overwrite(dy, MILLI_ARC_SECONDS_TO_RADIANS);
  357.                 } else if (qName.equals(BULLETIN_A_ELT)) {
  358.                     inBulletinA = false;
  359.                 } else if (qName.equals(EOP_SET_ELT)) {
  360.                     checkDates();
  361.                     if ((!Double.isNaN(dtu1)) && (!Double.isNaN(lod)) && (!Double.isNaN(x)) && (!Double.isNaN(y))) {
  362.                         final double[] equinox;
  363.                         final double[] nro;
  364.                         if (Double.isNaN(dpsi)) {
  365.                             nro = new double[] {
  366.                                 dx, dy
  367.                             };
  368.                             equinox = converter.toEquinox(mjdDate, nro[0], nro[1]);
  369.                         } else {
  370.                             equinox = new double[] {
  371.                                 dpsi, deps
  372.                             };
  373.                             nro = converter.toNonRotating(mjdDate, equinox[0], equinox[1]);
  374.                         }
  375.                         if (configuration == null || !configuration.isValid(mjd)) {
  376.                             // get a configuration for current name and date range
  377.                             configuration = itrfVersionLoader.getConfiguration(name, mjd);
  378.                         }
  379.                         history.add(new EOPEntry(mjd, dtu1, lod, x, y, equinox[0], equinox[1], nro[0], nro[1],
  380.                                                  configuration.getVersion()));
  381.                     }
  382.                 }
  383.             }

  384.             /** Overwrite a value if it is not set or if we are in a bulletinB.
  385.              * @param oldValue old value to overwrite (may be NaN)
  386.              * @param factor multiplicative factor to apply to raw read data
  387.              * @return a new value
  388.              */
  389.             private double overwrite(final double oldValue, final double factor) {
  390.                 if (buffer.length() == 0) {
  391.                     // there is nothing to overwrite with
  392.                     return oldValue;
  393.                 } else if (inBulletinA && (!Double.isNaN(oldValue))) {
  394.                     // the value is already set and bulletin A values have a low priority
  395.                     return oldValue;
  396.                 } else {
  397.                     // either the value is not set or it is a high priority bulletin B value
  398.                     return Double.parseDouble(buffer.toString()) * factor;
  399.                 }
  400.             }

  401.             /** Check if the year, month, day date and MJD date are consistent.
  402.              */
  403.             private void checkDates() {
  404.                 if (new DateComponents(year, month, day).getMJD() != mjd) {
  405.                     throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  406.                                               name, year, month, day, mjd);
  407.                 }
  408.             }

  409.         }

  410.         /** Enumerate for data file content. */
  411.         private enum DataFileContent {

  412.             /** Unknown content. */
  413.             UNKNOWN,

  414.             /** Daily data. */
  415.             DAILY,

  416.             /** Final data. */
  417.             FINAL;

  418.         }

  419.     }

  420. }