GeoMagneticModelLoader.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.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.orekit.data.DataLoader;

/** Loads geomagnetic field models from a given input stream. A stream may contain multiple
 * models, the loader reads all available models in consecutive order.
 * <p>
 * The format of the expected model file is either:
 * <ul>
 *   <li>combined format as used by the geomag software, available from the
 *       <a href="http://www.ngdc.noaa.gov/IAGA/vmod/igrf.html">IGRF model site</a>;
 *       supports multiple epochs per file</li>
 *   <li>original format as used by the
 *       <a href="http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml">WMM model site</a>.
 * </ul>
 * <p>
 * <b>Combined Format</b>
 * <pre>
 *     {model name} {epoch} {nMax} {nMaxSec} {nMax3} {validity start} {validity end} {minAlt} {maxAlt} {model name} {line number}
 * {n} {m} {gnm} {hnm} {dgnm} {dhnm} {model name} {line number}
 * </pre>
 * <p>
 * Example:
 * </p>
 * <pre>
 *    WMM2010  2010.00 12 12  0 2010.00 2015.00   -1.0  600.0          WMM2010   0
 * 1  0  -29496.6       0.0      11.6       0.0                        WMM2010   1
 * 1  1   -1586.3    4944.4      16.5     -25.9                        WMM2010   2
 * </pre>
 * <p>
 * <b>Original WMM Format</b>
 * <pre>
 *    {epoch} {model name} {validity start}
 * {n} {m} {gnm} {hnm} {dgnm} {dhnm}
 * </pre>
 * <p>
 * Example:
 * </p>
 * <pre>
 *    2015.0            WMM-2015        12/15/2014
 *  1  0  -29438.5       0.0       10.7        0.0
 *  1  1   -1501.1    4796.2       17.9      -26.8
 * </pre>
 *
 * @author Thomas Neidhart
 */
public class GeoMagneticModelLoader implements DataLoader {

    /** The loaded models. */
    private List<GeoMagneticField> models = new LinkedList<GeoMagneticField>();

    /** Empty constructor.
     * <p>
     * This constructor is not strictly necessary, but it prevents spurious
     * javadoc warnings with JDK 18 and later.
     * </p>
     * @since 12.0
     */
    public GeoMagneticModelLoader() {
        // nothing to do
    }

    /** Returns a {@link Collection} of the {@link GeoMagneticField} models that
     * have been successfully loaded. The {@link Collection} is in
     * insertion-order, thus it may not be sorted in order of the model epoch.
     * @return a {@link Collection} of {@link GeoMagneticField} models
     */
    public Collection<GeoMagneticField> getModels() {
        return models;
    }

    /** {@inheritDoc} */
    public boolean stillAcceptsData() {
        return models == null || models.isEmpty();
    }

    /** {@inheritDoc} */
    public void loadData(final InputStream input, final String name)
        throws IOException, ParseException {

        // open data file and parse values
        final StreamTokenizer str = new StreamTokenizer(new InputStreamReader(input, StandardCharsets.UTF_8));

        while (true) {
            final GeoMagneticField model = readModel(str);
            if (model != null) {
                models.add(model);
            } else {
                break;
            }
        }
    }

    /** Read the model from the given {@link StreamTokenizer}.
     * @param stream the stream to read the model from
     * @return the parsed geomagnetic field model
     * @throws IOException if an I/O error occurs
     */
    private GeoMagneticField readModel(final StreamTokenizer stream) throws IOException {

        // check whether there is another model available in the stream
        final int ttype = stream.nextToken();
        if (ttype == StreamTokenizer.TT_EOF) {
            return null;
        }

        if (ttype == StreamTokenizer.TT_WORD) {
            return readCombinedFormat(stream);
        } else {
            return readOriginalWMMFormat(stream);
        }
    }

    /** Read a magnetic field from combined format.
     * @param stream the stream to read the model from
     * @return magnetic field
     * @throws IOException if some read error occurs
     */
    private GeoMagneticField readCombinedFormat(final StreamTokenizer stream)
        throws IOException {
        final String modelName = stream.sval;
        stream.nextToken();
        final double epoch = stream.nval;
        stream.nextToken();
        final int nMax = (int) stream.nval;
        stream.nextToken();
        final int nMaxSecVar = (int) stream.nval;

        // ignored
        stream.nextToken();

        stream.nextToken();
        final double startYear = stream.nval;

        stream.nextToken();
        final double endYear = stream.nval;

        final GeoMagneticField model = new GeoMagneticField(modelName, epoch, nMax, nMaxSecVar,
                                                            startYear, endYear);

        // the rest is ignored
        stream.nextToken();
        @SuppressWarnings("unused")
        final double altmin = stream.nval;

        stream.nextToken();
        @SuppressWarnings("unused")
        final double altmax = stream.nval;

        stream.nextToken();
        stream.nextToken();

        // loop to get model data from file
        boolean done = false;
        int n;
        int m;

        do {
            stream.nextToken();
            n = (int) stream.nval;
            stream.nextToken();
            m = (int) stream.nval;

            stream.nextToken();
            final double gnm = stream.nval;
            stream.nextToken();
            final double hnm = stream.nval;
            stream.nextToken();
            final double dgnm = stream.nval;
            stream.nextToken();
            final double dhnm = stream.nval;

            model.setMainFieldCoefficients(n, m, gnm, hnm);
            if (n <= nMaxSecVar && m <= nMaxSecVar) {
                model.setSecularVariationCoefficients(n, m, dgnm, dhnm);
            }

            stream.nextToken();
            stream.nextToken();

            done = n == nMax && m == nMax;
        } while (!done);

        return model;
    }

    /** Read a magnetic field from original WMM files.
     * @param stream the stream to read the model from
     * @return magnetic field
     * @throws IOException if some read error occurs
     */
    private GeoMagneticField readOriginalWMMFormat(final StreamTokenizer stream)
        throws IOException {

        // hard-coded values in original WMM format
        final int nMax = 12;
        final int nMaxSecVar = 12;

        // the validity start is encoded in format MM/dd/yyyy
        // use the slash as whitespace character to get separate tokens
        stream.whitespaceChars('/', '/');

        final double epoch = stream.nval;
        stream.nextToken();
        final String modelName = stream.sval;
        stream.nextToken();
        final double month = stream.nval;
        stream.nextToken();
        final double day = stream.nval;
        stream.nextToken();
        final double year = stream.nval;

        final double startYear = GeoMagneticField.getDecimalYear((int) day, (int) month, (int) year);

        final GeoMagneticField model = new GeoMagneticField(modelName, epoch, nMax, nMaxSecVar,
                                                            startYear, epoch + 5.0);

        // loop to get model data from file
        boolean done = false;
        int n;
        int m;

        do {
            stream.nextToken();
            n = (int) stream.nval;
            stream.nextToken();
            m = (int) stream.nval;

            stream.nextToken();
            final double gnm = stream.nval;
            stream.nextToken();
            final double hnm = stream.nval;
            stream.nextToken();
            final double dgnm = stream.nval;
            stream.nextToken();
            final double dhnm = stream.nval;

            model.setMainFieldCoefficients(n, m, gnm, hnm);
            if (n <= nMaxSecVar && m <= nMaxSecVar) {
                model.setSecularVariationCoefficients(n, m, dgnm, dhnm);
            }

            done = n == nMax && m == nMax;
        } while (!done);

        // the original format closes with two delimiting lines of '9's
        stream.nextToken();
        stream.nextToken();

        return model;
    }

}