GeoMagneticFieldFactory.java

/* Copyright 2011-2012 Space Applications Services
 * Licensed to CS Communication & Systèmes (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.SortedMap;
import java.util.TreeMap;

import org.apache.commons.math3.util.FastMath;
import org.orekit.data.DataProvidersManager;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;

/** Factory for different {@link GeoMagneticField} models.
 * <p>
 * This is a utility class, so its constructor is private.
 * </p>
 * @author Thomas Neidhart
 */
public class GeoMagneticFieldFactory {

    /** The currently supported geomagnetic field models. */
    public enum FieldModel {
        /** World Magnetic Model. */
        WMM,
        /** International Geomagnetic Reference Field. */
        IGRF
    }

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

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

    /** Private constructor.
     * <p>
     * This class is a utility class, it should neither have a public nor a
     * default constructor. This private constructor prevents the compiler from
     * generating one automatically.
     * </p>
     */
    private GeoMagneticFieldFactory() {
    }

    /** Get the {@link GeoMagneticField} for the given model type and year.
     * @param type the field model type
     * @param year the decimal year
     * @return a {@link GeoMagneticField} for the given year and model
     * @throws OrekitException if the models could not be loaded
     * @see GeoMagneticField#getDecimalYear(int, int, int)
     */
    public static GeoMagneticField getField(final FieldModel type, final double year)
        throws OrekitException {

        switch (type) {
        case WMM:
            return getWMM(year);
        case IGRF:
            return getIGRF(year);
        default:
            throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
        }
    }

    /** Get the IGRF model for the given year.
     * @param year the decimal year
     * @return a {@link GeoMagneticField} for the given year
     * @throws OrekitException
     *             if the IGRF models could not be loaded
     * @see GeoMagneticField#getDecimalYear(int, int, int)
     */
    public static GeoMagneticField getIGRF(final double year) throws OrekitException {
        synchronized (GeoMagneticFieldFactory.class) {
            if (igrfModels == null) {
                igrfModels = loadModels("^IGRF\\.COF$");
            }
            return getModel(FieldModel.IGRF, igrfModels, year);
        }
    }

    /** Get the WMM model for the given year.
     * @param year the decimal year
     * @return a {@link GeoMagneticField} for the given year
     * @throws OrekitException if the WMM models could not be loaded
     * @see GeoMagneticField#getDecimalYear(int, int, int)
     */
    public static GeoMagneticField getWMM(final double year) throws OrekitException {
        synchronized (GeoMagneticFieldFactory.class) {
            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 TreeMap} 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 TreeMap} of all loaded models
     * @throws OrekitException if the models could not be loaded
     */
    private static TreeMap<Integer, GeoMagneticField> loadModels(final String supportedNames)
        throws OrekitException {

        TreeMap<Integer, GeoMagneticField> loadedModels = null;
        final GeoMagneticModelLoader loader = new GeoMagneticModelLoader();
        DataProvidersManager.getInstance().feed(supportedNames, loader);

        if (!loader.stillAcceptsData()) {
            final Collection<GeoMagneticField> models = loader.getModels();
            if (models != null) {
                loadedModels = new TreeMap<Integer, GeoMagneticField>();
                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
     * @throws OrekitException if the specified year is out of range of the available models
     */
    private static GeoMagneticField getModel(final FieldModel type,
                                             final TreeMap<Integer, GeoMagneticField> models,
                                             final double year)
        throws OrekitException {

        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;
    }
}