ITURP834WeatherParametersProvider.java

  1. /* Copyright 2022-2025 Thales Alenia Space
  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.iturp834;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.bodies.FieldGeodeticPoint;
  21. import org.orekit.bodies.GeodeticPoint;
  22. import org.orekit.models.earth.troposphere.TroposphericModelUtils;
  23. import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
  24. import org.orekit.models.earth.weather.PressureTemperatureHumidity;
  25. import org.orekit.models.earth.weather.PressureTemperatureHumidityProvider;
  26. import org.orekit.time.AbsoluteDate;
  27. import org.orekit.time.FieldAbsoluteDate;
  28. import org.orekit.time.TimeScale;
  29. import org.orekit.utils.Constants;
  30. import org.orekit.utils.units.Unit;

  31. /** Provider for the ITU-R P.834 weather parameters.
  32.  * <p>
  33.  * This class implements the weather parameters part of the model,
  34.  * i.e. equations 27b to 27i in section 6 of the recommendation.
  35.  * </p>
  36.  * @see ITURP834PathDelay
  37.  * @see ITURP834MappingFunction
  38.  * @author Luc Maisonobe
  39.  * @see <a href="https://www.itu.int/rec/R-REC-P.834/en">P.834 : Effects of tropospheric refraction on radiowave propagation</a>
  40.  * @since 13.0
  41.  */
  42. public class ITURP834WeatherParametersProvider implements PressureTemperatureHumidityProvider {

  43.     /** Prefix fo air total pressure at the Earth surface. */
  44.     private static final String AIR_TOTAL_PRESSURE_PREFIX = "pres";

  45.     /** Prefix for water vapour partial pressure at the Earth surface. */
  46.     private static final String WATER_VAPOUR_PARTIAL_PRESSURE_PREFIX = "vapr";

  47.     /** Prefix for mean temperature of the water vapour column above the surface. */
  48.     private static final String MEAN_TEMPERATURE_PREFIX = "tmpm";

  49.     /** Prefix for vapour pressure decrease factor. */
  50.     private static final String VAPOUR_PRESSURE_DECREASE_FACTOR_PREFIX = "lamd";

  51.     /** Prefix for lapse rate of mean temperature of water vapour from Earth surface. */
  52.     private static final String LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR_PREFIX = "alfm";

  53.     /** Suffix for average data.*/
  54.     private static final String AVERAGE_SUFFIX = "_gd_a1.dat";

  55.     /** Suffix for seasonal fluctuation.*/
  56.     private static final String SEASONAL_SUFFIX = "_gd_a2.dat";

  57.     /** Suffix for day of minimum value. */
  58.     private static final String DAY_OF_MINIMUM_SUFFIX = "_gd_a3.dat";

  59.     /** Name of height reference level. */
  60.     private static final String AVERAGE_HEIGHT_REFERENCE_LEVEL_NAME = "hreflev.dat";

  61.     /** Molar gas constant (J/mol K). */
  62.     private static final double R = 8.314;

  63.     /** Dry air molar mass (kg/mol). */
  64.     private static final double MD = Unit.GRAM.toSI(28.9644);

  65.     /** Rd factor. **/
  66.     private static final double RD = R / MD;

  67.     /** Air total pressure at the Earth surface. */
  68.     private static final SeasonalGrid AIR_TOTAL_PRESSURE;

  69.     /** Water vapour partial pressure at the Earth surface. */
  70.     private static final SeasonalGrid WATER_VAPOUR_PARTIAL_PRESSURE;

  71.     /** Mean temperature of the water vapour column above the surface. */
  72.     private static final SeasonalGrid MEAN_TEMPERATURE;

  73.     /** Vapour pressure decrease factor. */
  74.     private static final SeasonalGrid VAPOUR_PRESSURE_DECREASE_FACTOR;

  75.     /** Lapse rate of mean temperature of water vapour from Earth surface. */
  76.     private static final SeasonalGrid LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR;

  77.     /** Average height of reference level with respect to mean seal level. */
  78.     private static final ConstantGrid AVERAGE_HEIGHT_REFERENCE_LEVEL;

  79.     /** UTC time scale to evaluate time-dependent tables. */
  80.     private final TimeScale utc;

  81.     // load all model data files
  82.     static {

  83.         // load data files
  84.         AIR_TOTAL_PRESSURE =
  85.                 new SeasonalGrid(TroposphericModelUtils.HECTO_PASCAL,
  86.                                  AIR_TOTAL_PRESSURE_PREFIX + AVERAGE_SUFFIX,
  87.                                  AIR_TOTAL_PRESSURE_PREFIX + SEASONAL_SUFFIX,
  88.                                  AIR_TOTAL_PRESSURE_PREFIX + DAY_OF_MINIMUM_SUFFIX);
  89.         WATER_VAPOUR_PARTIAL_PRESSURE =
  90.                 new SeasonalGrid(TroposphericModelUtils.HECTO_PASCAL,
  91.                                  WATER_VAPOUR_PARTIAL_PRESSURE_PREFIX + AVERAGE_SUFFIX,
  92.                                  WATER_VAPOUR_PARTIAL_PRESSURE_PREFIX + SEASONAL_SUFFIX,
  93.                                  WATER_VAPOUR_PARTIAL_PRESSURE_PREFIX + DAY_OF_MINIMUM_SUFFIX);
  94.         MEAN_TEMPERATURE =
  95.                 new SeasonalGrid(Unit.NONE,
  96.                                  MEAN_TEMPERATURE_PREFIX + AVERAGE_SUFFIX,
  97.                                  MEAN_TEMPERATURE_PREFIX + SEASONAL_SUFFIX,
  98.                                  MEAN_TEMPERATURE_PREFIX + DAY_OF_MINIMUM_SUFFIX);
  99.         VAPOUR_PRESSURE_DECREASE_FACTOR =
  100.                 new SeasonalGrid(Unit.NONE,
  101.                                  VAPOUR_PRESSURE_DECREASE_FACTOR_PREFIX + AVERAGE_SUFFIX,
  102.                                  VAPOUR_PRESSURE_DECREASE_FACTOR_PREFIX + SEASONAL_SUFFIX,
  103.                                  VAPOUR_PRESSURE_DECREASE_FACTOR_PREFIX + DAY_OF_MINIMUM_SUFFIX);
  104.         LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR =
  105.                 new SeasonalGrid(Unit.parse("km⁻¹"),
  106.                                  LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR_PREFIX + AVERAGE_SUFFIX,
  107.                                  LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR_PREFIX + SEASONAL_SUFFIX,
  108.                                  LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR_PREFIX + DAY_OF_MINIMUM_SUFFIX);
  109.         AVERAGE_HEIGHT_REFERENCE_LEVEL = new ConstantGrid(Unit.METRE, AVERAGE_HEIGHT_REFERENCE_LEVEL_NAME);

  110.     }

  111.     /** Simple constructor.
  112.      * @param utc UTC time scale to evaluate time-dependent tables
  113.      */
  114.     public ITURP834WeatherParametersProvider(final TimeScale utc) {
  115.         this.utc = utc;
  116.     }

  117.     /** {@inheritDoc} */
  118.     @Override
  119.     public PressureTemperatureHumidity getWeatherParameters(final GeodeticPoint location, final AbsoluteDate date) {

  120.         // evaluate grid points for current date at reference height
  121.         final double   soy        = date.getDayOfYear(utc) * Constants.JULIAN_DAY;
  122.         final GridCell pHRef      = AIR_TOTAL_PRESSURE.getCell(location, soy);
  123.         final GridCell eHRef      = WATER_VAPOUR_PARTIAL_PRESSURE.getCell(location, soy);
  124.         final GridCell tmHRef     = MEAN_TEMPERATURE.getCell(location, soy);
  125.         final GridCell lambdaHRef = VAPOUR_PRESSURE_DECREASE_FACTOR.getCell(location, soy);
  126.         final GridCell alphaHRef  = LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR.getCell(location, soy);
  127.         final GridCell hRef       = AVERAGE_HEIGHT_REFERENCE_LEVEL.getCell(location, soy);
  128.         final GridCell g          = Gravity.getGravityAtSurface(location);

  129.         // mean temperature at current height, equation 27b
  130.         final GridCell tm    = new GridCell((ct, ca, ch) -> ct - ca * (location.getAltitude() - ch),
  131.                                             tmHRef, alphaHRef, hRef);

  132.         // lapse rate of air temperature, equation 27f, using Rd instead of Rd' because we have SI units
  133.         final GridCell fraction = new GridCell((cl, cg) -> (cl + 1) * cg / RD,
  134.                                                lambdaHRef, g);
  135.         final GridCell alpha = new GridCell((cf, ca) -> 0.5 * (cf - FastMath.sqrt(cf * (cf - 4 * ca))),
  136.                                             fraction, alphaHRef);

  137.         // temperature at Earth surface, equation 27e
  138.         final GridCell t     = new GridCell((ct, ca, cf) -> ct / (1 - ca / cf),
  139.                                             tmHRef, alpha, fraction);

  140.         // pressure at current height, equation 27c, using Rd instead of Rd' because we have SI units
  141.         final GridCell p = new GridCell((cp, ca, ch, ct, cg) ->
  142.                                         cp * FastMath.pow(1 - ca * (location.getAltitude() - ch) / ct, cg / (ca * RD)),
  143.                                         pHRef, alpha, hRef, t, g);

  144.         // water vapour pressure et current height, equation 27d
  145.         final GridCell e = new GridCell((ce, cp, cpr, cl) ->
  146.                                         ce * FastMath.pow(cp / cpr, cl + 1),
  147.                                         eHRef, p, pHRef, lambdaHRef);

  148.         // ITU-R P.834 recommendation calls for computing ΔLᵥ (both hydrostatic and wet versions)
  149.         // at the four corners of the cell using the weather parameters et each corner, and to perform
  150.         // bi-linear interpolation on the cell corners afterward (equations 27h and 27i)
  151.         // the TroposphericModel.pathDelay method, on the other hand, calls for a single weather parameter
  152.         // valid at the desired location, hence the bi-linear interpolation is performed on each weather
  153.         // parameter independently first, and they are combined afterward to compute ΔLᵥ
  154.         // in order to reconcile both approaches, i.e. implement properly ITU-R P.834 that applies
  155.         // 27h and 27i first and interpolates afterward despite using a single set of weather parameters,
  156.         // we set up scaling factors that compensate interpolation effect, by very slightly changing the
  157.         // pressure parameter (for hydrostatic ΔLᵥ) and the water vapour pressure parameter (for wet ΔLᵥ)
  158.         final GridCell gm                       = Gravity.getGravityAtAltitude(location);
  159.         final double   gmInterp                 = gm.evaluate();
  160.         final double   lambdaInterp             = lambdaHRef.evaluate();
  161.         final double   tmInterp                 = tm.evaluate();
  162.         final GridCell pOverG                   = new GridCell((cp, cg) -> cp / cg, p, gm);
  163.         final double   compensatedPressure      = pOverG.evaluate() * gmInterp;
  164.         final GridCell eOverGLT                 = new GridCell((ce, cg, cl, ctm) -> ce / (cg * (cl + 1) * ctm),
  165.                                                                e, gm, lambdaHRef, tm);
  166.         final double   compensatedWaterPressure = eOverGLT.evaluate() * gmInterp * (lambdaInterp + 1) * tmInterp;
  167.         return new PressureTemperatureHumidity(location.getAltitude(),
  168.                                                compensatedPressure,
  169.                                                t.evaluate(),
  170.                                                compensatedWaterPressure,
  171.                                                tmInterp,
  172.                                                lambdaInterp);

  173.     }

  174.     /** {@inheritDoc} */
  175.     @Override
  176.     public <T extends CalculusFieldElement<T>> FieldPressureTemperatureHumidity<T>
  177.         getWeatherParameters(final FieldGeodeticPoint<T> location, final FieldAbsoluteDate<T> date) {

  178.         // evaluate grid points for current date at reference height
  179.         final T                soy        = date.getDayOfYear(utc).multiply(Constants.JULIAN_DAY);
  180.         final FieldGridCell<T> pHRef      = AIR_TOTAL_PRESSURE.getCell(location, soy);
  181.         final FieldGridCell<T> eHRef      = WATER_VAPOUR_PARTIAL_PRESSURE.getCell(location, soy);
  182.         final FieldGridCell<T> tmHRef     = MEAN_TEMPERATURE.getCell(location, soy);
  183.         final FieldGridCell<T> lambdaHRef = VAPOUR_PRESSURE_DECREASE_FACTOR.getCell(location, soy);
  184.         final FieldGridCell<T> alphaHRef  = LAPSE_RATE_MEAN_TEMPERATURE_WATER_VAPOUR.getCell(location, soy);
  185.         final FieldGridCell<T> hRef       = AVERAGE_HEIGHT_REFERENCE_LEVEL.getCell(location, soy);
  186.         final FieldGridCell<T> g          = Gravity.getGravityAtSurface(location);

  187.         // mean temperature at current height, equation 27b
  188.         final FieldGridCell<T> tm    =
  189.             new FieldGridCell<>((ct, ca, ch) -> ct.subtract(ca.multiply(location.getAltitude().subtract(ch))),
  190.                                 tmHRef, alphaHRef, hRef);

  191.         // lapse rate of air temperature, equation 27f, using Rd instead of Rd' because we have SI units
  192.         final FieldGridCell<T> fraction =
  193.             new FieldGridCell<>((cl, cg) -> cl.add(1).multiply(cg).divide(RD),
  194.                                 lambdaHRef, g);
  195.         final FieldGridCell<T> alpha =
  196.             new FieldGridCell<>((cf, ca) -> cf.subtract(FastMath.sqrt(cf.multiply(cf.subtract(ca.multiply(4))))).multiply(0.5),
  197.                                 fraction, alphaHRef);

  198.         // temperature at Earth surface, equation 27e
  199.         final FieldGridCell<T> t     =
  200.             new FieldGridCell<>((ct, ca, cf) -> ct.divide(ca.divide(cf).subtract(1).negate()),
  201.                                 tmHRef, alpha, fraction);

  202.         // pressure at current height, equation 27c, using Rd instead of Rd' because we have SI units
  203.         final FieldGridCell<T> p =
  204.             new FieldGridCell<>((cp, ca, ch, ct, cg) ->
  205.                                 cp.multiply(FastMath.pow(ca.multiply(location.getAltitude().subtract(ch)).divide(ct).subtract(1).negate(),
  206.                                                          cg.divide(ca.multiply(RD)))),
  207.                                 pHRef, alpha, hRef, t, g);

  208.         // water vapour pressure et current height, equation 27d
  209.         final FieldGridCell<T> e =
  210.             new FieldGridCell<>((ce, cp, cpr, cl) ->
  211.                                 ce.multiply(FastMath.pow(cp.divide(cpr), cl.add(1))),
  212.                                 eHRef, p, pHRef, lambdaHRef);

  213.         // ITU-R P.834 recommendation calls for computing ΔLᵥ (both hydrostatic and wet versions)
  214.         // at the four corners of the cell using the weather parameters et each corner, and to perform
  215.         // bi-linear interpolation on the cell corners afterward (equations 27h and 27i)
  216.         // the TroposphericModel.pathDelay method, on the other hand, calls for a single weather parameter
  217.         // valid at the desired location, hence the bi-linear interpolation is performed on each weather
  218.         // parameter independently first, and they are combined afterward to compute ΔLᵥ
  219.         // in order to reconcile both approaches, i.e. implement properly ITU-R P.834 that applies
  220.         // 27h and 27i first and interpolates afterward despite using a single set of weather parameters,
  221.         // we set up scaling factors that compensate interpolation effect, by very slightly changing the
  222.         // pressure parameter (for hydrostatic ΔLᵥ) and the water vapour pressure parameter (for wet ΔLᵥ)
  223.         final FieldGridCell<T> gm           = Gravity.getGravityAtAltitude(location);
  224.         final T                gmInterp     = gm.evaluate();
  225.         final T                lambdaInterp = lambdaHRef.evaluate();
  226.         final T                tmInterp     = tm.evaluate();
  227.         final FieldGridCell<T> pOverG       = new FieldGridCell<>(CalculusFieldElement::divide, p, gm);
  228.         final T compensatedPressure         = pOverG.evaluate().multiply(gmInterp);
  229.         final FieldGridCell<T> eOverGLT     = new FieldGridCell<>((ce, cg, cl, ctm) ->
  230.                                                                   ce.divide(cg.multiply(cl.add(1)).multiply(ctm)),
  231.                                                                   e, gm, lambdaHRef, tm);
  232.         final T compensatedWaterPressure    = eOverGLT.evaluate().multiply(gmInterp).multiply(lambdaInterp.add(1)).multiply(tmInterp);
  233.         return new FieldPressureTemperatureHumidity<>(location.getAltitude(),
  234.                                                       compensatedPressure,
  235.                                                       t.evaluate(),
  236.                                                       compensatedWaterPressure,
  237.                                                       tmInterp,
  238.                                                       lambdaInterp);

  239.     }

  240. }