ITURP834MappingFunction.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.analysis.interpolation.BilinearInterpolatingFunction;
  20. import org.hipparchus.util.FastMath;
  21. import org.hipparchus.util.FieldSinCos;
  22. import org.hipparchus.util.MathArrays;
  23. import org.hipparchus.util.MathUtils;
  24. import org.hipparchus.util.SinCos;
  25. import org.orekit.bodies.FieldGeodeticPoint;
  26. import org.orekit.bodies.GeodeticPoint;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.models.earth.troposphere.TroposphereMappingFunction;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.FieldAbsoluteDate;
  32. import org.orekit.time.TimeScale;
  33. import org.orekit.utils.FieldTrackingCoordinates;
  34. import org.orekit.utils.TrackingCoordinates;

  35. import java.io.BufferedReader;
  36. import java.io.IOException;
  37. import java.io.InputStream;
  38. import java.io.InputStreamReader;
  39. import java.nio.charset.StandardCharsets;
  40. import java.util.regex.Pattern;

  41. /** ITU-R P.834 mapping function.
  42.  * @see ITURP834PathDelay
  43.  * @see ITURP834WeatherParametersProvider
  44.  * @author Luc Maisonobe
  45.  * @see <a href="https://www.itu.int/rec/R-REC-P.834/en">P.834 : Effects of tropospheric refraction on radiowave propagation</a>
  46.  * @since 13.0
  47.  */
  48. public class ITURP834MappingFunction implements TroposphereMappingFunction {

  49.     /** Splitter for fields in lines. */
  50.     private static final Pattern SPLITTER = Pattern.compile("\\s+");

  51.     /** Name of data file. */
  52.     private static final String MAPPING_FUNCTION_NAME = "/assets/org/orekit/ITU-R-P.834/p834_mf_coeff_v1.txt";

  53.     /** Minimum latitude, including extra margin for dealing with boundaries (degrees). */
  54.     private static final double MIN_LAT = -92.5;

  55.     /** Maximum latitude, including extra margin for dealing with boundaries (degrees). */
  56.     private static final double MAX_LAT =  92.5;

  57.     /** Grid step in latitude (degrees). */
  58.     private static final double STEP_LAT =  5.0;

  59.     /** Minimum longitude, including extra margin for dealing with boundaries (degrees). */
  60.     private static final double MIN_LON = -182.5;

  61.     /** Maximum longitude, including extra margin for dealing with boundaries (degrees). */
  62.     private static final double MAX_LON =  182.5;

  63.     /** Grid step in longitude (degrees). */
  64.     private static final double STEP_LON =  5.0;

  65.     /** Second coefficient for hydrostatic component. */
  66.     private static final double BH = 0.0029;

  67.     /** Constants for third coefficient for hydrostatic component, Northern hemisphere. */
  68.     private static final double[] CH_NORTH = { 0.062, 0.001, 0.005, 0.0 };

  69.     /** Constants for third coefficient for hydrostatic component, Southern hemisphere. */
  70.     private static final double[] CH_SOUTH = { 0.062, 0.002, 0.007, FastMath.PI };

  71.     /** Reference day of year for third coefficient for hydrostatic component. */
  72.     private static final double REF_DOY = 28;

  73.     /** Year length (in days). */
  74.     private static final double YEAR = 365.25;

  75.     /** Second coefficient for wet component. */
  76.     private static final double BW = 0.00146;

  77.     /** Third coefficient for wet component. */
  78.     private static final double CW = 0.04391;

  79.     /** Global factor to apply to first coefficient. */
  80.     private static final double FACTOR = 1.0e-3;

  81.     /** Interpolator for constant hydrostatic coefficient. */
  82.     private static final BilinearInterpolatingFunction A0H;

  83.     /** Interpolator for annual cosine hydrostatic coefficient. */
  84.     private static final BilinearInterpolatingFunction A1H;

  85.     /** Interpolator for annual sine hydrostatic coefficient. */
  86.     private static final BilinearInterpolatingFunction B1H;

  87.     /** Interpolator for semi-annual cosine hydrostatic coefficient. */
  88.     private static final BilinearInterpolatingFunction A2H;

  89.     /** Interpolator for semin-annual sine hydrostatic coefficient. */
  90.     private static final BilinearInterpolatingFunction B2H;

  91.     /** Interpolator for constant wet coefficient. */
  92.     private static final BilinearInterpolatingFunction A0W;

  93.     /** Interpolator for annual cosine wet coefficient. */
  94.     private static final BilinearInterpolatingFunction A1W;

  95.     /** Interpolator for annual sine wet coefficient. */
  96.     private static final BilinearInterpolatingFunction B1W;

  97.     /** Interpolator for semi-annual cosine wet coefficient. */
  98.     private static final BilinearInterpolatingFunction A2W;

  99.     /** Interpolator for semin-annual sine wet coefficient. */
  100.     private static final BilinearInterpolatingFunction B2W;

  101.     // load model data
  102.     static {

  103.         // create the various tables
  104.         // we add extra lines and columns to the official files, for dealing with boundaries
  105.         final double[]   longitudes = interpolationPoints(MIN_LON, MAX_LON, STEP_LON);
  106.         final double[]   latitudes  = interpolationPoints(MIN_LAT, MAX_LAT, STEP_LAT);
  107.         final double[][] a0h        = new double[longitudes.length][latitudes.length];
  108.         final double[][] a1h        = new double[longitudes.length][latitudes.length];
  109.         final double[][] b1h        = new double[longitudes.length][latitudes.length];
  110.         final double[][] a2h        = new double[longitudes.length][latitudes.length];
  111.         final double[][] b2h        = new double[longitudes.length][latitudes.length];
  112.         final double[][] a0w        = new double[longitudes.length][latitudes.length];
  113.         final double[][] a1w        = new double[longitudes.length][latitudes.length];
  114.         final double[][] b1w        = new double[longitudes.length][latitudes.length];
  115.         final double[][] a2w        = new double[longitudes.length][latitudes.length];
  116.         final double[][] b2w        = new double[longitudes.length][latitudes.length];

  117.         try (InputStream       is     = ITURP834MappingFunction.class.getResourceAsStream(MAPPING_FUNCTION_NAME);
  118.              InputStreamReader isr    = is  == null ? null : new InputStreamReader(is, StandardCharsets.UTF_8);
  119.              BufferedReader    reader = isr == null ? null : new BufferedReader(isr)) {
  120.             if (reader == null) {
  121.                 // this should never happen with embedded data
  122.                 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, MAPPING_FUNCTION_NAME);
  123.             }
  124.             int lineNumber = 0;
  125.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  126.                 ++lineNumber;
  127.                 final String[] fields = SPLITTER.split(line.trim());
  128.                 if (fields.length != 12) {
  129.                     // this should never happen with the embedded data
  130.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  131.                                               lineNumber, MAPPING_FUNCTION_NAME, line);
  132.                 }

  133.                 // parse the fields
  134.                 final double[] numericFields = new double[fields.length];
  135.                 for (int i = 0; i < fields.length; ++i) {
  136.                     numericFields[i] = Double.parseDouble(fields[i]);
  137.                 }

  138.                 // find indices in our extended grid
  139.                 final int longitudeIndex = (int) FastMath.rint((numericFields[1] - MIN_LON) / STEP_LON);
  140.                 final int latitudeIndex  = (int) FastMath.rint((numericFields[0] - MIN_LAT) / STEP_LAT);

  141.                 // fill-in tables
  142.                 a0h[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 2];
  143.                 a1h[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 3];
  144.                 b1h[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 4];
  145.                 a2h[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 5];
  146.                 b2h[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 6];
  147.                 a0w[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 7];
  148.                 a1w[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 8];
  149.                 b1w[longitudeIndex][latitudeIndex] = FACTOR * numericFields[ 9];
  150.                 a2w[longitudeIndex][latitudeIndex] = FACTOR * numericFields[10];
  151.                 b2w[longitudeIndex][latitudeIndex] = FACTOR * numericFields[11];

  152.             }

  153.             // extend tables in latitude to cover poles
  154.             for (int i = 1; i < longitudes.length - 1; ++i) {
  155.                 a0h[i][0]                    = a0h[i][1];
  156.                 a0h[i][latitudes.length - 1] = a0h[i][latitudes.length - 2];
  157.                 a1h[i][0]                    = a1h[i][1];
  158.                 a1h[i][latitudes.length - 1] = a1h[i][latitudes.length - 2];
  159.                 b1h[i][0]                    = b1h[i][1];
  160.                 b1h[i][latitudes.length - 1] = b1h[i][latitudes.length - 2];
  161.                 a2h[i][0]                    = a2h[i][1];
  162.                 a2h[i][latitudes.length - 1] = a2h[i][latitudes.length - 2];
  163.                 b2h[i][0]                    = b2h[i][1];
  164.                 b2h[i][latitudes.length - 1] = b2h[i][latitudes.length - 2];
  165.                 a0w[i][0]                    = a0w[i][1];
  166.                 a0w[i][latitudes.length - 1] = a0w[i][latitudes.length - 2];
  167.                 a1w[i][0]                    = a1w[i][1];
  168.                 a1w[i][latitudes.length - 1] = a1w[i][latitudes.length - 2];
  169.                 b1w[i][0]                    = b1w[i][1];
  170.                 b1w[i][latitudes.length - 1] = b1w[i][latitudes.length - 2];
  171.                 a2w[i][0]                    = a2w[i][1];
  172.                 a2w[i][latitudes.length - 1] = a2w[i][latitudes.length - 2];
  173.                 b2w[i][0]                    = b2w[i][1];
  174.                 b2w[i][latitudes.length - 1] = b2w[i][latitudes.length - 2];
  175.             }

  176.             // extend tables in longitude to cover anti-meridian
  177.             for (int j = 0; j < latitudes.length; ++j) {
  178.                 a0h[0][j]                     = a0h[longitudes.length - 2][j];
  179.                 a0h[longitudes.length - 1][j] = a0h[1][j];
  180.                 a1h[0][j]                     = a1h[longitudes.length - 2][j];
  181.                 a1h[longitudes.length - 1][j] = a1h[1][j];
  182.                 b1h[0][j]                     = b1h[longitudes.length - 2][j];
  183.                 b1h[longitudes.length - 1][j] = b1h[1][j];
  184.                 a2h[0][j]                     = a2h[longitudes.length - 2][j];
  185.                 a2h[longitudes.length - 1][j] = a2h[1][j];
  186.                 b2h[0][j]                     = b2h[longitudes.length - 2][j];
  187.                 b2h[longitudes.length - 1][j] = b2h[1][j];
  188.                 a0w[0][j]                     = a0w[longitudes.length - 2][j];
  189.                 a0w[longitudes.length - 1][j] = a0w[1][j];
  190.                 a1w[0][j]                     = a1w[longitudes.length - 2][j];
  191.                 a1w[longitudes.length - 1][j] = a1w[1][j];
  192.                 b1w[0][j]                     = b1w[longitudes.length - 2][j];
  193.                 b1w[longitudes.length - 1][j] = b1w[1][j];
  194.                 a2w[0][j]                     = a2w[longitudes.length - 2][j];
  195.                 a2w[longitudes.length - 1][j] = a2w[1][j];
  196.                 b2w[0][j]                     = b2w[longitudes.length - 2][j];
  197.                 b2w[longitudes.length - 1][j] = b2w[1][j];
  198.             }

  199.             // build interpolators
  200.             A0H = new BilinearInterpolatingFunction(longitudes, latitudes, a0h);
  201.             A1H = new BilinearInterpolatingFunction(longitudes, latitudes, a1h);
  202.             B1H = new BilinearInterpolatingFunction(longitudes, latitudes, b1h);
  203.             A2H = new BilinearInterpolatingFunction(longitudes, latitudes, a2h);
  204.             B2H = new BilinearInterpolatingFunction(longitudes, latitudes, b2h);
  205.             A0W = new BilinearInterpolatingFunction(longitudes, latitudes, a0w);
  206.             A1W = new BilinearInterpolatingFunction(longitudes, latitudes, a1w);
  207.             B1W = new BilinearInterpolatingFunction(longitudes, latitudes, b1w);
  208.             A2W = new BilinearInterpolatingFunction(longitudes, latitudes, a2w);
  209.             B2W = new BilinearInterpolatingFunction(longitudes, latitudes, b2w);

  210.         } catch (IOException ioe) {
  211.             // this should never happen with the embedded data
  212.             throw new OrekitException(OrekitMessages.INTERNAL_ERROR, ioe);
  213.         }
  214.     }

  215.     /** UTC time scale. */
  216.     private final TimeScale utc;

  217.     /** Simple constructor.
  218.      * @param utc UTC time scale
  219.      */
  220.     public ITURP834MappingFunction(final TimeScale utc) {
  221.         this.utc = utc;
  222.     }

  223.     /** Create interpolation points coordinates.
  224.      * @param min min angle in degrees (included)
  225.      * @param max max angle in degrees (included)
  226.      * @param step step between points
  227.      * @return interpolation points coordinates (in radians)
  228.      */
  229.     private static double[] interpolationPoints(final double min, final double max, final double step) {
  230.         final double[] points = new double[(int) FastMath.rint((max - min) / step) + 1];
  231.         for (int i = 0; i < points.length; i++) {
  232.             points[i] = FastMath.toRadians(min + i * step);
  233.         }
  234.         return points;
  235.     }

  236.     @Override
  237.     public double[] mappingFactors(final TrackingCoordinates trackingCoordinates, final GeodeticPoint point,
  238.                                    final AbsoluteDate date) {

  239.         final double doy = date.getDayOfYear(utc);

  240.         // compute third dry coefficient
  241.         // equation 28c in ITU-R P.834 recommendation
  242.         final double[] c    = point.getLatitude() >= 0.0 ? CH_NORTH : CH_SOUTH;
  243.         final double   cosL = FastMath.cos(point.getLatitude());
  244.         final double   cosD = FastMath.cos((doy - REF_DOY) * MathUtils.TWO_PI / YEAR + c[3]);
  245.         final double   ch   = c[0] + ((cosD + 1) * c[2] * 0.5 + c[1]) * (1 - cosL);

  246.         // compute first coefficient
  247.         final SinCos sc1 = FastMath.sinCos(MathUtils.TWO_PI * doy / YEAR);
  248.         final SinCos sc2 = SinCos.sum(sc1, sc1);

  249.         // equation 28d in ITU-R P.834 recommendation
  250.         final double ah  = A0H.value(point.getLongitude(), point.getLatitude()) +
  251.                            A1H.value(point.getLongitude(), point.getLatitude()) * sc1.cos() +
  252.                            B1H.value(point.getLongitude(), point.getLatitude()) * sc1.sin() +
  253.                            A2H.value(point.getLongitude(), point.getLatitude()) * sc2.cos() +
  254.                            B2H.value(point.getLongitude(), point.getLatitude()) * sc2.sin();

  255.         // equation 28e in ITU-R P.834 recommendation
  256.         final double aw  = A0W.value(point.getLongitude(), point.getLatitude()) +
  257.                            A1W.value(point.getLongitude(), point.getLatitude()) * sc1.cos() +
  258.                            B1W.value(point.getLongitude(), point.getLatitude()) * sc1.sin() +
  259.                            A2W.value(point.getLongitude(), point.getLatitude()) * sc2.cos() +
  260.                            B2W.value(point.getLongitude(), point.getLatitude()) * sc2.sin();

  261.         // mapping function, equations 28a and 28b in ITU-R P.834 recommendation
  262.         final double sinTheta = FastMath.sin(trackingCoordinates.getElevation());
  263.         final double mh = (1 + ah / (1 + BH / (1 + ch))) /
  264.                           (sinTheta + ah / (sinTheta + BH / (sinTheta + ch)));
  265.         final double mw = (1 + aw / (1 + BW / (1 + CW))) /
  266.                           (sinTheta + aw / (sinTheta + BW / (sinTheta + CW)));

  267.         return new double[] {
  268.             mh, mw
  269.         };

  270.     }

  271.     @Override
  272.     public <T extends CalculusFieldElement<T>> T[] mappingFactors(final FieldTrackingCoordinates<T> trackingCoordinates,
  273.                                                                   final FieldGeodeticPoint<T> point,
  274.                                                                   final FieldAbsoluteDate<T> date) {

  275.         final T doy = date.getDayOfYear(utc);

  276.         // compute third dry coefficient
  277.         // equation 28c in ITU-R P.834 recommendation
  278.         final double[] c    = point.getLatitude().getReal() >= 0.0 ? CH_NORTH : CH_SOUTH;
  279.         final T        cosL = FastMath.cos(point.getLatitude());
  280.         final T        cosD = FastMath.cos(doy.subtract(REF_DOY).multiply(MathUtils.TWO_PI / YEAR).add(c[3]));
  281.         final T        ch   = cosD.add(1).multiply(c[2] * 0.5).add(c[1]).multiply(cosL.subtract(1).negate()).add(c[0]);

  282.         // compute first coefficient
  283.         final FieldSinCos<T> sc1 = FastMath.sinCos(doy.multiply(MathUtils.TWO_PI / YEAR));
  284.         final FieldSinCos<T> sc2 = FieldSinCos.sum(sc1, sc1);

  285.         // equation 28d in ITU-R P.834 recommendation
  286.         final T ah  =     A0H.value(point.getLongitude(), point.getLatitude()).
  287.                       add(A1H.value(point.getLongitude(), point.getLatitude()).multiply(sc1.cos())).
  288.                       add(B1H.value(point.getLongitude(), point.getLatitude()).multiply(sc1.sin())).
  289.                       add(A2H.value(point.getLongitude(), point.getLatitude()).multiply(sc2.cos())).
  290.                       add(B2H.value(point.getLongitude(), point.getLatitude()).multiply(sc2.sin()));

  291.         // equation 28e in ITU-R P.834 recommendation
  292.         final T aw  =     A0W.value(point.getLongitude(), point.getLatitude()).
  293.                       add(A1W.value(point.getLongitude(), point.getLatitude()).multiply(sc1.cos())).
  294.                       add(B1W.value(point.getLongitude(), point.getLatitude()).multiply(sc1.sin())).
  295.                       add(A2W.value(point.getLongitude(), point.getLatitude()).multiply(sc2.cos())).
  296.                       add(B2W.value(point.getLongitude(), point.getLatitude()).multiply(sc2.sin()));

  297.         // mapping function, equations 28a and 28b in ITU-R P.834 recommendation
  298.         final T sinTheta = FastMath.sin(trackingCoordinates.getElevation());
  299.         final T mh = ch.add(1).reciprocal().multiply(BH).add(1).reciprocal().multiply(ah).add(1).
  300.                      divide(ch.add(sinTheta).reciprocal().multiply(BH).add(sinTheta).reciprocal().multiply(ah).add(sinTheta));
  301.         final T mw = aw.divide(1 + BW / (1 + CW)).add(1).
  302.                      divide(sinTheta.add(CW).reciprocal().multiply(BW).add(sinTheta).reciprocal().multiply(aw).add(sinTheta));

  303.         final T[] m = MathArrays.buildArray(date.getField(), 2);
  304.         m[0] = mh;
  305.         m[1] = mw;
  306.         return m;

  307.     }

  308. }