ModifiedSaastamoinenModel.java

  1. /* Copyright 2011-2012 Space Applications Services
  2.  * Licensed to CS Communication & Systèmes (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.  * CS 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.troposphere;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
  21. import org.hipparchus.analysis.interpolation.LinearInterpolator;
  22. import org.hipparchus.analysis.polynomials.PolynomialSplineFunction;
  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.annotation.DefaultDataContext;
  25. import org.orekit.bodies.FieldGeodeticPoint;
  26. import org.orekit.bodies.GeodeticPoint;
  27. import org.orekit.data.DataContext;
  28. import org.orekit.data.DataProvidersManager;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.models.earth.weather.ConstantPressureTemperatureHumidityProvider;
  32. import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
  33. import org.orekit.models.earth.weather.HeightDependentPressureTemperatureHumidityConverter;
  34. import org.orekit.models.earth.weather.PressureTemperatureHumidity;
  35. import org.orekit.models.earth.weather.PressureTemperatureHumidityProvider;
  36. import org.orekit.models.earth.weather.water.Wang1988;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.time.FieldAbsoluteDate;
  39. import org.orekit.utils.FieldTrackingCoordinates;
  40. import org.orekit.utils.InterpolationTableLoader;
  41. import org.orekit.utils.ParameterDriver;
  42. import org.orekit.utils.TrackingCoordinates;

  43. import java.util.Collections;
  44. import java.util.List;
  45. import java.util.regex.Pattern;

  46. /** The modified Saastamoinen model. Estimates the path delay imposed to
  47.  * electro-magnetic signals by the troposphere according to the formula:
  48.  * <pre>
  49.  * δ = 2.277e-3 / cos z * (P + (1255 / T + 0.05) * e - B * tan² z) + δR
  50.  * </pre>
  51.  * with the following input data provided to the model:
  52.  * <ul>
  53.  * <li>z: zenith angle</li>
  54.  * <li>P: atmospheric pressure</li>
  55.  * <li>T: temperature</li>
  56.  * <li>e: partial pressure of water vapour</li>
  57.  * <li>B, δR: correction terms</li>
  58.  * </ul>
  59.  * <p>
  60.  * The model supports custom δR correction terms to be read from a
  61.  * configuration file (saastamoinen-correction.txt) via the
  62.  * {@link DataProvidersManager}.
  63.  * </p>
  64.  * @author Thomas Neidhart
  65.  * @see "Guochang Xu, GPS - Theory, Algorithms and Applications, Springer, 2007"
  66.  * @since 12.0
  67.  */
  68. public class ModifiedSaastamoinenModel implements TroposphericModel, DiscreteTroposphericModel {

  69.     /** Default file name for δR correction term table. */
  70.     public static final String DELTA_R_FILE_NAME = "^saastamoinen-correction\\.txt$";

  71.     /** Default lowest acceptable elevation angle [rad]. */
  72.     public static final double DEFAULT_LOW_ELEVATION_THRESHOLD = 0.05;

  73.     /** Provider for water pressure. */
  74.     public static final Wang1988 WATER = new Wang1988();

  75.     /** First pattern for δR correction term table. */
  76.     private static final Pattern FIRST_DELTA_R_PATTERN = Pattern.compile("^\\^");

  77.     /** Second pattern for δR correction term table. */
  78.     private static final Pattern SECOND_DELTA_R_PATTERN = Pattern.compile("\\$$");

  79.     /** Base delay coefficient. */
  80.     private static final double L0 = 2.277e-5;

  81.     /** Temperature numerator. */
  82.     private static final double T_NUM = 1255;

  83.     /** Wet offset. */
  84.     private static final double WET_OFFSET = 0.05;

  85.     /** X values for the B function. */
  86.     private static final double[] X_VALUES_FOR_B = {
  87.         0.0, 500.0, 1000.0, 1500.0, 2000.0, 2500.0, 3000.0, 4000.0, 5000.0
  88.     };

  89.     /** Y values for the B function.
  90.      * <p>
  91.      * The values have been scaled up by a factor 100.0 due to conversion from hPa to Pa.
  92.      * </p>
  93.      */
  94.     private static final double[] Y_VALUES_FOR_B = {
  95.         115.6, 107.9, 100.6, 93.8, 87.4, 81.3, 75.7, 65.4, 56.3
  96.     };

  97.     /** Interpolation function for the B correction term. */
  98.     private static final PolynomialSplineFunction B_FUNCTION = new LinearInterpolator().interpolate(X_VALUES_FOR_B, Y_VALUES_FOR_B);

  99.     /** Interpolation function for the delta R correction term. */
  100.     private final BilinearInterpolatingFunction deltaRFunction;

  101.     /** Provider for atmospheric pressure, temperature and humidity at reference altitude. */
  102.     private final PressureTemperatureHumidityProvider pth0Provider;

  103.     /** Height dependent converter for pressure, temperature and humidity. */
  104.     private final HeightDependentPressureTemperatureHumidityConverter converter;

  105.     /** Lowest acceptable elevation angle [rad]. */
  106.     private double lowElevationThreshold;

  107.     /**
  108.      * Create a new Saastamoinen model for the troposphere using the given environmental
  109.      * conditions and table from the reference book.
  110.      *
  111.      * @param t0 the temperature at the station [K]
  112.      * @param p0 the atmospheric pressure at the station [mbar]
  113.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  114.      * @see #ModifiedSaastamoinenModel(double, double, double, String, DataProvidersManager)
  115.      * @since 10.1
  116.      * @deprecated as of 12.1.1, replaced by {@link #ModifiedSaastamoinenModel(PressureTemperatureHumidityProvider)}
  117.      */
  118.     @Deprecated
  119.     public ModifiedSaastamoinenModel(final double t0, final double p0, final double r0) {
  120.         this(t0, p0, r0, defaultDeltaR());
  121.     }

  122.     /** Create a new Saastamoinen model for the troposphere using the given
  123.      * environmental conditions. This constructor uses the {@link DataContext#getDefault()
  124.      * default data context} if {@code deltaRFileName != null}.
  125.      *
  126.      * @param t0 the temperature at the station [K]
  127.      * @param p0 the atmospheric pressure at the station [mbar]
  128.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  129.      * @param deltaRFileName regular expression for filename containing δR
  130.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  131.      * default values from the reference book are used
  132.      * @since 7.1
  133.      * @see #ModifiedSaastamoinenModel(double, double, double, String, DataProvidersManager)
  134.      * @deprecated as of 12.1.1, replaced by {@link #ModifiedSaastamoinenModel(PressureTemperatureHumidityProvider, String)}
  135.      */
  136.     @Deprecated
  137.     @DefaultDataContext
  138.     public ModifiedSaastamoinenModel(final double t0, final double p0, final double r0,
  139.                                      final String deltaRFileName) {
  140.         this(t0, p0, r0, deltaRFileName,
  141.                 DataContext.getDefault().getDataProvidersManager());
  142.     }

  143.     /** Create a new Saastamoinen model for the troposphere using the given
  144.      * environmental conditions. This constructor allows the user to specify the source of
  145.      * of the δR file.
  146.      *
  147.      * @param t0 the temperature at the station [K]
  148.      * @param p0 the atmospheric pressure at the station [mbar]
  149.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  150.      * @param deltaRFileName regular expression for filename containing δR
  151.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  152.      * default values from the reference book are used
  153.      * @param dataProvidersManager provides access to auxiliary data.
  154.      * @since 10.1
  155.      * @deprecated as of 12.1.1, replaced by {@link #ModifiedSaastamoinenModel(PressureTemperatureHumidityProvider, String, DataProvidersManager)}
  156.      */
  157.     @Deprecated
  158.     public ModifiedSaastamoinenModel(final double t0,
  159.                                      final double p0,
  160.                                      final double r0,
  161.                                      final String deltaRFileName,
  162.                                      final DataProvidersManager dataProvidersManager) {
  163.         this(t0, p0, r0,
  164.              deltaRFileName == null ?
  165.                      defaultDeltaR() :
  166.                      loadDeltaR(deltaRFileName, dataProvidersManager));
  167.     }

  168.     /** Create a new Saastamoinen model.
  169.      *
  170.      * @param t0 the temperature at the station [K]
  171.      * @param p0 the atmospheric pressure at the station [mbar]
  172.      * @param r0 the humidity at the station [fraction] (50% -> 0.5)
  173.      * @param deltaR δR correction term function
  174.      * @since 7.1
  175.      * @deprecated as of 12.1.1, replaced by {@link #ModifiedSaastamoinenModel(PressureTemperatureHumidityProvider, BilinearInterpolatingFunction)}
  176.      */
  177.     @Deprecated
  178.     private ModifiedSaastamoinenModel(final double t0, final double p0, final double r0,
  179.                                       final BilinearInterpolatingFunction deltaR) {
  180.         this(new ConstantPressureTemperatureHumidityProvider(
  181.             new PressureTemperatureHumidity(0,
  182.                                             TroposphericModelUtils.HECTO_PASCAL.toSI(p0),
  183.                                             t0,
  184.                                             WATER.waterVaporPressure(TroposphericModelUtils.HECTO_PASCAL.toSI(p0), t0, r0),
  185.                                             Double.NaN,
  186.                                             Double.NaN)),
  187.              deltaR);
  188.     }

  189.     /**
  190.      * Create a new Saastamoinen model for the troposphere using the given environmental
  191.      * conditions and table from the reference book.
  192.      *
  193.      * @param pth0Provider provider for atmospheric pressure, temperature and humidity at reference altitude
  194.      * @see #ModifiedSaastamoinenModel(PressureTemperatureHumidityProvider, String, DataProvidersManager)
  195.      */
  196.     public ModifiedSaastamoinenModel(final PressureTemperatureHumidityProvider pth0Provider) {
  197.         this(pth0Provider, defaultDeltaR());
  198.     }

  199.     /** Create a new Saastamoinen model for the troposphere using the given
  200.      * environmental conditions. This constructor uses the {@link DataContext#getDefault()
  201.      * default data context} if {@code deltaRFileName != null}.
  202.      *
  203.      * @param pth0Provider provider for atmospheric pressure, temperature and humidity at reference altitude
  204.      * @param deltaRFileName regular expression for filename containing δR
  205.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  206.      * default values from the reference book are used
  207.      * @see #ModifiedSaastamoinenModel(PressureTemperatureHumidityProvider, String, DataProvidersManager)
  208.      */
  209.     @DefaultDataContext
  210.     public ModifiedSaastamoinenModel(final PressureTemperatureHumidityProvider pth0Provider,
  211.                                      final String deltaRFileName) {
  212.         this(pth0Provider, deltaRFileName,
  213.              DataContext.getDefault().getDataProvidersManager());
  214.     }

  215.     /** Create a new Saastamoinen model for the troposphere using the given
  216.      * environmental conditions. This constructor allows the user to specify the source of
  217.      * of the δR file.
  218.      *
  219.      * @param pth0Provider provider for atmospheric pressure, temperature and humidity at reference altitude
  220.      * @param deltaRFileName regular expression for filename containing δR
  221.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  222.      * default values from the reference book are used
  223.      * @param dataProvidersManager provides access to auxiliary data.
  224.      */
  225.     public ModifiedSaastamoinenModel(final PressureTemperatureHumidityProvider pth0Provider,
  226.                                      final String deltaRFileName,
  227.                                      final DataProvidersManager dataProvidersManager) {
  228.         this(pth0Provider,
  229.              deltaRFileName == null ?
  230.                      defaultDeltaR() :
  231.                      loadDeltaR(deltaRFileName, dataProvidersManager));
  232.     }

  233.     /** Create a new Saastamoinen model.
  234.      *
  235.      * @param pth0Provider provider for atmospheric pressure, temperature and humidity at reference altitude
  236.      * @param deltaR δR correction term function
  237.      */
  238.     private ModifiedSaastamoinenModel(final PressureTemperatureHumidityProvider pth0Provider,
  239.                                       final BilinearInterpolatingFunction deltaR) {
  240.         this.pth0Provider          = pth0Provider;
  241.         this.converter             = new HeightDependentPressureTemperatureHumidityConverter(WATER);
  242.         this.deltaRFunction        = deltaR;
  243.         this.lowElevationThreshold = DEFAULT_LOW_ELEVATION_THRESHOLD;
  244.     }

  245.     /** Create a new Saastamoinen model using a standard atmosphere model.
  246.      *
  247.      * <ul>
  248.      * <li>altitude: 0m</li>
  249.      * <li>temperature: 18 degree Celsius</li>
  250.      * <li>pressure: 1013.25 mbar</li>
  251.      * <li>humidity: 50%</li>
  252.      * <li>@link {@link Wang1988 Wang 1988} model to compute water vapor pressure</li>
  253.      * </ul>
  254.      *
  255.      * @return a Saastamoinen model with standard environmental values
  256.      */
  257.     public static ModifiedSaastamoinenModel getStandardModel() {
  258.         final double altitude    = 0;
  259.         final double pressure    = TroposphericModelUtils.HECTO_PASCAL.toSI(1013.25);
  260.         final double temperature = 273.15 + 18;
  261.         final double humidity    = 0.5;
  262.         final PressureTemperatureHumidity pth = new PressureTemperatureHumidity(altitude,
  263.                                                                                 pressure,
  264.                                                                                 temperature,
  265.                                                                                 WATER.waterVaporPressure(pressure,
  266.                                                                                                          temperature,
  267.                                                                                                          humidity),
  268.                                                                                 Double.NaN,
  269.                                                                                 Double.NaN);
  270.         final PressureTemperatureHumidityProvider pth0Provider = new ConstantPressureTemperatureHumidityProvider(pth);
  271.         return new ModifiedSaastamoinenModel(pth0Provider);
  272.     }

  273.     /** Get provider for atmospheric pressure, temperature and humidity at reference altitude.
  274.      * @return provider for atmospheric pressure, temperature and humidity at reference altitude
  275.      */
  276.     public PressureTemperatureHumidityProvider getPth0Provider() {
  277.         return pth0Provider;
  278.     }

  279.     /** {@inheritDoc}
  280.      * <p>
  281.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  282.      * reasons, we use the value for h = 0 when altitude is negative.
  283.      * </p>
  284.      * <p>
  285.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  286.      * elevations lower than a threshold will use the value obtained
  287.      * for the threshold itself.
  288.      * </p>
  289.      * @see #getLowElevationThreshold()
  290.      * @see #setLowElevationThreshold(double)
  291.      */
  292.     @Override
  293.     public double pathDelay(final double elevation, final GeodeticPoint point,
  294.                             final double[] parameters, final AbsoluteDate date) {
  295.         return pathDelay(new TrackingCoordinates(0.0, elevation, 0.0), point,
  296.                          pth0Provider.getWeatherParamerers(point, date),
  297.                          parameters, date).
  298.                getDelay();
  299.     }

  300.     /** {@inheritDoc}
  301.      * <p>
  302.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  303.      * reasons, we use the value for h = 0 when altitude is negative.
  304.      * </p>
  305.      * <p>
  306.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  307.      * elevations lower than a threshold will use the value obtained
  308.      * for the threshold itself.
  309.      * </p>
  310.      * @see #getLowElevationThreshold()
  311.      * @see #setLowElevationThreshold(double)
  312.      */
  313.     @Override
  314.     public <T extends CalculusFieldElement<T>> T pathDelay(final T elevation, final FieldGeodeticPoint<T> point,
  315.                                                        final T[] parameters, final FieldAbsoluteDate<T> date) {
  316.         return pathDelay(new FieldTrackingCoordinates<>(date.getField().getZero(), elevation, date.getField().getZero()),
  317.                          point,
  318.                          pth0Provider.getWeatherParamerers(point, date),
  319.                          parameters, date).
  320.                getDelay();
  321.     }

  322.     /** {@inheritDoc}
  323.      * <p>
  324.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  325.      * reasons, we use the value for h = 0 when altitude is negative.
  326.      * </p>
  327.      * <p>
  328.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  329.      * elevations lower than a threshold will use the value obtained
  330.      * for the threshold itself.
  331.      * </p>
  332.      * @see #getLowElevationThreshold()
  333.      * @see #setLowElevationThreshold(double)
  334.      */
  335.     @Override
  336.     public TroposphericDelay pathDelay(final TrackingCoordinates trackingCoordinates,
  337.                                        final GeodeticPoint point,
  338.                                        final PressureTemperatureHumidity weather,
  339.                                        final double[] parameters, final AbsoluteDate date) {

  340.         // limit the height to model range
  341.         final double fixedHeight = FastMath.min(FastMath.max(point.getAltitude(), X_VALUES_FOR_B[0]),
  342.                                                 X_VALUES_FOR_B[X_VALUES_FOR_B.length - 1]);

  343.         final PressureTemperatureHumidity pth = converter.convert(weather, fixedHeight);

  344.         // interpolate the b correction term
  345.         final double B = B_FUNCTION.value(fixedHeight);

  346.         // calculate the zenith angle from the elevation
  347.         final double z = FastMath.abs(0.5 * FastMath.PI -
  348.                                       FastMath.max(trackingCoordinates.getElevation(), lowElevationThreshold));

  349.         // get correction factor
  350.         final double deltaR = getDeltaR(fixedHeight, z);

  351.         // calculate the path delay in m
  352.         // beware since version 12.1 pressures are in Pa and not in hPa, hence the scaling has changed
  353.         final double invCos = 1.0 / FastMath.cos(z);
  354.         final double tan    = FastMath.tan(z);
  355.         final double zh     = L0 * pth.getPressure();
  356.         final double zw     = L0 * (T_NUM / pth.getTemperature() + WET_OFFSET) * pth.getWaterVaporPressure();
  357.         final double sh     = zh * invCos;
  358.         final double sw     = (zw - L0 * B * tan * tan) * invCos + deltaR;
  359.         return new TroposphericDelay(zh, zw, sh, sw);

  360.     }

  361.     /** {@inheritDoc}
  362.      * <p>
  363.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  364.      * reasons, we use the value for h = 0 when altitude is negative.
  365.      * </p>
  366.      * <p>
  367.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  368.      * elevations lower than a threshold will use the value obtained
  369.      * for the threshold itself.
  370.      * </p>
  371.      * @see #getLowElevationThreshold()
  372.      * @see #setLowElevationThreshold(double)
  373.      */
  374.     @Override
  375.     public <T extends CalculusFieldElement<T>> FieldTroposphericDelay<T> pathDelay(final FieldTrackingCoordinates<T> trackingCoordinates,
  376.                                                                                    final FieldGeodeticPoint<T> point,
  377.                                                                                    final FieldPressureTemperatureHumidity<T> weather,
  378.                                                                                    final T[] parameters, final FieldAbsoluteDate<T> date) {

  379.         // limit the height to model range
  380.         final T fixedHeight = FastMath.min(FastMath.max(point.getAltitude(), X_VALUES_FOR_B[0]),
  381.                                            X_VALUES_FOR_B[X_VALUES_FOR_B.length - 1]);

  382.         final FieldPressureTemperatureHumidity<T> pth = converter.convert(weather, fixedHeight);

  383.         final Field<T> field = date.getField();
  384.         final T zero = field.getZero();

  385.         // interpolate the b correction term
  386.         final T B = B_FUNCTION.value(fixedHeight);

  387.         // calculate the zenith angle from the elevation
  388.         final T z = FastMath.abs(FastMath.max(trackingCoordinates.getElevation(),
  389.                                               zero.newInstance(lowElevationThreshold)).negate().
  390.                                  add(zero.getPi().multiply(0.5)));

  391.         // get correction factor
  392.         final T deltaR = getDeltaR(fixedHeight, z, field);

  393.         // calculate the path delay in m
  394.         // beware since version 12.1 pressures are in Pa and not in hPa, hence the scaling has changed
  395.         final T invCos = FastMath.cos(z).reciprocal();
  396.         final T tan    = FastMath.tan(z);
  397.         final T zh     = pth.getPressure().multiply(L0);
  398.         final T zw     = pth.getTemperature().reciprocal().multiply(T_NUM).add(WET_OFFSET).
  399.                          multiply(pth.getWaterVaporPressure()).multiply(L0);
  400.         final T sh     = zh.multiply(invCos);
  401.         final T sw     = zw.subtract(B.multiply(tan).multiply(tan).multiply(L0)).multiply(invCos).add(deltaR);
  402.         return new FieldTroposphericDelay<>(zh, zw, sh, sw);

  403.     }

  404.     /** Calculates the delta R correction term using linear interpolation.
  405.      * @param height the height of the station in m
  406.      * @param zenith the zenith angle of the satellite
  407.      * @return the delta R correction term in m
  408.      */
  409.     private double getDeltaR(final double height, final double zenith) {
  410.         // limit the height to a range of [0, 5000] m
  411.         final double h = FastMath.min(FastMath.max(0, height), 5000);
  412.         // limit the zenith angle to 90 degree
  413.         // Note: the function is symmetric for negative zenith angles
  414.         final double z = FastMath.min(Math.abs(zenith), 0.5 * FastMath.PI);
  415.         return deltaRFunction.value(h, z);
  416.     }

  417.     /** Calculates the delta R correction term using linear interpolation.
  418.      * @param <T> type of the elements
  419.      * @param height the height of the station in m
  420.      * @param zenith the zenith angle of the satellite
  421.      * @param field field used by default
  422.      * @return the delta R correction term in m
  423.      */
  424.     private  <T extends CalculusFieldElement<T>> T getDeltaR(final T height, final T zenith,
  425.                                                          final Field<T> field) {
  426.         final T zero = field.getZero();
  427.         // limit the height to a range of [0, 5000] m
  428.         final T h = FastMath.min(FastMath.max(zero, height), zero.add(5000));
  429.         // limit the zenith angle to 90 degree
  430.         // Note: the function is symmetric for negative zenith angles
  431.         final T z = FastMath.min(zenith.abs(), zero.getPi().multiply(0.5));
  432.         return deltaRFunction.value(h, z);
  433.     }

  434.     /** Load δR function.
  435.      * @param deltaRFileName regular expression for filename containing δR
  436.      * correction term table
  437.      * @param dataProvidersManager provides access to auxiliary data.
  438.      * @return δR function
  439.      */
  440.     private static BilinearInterpolatingFunction loadDeltaR(
  441.             final String deltaRFileName,
  442.             final DataProvidersManager dataProvidersManager) {

  443.         // read the δR interpolation function from the config file
  444.         final InterpolationTableLoader loader = new InterpolationTableLoader();
  445.         dataProvidersManager.feed(deltaRFileName, loader);
  446.         if (!loader.stillAcceptsData()) {
  447.             final double[] elevations = loader.getOrdinateGrid();
  448.             for (int i = 0; i < elevations.length; ++i) {
  449.                 elevations[i] = FastMath.toRadians(elevations[i]);
  450.             }
  451.             return new BilinearInterpolatingFunction(loader.getAbscissaGrid(), elevations,
  452.                                                      loader.getValuesSamples());
  453.         }
  454.         throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE,
  455.                                   SECOND_DELTA_R_PATTERN.
  456.                                   matcher(FIRST_DELTA_R_PATTERN.matcher(deltaRFileName).replaceAll("")).
  457.                                   replaceAll(""));
  458.     }

  459.     /** Create the default δR function.
  460.      * @return δR function
  461.      */
  462.     private static BilinearInterpolatingFunction defaultDeltaR() {

  463.         // the correction table in the referenced book only contains values for an angle of 60 - 80
  464.         // degree, thus for 0 degree, the correction term is assumed to be 0, for degrees > 80 it
  465.         // is assumed to be the same value as for 80.

  466.         // the height in m
  467.         final double[] xValForR = {
  468.             0, 500, 1000, 1500, 2000, 3000, 4000, 5000
  469.         };

  470.         // the zenith angle
  471.         final double[] yValForR = {
  472.             FastMath.toRadians( 0.00), FastMath.toRadians(60.00), FastMath.toRadians(66.00), FastMath.toRadians(70.00),
  473.             FastMath.toRadians(73.00), FastMath.toRadians(75.00), FastMath.toRadians(76.00), FastMath.toRadians(77.00),
  474.             FastMath.toRadians(78.00), FastMath.toRadians(78.50), FastMath.toRadians(79.00), FastMath.toRadians(79.50),
  475.             FastMath.toRadians(79.75), FastMath.toRadians(80.00), FastMath.toRadians(90.00)
  476.         };

  477.         final double[][] fval = new double[][] {
  478.             {
  479.                 0.000, 0.003, 0.006, 0.012, 0.020, 0.031, 0.039, 0.050, 0.065, 0.075, 0.087, 0.102, 0.111, 0.121, 0.121
  480.             }, {
  481.                 0.000, 0.003, 0.006, 0.011, 0.018, 0.028, 0.035, 0.045, 0.059, 0.068, 0.079, 0.093, 0.101, 0.110, 0.110
  482.             }, {
  483.                 0.000, 0.002, 0.005, 0.010, 0.017, 0.025, 0.032, 0.041, 0.054, 0.062, 0.072, 0.085, 0.092, 0.100, 0.100
  484.             }, {
  485.                 0.000, 0.002, 0.005, 0.009, 0.015, 0.023, 0.029, 0.037, 0.049, 0.056, 0.065, 0.077, 0.083, 0.091, 0.091
  486.             }, {
  487.                 0.000, 0.002, 0.004, 0.008, 0.013, 0.021, 0.026, 0.033, 0.044, 0.051, 0.059, 0.070, 0.076, 0.083, 0.083
  488.             }, {
  489.                 0.000, 0.002, 0.003, 0.006, 0.011, 0.017, 0.021, 0.027, 0.036, 0.042, 0.049, 0.058, 0.063, 0.068, 0.068
  490.             }, {
  491.                 0.000, 0.001, 0.003, 0.005, 0.009, 0.014, 0.017, 0.022, 0.030, 0.034, 0.040, 0.047, 0.052, 0.056, 0.056
  492.             }, {
  493.                 0.000, 0.001, 0.002, 0.004, 0.007, 0.011, 0.014, 0.018, 0.024, 0.028, 0.033, 0.039, 0.043, 0.047, 0.047
  494.             }
  495.         };

  496.         // the actual delta R is interpolated using a a bilinear interpolator
  497.         return new BilinearInterpolatingFunction(xValForR, yValForR, fval);

  498.     }

  499.     /** {@inheritDoc} */
  500.     @Override
  501.     public List<ParameterDriver> getParametersDrivers() {
  502.         return Collections.emptyList();
  503.     }

  504.     /** Get the low elevation threshold value for path delay computation.
  505.      * @return low elevation threshold, in rad.
  506.      * @see #pathDelay(TrackingCoordinates, GeodeticPoint, PressureTemperatureHumidity, double[], AbsoluteDate)
  507.      * @see #pathDelay(FieldTrackingCoordinates, FieldGeodeticPoint, FieldPressureTemperatureHumidity, CalculusFieldElement[], FieldAbsoluteDate)
  508.      * @since 10.2
  509.      */
  510.     public double getLowElevationThreshold() {
  511.         return lowElevationThreshold;
  512.     }

  513.     /** Set the low elevation threshold value for path delay computation.
  514.      * @param lowElevationThreshold The new value for the threshold [rad]
  515.      * @see #pathDelay(TrackingCoordinates, GeodeticPoint, PressureTemperatureHumidity, double[], AbsoluteDate)
  516.      * @see #pathDelay(FieldTrackingCoordinates, FieldGeodeticPoint, FieldPressureTemperatureHumidity, CalculusFieldElement[], FieldAbsoluteDate)
  517.      * @since 10.2
  518.      */
  519.     public void setLowElevationThreshold(final double lowElevationThreshold) {
  520.         this.lowElevationThreshold = lowElevationThreshold;
  521.     }
  522. }