1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.orekit.models.earth.ionosphere;
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.nio.charset.StandardCharsets;
25  import java.text.ParseException;
26  import java.util.Locale;
27  import java.util.regex.Pattern;
28  
29  import org.orekit.annotation.DefaultDataContext;
30  import org.orekit.data.AbstractSelfFeedingLoader;
31  import org.orekit.data.DataContext;
32  import org.orekit.data.DataLoader;
33  import org.orekit.data.DataProvidersManager;
34  import org.orekit.errors.OrekitException;
35  import org.orekit.errors.OrekitMessages;
36  import org.orekit.time.DateComponents;
37  
38  /** Loads Klobuchar-Style ionospheric coefficients a given input stream.
39   * A stream contains the alphas and betas coefficient for a given day.
40   * <p>
41   * They are obtained from <a href="ftp://ftp.aiub.unibe.ch/CODE/">University of Bern Astronomical Institute ftp</a>.
42   * 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>.
43   * <p>
44   * The files are UNIX-style compressed (.Z) files.
45   * They have to be extracted to UTF-8 text files before being read by this loader.
46   * <p>
47   * After extraction, it is assumed they are named CGIMDDD0.YYN where DDD and YY substitute day of year and 2-digits year.
48   * <p>
49   * The format is always the same, with and example shown below. Only the last 2 lines contains the Klobuchar coefficients.
50   * <p>
51   * Example:
52   * </p>
53   * <pre>
54   *      2              NAVIGATION DATA     GPS                 RINEX VERSION / TYPE
55   * INXFIT V5.3         AIUB                06-JAN-17 09:12     PGM / RUN BY / DATE
56   * CODE'S KLOBUCHAR-STYLE IONOSPHERE MODEL FOR DAY 001, 2017   COMMENT
57   * Contact address: code(at)aiub.unibe.ch                      COMMENT
58   * Data archive:    ftp.unibe.ch/aiub/CODE/                    COMMENT
59   *                  www.aiub.unibe.ch/download/CODE/           COMMENT
60   * WARNING: USE DATA AT SOUTHERN POLAR REGION WITH CARE        COMMENT
61   *     1.2821D-08 -9.6222D-09 -3.5982D-07 -6.0901D-07          ION ALPHA
62   *     1.0840D+05 -1.3197D+05 -2.6331D+05  4.0570D+05          ION BETA
63   *                                                             END OF HEADER
64   * </pre>
65   *
66   * <p>It is not safe for multiple threads to share a single instance of this class.
67   *
68   * @author Maxime Journot
69   */
70  public class KlobucharIonoCoefficientsLoader extends AbstractSelfFeedingLoader
71          implements DataLoader {
72  
73      /** Default supported files name pattern. */
74      public static final String DEFAULT_SUPPORTED_NAMES = "CGIM*0\\.*N$";
75  
76      /** Pattern for delimiting regular expressions. */
77      private static final Pattern SEPARATOR = Pattern.compile("\\s+");
78  
79      /** The alpha coefficients loaded. */
80      private double[] alpha;
81  
82      /** The beta coefficients loaded. */
83      private double[] beta;
84  
85      /**
86       * Constructor with supported names given by user. This constructor uses the {@link
87       * DataContext#getDefault() default data context}.
88       *
89       * @param supportedNames regular expression that matches the names of the RINEX files
90       *                       with Klobuchar coefficients.
91       * @see #KlobucharIonoCoefficientsLoader(String, DataProvidersManager)
92       */
93      @DefaultDataContext
94      public KlobucharIonoCoefficientsLoader(final String supportedNames) {
95          this(supportedNames, DataContext.getDefault().getDataProvidersManager());
96      }
97  
98      /**
99       * Constructor that uses user defined supported names and data context.
100      *
101      * @param supportedNames       regular expression that matches the names of the RINEX
102      *                             files with Klobuchar coefficients.
103      * @param dataProvidersManager provides access to auxiliary data files.
104      */
105     public KlobucharIonoCoefficientsLoader(final String supportedNames,
106                                            final DataProvidersManager dataProvidersManager) {
107         super(supportedNames, dataProvidersManager);
108         this.alpha = null;
109         this.beta = null;
110     }
111 
112     /**
113      * Constructor with default supported names. This constructor uses the {@link
114      * DataContext#getDefault() default data context}.
115      *
116      * @see #KlobucharIonoCoefficientsLoader(String, DataProvidersManager)
117      * @see #KlobucharIonoCoefficientsLoader(String)
118      */
119     @DefaultDataContext
120     public KlobucharIonoCoefficientsLoader() {
121         this(DEFAULT_SUPPORTED_NAMES);
122     }
123 
124     /** Returns the alpha coefficients array.
125      * @return the alpha coefficients array
126      */
127     public double[] getAlpha() {
128         return alpha.clone();
129     }
130 
131     /** Returns the beta coefficients array.
132      * @return the beta coefficients array
133      */
134     public double[] getBeta() {
135         return beta.clone();
136     }
137 
138     @Override
139     public String getSupportedNames() {
140         return super.getSupportedNames();
141     }
142 
143     /** Load the data using supported names .
144      */
145     public void loadKlobucharIonosphericCoefficients() {
146         feed(this);
147 
148         // Throw an exception if alphas or betas were not loaded properly
149         if (alpha == null || beta == null) {
150             throw new OrekitException(OrekitMessages.KLOBUCHAR_ALPHA_BETA_NOT_LOADED,
151                     getSupportedNames());
152         }
153     }
154 
155     /** Load the data for a given day.
156      * @param dateComponents day given but its DateComponents
157      */
158     public void loadKlobucharIonosphericCoefficients(final DateComponents dateComponents) {
159 
160         // The files are named CGIMDDD0.YYN where DDD and YY substitute day of year and 2-digits year
161         final int    doy        = dateComponents.getDayOfYear();
162         final String yearString = String.valueOf(dateComponents.getYear());
163 
164         this.setSupportedNames(String.format(Locale.US, "CGIM%03d0.%2sN",
165                                              doy, yearString.substring(yearString.length() - 2)));
166 
167         try {
168             this.loadKlobucharIonosphericCoefficients();
169         } catch (OrekitException oe) {
170             throw new OrekitException(oe,
171                                       OrekitMessages.KLOBUCHAR_ALPHA_BETA_NOT_AVAILABLE_FOR_DATE,
172                                       dateComponents.toString());
173         }
174     }
175 
176     /** {@inheritDoc} */
177     public boolean stillAcceptsData() {
178         return true;
179     }
180 
181     /** Load Klobuchar-Style ionospheric coefficients read from some file.
182      * @param input data input stream
183      * @param name name of the file (or zip entry)
184      * @exception IOException if data can't be read
185      * @exception ParseException if data can't be parsed
186      */
187     public void loadData(final InputStream input, final String name)
188         throws IOException, ParseException {
189 
190         int lineNumber = 0;
191         String line = null;
192         // Open stream and parse data
193         try (BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
194 
195             for (line = br.readLine(); line != null; line = br.readLine()) {
196                 ++lineNumber;
197                 line = line.trim();
198 
199                 // Read alphas
200                 if (line.length() > 0 && line.endsWith("ALPHA")) {
201                     final String[] alpha_line = SEPARATOR.split(line);
202                     alpha = new double[4];
203                     for (int j = 0; j < 4; j++) {
204                         alpha[j] = Double.parseDouble(alpha_line[j].replace("D", "E"));
205                     }
206                 }
207 
208                 // Read betas
209                 if (line.length() > 0 && line.endsWith("BETA")) {
210                     final String[] beta_line = SEPARATOR.split(line);
211                     beta = new double[4];
212                     for (int j = 0; j < 4; j++) {
213                         beta[j] = Double.parseDouble(beta_line[j].replace("D", "E"));
214                     }
215                 }
216             }
217 
218         } catch (NumberFormatException nfe) {
219             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
220                                       lineNumber, name, line);
221         }
222 
223         // Check that alphas and betas were found
224         if (alpha == null || beta == null) {
225             throw new OrekitException(OrekitMessages.NO_KLOBUCHAR_ALPHA_BETA_IN_FILE, name);
226         }
227 
228     }
229 }