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 }