KlobucharIonoCoefficientsLoader.java

/* Copyright 2002-2024 CS GROUP
 * 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.ionosphere;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.regex.Pattern;

import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.AbstractSelfFeedingLoader;
import org.orekit.data.DataContext;
import org.orekit.data.DataLoader;
import org.orekit.data.DataProvidersManager;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.DateComponents;

/** Loads Klobuchar-Style ionospheric coefficients a given input stream.
 * A stream contains the alphas and betas coefficient for a given day.
 * <p>
 * They are obtained from <a href="ftp://ftp.aiub.unibe.ch/CODE/">University of Bern Astronomical Institute ftp</a>.
 * Find more on the files at the <a href="http://www.aiub.unibe.ch/research/code___analysis_center/klobuchar_style_ionospheric_coefficients/index_eng.html">Astronomical Institute site</a>.
 * <p>
 * The files are UNIX-style compressed (.Z) files.
 * They have to be extracted to UTF-8 text files before being read by this loader.
 * <p>
 * After extraction, it is assumed they are named CGIMDDD0.YYN where DDD and YY substitute day of year and 2-digits year.
 * <p>
 * The format is always the same, with and example shown below. Only the last 2 lines contains the Klobuchar coefficients.
 * <p>
 * Example:
 * </p>
 * <pre>
 *      2              NAVIGATION DATA     GPS                 RINEX VERSION / TYPE
 * INXFIT V5.3         AIUB                06-JAN-17 09:12     PGM / RUN BY / DATE
 * CODE'S KLOBUCHAR-STYLE IONOSPHERE MODEL FOR DAY 001, 2017   COMMENT
 * Contact address: code(at)aiub.unibe.ch                      COMMENT
 * Data archive:    ftp.unibe.ch/aiub/CODE/                    COMMENT
 *                  www.aiub.unibe.ch/download/CODE/           COMMENT
 * WARNING: USE DATA AT SOUTHERN POLAR REGION WITH CARE        COMMENT
 *     1.2821D-08 -9.6222D-09 -3.5982D-07 -6.0901D-07          ION ALPHA
 *     1.0840D+05 -1.3197D+05 -2.6331D+05  4.0570D+05          ION BETA
 *                                                             END OF HEADER
 * </pre>
 *
 * <p>It is not safe for multiple threads to share a single instance of this class.
 *
 * @author Maxime Journot
 */
public class KlobucharIonoCoefficientsLoader extends AbstractSelfFeedingLoader
        implements DataLoader {

    /** Default supported files name pattern. */
    public static final String DEFAULT_SUPPORTED_NAMES = "CGIM*0\\.*N$";

    /** Pattern for delimiting regular expressions. */
    private static final Pattern SEPARATOR = Pattern.compile("\\s+");

    /** The alpha coefficients loaded. */
    private double[] alpha;

    /** The beta coefficients loaded. */
    private double[] beta;

    /**
     * Constructor with supported names given by user. This constructor uses the {@link
     * DataContext#getDefault() default data context}.
     *
     * @param supportedNames regular expression that matches the names of the RINEX files
     *                       with Klobuchar coefficients.
     * @see #KlobucharIonoCoefficientsLoader(String, DataProvidersManager)
     */
    @DefaultDataContext
    public KlobucharIonoCoefficientsLoader(final String supportedNames) {
        this(supportedNames, DataContext.getDefault().getDataProvidersManager());
    }

    /**
     * Constructor that uses user defined supported names and data context.
     *
     * @param supportedNames       regular expression that matches the names of the RINEX
     *                             files with Klobuchar coefficients.
     * @param dataProvidersManager provides access to auxiliary data files.
     */
    public KlobucharIonoCoefficientsLoader(final String supportedNames,
                                           final DataProvidersManager dataProvidersManager) {
        super(supportedNames, dataProvidersManager);
        this.alpha = null;
        this.beta = null;
    }

    /**
     * Constructor with default supported names. This constructor uses the {@link
     * DataContext#getDefault() default data context}.
     *
     * @see #KlobucharIonoCoefficientsLoader(String, DataProvidersManager)
     * @see #KlobucharIonoCoefficientsLoader(String)
     */
    @DefaultDataContext
    public KlobucharIonoCoefficientsLoader() {
        this(DEFAULT_SUPPORTED_NAMES);
    }

    /** Returns the alpha coefficients array.
     * @return the alpha coefficients array
     */
    public double[] getAlpha() {
        return alpha.clone();
    }

    /** Returns the beta coefficients array.
     * @return the beta coefficients array
     */
    public double[] getBeta() {
        return beta.clone();
    }

    @Override
    public String getSupportedNames() {
        return super.getSupportedNames();
    }

    /** Load the data using supported names .
     */
    public void loadKlobucharIonosphericCoefficients() {
        feed(this);

        // Throw an exception if alphas or betas were not loaded properly
        if (alpha == null || beta == null) {
            throw new OrekitException(OrekitMessages.KLOBUCHAR_ALPHA_BETA_NOT_LOADED,
                    getSupportedNames());
        }
    }

    /** Load the data for a given day.
     * @param dateComponents day given but its DateComponents
     */
    public void loadKlobucharIonosphericCoefficients(final DateComponents dateComponents) {

        // The files are named CGIMDDD0.YYN where DDD and YY substitute day of year and 2-digits year
        final int    doy        = dateComponents.getDayOfYear();
        final String yearString = String.valueOf(dateComponents.getYear());

        this.setSupportedNames(String.format("CGIM%03d0.%2sN", doy, yearString.substring(yearString.length() - 2)));

        try {
            this.loadKlobucharIonosphericCoefficients();
        } catch (OrekitException oe) {
            throw new OrekitException(oe,
                                      OrekitMessages.KLOBUCHAR_ALPHA_BETA_NOT_AVAILABLE_FOR_DATE,
                                      dateComponents.toString());
        }
    }

    /** {@inheritDoc} */
    public boolean stillAcceptsData() {
        return true;
    }

    /** Load Klobuchar-Style ionospheric coefficients read from some file.
     * @param input data input stream
     * @param name name of the file (or zip entry)
     * @exception IOException if data can't be read
     * @exception ParseException if data can't be parsed
     */
    public void loadData(final InputStream input, final String name)
        throws IOException, ParseException {

        int lineNumber = 0;
        String line = null;
        // Open stream and parse data
        try (BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

            for (line = br.readLine(); line != null; line = br.readLine()) {
                ++lineNumber;
                line = line.trim();

                // Read alphas
                if (line.length() > 0 && line.endsWith("ALPHA")) {
                    final String[] alpha_line = SEPARATOR.split(line);
                    alpha = new double[4];
                    for (int j = 0; j < 4; j++) {
                        alpha[j] = Double.parseDouble(alpha_line[j].replace("D", "E"));
                    }
                }

                // Read betas
                if (line.length() > 0 && line.endsWith("BETA")) {
                    final String[] beta_line = SEPARATOR.split(line);
                    beta = new double[4];
                    for (int j = 0; j < 4; j++) {
                        beta[j] = Double.parseDouble(beta_line[j].replace("D", "E"));
                    }
                }
            }

        } catch (NumberFormatException nfe) {
            throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
                                      lineNumber, name, line);
        }

        // Check that alphas and betas were found
        if (alpha == null || beta == null) {
            throw new OrekitException(OrekitMessages.NO_KLOBUCHAR_ALPHA_BETA_IN_FILE, name);
        }

    }
}