MarshallSolarActivityFutureEstimationLoader.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.models.earth.atmosphere.data;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.models.earth.atmosphere.data.MarshallSolarActivityFutureEstimation.StrengthLevel;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.ChronologicalComparator;
import org.orekit.time.DateComponents;
import org.orekit.time.Month;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeStamped;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* This class reads solar activity data needed by atmospheric models: F107 solar flux, Ap and Kp indexes.
* <p>
* The data are retrieved through the NASA Marshall Solar Activity Future Estimation (MSAFE) as estimates of monthly F10.7
* Mean solar flux and Ap geomagnetic parameter. The data can be retrieved at the NASA <a
* href="https://www.nasa.gov/msfcsolar/archivedforecast"> Marshall Solar Activity website</a>. Here Kp indices are deduced
* from Ap indexes, which in turn are tabulated equivalent of retrieved Ap values.
* </p>
* <p>
* If several MSAFE files are available, some dates may appear in several files (for example August 2007 is in all files from
* the first one published in March 1999 to the February 2008 file). In this case, the data from the most recent file is used
* and the older ones are discarded. The date of the file is assumed to be 6 months after its first entry (which explains why
* the file having August 2007 as its first entry is the February 2008 file). This implies that MSAFE files must <em>not</em>
* be edited to change their time span, otherwise this would break the old entries overriding mechanism.
* </p>
*
* <h2>References</h2>
*
* <ol> <li> Jacchia, L. G. "CIRA 1972, recent atmospheric models, and improvements in
* progress." COSPAR, 21st Plenary Meeting. Vol. 1. 1978. </li> </ol>
*
* @author Bruno Revelin
* @author Luc Maisonobe
* @author Evan Ward
* @author Pascal Parraud
* @author Vincent Cucchietti
*/
public class MarshallSolarActivityFutureEstimationLoader
extends AbstractSolarActivityDataLoader<MarshallSolarActivityFutureEstimationLoader.LineParameters> {
/** Pattern for the data fields of MSAFE data. */
private final Pattern dataPattern;
/** Data set. */
private final SortedSet<TimeStamped> data;
/** Selected strength level of activity. */
private final StrengthLevel strengthLevel;
/**
* Simple constructor. This constructor uses the {@link DataContext#getDefault() default data context}.
*
* @param strengthLevel selected strength level of activity
*/
@DefaultDataContext
public MarshallSolarActivityFutureEstimationLoader(final StrengthLevel strengthLevel) {
this(strengthLevel, DataContext.getDefault().getTimeScales().getUTC());
}
/**
* Constructor that allows specifying the source of the MSAFE auxiliary data files.
*
* @param strengthLevel selected strength level of activity
* @param utc UTC time scale.
*
* @since 10.1
*/
public MarshallSolarActivityFutureEstimationLoader(final StrengthLevel strengthLevel, final TimeScale utc) {
super(utc);
this.data = new TreeSet<>(new ChronologicalComparator());
this.strengthLevel = strengthLevel;
// the data lines have the following form:
// 2010.5003 JUL 83.4 81.3 78.7 6.4 5.9 5.2
// 2010.5837 AUG 87.3 83.4 78.5 7.0 6.1 4.9
// 2010.6670 SEP 90.8 85.5 79.4 7.8 6.2 4.7
// 2010.7503 OCT 94.2 87.6 80.4 9.1 6.4 4.9
final StringBuilder builder = new StringBuilder("^");
// first group: year
builder.append("\\p{Blank}*(\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})");
// month as fraction of year, not stored in a group
builder.append("\\.\\p{Digit}+");
// second group: month as a three upper case letters abbreviation
builder.append("\\p{Blank}+(");
for (final Month month : Month.values()) {
builder.append(month.getUpperCaseAbbreviation());
builder.append('|');
}
builder.delete(builder.length() - 1, builder.length());
builder.append(")");
// third to eighth group: data fields
for (int i = 0; i < 6; ++i) {
builder.append("\\p{Blank}+([-+]?[0-9]+\\.[0-9]+)");
}
// end of line
builder.append("\\p{Blank}*$");
// compile the pattern
this.dataPattern = Pattern.compile(builder.toString());
}
/** {@inheritDoc} */
public void loadData(final InputStream input, final String name)
throws IOException, ParseException, OrekitException {
// select the groups we want to store
final int f107Group;
final int apGroup;
switch (strengthLevel) {
case STRONG:
f107Group = 3;
apGroup = 6;
break;
case AVERAGE:
f107Group = 4;
apGroup = 7;
break;
default:
f107Group = 5;
apGroup = 8;
break;
}
boolean inData = false;
DateComponents fileDate = null;
// try to read the data
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
// Go through each line
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
line = line.trim();
if (!line.isEmpty()) {
final Matcher matcher = dataPattern.matcher(line);
if (matcher.matches()) {
// We are in the data section
inData = true;
// Extract the data from the line
final int year = Integer.parseInt(matcher.group(1));
final Month month = Month.parseMonth(matcher.group(2));
final AbsoluteDate date = new AbsoluteDate(year, month, 1, getUTC());
if (fileDate == null) {
/* The first entry of each file correspond exactly to 6 months before file publication,
so we compute the file date by adding 6 months to its first entry */
if (month.getNumber() > 6) {
fileDate = new DateComponents(year + 1, month.getNumber() - 6, 1);
} else {
fileDate = new DateComponents(year, month.getNumber() + 6, 1);
}
}
// check if there is already an entry for this date or not
boolean addEntry = false;
final Iterator<TimeStamped> iterator = data.tailSet(date).iterator();
if (iterator.hasNext()) {
final LineParameters existingEntry = (LineParameters) iterator.next();
if (existingEntry.getDate().equals(date)) {
// there is an entry for this date
if (existingEntry.getFileDate().compareTo(fileDate) < 0) {
// the entry was read from an earlier file
// we replace it with the new entry as it is fresher
iterator.remove();
addEntry = true;
}
} else {
// it is the first entry we get for this date
addEntry = true;
}
} else {
// it is the first entry we get for this date
addEntry = true;
}
if (addEntry) {
// we must add the new entry
data.add(new LineParameters(fileDate, date,
Double.parseDouble(matcher.group(f107Group)),
Double.parseDouble(matcher.group(apGroup))));
}
} else {
if (inData) {
/* We have already read some data, so we are not in the header anymore
however, we don't recognize this non-empty line, we consider the file is corrupted */
throw new OrekitException(OrekitMessages.NOT_A_MARSHALL_SOLAR_ACTIVITY_FUTURE_ESTIMATION_FILE,
name);
}
}
}
}
}
if (data.isEmpty()) {
throw new OrekitException(OrekitMessages.NOT_A_MARSHALL_SOLAR_ACTIVITY_FUTURE_ESTIMATION_FILE, name);
}
setMinDate(data.first().getDate());
setMaxDate(data.last().getDate());
}
/** @return the data set */
@Override
public SortedSet<LineParameters> getDataSet() {
return data.stream().map(value -> (LineParameters) value).collect(Collectors.toCollection(TreeSet::new));
}
/** Container class for Solar activity indexes. */
public static class LineParameters extends AbstractSolarActivityDataLoader.LineParameters {
/** Serializable UID. */
private static final long serialVersionUID = 6607862001953526475L;
/** File date. */
private final DateComponents fileDate;
/** F10.7 flux at date. */
private final double f107;
/** Ap index at date. */
private final double ap;
/**
* Simple constructor.
*
* @param fileDate file date
* @param date entry date
* @param f107 F10.7 flux at date
* @param ap Ap index at date
*/
private LineParameters(final DateComponents fileDate, final AbsoluteDate date, final double f107, final double ap) {
super(date);
this.fileDate = fileDate;
this.f107 = f107;
this.ap = ap;
}
/** {@inheritDoc} */
@Override
public int compareTo(final AbstractSolarActivityDataLoader.LineParameters lineParameters) {
return getDate().compareTo(lineParameters.getDate());
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object otherInstance) {
if (this == otherInstance) {
return true;
}
if (otherInstance == null || getClass() != otherInstance.getClass()) {
return false;
}
final LineParameters msafeParams = (LineParameters) otherInstance;
if (Double.compare(getF107(), msafeParams.getF107()) != 0) {
return false;
}
if (Double.compare(getAp(), msafeParams.getAp()) != 0) {
return false;
}
return getFileDate().equals(msafeParams.getFileDate());
}
/** {@inheritDoc} */
@Override
public int hashCode() {
int result;
long temp;
result = getFileDate().hashCode();
temp = Double.doubleToLongBits(getF107());
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(getAp());
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
/**
* Get the file date.
*
* @return file date
*/
public DateComponents getFileDate() {
return fileDate;
}
/**
* Get the F10.0 flux.
*
* @return f10.7 flux
*/
public double getF107() {
return f107;
}
/**
* Get the Ap index.
*
* @return Ap index
*/
public double getAp() {
return ap;
}
}
}