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.forces.gravity.potential;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.StandardCharsets;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import org.orekit.errors.OrekitException;
28  import org.orekit.errors.OrekitMessages;
29  
30  /** Reader for ocean tides files following the fes2004_Cnm-Snm.dat format.
31   * @since 6.1
32   * @author Luc Maisonobe
33   */
34  public class FESCnmSnmReader extends OceanTidesReader {
35  
36      /** Default pattern for fields with unknown type (non-space characters). */
37      private static final String  UNKNOWN_TYPE_PATTERN = "\\S+";
38  
39      /** Pattern for fields with integer type. */
40      private static final String  INTEGER_TYPE_PATTERN = "[-+]?\\p{Digit}+";
41  
42      /** Pattern for fields with real type. */
43      private static final String  REAL_TYPE_PATTERN = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
44  
45      /** Pattern for fields with Doodson number. */
46      private static final String  DOODSON_TYPE_PATTERN = "\\p{Digit}{2,3}[.,]\\p{Digit}{3}";
47  
48      /** Pattern for regular data. */
49      private static final Pattern PATTERN = Pattern.compile("[.,]");
50  
51      /** Scale of the Cnm, Snm parameters. */
52      private final double scale;
53  
54      /** Simple constructor.
55       * @param supportedNames regular expression for supported files names
56       * @param scale scale of the Cnm, Snm parameters
57       */
58      public FESCnmSnmReader(final String supportedNames, final double scale) {
59          super(supportedNames);
60          this.scale = scale;
61      }
62  
63      /** {@inheritDoc} */
64      @Override
65      public void loadData(final InputStream input, final String name)
66          throws IOException {
67  
68          // FES ocean tides models have the following form:
69          //    Coefficients to compute variations in normalized Stokes coefficients (unit = 10^-12)
70          //    Ocean tide model: FES2004 normalized model (fev. 2004) up to (100,100)
71          //    (long period from FES2002 up to (50,50) + equilibrium Om1/Om2, atmospheric tide NOT included)
72          //    Doodson Darw  l   m    DelC+     DelS+       DelC-     DelS-
73          //     55.565 Om1   2   0   6.58128  -0.00000    -0.00000  -0.00000
74          //     55.575 Om2   2   0  -0.06330   0.00000     0.00000   0.00000
75          //     56.554 Sa    1   0  -0.00000  -0.00000    -0.00000  -0.00000
76          //     56.554 Sa    2   0   0.56720   0.01099    -0.00000  -0.00000
77          //     56.554 Sa    3   0   0.00908  -0.00050    -0.00000  -0.00000
78          final String[] fieldsPatterns = new String[] {
79              DOODSON_TYPE_PATTERN,
80              UNKNOWN_TYPE_PATTERN,
81              INTEGER_TYPE_PATTERN,
82              INTEGER_TYPE_PATTERN,
83              REAL_TYPE_PATTERN,
84              REAL_TYPE_PATTERN,
85              REAL_TYPE_PATTERN,
86              REAL_TYPE_PATTERN
87          };
88          final StringBuilder builder = new StringBuilder("^\\p{Space}*");
89          for (int i = 0; i < fieldsPatterns.length; ++i) {
90              builder.append("(");
91              builder.append(fieldsPatterns[i]);
92              builder.append(")");
93              builder.append((i < fieldsPatterns.length - 1) ? "\\p{Space}+" : "\\p{Space}*$");
94          }
95          final Pattern regularLinePattern = Pattern.compile(builder.toString());
96  
97          // parse the file
98          startParse(name);
99          try (BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
100             int lineNumber      = 0;
101             boolean dataStarted = false;
102             for (String line = r.readLine(); line != null; line = r.readLine()) {
103                 ++lineNumber;
104                 final Matcher regularMatcher = regularLinePattern.matcher(line);
105                 if (regularMatcher.matches()) {
106                     // we have found a regular data line
107 
108                     // parse Doodson, degree and order fields
109                     final int doodson = Integer.parseInt(PATTERN.matcher(regularMatcher.group(1)).replaceAll(""));
110                     final int n       = Integer.parseInt(regularMatcher.group(3));
111                     final int m       = Integer.parseInt(regularMatcher.group(4));
112 
113                     if (canAdd(n, m)) {
114 
115                         // parse coefficients
116                         final double cPlus  = scale * Double.parseDouble(regularMatcher.group(5));
117                         final double sPlus  = scale * Double.parseDouble(regularMatcher.group(6));
118                         final double cMinus = scale * Double.parseDouble(regularMatcher.group(7));
119                         final double sMinus = scale * Double.parseDouble(regularMatcher.group(8));
120 
121                         // store parsed fields
122                         addWaveCoefficients(doodson, n, m, cPlus,  sPlus, cMinus, sMinus, lineNumber, line);
123                         dataStarted = true;
124 
125                     }
126 
127                 } else if (dataStarted) {
128                     // once the first data line has been encountered,
129                     // all remaining lines should also be data lines
130                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
131                                               lineNumber, name, line);
132                 }
133             }
134         }
135         endParse();
136 
137     }
138 
139 }