CssiSpaceWeatherDataLoader.java

  1. /* Copyright 2020 Clément Jonglez
  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.  * Clément Jonglez 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. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.io.Serializable;
  23. import java.nio.charset.StandardCharsets;
  24. import java.text.ParseException;
  25. import java.util.NoSuchElementException;
  26. import java.util.SortedSet;
  27. import java.util.TreeSet;

  28. import org.hipparchus.exception.Localizable;
  29. import org.orekit.data.DataLoader;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.errors.OrekitMessages;
  32. import org.orekit.time.AbsoluteDate;
  33. import org.orekit.time.ChronologicalComparator;
  34. import org.orekit.time.TimeScale;
  35. import org.orekit.time.TimeStamped;

  36. /**
  37.  * This class reads solar activity data from CSSI Space Weather files for the
  38.  * class {@link CssiSpaceWeatherData}.
  39.  * <p>
  40.  * The data are retrieved through space weather files offered by CSSI/AGI. The
  41.  * data can be retrieved on the AGI
  42.  * <a href="ftp://ftp.agi.com/pub/DynamicEarthData/SpaceWeather-All-v1.2.txt">
  43.  * FTP</a>. This file is updated several times a day by using several sources
  44.  * mentioned in the <a href="http://celestrak.com/SpaceData/SpaceWx-format.php">
  45.  * Celestrak space weather data documentation</a>.
  46.  * </p>
  47.  *
  48.  * @author Clément Jonglez
  49.  * @since 10.2
  50.  */
  51. public class CssiSpaceWeatherDataLoader implements DataLoader {

  52.     /** Helper class to parse line data and to raise exceptions if needed.
  53.      * @deprecated as of 11.2, replaced by {@link CommonLineReader} to remove duplicated code.
  54.      */
  55.     @Deprecated
  56.     public static class LineReader {

  57.         /** Name of the file. Used in error messages. */
  58.         private final String name;

  59.         /** The input stream. */
  60.         private final BufferedReader in;

  61.         /** The last line read from the file. */
  62.         private String line;

  63.         /** The number of the last line read from the file. */
  64.         private long lineNo;

  65.         /**
  66.          * Create a line reader.
  67.          *
  68.          * @param name of the data source for error messages.
  69.          * @param in   the input data stream.
  70.          */
  71.         public LineReader(final String name, final BufferedReader in) {
  72.             this.name = name;
  73.             this.in = in;
  74.             this.line = null;
  75.             this.lineNo = 0;
  76.         }

  77.         /**
  78.          * Read a line from the input data stream.
  79.          *
  80.          * @return the next line without the line termination character, or {@code null}
  81.          *         if the end of the stream has been reached.
  82.          * @throws IOException if an I/O error occurs.
  83.          * @see BufferedReader#readLine()
  84.          */
  85.         public String readLine() throws IOException {
  86.             line = in.readLine();
  87.             lineNo++;
  88.             return line;
  89.         }

  90.         /**
  91.          * Read a line from the input data stream, or if the end of the stream has been
  92.          * reached throw an exception.
  93.          *
  94.          * @param message for the exception if the end of the stream is reached.
  95.          * @param args    for the exception if the end of stream is reached.
  96.          * @return the next line without the line termination character, or {@code null}
  97.          *         if the end of the stream has been reached.
  98.          * @throws IOException     if an I/O error occurs.
  99.          * @throws OrekitException if a line could not be read because the end of the
  100.          *                         stream has been reached.
  101.          * @see #readLine()
  102.          */
  103.         public String readLineOrThrow(final Localizable message, final Object... args)
  104.                 throws IOException, OrekitException {

  105.             final String text = readLine();
  106.             if (text == null) {
  107.                 throw new OrekitException(message, args);
  108.             }
  109.             return text;
  110.         }

  111.         /**
  112.          * Annotate an exception with the file context.
  113.          *
  114.          * @param cause the reason why the line could not be parsed.
  115.          * @return an exception with the cause, file name, line number, and line text.
  116.          */
  117.         public OrekitException unableToParseLine(final Throwable cause) {
  118.             return new OrekitException(cause, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNo, name, line);
  119.         }

  120.         /**
  121.          * Get the last line read from the stream.
  122.          *
  123.          * @return May be {@code null} if no lines have been read or the end of stream
  124.          *         has been reached.
  125.          */
  126.         public String getLine() {
  127.             return line;
  128.         }

  129.         /**
  130.          * Get the line number of the last line read from the file.
  131.          *
  132.          * @return the line number.
  133.          */
  134.         public long getLineNumber() {
  135.             return lineNo;
  136.         }

  137.     }

  138.     /** Container class for Solar activity indexes. */
  139.     public static class LineParameters implements TimeStamped, Serializable {

  140.         /** Serializable UID. */
  141.         private static final long serialVersionUID = 8151260459653484163L;

  142.         /** Entry date. */
  143.         private final AbsoluteDate date;

  144.         /** Array of 8 three-hourly Kp indices for this entry. */
  145.         private final double[] threeHourlyKp;

  146.         /**
  147.          * Sum of the 8 Kp indices for the day expressed to the nearest third of a unit.
  148.          */
  149.         private final double kpSum;

  150.         /** Array of 8 three-hourly Ap indices for this entry. */
  151.         private final double[] threeHourlyAp;

  152.         /** Arithmetic average of the 8 Ap indices for the day. */
  153.         private final double apAvg;

  154.         /** 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU. */
  155.         private final double f107Adj;

  156.         /** Flux Qualifier. */
  157.         private final int fluxQualifier;

  158.         /** Centered 81-day arithmetic average of F10.7 (adjusted). */
  159.         private final double ctr81Adj;

  160.         /** Last 81-day arithmetic average of F10.7 (adjusted). */
  161.         private final double lst81Adj;

  162.         /** Observed (unadjusted) value of F10.7. */
  163.         private final double f107Obs;

  164.         /** Centered 81-day arithmetic average of F10.7 (observed). */
  165.         private final double ctr81Obs;

  166.         /** Last 81-day arithmetic average of F10.7 (observed). */
  167.         private final double lst81Obs;

  168.         /**
  169.          * Constructor.
  170.          * @param date entry date
  171.          * @param threeHourlyKp array of 8 three-hourly Kp indices for this entry
  172.          * @param kpSum sum of the 8 Kp indices for the day expressed to the nearest third of a unit
  173.          * @param threeHourlyAp array of 8 three-hourly Ap indices for this entry
  174.          * @param apAvg arithmetic average of the 8 Ap indices for the day
  175.          * @param f107Adj 10.7-cm Solar Radio Flux (F10.7)
  176.          * @param fluxQualifier flux Qualifier
  177.          * @param ctr81Adj centered 81-day arithmetic average of F10.7
  178.          * @param lst81Adj last 81-day arithmetic average of F10.7
  179.          * @param f107Obs observed (unadjusted) value of F10.7
  180.          * @param ctr81Obs centered 81-day arithmetic average of F10.7 (observed)
  181.          * @param lst81Obs last 81-day arithmetic average of F10.7 (observed)
  182.          */
  183.         public LineParameters(final AbsoluteDate date, final double[] threeHourlyKp, final double kpSum,
  184.                 final double[] threeHourlyAp, final double apAvg, final double f107Adj, final int fluxQualifier,
  185.                 final double ctr81Adj, final double lst81Adj, final double f107Obs, final double ctr81Obs,
  186.                 final double lst81Obs) {
  187.             this.date = date;
  188.             this.threeHourlyKp = threeHourlyKp.clone();
  189.             this.kpSum = kpSum;
  190.             this.threeHourlyAp = threeHourlyAp.clone();
  191.             this.apAvg = apAvg;
  192.             this.f107Adj = f107Adj;
  193.             this.fluxQualifier = fluxQualifier;
  194.             this.ctr81Adj = ctr81Adj;
  195.             this.lst81Adj = lst81Adj;
  196.             this.f107Obs = f107Obs;
  197.             this.ctr81Obs = ctr81Obs;
  198.             this.lst81Obs = lst81Obs;
  199.         }

  200.         @Override
  201.         public AbsoluteDate getDate() {
  202.             return date;
  203.         }

  204.         /**
  205.          * Gets the array of the eight three-hourly Kp indices for the current entry.
  206.          * @return the array of eight three-hourly Kp indices
  207.          */
  208.         public double[] getThreeHourlyKp() {
  209.             return threeHourlyKp.clone();
  210.         }

  211.         /**
  212.          * Gets the three-hourly Kp index at index i from the threeHourlyKp array.
  213.          * @param i index of the Kp index to retrieve [0-7]
  214.          * @return the three hourly Kp index at index i
  215.          */
  216.         public double getThreeHourlyKp(final int i) {
  217.             return threeHourlyKp[i];
  218.         }

  219.         /**
  220.          * Gets the sum of all eight Kp indices for the current entry.
  221.          * @return the sum of all eight Kp indices
  222.          */
  223.         public double getKpSum() {
  224.             return kpSum;
  225.         }

  226.         /**
  227.          * Gets the array of the eight three-hourly Ap indices for the current entry.
  228.          * @return the array of eight three-hourly Ap indices
  229.          */
  230.         public double[] getThreeHourlyAp() {
  231.             return threeHourlyAp.clone();
  232.         }

  233.         /**
  234.          * Gets the three-hourly Ap index at index i from the threeHourlyAp array.
  235.          * @param i index of the Ap to retrieve [0-7]
  236.          * @return the three hourly Ap index at index i
  237.          */
  238.         public double getThreeHourlyAp(final int i) {
  239.             return threeHourlyAp[i];
  240.         }

  241.         /**
  242.          * Gets the arithmetic average of all eight Ap indices for the current entry.
  243.          * @return the average of all eight Ap indices
  244.          */
  245.         public double getApAvg() {
  246.             return apAvg;
  247.         }

  248.         /**
  249.          * Gets the last 81-day arithmetic average of F10.7 (observed).
  250.          * @return the last 81-day arithmetic average of F10.7 (observed)
  251.          */
  252.         public double getLst81Obs() {
  253.             return lst81Obs;
  254.         }

  255.         /**
  256.          * Gets the centered 81-day arithmetic average of F10.7 (observed).
  257.          * @return the centered 81-day arithmetic average of F10.7 (observed)
  258.          */
  259.         public double getCtr81Obs() {
  260.             return ctr81Obs;
  261.         }

  262.         /**
  263.          * Gets the observed (unadjusted) value of F10.7.
  264.          * @return the observed (unadjusted) value of F10.7
  265.          */
  266.         public double getF107Obs() {
  267.             return f107Obs;
  268.         }

  269.         /**
  270.          * Gets the last 81-day arithmetic average of F10.7 (adjusted).
  271.          * @return the last 81-day arithmetic average of F10.7 (adjusted)
  272.          */
  273.         public double getLst81Adj() {
  274.             return lst81Adj;
  275.         }

  276.         /**
  277.          * Gets the centered 81-day arithmetic average of F10.7 (adjusted).
  278.          * @return the centered 81-day arithmetic average of F10.7 (adjusted)
  279.          */
  280.         public double getCtr81Adj() {
  281.             return ctr81Adj;
  282.         }

  283.         /**
  284.          * Gets the Flux Qualifier.
  285.          * @return the Flux Qualifier
  286.          */
  287.         public int getFluxQualifier() {
  288.             return fluxQualifier;
  289.         }

  290.         /**
  291.          * Gets the 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU.
  292.          * @return the 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU
  293.          */
  294.         public double getF107Adj() {
  295.             return f107Adj;
  296.         }
  297.     }

  298.     /** UTC time scale. */
  299.     private final TimeScale utc;

  300.     /** First available date. */
  301.     private AbsoluteDate firstDate;

  302.     /** Date of last data before the prediction starts. */
  303.     private AbsoluteDate lastObservedDate;

  304.     /** Date of last daily prediction before the monthly prediction starts. */
  305.     private AbsoluteDate lastDailyPredictedDate;

  306.     /** Last available date. */
  307.     private AbsoluteDate lastDate;

  308.     /** Data set. */
  309.     private SortedSet<TimeStamped> set;

  310.     /**
  311.      * Constructor.
  312.      * @param utc UTC time scale
  313.      */
  314.     public CssiSpaceWeatherDataLoader(final TimeScale utc) {
  315.         this.utc = utc;
  316.         firstDate = null;
  317.         lastDailyPredictedDate = null;
  318.         lastDate = null;
  319.         lastObservedDate = null;
  320.         set = new TreeSet<>(new ChronologicalComparator());
  321.     }

  322.     /**
  323.      * Getter for the data set.
  324.      * @return the data set
  325.      */
  326.     public SortedSet<TimeStamped> getDataSet() {
  327.         return set;
  328.     }

  329.     /**
  330.      * Gets the available data range minimum date.
  331.      * @return the minimum date.
  332.      */
  333.     public AbsoluteDate getMinDate() {
  334.         return firstDate;
  335.     }

  336.     /**
  337.      * Gets the available data range maximum date.
  338.      * @return the maximum date.
  339.      */
  340.     public AbsoluteDate getMaxDate() {
  341.         return lastDate;
  342.     }

  343.     /**
  344.      * Gets the day (at data start) of the last daily data entry.
  345.      * @return the last daily predicted date
  346.      */
  347.     public AbsoluteDate getLastDailyPredictedDate() {
  348.         return lastDailyPredictedDate;
  349.     }

  350.     /**
  351.      * Gets the day (at data start) of the last observed data entry.
  352.      * @return the last observed date
  353.      */
  354.     public AbsoluteDate getLastObservedDate() {
  355.         return lastObservedDate;
  356.     }

  357.     /**
  358.      * Checks if the string contains a floating point number.
  359.      *
  360.      * @param strNum string to check
  361.      * @return true if string contains a valid floating point number, else false
  362.      */
  363.     private static boolean isNumeric(final String strNum) {
  364.         if (strNum == null) {
  365.             return false;
  366.         }
  367.         try {
  368.             Double.parseDouble(strNum);
  369.         } catch (NumberFormatException nfe) {
  370.             return false;
  371.         }
  372.         return true;
  373.     }

  374.     /** {@inheritDoc} */
  375.     public void loadData(final InputStream input, final String name)
  376.             throws IOException, ParseException, OrekitException {

  377.         // read the data
  378.         int lineNumber = 0;
  379.         String line = null;

  380.         try (BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

  381.             final CommonLineReader reader = new CommonLineReader(br);

  382.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  383.                 lineNumber++;

  384.                 line = line.trim();
  385.                 if (line.length() > 0) {

  386.                     if (line.equals("BEGIN DAILY_PREDICTED")) {
  387.                         lastObservedDate = set.last().getDate();
  388.                     }

  389.                     if (line.equals("BEGIN MONTHLY_FIT")) {
  390.                         lastDailyPredictedDate = set.last().getDate();
  391.                     }

  392.                     if (line.length() == 130 && isNumeric(line.substring(0, 4))) {
  393.                         // extract the data from the line
  394.                         final int year = Integer.parseInt(line.substring(0, 4));
  395.                         final int month = Integer.parseInt(line.substring(5, 7));
  396.                         final int day = Integer.parseInt(line.substring(8, 10));
  397.                         final AbsoluteDate date = new AbsoluteDate(year, month, day, this.utc);

  398.                         if (!set.contains(date)) { // Checking if entry doesn't exist yet
  399.                             final double[] threeHourlyKp = new double[8];
  400.                             /**
  401.                              * Kp is written as an integer where a unit equals 0.1, the conversion is
  402.                              * Kp_double = 0.1 * double(Kp_integer)
  403.                              */
  404.                             for (int i = 0; i < 8; i++) {
  405.                                 threeHourlyKp[i] = 0.1 * Double.parseDouble(line.substring(19 + 3 * i, 21 + 3 * i));
  406.                             }
  407.                             final double kpSum = 0.1 * Double.parseDouble(line.substring(43, 46));

  408.                             final double[] threeHourlyAp = new double[8];
  409.                             for (int i = 0; i < 8; i++) {
  410.                                 threeHourlyAp[i] = Double.parseDouble(line.substring(47 + 4 * i, 50 + 4 * i));
  411.                             }
  412.                             final double apAvg = Double.parseDouble(line.substring(79, 82));

  413.                             final double f107Adj = Double.parseDouble(line.substring(93, 98));

  414.                             final int fluxQualifier = Integer.parseInt(line.substring(99, 100));

  415.                             final double ctr81Adj = Double.parseDouble(line.substring(101, 106));

  416.                             final double lst81Adj = Double.parseDouble(line.substring(107, 112));

  417.                             final double f107Obs = Double.parseDouble(line.substring(113, 118));

  418.                             final double ctr81Obs = Double.parseDouble(line.substring(119, 124));

  419.                             final double lst81Obs = Double.parseDouble(line.substring(125, 130));

  420.                             set.add(new LineParameters(date, threeHourlyKp, kpSum, threeHourlyAp, apAvg, f107Adj,
  421.                                     fluxQualifier, ctr81Adj, lst81Adj, f107Obs, ctr81Obs, lst81Obs));
  422.                         }
  423.                     }
  424.                 }
  425.             }
  426.         } catch (NumberFormatException nfe) {
  427.             throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber, name, line);
  428.         }

  429.         try {
  430.             firstDate = set.first().getDate();
  431.             lastDate = set.last().getDate();
  432.         } catch (NoSuchElementException nse) {
  433.             throw new OrekitException(nse, OrekitMessages.NO_DATA_IN_FILE, name);
  434.         }

  435.     }

  436.     /** {@inheritDoc} */
  437.     public boolean stillAcceptsData() {
  438.         return true;
  439.     }
  440. }