GlobalIonosphereMapModel.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.ionosphere;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
import org.hipparchus.exception.DummyLocalizable;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.bodies.BodyShape;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.data.DataContext;
import org.orekit.data.DataLoader;
import org.orekit.data.DataProvidersManager;
import org.orekit.data.DataSource;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.frames.TopocentricFrame;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.TimeSpanMap;
/**
* Global Ionosphere Map (GIM) model.
* The ionospheric delay is computed according to the formulas:
* <pre>
* 40.3
* δ = -------- * STEC with, STEC = VTEC * F(elevation)
* f²
* </pre>
* With:
* <ul>
* <li>f: The frequency of the signal in Hz.</li>
* <li>STEC: The Slant Total Electron Content in TECUnits.</li>
* <li>VTEC: The Vertical Total Electron Content in TECUnits.</li>
* <li>F(elevation): A mapping function which depends on satellite elevation.</li>
* </ul>
* The VTEC is read from a IONEX file. A stream contains, for a given day, the values of the TEC for each hour of the day.
* Values are given on a global 2.5° x 5.0° (latitude x longitude) grid.
* <p>
* A bilinear interpolation is performed the case of the user initialize the latitude and the
* longitude with values that are not contained in the stream.
* </p><p>
* A temporal interpolation is also performed to compute the VTEC at the desired date.
* </p><p>
* IONEX files are obtained from
* <a href="ftp://cddis.nasa.gov/gnss/products/ionex/"> The Crustal Dynamics Data Information System</a>.
* </p><p>
* The files have to be extracted to UTF-8 text files before being read by this loader.
* </p><p>
* Example of file:
* </p>
* <pre>
* 1.0 IONOSPHERE MAPS GPS IONEX VERSION / TYPE
* BIMINX V5.3 AIUB 16-JAN-19 07:26 PGM / RUN BY / DATE
* BROADCAST IONOSPHERE MODEL FOR DAY 015, 2019 COMMENT
* 2019 1 15 0 0 0 EPOCH OF FIRST MAP
* 2019 1 16 0 0 0 EPOCH OF LAST MAP
* 3600 INTERVAL
* 25 # OF MAPS IN FILE
* NONE MAPPING FUNCTION
* 0.0 ELEVATION CUTOFF
* OBSERVABLES USED
* 6371.0 BASE RADIUS
* 2 MAP DIMENSION
* 350.0 350.0 0.0 HGT1 / HGT2 / DHGT
* 87.5 -87.5 -2.5 LAT1 / LAT2 / DLAT
* -180.0 180.0 5.0 LON1 / LON2 / DLON
* -1 EXPONENT
* TEC/RMS values in 0.1 TECU; 9999, if no value available COMMENT
* END OF HEADER
* 1 START OF TEC MAP
* 2019 1 15 0 0 0 EPOCH OF CURRENT MAP
* 87.5-180.0 180.0 5.0 350.0 LAT/LON1/LON2/DLON/H
* 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92
* 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92
* 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92
* 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92 92
* 92 92 92 92 92 92 92 92 92
* ...
* </pre>
*
* @see "Schaer, S., W. Gurtner, and J. Feltens, 1998, IONEX: The IONosphere Map EXchange
* Format Version 1, February 25, 1998, Proceedings of the IGS AC Workshop
* Darmstadt, Germany, February 9–11, 1998"
*
* @author Bryan Cazabonne
*
*/
public class GlobalIonosphereMapModel implements IonosphericModel {
/** Map of interpolable TEC. */
private final TimeSpanMap<TECMapPair> tecMap;
/** UTC time scale. */
private final TimeScale utc;
/** Loaded IONEX files.
* @since 12.0
*/
private String names;
/**
* Constructor with supported names given by user. This constructor uses the {@link
* DataContext#getDefault() default data context}.
*
* @param supportedNames regular expression that matches the names of the IONEX files
* to be loaded. See {@link DataProvidersManager#feed(String,
* DataLoader)}.
* @see #GlobalIonosphereMapModel(String, DataProvidersManager, TimeScale)
*/
@DefaultDataContext
public GlobalIonosphereMapModel(final String supportedNames) {
this(supportedNames,
DataContext.getDefault().getDataProvidersManager(),
DataContext.getDefault().getTimeScales().getUTC());
}
/**
* Constructor that uses user defined supported names and data context.
*
* @param supportedNames regular expression that matches the names of the IONEX
* files to be loaded. See {@link DataProvidersManager#feed(String,
* DataLoader)}.
* @param dataProvidersManager provides access to auxiliary data files.
* @param utc UTC time scale.
* @since 10.1
*/
public GlobalIonosphereMapModel(final String supportedNames,
final DataProvidersManager dataProvidersManager,
final TimeScale utc) {
this.utc = utc;
this.tecMap = new TimeSpanMap<>(null);
this.names = "";
// Read files
dataProvidersManager.feed(supportedNames, new Parser());
}
/**
* Constructor that uses user defined data sources.
*
* @param utc UTC time scale.
* @param ionex sources for the IONEX files
* @since 12.0
*/
public GlobalIonosphereMapModel(final TimeScale utc,
final DataSource... ionex) {
try {
this.utc = utc;
this.tecMap = new TimeSpanMap<>(null);
this.names = "";
final Parser parser = new Parser();
for (final DataSource source : ionex) {
try (InputStream is = source.getOpener().openStreamOnce();
BufferedInputStream bis = new BufferedInputStream(is)) {
parser.loadData(bis, source.getName());
}
}
} catch (IOException ioe) {
throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
}
}
/**
* Calculates the ionospheric path delay for the signal path from a ground
* station to a satellite traversing ionosphere single layer at some pierce point.
* <p>
* The path delay can be computed for any elevation angle.
* </p>
* @param date current date
* @param piercePoint ionospheric pierce point
* @param elevation elevation of the satellite from receiver point in radians
* @param frequency frequency of the signal in Hz
* @return the path delay due to the ionosphere in m
*/
private double pathDelayAtIPP(final AbsoluteDate date, final GeodeticPoint piercePoint,
final double elevation, final double frequency) {
// TEC in TECUnits
final TECMapPair pair = getPairAtDate(date);
final double tec = pair.getTEC(date, piercePoint);
// Square of the frequency
final double freq2 = frequency * frequency;
// "Slant" Total Electron Content
final double stec;
// Check if a mapping factor is needed
if (pair.mapping) {
stec = tec;
} else {
// Mapping factor
final double fz = mappingFunction(elevation, pair.r0, pair.h);
stec = tec * fz;
}
// Delay computation
final double alpha = 40.3e16 / freq2;
return alpha * stec;
}
@Override
public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
final double frequency, final double[] parameters) {
// Satellite position in body frame
final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
final Vector3D satPoint = state.getPosition(bodyFrame);
// Elevation in radians
final double elevation = bodyFrame.
getStaticTransformTo(baseFrame, state.getDate()).
transformPosition(satPoint).
getDelta();
// Only consider measures above the horizon
if (elevation > 0.0) {
// Normalized Line Of Sight in body frame
final Vector3D los = satPoint.subtract(baseFrame.getCartesianPoint()).normalize();
// Ionosphere Pierce Point
final GeodeticPoint ipp = piercePoint(state.getDate(), baseFrame.getCartesianPoint(), los, baseFrame.getParentShape());
if (ipp != null) {
// Delay
return pathDelayAtIPP(state.getDate(), ipp, elevation, frequency);
}
}
return 0.0;
}
/**
* Calculates the ionospheric path delay for the signal path from a ground
* station to a satellite traversing ionosphere single layer at some pierce point.
* <p>
* The path delay can be computed for any elevation angle.
* </p>
* @param <T> type of the elements
* @param date current date
* @param piercePoint ionospheric pierce point
* @param elevation elevation of the satellite from receiver point in radians
* @param frequency frequency of the signal in Hz
* @return the path delay due to the ionosphere in m
*/
private <T extends CalculusFieldElement<T>> T pathDelayAtIPP(final FieldAbsoluteDate<T> date,
final GeodeticPoint piercePoint,
final T elevation, final double frequency) {
// TEC in TECUnits
final TECMapPair pair = getPairAtDate(date.toAbsoluteDate());
final T tec = pair.getTEC(date, piercePoint);
// Square of the frequency
final double freq2 = frequency * frequency;
// "Slant" Total Electron Content
final T stec;
// Check if a mapping factor is needed
if (pair.mapping) {
stec = tec;
} else {
// Mapping factor
final T fz = mappingFunction(elevation, pair.r0, pair.h);
stec = tec.multiply(fz);
}
// Delay computation
final double alpha = 40.3e16 / freq2;
return stec.multiply(alpha);
}
@Override
public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state, final TopocentricFrame baseFrame,
final double frequency, final T[] parameters) {
// Satellite position in body frame
final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
final FieldVector3D<T> satPoint = state.getPosition(bodyFrame);
// Elevation in radians
final T elevation = bodyFrame.
getStaticTransformTo(baseFrame, state.getDate()).
transformPosition(satPoint).
getDelta();
// Only consider measures above the horizon
if (elevation.getReal() > 0.0) {
// Normalized Line Of Sight in body frame
final Vector3D los = satPoint.toVector3D().subtract(baseFrame.getCartesianPoint()).normalize();
// Ionosphere Pierce Point
final GeodeticPoint ipp = piercePoint(state.getDate().toAbsoluteDate(), baseFrame.getCartesianPoint(), los, baseFrame.getParentShape());
if (ipp != null) {
// Delay
return pathDelayAtIPP(state.getDate(), ipp, elevation, frequency);
}
}
return elevation.getField().getZero();
}
/** Get the pair valid at date.
* @param date computation date
* @return pair valid at date
* @since 12.0
*/
private TECMapPair getPairAtDate(final AbsoluteDate date) {
final TECMapPair pair = tecMap.get(date);
if (pair == null) {
throw new OrekitException(OrekitMessages.NO_TEC_DATA_IN_FILES_FOR_DATE,
names, date);
}
return pair;
}
@Override
public List<ParameterDriver> getParametersDrivers() {
return Collections.emptyList();
}
/**
* Computes the ionospheric mapping function.
* @param elevation the elevation of the satellite in radians
* @param r0 mean Earth radius
* @param h height of the ionospheric layer
* @return the mapping function
*/
private double mappingFunction(final double elevation,
final double r0, final double h) {
// Calculate the zenith angle from the elevation
final double z = FastMath.abs(0.5 * FastMath.PI - elevation);
// Distance ratio
final double ratio = r0 / (r0 + h);
// Mapping function
final double coef = FastMath.sin(z) * ratio;
final double fz = 1.0 / FastMath.sqrt(1.0 - coef * coef);
return fz;
}
/**
* Computes the ionospheric mapping function.
* @param <T> type of the elements
* @param elevation the elevation of the satellite in radians
* @param r0 mean Earth radius
* @param h height of the ionospheric layer
* @return the mapping function
*/
private <T extends CalculusFieldElement<T>> T mappingFunction(final T elevation,
final double r0, final double h) {
// Calculate the zenith angle from the elevation
final T z = FastMath.abs(elevation.getPi().multiply(0.5).subtract(elevation));
// Distance ratio
final double ratio = r0 / (r0 + h);
// Mapping function
final T coef = FastMath.sin(z).multiply(ratio);
final T fz = FastMath.sqrt(coef.multiply(coef).negate().add(1.0)).reciprocal();
return fz;
}
/** Compute Ionospheric Pierce Point.
* <p>
* The pierce point is computed assuming a spherical ionospheric shell above mean Earth radius.
* </p>
* @param date computation date
* @param recPoint point at receiver station in body frame
* @param los normalized line of sight in body frame
* @param bodyShape shape of the body
* @return pierce point, or null if recPoint is above ionosphere single layer
* @since 12.0
*/
private GeodeticPoint piercePoint(final AbsoluteDate date,
final Vector3D recPoint, final Vector3D los,
final BodyShape bodyShape) {
final TECMapPair pair = getPairAtDate(date);
final double r = pair.r0 + pair.h;
final double r2 = r * r;
final double p2 = recPoint.getNormSq();
if (p2 >= r2) {
// we are above ionosphere single layer
return null;
}
// compute positive k such that recPoint + k los is on the spherical shell at radius r
final double dot = Vector3D.dotProduct(recPoint, los);
final double k = FastMath.sqrt(dot * dot + r2 - p2) - dot;
// Ionosphere Pierce Point in body frame
final Vector3D ipp = new Vector3D(1, recPoint, k, los);
return bodyShape.transform(ipp, bodyShape.getBodyFrame(), null);
}
/** Parser for IONEX files. */
private class Parser implements DataLoader {
/** String for the end of a TEC map. */
private static final String END = "END OF TEC MAP";
/** String for the epoch of a TEC map. */
private static final String EPOCH = "EPOCH OF CURRENT MAP";
/** Index of label in data lines. */
private static final int LABEL_START = 60;
/** Kilometers to meters conversion factor. */
private static final double KM_TO_M = 1000.0;
/** Header of the IONEX file. */
private IONEXHeader header;
/** List of TEC Maps. */
private List<TECMap> maps;
@Override
public boolean stillAcceptsData() {
return true;
}
@Override
public void loadData(final InputStream input, final String name)
throws IOException {
maps = new ArrayList<>();
// Open stream and parse data
int lineNumber = 0;
String line = null;
try (InputStreamReader isr = new InputStreamReader(input, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr)) {
// Placeholders for parsed data
int nbOfMaps = 1;
int exponent = -1;
double baseRadius = Double.NaN;
double hIon = Double.NaN;
boolean mappingF = false;
boolean inTEC = false;
double[] latitudes = null;
double[] longitudes = null;
AbsoluteDate firstEpoch = null;
AbsoluteDate lastEpoch = null;
AbsoluteDate epoch = firstEpoch;
ArrayList<Double> values = new ArrayList<>();
for (line = br.readLine(); line != null; line = br.readLine()) {
++lineNumber;
if (line.length() > LABEL_START) {
switch (line.substring(LABEL_START).trim()) {
case "EPOCH OF FIRST MAP" :
firstEpoch = parseDate(line);
break;
case "EPOCH OF LAST MAP" :
lastEpoch = parseDate(line);
break;
case "INTERVAL" :
// ignored;
break;
case "# OF MAPS IN FILE" :
nbOfMaps = parseInt(line, 2, 4);
break;
case "BASE RADIUS" :
// Value is in kilometers
baseRadius = parseDouble(line, 2, 6) * KM_TO_M;
break;
case "MAPPING FUNCTION" :
mappingF = !parseString(line, 2, 4).equals("NONE");
break;
case "EXPONENT" :
exponent = parseInt(line, 4, 2);
break;
case "HGT1 / HGT2 / DHGT" :
if (parseDouble(line, 17, 3) == 0.0) {
// Value is in kilometers
hIon = parseDouble(line, 3, 5) * KM_TO_M;
}
break;
case "LAT1 / LAT2 / DLAT" :
latitudes = parseCoordinate(line);
break;
case "LON1 / LON2 / DLON" :
longitudes = parseCoordinate(line);
break;
case "END OF HEADER" :
// Check that latitude and longitude bondaries were found
if (latitudes == null || longitudes == null) {
throw new OrekitException(OrekitMessages.NO_LATITUDE_LONGITUDE_BONDARIES_IN_IONEX_HEADER, name);
}
// Check that first and last epochs were found
if (firstEpoch == null || lastEpoch == null) {
throw new OrekitException(OrekitMessages.NO_EPOCH_IN_IONEX_HEADER, name);
}
// At the end of the header, we build the IONEXHeader object
header = new IONEXHeader(nbOfMaps, baseRadius, hIon, mappingF);
break;
case "START OF TEC MAP" :
inTEC = true;
break;
case END :
final BilinearInterpolatingFunction tec = interpolateTEC(values, exponent, latitudes, longitudes);
final TECMap map = new TECMap(epoch, tec);
maps.add(map);
// Reset parameters
inTEC = false;
values = new ArrayList<>();
epoch = null;
break;
default :
if (inTEC) {
// Date
if (line.endsWith(EPOCH)) {
epoch = parseDate(line);
}
// Fill TEC values list
if (!line.endsWith("LAT/LON1/LON2/DLON/H") &&
!line.endsWith(END) &&
!line.endsWith(EPOCH)) {
for (int fieldStart = 0; fieldStart < line.length(); fieldStart += 5) {
values.add((double) Integer.parseInt(line.substring(fieldStart, fieldStart + 5).trim()));
}
}
}
break;
}
} else {
if (inTEC) {
// Here, we are parsing the last line of TEC data for a given latitude
// The size of this line is lower than 60.
for (int fieldStart = 0; fieldStart < line.length(); fieldStart += 5) {
values.add((double) Integer.parseInt(line.substring(fieldStart, fieldStart + 5).trim()));
}
}
}
}
// Close the stream after reading
input.close();
} catch (NumberFormatException nfe) {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, name, line);
}
// TEC map
if (maps.size() != header.getTECMapsNumer()) {
throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_TEC_MAPS_IN_FILE,
maps.size(), header.getTECMapsNumer());
}
TECMap previous = null;
for (TECMap current : maps) {
if (previous != null) {
tecMap.addValidBetween(new TECMapPair(previous, current,
header.getEarthRadius(), header.getHIon(), header.isMappingFunction()),
previous.date, current.date);
}
previous = current;
}
names = names.isEmpty() ? name : (names + ", " + name);
}
/** Extract a string from a line.
* @param line to parse
* @param start start index of the string
* @param length length of the string
* @return parsed string
*/
private String parseString(final String line, final int start, final int length) {
return line.substring(start, FastMath.min(line.length(), start + length)).trim();
}
/** Extract an integer from a line.
* @param line to parse
* @param start start index of the integer
* @param length length of the integer
* @return parsed integer
*/
private int parseInt(final String line, final int start, final int length) {
return Integer.parseInt(parseString(line, start, length));
}
/** Extract a double from a line.
* @param line to parse
* @param start start index of the real
* @param length length of the real
* @return parsed real
*/
private double parseDouble(final String line, final int start, final int length) {
return Double.parseDouble(parseString(line, start, length));
}
/** Extract a date from a parsed line.
* @param line to parse
* @return an absolute date
*/
private AbsoluteDate parseDate(final String line) {
return new AbsoluteDate(parseInt(line, 0, 6),
parseInt(line, 6, 6),
parseInt(line, 12, 6),
parseInt(line, 18, 6),
parseInt(line, 24, 6),
parseDouble(line, 30, 13),
utc);
}
/** Build the coordinate array from a parsed line.
* @param line to parse
* @return an array of coordinates in radians
*/
private double[] parseCoordinate(final String line) {
final double a = parseDouble(line, 2, 6);
final double b = parseDouble(line, 8, 6);
final double c = parseDouble(line, 14, 6);
final double[] coordinate = new double[((int) FastMath.abs((a - b) / c)) + 1];
int i = 0;
for (double cor = FastMath.min(a, b); cor <= FastMath.max(a, b); cor += FastMath.abs(c)) {
coordinate[i] = FastMath.toRadians(cor);
i++;
}
return coordinate;
}
/** Interpolate the TEC in latitude and longitude.
* @param exponent exponent defining the unit of the values listed in the data blocks
* @param values TEC values
* @param latitudes array containing the different latitudes in radians
* @param longitudes array containing the different latitudes in radians
* @return the interpolating TEC functiopn in TECUnits
*/
private BilinearInterpolatingFunction interpolateTEC(final ArrayList<Double> values, final double exponent,
final double[] latitudes, final double[] longitudes) {
// Array dimensions
final int dimLat = latitudes.length;
final int dimLon = longitudes.length;
// Build the array of TEC data
final double factor = FastMath.pow(10.0, exponent);
final double[][] fvalTEC = new double[dimLat][dimLon];
int index = dimLon * dimLat;
for (int x = 0; x < dimLat; x++) {
for (int y = dimLon - 1; y >= 0; y--) {
index = index - 1;
fvalTEC[x][y] = values.get(index) * factor;
}
}
// Build Bilinear Interpolation function
return new BilinearInterpolatingFunction(latitudes, longitudes, fvalTEC);
}
}
/**
* Container for IONEX data.
* <p>
* The TEC contained in the map is previously interpolated
* according to the latitude and the longitude given by the user.
* </p>
*/
private static class TECMap {
/** Date of the TEC Map. */
private AbsoluteDate date;
/** Interpolated TEC [TECUnits]. */
private BilinearInterpolatingFunction tec;
/**
* Constructor.
* @param date date of the TEC map
* @param tec interpolated tec
*/
TECMap(final AbsoluteDate date, final BilinearInterpolatingFunction tec) {
this.date = date;
this.tec = tec;
}
}
/** Container for a consecutive pair of TEC maps.
* @since 12.0
*/
private static class TECMapPair {
/** First snapshot. */
private final TECMap first;
/** Second snapshot. */
private final TECMap second;
/** Mean earth radius [m]. */
private double r0;
/** Height of the ionospheric single layer [m]. */
private double h;
/** Flag for mapping function computation. */
private boolean mapping;
/** Simple constructor.
* @param first first snapshot
* @param second second snapshot
* @param r0 mean Earth radius
* @param h height of the ionospheric layer
* @param mapping flag for mapping computation
*/
TECMapPair(final TECMap first, final TECMap second,
final double r0, final double h, final boolean mapping) {
this.first = first;
this.second = second;
this.r0 = r0;
this.h = h;
this.mapping = mapping;
}
/** Get TEC at pierce point.
* @param date date
* @param ipp Ionospheric Pierce Point
* @return TEC
*/
public double getTEC(final AbsoluteDate date, final GeodeticPoint ipp) {
// Get the TEC values at the two closest dates
final AbsoluteDate t1 = first.date;
final double tec1 = first.tec.value(ipp.getLatitude(), ipp.getLongitude());
final AbsoluteDate t2 = second.date;
final double tec2 = second.tec.value(ipp.getLatitude(), ipp.getLongitude());
final double dt = t2.durationFrom(t1);
// Perform temporal interpolation (Ref, Eq. 2)
return (t2.durationFrom(date) / dt) * tec1 + (date.durationFrom(t1) / dt) * tec2;
}
/** Get TEC at pierce point.
* @param date date
* @param ipp Ionospheric Pierce Point
* @param <T> type of the field elements
* @return TEC
*/
public <T extends CalculusFieldElement<T>> T getTEC(final FieldAbsoluteDate<T> date, final GeodeticPoint ipp) {
// Get the TEC values at the two closest dates
final AbsoluteDate t1 = first.date;
final double tec1 = first.tec.value(ipp.getLatitude(), ipp.getLongitude());
final AbsoluteDate t2 = second.date;
final double tec2 = second.tec.value(ipp.getLatitude(), ipp.getLongitude());
final double dt = t2.durationFrom(t1);
// Perform temporal interpolation (Ref, Eq. 2)
return date.durationFrom(t2).negate().divide(dt).multiply(tec1).add(date.durationFrom(t1).divide(dt).multiply(tec2));
}
}
/** Container for IONEX header. */
private static class IONEXHeader {
/** Number of maps contained in the IONEX file. */
private int nbOfMaps;
/** Mean earth radius [m]. */
private double baseRadius;
/** Height of the ionospheric single layer [m]. */
private double hIon;
/** Flag for mapping function adopted for TEC determination. */
private boolean isMappingFunction;
/**
* Constructor.
* @param nbOfMaps number of TEC maps contained in the file
* @param baseRadius mean earth radius in meters
* @param hIon height of the ionospheric single layer in meters
* @param mappingFunction flag for mapping function adopted for TEC determination
*/
IONEXHeader(final int nbOfMaps,
final double baseRadius, final double hIon,
final boolean mappingFunction) {
this.nbOfMaps = nbOfMaps;
this.baseRadius = baseRadius;
this.hIon = hIon;
this.isMappingFunction = mappingFunction;
}
/**
* Get the number of TEC maps contained in the file.
* @return the number of TEC maps
*/
public int getTECMapsNumer() {
return nbOfMaps;
}
/**
* Get the mean earth radius in meters.
* @return the mean earth radius
*/
public double getEarthRadius() {
return baseRadius;
}
/**
* Get the height of the ionospheric single layer in meters.
* @return the height of the ionospheric single layer
*/
public double getHIon() {
return hIon;
}
/**
* Get the mapping function flag.
* @return false if mapping function computation is needed
*/
public boolean isMappingFunction() {
return isMappingFunction;
}
}
}