OceanLoadingCoefficientsBlqParser.java

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

  18. import org.hipparchus.exception.LocalizedCoreFormats;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.bodies.GeodeticPoint;
  21. import org.orekit.data.DataSource;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.errors.OrekitMessages;

  24. import java.io.BufferedReader;
  25. import java.io.IOException;
  26. import java.util.ArrayList;
  27. import java.util.List;
  28. import java.util.regex.Matcher;
  29. import java.util.regex.Pattern;

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

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

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

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

  58.     /** Pattern for site name and coordinates lines. */
  59.     private static final String  SITE_LINE_PATTERN = "^\\$\\$ *([^,]*),\\p{Space}*(?:RADI TANG)?\\p{Space}*lon/lat:" +
  60.                                                      REAL_FIELD_PATTERN +
  61.                                                      REAL_FIELD_PATTERN +
  62.                                                      REAL_FIELD_PATTERN +
  63.                                                      END_OF_LINE_PATTERN;

  64.     /** Pattern for coefficients lines. */
  65.     private static final String  DATA_LINE_PATTERN = "^" +
  66.                                                      REAL_FIELD_PATTERN + // M₂ tide
  67.                                                      REAL_FIELD_PATTERN + // S₂ tide
  68.                                                      REAL_FIELD_PATTERN + // N₂ tide
  69.                                                      REAL_FIELD_PATTERN + // K₂ tide
  70.                                                      REAL_FIELD_PATTERN + // K₁ tide
  71.                                                      REAL_FIELD_PATTERN + // O₁ tide
  72.                                                      REAL_FIELD_PATTERN + // P₁ tide
  73.                                                      REAL_FIELD_PATTERN + // Q₁ tide
  74.                                                      REAL_FIELD_PATTERN + // Mf tide
  75.                                                      REAL_FIELD_PATTERN + // Mm tide
  76.                                                      REAL_FIELD_PATTERN + // Ssa tide
  77.                                                      END_OF_LINE_PATTERN;

  78.     /** Pattern for site name and coordinates lines. */
  79.     private static final Pattern SITE_PATTERN = Pattern.compile(SITE_LINE_PATTERN);

  80.     /** Pattern for coefficients lines. */
  81.     private static final Pattern DATA_PATTERN = Pattern.compile(DATA_LINE_PATTERN);

  82.     /** Main tides. */
  83.     private static final Tide[][] TIDES = {
  84.         {
  85.             Tide.SSA, Tide.MM, Tide.MF
  86.         }, {
  87.             Tide.Q1,  Tide.O1, Tide.P1, Tide.K1
  88.         }, {
  89.             Tide.N2,  Tide.M2, Tide.S2, Tide.K2
  90.         }
  91.     };

  92.     /** Species index for each column. */
  93.     private static final int[] I = {
  94.         2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0
  95.     };

  96.     /** Tides index for each column. */
  97.     private static final int[] J = {
  98.         1, 2, 0, 3, 3, 1, 2, 0, 2, 1, 0
  99.     };

  100.     /** Parse a BLQ file.
  101.      * <p>
  102.      * Files in BLQ format can be generated using the form at the
  103.      * <a href="https://holt.oso.chalmers.se/loading/">Bos-Scherneck web site</a>,
  104.      * selecting BLQ as the output format.
  105.      * </p>
  106.      * <p>
  107.      * when completing the web site form, the email received as the following form:
  108.      * </p>
  109.      * <pre>{@literal
  110.      * $$ Ocean loading displacement
  111.      * $$
  112.      * $$ Calculated on holt using olfg/olmpp of H.-G. Scherneck
  113.      * $$
  114.      * $$ COLUMN ORDER:  M2  S2  N2  K2  K1  O1  P1  Q1  MF  MM SSA
  115.      * $$
  116.      * $$ ROW ORDER:
  117.      * $$ AMPLITUDES (m)
  118.      * $$   RADIAL
  119.      * $$   TANGENTL    EW
  120.      * $$   TANGENTL    NS
  121.      * $$ PHASES (degrees)
  122.      * $$   RADIAL
  123.      * $$   TANGENTL    EW
  124.      * $$   TANGENTL    NS
  125.      * $$
  126.      * $$ Displacement is defined positive in upwards, South and West direction.
  127.      * $$ The phase lag is relative to Greenwich and lags positive. The
  128.      * $$ Gutenberg-Bullen Greens function is used. In the ocean tide model the
  129.      * $$ deficit of tidal water mass has been corrected by subtracting a uniform
  130.      * $$ layer of water with a certain phase lag globally.
  131.      * $$
  132.      * $$ Complete <model name> : No interpolation of ocean model was necessary
  133.      * $$ <model name>_PP       : Ocean model has been interpolated near the station
  134.      * $$                         (PP = Post-Processing)
  135.      * $$
  136.      * $$ Ocean tide model: CSR4.0, long-period tides from FES99
  137.      * $$
  138.      * $$ END HEADER
  139.      * $$
  140.      *   Goldstone
  141.      * $$ Complete CSR4.0_f
  142.      * $$ Computed by OLFG, H.-G. Scherneck, Onsala Space Observatory 2017-Sep-28
  143.      * $$ Goldstone,                 RADI TANG  lon/lat:  243.1105   35.4259    0.000
  144.      *   .00130 .00155 .00064 .00052 .01031 .00661 .00339 .00119 .00005 .00002 .00003
  145.      *   .00136 .00020 .00024 .00004 .00322 .00202 .00106 .00036 .00007 .00003 .00001
  146.      *   .00372 .00165 .00082 .00045 .00175 .00113 .00057 .00022 .00004 .00002 .00003
  147.      *     -2.7 -106.3  -62.6 -106.8   41.6   27.3   40.4   24.0 -119.1 -123.2 -169.7
  148.      *   -145.3  -88.4  178.5  -66.3 -130.5 -145.3 -131.7 -148.7  124.3  139.6   23.3
  149.      *     90.7  111.1   74.1  111.3  176.9  165.3  175.8  164.0   48.9   25.3    4.5
  150.      * $$
  151.      *   ONSALA
  152.      * $$ CSR4.0_f_PP ID: 2017-09-28 15:01:14
  153.      * $$ Computed by OLMPP by H G Scherneck, Onsala Space Observatory, 2017
  154.      * $$ Onsala,                    RADI TANG  lon/lat:   11.9264   57.3958    0.000
  155.      *   .00344 .00121 .00078 .00031 .00189 .00116 .00064 .00004 .00090 .00048 .00041
  156.      *   .00143 .00035 .00035 .00008 .00053 .00051 .00018 .00009 .00013 .00006 .00007
  157.      *   .00086 .00023 .00023 .00006 .00029 .00025 .00010 .00008 .00003 .00001 .00000
  158.      *    -64.6  -50.3  -95.0  -53.1  -58.8 -152.4  -65.5 -133.8    9.8    5.8    2.1
  159.      *     85.4  115.2   56.7  114.7   99.5   15.9   94.2  -10.0 -166.3 -169.8 -177.7
  160.      *    110.7  147.1   93.9  148.6   49.4  -56.5   34.8 -169.9  -35.3   -3.7   10.1
  161.      * $$ END TABLE
  162.      * Errors:
  163.      * Warnings:
  164.      * }</pre>
  165.      * <p>
  166.      * We only parse blocks 7 lines blocks starting with the lines with the station names
  167.      * and their coordinates and the 6 data lines that follows. Several such blocks may
  168.      * appear in the file.
  169.      * </p>
  170.      * @param source source for BLQ data
  171.      * @return parsed coefficients
  172.      */
  173.     public List<OceanLoadingCoefficients> parse(final DataSource source) {

  174.         final List<OceanLoadingCoefficients> coefficients = new ArrayList<>();

  175.         // temporary holders for parsed data
  176.         String siteName = null;
  177.         GeodeticPoint siteLocation = null;
  178.         final double[][][] data = new double[6][3][];
  179.         for (int i = 0; i < data.length; ++i) {
  180.             data[i][0] = new double[3];
  181.             data[i][1] = new double[4];
  182.             data[i][2] = new double[4];
  183.         }

  184.         try (BufferedReader reader = new BufferedReader(
  185.             source.getOpener().openReaderOnce())) {

  186.             int lineNumber = 0;
  187.             int dataLine = -1;
  188.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  189.                 ++lineNumber;

  190.                 if (dataLine < 0) {
  191.                     // we are looking for a site line
  192.                     final Matcher matcher = SITE_PATTERN.matcher(line);
  193.                     if (matcher.matches()) {
  194.                         // the current line is a site description line
  195.                         siteName = matcher.group(1);
  196.                         siteLocation =
  197.                             new GeodeticPoint(FastMath.toRadians(Double.parseDouble(matcher.group(3))),
  198.                                               FastMath.toRadians(Double.parseDouble(matcher.group(2))),
  199.                                               Double.parseDouble(matcher.group(4)));
  200.                         // next line must be line 0 of the data
  201.                         dataLine = 0;
  202.                     }
  203.                 } else {
  204.                     // we are looking for a data line
  205.                     final Matcher matcher = DATA_PATTERN.matcher(line);
  206.                     if (!matcher.matches()) {
  207.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  208.                                                   lineNumber, source.getName(), line);
  209.                     }
  210.                     for (int k = 0; k < I.length; ++k) {
  211.                         if (dataLine < 3) {
  212.                             // amplitude data
  213.                             data[dataLine][I[k]][J[k]] = Double.parseDouble(matcher.group(k + 1));
  214.                         } else {
  215.                             // phase data (reversed to be negative for lags)
  216.                             data[dataLine][I[k]][J[k]] = -FastMath.toRadians(Double.parseDouble(matcher.group(k + 1)));
  217.                         }
  218.                     }
  219.                     if (dataLine < data.length - 1) {
  220.                         // we need more data
  221.                         ++dataLine;
  222.                     } else {
  223.                         // it was the last data line
  224.                         coefficients.add(new OceanLoadingCoefficients(siteName, siteLocation,
  225.                                                                       TIDES, data[0],
  226.                                                                       data[3], data[1],
  227.                                                                       data[4], data[2],
  228.                                                                       data[5]));
  229.                         dataLine = -1;
  230.                     }
  231.                 }
  232.             }

  233.             if (dataLine >= 0) {
  234.                 // we were looking for a line that did not appear
  235.                 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE_AFTER_LINE,
  236.                                           source.getName(), lineNumber);
  237.             }

  238.         } catch (IOException ioe) {
  239.             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE,
  240.                                       ioe.getLocalizedMessage());
  241.         }

  242.         return coefficients;

  243.     }

  244. }