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