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.annotation.DefaultDataContext;
21 import org.orekit.data.DataContext;
22 import org.orekit.data.DataProvidersManager;
23 import org.orekit.data.DataSource;
24 import org.orekit.models.earth.atmosphere.data.CssiSpaceWeatherDataLoader.LineParameters;
25 import org.orekit.time.AbsoluteDate;
26 import org.orekit.time.TimeScale;
27 import org.orekit.utils.Constants;
28 import org.orekit.utils.GenericTimeStampedCache;
29 import org.orekit.utils.OrekitConfiguration;
30
31 /**
32 * This class provides three-hourly and daily solar activity data needed by atmospheric models: F107 solar flux, Ap and Kp
33 * indexes. The {@link org.orekit.data.DataLoader} implementation and the parsing is handled by the class
34 * {@link CssiSpaceWeatherDataLoader}.
35 * <p>
36 * The data are retrieved through space weather files offered by AGI/CSSI on the AGI
37 * <a href="https://ftp.agi.com/pub/DynamicEarthData/SpaceWeather-All-v1.2.txt">FTP</a> as
38 * well as on the CelesTrack <a href="http://celestrak.com/SpaceData/">website</a>. These files are updated several times a
39 * day by using several sources mentioned in the
40 * <a href="http://celestrak.com/SpaceData/SpaceWx-format.php">Celestrak space
41 * weather data documentation</a>.
42 * </p>
43 *
44 * @author Clément Jonglez
45 * @author Vincent Cucchietti
46 * @since 10.2
47 */
48 public class CssiSpaceWeatherData extends AbstractSolarActivityData<LineParameters, CssiSpaceWeatherDataLoader> {
49
50 /** Default regular expression for supported names that works with all officially published files. */
51 public static final String DEFAULT_SUPPORTED_NAMES = "^S(?:pace)?W(?:eather)?-(?:All)?.*\\.txt$";
52
53 /** Serializable UID. */
54 private static final long serialVersionUID = 4249411710645968978L;
55
56 /** Date of last data before the prediction starts. */
57 private final AbsoluteDate lastObservedDate;
58
59 /** Date of last daily prediction before the monthly prediction starts. */
60 private final AbsoluteDate lastDailyPredictedDate;
61
62 /**
63 * Simple constructor. This constructor uses the default data context.
64 * <p>
65 * The original file names provided by AGI/CSSI are of the form: SpaceWeather-All-v1.2.txt
66 * (<a href="https://ftp.agi.com/pub/DynamicEarthData/SpaceWeather-All-v1.2.txt">AGI's ftp</a>). So a recommended regular
67 * expression for the supported names that works with all published files is: {@link #DEFAULT_SUPPORTED_NAMES}.
68 * <p>
69 * It provides a default configuration for the thread safe cache :
70 * <ul>
71 * <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
72 * <li>Max span : {@code Constants.JULIAN_DAY}</li>
73 * <li>Max span interval : {@code 0}</li>
74 * </ul>
75 *
76 * @param supportedNames regular expression for supported AGI/CSSI space weather files names
77 */
78 @DefaultDataContext
79 public CssiSpaceWeatherData(final String supportedNames) {
80 this(supportedNames, DataContext.getDefault().getDataProvidersManager(),
81 DataContext.getDefault().getTimeScales().getUTC());
82 }
83
84 /**
85 * Constructor that allows specifying the source of the CSSI space weather file.
86 * <p>
87 * It provides a default configuration for the thread safe cache :
88 * <ul>
89 * <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
90 * <li>Max span : {@code Constants.JULIAN_DAY}</li>
91 * <li>Max span interval : {@code 0}</li>
92 * </ul>
93 *
94 * @param supportedNames regular expression for supported AGI/CSSI space weather files names
95 * @param dataProvidersManager provides access to auxiliary data files.
96 * @param utc UTC time scale.
97 */
98 public CssiSpaceWeatherData(final String supportedNames, final DataProvidersManager dataProvidersManager,
99 final TimeScale utc) {
100 this(supportedNames, new CssiSpaceWeatherDataLoader(utc), dataProvidersManager, utc);
101 }
102
103 /**
104 * Constructor that allows specifying the source of the CSSI space weather file.
105 * <p>
106 * It provides a default configuration for the thread safe cache :
107 * <ul>
108 * <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
109 * <li>Max span : {@code Constants.JULIAN_DAY}</li>
110 * <li>Max span interval : {@code 0}</li>
111 * </ul>
112 *
113 * @param supportedNames regular expression for supported AGI/CSSI space weather files names
114 * @param loader data loader
115 * @param dataProvidersManager provides access to auxiliary data files.
116 * @param utc UTC time scale
117 */
118 public CssiSpaceWeatherData(final String supportedNames, final CssiSpaceWeatherDataLoader loader,
119 final DataProvidersManager dataProvidersManager, final TimeScale utc) {
120 this(supportedNames, loader, dataProvidersManager, utc, OrekitConfiguration.getCacheSlotsNumber(),
121 Constants.JULIAN_DAY, 0);
122 }
123
124 /**
125 * Constructor that allows specifying the source of the CSSI space weather file and customizable thread safe cache
126 * configuration.
127 *
128 * @param supportedNames regular expression for supported AGI/CSSI space weather files names
129 * @param loader data loader
130 * @param dataProvidersManager provides access to auxiliary data files.
131 * @param utc UTC time scale
132 * @param maxSlots maximum number of independent cached time slots in the
133 * {@link GenericTimeStampedCache time-stamped cache}
134 * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
135 * @param maxInterval time interval above which a new slot is created in the
136 * {@link GenericTimeStampedCache time-stamped cache}
137 */
138 public CssiSpaceWeatherData(final String supportedNames, final CssiSpaceWeatherDataLoader loader,
139 final DataProvidersManager dataProvidersManager, final TimeScale utc, final int maxSlots,
140 final double maxSpan, final double maxInterval) {
141 super(supportedNames, loader, dataProvidersManager, utc, maxSlots, maxSpan, maxInterval, Constants.JULIAN_DAY);
142
143 // Initialise fields
144 this.lastObservedDate = loader.getLastObservedDate();
145 this.lastDailyPredictedDate = loader.getLastDailyPredictedDate();
146 }
147
148 /**
149 * Simple constructor which use the {@link DataContext#getDefault() default data context}.
150 * <p>
151 * It provides a default configuration for the thread safe cache :
152 * <ul>
153 * <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
154 * <li>Max span : {@code Constants.JULIAN_DAY}</li>
155 * <li>Max span interval : {@code 0}</li>
156 * </ul>
157 *
158 * @param source source for the data
159 *
160 * @since 12.0
161 */
162 @DefaultDataContext
163 public CssiSpaceWeatherData(final DataSource source) {
164 this(source, DataContext.getDefault().getTimeScales().getUTC());
165 }
166
167 /**
168 * Simple constructor.
169 * <p>
170 * It provides a default configuration for the thread safe cache :
171 * <ul>
172 * <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
173 * <li>Max span : {@code Constants.JULIAN_DAY}</li>
174 * <li>Max span interval : {@code 0}</li>
175 * </ul>
176 *
177 * @param source source for the data
178 * @param utc UTC time scale
179 *
180 * @since 12.0
181 */
182 public CssiSpaceWeatherData(final DataSource source, final TimeScale utc) {
183 this(source, new CssiSpaceWeatherDataLoader(utc), utc);
184 }
185
186 /**
187 * Simple constructor.
188 * <p>
189 * It provides a default configuration for the thread safe cache :
190 * <ul>
191 * <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
192 * <li>Max span : {@code Constants.JULIAN_DAY}</li>
193 * <li>Max span interval : {@code 0}</li>
194 * </ul>
195 *
196 * @param source source for the data
197 * @param loader data loader
198 * @param utc UTC time scale
199 *
200 * @since 12.0
201 */
202 public CssiSpaceWeatherData(final DataSource source, final CssiSpaceWeatherDataLoader loader, final TimeScale utc) {
203 this(source, loader, utc, OrekitConfiguration.getCacheSlotsNumber(), Constants.JULIAN_DAY, 0);
204 }
205
206 /**
207 * Simple constructor with customizable thread safe cache configuration.
208 *
209 * @param source source for the data
210 * @param loader data loader
211 * @param utc UTC time scale
212 * @param maxSlots maximum number of independent cached time slots in the
213 * {@link GenericTimeStampedCache time-stamped cache}
214 * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
215 * @param maxInterval time interval above which a new slot is created in the
216 * {@link GenericTimeStampedCache time-stamped cache}
217 *
218 * @since 12.0
219 */
220 public CssiSpaceWeatherData(final DataSource source, final CssiSpaceWeatherDataLoader loader, final TimeScale utc,
221 final int maxSlots, final double maxSpan, final double maxInterval) {
222 super(source, loader, utc, maxSlots, maxSpan, maxInterval, Constants.JULIAN_DAY);
223 this.lastObservedDate = loader.getLastObservedDate();
224 this.lastDailyPredictedDate = loader.getLastDailyPredictedDate();
225 }
226
227 /** {@inheritDoc} */
228 public double getInstantFlux(final AbsoluteDate date) {
229 return getLinearInterpolation(date, LineParameters::getF107Obs);
230 }
231
232 /** {@inheritDoc} */
233 public double getMeanFlux(final AbsoluteDate date) {
234 return getAverageFlux(date);
235 }
236
237 /** {@inheritDoc} */
238 public double getThreeHourlyKP(final AbsoluteDate date) {
239 if (date.compareTo(lastObservedDate) <= 0) {
240 /* If observation data is available, it contains three-hourly data */
241 final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
242 final LineParameters previousParam = localSolarActivity.getPreviousParam();
243 final double hourOfDay = date.offsetFrom(previousParam.getDate(), getUTC()) / 3600;
244 int i_kp = (int) (hourOfDay / 3);
245 if (i_kp >= 8) {
246 /* hourOfDay can take the value 24.0 at midnight due to floating point precision
247 * when bracketing the dates or during a leap second because the hour of day is
248 * computed in UTC view */
249 i_kp = 7;
250 }
251 return previousParam.getThreeHourlyKp(i_kp);
252 } else {
253 /* Only predictions are available, there are no three-hourly data */
254 return get24HoursKp(date);
255 }
256 }
257
258 /** {@inheritDoc} */
259 public double get24HoursKp(final AbsoluteDate date) {
260 // Get the neighboring solar activity
261 final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
262
263 if (date.compareTo(lastDailyPredictedDate) <= 0) {
264 // Daily data is available, just taking the daily average
265 return localSolarActivity.getPreviousParam().getKpSum() / 8;
266 } else {
267 // Only monthly data is available, better interpolate between two months
268 return getLinearInterpolation(localSolarActivity, lineParam -> lineParam.getKpSum() / 8);
269 }
270 }
271
272 /** {@inheritDoc} */
273 public double getDailyFlux(final AbsoluteDate date) {
274 // Getting the value for the previous day
275 return getDailyFluxOnDay(date.shiftedBy(-Constants.JULIAN_DAY));
276 }
277
278 /** {@inheritDoc} */
279 public double getAverageFlux(final AbsoluteDate date) {
280 // Get the neighboring solar activity
281 final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
282
283 if (date.compareTo(lastDailyPredictedDate) <= 0) {
284 return localSolarActivity.getPreviousParam().getCtr81Obs();
285 } else {
286 // Only monthly data is available, better interpolate between two months
287 return getLinearInterpolation(localSolarActivity, LineParameters::getCtr81Obs);
288 }
289 }
290
291 /** {@inheritDoc} */
292 public double[] getAp(final AbsoluteDate date) {
293 final double[] apArray = new double[7];
294 apArray[0] = getDailyAp(date);
295 apArray[1] = getThreeHourlyAp(date);
296 apArray[2] = getThreeHourlyAp(date.shiftedBy(-3.0 * 3600.0));
297 apArray[3] = getThreeHourlyAp(date.shiftedBy(-6.0 * 3600.0));
298 apArray[4] = getThreeHourlyAp(date.shiftedBy(-9.0 * 3600.0));
299 apArray[5] = get24HoursAverageAp(date.shiftedBy(-12.0 * 3600.0));
300 apArray[6] = get24HoursAverageAp(date.shiftedBy(-36.0 * 3600.0));
301 return apArray;
302 }
303
304 /**
305 * Gets the daily flux on the current day.
306 *
307 * @param date the current date
308 *
309 * @return the daily F10.7 flux (observed)
310 */
311 private double getDailyFluxOnDay(final AbsoluteDate date) {
312 // Get the neighboring solar activity
313 final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
314
315 if (date.compareTo(lastDailyPredictedDate) <= 0) {
316 // Getting the value for the previous day
317 return localSolarActivity.getPreviousParam().getF107Obs();
318 } else {
319 // Only monthly data is available, better interpolate between two months
320 return getLinearInterpolation(localSolarActivity, LineParameters::getF107Obs);
321 }
322 }
323
324 /**
325 * Gets the value of the three-hourly Ap index for the given date.
326 *
327 * @param date the current date
328 *
329 * @return the current three-hourly Ap index
330 */
331 private double getThreeHourlyAp(final AbsoluteDate date) {
332 if (date.compareTo(lastObservedDate.shiftedBy(Constants.JULIAN_DAY)) < 0) {
333 // If observation data is available, it contains three-hourly data.
334 // Get the neighboring solar activity
335 final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
336
337 final LineParameters previousParam = localSolarActivity.getPreviousParam();
338 final double hourOfDay = date.offsetFrom(previousParam.getDate(), getUTC()) / 3600;
339 int i_ap = (int) (hourOfDay / 3);
340 if (i_ap >= 8) {
341 /* hourOfDay can take the value 24.0 at midnight due to floating point precision
342 * when bracketing the dates or during a leap second because the hour of day is
343 * computed in UTC view */
344 i_ap = 7;
345 }
346 return previousParam.getThreeHourlyAp(i_ap);
347 } else {
348 /* Only predictions are available, there are no three-hourly data */
349 return getDailyAp(date);
350 }
351 }
352
353 /**
354 * Gets the running average of the 8 three-hourly Ap indices prior to current time If three-hourly data is available, the
355 * result is different than getDailyAp.
356 *
357 * @param date the current date
358 *
359 * @return the 24 hours running average of the Ap index
360 */
361 private double get24HoursAverageAp(final AbsoluteDate date) {
362 if (date.compareTo(lastDailyPredictedDate) <= 0) {
363 // Computing running mean
364 double apSum = 0.0;
365 for (int i = 0; i < 8; i++) {
366 apSum += getThreeHourlyAp(date.shiftedBy(-3.0 * 3600 * i));
367 }
368 return apSum / 8;
369 } else {
370 /* Only monthly predictions are available, no need to compute the average from
371 * three hourly data */
372 return getDailyAp(date);
373 }
374 }
375
376 /**
377 * Get the daily Ap index for the given date.
378 *
379 * @param date the current date
380 *
381 * @return the daily Ap index
382 */
383 private double getDailyAp(final AbsoluteDate date) {
384 // Get the neighboring solar activity
385 final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
386
387 if (date.compareTo(lastDailyPredictedDate) <= 0) {
388 // Daily data is available, just taking the daily average
389 return localSolarActivity.getPreviousParam().getApAvg();
390 } else {
391 // Only monthly data is available, better interpolate between two months
392 return getLinearInterpolation(localSolarActivity, LineParameters::getApAvg);
393 }
394 }
395
396 }