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 }