CssiSpaceWeatherData.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.annotation.DefaultDataContext;
  19. import org.orekit.data.DataContext;
  20. import org.orekit.data.DataProvidersManager;
  21. import org.orekit.data.DataSource;
  22. import org.orekit.models.earth.atmosphere.data.CssiSpaceWeatherDataLoader.LineParameters;
  23. import org.orekit.time.AbsoluteDate;
  24. import org.orekit.time.TimeScale;
  25. import org.orekit.utils.Constants;
  26. import org.orekit.utils.GenericTimeStampedCache;
  27. import org.orekit.utils.OrekitConfiguration;

  28. /**
  29.  * This class provides three-hourly and daily solar activity data needed by atmospheric models: F107 solar flux, Ap and Kp
  30.  * indexes. The {@link org.orekit.data.DataLoader} implementation and the parsing is handled by the class
  31.  * {@link CssiSpaceWeatherDataLoader}.
  32.  * <p>
  33.  * The data are retrieved through space weather files offered by AGI/CSSI on the AGI
  34.  * <a href="https://ftp.agi.com/pub/DynamicEarthData/SpaceWeather-All-v1.2.txt">FTP</a> as
  35.  * well as on the CelesTrack <a href="http://celestrak.com/SpaceData/">website</a>. These files are updated several times a
  36.  * day by using several sources mentioned in the
  37.  * <a href="http://celestrak.com/SpaceData/SpaceWx-format.php">Celestrak space
  38.  * weather data documentation</a>.
  39.  * </p>
  40.  *
  41.  * @author Clément Jonglez
  42.  * @author Vincent Cucchietti
  43.  * @since 10.2
  44.  */
  45. public class CssiSpaceWeatherData extends AbstractSolarActivityData<LineParameters, CssiSpaceWeatherDataLoader> {

  46.     /** Default regular expression for supported names that works with all officially published files. */
  47.     public static final String DEFAULT_SUPPORTED_NAMES = "^S(?:pace)?W(?:eather)?-(?:All)?.*\\.txt$";

  48.     /** Serializable UID. */
  49.     private static final long serialVersionUID = 4249411710645968978L;

  50.     /** Date of last data before the prediction starts. */
  51.     private final AbsoluteDate lastObservedDate;

  52.     /** Date of last daily prediction before the monthly prediction starts. */
  53.     private final AbsoluteDate lastDailyPredictedDate;

  54.     /**
  55.      * Simple constructor. This constructor uses the default data context.
  56.      * <p>
  57.      * The original file names provided by AGI/CSSI are of the form: SpaceWeather-All-v1.2.txt
  58.      * (<a href="https://ftp.agi.com/pub/DynamicEarthData/SpaceWeather-All-v1.2.txt">AGI's ftp</a>). So a recommended regular
  59.      * expression for the supported names that works with all published files is: {@link #DEFAULT_SUPPORTED_NAMES}.
  60.      * <p>
  61.      * It provides a default configuration for the thread safe cache :
  62.      * <ul>
  63.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  64.      *     <li>Max span : {@code Constants.JULIAN_DAY}</li>
  65.      *     <li>Max span interval : {@code 0}</li>
  66.      * </ul>
  67.      *
  68.      * @param supportedNames regular expression for supported AGI/CSSI space weather files names
  69.      */
  70.     @DefaultDataContext
  71.     public CssiSpaceWeatherData(final String supportedNames) {
  72.         this(supportedNames, DataContext.getDefault().getDataProvidersManager(),
  73.              DataContext.getDefault().getTimeScales().getUTC());
  74.     }

  75.     /**
  76.      * Constructor that allows specifying the source of the CSSI space weather file.
  77.      * <p>
  78.      * It provides a default configuration for the thread safe cache :
  79.      * <ul>
  80.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  81.      *     <li>Max span : {@code Constants.JULIAN_DAY}</li>
  82.      *     <li>Max span interval : {@code 0}</li>
  83.      * </ul>
  84.      *
  85.      * @param supportedNames regular expression for supported AGI/CSSI space weather files names
  86.      * @param dataProvidersManager provides access to auxiliary data files.
  87.      * @param utc UTC time scale.
  88.      */
  89.     public CssiSpaceWeatherData(final String supportedNames, final DataProvidersManager dataProvidersManager,
  90.                                 final TimeScale utc) {
  91.         this(supportedNames, new CssiSpaceWeatherDataLoader(utc), dataProvidersManager, utc);
  92.     }

  93.     /**
  94.      * Constructor that allows specifying the source of the CSSI space weather file.
  95.      * <p>
  96.      * It provides a default configuration for the thread safe cache :
  97.      * <ul>
  98.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  99.      *     <li>Max span : {@code Constants.JULIAN_DAY}</li>
  100.      *     <li>Max span interval : {@code 0}</li>
  101.      * </ul>
  102.      *
  103.      * @param supportedNames regular expression for supported AGI/CSSI space weather files names
  104.      * @param loader data loader
  105.      * @param dataProvidersManager provides access to auxiliary data files.
  106.      * @param utc UTC time scale
  107.      */
  108.     public CssiSpaceWeatherData(final String supportedNames, final CssiSpaceWeatherDataLoader loader,
  109.                                 final DataProvidersManager dataProvidersManager, final TimeScale utc) {
  110.         this(supportedNames, loader, dataProvidersManager, utc, OrekitConfiguration.getCacheSlotsNumber(),
  111.              Constants.JULIAN_DAY, 0);
  112.     }

  113.     /**
  114.      * Constructor that allows specifying the source of the CSSI space weather file and customizable thread safe cache
  115.      * configuration.
  116.      *
  117.      * @param supportedNames regular expression for supported AGI/CSSI space weather files names
  118.      * @param loader data loader
  119.      * @param dataProvidersManager provides access to auxiliary data files.
  120.      * @param utc UTC time scale
  121.      * @param maxSlots maximum number of independent cached time slots in the
  122.      * {@link GenericTimeStampedCache time-stamped cache}
  123.      * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
  124.      * @param maxInterval time interval above which a new slot is created in the
  125.      * {@link GenericTimeStampedCache time-stamped cache}
  126.      */
  127.     public CssiSpaceWeatherData(final String supportedNames, final CssiSpaceWeatherDataLoader loader,
  128.                                 final DataProvidersManager dataProvidersManager, final TimeScale utc, final int maxSlots,
  129.                                 final double maxSpan, final double maxInterval) {
  130.         super(supportedNames, loader, dataProvidersManager, utc, maxSlots, maxSpan, maxInterval, Constants.JULIAN_DAY);

  131.         // Initialise fields
  132.         this.lastObservedDate       = loader.getLastObservedDate();
  133.         this.lastDailyPredictedDate = loader.getLastDailyPredictedDate();
  134.     }

  135.     /**
  136.      * Simple constructor which use the {@link DataContext#getDefault() default data context}.
  137.      * <p>
  138.      * It provides a default configuration for the thread safe cache :
  139.      * <ul>
  140.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  141.      *     <li>Max span : {@code Constants.JULIAN_DAY}</li>
  142.      *     <li>Max span interval : {@code 0}</li>
  143.      * </ul>
  144.      *
  145.      * @param source source for the data
  146.      *
  147.      * @since 12.0
  148.      */
  149.     @DefaultDataContext
  150.     public CssiSpaceWeatherData(final DataSource source) {
  151.         this(source, DataContext.getDefault().getTimeScales().getUTC());
  152.     }

  153.     /**
  154.      * Simple constructor.
  155.      * <p>
  156.      * It provides a default configuration for the thread safe cache :
  157.      * <ul>
  158.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  159.      *     <li>Max span : {@code Constants.JULIAN_DAY}</li>
  160.      *     <li>Max span interval : {@code 0}</li>
  161.      * </ul>
  162.      *
  163.      * @param source source for the data
  164.      * @param utc UTC time scale
  165.      *
  166.      * @since 12.0
  167.      */
  168.     public CssiSpaceWeatherData(final DataSource source, final TimeScale utc) {
  169.         this(source, new CssiSpaceWeatherDataLoader(utc), utc);
  170.     }

  171.     /**
  172.      * Simple constructor.
  173.      * <p>
  174.      * It provides a default configuration for the thread safe cache :
  175.      * <ul>
  176.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  177.      *     <li>Max span : {@code Constants.JULIAN_DAY}</li>
  178.      *     <li>Max span interval : {@code 0}</li>
  179.      * </ul>
  180.      *
  181.      * @param source source for the data
  182.      * @param loader data loader
  183.      * @param utc UTC time scale
  184.      *
  185.      * @since 12.0
  186.      */
  187.     public CssiSpaceWeatherData(final DataSource source, final CssiSpaceWeatherDataLoader loader, final TimeScale utc) {
  188.         this(source, loader, utc, OrekitConfiguration.getCacheSlotsNumber(), Constants.JULIAN_DAY, 0);
  189.     }

  190.     /**
  191.      * Simple constructor with customizable thread safe cache configuration.
  192.      *
  193.      * @param source source for the data
  194.      * @param loader data loader
  195.      * @param utc UTC time scale
  196.      * @param maxSlots maximum number of independent cached time slots in the
  197.      * {@link GenericTimeStampedCache time-stamped cache}
  198.      * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
  199.      * @param maxInterval time interval above which a new slot is created in the
  200.      * {@link GenericTimeStampedCache time-stamped cache}
  201.      *
  202.      * @since 12.0
  203.      */
  204.     public CssiSpaceWeatherData(final DataSource source, final CssiSpaceWeatherDataLoader loader, final TimeScale utc,
  205.                                 final int maxSlots, final double maxSpan, final double maxInterval) {
  206.         super(source, loader, utc, maxSlots, maxSpan, maxInterval, Constants.JULIAN_DAY);
  207.         this.lastObservedDate       = loader.getLastObservedDate();
  208.         this.lastDailyPredictedDate = loader.getLastDailyPredictedDate();
  209.     }

  210.     /** {@inheritDoc} */
  211.     public double getInstantFlux(final AbsoluteDate date) {
  212.         return getLinearInterpolation(date, LineParameters::getF107Obs);
  213.     }

  214.     /** {@inheritDoc} */
  215.     public double getMeanFlux(final AbsoluteDate date) {
  216.         return getAverageFlux(date);
  217.     }

  218.     /** {@inheritDoc} */
  219.     public double getThreeHourlyKP(final AbsoluteDate date) {
  220.         if (date.compareTo(lastObservedDate) <= 0) {
  221.             /* If observation data is available, it contains three-hourly data */
  222.             final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
  223.             final LineParameters     previousParam      = localSolarActivity.getPreviousParam();
  224.             final double             hourOfDay          = date.offsetFrom(previousParam.getDate(), getUTC()) / 3600;
  225.             int                      i_kp               = (int) (hourOfDay / 3);
  226.             if (i_kp >= 8) {
  227.                 /* hourOfDay can take the value 24.0 at midnight due to floating point precision
  228.                  * when bracketing the dates or during a leap second because the hour of day is
  229.                  * computed in UTC view */
  230.                 i_kp = 7;
  231.             }
  232.             return previousParam.getThreeHourlyKp(i_kp);
  233.         } else {
  234.             /* Only predictions are available, there are no three-hourly data */
  235.             return get24HoursKp(date);
  236.         }
  237.     }

  238.     /** {@inheritDoc} */
  239.     public double get24HoursKp(final AbsoluteDate date) {
  240.         // Get the neighboring solar activity
  241.         final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);

  242.         if (date.compareTo(lastDailyPredictedDate) <= 0) {
  243.             // Daily data is available, just taking the daily average
  244.             return localSolarActivity.getPreviousParam().getKpSum() / 8;
  245.         } else {
  246.             // Only monthly data is available, better interpolate between two months
  247.             return getLinearInterpolation(localSolarActivity, lineParam -> lineParam.getKpSum() / 8);
  248.         }
  249.     }

  250.     /** {@inheritDoc} */
  251.     public double getDailyFlux(final AbsoluteDate date) {
  252.         // Getting the value for the previous day
  253.         return getDailyFluxOnDay(date.shiftedBy(-Constants.JULIAN_DAY));
  254.     }

  255.     /** {@inheritDoc} */
  256.     public double getAverageFlux(final AbsoluteDate date) {
  257.         // Get the neighboring solar activity
  258.         final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);

  259.         if (date.compareTo(lastDailyPredictedDate) <= 0) {
  260.             return localSolarActivity.getPreviousParam().getCtr81Obs();
  261.         } else {
  262.             // Only monthly data is available, better interpolate between two months
  263.             return getLinearInterpolation(localSolarActivity, LineParameters::getCtr81Obs);
  264.         }
  265.     }

  266.     /** {@inheritDoc} */
  267.     public double[] getAp(final AbsoluteDate date) {
  268.         final double[] apArray = new double[7];
  269.         apArray[0] = getDailyAp(date);
  270.         apArray[1] = getThreeHourlyAp(date);
  271.         apArray[2] = getThreeHourlyAp(date.shiftedBy(-3.0 * 3600.0));
  272.         apArray[3] = getThreeHourlyAp(date.shiftedBy(-6.0 * 3600.0));
  273.         apArray[4] = getThreeHourlyAp(date.shiftedBy(-9.0 * 3600.0));
  274.         apArray[5] = get24HoursAverageAp(date.shiftedBy(-12.0 * 3600.0));
  275.         apArray[6] = get24HoursAverageAp(date.shiftedBy(-36.0 * 3600.0));
  276.         return apArray;
  277.     }

  278.     /**
  279.      * Gets the daily flux on the current day.
  280.      *
  281.      * @param date the current date
  282.      *
  283.      * @return the daily F10.7 flux (observed)
  284.      */
  285.     private double getDailyFluxOnDay(final AbsoluteDate date) {
  286.         // Get the neighboring solar activity
  287.         final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);

  288.         if (date.compareTo(lastDailyPredictedDate) <= 0) {
  289.             // Getting the value for the previous day
  290.             return localSolarActivity.getPreviousParam().getF107Obs();
  291.         } else {
  292.             // Only monthly data is available, better interpolate between two months
  293.             return getLinearInterpolation(localSolarActivity, LineParameters::getF107Obs);
  294.         }
  295.     }

  296.     /**
  297.      * Gets the value of the three-hourly Ap index for the given date.
  298.      *
  299.      * @param date the current date
  300.      *
  301.      * @return the current three-hourly Ap index
  302.      */
  303.     private double getThreeHourlyAp(final AbsoluteDate date) {
  304.         if (date.compareTo(lastObservedDate.shiftedBy(Constants.JULIAN_DAY)) < 0) {
  305.             // If observation data is available, it contains three-hourly data.
  306.             // Get the neighboring solar activity
  307.             final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);

  308.             final LineParameters previousParam = localSolarActivity.getPreviousParam();
  309.             final double         hourOfDay     = date.offsetFrom(previousParam.getDate(), getUTC()) / 3600;
  310.             int                  i_ap          = (int) (hourOfDay / 3);
  311.             if (i_ap >= 8) {
  312.                 /* hourOfDay can take the value 24.0 at midnight due to floating point precision
  313.                  * when bracketing the dates or during a leap second because the hour of day is
  314.                  * computed in UTC view */
  315.                 i_ap = 7;
  316.             }
  317.             return previousParam.getThreeHourlyAp(i_ap);
  318.         } else {
  319.             /* Only predictions are available, there are no three-hourly data */
  320.             return getDailyAp(date);
  321.         }
  322.     }

  323.     /**
  324.      * Gets the running average of the 8 three-hourly Ap indices prior to current time If three-hourly data is available, the
  325.      * result is different than getDailyAp.
  326.      *
  327.      * @param date the current date
  328.      *
  329.      * @return the 24 hours running average of the Ap index
  330.      */
  331.     private double get24HoursAverageAp(final AbsoluteDate date) {
  332.         if (date.compareTo(lastDailyPredictedDate) <= 0) {
  333.             // Computing running mean
  334.             double apSum = 0.0;
  335.             for (int i = 0; i < 8; i++) {
  336.                 apSum += getThreeHourlyAp(date.shiftedBy(-3.0 * 3600 * i));
  337.             }
  338.             return apSum / 8;
  339.         } else {
  340.             /* Only monthly predictions are available, no need to compute the average from
  341.              * three hourly data */
  342.             return getDailyAp(date);
  343.         }
  344.     }

  345.     /**
  346.      * Get the daily Ap index for the given date.
  347.      *
  348.      * @param date the current date
  349.      *
  350.      * @return the daily Ap index
  351.      */
  352.     private double getDailyAp(final AbsoluteDate date) {
  353.         // Get the neighboring solar activity
  354.         final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);

  355.         if (date.compareTo(lastDailyPredictedDate) <= 0) {
  356.             // Daily data is available, just taking the daily average
  357.             return localSolarActivity.getPreviousParam().getApAvg();
  358.         } else {
  359.             // Only monthly data is available, better interpolate between two months
  360.             return getLinearInterpolation(localSolarActivity, LineParameters::getApAvg);
  361.         }
  362.     }

  363. }