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.BufferedReader;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.Serializable;
25  import java.nio.charset.StandardCharsets;
26  import java.text.ParseException;
27  import java.util.NoSuchElementException;
28  import java.util.SortedSet;
29  import java.util.TreeSet;
30  import java.util.regex.Pattern;
31  import java.util.HashSet;
32  import java.util.Set;
33  
34  import org.orekit.data.DataLoader;
35  import org.orekit.errors.OrekitException;
36  import org.orekit.errors.OrekitMessages;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.ChronologicalComparator;
39  import org.orekit.time.TimeScale;
40  import org.orekit.time.TimeStamped;
41  import org.orekit.utils.Constants;
42  
43  
44  /**
45   * This class reads solar activity data from DTCFILE files for the class
46   * {@link JB2008SpaceEnvironmentData}. The code in this class is based of the
47   * CssiSpaceWeatherDataLoader class.
48   * The DTCFILE file contain pre-computed data from Space Environment using the Dst indices
49   * as well as Ap indices. This computation can be realised using the Fortran code provided
50   * by Space Environment Technologies. See <a href="https://sol.spacenvironment.net/JB2008/indices/DTCFILE.TXT">
51   * this link</a> for more information.
52   * <p>
53   * The data is provided by Space Environment Technologies through their website
54   * <a href="https://sol.spacenvironment.net/JB2008/indices/DTCFILE.TXT">Link</a>.
55   * </p>
56   * The work done for this class is based on the CssiSpaceWeatherDataLoader class
57   * by Clément Jonglez, the JB2008 interface by Pascal Parraud, and corrections for
58   * DataLoader implementation by Bryan Cazabonne and Evan Ward .
59   *
60   * @author Louis Aucouturier
61   * @since 11.2
62   */
63  public class DtcDataLoader implements DataLoader {
64  
65      /** Container class for Solar activity indexes. */
66      public static class LineParameters implements TimeStamped, Serializable {
67  
68          /** Serializable UID. */
69          private static final long serialVersionUID = 8239275953453087629L;
70  
71          /** Entry date. */
72          private final AbsoluteDate date;
73  
74          /** dTc temperature correction data. */
75          private final double dtc;
76  
77          /**
78           * Constructor.
79           * @param date  entry date
80           * @param dtc   Temperature correction for geomagnetic storms
81           */
82          public LineParameters(final AbsoluteDate date, final double dtc) {
83              this.date = date;
84              this.dtc = dtc;
85          }
86  
87          @Override
88          public AbsoluteDate getDate() {
89              return date;
90          }
91  
92          /**
93           * Get the DSTDTC parameter.
94           * <p>
95           * It represents the temperature correction for geomagnetic storms.
96           * </p>
97           * @return dTc  Temperature correction for geomagnetic storms
98           */
99          public double getDSTDTC() {
100             return dtc;
101         }
102 
103     }
104 
105     /** Pattern for regular data. */
106     private static final Pattern PATTERN_SPACE = Pattern.compile("\\s+");
107 
108     /** UTC time scale. */
109     private final TimeScale utc;
110 
111     /** First available date. */
112     private AbsoluteDate firstDate;
113 
114     /** Last available date. */
115     private AbsoluteDate lastDate;
116 
117     /** Data set. */
118     private SortedSet<LineParameters> set;
119 
120     /**
121      * Constructor.
122      * @param utc UTC time scale
123      */
124     public DtcDataLoader(final TimeScale utc) {
125         this.utc = utc;
126         firstDate = null;
127         lastDate = null;
128         set = new TreeSet<>(new ChronologicalComparator());
129     }
130 
131     /**
132      * Getter for the data set.
133      * @return the data set
134      */
135     public SortedSet<LineParameters> getDataSet() {
136         return set;
137     }
138 
139     /**
140      * Gets the available data range minimum date.
141      * @return the minimum date.
142      */
143     public AbsoluteDate getMinDate() {
144         return firstDate;
145     }
146 
147     /**
148      * Gets the available data range maximum date.
149      * @return the maximum date.
150      */
151     public AbsoluteDate getMaxDate() {
152         return lastDate;
153     }
154 
155     /** {@inheritDoc} */
156     public void loadData(final InputStream input, final String name)
157             throws IOException, ParseException, OrekitException {
158 
159         int lineNumber = 0;
160         String line = null;
161         final int nHours = 24;
162         final Set<AbsoluteDate> parsedEpochs = new HashSet<>();
163 
164         try (BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
165 
166             final CommonLineReader reader = new CommonLineReader(br);
167 
168             for (line = reader.readLine(); line != null; line = reader.readLine()) {
169                 lineNumber++;
170                 if (!reader.isEmptyLine()) {
171                     /** extract the data from the line
172                      * The data is extracted from substrings as the spacing between
173                      * columns is constant.
174                      */
175 
176                     /**
177                      * The date is expressed as a year and the day-number in this year.
178                      * Then the dTc is expressed in each column at a different hour, with
179                      * column 4 being the first hour of  the day and column 28 the last hour
180                      * of the day.
181                      * Each column is converted to a single LineParameters object.
182                      */
183 
184                     final String[] splitLine = PATTERN_SPACE.split(line);
185                     final int year = Integer.parseInt(splitLine[1]);
186                     final int dayYear = Integer.parseInt(splitLine[2]);
187                     final AbsoluteDate initDate = new AbsoluteDate(year, 1, 1, utc);
188                     final AbsoluteDate currDate = initDate.shiftedBy((dayYear - 1) * Constants.JULIAN_DAY);
189 
190                     for (int i = 0; i < nHours; i++) {
191 
192                         final AbsoluteDate date = currDate.shiftedBy(3600 * i);
193 
194                         if (parsedEpochs.add(date)) { // Checking if entry doesn't exist yet
195                             final double dtc = Integer.parseInt(splitLine[3 + i]);
196                             set.add(new LineParameters(date, dtc));
197                         }
198                     }
199                 }
200             }
201         } catch (NumberFormatException nfe) {
202             throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber, name, line);
203         }
204 
205         try {
206             firstDate = set.first().getDate();
207             lastDate = set.last().getDate();
208         } catch (NoSuchElementException nse) {
209             throw new OrekitException(nse, OrekitMessages.NO_DATA_IN_FILE, name);
210         }
211     }
212 
213     /** {@inheritDoc} */
214     public boolean stillAcceptsData() {
215         return true;
216     }
217 }