1 /* Copyright 2020-2025 Clément Jonglez
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 * Clément Jonglez 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.atmosphere.data;
19
20 import org.orekit.errors.OrekitException;
21 import org.orekit.errors.OrekitMessages;
22 import org.orekit.time.AbsoluteDate;
23 import org.orekit.time.ChronologicalComparator;
24 import org.orekit.time.TimeScale;
25
26 import java.io.BufferedReader;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.nio.charset.StandardCharsets;
31 import java.text.ParseException;
32 import java.util.Arrays;
33 import java.util.HashSet;
34 import java.util.NoSuchElementException;
35 import java.util.Set;
36 import java.util.SortedSet;
37 import java.util.TreeSet;
38
39 /**
40 * This class reads solar activity data from CSSI Space Weather files for the class {@link CssiSpaceWeatherData}.
41 * <p>
42 * The data are retrieved through space weather files offered by CSSI/AGI. The data can be retrieved on the AGI
43 * <a href="ftp://ftp.agi.com/pub/DynamicEarthData/SpaceWeather-All-v1.2.txt">
44 * FTP</a>. This file is updated several times a day by using several sources mentioned in the <a
45 * href="http://celestrak.com/SpaceData/SpaceWx-format.php"> Celestrak space weather data documentation</a>.
46 * </p>
47 *
48 * @author Clément Jonglez
49 * @since 10.2
50 */
51 public class CssiSpaceWeatherDataLoader extends AbstractSolarActivityDataLoader<CssiSpaceWeatherDataLoader.LineParameters> {
52
53 /** Date of last data before the prediction starts. */
54 private AbsoluteDate lastObservedDate;
55
56 /** Date of last daily prediction before the monthly prediction starts. */
57 private AbsoluteDate lastDailyPredictedDate;
58
59 /** Data set. */
60 private final SortedSet<LineParameters> set;
61
62 /**
63 * Constructor.
64 *
65 * @param utc UTC time scale
66 */
67 public CssiSpaceWeatherDataLoader(final TimeScale utc) {
68 super(utc);
69 this.lastDailyPredictedDate = null;
70 this.lastObservedDate = null;
71 this.set = new TreeSet<>(new ChronologicalComparator());
72 }
73
74 /**
75 * Checks if the string contains a floating point number.
76 *
77 * @param strNum string to check
78 *
79 * @return true if string contains a valid floating point number, else false
80 */
81 private static boolean isNumeric(final String strNum) {
82 if (strNum == null) {
83 return false;
84 }
85 try {
86 Double.parseDouble(strNum);
87 }
88 catch (NumberFormatException nfe) {
89 return false;
90 }
91 return true;
92 }
93
94 /** {@inheritDoc} */
95 public void loadData(final InputStream input, final String name) throws IOException, ParseException, OrekitException {
96
97 int lineNumber = 0;
98 String line = null;
99 final Set<AbsoluteDate> parsedEpochs = new HashSet<>();
100
101 try (BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
102
103 final CommonLineReader reader = new CommonLineReader(br);
104
105 for (line = reader.readLine(); line != null; line = reader.readLine()) {
106 lineNumber++;
107
108 line = line.trim();
109 if (!line.isEmpty()) {
110
111 if (line.equals("BEGIN DAILY_PREDICTED")) {
112 lastObservedDate = set.last().getDate();
113 }
114
115 if (line.equals("BEGIN MONTHLY_FIT")) {
116 lastDailyPredictedDate = set.last().getDate();
117 }
118
119 if (line.length() == 130 && isNumeric(line.substring(0, 4))) {
120 // extract the data from the line
121 final int year = Integer.parseInt(line.substring(0, 4));
122 final int month = Integer.parseInt(line.substring(5, 7));
123 final int day = Integer.parseInt(line.substring(8, 10));
124 final AbsoluteDate date = new AbsoluteDate(year, month, day, getUTC());
125
126 if (parsedEpochs.add(date)) { // Checking if entry doesn't exist yet
127 final double[] threeHourlyKp = new double[8];
128 /* Kp is written as an integer where a unit equals 0.1, the conversion is
129 * Kp_double = 0.1 * double(Kp_integer) */
130 for (int i = 0; i < 8; i++) {
131 threeHourlyKp[i] = 0.1 * Double.parseDouble(line.substring(19 + 3 * i, 21 + 3 * i));
132 }
133 final double kpSum = 0.1 * Double.parseDouble(line.substring(43, 46));
134
135 final double[] threeHourlyAp = new double[8];
136 for (int i = 0; i < 8; i++) {
137 threeHourlyAp[i] = Double.parseDouble(line.substring(47 + 4 * i, 50 + 4 * i));
138 }
139 final double apAvg = Double.parseDouble(line.substring(79, 82));
140
141 final double f107Adj = Double.parseDouble(line.substring(93, 98));
142
143 final int fluxQualifier = Integer.parseInt(line.substring(99, 100));
144
145 final double ctr81Adj = Double.parseDouble(line.substring(101, 106));
146
147 final double lst81Adj = Double.parseDouble(line.substring(107, 112));
148
149 final double f107Obs = Double.parseDouble(line.substring(113, 118));
150
151 final double ctr81Obs = Double.parseDouble(line.substring(119, 124));
152
153 final double lst81Obs = Double.parseDouble(line.substring(125, 130));
154
155 set.add(new LineParameters(date, threeHourlyKp, kpSum, threeHourlyAp, apAvg, f107Adj,
156 fluxQualifier, ctr81Adj, lst81Adj, f107Obs, ctr81Obs, lst81Obs));
157 }
158 }
159 }
160 }
161 }
162 catch (NumberFormatException nfe) {
163 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber, name, line);
164 }
165
166 try {
167 setMinDate(set.first().getDate());
168 setMaxDate(set.last().getDate());
169 }
170 catch (NoSuchElementException nse) {
171 throw new OrekitException(nse, OrekitMessages.NO_DATA_IN_FILE, name);
172 }
173
174 }
175
176 /**
177 * Getter for the data set.
178 *
179 * @return the data set
180 */
181 @Override
182 public SortedSet<LineParameters> getDataSet() {
183 return set;
184 }
185
186 /**
187 * Gets the day (at data start) of the last daily data entry.
188 *
189 * @return the last daily predicted date
190 */
191 public AbsoluteDate getLastDailyPredictedDate() {
192 return lastDailyPredictedDate;
193 }
194
195 /**
196 * Gets the day (at data start) of the last observed data entry.
197 *
198 * @return the last observed date
199 */
200 public AbsoluteDate getLastObservedDate() {
201 return lastObservedDate;
202 }
203
204 /** Container class for Solar activity indexes. */
205 public static class LineParameters extends AbstractSolarActivityDataLoader.LineParameters {
206
207 /** Serializable UID. */
208 private static final long serialVersionUID = 8151260459653484163L;
209
210 /** Array of 8 three-hourly Kp indices for this entry. */
211 private final double[] threeHourlyKp;
212
213 /**
214 * Sum of the 8 Kp indices for the day expressed to the nearest third of a unit.
215 */
216 private final double kpSum;
217
218 /** Array of 8 three-hourly Ap indices for this entry. */
219 private final double[] threeHourlyAp;
220
221 /** Arithmetic average of the 8 Ap indices for the day. */
222 private final double apAvg;
223
224 /** 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU. */
225 private final double f107Adj;
226
227 /** Flux Qualifier. */
228 private final int fluxQualifier;
229
230 /** Centered 81-day arithmetic average of F10.7 (adjusted). */
231 private final double ctr81Adj;
232
233 /** Last 81-day arithmetic average of F10.7 (adjusted). */
234 private final double lst81Adj;
235
236 /** Observed (unadjusted) value of F10.7. */
237 private final double f107Obs;
238
239 /** Centered 81-day arithmetic average of F10.7 (observed). */
240 private final double ctr81Obs;
241
242 /** Last 81-day arithmetic average of F10.7 (observed). */
243 private final double lst81Obs;
244
245 /**
246 * Constructor.
247 *
248 * @param date entry date
249 * @param threeHourlyKp array of 8 three-hourly Kp indices for this entry
250 * @param kpSum sum of the 8 Kp indices for the day expressed to the nearest third of a unit
251 * @param threeHourlyAp array of 8 three-hourly Ap indices for this entry
252 * @param apAvg arithmetic average of the 8 Ap indices for the day
253 * @param f107Adj 10.7-cm Solar Radio Flux (F10.7)
254 * @param fluxQualifier flux Qualifier
255 * @param ctr81Adj centered 81-day arithmetic average of F10.7
256 * @param lst81Adj last 81-day arithmetic average of F10.7
257 * @param f107Obs observed (unadjusted) value of F10.7
258 * @param ctr81Obs centered 81-day arithmetic average of F10.7 (observed)
259 * @param lst81Obs last 81-day arithmetic average of F10.7 (observed)
260 */
261 public LineParameters(final AbsoluteDate date, final double[] threeHourlyKp, final double kpSum,
262 final double[] threeHourlyAp, final double apAvg, final double f107Adj,
263 final int fluxQualifier, final double ctr81Adj, final double lst81Adj,
264 final double f107Obs, final double ctr81Obs, final double lst81Obs) {
265 super(date);
266 this.threeHourlyKp = threeHourlyKp.clone();
267 this.kpSum = kpSum;
268 this.threeHourlyAp = threeHourlyAp.clone();
269 this.apAvg = apAvg;
270 this.f107Adj = f107Adj;
271 this.fluxQualifier = fluxQualifier;
272 this.ctr81Adj = ctr81Adj;
273 this.lst81Adj = lst81Adj;
274 this.f107Obs = f107Obs;
275 this.ctr81Obs = ctr81Obs;
276 this.lst81Obs = lst81Obs;
277 }
278
279 /** {@inheritDoc} */
280 @Override
281 public int compareTo(final AbstractSolarActivityDataLoader.LineParameters lineParameters) {
282 return getDate().compareTo(lineParameters.getDate());
283 }
284
285 /** {@inheritDoc} */
286 @Override
287 public boolean equals(final Object o) {
288 if (this == o) {
289 return true;
290 }
291 if (o == null || getClass() != o.getClass()) {
292 return false;
293 }
294
295 final LineParameters that = (LineParameters) o;
296
297 if (Double.compare(getKpSum(), that.getKpSum()) != 0) {
298 return false;
299 }
300 if (Double.compare(getApAvg(), that.getApAvg()) != 0) {
301 return false;
302 }
303 if (Double.compare(getF107Adj(), that.getF107Adj()) != 0) {
304 return false;
305 }
306 if (getFluxQualifier() != that.getFluxQualifier()) {
307 return false;
308 }
309 if (Double.compare(getCtr81Adj(), that.getCtr81Adj()) != 0) {
310 return false;
311 }
312 if (Double.compare(getLst81Adj(), that.getLst81Adj()) != 0) {
313 return false;
314 }
315 if (Double.compare(getF107Obs(), that.getF107Obs()) != 0) {
316 return false;
317 }
318 if (Double.compare(getCtr81Obs(), that.getCtr81Obs()) != 0) {
319 return false;
320 }
321 if (Double.compare(getLst81Obs(), that.getLst81Obs()) != 0) {
322 return false;
323 }
324 if (!Arrays.equals(getThreeHourlyKp(), that.getThreeHourlyKp())) {
325 return false;
326 }
327 return Arrays.equals(getThreeHourlyAp(), that.getThreeHourlyAp());
328 }
329
330 /** {@inheritDoc} */
331 @Override
332 public int hashCode() {
333 int result;
334 result = Arrays.hashCode(getThreeHourlyKp());
335 result = 31 * result + Double.hashCode(getKpSum());
336 result = 31 * result + Arrays.hashCode(getThreeHourlyAp());
337 result = 31 * result + Double.hashCode(getApAvg());
338 result = 31 * result + Double.hashCode(getF107Adj());
339 result = 31 * result + getFluxQualifier();
340 result = 31 * result + Double.hashCode(getCtr81Adj());
341 result = 31 * result + Double.hashCode(getLst81Adj());
342 result = 31 * result + Double.hashCode(getF107Obs());
343 result = 31 * result + Double.hashCode(getCtr81Obs());
344 result = 31 * result + Double.hashCode(getLst81Obs());
345 return result;
346 }
347
348 /**
349 * Gets the three-hourly Kp index at index i from the threeHourlyKp array.
350 *
351 * @param i index of the Kp index to retrieve [0-7]
352 *
353 * @return the three hourly Kp index at index i
354 */
355 public double getThreeHourlyKp(final int i) {
356 return threeHourlyKp[i];
357 }
358
359 /**
360 * Gets the three-hourly Ap index at index i from the threeHourlyAp array.
361 *
362 * @param i index of the Ap to retrieve [0-7]
363 *
364 * @return the three hourly Ap index at index i
365 */
366 public double getThreeHourlyAp(final int i) {
367 return threeHourlyAp[i];
368 }
369
370 /**
371 * Gets the array of the eight three-hourly Kp indices for the current entry.
372 *
373 * @return the array of eight three-hourly Kp indices
374 */
375 public double[] getThreeHourlyKp() {
376 return threeHourlyKp.clone();
377 }
378
379 /**
380 * Gets the sum of all eight Kp indices for the current entry.
381 *
382 * @return the sum of all eight Kp indices
383 */
384 public double getKpSum() {
385 return kpSum;
386 }
387
388 /**
389 * Gets the array of the eight three-hourly Ap indices for the current entry.
390 *
391 * @return the array of eight three-hourly Ap indices
392 */
393 public double[] getThreeHourlyAp() {
394 return threeHourlyAp.clone();
395 }
396
397 /**
398 * Gets the arithmetic average of all eight Ap indices for the current entry.
399 *
400 * @return the average of all eight Ap indices
401 */
402 public double getApAvg() {
403 return apAvg;
404 }
405
406 /**
407 * Gets the last 81-day arithmetic average of F10.7 (observed).
408 *
409 * @return the last 81-day arithmetic average of F10.7 (observed)
410 */
411 public double getLst81Obs() {
412 return lst81Obs;
413 }
414
415 /**
416 * Gets the centered 81-day arithmetic average of F10.7 (observed).
417 *
418 * @return the centered 81-day arithmetic average of F10.7 (observed)
419 */
420 public double getCtr81Obs() {
421 return ctr81Obs;
422 }
423
424 /**
425 * Gets the observed (unadjusted) value of F10.7.
426 *
427 * @return the observed (unadjusted) value of F10.7
428 */
429 public double getF107Obs() {
430 return f107Obs;
431 }
432
433 /**
434 * Gets the last 81-day arithmetic average of F10.7 (adjusted).
435 *
436 * @return the last 81-day arithmetic average of F10.7 (adjusted)
437 */
438 public double getLst81Adj() {
439 return lst81Adj;
440 }
441
442 /**
443 * Gets the centered 81-day arithmetic average of F10.7 (adjusted).
444 *
445 * @return the centered 81-day arithmetic average of F10.7 (adjusted)
446 */
447 public double getCtr81Adj() {
448 return ctr81Adj;
449 }
450
451 /**
452 * Gets the Flux Qualifier.
453 *
454 * @return the Flux Qualifier
455 */
456 public int getFluxQualifier() {
457 return fluxQualifier;
458 }
459
460 /**
461 * Gets the 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU.
462 *
463 * @return the 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU
464 */
465 public double getF107Adj() {
466 return f107Adj;
467 }
468 }
469
470 }