1   /* Copyright 2002-2026 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.atmosphere.data;
19  
20  import java.io.BufferedInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.Serial;
24  import java.text.ParseException;
25  import java.util.List;
26  import java.util.stream.Collectors;
27  
28  import org.hipparchus.exception.DummyLocalizable;
29  import org.orekit.annotation.DefaultDataContext;
30  import org.orekit.data.DataContext;
31  import org.orekit.data.DataProvidersManager;
32  import org.orekit.data.DataSource;
33  import org.orekit.errors.OrekitException;
34  import org.orekit.errors.OrekitMessages;
35  import org.orekit.models.earth.atmosphere.JB2008InputParameters;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.time.TimeScale;
38  import org.orekit.utils.Constants;
39  import org.orekit.utils.ImmutableTimeStampedCache;
40  
41  
42  /**
43   * This class provides a container for the solar indices data required by the JB2008
44   * atmospheric model. This container only stores information provided in the SOLFSMY and DTCFILE text file
45   * provided by Space Environment Technologies. Therefore it doesn't provide  the geomagnetic storm
46   * indices available in the SOLRESAP file.
47   * The {@link org.orekit.data.DataLoader} implementations and the parsing are handled by
48   * the {@link SOLFSMYDataLoader} {@link DtcDataLoader} classes.
49   * <p>
50   * Data are available on Space Environment Technologies'
51   * <a href="http://sol.spacenvironment.net/jb2008">website</a>.
52   *
53   * The work done for this class is based on the CssiSpaceWeatherData class
54   * by Clément Jonglez, the JB2008 interface by Pascal Parraud, and corrections for
55   * the CssiSpaceWeatherData implementation by Bryan Cazabonne and Evan Ward.
56   *
57   * @author Louis Aucouturier
58   * @since 11.2
59   */
60  public class JB2008SpaceEnvironmentData implements JB2008InputParameters {
61  
62      /** Default regular expression for supported names that works with test and published files for the SOLFSMY file. */
63      public static final String DEFAULT_SUPPORTED_NAMES_SOLFSMY = "(SOLFSMY)(.*)((\\.txt)|(\\.TXT))";
64  
65      /** Default regular expression for supported names that works with test and published files for the DTCFILE file. */
66      public static final String DEFAULT_SUPPORTED_NAMES_DTC = "DTCFILE.TXT";
67  
68      /** Serializable UID. */
69      @Serial
70      private static final long serialVersionUID = 7735042547323407578L;
71  
72      /** Size of the list. */
73      private static final int N_NEIGHBORS = 2;
74  
75      /** Data set for SOLFSMY file. */
76      private final transient ImmutableTimeStampedCache<SOLFSMYDataLoader.LineParameters> dataSOL;
77  
78      /** Data set for DTCFILE file. */
79      private final transient ImmutableTimeStampedCache<DtcDataLoader.LineParameters> dataDTC;
80  
81      /** First available date. */
82      private final AbsoluteDate firstDate;
83  
84      /** Last available date. */
85      private final AbsoluteDate lastDate;
86  
87      /** Previous set of solar activity parameters. */
88      private SOLFSMYDataLoader.LineParameters previousParamSOL;
89  
90      /** Current set of solar activity parameters. */
91      private SOLFSMYDataLoader.LineParameters nextParamSOL;
92  
93      /** Previous set of solar activity parameters. */
94      private DtcDataLoader.LineParameters previousParamDTC;
95  
96      /** Current set of solar activity parameters. */
97      private DtcDataLoader.LineParameters nextParamDTC;
98  
99      /**
100      * Simple constructor. This constructor uses the default data context.
101      *
102      * @param supportedNamesSOL regular expression for SOLFSMY space weather files names
103      * with variations allowed between SOLFSMY and the file extension.
104      * @param supportedNamesDTC regular expression for DTCFILE files names
105      * with variations allowed between DTCFILE and the file extension.
106      */
107     @DefaultDataContext
108     public JB2008SpaceEnvironmentData(final String supportedNamesSOL, final String supportedNamesDTC) {
109         this(supportedNamesSOL, supportedNamesDTC, DataContext.getDefault().getDataProvidersManager(),
110                 DataContext.getDefault().getTimeScales().getUTC());
111     }
112 
113     /**
114      * Constructor that allows specifying the source of the SOLFSMY space weather
115      * file.
116      * This constructor takes a supplementary argument, the supported names for DTCFILE,
117      * in order to setup the second loader.
118      *
119      * @param supportedNamesSOL regular expression for SOLFSMY space weather files names
120      * with variations allowed between SOLFSMY and the file extension.
121      * @param supportedNamesDTC regular expression for DTCFILE files names
122      * with variations allowed between DTCFILE and the file extension.
123      * @param dataProvidersManager provides access to auxiliary data files.
124      * @param utc                  UTC time scale.
125      */
126     public JB2008SpaceEnvironmentData(final String supportedNamesSOL,
127                                       final String supportedNamesDTC,
128                                       final DataProvidersManager dataProvidersManager,
129                                       final TimeScale utc) {
130 
131         // Load SOLFSMY data
132         final SOLFSMYDataLoader loaderSOL = new SOLFSMYDataLoader(utc);
133         dataProvidersManager.feed(supportedNamesSOL, loaderSOL);
134         dataSOL = new ImmutableTimeStampedCache<>(N_NEIGHBORS, loaderSOL.getDataSet());
135 
136         // Load DTC data
137         final DtcDataLoader loaderDTC = new DtcDataLoader(utc);
138         dataProvidersManager.feed(supportedNamesDTC, loaderDTC);
139         dataDTC = new ImmutableTimeStampedCache<>(N_NEIGHBORS, loaderDTC.getDataSet());
140 
141         // Because the two files are generated by the same organism,
142         // the first and last epochs are identical between the two files
143         firstDate = loaderSOL.getMinDate();
144         lastDate  = loaderSOL.getMaxDate();
145 
146     }
147 
148     /**
149      * Simple constructor. This constructor uses the {@link DataContext#getDefault()
150      * default data context}.
151      * @param sourceSolfsmy source for the SOLFSMY data
152      * @param sourceDtc     source for the DTC data
153      * @since 12.0
154      */
155     @DefaultDataContext
156     public JB2008SpaceEnvironmentData(final DataSource sourceSolfsmy,
157                                       final DataSource sourceDtc) {
158         this(sourceSolfsmy, sourceDtc, DataContext.getDefault().getTimeScales().getUTC());
159     }
160 
161     /**
162      * Constructor that allows specifying the source of the SOLFSMY space weather
163      * file.
164      * This constructor takes a supplementary argument, the source for DTCFILE,
165      * in order to setup the second loader.
166      *
167      * @param sourceSolfsmy source for the SOLFSMY data
168      * @param sourceDtc     source for the DTC data
169      * @param utc           UTC time scale
170      * @since 12.0
171      */
172     public JB2008SpaceEnvironmentData(final DataSource sourceSolfsmy,
173                                       final DataSource sourceDtc,
174                                       final TimeScale utc) {
175         try {
176 
177             // Load SOLFSMY data
178             final SOLFSMYDataLoader loaderSOL = new SOLFSMYDataLoader(utc);
179             try (InputStream is = sourceSolfsmy.getOpener().openStreamOnce();
180                  BufferedInputStream bis = new BufferedInputStream(is)) {
181                 loaderSOL.loadData(bis, sourceSolfsmy.getName());
182             }
183 
184             // Load DTC data
185             final DtcDataLoader loaderDTC = new DtcDataLoader(utc);
186             try (InputStream is = sourceDtc.getOpener().openStreamOnce();
187                  BufferedInputStream bis = new BufferedInputStream(is)) {
188                 loaderDTC.loadData(bis, sourceDtc.getName());
189             }
190 
191             // Initialise fields
192             dataSOL = new ImmutableTimeStampedCache<>(N_NEIGHBORS, loaderSOL.getDataSet());
193             dataDTC = new ImmutableTimeStampedCache<>(N_NEIGHBORS, loaderDTC.getDataSet());
194             // Because the two files are generated by the same organism,
195             // the first and last epochs are identical between the two files
196             firstDate = loaderSOL.getMinDate();
197             lastDate  = loaderSOL.getMaxDate();
198 
199         } catch (IOException | ParseException ioe) {
200             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
201         }
202 
203     }
204 
205     /** {@inheritDoc} */
206     public AbsoluteDate getMinDate() {
207         return firstDate;
208     }
209 
210     /** {@inheritDoc} */
211     public AbsoluteDate getMaxDate() {
212         return lastDate;
213     }
214 
215     /**
216      * Find the data bracketing a specified date in dataSOL.
217      *
218      * @param date date to bracket
219      */
220     private void bracketDateSOL(final AbsoluteDate date) {
221         /**
222          * The presence of the shift in dates for checks on the validity of dates
223          * is here to enforce the lag on the parameters (5-day lag max for Y10 parameters).
224          * This lag is already present in the date parameter.
225          */
226         final AbsoluteDate firstDateUsefulSOL = firstDate.shiftedBy(-5 * Constants.JULIAN_DAY);
227         if (date.durationFrom(firstDateUsefulSOL) < 0) {
228             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE,
229                     date, firstDateUsefulSOL, lastDate, firstDateUsefulSOL.durationFrom(date));
230         }
231         if (date.durationFrom(lastDate) > 0) {
232             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_AFTER,
233                     date, firstDateUsefulSOL, lastDate, date.durationFrom(lastDate));
234         }
235 
236         // don't search if the cached selection is fine
237         if (previousParamSOL != null && date.durationFrom(previousParamSOL.getDate()) > 0 &&
238                 date.durationFrom(nextParamSOL.getDate()) <= 0) {
239             return;
240         }
241 
242         final List<SOLFSMYDataLoader.LineParameters> neigbors = dataSOL.getNeighbors(date).collect(Collectors.toList());
243         previousParamSOL = neigbors.getFirst();
244         nextParamSOL = neigbors.get(1);
245 
246     }
247 
248     /**
249      * Find the data bracketing a specified date in dataDTC.
250      *
251      * @param date date to bracket
252      */
253     private void bracketDateDTC(final AbsoluteDate date) {
254         // No data lag
255         final AbsoluteDate firstDateUsefulDTC = firstDate;
256         if (date.durationFrom(firstDateUsefulDTC) < 0) {
257             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE,
258                     date, firstDateUsefulDTC, lastDate, firstDateUsefulDTC.durationFrom(date));
259         }
260         if (date.durationFrom(lastDate) > 0) {
261             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_AFTER,
262                     date, firstDateUsefulDTC, lastDate, date.durationFrom(lastDate));
263         }
264 
265         // don't search if the cached selection is fine
266         if (previousParamDTC != null && date.durationFrom(previousParamDTC.getDate()) > 0 &&
267                 date.durationFrom(nextParamDTC.getDate()) <= 0) {
268             return;
269         }
270 
271         final List<DtcDataLoader.LineParameters> neigbors = dataDTC.getNeighbors(date).collect(Collectors.toList());
272         previousParamDTC = neigbors.getFirst();
273         nextParamDTC = neigbors.get(1);
274 
275     }
276 
277     /**
278      * Performs a linear interpolation between two values The weights are computed
279      * from the time delta between previous date, current date, next date.
280      *
281      * @param date          the current date
282      * @param previousValue the value at previous date
283      * @param nextValue     the value at next date
284      * @return the value interpolated for the current date
285      */
286     private double getLinearInterpolationSOL(final AbsoluteDate date, final double previousValue, final double nextValue) {
287         // perform a linear interpolation
288         return linearInterpolation(date, previousValue, previousParamSOL.getDate(), nextValue, nextParamSOL.getDate());
289     }
290 
291     /**
292      * Performs a linear interpolation between two values The weights are computed
293      * from the time delta between previous date, current date, next date.
294      *
295      * @param date          the current date
296      * @param previousValue the value at previous date
297      * @param nextValue     the value at next date
298      * @return the value interpolated for the current date
299      */
300     private double getLinearInterpolationDTC(final AbsoluteDate date, final double previousValue, final double nextValue) {
301         // perform a linear interpolation
302         return linearInterpolation(date, previousValue, previousParamDTC.getDate(), nextValue, nextParamDTC.getDate());
303     }
304 
305     /**
306      * Linear interpolation.
307      * @param date the current date
308      * @param previousValue the value at previous date
309      * @param previousDate the previous date
310      * @param nextValue the value at next date
311      * @param nextDate the next date
312      * @return the inteprolated value
313      */
314     private double linearInterpolation(final AbsoluteDate date,
315                                        final double previousValue, final AbsoluteDate previousDate,
316                                        final double nextValue, final AbsoluteDate nextDate) {
317         // perform a linear interpolation
318         final double dt = nextDate.durationFrom(previousDate);
319         final double previousWeight = nextDate.durationFrom(date) / dt;
320         final double nextWeight = date.durationFrom(previousDate) / dt;
321 
322         // returns the data interpolated at the date
323         return previousValue * previousWeight + nextValue * nextWeight;
324     }
325 
326     /** {@inheritDoc} */
327     public double getF10(final AbsoluteDate date) {
328         // The date is shifted by 1 day as described in the JB2008 Model with a 1-day lag.
329         final AbsoluteDate workDate = date.shiftedBy(-Constants.JULIAN_DAY);
330         bracketDateSOL(workDate);
331         return getLinearInterpolationSOL(workDate, previousParamSOL.getF10(), nextParamSOL.getF10());
332     }
333 
334     /** {@inheritDoc} */
335     public double getF10B(final AbsoluteDate date) {
336         // The date is shifted by 1 day as described in the JB2008 Model with a 1-day lag.
337         final AbsoluteDate workDate = date.shiftedBy(-Constants.JULIAN_DAY);
338         bracketDateSOL(workDate);
339         return getLinearInterpolationSOL(workDate, previousParamSOL.getF10B(), nextParamSOL.getF10B());
340     }
341 
342     /** {@inheritDoc} */
343     public double getS10(final AbsoluteDate date) {
344         // The date is shifted by 1 day as described in the JB2008 Model with a 1-day lag.
345         final AbsoluteDate workDate = date.shiftedBy(-Constants.JULIAN_DAY);
346         bracketDateSOL(workDate);
347         return getLinearInterpolationSOL(workDate, previousParamSOL.getS10(), nextParamSOL.getS10());
348     }
349 
350     /** {@inheritDoc} */
351     public double getS10B(final AbsoluteDate date) {
352         // The date is shifted by 1 day as described in the JB2008 Model with a 1-day lag.
353         final AbsoluteDate workDate = date.shiftedBy(-Constants.JULIAN_DAY);
354         bracketDateSOL(workDate);
355         return getLinearInterpolationSOL(workDate, previousParamSOL.getS10B(), nextParamSOL.getS10B());
356     }
357 
358     /** {@inheritDoc} */
359     public double getXM10(final AbsoluteDate date) {
360         // The date is shifted by 2 day as described in the JB2008 Model with a 2-day lag.
361         final AbsoluteDate workDate = date.shiftedBy(-2.0 * Constants.JULIAN_DAY);
362         bracketDateSOL(workDate);
363         return getLinearInterpolationSOL(workDate, previousParamSOL.getXM10(), nextParamSOL.getXM10());
364     }
365 
366     /** {@inheritDoc} */
367     public double getXM10B(final AbsoluteDate date) {
368         // The date is shifted by 2 day as described in the JB2008 Model with a 2-day lag.
369         final AbsoluteDate workDate = date.shiftedBy(-2.0 * Constants.JULIAN_DAY);
370         bracketDateSOL(workDate);
371         return getLinearInterpolationSOL(workDate, previousParamSOL.getXM10B(), nextParamSOL.getXM10B());
372     }
373 
374     /** {@inheritDoc} */
375     public double getY10(final AbsoluteDate date) {
376         // The date is shifted by 5 day as described in the JB2008 Model with a 5-day lag.
377         final AbsoluteDate workDate = date.shiftedBy(-5.0 * Constants.JULIAN_DAY);
378         bracketDateSOL(workDate);
379         return getLinearInterpolationSOL(workDate, previousParamSOL.getY10(), nextParamSOL.getY10());
380     }
381 
382     /** {@inheritDoc} */
383     public double getY10B(final AbsoluteDate date) {
384         // The date is shifted by 5 day as described in the JB2008 Model with a 5-day lag.
385         final AbsoluteDate workDate = date.shiftedBy(-5.0 * Constants.JULIAN_DAY);
386         bracketDateSOL(workDate);
387         return getLinearInterpolationSOL(workDate, previousParamSOL.getY10B(), nextParamSOL.getY10B());
388     }
389 
390     /** {@inheritDoc} */
391     public double getDSTDTC(final AbsoluteDate date) {
392         bracketDateDTC(date);
393         return getLinearInterpolationDTC(date, previousParamDTC.getDSTDTC(), nextParamDTC.getDSTDTC());
394     }
395 
396 }