OceanLoadingCoefficientsBLQFactory.java

  1. /* Copyright 2002-2018 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.displacement;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.ArrayList;
  23. import java.util.List;
  24. import java.util.Optional;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.Pattern;
  27. import java.util.stream.Collectors;

  28. import org.hipparchus.util.FastMath;
  29. import org.orekit.bodies.GeodeticPoint;
  30. import org.orekit.data.DataLoader;
  31. import org.orekit.data.DataProvidersManager;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;

  34. /**
  35.  * Factory for ocean loading coefficients, using Onsala Space Observatory files in BLQ format.
  36.  * <p>
  37.  * Files in BLQ format can be generated using the form at the
  38.  * <a href="http://holt.oso.chalmers.se/loading/">Bos-Scherneck web site</a>,
  39.  * selecting BLQ as the output format.
  40.  * </p>
  41.  * <p>
  42.  * The sites names are extracted from the file content, not the file name, because the
  43.  * file can contain more than one station. As we expect existing files may have been
  44.  * stripped from headers and footers, we do not attempt to parse them. We only parse
  45.  * the series of 7 lines blocks starting with the lines with the station names and their
  46.  * coordinates and the 6 data lines that follows. Several such blocks may appear in the
  47.  * file. Copy-pasting the entire mail received from OSO after completing the web site
  48.  * form works, as intermediate lines between the 7 lines blocks are simply ignored.
  49.  * </p>
  50.  * @see OceanLoadingCoefficients
  51.  * @see OceanLoading
  52.  * @since 9.1
  53.  * @author Luc Maisonobe
  54.  */
  55. public class OceanLoadingCoefficientsBLQFactory {

  56.     /** Default supported files name pattern for Onsala Space Observatory files in BLQ format. */
  57.     public static final String DEFAULT_BLQ_SUPPORTED_NAMES = "^.+\\.blq$";

  58.     /** Main tides. */
  59.     private static final Tide[][] TIDES = {
  60.         {
  61.             Tide.SSA, Tide.MM, Tide.MF
  62.         }, {
  63.             Tide.Q1,  Tide.O1, Tide.P1, Tide.K1
  64.         }, {
  65.             Tide.N2,  Tide.M2, Tide.S2, Tide.K2
  66.         }
  67.     };

  68.     /** Species index for each column. */
  69.     private static final int[] I = {
  70.         2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0
  71.     };

  72.     /** Tides index for each column. */
  73.     private static final int[] J = {
  74.         1, 2, 0, 3, 3, 1, 2, 0, 2, 1, 0
  75.     };

  76.     /** Regular expression for supported files names. */
  77.     private final String supportedNames;

  78.     /** Parsed coefficients. */
  79.     private final List<OceanLoadingCoefficients> coefficients;

  80.     /** Simple constructor.
  81.      * <p>
  82.      * Files in BLQ format can be generated using the form at the
  83.      * <a href="http://holt.oso.chalmers.se/loading/">Bos-Scherneck web site</a>,
  84.      * selecting BLQ as the output format.
  85.      * </p>
  86.      * @param supportedNames regular expression for supported files names
  87.      * @see #DEFAULT_BLQ_SUPPORTED_NAMES
  88.      */
  89.     public OceanLoadingCoefficientsBLQFactory(final String supportedNames) {

  90.         this.supportedNames = supportedNames;
  91.         this.coefficients   = new ArrayList<>();

  92.     }

  93.     /** Lazy loading of coefficients.
  94.      * @exception OrekitException if coefficients cannot be read
  95.      */
  96.     private void loadsIfNeeded() throws OrekitException {
  97.         if (coefficients.isEmpty()) {
  98.             DataProvidersManager.getInstance().feed(supportedNames, new BLQParser());
  99.         }
  100.     }

  101.     /** Get the list of sites for which we have found coefficients, in lexicographic order ignoring case.
  102.      * @return list of sites for which we have found coefficients, in lexicographic order ignoring case
  103.      * @exception OrekitException if coefficients cannot be read
  104.      */
  105.     public List<String> getSites()
  106.         throws OrekitException {

  107.         loadsIfNeeded();

  108.         // extract sites names from the map
  109.         final List<String> sites = coefficients.stream().map(c -> c.getSiteName()).collect(Collectors.toList());

  110.         // sort to ensure we have a reproducible order
  111.         sites.sort((s1, s2) -> s1.compareToIgnoreCase(s2));

  112.         return sites;

  113.     }

  114.     /** Get the coefficients for a given site.
  115.      * @param site site name (as it appears in the Onsala Space Observatory files in BLQ format),
  116.      * ignoring case
  117.      * @return coefficients for the site
  118.      * @exception OrekitException if no coefficients are found for the specified site
  119.      */
  120.     public OceanLoadingCoefficients getCoefficients(final String site)
  121.         throws OrekitException {

  122.         loadsIfNeeded();

  123.         final Optional<OceanLoadingCoefficients> optional =
  124.                         coefficients.stream().filter(c -> c.getSiteName().equalsIgnoreCase(site)).findFirst();
  125.         if (!optional.isPresent()) {
  126.             throw new OrekitException(OrekitMessages.STATION_NOT_FOUND,
  127.                                       site,
  128.                                       getSites().stream().collect(Collectors.joining(", ")));
  129.         }

  130.         return optional.get();

  131.     }

  132.     /** Local parser for the Onsala Space Observatory BLQ files.
  133.      * <p>
  134.      * when completing the web site form, the email received as the following form:
  135.      * </p>
  136.      * <pre>
  137.      * $$ Ocean loading displacement
  138.      * $$
  139.      * $$ Calculated on holt using olfg/olmpp of H.-G. Scherneck
  140.      * $$
  141.      * $$ COLUMN ORDER:  M2  S2  N2  K2  K1  O1  P1  Q1  MF  MM SSA
  142.      * $$
  143.      * $$ ROW ORDER:
  144.      * $$ AMPLITUDES (m)
  145.      * $$   RADIAL
  146.      * $$   TANGENTL    EW
  147.      * $$   TANGENTL    NS
  148.      * $$ PHASES (degrees)
  149.      * $$   RADIAL
  150.      * $$   TANGENTL    EW
  151.      * $$   TANGENTL    NS
  152.      * $$
  153.      * $$ Displacement is defined positive in upwards, South and West direction.
  154.      * $$ The phase lag is relative to Greenwich and lags positive. The
  155.      * $$ Gutenberg-Bullen Greens function is used. In the ocean tide model the
  156.      * $$ deficit of tidal water mass has been corrected by subtracting a uniform
  157.      * $$ layer of water with a certain phase lag globally.
  158.      * $$
  159.      * $$ Complete <model name> : No interpolation of ocean model was necessary
  160.      * $$ <model name>_PP       : Ocean model has been interpolated near the station
  161.      * $$                         (PP = Post-Processing)
  162.      * $$
  163.      * $$ Ocean tide model: CSR4.0, long-period tides from FES99
  164.      * $$
  165.      * $$ END HEADER
  166.      * $$
  167.      *   Goldstone
  168.      * $$ Complete CSR4.0_f
  169.      * $$ Computed by OLFG, H.-G. Scherneck, Onsala Space Observatory 2017-Sep-28
  170.      * $$ Goldstone,                 RADI TANG  lon/lat:  243.1105   35.4259    0.000
  171.      *   .00130 .00155 .00064 .00052 .01031 .00661 .00339 .00119 .00005 .00002 .00003
  172.      *   .00136 .00020 .00024 .00004 .00322 .00202 .00106 .00036 .00007 .00003 .00001
  173.      *   .00372 .00165 .00082 .00045 .00175 .00113 .00057 .00022 .00004 .00002 .00003
  174.      *     -2.7 -106.3  -62.6 -106.8   41.6   27.3   40.4   24.0 -119.1 -123.2 -169.7
  175.      *   -145.3  -88.4  178.5  -66.3 -130.5 -145.3 -131.7 -148.7  124.3  139.6   23.3
  176.      *     90.7  111.1   74.1  111.3  176.9  165.3  175.8  164.0   48.9   25.3    4.5
  177.      * $$
  178.      *   ONSALA
  179.      * $$ CSR4.0_f_PP ID: 2017-09-28 15:01:14
  180.      * $$ Computed by OLMPP by H G Scherneck, Onsala Space Observatory, 2017
  181.      * $$ Onsala,                    RADI TANG  lon/lat:   11.9264   57.3958    0.000
  182.      *   .00344 .00121 .00078 .00031 .00189 .00116 .00064 .00004 .00090 .00048 .00041
  183.      *   .00143 .00035 .00035 .00008 .00053 .00051 .00018 .00009 .00013 .00006 .00007
  184.      *   .00086 .00023 .00023 .00006 .00029 .00025 .00010 .00008 .00003 .00001 .00000
  185.      *    -64.6  -50.3  -95.0  -53.1  -58.8 -152.4  -65.5 -133.8    9.8    5.8    2.1
  186.      *     85.4  115.2   56.7  114.7   99.5   15.9   94.2  -10.0 -166.3 -169.8 -177.7
  187.      *    110.7  147.1   93.9  148.6   49.4  -56.5   34.8 -169.9  -35.3   -3.7   10.1
  188.      * $$ END TABLE
  189.      * Errors:
  190.      * Warnings:
  191.      * </pre>
  192.      * <p>
  193.      * We only parse blocks 7 lines blocks starting with the lines with the station names
  194.      * and their coordinates and the 6 data lines that follows. Several such blocks may
  195.      * appear in the file.
  196.      * </p>
  197.      */
  198.     private class BLQParser implements DataLoader {

  199.         /** Pattern for fields with real type. */
  200.         private static final String  REAL_TYPE_PATTERN = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";

  201.         /** Pattern for extracted real fields. */
  202.         private static final String  REAL_FIELD_PATTERN = "\\p{Space}*(" + REAL_TYPE_PATTERN + ")";

  203.         /** Pattern for end of line. */
  204.         private static final String  END_OF_LINE_PATTERN = "\\p{Space}*$";

  205.         /** Pattern for site name and coordinates lines. */
  206.         private static final String  SITE_LINE_PATTERN = "^\\$\\$ *([^,]*),\\p{Space}*(?:RADI TANG)?\\p{Space}*lon/lat:" +
  207.                                                          REAL_FIELD_PATTERN +
  208.                                                          REAL_FIELD_PATTERN +
  209.                                                          REAL_FIELD_PATTERN +
  210.                                                          END_OF_LINE_PATTERN;

  211.         /** Pattern for coefficients lines. */
  212.         private static final String  DATA_LINE_PATTERN = "^" +
  213.                                                          REAL_FIELD_PATTERN + // M₂ tide
  214.                                                          REAL_FIELD_PATTERN + // S₂ tide
  215.                                                          REAL_FIELD_PATTERN + // N₂ tide
  216.                                                          REAL_FIELD_PATTERN + // K₂ tide
  217.                                                          REAL_FIELD_PATTERN + // K₁ tide
  218.                                                          REAL_FIELD_PATTERN + // O₁ tide
  219.                                                          REAL_FIELD_PATTERN + // P₁ tide
  220.                                                          REAL_FIELD_PATTERN + // Q₁ tide
  221.                                                          REAL_FIELD_PATTERN + // Mf tide
  222.                                                          REAL_FIELD_PATTERN + // Mm tide
  223.                                                          REAL_FIELD_PATTERN + // Ssa tide
  224.                                                          END_OF_LINE_PATTERN;

  225.         /** Pattern for site name and coordinates lines. */
  226.         private final Pattern sitePattern;

  227.         /** Pattern for coefficients lines. */
  228.         private final Pattern dataPattern;

  229.         /** Simple constructor.
  230.          */
  231.         BLQParser() {
  232.             sitePattern = Pattern.compile(SITE_LINE_PATTERN);
  233.             dataPattern = Pattern.compile(DATA_LINE_PATTERN);
  234.         }

  235.         /** {@inheritDoc} */
  236.         @Override
  237.         public boolean stillAcceptsData() {
  238.             // as data for different stations may be in different files
  239.             // we always accept new data, even if we have already parsed
  240.             // some files
  241.             return true;
  242.         }

  243.         /** {@inheritDoc} */
  244.         @Override
  245.         public void loadData(final InputStream input, final String name)
  246.             throws IOException, OrekitException {

  247.             // temporary holders for parsed data
  248.             String         siteName     = null;
  249.             GeodeticPoint  siteLocation = null;
  250.             final double[][][] data     = new double[6][3][];
  251.             for (int i = 0; i < data.length; ++i) {
  252.                 data[i][0] = new double[3];
  253.                 data[i][1] = new double[4];
  254.                 data[i][2] = new double[4];
  255.             }

  256.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {

  257.                 int     lineNumber = 0;
  258.                 int     dataLine   = -1;
  259.                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  260.                     ++lineNumber;

  261.                     if (dataLine < 0) {
  262.                         // we are looking for a site line
  263.                         final Matcher matcher = sitePattern.matcher(line);
  264.                         if (matcher.matches()) {
  265.                             // the current line is a site description line
  266.                             siteName = matcher.group(1);
  267.                             siteLocation = new GeodeticPoint(FastMath.toRadians(Double.parseDouble(matcher.group(3))),
  268.                                                              FastMath.toRadians(Double.parseDouble(matcher.group(2))),
  269.                                                              Double.parseDouble(matcher.group(4)));
  270.                             // next line must be line 0 of the data
  271.                             dataLine = 0;
  272.                         }
  273.                     } else {
  274.                         // we are looking for a data line
  275.                         final Matcher matcher = dataPattern.matcher(line);
  276.                         if (!matcher.matches()) {
  277.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  278.                                                       lineNumber, name, line);
  279.                         }
  280.                         for (int k = 0; k < I.length; ++k) {
  281.                             if (dataLine < 3) {
  282.                                 // amplitude data
  283.                                 data[dataLine][I[k]][J[k]] = Double.parseDouble(matcher.group(k + 1));
  284.                             } else {
  285.                                 // phase data (reversed to be negative for lags)
  286.                                 data[dataLine][I[k]][J[k]] = -FastMath.toRadians(Double.parseDouble(matcher.group(k + 1)));
  287.                             }
  288.                         }
  289.                         if (dataLine < data.length - 1) {
  290.                             // we need more data
  291.                             ++dataLine;
  292.                         } else {
  293.                             // it was the last data line
  294.                             coefficients.add(new OceanLoadingCoefficients(siteName, siteLocation, TIDES,
  295.                                                                           data[0], data[3],
  296.                                                                           data[1], data[4],
  297.                                                                           data[2], data[5]));
  298.                             dataLine = -1;
  299.                         }
  300.                     }
  301.                 }

  302.                 if (dataLine >= 0) {
  303.                     // we were looking for a line that did not appear
  304.                     throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  305.                                               name, lineNumber);
  306.                 }

  307.             }
  308.         }

  309.     }

  310. }