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