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