SaastamoinenModel.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 java.util.Collections;
  19. import java.util.List;
  20. import java.util.regex.Pattern;

  21. import org.hipparchus.Field;
  22. import org.hipparchus.CalculusFieldElement;
  23. import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
  24. import org.hipparchus.analysis.interpolation.LinearInterpolator;
  25. import org.hipparchus.analysis.polynomials.PolynomialFunction;
  26. import org.hipparchus.analysis.polynomials.PolynomialSplineFunction;
  27. import org.hipparchus.util.FastMath;
  28. import org.orekit.annotation.DefaultDataContext;
  29. import org.orekit.bodies.FieldGeodeticPoint;
  30. import org.orekit.bodies.GeodeticPoint;
  31. import org.orekit.data.DataContext;
  32. import org.orekit.data.DataProvidersManager;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.time.AbsoluteDate;
  36. import org.orekit.time.FieldAbsoluteDate;
  37. import org.orekit.utils.InterpolationTableLoader;
  38. import org.orekit.utils.ParameterDriver;

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

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

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

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

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

  70.     /** X values for the B function. */
  71.     private static final double[] X_VALUES_FOR_B = {
  72.         0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0
  73.     };

  74.     /** E values for the B function. */
  75.     private static final double[] Y_VALUES_FOR_B = {
  76.         1.156, 1.079, 1.006, 0.938, 0.874, 0.813, 0.757, 0.654, 0.563
  77.     };

  78.     /** Coefficients for the partial pressure of water vapor polynomial. */
  79.     private static final double[] E_COEFFICIENTS = {
  80.         -37.2465, 0.213166, -0.000256908
  81.     };

  82.     /** Interpolation function for the B correction term. */
  83.     private final PolynomialSplineFunction bFunction;

  84.     /** Polynomial function for the e term. */
  85.     private final PolynomialFunction eFunction;

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

  88.     /** The temperature at the station [K]. */
  89.     private double t0;

  90.     /** The atmospheric pressure [mbar]. */
  91.     private double p0;

  92.     /** The humidity [percent]. */
  93.     private double r0;

  94.     /** Lowest acceptable elevation angle [rad]. */
  95.     private double lowElevationThreshold;

  96.     /**
  97.      * Create a new Saastamoinen model for the troposphere using the given environmental
  98.      * conditions and table from the reference book.
  99.      *
  100.      * @param t0 the temperature at the station [K]
  101.      * @param p0 the atmospheric pressure at the station [mbar]
  102.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  103.      * @see #SaastamoinenModel(double, double, double, String, DataProvidersManager)
  104.      * @since 10.1
  105.      */
  106.     public SaastamoinenModel(final double t0, final double p0, final double r0) {
  107.         this(t0, p0, r0, defaultDeltaR());
  108.     }

  109.     /** Create a new Saastamoinen model for the troposphere using the given
  110.      * environmental conditions. This constructor uses the {@link DataContext#getDefault()
  111.      * default data context} if {@code deltaRFileName != null}.
  112.      *
  113.      * @param t0 the temperature at the station [K]
  114.      * @param p0 the atmospheric pressure at the station [mbar]
  115.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  116.      * @param deltaRFileName regular expression for filename containing δR
  117.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  118.      * default values from the reference book are used
  119.      * @since 7.1
  120.      * @see #SaastamoinenModel(double, double, double, String, DataProvidersManager)
  121.      */
  122.     @DefaultDataContext
  123.     public SaastamoinenModel(final double t0, final double p0, final double r0,
  124.                              final String deltaRFileName) {
  125.         this(t0, p0, r0, deltaRFileName,
  126.                 DataContext.getDefault().getDataProvidersManager());
  127.     }

  128.     /** Create a new Saastamoinen model for the troposphere using the given
  129.      * environmental conditions. This constructor allows the user to specify the source of
  130.      * of the δR file.
  131.      *
  132.      * @param t0 the temperature at the station [K]
  133.      * @param p0 the atmospheric pressure at the station [mbar]
  134.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  135.      * @param deltaRFileName regular expression for filename containing δR
  136.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  137.      * default values from the reference book are used
  138.      * @param dataProvidersManager provides access to auxiliary data.
  139.      * @since 10.1
  140.      */
  141.     public SaastamoinenModel(final double t0,
  142.                              final double p0,
  143.                              final double r0,
  144.                              final String deltaRFileName,
  145.                              final DataProvidersManager dataProvidersManager) {
  146.         this(t0, p0, r0,
  147.              deltaRFileName == null ?
  148.                      defaultDeltaR() :
  149.                      loadDeltaR(deltaRFileName, dataProvidersManager));
  150.     }

  151.     /** Create a new Saastamoinen model.
  152.      *
  153.      * @param t0 the temperature at the station [K]
  154.      * @param p0 the atmospheric pressure at the station [mbar]
  155.      * @param r0 the humidity at the station [fraction] (50% -> 0.5)
  156.      * @param deltaR δR correction term function
  157.      * @since 7.1
  158.      */
  159.     private SaastamoinenModel(final double t0, final double p0, final double r0,
  160.                               final BilinearInterpolatingFunction deltaR) {
  161.         this.t0             = t0;
  162.         this.p0             = p0;
  163.         this.r0             = r0;
  164.         this.bFunction      = new LinearInterpolator().interpolate(X_VALUES_FOR_B, Y_VALUES_FOR_B);
  165.         this.eFunction      = new PolynomialFunction(E_COEFFICIENTS);
  166.         this.deltaRFunction = deltaR;
  167.         this.lowElevationThreshold = DEFAULT_LOW_ELEVATION_THRESHOLD;
  168.     }

  169.     /** Create a new Saastamoinen model using a standard atmosphere model.
  170.      *
  171.      * <ul>
  172.      * <li>temperature: 18 degree Celsius
  173.      * <li>pressure: 1013.25 mbar
  174.      * <li>humidity: 50%
  175.      * </ul>
  176.      *
  177.      * @return a Saastamoinen model with standard environmental values
  178.      */
  179.     public static SaastamoinenModel getStandardModel() {
  180.         return new SaastamoinenModel(273.16 + 18, 1013.25, 0.5);
  181.     }

  182.     /** {@inheritDoc}
  183.      * <p>
  184.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  185.      * reasons, we use the value for h = 0 when altitude is negative.
  186.      * </p>
  187.      * <p>
  188.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  189.      * elevations lower than a threshold will use the value obtained
  190.      * for the threshold itself.
  191.      * </p>
  192.      * @see #getLowElevationThreshold()
  193.      * @see #setLowElevationThreshold(double)
  194.      */
  195.     @Override
  196.     public double pathDelay(final double elevation, final GeodeticPoint point,
  197.                             final double[] parameters, final AbsoluteDate date) {

  198.         // there are no data in the model for negative altitudes and altitude bigger than 5000 m
  199.         // limit the height to a range of [0, 5000] m
  200.         final double fixedHeight = FastMath.min(FastMath.max(0, point.getAltitude()), 5000);

  201.         // the corrected temperature using a temperature gradient of -6.5 K/km
  202.         final double T = t0 - 6.5e-3 * fixedHeight;
  203.         // the corrected pressure
  204.         final double P = p0 * FastMath.pow(1.0 - 2.26e-5 * fixedHeight, 5.225);
  205.         // the corrected humidity
  206.         final double R = r0 * FastMath.exp(-6.396e-4 * fixedHeight);

  207.         // interpolate the b correction term
  208.         final double B = bFunction.value(fixedHeight / 1e3);
  209.         // calculate e
  210.         final double e = R * FastMath.exp(eFunction.value(T));

  211.         // calculate the zenith angle from the elevation
  212.         final double z = FastMath.abs(0.5 * FastMath.PI - FastMath.max(elevation, lowElevationThreshold));

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

  215.         // calculate the path delay in m
  216.         final double tan = FastMath.tan(z);
  217.         final double delta = 2.277e-3 / FastMath.cos(z) *
  218.                              (P + (1255d / T + 5e-2) * e - B * tan * tan) + deltaR;

  219.         return delta;
  220.     }

  221.     /** {@inheritDoc}
  222.      * <p>
  223.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  224.      * reasons, we use the value for h = 0 when altitude is negative.
  225.      * </p>
  226.      * <p>
  227.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  228.      * elevations lower than a threshold will use the value obtained
  229.      * for the threshold itself.
  230.      * </p>
  231.      * @see #getLowElevationThreshold()
  232.      * @see #setLowElevationThreshold(double)
  233.      */
  234.     @Override
  235.     public <T extends CalculusFieldElement<T>> T pathDelay(final T elevation, final FieldGeodeticPoint<T> point,
  236.                                                        final T[] parameters, final FieldAbsoluteDate<T> date) {

  237.         final Field<T> field = date.getField();
  238.         final T zero = field.getZero();
  239.         // there are no data in the model for negative altitudes and altitude bigger than 5000 m
  240.         // limit the height to a range of [0, 5000] m
  241.         final T fixedHeight = FastMath.min(FastMath.max(zero, point.getAltitude()), zero.add(5000));

  242.         // the corrected temperature using a temperature gradient of -6.5 K/km
  243.         final T T = fixedHeight.multiply(6.5e-3).negate().add(t0);
  244.         // the corrected pressure
  245.         final T P = fixedHeight.multiply(2.26e-5).negate().add(1.0).pow(5.225).multiply(p0);
  246.         // the corrected humidity
  247.         final T R = FastMath.exp(fixedHeight.multiply(-6.396e-4)).multiply(r0);

  248.         // interpolate the b correction term
  249.         final T B = bFunction.value(fixedHeight.divide(1e3));
  250.         // calculate e
  251.         final T e = R.multiply(FastMath.exp(eFunction.value(T)));

  252.         // calculate the zenith angle from the elevation
  253.         final T z = FastMath.abs(FastMath.max(elevation, zero.add(lowElevationThreshold)).negate().add(zero.getPi().multiply(0.5)));

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

  256.         // calculate the path delay in m
  257.         final T tan = FastMath.tan(z);
  258.         final T delta = FastMath.cos(z).divide(2.277e-3).reciprocal().
  259.                         multiply(P.add(T.divide(1255d).reciprocal().add(5e-2).multiply(e)).subtract(B.multiply(tan).multiply(tan))).add(deltaR);

  260.         return delta;
  261.     }

  262.     /** Calculates the delta R correction term using linear interpolation.
  263.      * @param height the height of the station in m
  264.      * @param zenith the zenith angle of the satellite
  265.      * @return the delta R correction term in m
  266.      */
  267.     private double getDeltaR(final double height, final double zenith) {
  268.         // limit the height to a range of [0, 5000] m
  269.         final double h = FastMath.min(FastMath.max(0, height), 5000);
  270.         // limit the zenith angle to 90 degree
  271.         // Note: the function is symmetric for negative zenith angles
  272.         final double z = FastMath.min(Math.abs(zenith), 0.5 * FastMath.PI);
  273.         return deltaRFunction.value(h, z);
  274.     }

  275.     /** Calculates the delta R correction term using linear interpolation.
  276.      * @param <T> type of the elements
  277.      * @param height the height of the station in m
  278.      * @param zenith the zenith angle of the satellite
  279.      * @param field field used by default
  280.      * @return the delta R correction term in m
  281.      */
  282.     private  <T extends CalculusFieldElement<T>> T getDeltaR(final T height, final T zenith,
  283.                                                          final Field<T> field) {
  284.         final T zero = field.getZero();
  285.         // limit the height to a range of [0, 5000] m
  286.         final T h = FastMath.min(FastMath.max(zero, height), zero.add(5000));
  287.         // limit the zenith angle to 90 degree
  288.         // Note: the function is symmetric for negative zenith angles
  289.         final T z = FastMath.min(zenith.abs(), zero.getPi().multiply(0.5));
  290.         return deltaRFunction.value(h, z);
  291.     }

  292.     /** Load δR function.
  293.      * @param deltaRFileName regular expression for filename containing δR
  294.      * correction term table
  295.      * @param dataProvidersManager provides access to auxiliary data.
  296.      * @return δR function
  297.      */
  298.     private static BilinearInterpolatingFunction loadDeltaR(
  299.             final String deltaRFileName,
  300.             final DataProvidersManager dataProvidersManager) {

  301.         // read the δR interpolation function from the config file
  302.         final InterpolationTableLoader loader = new InterpolationTableLoader();
  303.         dataProvidersManager.feed(deltaRFileName, loader);
  304.         if (!loader.stillAcceptsData()) {
  305.             final double[] elevations = loader.getOrdinateGrid();
  306.             for (int i = 0; i < elevations.length; ++i) {
  307.                 elevations[i] = FastMath.toRadians(elevations[i]);
  308.             }
  309.             return new BilinearInterpolatingFunction(loader.getAbscissaGrid(), elevations,
  310.                                                      loader.getValuesSamples());
  311.         }
  312.         throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE,
  313.                                   SECOND_DELTA_R_PATTERN.
  314.                                   matcher(FIRST_DELTA_R_PATTERN.matcher(deltaRFileName).replaceAll("")).
  315.                                   replaceAll(""));
  316.     }

  317.     /** Create the default δR function.
  318.      * @return δR function
  319.      */
  320.     private static BilinearInterpolatingFunction defaultDeltaR() {

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

  324.         // the height in m
  325.         final double[] xValForR = {
  326.             0, 500, 1000, 1500, 2000, 3000, 4000, 5000
  327.         };

  328.         // the zenith angle
  329.         final double[] yValForR = {
  330.             FastMath.toRadians( 0.00), FastMath.toRadians(60.00), FastMath.toRadians(66.00), FastMath.toRadians(70.00),
  331.             FastMath.toRadians(73.00), FastMath.toRadians(75.00), FastMath.toRadians(76.00), FastMath.toRadians(77.00),
  332.             FastMath.toRadians(78.00), FastMath.toRadians(78.50), FastMath.toRadians(79.00), FastMath.toRadians(79.50),
  333.             FastMath.toRadians(79.75), FastMath.toRadians(80.00), FastMath.toRadians(90.00)
  334.         };

  335.         final double[][] fval = new double[][] {
  336.             {
  337.                 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
  338.             }, {
  339.                 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
  340.             }, {
  341.                 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
  342.             }, {
  343.                 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
  344.             }, {
  345.                 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
  346.             }, {
  347.                 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
  348.             }, {
  349.                 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
  350.             }, {
  351.                 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
  352.             }
  353.         };

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

  356.     }

  357.     /** {@inheritDoc} */
  358.     @Override
  359.     public List<ParameterDriver> getParametersDrivers() {
  360.         return Collections.emptyList();
  361.     }

  362.     /** Get the low elevation threshold value for path delay computation.
  363.      * @return low elevation threshold, in rad.
  364.      * @see #pathDelay(double, GeodeticPoint, double[], AbsoluteDate)
  365.      * @see #pathDelay(CalculusFieldElement, FieldGeodeticPoint, CalculusFieldElement[], FieldAbsoluteDate)
  366.      * @since 10.2
  367.      */
  368.     public double getLowElevationThreshold() {
  369.         return lowElevationThreshold;
  370.     }

  371.     /** Set the low elevation threshold value for path delay computation.
  372.      * @param lowElevationThreshold The new value for the threshold [rad]
  373.      * @see #pathDelay(double, GeodeticPoint, double[], AbsoluteDate)
  374.      * @see #pathDelay(CalculusFieldElement, FieldGeodeticPoint, CalculusFieldElement[], FieldAbsoluteDate)
  375.      * @since 10.2
  376.      */
  377.     public void setLowElevationThreshold(final double lowElevationThreshold) {
  378.         this.lowElevationThreshold = lowElevationThreshold;
  379.     }
  380. }