LazyLoadedEop.java

/* Contributed in the public domain.
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.frames;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Supplier;

import org.orekit.data.DataProvidersManager;
import org.orekit.errors.OrekitException;
import org.orekit.time.ChronologicalComparator;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScales;
import org.orekit.utils.Constants;
import org.orekit.utils.IERSConventions;

/**
 * Loads Earth Orientation Parameters (EOP) from a configured set of {@link
 * EopHistoryLoader}s on demand. Methods are synchronized so it is safe for access from
 * multiple threads.
 *
 * @author Guylaine Prat
 * @author Luc Maisonobe
 * @author Pascal Parraud
 * @author Evan Ward
 * @see LazyLoadedFrames
 * @see FramesFactory
 * @since 10.1
 */
public class LazyLoadedEop {

    /** Provides access to the EOP data files. */
    private final DataProvidersManager dataProvidersManager;
    /** Loaders for Earth Orientation parameters. */
    private final Map<IERSConventions, List<EopHistoryLoader>> eopHistoryLoaders;
    /** Threshold for EOP continuity. */
    private double eopContinuityThreshold;
    /** Degree for EOP interpolation.
     * @since 12.0
     */
    private int interpolationDegree;

    /**
     * Create a new instance for loading EOP data from multiple {@link
     * EopHistoryLoader}s.
     *
     * @param dataProvidersManager provides access to the needed EOP data files.
     */
    public LazyLoadedEop(final DataProvidersManager dataProvidersManager) {
        this.dataProvidersManager   = dataProvidersManager;
        this.eopHistoryLoaders      = new HashMap<>();
        this.eopContinuityThreshold = 5 * Constants.JULIAN_DAY;
        this.interpolationDegree    = EOPHistory.DEFAULT_INTERPOLATION_DEGREE;
    }

    /**
     * Get the data providers manager for this instance.
     *
     * @return the provider of EOP data files.
     */
    public DataProvidersManager getDataProvidersManager() {
        return dataProvidersManager;
    }

    /**
     * Add the default loaders EOP history (IAU 1980 precession/nutation).
     * <p>
     * The default loaders look for IERS EOP C04 and bulletins B files. They correspond to
     * {@link IERSConventions#IERS_1996 IERS 1996} conventions.
     * </p>
     *
     * @param rapidDataColumnsSupportedNames regular expression for supported rapid data
     *                                       columns EOP files names (may be null if the
     *                                       default IERS file names are used)
     * @param xmlSupportedNames              regular expression for supported XML EOP
     *                                       files names (may be null if the
     *                                       default IERS file names are used)
     * @param eopC04SupportedNames           regular expression for supported EOP C04
     *                                       files names (may be null if the default IERS
     *                                       file names are used)
     * @param bulletinBSupportedNames        regular expression for supported bulletin B
     *                                       files names (may be null if the default IERS
     *                                       file names are used)
     * @param bulletinASupportedNames        regular expression for supported bulletin A
     *                                       files names (may be null if the default IERS
     *                                       file names are used)
     * @param csvSupportedNames              regular expression for supported csv files names
     *                                       (may be null if the default IERS file names are used)
     * @param utcSupplier                    UTC time scale supplier. Value is not
     *                                       accessed until attempting to load EOP.
     * @see <a href="https://datacenter.iers.org/products/eop/">IERS https data download</a>
     * @see #addEOPHistoryLoader(IERSConventions, EopHistoryLoader)
     * @see #clearEOPHistoryLoaders()
     * @see #addDefaultEOP2000HistoryLoaders(String, String, String, String, String, String, Supplier)
     * @since 12.0
     */
    public void addDefaultEOP1980HistoryLoaders(final String rapidDataColumnsSupportedNames,
                                                final String xmlSupportedNames,
                                                final String eopC04SupportedNames,
                                                final String bulletinBSupportedNames,
                                                final String bulletinASupportedNames,
                                                final String csvSupportedNames,
                                                final Supplier<TimeScale> utcSupplier) {
        final String rapidColNames =
                (rapidDataColumnsSupportedNames == null) ?
                        FramesFactory.RAPID_DATA_PREDICTION_COLUMNS_1980_FILENAME :
                        rapidDataColumnsSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_1996,
                new RapidDataAndPredictionColumnsLoader(false, rapidColNames,
                        dataProvidersManager, utcSupplier));
        final String xmlNames = (xmlSupportedNames == null) ?
                                FramesFactory.XML_1980_FILENAME :
                                xmlSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_1996,
                            new EopXmlLoader(xmlNames, dataProvidersManager, utcSupplier));
        final String eopcNames =
                (eopC04SupportedNames == null) ?
                        FramesFactory.EOPC04_1980_FILENAME : eopC04SupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_1996,
                new EopC04FilesLoader(eopcNames, dataProvidersManager, utcSupplier));
        final String bulBNames =
                (bulletinBSupportedNames == null) ?
                        FramesFactory.BULLETINB_1980_FILENAME : bulletinBSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_1996,
                new BulletinBFilesLoader(bulBNames, dataProvidersManager, utcSupplier));
        final String bulANames =
                (bulletinASupportedNames == null) ?
                        FramesFactory.BULLETINA_FILENAME : bulletinASupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_1996,
                new BulletinAFilesLoader(bulANames, dataProvidersManager, utcSupplier));
        final String csvNames = (csvSupportedNames == null) ?
                                FramesFactory.CSV_FILENAME : csvSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_1996,
                            new EopCsvFilesLoader(csvNames, dataProvidersManager, utcSupplier));
    }

    /**
     * Add the default loaders for EOP history (IAU 2000/2006 precession/nutation).
     * <p>
     * The default loaders look for IERS EOP C04 and bulletins B files. They correspond to
     * both {@link IERSConventions#IERS_2003 IERS 2003} and {@link
     * IERSConventions#IERS_2010 IERS 2010} conventions.
     * </p>
     *
     * @param rapidDataColumnsSupportedNames regular expression for supported rapid data
     *                                       columns EOP files names (may be null if the
     *                                       default IERS file names are used)
     * @param xmlSupportedNames              regular expression for supported XML EOP
     *                                       files names (may be null if the
     *                                       default IERS file names are used)
     * @param eopC04SupportedNames           regular expression for supported EOP C04
     *                                       files names (may be null if the default IERS
     *                                       file names are used)
     * @param bulletinBSupportedNames        regular expression for supported bulletin B
     *                                       files names (may be null if the default IERS
     *                                       file names are used)
     * @param bulletinASupportedNames        regular expression for supported bulletin A
     *                                       files names (may be null if the default IERS
     *                                       file names are used)
     * @param csvSupportedNames              regular expression for supported csv files names
     *                                       (may be null if the default IERS file names are used)
     * @param utcSupplier                    UTC time scale supplier. Value is not
     *                                       accessed until attempting to load EOP.
     * @see <a href="https://datacenter.iers.org/products/eop/">IERS https data download</a>
     * @see #addEOPHistoryLoader(IERSConventions, EopHistoryLoader)
     * @see #clearEOPHistoryLoaders()
     * @see #addDefaultEOP1980HistoryLoaders(String, String, String, String, String, String, Supplier)
     * @since 12.0
     */
    public void addDefaultEOP2000HistoryLoaders(final String rapidDataColumnsSupportedNames,
                                                final String xmlSupportedNames,
                                                final String eopC04SupportedNames,
                                                final String bulletinBSupportedNames,
                                                final String bulletinASupportedNames,
                                                final String csvSupportedNames,
                                                final Supplier<TimeScale> utcSupplier) {
        final String rapidColNames =
                (rapidDataColumnsSupportedNames == null) ?
                        FramesFactory.RAPID_DATA_PREDICTION_COLUMNS_2000_FILENAME :
                        rapidDataColumnsSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_2003,
                            new RapidDataAndPredictionColumnsLoader(true, rapidColNames, dataProvidersManager, utcSupplier));
        addEOPHistoryLoader(IERSConventions.IERS_2010,
                            new RapidDataAndPredictionColumnsLoader(true, rapidColNames, dataProvidersManager, utcSupplier));
        final String xmlNames = (xmlSupportedNames == null) ?
                                FramesFactory.XML_2000_FILENAME :
                                xmlSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_2003,
                            new EopXmlLoader(xmlNames, dataProvidersManager, utcSupplier));
        addEOPHistoryLoader(IERSConventions.IERS_2010,
                            new EopXmlLoader(xmlNames, dataProvidersManager, utcSupplier));
        final String eopcNames = (eopC04SupportedNames == null) ?
                                 FramesFactory.EOPC04_2000_FILENAME : eopC04SupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_2003,
                new EopC04FilesLoader(eopcNames, dataProvidersManager, utcSupplier));
        addEOPHistoryLoader(IERSConventions.IERS_2010,
                new EopC04FilesLoader(eopcNames, dataProvidersManager, utcSupplier));
        final String bulBNames = (bulletinBSupportedNames == null) ?
                                 FramesFactory.BULLETINB_2000_FILENAME : bulletinBSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_2003,
                new BulletinBFilesLoader(bulBNames, dataProvidersManager, utcSupplier));
        addEOPHistoryLoader(IERSConventions.IERS_2010,
                new BulletinBFilesLoader(bulBNames, dataProvidersManager, utcSupplier));
        final String bulANames = (bulletinASupportedNames == null) ?
                                 FramesFactory.BULLETINA_FILENAME : bulletinASupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_2003,
                            new BulletinAFilesLoader(bulANames, dataProvidersManager, utcSupplier));
        addEOPHistoryLoader(IERSConventions.IERS_2010,
                            new BulletinAFilesLoader(bulANames, dataProvidersManager, utcSupplier));
        final String csvNames = (csvSupportedNames == null) ?
                                FramesFactory.CSV_FILENAME : csvSupportedNames;
        addEOPHistoryLoader(IERSConventions.IERS_2003,
                            new EopCsvFilesLoader(csvNames, dataProvidersManager, utcSupplier));
        addEOPHistoryLoader(IERSConventions.IERS_2010,
                            new EopCsvFilesLoader(csvNames, dataProvidersManager, utcSupplier));
    }

    /**
     * Add a loader for Earth Orientation Parameters history.
     *
     * @param conventions IERS conventions to which EOP history applies
     * @param loader      custom loader to add for the EOP history
     * @see #addDefaultEOP1980HistoryLoaders(String, String, String, String, String, String, Supplier)
     * @see #clearEOPHistoryLoaders()
     */
    public void addEOPHistoryLoader(final IERSConventions conventions, final EopHistoryLoader loader) {
        synchronized (eopHistoryLoaders) {
            if (!eopHistoryLoaders.containsKey(conventions)) {
                eopHistoryLoaders.put(conventions, new ArrayList<>());
            }
            eopHistoryLoaders.get(conventions).add(loader);
        }
    }

    /**
     * Clear loaders for Earth Orientation Parameters history.
     *
     * @see #addEOPHistoryLoader(IERSConventions, EopHistoryLoader)
     * @see #addDefaultEOP1980HistoryLoaders(String, String, String, String, String, String, Supplier)
     */
    public void clearEOPHistoryLoaders() {
        synchronized (eopHistoryLoaders) {
            eopHistoryLoaders.clear();
        }
    }

    /**
     * Set the threshold to check EOP continuity.
     * <p>
     * The default threshold (used if this method is never called) is 5 Julian days. If
     * after loading EOP entries some holes between entries exceed this threshold, an
     * exception will be triggered.
     * </p>
     * <p>
     * One case when calling this method is really useful is for applications that use a
     * single Bulletin A, as these bulletins have a roughly one month wide hole for the
     * first bulletin of each month, which contains older final data in addition to the
     * rapid data and the predicted data.
     * </p>
     *
     * @param threshold threshold to use for checking EOP continuity (in seconds)
     */
    public void setEOPContinuityThreshold(final double threshold) {
        eopContinuityThreshold = threshold;
    }

    /**
     * Set the degree for interpolation degree.
     * <p>
     * The default threshold (used if this method is never called) is {@link EOPHistory#DEFAULT_INTERPOLATION_DEGREE}.
     * </p>
     *
     * @param interpolationDegree interpolation degree, must be of the form 4k-1
     * @since 12.0
     */
    public void setInterpolationDegree(final int interpolationDegree) {
        this.interpolationDegree = interpolationDegree;
    }

    /**
     * Get Earth Orientation Parameters history.
     * <p>
     * If no {@link EopHistoryLoader} has been added by calling {@link
     * #addEOPHistoryLoader(IERSConventions, EopHistoryLoader) addEOPHistoryLoader} or if
     * {@link #clearEOPHistoryLoaders() clearEOPHistoryLoaders} has been called
     * afterwards, the {@link #addDefaultEOP1980HistoryLoaders(String, String, String,
     * String, String, String, Supplier)} and {@link #addDefaultEOP2000HistoryLoaders(String,
     * String, String, String, String, String, Supplier)} methods will be called automatically
     * with supported file names parameters all set to null, in order to get the default
     * loaders configuration.
     * </p>
     *
     * @param conventions conventions for which EOP history is requested
     * @param simpleEOP   if true, tidal effects are ignored when interpolating EOP
     * @param timeScales  to use when loading EOP and computing corrections.
     * @return Earth Orientation Parameters history
     */
    public EOPHistory getEOPHistory(final IERSConventions conventions,
                                    final boolean simpleEOP,
                                    final TimeScales timeScales) {

        synchronized (eopHistoryLoaders) {

            if (eopHistoryLoaders.isEmpty()) {
                // set up using default loaders
                final Supplier<TimeScale> utcSupplier = timeScales::getUTC;
                addDefaultEOP2000HistoryLoaders(null, null, null, null, null, null, utcSupplier);
                addDefaultEOP1980HistoryLoaders(null, null, null, null, null, null, utcSupplier);
            }

            // TimeStamped based set needed to remove duplicates
            OrekitException pendingException = null;
            final SortedSet<EOPEntry> data = new TreeSet<>(new ChronologicalComparator());

            // try to load canonical data if available
            if (eopHistoryLoaders.containsKey(conventions)) {
                for (final EopHistoryLoader loader : eopHistoryLoaders.get(conventions)) {
                    try {
                        loader.fillHistory(conventions.getNutationCorrectionConverter(timeScales),
                                           data);
                    } catch (OrekitException oe) {
                        pendingException = oe;
                    }
                }
            }

            if (data.isEmpty() && pendingException != null) {
                throw pendingException;
            }

            final EOPHistory history = new EOPHistory(conventions, interpolationDegree, data, simpleEOP, timeScales);
            history.checkEOPContinuity(eopContinuityThreshold);
            return history;

        }

    }

}