MendesPavlisModel.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (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 org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.Field;
  22. import org.hipparchus.util.FastMath;
  23. import org.hipparchus.util.MathArrays;
  24. import org.orekit.bodies.FieldGeodeticPoint;
  25. import org.orekit.bodies.GeodeticPoint;
  26. import org.orekit.models.earth.weather.ConstantPressureTemperatureHumidityProvider;
  27. import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
  28. import org.orekit.models.earth.weather.PressureTemperatureHumidity;
  29. import org.orekit.models.earth.weather.PressureTemperatureHumidityProvider;
  30. import org.orekit.models.earth.weather.water.CIPM2007;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.time.FieldAbsoluteDate;
  33. import org.orekit.utils.FieldTrackingCoordinates;
  34. import org.orekit.utils.ParameterDriver;
  35. import org.orekit.utils.TrackingCoordinates;
  36. import org.orekit.utils.units.Unit;
  37. import org.orekit.utils.units.UnitsConverter;

  38. /** The Mendes - Pavlis tropospheric delay model for optical techniques.
  39. * It is valid for a wide range of wavelengths from 0.355µm to 1.064µm (Mendes and Pavlis, 2003)
  40. *
  41. * @see "Mendes, V. B., & Pavlis, E. C. (2004). High‐accuracy zenith delay prediction at
  42. *      optical wavelengths. Geophysical Research Letters, 31(14)."
  43. *
  44. * @see "Petit, G. and Luzum, B. (eds.), IERS Conventions (2010),
  45. *      IERS Technical Note No. 36, BKG (2010)"
  46. *
  47. * @author Bryan Cazabonne
  48. */
  49. public class MendesPavlisModel implements TroposphericModel, TroposphereMappingFunction {

  50.     /** Coefficients for the dispersion equation for the hydrostatic component [µm<sup>-2</sup>]. */
  51.     private static final double[] K_COEFFICIENTS = {
  52.         238.0185, 19990.975, 57.362, 579.55174
  53.     };

  54.     /** Coefficients for the dispersion equation for the non-hydrostatic component. */
  55.     private static final double[] W_COEFFICIENTS = {
  56.         295.235, 2.6422, -0.032380, 0.004028
  57.     };

  58.     /** Coefficients for the mapping function. */
  59.     private static final double[][] A_COEFFICIENTS = {
  60.         {12100.8e-7, 1729.5e-9, 319.1e-7, -1847.8e-11},
  61.         {30496.5e-7, 234.4e-8, -103.5e-6, -185.6e-10},
  62.         {6877.7e-5, 197.2e-7, -345.8e-5, 106.0e-9}
  63.     };

  64.     /** Carbon dioxyde content (IAG recommendations). */
  65.     private static final double C02 = 0.99995995;

  66.     /** Dispersion equation for the hydrostatic component. */
  67.     private final double fLambdaH;

  68.     /** Dispersion equation for the non-hydrostatic component. */
  69.     private final double fLambdaNH;

  70.     /** Provider for pressure, temperature and humidity. */
  71.     private final PressureTemperatureHumidityProvider pthProvider;

  72.     /** Create a new Mendes-Pavlis model for the troposphere.
  73.      * @param pthProvider provider for atmospheric pressure, temperature and humidity at the station
  74.      * @param lambda laser wavelength
  75.      * @param lambdaUnits units in which {@code lambda} is given
  76.      * @see TroposphericModelUtils#MICRO_M
  77.      * @see TroposphericModelUtils#NANO_M
  78.      * @since 12.1
  79.      * */
  80.     public MendesPavlisModel(final PressureTemperatureHumidityProvider pthProvider,
  81.                              final double lambda, final Unit lambdaUnits) {
  82.         this.pthProvider = pthProvider;

  83.         // Dispersion equation for the hydrostatic component
  84.         final double lambdaMicrometer = new UnitsConverter(lambdaUnits, TroposphericModelUtils.MICRO_M).convert(lambda);
  85.         final double sigma  = 1.0 / lambdaMicrometer;
  86.         final double sigma2 = sigma * sigma;
  87.         final double coef1  = K_COEFFICIENTS[0] + sigma2;
  88.         final double coef2  = K_COEFFICIENTS[0] - sigma2;
  89.         final double coef3  = K_COEFFICIENTS[2] + sigma2;
  90.         final double coef4  = K_COEFFICIENTS[2] - sigma2;
  91.         final double frac1 = coef1 / (coef2 * coef2);
  92.         final double frac2 = coef3 / (coef4 * coef4);
  93.         fLambdaH = 0.01 * (K_COEFFICIENTS[1] * frac1 + K_COEFFICIENTS[3] * frac2) * C02;

  94.         // Dispersion equation for the non-hydrostatic component
  95.         final double sigma4 = sigma2 * sigma2;
  96.         final double sigma6 = sigma4 * sigma2;
  97.         final double w1s2  = 3 * W_COEFFICIENTS[1] * sigma2;
  98.         final double w2s4  = 5 * W_COEFFICIENTS[2] * sigma4;
  99.         final double w3s6  = 7 * W_COEFFICIENTS[3] * sigma6;

  100.         fLambdaNH = 0.003101 * (W_COEFFICIENTS[0] + w1s2 + w2s4 + w3s6);

  101.     }

  102.     /** Create a new Mendes-Pavlis model using a standard atmosphere model.
  103.      *
  104.      * <ul>
  105.      * <li>altitude: 0m</li>
  106.      * <li>temperature: 18 degree Celsius</li>
  107.      * <li>pressure: 1013.25 hPa</li>
  108.      * <li>humidity: 50%</li>
  109.      * </ul>
  110.      *
  111.      * @param lambda laser wavelength, µm
  112.      * @param lambdaUnits units in which {@code lambda} is given
  113.      * @return a Mendes-Pavlis model with standard environmental values
  114.      * @see TroposphericModelUtils#MICRO_M
  115.      * @see TroposphericModelUtils#NANO_M
  116.      * @since 12.1
  117.      */
  118.     public static MendesPavlisModel getStandardModel(final double lambda, final Unit lambdaUnits) {
  119.         final double h  = 0;
  120.         final double p  = TroposphericModelUtils.HECTO_PASCAL.toSI(1013.25);
  121.         final double t  = 273.15 + 18;
  122.         final double rh = 0.5;
  123.         final PressureTemperatureHumidity pth = new PressureTemperatureHumidity(h, p, t,
  124.                                                                                 new CIPM2007().waterVaporPressure(p, t, rh),
  125.                                                                                 Double.NaN,
  126.                                                                                 Double.NaN);
  127.         return new MendesPavlisModel(new ConstantPressureTemperatureHumidityProvider(pth),
  128.                                      lambda, lambdaUnits);
  129.     }

  130.     /** {@inheritDoc} */
  131.     @Override
  132.     public TroposphericDelay pathDelay(final TrackingCoordinates trackingCoordinates,
  133.                                        final GeodeticPoint point,
  134.                                        final double[] parameters, final AbsoluteDate date) {
  135.         // Zenith delay
  136.         final double[] zenithDelay = computeZenithDelay(point, date);
  137.         // Mapping function
  138.         final double[] mappingFunction = mappingFactors(trackingCoordinates, point, date);
  139.         // Tropospheric path delay
  140.         return new TroposphericDelay(zenithDelay[0],
  141.                                      zenithDelay[1],
  142.                                      zenithDelay[0] * mappingFunction[0],
  143.                                      zenithDelay[1] * mappingFunction[1]);
  144.     }

  145.     /** {@inheritDoc} */
  146.     @Override
  147.     public <T extends CalculusFieldElement<T>> FieldTroposphericDelay<T> pathDelay(final FieldTrackingCoordinates<T> trackingCoordinates,
  148.                                                                                    final FieldGeodeticPoint<T> point,
  149.                                                                                    final T[] parameters, final FieldAbsoluteDate<T> date) {
  150.         // Zenith delay
  151.         final T[] zenithDelay = computeZenithDelay(point, date);
  152.         // Mapping function
  153.         final T[] mappingFunction = mappingFactors(trackingCoordinates, point, date);
  154.         // Tropospheric path delay
  155.         return new FieldTroposphericDelay<>(zenithDelay[0],
  156.                                             zenithDelay[1],
  157.                                             zenithDelay[0].multiply(mappingFunction[0]),
  158.                                             zenithDelay[1].multiply(mappingFunction[1]));
  159.     }

  160.     /**
  161.      * This method allows the  computation of the zenith hydrostatic and
  162.      * zenith wet delay. The resulting element is an array having the following form:
  163.      * <ul>
  164.      * <li>double[0] = D<sub>hz</sub> → zenith hydrostatic delay
  165.      * <li>double[1] = D<sub>wz</sub> → zenith wet delay
  166.      * </ul>
  167.      *
  168.      * @param point station location
  169.      * @param date  current date
  170.      * @return a two components array containing the zenith hydrostatic and wet delays.
  171.      */
  172.     public double[] computeZenithDelay(final GeodeticPoint point, final AbsoluteDate date) {

  173.         final PressureTemperatureHumidity pth = pthProvider.getWeatherParameters(point, date);
  174.         final double fsite   = getSiteFunctionValue(point);

  175.         // Array for zenith delay
  176.         final double[] delay = new double[2];

  177.         // Zenith delay for the hydrostatic component
  178.         // beware since version 12.1 pressure is in Pa and not in hPa, hence the scaling has changed
  179.         delay[0] = pth.getPressure() * 0.00002416579 * (fLambdaH / fsite);

  180.         // Zenith delay for the non-hydrostatic component
  181.         // beware since version 12.1 e0 is in Pa and not in hPa, hence the scaling has changed
  182.         delay[1] = 0.000001 * (5.316 * fLambdaNH - 3.759 * fLambdaH) * (pth.getWaterVaporPressure() / fsite);

  183.         return delay;
  184.     }

  185.     /**
  186.      * This method allows the  computation of the zenith hydrostatic and
  187.      * zenith wet delay. The resulting element is an array having the following form:
  188.      * <ul>
  189.      * <li>T[0] = D<sub>hz</sub> → zenith hydrostatic delay
  190.      * <li>T[1] = D<sub>wz</sub> → zenith wet delay
  191.      * </ul>
  192.      *
  193.      * @param <T>   type of the elements
  194.      * @param point station location
  195.      * @param date  current date
  196.      * @return a two components array containing the zenith hydrostatic and wet delays.
  197.      */
  198.     public <T extends CalculusFieldElement<T>> T[] computeZenithDelay(final FieldGeodeticPoint<T> point,
  199.                                                                       final FieldAbsoluteDate<T> date) {

  200.         final FieldPressureTemperatureHumidity<T> pth = pthProvider.getWeatherParameters(point, date);

  201.         final T fsite   = getSiteFunctionValue(point);

  202.         // Array for zenith delay
  203.         final T[] delay = MathArrays.buildArray(date.getField(), 2);

  204.         // Zenith delay for the hydrostatic component
  205.         // beware since version 12.1 pressure is in Pa and not in hPa, hence the scaling has changed
  206.         delay[0] =  pth.getPressure().multiply(0.00002416579).multiply(fLambdaH).divide(fsite);

  207.         // Zenith delay for the non-hydrostatic component
  208.         // beware since version 12.1 e0 is in Pa and not in hPa, hence the scaling has changed
  209.         delay[1] = pth.getWaterVaporPressure().divide(fsite).
  210.                    multiply(0.000001 * (5.316 * fLambdaNH - 3.759 * fLambdaH));

  211.         return delay;

  212.     }

  213.     /** With the Mendes Pavlis tropospheric model, the mapping
  214.      * function is not split into hydrostatic and wet component.
  215.      * <p>
  216.      * Therefore, the two components of the resulting array are equals.
  217.      * <ul>
  218.      * <li>double[0] = m(e) → total mapping function
  219.      * <li>double[1] = m(e) → total mapping function
  220.      * </ul>
  221.      * <p>
  222.      * The total delay will thus be computed as:<br>
  223.      * δ = D<sub>hz</sub> * m(e) + D<sub>wz</sub> * m(e)<br>
  224.      * δ = (D<sub>hz</sub> + D<sub>wz</sub>) * m(e) = δ<sub>z</sub> * m(e)
  225.      */
  226.     @Override
  227.     public double[] mappingFactors(final TrackingCoordinates trackingCoordinates,
  228.                                    final GeodeticPoint point,
  229.                                    final AbsoluteDate date) {
  230.         final double sinE = FastMath.sin(trackingCoordinates.getElevation());

  231.         final PressureTemperatureHumidity pth = pthProvider.getWeatherParameters(point, date);
  232.         final double T2degree = pth.getTemperature() - 273.15;

  233.         // Mapping function coefficients
  234.         final double a1 = computeMFCoeffient(A_COEFFICIENTS[0][0], A_COEFFICIENTS[0][1],
  235.                                              A_COEFFICIENTS[0][2], A_COEFFICIENTS[0][3],
  236.                                              T2degree, point);
  237.         final double a2 = computeMFCoeffient(A_COEFFICIENTS[1][0], A_COEFFICIENTS[1][1],
  238.                                              A_COEFFICIENTS[1][2], A_COEFFICIENTS[1][3],
  239.                                              T2degree, point);
  240.         final double a3 = computeMFCoeffient(A_COEFFICIENTS[2][0], A_COEFFICIENTS[2][1],
  241.                                              A_COEFFICIENTS[2][2], A_COEFFICIENTS[2][3],
  242.                                              T2degree, point);

  243.         // Numerator
  244.         final double numMP = 1 + a1 / (1 + a2 / (1 + a3));
  245.         // Denominator
  246.         final double denMP = sinE + a1 / (sinE + a2 / (sinE + a3));

  247.         final double factor = numMP / denMP;

  248.         return new double[] {
  249.             factor,
  250.             factor
  251.         };
  252.     }

  253.     /** With the Mendes Pavlis tropospheric model, the mapping
  254.      * function is not split into hydrostatic and wet component.
  255.      * <p>
  256.      * Therefore, the two components of the resulting array are equals.
  257.      * <ul>
  258.      * <li>double[0] = m(e) → total mapping function
  259.      * <li>double[1] = m(e) → total mapping function
  260.      * </ul>
  261.      * <p>
  262.      * The total delay will thus be computed as:<br>
  263.      * δ = D<sub>hz</sub> * m(e) + D<sub>wz</sub> * m(e)<br>
  264.      * δ = (D<sub>hz</sub> + D<sub>wz</sub>) * m(e) = δ<sub>z</sub> * m(e)
  265.      */
  266.     @Override
  267.     public <T extends CalculusFieldElement<T>> T[] mappingFactors(final FieldTrackingCoordinates<T> trackingCoordinates,
  268.                                                                   final FieldGeodeticPoint<T> point,
  269.                                                                   final FieldAbsoluteDate<T> date) {
  270.         final Field<T> field = date.getField();

  271.         final T sinE = FastMath.sin(trackingCoordinates.getElevation());

  272.         final FieldPressureTemperatureHumidity<T> pth = pthProvider.getWeatherParameters(point, date);
  273.         final T T2degree = pth.getTemperature().subtract(273.15);

  274.         // Mapping function coefficients
  275.         final T a1 = computeMFCoeffient(A_COEFFICIENTS[0][0], A_COEFFICIENTS[0][1],
  276.                                         A_COEFFICIENTS[0][2], A_COEFFICIENTS[0][3],
  277.                                         T2degree, point);
  278.         final T a2 = computeMFCoeffient(A_COEFFICIENTS[1][0], A_COEFFICIENTS[1][1],
  279.                                         A_COEFFICIENTS[1][2], A_COEFFICIENTS[1][3],
  280.                                         T2degree, point);
  281.         final T a3 = computeMFCoeffient(A_COEFFICIENTS[2][0], A_COEFFICIENTS[2][1],
  282.                                         A_COEFFICIENTS[2][2], A_COEFFICIENTS[2][3],
  283.                                         T2degree, point);

  284.         // Numerator
  285.         final T numMP = a1.divide(a2.divide(a3.add(1.0)).add(1.0)).add(1.0);
  286.         // Denominator
  287.         final T denMP = a1.divide(a2.divide(a3.add(sinE)).add(sinE)).add(sinE);

  288.         final T factor = numMP.divide(denMP);

  289.         final T[] mapping = MathArrays.buildArray(field, 2);
  290.         mapping[0] = factor;
  291.         mapping[1] = factor;

  292.         return mapping;
  293.     }

  294.     /** {@inheritDoc} */
  295.     @Override
  296.     public List<ParameterDriver> getParametersDrivers() {
  297.         return Collections.emptyList();
  298.     }

  299.     /** Get the site parameter.
  300.      *
  301.      * @param point station location
  302.      * @return the site parameter.
  303.      */
  304.     private double getSiteFunctionValue(final GeodeticPoint point) {
  305.         return 1. - 0.00266 * FastMath.cos(2. * point.getLatitude()) - 0.00000028 * point.getAltitude();
  306.     }

  307.     /** Get the site parameter.
  308.      *
  309.      * @param <T> type of the elements
  310.      * @param point station location
  311.      * @return the site parameter.
  312.      */
  313.     private <T extends CalculusFieldElement<T>> T getSiteFunctionValue(final FieldGeodeticPoint<T> point) {
  314.         return FastMath.cos(point.getLatitude().multiply(2.)).multiply(0.00266).add(point.getAltitude().multiply(0.00000028)).negate().add(1.);
  315.     }

  316.     /** Compute the coefficients of the Mapping Function.
  317.      *
  318.      * @param t the temperature at the station site, °C
  319.      * @param a0 first coefficient
  320.      * @param a1 second coefficient
  321.      * @param a2 third coefficient
  322.      * @param a3 fourth coefficient
  323.      * @param point station location
  324.      * @return the value of the coefficient
  325.      */
  326.     private double computeMFCoeffient(final double a0, final double a1, final double a2, final double a3,
  327.                                       final double t, final GeodeticPoint point) {
  328.         return a0 + a1 * t + a2 * FastMath.cos(point.getLatitude()) + a3 * point.getAltitude();
  329.     }

  330.     /** Compute the coefficients of the Mapping Function.
  331.      *
  332.      * @param <T> type of the elements
  333.      * @param t the temperature at the station site, °C
  334.      * @param a0 first coefficient
  335.      * @param a1 second coefficient
  336.      * @param a2 third coefficient
  337.      * @param a3 fourth coefficient
  338.      * @param point station location
  339.      * @return the value of the coefficient
  340.      */
  341.     private <T extends CalculusFieldElement<T>> T computeMFCoeffient(final double a0, final double a1, final double a2, final double a3,
  342.                                                                      final T t, final FieldGeodeticPoint<T> point) {
  343.         return point.getAltitude().multiply(a3).add(FastMath.cos(point.getLatitude()).multiply(a2)).add(t.multiply(a1).add(a0));
  344.     }

  345. }