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;

  18. import java.io.Serializable;
  19. import java.util.Arrays;

  20. import org.hipparchus.analysis.BivariateFunction;
  21. import org.hipparchus.analysis.UnivariateFunction;
  22. import org.hipparchus.analysis.interpolation.LinearInterpolator;
  23. import org.hipparchus.analysis.polynomials.PolynomialFunction;
  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.util.FastMath;
  27. import org.hipparchus.util.MathArrays;
  28. import org.orekit.data.DataProvidersManager;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.utils.InterpolationTableLoader;

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

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

  57.     /** Serializable UID. */
  58.     private static final long serialVersionUID = 20160126L;

  59.     /** X values for the B function. */
  60.     private static final double[] X_VALUES_FOR_B = {
  61.         0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0
  62.     };

  63.     /** E values for the B function. */
  64.     private static final double[] Y_VALUES_FOR_B = {
  65.         1.156, 1.079, 1.006, 0.938, 0.874, 0.813, 0.757, 0.654, 0.563
  66.     };

  67.     /** Coefficients for the partial pressure of water vapor polynomial. */
  68.     private static final double[] E_COEFFICIENTS = {
  69.         -37.2465, 0.213166, -0.000256908
  70.     };

  71.     /** Interpolation function for the B correction term. */
  72.     private final transient UnivariateFunction bFunction;

  73.     /** Polynomial function for the e term. */
  74.     private final transient PolynomialFunction eFunction;

  75.     /** Interpolation function for the delta R correction term. */
  76.     private final transient BilinearInterpolatingFunction deltaRFunction;

  77.     /** The temperature at the station [K]. */
  78.     private double t0;

  79.     /** The atmospheric pressure [mbar]. */
  80.     private double p0;

  81.     /** The humidity [percent]. */
  82.     private double r0;

  83.     /** Create a new Saastamoinen model for the troposphere using the given
  84.      * environmental conditions.
  85.      * @param t0 the temperature at the station [K]
  86.      * @param p0 the atmospheric pressure at the station [mbar]
  87.      * @param r0 the humidity at the station [fraction] (50% -&gt; 0.5)
  88.      * @param deltaRFileName regular expression for filename containing δR
  89.      * correction term table (typically {@link #DELTA_R_FILE_NAME}), if null
  90.      * default values from the reference book are used
  91.      * @exception OrekitException if δR correction term table cannot be loaded
  92.      * @since 7.1
  93.      */
  94.     public SaastamoinenModel(final double t0, final double p0, final double r0,
  95.                              final String deltaRFileName)
  96.         throws OrekitException {
  97.         this(t0, p0, r0,
  98.              deltaRFileName == null ? defaultDeltaR() : loadDeltaR(deltaRFileName));
  99.     }

  100.     /** Create a new Saastamoinen model.
  101.      * @param t0 the temperature at the station [K]
  102.      * @param p0 the atmospheric pressure at the station [mbar]
  103.      * @param r0 the humidity at the station [fraction] (50% -> 0.5)
  104.      * @param deltaR δR correction term function
  105.      * @since 7.1
  106.      */
  107.     private SaastamoinenModel(final double t0, final double p0, final double r0,
  108.                               final BilinearInterpolatingFunction deltaR) {
  109.         this.t0             = t0;
  110.         this.p0             = p0;
  111.         this.r0             = r0;
  112.         this.bFunction      = new LinearInterpolator().interpolate(X_VALUES_FOR_B, Y_VALUES_FOR_B);
  113.         this.eFunction      = new PolynomialFunction(E_COEFFICIENTS);
  114.         this.deltaRFunction = deltaR;
  115.     }

  116.     /** Create a new Saastamoinen model using a standard atmosphere model.
  117.      *
  118.      * <ul>
  119.      * <li>temperature: 18 degree Celsius
  120.      * <li>pressure: 1013.25 mbar
  121.      * <li>humidity: 50%
  122.      * </ul>
  123.      *
  124.      * @return a Saastamoinen model with standard environmental values
  125.      * @exception OrekitException if δR correction term table cannot be loaded
  126.      */
  127.     public static SaastamoinenModel getStandardModel()
  128.         throws OrekitException {
  129.         return new SaastamoinenModel(273.16 + 18, 1013.25, 0.5, (String) null);
  130.     }

  131.     /** {@inheritDoc}
  132.      * <p>
  133.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  134.      * reasons, we use the value for h = 0 when altitude is negative.
  135.      * </p>
  136.      */
  137.     public double pathDelay(final double elevation, final double height) {

  138.         // there are no data in the model for negative altitudes
  139.         // we use the data for the lowest available altitude: 0.0
  140.         final double fixedHeight = FastMath.max(0.0, height);

  141.         // the corrected temperature using a temperature gradient of -6.5 K/km
  142.         final double T = t0 - 6.5e-3 * fixedHeight;
  143.         // the corrected pressure
  144.         final double P = p0 * FastMath.pow(1.0 - 2.26e-5 * fixedHeight, 5.225);
  145.         // the corrected humidity
  146.         final double R = r0 * FastMath.exp(-6.396e-4 * fixedHeight);

  147.         // interpolate the b correction term
  148.         final double B = bFunction.value(fixedHeight / 1e3);
  149.         // calculate e
  150.         final double e = R * FastMath.exp(eFunction.value(T));

  151.         // calculate the zenith angle from the elevation
  152.         final double z = FastMath.abs(0.5 * FastMath.PI - elevation);

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

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

  159.         return delta;
  160.     }

  161.     /** Calculates the delta R correction term using linear interpolation.
  162.      * @param height the height of the station in m
  163.      * @param zenith the zenith angle of the satellite
  164.      * @return the delta R correction term in m
  165.      */
  166.     private double getDeltaR(final double height, final double zenith) {
  167.         // limit the height to a range of [0, 5000] m
  168.         final double h = FastMath.min(FastMath.max(0, height), 5000);
  169.         // limit the zenith angle to 90 degree
  170.         // Note: the function is symmetric for negative zenith angles
  171.         final double z = FastMath.min(Math.abs(zenith), 0.5 * FastMath.PI);
  172.         return deltaRFunction.value(h, z);
  173.     }

  174.     /** Load δR function.
  175.      * @param deltaRFileName regular expression for filename containing δR
  176.      * correction term table
  177.      * @return δR function
  178.      * @exception OrekitException if table cannot be loaded
  179.      */
  180.     private static BilinearInterpolatingFunction loadDeltaR(final String deltaRFileName)
  181.         throws OrekitException {

  182.         // read the δR interpolation function from the config file
  183.         final InterpolationTableLoader loader = new InterpolationTableLoader();
  184.         DataProvidersManager.getInstance().feed(deltaRFileName, loader);
  185.         if (!loader.stillAcceptsData()) {
  186.             final double[] elevations = loader.getOrdinateGrid();
  187.             for (int i = 0; i < elevations.length; ++i) {
  188.                 elevations[i] = FastMath.toRadians(elevations[i]);
  189.             }
  190.             return new BilinearInterpolatingFunction(loader.getAbscissaGrid(), elevations,
  191.                                                      loader.getValuesSamples());
  192.         }
  193.         throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE,
  194.                                   deltaRFileName.replaceAll("^\\^", "").replaceAll("\\$$", ""));
  195.     }

  196.     /** Create the default δR function.
  197.      * @return δR function
  198.      */
  199.     private static BilinearInterpolatingFunction defaultDeltaR() {

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

  203.         // the height in m
  204.         final double xValForR[] = {
  205.             0, 500, 1000, 1500, 2000, 3000, 4000, 5000
  206.         };

  207.         // the zenith angle
  208.         final double yValForR[] = {
  209.             FastMath.toRadians( 0.00), FastMath.toRadians(60.00), FastMath.toRadians(66.00), FastMath.toRadians(70.00),
  210.             FastMath.toRadians(73.00), FastMath.toRadians(75.00), FastMath.toRadians(76.00), FastMath.toRadians(77.00),
  211.             FastMath.toRadians(78.00), FastMath.toRadians(78.50), FastMath.toRadians(79.00), FastMath.toRadians(79.50),
  212.             FastMath.toRadians(79.75), FastMath.toRadians(80.00), FastMath.toRadians(90.00)
  213.         };

  214.         final double[][] fval = new double[][] {
  215.             {
  216.                 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
  217.             }, {
  218.                 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
  219.             }, {
  220.                 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
  221.             }, {
  222.                 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
  223.             }, {
  224.                 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
  225.             }, {
  226.                 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
  227.             }, {
  228.                 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
  229.             }, {
  230.                 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
  231.             }
  232.         };

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

  235.     }

  236.     /** Replace the instance with a data transfer object for serialization.
  237.      * @return data transfer object that will be serialized
  238.      */
  239.     private Object writeReplace() {
  240.         return new DataTransferObject(this);
  241.     }

  242.     /** Specialization of the data transfer object for serialization. */
  243.     private static class DataTransferObject implements Serializable {

  244.         /** Serializable UID. */
  245.         private static final long serialVersionUID = 20160126L;

  246.         /** The temperature at the station [K]. */
  247.         private double t0;

  248.         /** The atmospheric pressure [mbar]. */
  249.         private double p0;

  250.         /** The humidity [percent]. */
  251.         private double r0;

  252.         /** Samples x-coordinates. */
  253.         private final double[] xval;

  254.         /** Samples y-coordinates. */
  255.         private final double[] yval;

  256.         /** Set of cubic splines patching the whole data grid. */
  257.         private final double[][] fval;

  258.         /** Simple constructor.
  259.          * @param model model to serialize
  260.          */
  261.         DataTransferObject(final SaastamoinenModel model) {
  262.             this.t0 = model.t0;
  263.             this.p0 = model.p0;
  264.             this.r0 = model.r0;
  265.             this.xval = model.deltaRFunction.xval.clone();
  266.             this.yval = model.deltaRFunction.yval.clone();
  267.             this.fval = model.deltaRFunction.fval.clone();
  268.         }

  269.         /** Replace the deserialized data transfer object with a {@link SaastamoinenModel}.
  270.          * @return replacement {@link SaastamoinenModel}
  271.          */
  272.         private Object readResolve() {
  273.             return new SaastamoinenModel(t0, p0, r0,
  274.                                          new BilinearInterpolatingFunction(xval, yval, fval));
  275.         }

  276.     }

  277.     /**
  278.      * Function that implements a standard bilinear interpolation.
  279.      * The interpolation as found
  280.      * in the Wikipedia reference <a href =
  281.      * "http://en.wikipedia.org/wiki/Bilinear_interpolation">BiLinear
  282.      * Interpolation</a>. This is a stand-in until Apache Math has a
  283.      * bilinear interpolator
  284.      */
  285.     private static class BilinearInterpolatingFunction implements BivariateFunction {

  286.         /**
  287.          * The minimum number of points that are needed to compute the
  288.          * function.
  289.          */
  290.         private static final int MIN_NUM_POINTS = 2;

  291.         /** Samples x-coordinates. */
  292.         private final double[] xval;

  293.         /** Samples y-coordinates. */
  294.         private final double[] yval;

  295.         /** Set of cubic splines patching the whole data grid. */
  296.         private final double[][] fval;

  297.         /**
  298.          * @param x Sample values of the x-coordinate, in increasing order.
  299.          * @param y Sample values of the y-coordinate, in increasing order.
  300.          * @param f Values of the function on every grid point. the expected
  301.          *        number of elements.
  302.          * @throws MathIllegalArgumentException if the length of x and y don't
  303.          *         match the row, column height of f, or if any of the arguments
  304.          *         are null, or if any of the arrays has zero length, or if
  305.          *         {@code x} or {@code y} are not strictly increasing.
  306.          */
  307.         BilinearInterpolatingFunction(final double[] x, final double[] y, final double[][] f)
  308.                         throws MathIllegalArgumentException {

  309.             if (x == null || y == null || f == null || f[0] == null) {
  310.                 throw new IllegalArgumentException("All arguments must be non-null");
  311.             }

  312.             final int xLen = x.length;
  313.             final int yLen = y.length;

  314.             if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) {
  315.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.NO_DATA);
  316.             }

  317.             if (xLen < MIN_NUM_POINTS || yLen < MIN_NUM_POINTS || f.length < MIN_NUM_POINTS ||
  318.                             f[0].length < MIN_NUM_POINTS) {
  319.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DATA);
  320.             }

  321.             if (xLen != f.length) {
  322.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  323.                                                        xLen, f.length);
  324.             }

  325.             if (yLen != f[0].length) {
  326.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  327.                                                        yLen, f[0].length);
  328.             }

  329.             MathArrays.checkOrder(x);
  330.             MathArrays.checkOrder(y);

  331.             xval = x.clone();
  332.             yval = y.clone();
  333.             fval = f.clone();
  334.         }

  335.         @Override
  336.         public double value(final double x, final double y) {
  337.             final int offset = 1;
  338.             final int count = offset + 1;
  339.             final int i = searchIndex(x, xval, offset, count);
  340.             final int j = searchIndex(y, yval, offset, count);

  341.             final double x1 = xval[i];
  342.             final double x2 = xval[i + 1];
  343.             final double y1 = yval[j];
  344.             final double y2 = yval[j + 1];
  345.             final double fQ11 = fval[i][j];
  346.             final double fQ21 = fval[i + 1][j];
  347.             final double fQ12 = fval[i][j + 1];
  348.             final double fQ22 = fval[i + 1][j + 1];

  349.             final double f = (fQ11 * (x2 - x)  * (y2 - y) +
  350.                             fQ21 * (x  - x1) * (y2 - y) +
  351.                             fQ12 * (x2 - x)  * (y  - y1) +
  352.                             fQ22 * (x  - x1) * (y  - y1)) /
  353.                             ((x2 - x1) * (y2 - y1));

  354.             return f;
  355.         }

  356.         /**
  357.          * @param c Coordinate.
  358.          * @param val Coordinate samples.
  359.          * @param offset how far back from found value to offset for
  360.          *        querying
  361.          * @param count total number of elements forward from beginning that
  362.          *        will be queried
  363.          * @return the index in {@code val} corresponding to the interval
  364.          *         containing {@code c}.
  365.          * @throws MathIllegalArgumentException if {@code c} is out of the range
  366.          *         defined by the boundary values of {@code val}.
  367.          */
  368.         private int searchIndex(final double c, final double[] val, final int offset, final int count)
  369.             throws MathIllegalArgumentException {
  370.             int r = Arrays.binarySearch(val, c);

  371.             if (r == -1 || r == -val.length - 1) {
  372.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE,
  373.                                                        c, val[0], val[val.length - 1]);
  374.             }

  375.             if (r < 0) {
  376.                 // "c" in within an interpolation sub-interval, which
  377.                 // returns
  378.                 // negative
  379.                 // need to remove the negative sign for consistency
  380.                 r = -r - offset - 1;
  381.             } else {
  382.                 r -= offset;
  383.             }

  384.             if (r < 0) {
  385.                 r = 0;
  386.             }

  387.             if ((r + count) >= val.length) {
  388.                 // "c" is the last sample of the range: Return the index
  389.                 // of the sample at the lower end of the last sub-interval.
  390.                 r = val.length - count;
  391.             }

  392.             return r;
  393.         }

  394.     }

  395. }