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  package org.orekit.models.earth.atmosphere.data;
18  
19  import org.hipparchus.exception.DummyLocalizable;
20  import org.orekit.data.DataProvidersManager;
21  import org.orekit.data.DataSource;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.models.earth.atmosphere.DTM2000InputParameters;
25  import org.orekit.models.earth.atmosphere.NRLMSISE00InputParameters;
26  import org.orekit.time.AbsoluteDate;
27  import org.orekit.time.TimeScale;
28  import org.orekit.time.TimeStamped;
29  import org.orekit.utils.GenericTimeStampedCache;
30  import org.orekit.utils.ImmutableTimeStampedCache;
31  import org.orekit.utils.TimeStampedGenerator;
32  
33  import java.io.BufferedInputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.Serial;
37  import java.text.ParseException;
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.List;
41  import java.util.function.Function;
42  import java.util.stream.Collectors;
43  
44  /**
45   * Abstract class for solar activity data.
46   *
47   * @param <L> type of the line parameters
48   * @param <D> type of the solar activity data loader
49   *
50   * @author Vincent Cucchietti
51   * @since 12.0
52   */
53  public abstract class AbstractSolarActivityData<L extends AbstractSolarActivityDataLoader.LineParameters, D extends AbstractSolarActivityDataLoader<L>>
54          implements DTM2000InputParameters, NRLMSISE00InputParameters {
55  
56      /** Size of the list. */
57      protected static final int N_NEIGHBORS = 2;
58  
59      /** Serializable UID. */
60      @Serial
61      private static final long serialVersionUID = 8804818166227680449L;
62  
63      /** Weather data thread safe cache. */
64      private final transient GenericTimeStampedCache<L> cache;
65  
66      /** Supported names. */
67      private final String supportedNames;
68  
69      /** UTC time scale. */
70      private final transient TimeScale utc;
71  
72      /** First available date. */
73      private final AbsoluteDate firstDate;
74  
75      /** Last available date. */
76      private final AbsoluteDate lastDate;
77  
78      /**
79       * Constructor.
80       * @param supportedNames regular expression for supported AGI/CSSI space weather files names
81       * @param loader data loader
82       * @param dataProvidersManager provides access to auxiliary data files.
83       * @param utc UTC time scale
84       * @param maxSlots maximum number of independent cached time slots in the
85       * {@link GenericTimeStampedCache time-stamped cache}
86       * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
87       * @param maxInterval time interval above which a new slot is created in the
88       * {@link GenericTimeStampedCache time-stamped cache}
89       * @param minimumStep overriding minimum step designed for non-homogeneous tabulated values. To be used for example when
90       * caching monthly tabulated values. May be null.
91       */
92      protected AbstractSolarActivityData(final String supportedNames, final D loader,
93                                          final DataProvidersManager dataProvidersManager, final TimeScale utc,
94                                          final int maxSlots, final double maxSpan, final double maxInterval,
95                                          final double minimumStep) {
96          // Load data
97          dataProvidersManager.feed(supportedNames, loader);
98  
99          // Create thread safe cache
100         this.cache = new GenericTimeStampedCache<>(N_NEIGHBORS, maxSlots, maxSpan, maxInterval,
101                                                    new SolarActivityGenerator(loader.getDataSet()), minimumStep);
102 
103         // Initialise fields
104         this.supportedNames = supportedNames;
105         this.utc            = utc;
106         this.firstDate      = loader.getMinDate();
107         this.lastDate       = loader.getMaxDate();
108     }
109 
110     /**
111      * Simple constructor.
112      *
113      * @param source source for the data
114      * @param loader data loader
115      * @param utc UTC time scale
116      * @param maxSlots maximum number of independent cached time slots in the
117      * {@link GenericTimeStampedCache time-stamped cache}
118      * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
119      * @param maxInterval time interval above which a new slot is created in the
120      * {@link GenericTimeStampedCache time-stamped cache}
121      * @param minimumStep overriding minimum step designed for non-homogeneous tabulated values. To be used for example when
122      * caching monthly tabulated values. May be null.
123      *
124      * @since 12.0
125      */
126     protected AbstractSolarActivityData(final DataSource source, final D loader, final TimeScale utc, final int maxSlots,
127                                         final double maxSpan, final double maxInterval, final double minimumStep) {
128         try {
129             // Load file
130             try (InputStream is = source.getOpener().openStreamOnce();
131                     BufferedInputStream bis = new BufferedInputStream(is)) {
132                 loader.loadData(bis, source.getName());
133             }
134 
135             // Create thread safe cache
136             this.cache = new GenericTimeStampedCache<>(N_NEIGHBORS, maxSlots, maxSpan, maxInterval,
137                                                        new SolarActivityGenerator(loader.getDataSet()), minimumStep);
138 
139             // Initialise fields
140             this.supportedNames = source.getName();
141             this.utc            = utc;
142             this.firstDate      = loader.getMinDate();
143             this.lastDate       = loader.getMaxDate();
144         }
145         catch (IOException | ParseException ioe) {
146             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
147         }
148     }
149 
150     /**
151      * Performs a linear interpolation between two values The weights are computed from the time delta between previous date,
152      * current date, next date.
153      *
154      * @param date current date
155      * @param solarActivityToDoubleMapper mapping function taking solar activity as input and returning a double
156      *
157      * @return the value interpolated for the current date
158      */
159     protected double getLinearInterpolation(final AbsoluteDate date, final Function<L, Double> solarActivityToDoubleMapper) {
160         // Create solar activity around current date
161         final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
162 
163         // Extract values
164         return getLinearInterpolation(localSolarActivity, solarActivityToDoubleMapper);
165     }
166 
167     /**
168      * Performs a linear interpolation between two values The weights are computed from the time delta between previous date,
169      * current date, next date.
170      *
171      * @param localSolarActivity solar activity around current date
172      * @param solarActivityToDoubleMapper mapping function taking solar activity as input and returning a double
173      *
174      * @return the value interpolated for the current date
175      */
176     protected double getLinearInterpolation(final LocalSolarActivity localSolarActivity,
177                                             final Function<L, Double> solarActivityToDoubleMapper) {
178         // Extract values
179         final L      previousParameters = localSolarActivity.getPreviousParam();
180         final double previousValue      = solarActivityToDoubleMapper.apply(previousParameters);
181 
182         final L      nextParameters = localSolarActivity.getNextParam();
183         final double nextValue      = solarActivityToDoubleMapper.apply(nextParameters);
184 
185         // Perform a linear interpolation
186         final AbsoluteDate previousDate   = localSolarActivity.getPreviousParam().getDate();
187         final AbsoluteDate currentDate    = localSolarActivity.getNextParam().getDate();
188         final double       dt             = currentDate.durationFrom(previousDate);
189         final AbsoluteDate date           = localSolarActivity.getDate();
190         final double       previousWeight = currentDate.durationFrom(date) / dt;
191         final double       nextWeight     = date.durationFrom(previousDate) / dt;
192 
193         // Returns the data interpolated at the date
194         return previousValue * previousWeight + nextValue * nextWeight;
195     }
196 
197     /**
198      * Get underlying cache.
199      *
200      * @return cache
201      */
202     public GenericTimeStampedCache<L> getCache() {
203         return cache;
204     }
205 
206     /**
207      * Get the supported names regular expression.
208      *
209      * @return the supported names.
210      */
211     public String getSupportedNames() {
212         return supportedNames;
213     }
214 
215     /**
216      * Get the UTC timescale.
217      *
218      * @return UTC timescale
219      */
220     public TimeScale getUTC() {
221         return utc;
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public AbsoluteDate getMinDate() {
227         return firstDate;
228     }
229 
230     /** {@inheritDoc} */
231     @Override
232     public AbsoluteDate getMaxDate() {
233         return lastDate;
234     }
235 
236     /** Container for weather parameters around current date. Allows for thread safe use. */
237     protected class LocalSolarActivity implements TimeStamped {
238 
239         /** Date. */
240         private final AbsoluteDate currentDate;
241 
242         /** Previous parameters. */
243         private final L previousParam;
244 
245         /** Next parameters. */
246         private final L nextParam;
247 
248         /**
249          * Constructor.
250          *
251          * @param date current date
252          */
253         public LocalSolarActivity(final AbsoluteDate date) {
254             // Asked date is before earliest available data
255             if (date.durationFrom(firstDate) < 0) {
256                 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE, date, firstDate, lastDate,
257                                           firstDate.durationFrom(date));
258             }
259             // Asked date is after latest available data
260             if (date.durationFrom(lastDate) > 0) {
261                 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_AFTER, date, firstDate, lastDate,
262                                           date.durationFrom(lastDate));
263             }
264 
265             final List<L> neighbours = cache.getNeighbors(date).collect(Collectors.toList());
266 
267             this.currentDate   = date;
268             this.previousParam = neighbours.getFirst();
269             this.nextParam     = neighbours.get(1);
270         }
271 
272         /** @return current date */
273         public AbsoluteDate getDate() {
274             return currentDate;
275         }
276 
277         /**
278          * Get previous parameters.
279          * @return previous parameters
280          */
281         public L getPreviousParam() {
282             return previousParam;
283         }
284 
285         /**
286          * Get next parameters.
287          * @return next parameters
288          */
289         public L getNextParam() {
290             return nextParam;
291         }
292     }
293 
294     /** Generator used in the weather data cache. */
295     protected class SolarActivityGenerator implements TimeStampedGenerator<L> {
296 
297         /**
298          * Default time step to shift the date.
299          * <p>
300          * It is used so that, in the case where the earliest date is exactly at noon, we do not get the following interval
301          * [previous day; current day] but rather the expected interval [current day; next day]
302          */
303         private static final double STEP = 1;
304 
305         /** Data set. */
306         private final ImmutableTimeStampedCache<L> data;
307 
308         /**
309          * Constructor.
310          *
311          * @param dataSet weather data
312          */
313         protected SolarActivityGenerator(final Collection<L> dataSet) {
314             this.data = new ImmutableTimeStampedCache<>(N_NEIGHBORS, dataSet);
315         }
316 
317         /** {@inheritDoc} */
318         @Override
319         public List<L> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {
320             // No prior data in the cache
321             if (existingDate == null) {
322                 return data.getNeighbors(date).collect(Collectors.toList());
323             }
324             // Prior data in the cache, fill with data between date and existing date
325             if (date.isBefore(existingDate)) {
326                 return generateDataFromEarliestToLatestDates(date, existingDate);
327             }
328             return generateDataFromEarliestToLatestDates(existingDate, date);
329         }
330 
331         /**
332          * Generate a list of parameters between earliest and latest dates.
333          *
334          * @param earliest earliest date
335          * @param latest latest date
336          *
337          * @return list of parameters between earliest and latest dates
338          */
339         public List<L> generateDataFromEarliestToLatestDates(final AbsoluteDate earliest, final AbsoluteDate latest) {
340             /* Gives first two parameters bracketing the earliest date
341              * A date shifted by step is used so that, in the case where the earliest date is exactly noon, we do not get the
342              * following interval [previous day; current day] but rather the expected interval [current day; next day] */
343             List<L> neighbours = data.getNeighbors(earliest.shiftedBy(STEP)).collect(Collectors.toList());
344 
345             // Get next parameter until it brackets the latest date
346             AbsoluteDate  latestNeighbourDate = neighbours.get(1).getDate();
347             final List<L> params              = new ArrayList<>(neighbours);
348             while (latestNeighbourDate.isBefore(latest)) {
349                 neighbours = data.getNeighbors(latestNeighbourDate.shiftedBy(STEP)).collect(Collectors.toList());
350                 params.add(neighbours.get(1));
351                 latestNeighbourDate = neighbours.get(1).getDate();
352             }
353             return params;
354         }
355     }
356 }