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 }