ViennaModelCoefficientsLoader.java

  1. /* Copyright 2002-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.text.ParseException;
  23. import java.util.ArrayList;

  24. import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
  25. import org.hipparchus.util.FastMath;
  26. import org.hipparchus.util.MathUtils;
  27. import org.orekit.data.DataLoader;
  28. import org.orekit.data.DataProvidersManager;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.time.DateTimeComponents;

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

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

  80.     /** Regular expression for supported file name. */
  81.     private String supportedNames;

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

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

  86.     /** Geodetic site latitude, radians.*/
  87.     private double latitude;

  88.     /** Geodetic site longitude, radians.*/
  89.     private double longitude;

  90.     /** Vienna tropospheric model type.*/
  91.     private ViennaModelType type;

  92.     /** Constructor with supported names given by user.
  93.      * @param supportedNames Supported names
  94.      * @param latitude geodetic latitude of the station, in radians
  95.      * @param longitude geodetic latitude of the station, in radians
  96.      * @param type the type of Vienna tropospheric model (one or three)
  97.      */
  98.     public ViennaModelCoefficientsLoader(final String supportedNames, final double latitude,
  99.                                          final double longitude, final ViennaModelType type) {
  100.         this.coefficientsA  = null;
  101.         this.zenithDelay    = null;
  102.         this.supportedNames = supportedNames;
  103.         this.type           = type;
  104.         this.latitude       = latitude;

  105.         // Normalize longitude between 0 and 2π
  106.         this.longitude = MathUtils.normalizeAngle(longitude, FastMath.PI);

  107.     }

  108.     /** Constructor with default supported names.
  109.      * @param latitude geodetic latitude of the station, in radians
  110.      * @param longitude geodetic latitude of the station, in radians
  111.      * @param type the type of Vienna tropospheric model (one or three)
  112.      */
  113.     public ViennaModelCoefficientsLoader(final double latitude, final double longitude,
  114.                                          final ViennaModelType type) {
  115.         this(DEFAULT_SUPPORTED_NAMES, latitude, longitude, type);
  116.     }

  117.     /** Returns the a coefficients array.
  118.      * <ul>
  119.      * <li>double[0] = a<sub>h</sub>
  120.      * <li>double[1] = a<sub>w</sub>
  121.      * </ul>
  122.      * @return the a coefficients array
  123.      */
  124.     public double[] getA() {
  125.         return coefficientsA.clone();
  126.     }

  127.     /** Returns the zenith delay array.
  128.      * <ul>
  129.      * <li>double[0] = D<sub>hz</sub> → zenith hydrostatic delay
  130.      * <li>double[1] = D<sub>wz</sub> → zenith wet delay
  131.      * </ul>
  132.      * @return the zenith delay array
  133.      */
  134.     public double[] getZenithDelay() {
  135.         return zenithDelay.clone();
  136.     }

  137.     /** Returns the supported names of the loader.
  138.      * @return the supported names
  139.      */
  140.     public String getSupportedNames() {
  141.         return supportedNames;
  142.     }

  143.     /** Load the data using supported names .
  144.      */
  145.     public void loadViennaCoefficients() {
  146.         DataProvidersManager.getInstance().feed(supportedNames, this);

  147.         // Throw an exception if ah, ah, zh or zw were not loaded properly
  148.         if (coefficientsA == null || zenithDelay == null) {
  149.             throw new OrekitException(OrekitMessages.VIENNA_ACOEF_OR_ZENITH_DELAY_NOT_LOADED, supportedNames);
  150.         }
  151.     }

  152.     /** Load the data for a given day.
  153.      * @param dateTimeComponents date and time component.
  154.      * @throws OrekitException if the coefficients could not be loaded
  155.      */
  156.     public void loadViennaCoefficients(final DateTimeComponents dateTimeComponents) {

  157.         // The files are named VMFG_YYYYMMDD.Hhh for Vienna-1 model and VMF3_YYYYMMDD.Hhh for Vienna-3 model.
  158.         // Where YYYY is the 4-digits year, MM the month, DD the day of the month and hh the 2-digits hour.
  159.         // Coefficients are only available for hh = 00 or 06 or 12 or 18.
  160.         final int    year        = dateTimeComponents.getDate().getYear();
  161.         final int    month       = dateTimeComponents.getDate().getMonth();
  162.         final int    day         = dateTimeComponents.getDate().getDay();
  163.         final int    hour        = dateTimeComponents.getTime().getHour();

  164.         // Correct month format is with 2-digits.
  165.         final String monthString;
  166.         if (month < 10) {
  167.             monthString = "0" + String.valueOf(month);
  168.         } else {
  169.             monthString = String.valueOf(month);
  170.         }

  171.         // Correct day format is with 2-digits.
  172.         final String dayString;
  173.         if (day < 10) {
  174.             dayString = "0" + String.valueOf(day);
  175.         } else {
  176.             dayString = String.valueOf(day);
  177.         }

  178.         // Correct hour format is with 2-digits.
  179.         final String hourString;
  180.         if (hour < 10) {
  181.             hourString = "0" + String.valueOf(hour);
  182.         } else {
  183.             hourString = String.valueOf(hour);
  184.         }

  185.         // Name of the file is different between VMF1 and VMF3.
  186.         // For VMF1 it starts with "VMFG" whereas with VMF3 it starts with "VMF3"
  187.         switch (type) {
  188.             case VIENNA_ONE:
  189.                 this.supportedNames = String.format("VMFG_%04d%2s%2s.H%2s", year, monthString, dayString, hourString);
  190.                 break;
  191.             case VIENNA_THREE:
  192.                 this.supportedNames = String.format("VMF3_%04d%2s%2s.H%2s", year, monthString, dayString, hourString);
  193.                 break;
  194.             default:
  195.                 break;
  196.         }

  197.         try {
  198.             this.loadViennaCoefficients();
  199.         } catch (OrekitException oe) {
  200.             throw new OrekitException(oe,
  201.                                       OrekitMessages.VIENNA_ACOEF_OR_ZENITH_DELAY_NOT_AVAILABLE_FOR_DATE,
  202.                                       dateTimeComponents.toString());
  203.         }
  204.     }

  205.     @Override
  206.     public boolean stillAcceptsData() {
  207.         return true;
  208.     }

  209.     @Override
  210.     public void loadData(final InputStream input, final String name)
  211.         throws IOException, ParseException {

  212.         // Open stream and parse data
  213.         final BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8"));
  214.         int lineNumber = 0;
  215.         final String splitter = "\\s+";

  216.         // Initialize Lists
  217.         final ArrayList<Double> latitudes  = new ArrayList<>();
  218.         final ArrayList<Double> longitudes = new ArrayList<>();
  219.         final ArrayList<Double> ah         = new ArrayList<>();
  220.         final ArrayList<Double> aw         = new ArrayList<>();
  221.         final ArrayList<Double> zhd        = new ArrayList<>();
  222.         final ArrayList<Double> zwd        = new ArrayList<>();

  223.         for (String line = br.readLine(); line != null; line = br.readLine()) {
  224.             ++lineNumber;
  225.             line = line.trim();

  226.             try {
  227.                 // Fill latitudes and longitudes lists
  228.                 if (line.length() > 0 && line.startsWith("! Range/resolution:")) {
  229.                     final String[] range_line = line.split(splitter);

  230.                     // Latitudes list
  231.                     for (double lat = Double.valueOf(range_line[2]); lat <= Double.valueOf(range_line[3]); lat = lat + Double.valueOf(range_line[6])) {
  232.                         latitudes.add(FastMath.toRadians(lat));
  233.                     }

  234.                     // Longitude list
  235.                     for (double lon = Double.valueOf(range_line[4]); lon <= Double.valueOf(range_line[5]); lon = lon + Double.valueOf(range_line[7])) {
  236.                         longitudes.add(FastMath.toRadians(lon));
  237.                         // For VFM1 files, header specify that longitudes end at 360°
  238.                         // In reality they end at 357.5°. That is why we stop the loop when the longitude
  239.                         // reaches 357.5°.
  240.                         if (type == ViennaModelType.VIENNA_ONE && lon >= 357.5) {
  241.                             break;
  242.                         }
  243.                     }
  244.                 }

  245.                 // Fill ah, aw, zhd and zwd lists
  246.                 if (line.length() > 0 && !line.startsWith("!")) {
  247.                     final String[] values_line = line.split(splitter);
  248.                     ah.add(Double.valueOf(values_line[2]));
  249.                     aw.add(Double.valueOf(values_line[3]));
  250.                     zhd.add(Double.valueOf(values_line[4]));
  251.                     zwd.add(Double.valueOf(values_line[5]));
  252.                 }

  253.             } catch (NumberFormatException nfe) {
  254.                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  255.                                           lineNumber, name, line);
  256.             }

  257.         }

  258.         // Close the stream after reading
  259.         input.close();

  260.         final int dimLat = latitudes.size();
  261.         final int dimLon = longitudes.size();

  262.         // Change Lists to Arrays
  263.         final double[] xVal = new double[dimLat];
  264.         for (int i = 0; i < dimLat; i++) {
  265.             xVal[i] = latitudes.get(i);
  266.         }

  267.         final double[] yVal = new double[dimLon];
  268.         for (int j = 0; j < dimLon; j++) {
  269.             yVal[j] = longitudes.get(j);
  270.         }

  271.         final double[][] fvalAH = new double[dimLat][dimLon];
  272.         final double[][] fvalAW = new double[dimLat][dimLon];
  273.         final double[][] fvalZH = new double[dimLat][dimLon];
  274.         final double[][] fvalZW = new double[dimLat][dimLon];

  275.         int index = dimLon * dimLat;
  276.         for (int x = 0; x < dimLat; x++) {
  277.             for (int y = dimLon - 1; y >= 0; y--) {
  278.                 index = index - 1;
  279.                 fvalAH[x][y] = ah.get(index);
  280.                 fvalAW[x][y] = aw.get(index);
  281.                 fvalZH[x][y] = zhd.get(index);
  282.                 fvalZW[x][y] = zwd.get(index);
  283.             }
  284.         }

  285.         // Build Bilinear Interpolation Functions
  286.         final BilinearInterpolatingFunction functionAH = new BilinearInterpolatingFunction(xVal, yVal, fvalAH);
  287.         final BilinearInterpolatingFunction functionAW = new BilinearInterpolatingFunction(xVal, yVal, fvalAW);
  288.         final BilinearInterpolatingFunction functionZH = new BilinearInterpolatingFunction(xVal, yVal, fvalZH);
  289.         final BilinearInterpolatingFunction functionZW = new BilinearInterpolatingFunction(xVal, yVal, fvalZW);

  290.         coefficientsA = new double[2];
  291.         zenithDelay   = new double[2];

  292.         // Get the values for the given latitude and longitude
  293.         coefficientsA[0] = functionAH.value(latitude, longitude);
  294.         coefficientsA[1] = functionAW.value(latitude, longitude);
  295.         zenithDelay[0]   = functionZH.value(latitude, longitude);
  296.         zenithDelay[1]   = functionZW.value(latitude, longitude);

  297.         // Check that ah, aw, zh and zw were found
  298.         if (coefficientsA == null || zenithDelay == null) {
  299.             throw new OrekitException(OrekitMessages.NO_VIENNA_ACOEF_OR_ZENITH_DELAY_IN_FILE, name);
  300.         }

  301.     }

  302. }