CssiSpaceWeatherDataLoader.java

  1. /* Copyright 2020-2025 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 org.orekit.errors.OrekitException;
  19. import org.orekit.errors.OrekitMessages;
  20. import org.orekit.time.AbsoluteDate;
  21. import org.orekit.time.ChronologicalComparator;
  22. import org.orekit.time.TimeScale;

  23. import java.io.BufferedReader;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.InputStreamReader;
  27. import java.nio.charset.StandardCharsets;
  28. import java.text.ParseException;
  29. import java.util.Arrays;
  30. import java.util.HashSet;
  31. import java.util.NoSuchElementException;
  32. import java.util.Set;
  33. import java.util.SortedSet;
  34. import java.util.TreeSet;

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

  48.     /** Date of last data before the prediction starts. */
  49.     private AbsoluteDate lastObservedDate;

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

  52.     /** Data set. */
  53.     private final SortedSet<LineParameters> set;

  54.     /**
  55.      * Constructor.
  56.      *
  57.      * @param utc UTC time scale
  58.      */
  59.     public CssiSpaceWeatherDataLoader(final TimeScale utc) {
  60.         super(utc);
  61.         this.lastDailyPredictedDate = null;
  62.         this.lastObservedDate       = null;
  63.         this.set                    = new TreeSet<>(new ChronologicalComparator());
  64.     }

  65.     /**
  66.      * Checks if the string contains a floating point number.
  67.      *
  68.      * @param strNum string to check
  69.      *
  70.      * @return true if string contains a valid floating point number, else false
  71.      */
  72.     private static boolean isNumeric(final String strNum) {
  73.         if (strNum == null) {
  74.             return false;
  75.         }
  76.         try {
  77.             Double.parseDouble(strNum);
  78.         }
  79.         catch (NumberFormatException nfe) {
  80.             return false;
  81.         }
  82.         return true;
  83.     }

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

  86.         int                     lineNumber   = 0;
  87.         String                  line         = null;
  88.         final Set<AbsoluteDate> parsedEpochs = new HashSet<>();

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

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

  91.             for (line = reader.readLine(); line != null; line = reader.readLine()) {
  92.                 lineNumber++;

  93.                 line = line.trim();
  94.                 if (!line.isEmpty()) {

  95.                     if (line.equals("BEGIN DAILY_PREDICTED")) {
  96.                         lastObservedDate = set.last().getDate();
  97.                     }

  98.                     if (line.equals("BEGIN MONTHLY_FIT")) {
  99.                         lastDailyPredictedDate = set.last().getDate();
  100.                     }

  101.                     if (line.length() == 130 && isNumeric(line.substring(0, 4))) {
  102.                         // extract the data from the line
  103.                         final int          year  = Integer.parseInt(line.substring(0, 4));
  104.                         final int          month = Integer.parseInt(line.substring(5, 7));
  105.                         final int          day   = Integer.parseInt(line.substring(8, 10));
  106.                         final AbsoluteDate date  = new AbsoluteDate(year, month, day, getUTC());

  107.                         if (parsedEpochs.add(date)) { // Checking if entry doesn't exist yet
  108.                             final double[] threeHourlyKp = new double[8];
  109.                             /* Kp is written as an integer where a unit equals 0.1, the conversion is
  110.                              * Kp_double = 0.1 * double(Kp_integer) */
  111.                             for (int i = 0; i < 8; i++) {
  112.                                 threeHourlyKp[i] = 0.1 * Double.parseDouble(line.substring(19 + 3 * i, 21 + 3 * i));
  113.                             }
  114.                             final double kpSum = 0.1 * Double.parseDouble(line.substring(43, 46));

  115.                             final double[] threeHourlyAp = new double[8];
  116.                             for (int i = 0; i < 8; i++) {
  117.                                 threeHourlyAp[i] = Double.parseDouble(line.substring(47 + 4 * i, 50 + 4 * i));
  118.                             }
  119.                             final double apAvg = Double.parseDouble(line.substring(79, 82));

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

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

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

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

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

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

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

  127.                             set.add(new LineParameters(date, threeHourlyKp, kpSum, threeHourlyAp, apAvg, f107Adj,
  128.                                                        fluxQualifier, ctr81Adj, lst81Adj, f107Obs, ctr81Obs, lst81Obs));
  129.                         }
  130.                     }
  131.                 }
  132.             }
  133.         }
  134.         catch (NumberFormatException nfe) {
  135.             throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber, name, line);
  136.         }

  137.         try {
  138.             setMinDate(set.first().getDate());
  139.             setMaxDate(set.last().getDate());
  140.         }
  141.         catch (NoSuchElementException nse) {
  142.             throw new OrekitException(nse, OrekitMessages.NO_DATA_IN_FILE, name);
  143.         }

  144.     }

  145.     /**
  146.      * Getter for the data set.
  147.      *
  148.      * @return the data set
  149.      */
  150.     @Override
  151.     public SortedSet<LineParameters> getDataSet() {
  152.         return set;
  153.     }

  154.     /**
  155.      * Gets the day (at data start) of the last daily data entry.
  156.      *
  157.      * @return the last daily predicted date
  158.      */
  159.     public AbsoluteDate getLastDailyPredictedDate() {
  160.         return lastDailyPredictedDate;
  161.     }

  162.     /**
  163.      * Gets the day (at data start) of the last observed data entry.
  164.      *
  165.      * @return the last observed date
  166.      */
  167.     public AbsoluteDate getLastObservedDate() {
  168.         return lastObservedDate;
  169.     }

  170.     /** Container class for Solar activity indexes. */
  171.     public static class LineParameters extends AbstractSolarActivityDataLoader.LineParameters {

  172.         /** Serializable UID. */
  173.         private static final long serialVersionUID = 8151260459653484163L;

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

  176.         /**
  177.          * Sum of the 8 Kp indices for the day expressed to the nearest third of a unit.
  178.          */
  179.         private final double kpSum;

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

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

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

  186.         /** Flux Qualifier. */
  187.         private final int fluxQualifier;

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

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

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

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

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

  198.         /**
  199.          * Constructor.
  200.          *
  201.          * @param date entry date
  202.          * @param threeHourlyKp array of 8 three-hourly Kp indices for this entry
  203.          * @param kpSum sum of the 8 Kp indices for the day expressed to the nearest third of a unit
  204.          * @param threeHourlyAp array of 8 three-hourly Ap indices for this entry
  205.          * @param apAvg arithmetic average of the 8 Ap indices for the day
  206.          * @param f107Adj 10.7-cm Solar Radio Flux (F10.7)
  207.          * @param fluxQualifier flux Qualifier
  208.          * @param ctr81Adj centered 81-day arithmetic average of F10.7
  209.          * @param lst81Adj last 81-day arithmetic average of F10.7
  210.          * @param f107Obs observed (unadjusted) value of F10.7
  211.          * @param ctr81Obs centered 81-day arithmetic average of F10.7 (observed)
  212.          * @param lst81Obs last 81-day arithmetic average of F10.7 (observed)
  213.          */
  214.         public LineParameters(final AbsoluteDate date, final double[] threeHourlyKp, final double kpSum,
  215.                               final double[] threeHourlyAp, final double apAvg, final double f107Adj,
  216.                               final int fluxQualifier, final double ctr81Adj, final double lst81Adj,
  217.                               final double f107Obs, final double ctr81Obs, final double lst81Obs) {
  218.             super(date);
  219.             this.threeHourlyKp = threeHourlyKp.clone();
  220.             this.kpSum         = kpSum;
  221.             this.threeHourlyAp = threeHourlyAp.clone();
  222.             this.apAvg         = apAvg;
  223.             this.f107Adj       = f107Adj;
  224.             this.fluxQualifier = fluxQualifier;
  225.             this.ctr81Adj      = ctr81Adj;
  226.             this.lst81Adj      = lst81Adj;
  227.             this.f107Obs       = f107Obs;
  228.             this.ctr81Obs      = ctr81Obs;
  229.             this.lst81Obs      = lst81Obs;
  230.         }

  231.         /** {@inheritDoc} */
  232.         @Override
  233.         public int compareTo(final AbstractSolarActivityDataLoader.LineParameters lineParameters) {
  234.             return getDate().compareTo(lineParameters.getDate());
  235.         }

  236.         /** {@inheritDoc} */
  237.         @Override
  238.         public boolean equals(final Object o) {
  239.             if (this == o) {
  240.                 return true;
  241.             }
  242.             if (o == null || getClass() != o.getClass()) {
  243.                 return false;
  244.             }

  245.             final LineParameters that = (LineParameters) o;

  246.             if (Double.compare(getKpSum(), that.getKpSum()) != 0) {
  247.                 return false;
  248.             }
  249.             if (Double.compare(getApAvg(), that.getApAvg()) != 0) {
  250.                 return false;
  251.             }
  252.             if (Double.compare(getF107Adj(), that.getF107Adj()) != 0) {
  253.                 return false;
  254.             }
  255.             if (getFluxQualifier() != that.getFluxQualifier()) {
  256.                 return false;
  257.             }
  258.             if (Double.compare(getCtr81Adj(), that.getCtr81Adj()) != 0) {
  259.                 return false;
  260.             }
  261.             if (Double.compare(getLst81Adj(), that.getLst81Adj()) != 0) {
  262.                 return false;
  263.             }
  264.             if (Double.compare(getF107Obs(), that.getF107Obs()) != 0) {
  265.                 return false;
  266.             }
  267.             if (Double.compare(getCtr81Obs(), that.getCtr81Obs()) != 0) {
  268.                 return false;
  269.             }
  270.             if (Double.compare(getLst81Obs(), that.getLst81Obs()) != 0) {
  271.                 return false;
  272.             }
  273.             if (!Arrays.equals(getThreeHourlyKp(), that.getThreeHourlyKp())) {
  274.                 return false;
  275.             }
  276.             return Arrays.equals(getThreeHourlyAp(), that.getThreeHourlyAp());
  277.         }

  278.         /** {@inheritDoc} */
  279.         @Override
  280.         public int hashCode() {
  281.             int  result;
  282.             result = Arrays.hashCode(getThreeHourlyKp());
  283.             result = 31 * result + Double.hashCode(getKpSum());
  284.             result = 31 * result + Arrays.hashCode(getThreeHourlyAp());
  285.             result = 31 * result + Double.hashCode(getApAvg());
  286.             result = 31 * result + Double.hashCode(getF107Adj());
  287.             result = 31 * result + getFluxQualifier();
  288.             result = 31 * result + Double.hashCode(getCtr81Adj());
  289.             result = 31 * result + Double.hashCode(getLst81Adj());
  290.             result = 31 * result + Double.hashCode(getF107Obs());
  291.             result = 31 * result + Double.hashCode(getCtr81Obs());
  292.             result = 31 * result + Double.hashCode(getLst81Obs());
  293.             return result;
  294.         }

  295.         /**
  296.          * Gets the three-hourly Kp index at index i from the threeHourlyKp array.
  297.          *
  298.          * @param i index of the Kp index to retrieve [0-7]
  299.          *
  300.          * @return the three hourly Kp index at index i
  301.          */
  302.         public double getThreeHourlyKp(final int i) {
  303.             return threeHourlyKp[i];
  304.         }

  305.         /**
  306.          * Gets the three-hourly Ap index at index i from the threeHourlyAp array.
  307.          *
  308.          * @param i index of the Ap to retrieve [0-7]
  309.          *
  310.          * @return the three hourly Ap index at index i
  311.          */
  312.         public double getThreeHourlyAp(final int i) {
  313.             return threeHourlyAp[i];
  314.         }

  315.         /**
  316.          * Gets the array of the eight three-hourly Kp indices for the current entry.
  317.          *
  318.          * @return the array of eight three-hourly Kp indices
  319.          */
  320.         public double[] getThreeHourlyKp() {
  321.             return threeHourlyKp.clone();
  322.         }

  323.         /**
  324.          * Gets the sum of all eight Kp indices for the current entry.
  325.          *
  326.          * @return the sum of all eight Kp indices
  327.          */
  328.         public double getKpSum() {
  329.             return kpSum;
  330.         }

  331.         /**
  332.          * Gets the array of the eight three-hourly Ap indices for the current entry.
  333.          *
  334.          * @return the array of eight three-hourly Ap indices
  335.          */
  336.         public double[] getThreeHourlyAp() {
  337.             return threeHourlyAp.clone();
  338.         }

  339.         /**
  340.          * Gets the arithmetic average of all eight Ap indices for the current entry.
  341.          *
  342.          * @return the average of all eight Ap indices
  343.          */
  344.         public double getApAvg() {
  345.             return apAvg;
  346.         }

  347.         /**
  348.          * Gets the last 81-day arithmetic average of F10.7 (observed).
  349.          *
  350.          * @return the last 81-day arithmetic average of F10.7 (observed)
  351.          */
  352.         public double getLst81Obs() {
  353.             return lst81Obs;
  354.         }

  355.         /**
  356.          * Gets the centered 81-day arithmetic average of F10.7 (observed).
  357.          *
  358.          * @return the centered 81-day arithmetic average of F10.7 (observed)
  359.          */
  360.         public double getCtr81Obs() {
  361.             return ctr81Obs;
  362.         }

  363.         /**
  364.          * Gets the observed (unadjusted) value of F10.7.
  365.          *
  366.          * @return the observed (unadjusted) value of F10.7
  367.          */
  368.         public double getF107Obs() {
  369.             return f107Obs;
  370.         }

  371.         /**
  372.          * Gets the last 81-day arithmetic average of F10.7 (adjusted).
  373.          *
  374.          * @return the last 81-day arithmetic average of F10.7 (adjusted)
  375.          */
  376.         public double getLst81Adj() {
  377.             return lst81Adj;
  378.         }

  379.         /**
  380.          * Gets the centered 81-day arithmetic average of F10.7 (adjusted).
  381.          *
  382.          * @return the centered 81-day arithmetic average of F10.7 (adjusted)
  383.          */
  384.         public double getCtr81Adj() {
  385.             return ctr81Adj;
  386.         }

  387.         /**
  388.          * Gets the Flux Qualifier.
  389.          *
  390.          * @return the Flux Qualifier
  391.          */
  392.         public int getFluxQualifier() {
  393.             return fluxQualifier;
  394.         }

  395.         /**
  396.          * Gets the 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU.
  397.          *
  398.          * @return the 10.7-cm Solar Radio Flux (F10.7) Adjusted to 1 AU
  399.          */
  400.         public double getF107Adj() {
  401.             return f107Adj;
  402.         }
  403.     }

  404. }