LazyLoadedGeoMagneticFields.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.models.earth;

import java.util.Collection;
import java.util.NavigableMap;
import java.util.SortedMap;
import java.util.TreeMap;

import org.hipparchus.util.FastMath;
import org.orekit.data.DataProvidersManager;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.models.earth.GeoMagneticFieldFactory.FieldModel;

/**
 * Loads magnetic fields on request and can be configured after creation. Designed to
 * match the behavior of {@link GeoMagneticFieldFactory} in Orekit 10.0
 *
 * @author Evan Ward
 * @author Thomas Neidhart
 * @since 10.1
 */
public class LazyLoadedGeoMagneticFields implements GeoMagneticFields {

    /** Loaded IGRF models. */
    private NavigableMap<Integer, GeoMagneticField> igrfModels = null;

    /** Loaded WMM models. */
    private NavigableMap<Integer, GeoMagneticField> wmmModels = null;

    /** Provides access to auxiliary data files. */
    private final DataProvidersManager dataProvidersManager;

    /**
     * Create a factory for magnetic fields that uses the given data manager to load
     * magnetic field files.
     *
     * @param dataProvidersManager provides access to auxiliary data files.
     */
    public LazyLoadedGeoMagneticFields(final DataProvidersManager dataProvidersManager) {
        this.dataProvidersManager = dataProvidersManager;
    }

    @Override
    public GeoMagneticField getField(final FieldModel type, final double year) {
        switch (type) {
            case WMM:
                return getWMM(year);
            case IGRF:
                return getIGRF(year);
            default:
                throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
        }
    }

    @Override
    public GeoMagneticField getIGRF(final double year) {
        synchronized (this) {
            if (igrfModels == null) {
                igrfModels = loadModels("^IGRF\\.COF$");
            }
            return getModel(FieldModel.IGRF, igrfModels, year);
        }
    }

    @Override
    public GeoMagneticField getWMM(final double year) {
        synchronized (this) {
            if (wmmModels == null) {
                wmmModels = loadModels("^WMM\\.COF$");
            }
            return getModel(FieldModel.WMM, wmmModels, year);
        }
    }

    /** Loads the geomagnetic model files from the given filename. The loaded
     * models are inserted in a {@link NavigableMap} with their epoch as key in order
     * to retrieve them in a sorted manner.
     * @param supportedNames a regular expression for valid filenames
     * @return a {@link NavigableMap} of all loaded models
     */
    private NavigableMap<Integer, GeoMagneticField> loadModels(final String supportedNames) {

        NavigableMap<Integer, GeoMagneticField> loadedModels = null;
        final GeoMagneticModelLoader loader = new GeoMagneticModelLoader();
        dataProvidersManager.feed(supportedNames, loader);

        if (!loader.stillAcceptsData()) {
            final Collection<GeoMagneticField> models = loader.getModels();
            if (models != null) {
                loadedModels = new TreeMap<>();
                for (GeoMagneticField model : models) {
                    // round to a precision of two digits after the comma
                    final int epoch = (int) FastMath.round(model.getEpoch() * 100d);
                    loadedModels.put(epoch, model);
                }
            }
        }

        // if no models could be loaded -> throw exception
        if (loadedModels == null || loadedModels.size() == 0) {
            throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_RESOURCE, supportedNames);
        }

        return loadedModels;
    }

    /** Gets a geomagnetic field model for the given year. In case the specified
     * year does not match an existing model epoch, the resulting field is
     * generated by either time-transforming an existing model using its secular
     * variation coefficients, or by linear interpolating two existing models.
     * @param type the type of the field (e.g. WMM or IGRF)
     * @param models all loaded field models, sorted by their epoch
     * @param year the epoch of the resulting field model
     * @return a {@link GeoMagneticField} model for the given year
     */
    private static GeoMagneticField getModel(final FieldModel type,
                                             final NavigableMap<Integer, GeoMagneticField> models,
                                             final double year) {

        final int epochKey = (int) (year * 100d);
        final SortedMap<Integer, GeoMagneticField> head = models.headMap(epochKey, true);

        if (head.isEmpty()) {
            throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
        }

        GeoMagneticField model = models.get(head.lastKey());
        if (model.getEpoch() < year) {
            if (model.supportsTimeTransform()) {
                model = model.transformModel(year);
            } else {
                final SortedMap<Integer, GeoMagneticField> tail = models.tailMap(epochKey, false);
                if (tail.isEmpty()) {
                    throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
                }
                final GeoMagneticField secondModel = models.get(tail.firstKey());
                if (secondModel != model) {
                    model = model.transformModel(secondModel, year);
                }
            }
        }
        return model;
    }

}