1   /* Copyright 2002-2016 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.io.Serializable;
24  import java.util.Calendar;
25  import java.util.GregorianCalendar;
26  import java.util.SortedSet;
27  import java.util.TimeZone;
28  import java.util.TreeSet;
29  
30  import org.apache.commons.math3.exception.util.DummyLocalizable;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.forces.drag.DTM2000InputParameters;
34  import org.orekit.forces.drag.JB2006InputParameters;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.time.ChronologicalComparator;
37  import org.orekit.time.TimeScalesFactory;
38  import org.orekit.time.TimeStamped;
39  import org.orekit.utils.Constants;
40  
41  
42  /** This class reads and provides solar activity data needed by the
43   * two atmospheric models. The data are furnished at the <a
44   * href="http://sol.spacenvironment.net/~JB2006/">
45   * official JB2006 website.</a>
46   *
47   * @author Fabien Maussion
48   */
49  public class SolarInputs97to05 implements JB2006InputParameters, DTM2000InputParameters {
50  
51      /** Serializable UID. */
52      private static final long serialVersionUID = -3687601846334870069L;
53  
54      private static final double third = 1.0/3.0;
55  
56      private static final double[] kpTab = new double[] {
57          0, 0+third, 1-third, 1, 1+third, 2-third, 2, 2+third,
58          3-third, 3, 3+third, 4-third, 4, 4+third, 5-third, 5,
59          5+third, 6-third, 6, 6+third, 7-third, 7, 7+third,
60          8-third, 8, 8+third, 9-third, 9
61      };
62  
63      private static final double[] apTab = new double[] {
64          0, 2, 3, 4, 5, 6, 7, 9, 12, 15, 18, 22, 27, 32,
65          39, 48, 56, 67, 80, 94, 111, 132, 154 , 179, 207, 236, 300, 400
66      };
67  
68      /** All entries. */
69      private SortedSet<TimeStamped> data;
70  
71      private LineParameters currentParam;
72      private AbsoluteDate firstDate;
73      private AbsoluteDate lastDate;
74  
75      /** Simple constructor.
76       * Data file address is set internally, nothing to be done here.
77       *
78       * @exception OrekitException
79       */
80      private SolarInputs97to05() throws OrekitException {
81  
82          data = new TreeSet<TimeStamped>(new ChronologicalComparator());
83          InputStream in = SolarInputs97to05.class.getResourceAsStream("/atmosphere/JB_All_97-05.txt");
84          BufferedReader rFlux = new BufferedReader(new InputStreamReader(in));
85  
86  
87          in = SolarInputs97to05.class.getResourceAsStream("/atmosphere/NOAA_ap_97-05.dat.txt");
88          BufferedReader rAp = new BufferedReader(new InputStreamReader(in));
89  
90          try {
91              read(rFlux, rAp);
92          } catch (IOException e) {
93              throw new RuntimeException(e);
94          }
95      }
96  
97      /** Singleton getter.
98       * @return the unique instance of this class.
99       * @exception OrekitException
100      */
101     public static SolarInputs97to05 getInstance() throws OrekitException {
102         if (LazyHolder.instance == null) {
103             throw LazyHolder.orekitException;
104         }
105         return LazyHolder.instance;
106     }
107 
108     private void read(BufferedReader rFlux, BufferedReader rAp) throws IOException, OrekitException {
109 
110         rFlux.readLine();
111         rFlux.readLine();
112         rFlux.readLine();
113         rFlux.readLine();
114         rAp.readLine();
115         String lineAp;
116         String[] flux;
117         String[] ap;
118         Calendar cal = new GregorianCalendar();
119         cal.setTimeZone(TimeZone.getTimeZone("UTC"));
120         cal.set(0, 0, 0, 0, 0, 0);
121         cal.set(Calendar.MILLISECOND, 0);
122 
123         AbsoluteDate date = null;
124         boolean first = true;
125 
126         for (String lineFlux = rFlux.readLine(); lineFlux != null; lineFlux = rFlux.readLine()) {
127 
128             flux = lineFlux.trim().split("\\s+");
129 
130             lineAp = rAp.readLine();
131             if (lineAp == null) {
132                 throw new OrekitException(new DummyLocalizable("inconsistent JB2006 and geomagnetic indices files"));
133             }
134             ap = lineAp.trim().split("\\s+");
135 
136             int fluxYear = Integer.parseInt(flux[0]);
137             int fluxDay = Integer.parseInt(flux[1]);
138             int apYear  = Integer.parseInt(ap[11]);
139 
140             if (fluxDay != Integer.parseInt(ap[0])) {
141                 throw new OrekitException(new DummyLocalizable("inconsistent JB2006 and geomagnetic indices files"));
142             }
143             if (((fluxYear <  2000) && ((fluxYear - 1900) != apYear)) ||
144                 ((fluxYear >= 2000) && ((fluxYear - 2000) != apYear))) {
145                 throw new OrekitException(new DummyLocalizable("inconsistent JB2006 and geomagnetic indices files"));
146             }
147 
148             cal.set(Calendar.YEAR, fluxYear);
149             cal.set(Calendar.DAY_OF_YEAR, fluxDay);
150 
151             date = new AbsoluteDate(cal.getTime(), TimeScalesFactory.getUTC());
152 
153             if (first) {
154                 first = false;
155                 firstDate = date;
156             }
157 
158             data.add(new LineParameters(date,
159                                         new double[] {
160                     Double.parseDouble(ap[3]),
161                     Double.parseDouble(ap[4]),
162                     Double.parseDouble(ap[5]),
163                     Double.parseDouble(ap[6]),
164                     Double.parseDouble(ap[7]),
165                     Double.parseDouble(ap[8]),
166                     Double.parseDouble(ap[9]),
167                     Double.parseDouble(ap[10]),
168 
169             },
170             Double.parseDouble(flux[3]),
171             Double.parseDouble(flux[4]),
172             Double.parseDouble(flux[5]),
173             Double.parseDouble(flux[6]),
174             Double.parseDouble(flux[7]),
175             Double.parseDouble(flux[8])));
176 
177         }
178         lastDate = date;
179 
180     }
181 
182     private void findClosestLine(AbsoluteDate date) throws OrekitException {
183 
184         if ((date.durationFrom(firstDate) < 0) || (date.durationFrom(lastDate) > Constants.JULIAN_DAY)) {
185             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE, date, firstDate, lastDate);
186         }
187 
188         // don't search if the cached selection is fine
189         if ((currentParam != null) && (date.durationFrom(currentParam.date) >= 0) &&
190                 (date.durationFrom(currentParam.date) < Constants.JULIAN_DAY )) {
191             return;
192         }
193         LineParameters before = new LineParameters(date.shiftedBy(-Constants.JULIAN_DAY), null, 0, 0, 0, 0, 0, 0);
194 
195         // search starting from entries a few steps before the target date
196         SortedSet<TimeStamped> tailSet = data.tailSet(before);
197         if (tailSet != null) {
198             currentParam = (LineParameters) tailSet.first();
199             if (currentParam.date.durationFrom(date) == -Constants.JULIAN_DAY) {
200                 currentParam = (LineParameters) data.tailSet(date).first();
201             }
202         } else {
203             throw new OrekitException(new DummyLocalizable("unable to find data for date {0}"), date);
204         }
205     }
206 
207     /** Container class for Solar activity indexes.  */
208     private static class LineParameters implements TimeStamped, Serializable {
209 
210         /** Serializable UID. */
211         private static final long serialVersionUID = -1127762834954768272L;
212 
213         /** Entries */
214         private  final AbsoluteDate date;
215         private final double[] ap;
216         private final double f10;
217         private final double f10B;
218         private final double s10;
219         private final double s10B;
220         private final double xm10;
221         private final double xm10B;
222 
223 
224         /** Simple constructor. */
225         private LineParameters(AbsoluteDate date, double[]  ap, double f10,
226                                double f10B, double s10, double s10B,
227                                double xm10, double xm10B) {
228             this.date = date;
229             this.ap = ap;
230             this.f10 = f10;
231             this.f10B = f10B;
232             this.s10 = s10;
233             this.s10B = s10B;
234             this.xm10 = xm10;
235             this.xm10B = xm10B;
236 
237         }
238 
239         /** Get the current date */
240         public AbsoluteDate getDate() {
241             return date;
242         }
243 
244     }
245 
246     public double getAp(AbsoluteDate date) {
247         double result = Double.NaN;
248         try {
249             findClosestLine(date);
250             Calendar cal = new GregorianCalendar();
251             cal.setTimeZone(TimeZone.getTimeZone("UTC"));
252             cal.setTime(date.toDate(TimeScalesFactory.getUTC()));
253             int hour = cal.get(Calendar.HOUR_OF_DAY);
254             for(int i= 0; i<8; i++) {
255                 if ((hour >= (i * 3)) && (hour < ((i + 1) * 3))) {
256                     result = currentParam.ap[i];
257                 }
258             }
259         }
260         catch (OrekitException e) {
261             // nothing
262         }
263         return result;
264     }
265 
266     public double getF10(AbsoluteDate date) {
267         double result = Double.NaN;
268         try {
269             findClosestLine(date);
270             result=currentParam.f10;
271         }
272         catch (OrekitException e) {
273             // nothing
274         }
275         return result;
276     }
277 
278     public double getF10B(AbsoluteDate date) {
279         double result = Double.NaN;
280         try {
281             findClosestLine(date);
282             result=currentParam.f10B;
283         }
284         catch (OrekitException e) {
285             // nothing
286         }
287         return result;
288     }
289 
290     public AbsoluteDate getMaxDate() {
291         return lastDate.shiftedBy(Constants.JULIAN_DAY);
292     }
293 
294     public AbsoluteDate getMinDate() {
295         return firstDate;
296     }
297 
298     public double getS10(AbsoluteDate date) {
299         double result = Double.NaN;
300         try {
301             findClosestLine(date);
302             result=currentParam.s10;
303         }
304         catch (OrekitException e) {
305             // nothing
306         }
307         return result;
308     }
309 
310     public double getS10B(AbsoluteDate date) {
311         double result = Double.NaN;
312         try {
313             findClosestLine(date);
314             result=currentParam.s10B;
315         }
316         catch (OrekitException e) {
317             // nothing
318         }
319         return result;
320     }
321 
322     public double getXM10(AbsoluteDate date) {
323         double result = Double.NaN;
324         try {
325             findClosestLine(date);
326             result=currentParam.xm10;
327         }
328         catch (OrekitException e) {
329             // nothing
330         }
331         return result;
332     }
333 
334     public double getXM10B(AbsoluteDate date) {
335         double result = Double.NaN;
336         try {
337             findClosestLine(date);
338             result=currentParam.xm10B;
339         }
340         catch (OrekitException e) {
341             // nothing
342         }
343         return result;
344     }
345 
346     public double get24HoursKp(AbsoluteDate date) {
347         double result = 0;
348         AbsoluteDate myDate = date;
349 
350         for(int i=0; i<8; i++) {
351             result += getThreeHourlyKP(date);
352             myDate = myDate.shiftedBy(3 * 3600);
353         }
354 
355         return result/8;
356     }
357 
358     public double getInstantFlux(AbsoluteDate date) {
359         return getF10(date);
360     }
361 
362     public double getMeanFlux(AbsoluteDate date) {
363         return getF10B(date);
364     }
365 
366     /** The 3-H Kp is derived from the Ap index.
367      * The used method is explained on <a
368      * href="http://www.ngdc.noaa.gov/stp/GEOMAG/kp_ap.shtml">
369      * NOAA website.</a>. Here is the corresponding tab :
370      * <pre>
371      * The scale is O to 9 expressed in thirds of a unit, e.g. 5- is 4 2/3,
372      * 5 is 5 and 5+ is 5 1/3.
373      *
374      * The 3-hourly ap (equivalent range) index is derived from the Kp index as follows:
375      *
376      * Kp = 0o   0+   1-   1o   1+   2-   2o   2+   3-   3o   3+   4-   4o   4+
377      * ap =  0    2    3    4    5    6    7    9   12   15   18   22   27   32
378      * Kp = 5-   5o   5+   6-   6o   6+   7-   7o   7+   8-   8o   8+   9-   9o
379      * ap = 39   48   56   67   80   94  111  132  154  179  207  236  300  400
380      *
381      * </pre>
382      */
383     public double getThreeHourlyKP(AbsoluteDate date) {
384 
385         double ap = getAp(date);
386         int i = 0;
387         for ( i= 0; ap>=apTab[i]; i++) {
388             if(i==apTab.length-1) {
389                 i++;
390                 break;
391             }
392         }
393         return kpTab[i-1];
394     }
395 
396     /** Holder for the singleton.
397      * <p>We use the Initialization on demand holder idiom to store
398      * the singleton, as it is both thread-safe, efficient (no
399      * synchronization) and works with all versions of java.</p>
400      */
401     private static class LazyHolder {
402         private static final SolarInputs97to05 instance;
403         private static final OrekitException orekitException;
404         static {
405             SolarInputs97to05 tmpInstance = null;
406             OrekitException tmpException = null;
407             try {
408                 tmpInstance = new SolarInputs97to05();
409             } catch (OrekitException oe) {
410                 tmpException = oe;
411             }
412             instance        = tmpInstance;
413             orekitException = tmpException;
414         }
415     }
416 
417 }