ViennaModelCoefficientsLoader.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.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.text.ParseException;
  24. import java.util.ArrayList;
  25. import java.util.Locale;
  26. import java.util.regex.Pattern;

  27. import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathUtils;
  30. import org.orekit.annotation.DefaultDataContext;
  31. import org.orekit.data.AbstractSelfFeedingLoader;
  32. import org.orekit.data.DataContext;
  33. import org.orekit.data.DataLoader;
  34. import org.orekit.data.DataProvidersManager;
  35. import org.orekit.errors.OrekitException;
  36. import org.orekit.errors.OrekitMessages;
  37. import org.orekit.time.DateTimeComponents;

  38. /** Loads Vienna tropospheric coefficients a given input stream.
  39.  * A stream contains, for a given day and a given hour, the hydrostatic and wet zenith delays
  40.  * and the ah and aw coefficients used for the computation of the mapping function.
  41.  * The coefficients are given with a time interval of 6 hours.
  42.  * <p>
  43.  * A bilinear interpolation is performed the case of the user initialize the latitude and the
  44.  * longitude with values that are not contained in the stream.
  45.  * </p>
  46.  * <p>
  47.  * The coefficients are obtained from <a href="http://vmf.geo.tuwien.ac.at/trop_products/GRID/">Vienna Mapping Functions Open Access Data</a>.
  48.  * Find more on the files at the <a href="http://vmf.geo.tuwien.ac.at/readme.txt">VMF Model Documentation</a>.
  49.  * <p>
  50.  * The files have to be extracted to UTF-8 text files before being read by this loader.
  51.  * <p>
  52.  * After extraction, it is assumed they are named VMFG_YYYYMMDD.Hhh for {@link ViennaOne} and VMF3_YYYYMMDD.Hhh {@link ViennaThree}.
  53.  * Where YYYY is the 4-digits year, MM the month, DD the day and hh the 2-digits hour.
  54.  *
  55.  * <p>
  56.  * The format is always the same, with and example shown below for VMF1 model.
  57.  * <p>
  58.  * Example:
  59.  * </p>
  60.  * <pre>
  61.  * ! Version:            1.0
  62.  * ! Source:             J. Boehm, TU Vienna (created: 2018-11-20)
  63.  * ! Data_types:         VMF1 (lat lon ah aw zhd zwd)
  64.  * ! Epoch:              2018 11 19 18 00  0.0
  65.  * ! Scale_factor:       1.e+00
  66.  * ! Range/resolution:   -90 90 0 360 2 2.5
  67.  * ! Comment:            http://vmf.geo.tuwien.ac.at/trop_products/GRID/2.5x2/VMF1/VMF1_OP/
  68.  *  90.0   0.0 0.00116059  0.00055318  2.3043  0.0096
  69.  *  90.0   2.5 0.00116059  0.00055318  2.3043  0.0096
  70.  *  90.0   5.0 0.00116059  0.00055318  2.3043  0.0096
  71.  *  90.0   7.5 0.00116059  0.00055318  2.3043  0.0096
  72.  *  90.0  10.0 0.00116059  0.00055318  2.3043  0.0096
  73.  *  90.0  12.5 0.00116059  0.00055318  2.3043  0.0096
  74.  *  90.0  15.0 0.00116059  0.00055318  2.3043  0.0096
  75.  *  90.0  17.5 0.00116059  0.00055318  2.3043  0.0096
  76.  *  90.0  20.0 0.00116059  0.00055318  2.3043  0.0096
  77.  *  90.0  22.5 0.00116059  0.00055318  2.3043  0.0096
  78.  *  90.0  25.0 0.00116059  0.00055318  2.3043  0.0096
  79.  *  90.0  27.5 0.00116059  0.00055318  2.3043  0.0096
  80.  * </pre>
  81.  *
  82.  * <p>It is not safe for multiple threads to share a single instance of this class.
  83.  *
  84.  * @author Bryan Cazabonne
  85.  */
  86. public class ViennaModelCoefficientsLoader extends AbstractSelfFeedingLoader
  87.         implements DataLoader {

  88.     /** Default supported files name pattern. */
  89.     public static final String DEFAULT_SUPPORTED_NAMES = "VMF*_\\\\*\\*\\.*H$";

  90.     /** Pattern for delimiting regular expressions. */
  91.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  92.     /** The hydrostatic and wet a coefficients loaded. */
  93.     private double[] coefficientsA;

  94.     /** The hydrostatic and wet zenith delays loaded. */
  95.     private double[] zenithDelay;

  96.     /** Geodetic site latitude, radians.*/
  97.     private final double latitude;

  98.     /** Geodetic site longitude, radians.*/
  99.     private final double longitude;

  100.     /** Vienna tropospheric model type.*/
  101.     private final ViennaModelType type;

  102.     /** Constructor with supported names given by user. This constructor uses the
  103.      * {@link DataContext#getDefault() default data context}.
  104.      *
  105.      * @param supportedNames Supported names
  106.      * @param latitude geodetic latitude of the station, in radians
  107.      * @param longitude geodetic latitude of the station, in radians
  108.      * @param type the type of Vienna tropospheric model (one or three)
  109.      * @see #ViennaModelCoefficientsLoader(String, double, double, ViennaModelType, DataProvidersManager)
  110.      */
  111.     @DefaultDataContext
  112.     public ViennaModelCoefficientsLoader(final String supportedNames, final double latitude,
  113.                                          final double longitude, final ViennaModelType type) {
  114.         this(supportedNames, latitude, longitude, type, DataContext.getDefault().getDataProvidersManager());
  115.     }

  116.     /**
  117.      * Constructor with supported names and source of mapping function files given by the
  118.      * user.
  119.      *
  120.      * @param supportedNames Supported names
  121.      * @param latitude       geodetic latitude of the station, in radians
  122.      * @param longitude      geodetic latitude of the station, in radians
  123.      * @param type           the type of Vienna tropospheric model (one or three)
  124.      * @param dataProvidersManager provides access to auxiliary files.
  125.      * @since 10.1
  126.      */
  127.     public ViennaModelCoefficientsLoader(final String supportedNames,
  128.                                          final double latitude,
  129.                                          final double longitude,
  130.                                          final ViennaModelType type,
  131.                                          final DataProvidersManager dataProvidersManager) {
  132.         super(supportedNames, dataProvidersManager);
  133.         this.coefficientsA  = null;
  134.         this.zenithDelay    = null;
  135.         this.type           = type;
  136.         this.latitude       = latitude;

  137.         // Normalize longitude between 0 and 2π
  138.         this.longitude = MathUtils.normalizeAngle(longitude, FastMath.PI);
  139.     }

  140.     /** Constructor with default supported names. This constructor uses the
  141.      * {@link DataContext#getDefault() default data context}.
  142.      *
  143.      * @param latitude geodetic latitude of the station, in radians
  144.      * @param longitude geodetic latitude of the station, in radians
  145.      * @param type the type of Vienna tropospheric model (one or three)
  146.      * @see #ViennaModelCoefficientsLoader(String, double, double, ViennaModelType, DataProvidersManager)
  147.      */
  148.     @DefaultDataContext
  149.     public ViennaModelCoefficientsLoader(final double latitude, final double longitude,
  150.                                          final ViennaModelType type) {
  151.         this(DEFAULT_SUPPORTED_NAMES, latitude, longitude, type);
  152.     }

  153.     /** Returns the a coefficients array.
  154.      * <ul>
  155.      * <li>double[0] = a<sub>h</sub>
  156.      * <li>double[1] = a<sub>w</sub>
  157.      * </ul>
  158.      * @return the a coefficients array
  159.      */
  160.     public double[] getA() {
  161.         return coefficientsA.clone();
  162.     }

  163.     /** Returns the zenith delay array.
  164.      * <ul>
  165.      * <li>double[0] = D<sub>hz</sub> → zenith hydrostatic delay
  166.      * <li>double[1] = D<sub>wz</sub> → zenith wet delay
  167.      * </ul>
  168.      * @return the zenith delay array
  169.      */
  170.     public double[] getZenithDelay() {
  171.         return zenithDelay.clone();
  172.     }

  173.     @Override
  174.     public String getSupportedNames() {
  175.         return super.getSupportedNames();
  176.     }

  177.     /** Load the data using supported names .
  178.      */
  179.     public void loadViennaCoefficients() {
  180.         feed(this);

  181.         // Throw an exception if ah, ah, zh or zw were not loaded properly
  182.         if (coefficientsA == null || zenithDelay == null) {
  183.             throw new OrekitException(OrekitMessages.VIENNA_ACOEF_OR_ZENITH_DELAY_NOT_LOADED,
  184.                     getSupportedNames());
  185.         }
  186.     }

  187.     /** Load the data for a given day.
  188.      * @param dateTimeComponents date and time component.
  189.      */
  190.     public void loadViennaCoefficients(final DateTimeComponents dateTimeComponents) {

  191.         // The files are named VMFG_YYYYMMDD.Hhh for Vienna-1 model and VMF3_YYYYMMDD.Hhh for Vienna-3 model.
  192.         // Where YYYY is the 4-digits year, MM the month, DD the day of the month and hh the 2-digits hour.
  193.         // Coefficients are only available for hh = 00 or 06 or 12 or 18.
  194.         final int    year        = dateTimeComponents.getDate().getYear();
  195.         final int    month       = dateTimeComponents.getDate().getMonth();
  196.         final int    day         = dateTimeComponents.getDate().getDay();
  197.         final int    hour        = dateTimeComponents.getTime().getHour();

  198.         // Correct month format is with 2-digits.
  199.         final String monthString;
  200.         if (month < 10) {
  201.             monthString = "0" + month;
  202.         } else {
  203.             monthString = String.valueOf(month);
  204.         }

  205.         // Correct day format is with 2-digits.
  206.         final String dayString;
  207.         if (day < 10) {
  208.             dayString = "0" + day;
  209.         } else {
  210.             dayString = String.valueOf(day);
  211.         }

  212.         // Correct hour format is with 2-digits.
  213.         final String hourString;
  214.         if (hour < 10) {
  215.             hourString = "0" + hour;
  216.         } else {
  217.             hourString = String.valueOf(hour);
  218.         }

  219.         // Name of the file is different between VMF1 and VMF3.
  220.         // For VMF1 it starts with "VMFG" whereas with VMF3 it starts with "VMF3"
  221.         switch (type) {
  222.             case VIENNA_ONE:
  223.                 setSupportedNames(String.format(Locale.US, "VMFG_%04d%2s%2s.H%2s",
  224.                                                 year, monthString, dayString, hourString));
  225.                 break;
  226.             case VIENNA_THREE:
  227.                 setSupportedNames(String.format(Locale.US, "VMF3_%04d%2s%2s.H%2s",
  228.                                                 year, monthString, dayString, hourString));
  229.                 break;
  230.             default:
  231.                 break;
  232.         }

  233.         try {
  234.             this.loadViennaCoefficients();
  235.         } catch (OrekitException oe) {
  236.             throw new OrekitException(oe,
  237.                                       OrekitMessages.VIENNA_ACOEF_OR_ZENITH_DELAY_NOT_AVAILABLE_FOR_DATE,
  238.                                       dateTimeComponents.toString());
  239.         }
  240.     }

  241.     @Override
  242.     public boolean stillAcceptsData() {
  243.         return true;
  244.     }

  245.     @Override
  246.     public void loadData(final InputStream input, final String name)
  247.         throws IOException, ParseException {

  248.         int lineNumber = 0;
  249.         String line = null;

  250.         // Initialize Lists
  251.         final ArrayList<Double> latitudes  = new ArrayList<>();
  252.         final ArrayList<Double> longitudes = new ArrayList<>();
  253.         final ArrayList<Double> ah         = new ArrayList<>();
  254.         final ArrayList<Double> aw         = new ArrayList<>();
  255.         final ArrayList<Double> zhd        = new ArrayList<>();
  256.         final ArrayList<Double> zwd        = new ArrayList<>();

  257.         // Open stream and parse data
  258.         try (BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

  259.             for (line = br.readLine(); line != null; line = br.readLine()) {
  260.                 ++lineNumber;
  261.                 line = line.trim();

  262.                 // Fill latitudes and longitudes lists
  263.                 if (line.startsWith("! Range/resolution:")) {
  264.                     final String[] range_line = SEPARATOR.split(line);

  265.                     // Latitudes list
  266.                     for (double lat = Double.parseDouble(range_line[2]); lat <= Double.parseDouble(range_line[3]); lat = lat + Double.parseDouble(range_line[6])) {
  267.                         latitudes.add(FastMath.toRadians(lat));
  268.                     }

  269.                     // Longitude list
  270.                     for (double lon = Double.parseDouble(range_line[4]); lon <= Double.parseDouble(range_line[5]); lon = lon + Double.parseDouble(range_line[7])) {
  271.                         longitudes.add(FastMath.toRadians(lon));
  272.                         // For VFM1 files, header specify that longitudes end at 360°
  273.                         // In reality they end at 357.5°. That is why we stop the loop when the longitude
  274.                         // reaches 357.5°.
  275.                         if (type == ViennaModelType.VIENNA_ONE && lon >= 357.5) {
  276.                             break;
  277.                         }
  278.                     }
  279.                 }

  280.                 // Fill ah, aw, zhd and zwd lists
  281.                 if (!line.startsWith("!")) {
  282.                     final String[] values_line = SEPARATOR.split(line);
  283.                     ah.add(Double.parseDouble(values_line[2]));
  284.                     aw.add(Double.parseDouble(values_line[3]));
  285.                     zhd.add(Double.parseDouble(values_line[4]));
  286.                     zwd.add(Double.parseDouble(values_line[5]));
  287.                 }
  288.             }

  289.         } catch (NumberFormatException nfe) {
  290.             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  291.                                       lineNumber, name, line);
  292.         }

  293.         // Check that ah, aw, zh and zw were found (only one check is enough)
  294.         if (ah.isEmpty()) {
  295.             throw new OrekitException(OrekitMessages.NO_VIENNA_ACOEF_OR_ZENITH_DELAY_IN_FILE, name);
  296.         }

  297.         final int dimLat = latitudes.size();
  298.         final int dimLon = longitudes.size();

  299.         // Change Lists to Arrays
  300.         final double[] xVal = new double[dimLat];
  301.         for (int i = 0; i < dimLat; i++) {
  302.             xVal[i] = latitudes.get(i);
  303.         }

  304.         final double[] yVal = new double[dimLon];
  305.         for (int j = 0; j < dimLon; j++) {
  306.             yVal[j] = longitudes.get(j);
  307.         }

  308.         final double[][] fvalAH = new double[dimLat][dimLon];
  309.         final double[][] fvalAW = new double[dimLat][dimLon];
  310.         final double[][] fvalZH = new double[dimLat][dimLon];
  311.         final double[][] fvalZW = new double[dimLat][dimLon];

  312.         int index = dimLon * dimLat;
  313.         for (int x = 0; x < dimLat; x++) {
  314.             for (int y = dimLon - 1; y >= 0; y--) {
  315.                 index = index - 1;
  316.                 fvalAH[x][y] = ah.get(index);
  317.                 fvalAW[x][y] = aw.get(index);
  318.                 fvalZH[x][y] = zhd.get(index);
  319.                 fvalZW[x][y] = zwd.get(index);
  320.             }
  321.         }

  322.         // Build Bilinear Interpolation Functions
  323.         final BilinearInterpolatingFunction functionAH = new BilinearInterpolatingFunction(xVal, yVal, fvalAH);
  324.         final BilinearInterpolatingFunction functionAW = new BilinearInterpolatingFunction(xVal, yVal, fvalAW);
  325.         final BilinearInterpolatingFunction functionZH = new BilinearInterpolatingFunction(xVal, yVal, fvalZH);
  326.         final BilinearInterpolatingFunction functionZW = new BilinearInterpolatingFunction(xVal, yVal, fvalZW);

  327.         coefficientsA = new double[2];
  328.         zenithDelay   = new double[2];

  329.         // Get the values for the given latitude and longitude
  330.         coefficientsA[0] = functionAH.value(latitude, longitude);
  331.         coefficientsA[1] = functionAW.value(latitude, longitude);
  332.         zenithDelay[0]   = functionZH.value(latitude, longitude);
  333.         zenithDelay[1]   = functionZW.value(latitude, longitude);

  334.     }

  335. }