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.Collections;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  
30  import org.hipparchus.util.FastMath;
31  import org.orekit.data.DataLoader;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  
35  /**
36   * Parser for tides astronomical amplitude H<sub>f</sub>.
37   * @author Luc Maisonobe
38   * @since 6.1
39   */
40  public class AstronomicalAmplitudeReader implements DataLoader {
41  
42      /** Pattern for optional fields (either nothing or non-space characters). */
43      private static final String  OPTIONAL_FIELD_PATTERN = "\\S*";
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 fields with real type. */
49      private static final String  REAL_TYPE_PATTERN =
50              "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
51  
52      /** Pattern for regular data. */
53      private static final Pattern PATTERN = Pattern.compile("[.,]");
54  
55      /** Regular expression for supported files names. */
56      private final String supportedNames;
57  
58      /** Pattern for regular data lines. */
59      private final Pattern regularLinePattern;
60  
61      /** Doodson number column. */
62      private final int columnDoodson;
63  
64      /** H<sub>f</sub> column. */
65      private final int columnHf;
66  
67      /** Scaling factor for astronomical amplitude. */
68      private final double scale;
69  
70      /** Amplitudes map. */
71      private final Map<Integer, Double> amplitudesMap;
72  
73      /** Simple constructor.
74       * @param supportedNames regular expression for supported files names
75       * @param columns number of columns
76       * @param columnDoodson Doodson number column (counting from 1)
77       * @param columnHf H<sub>f</sub> column (counting from 1)
78       * @param scale scaling factor for astronomical amplitude
79       */
80      public AstronomicalAmplitudeReader(final String supportedNames, final int columns,
81                                         final int columnDoodson, final int columnHf,
82                                         final double scale) {
83  
84          // build the pattern for the regular data lines
85          final StringBuilder builder = new StringBuilder("^\\p{Space}*");
86          for (int i = 1; i <= columns; ++i) {
87              builder.append("(");
88              if (i == columnDoodson) {
89                  builder.append(DOODSON_TYPE_PATTERN);
90              } else if (i == columnHf) {
91                  builder.append(REAL_TYPE_PATTERN);
92              } else {
93                  builder.append(OPTIONAL_FIELD_PATTERN);
94              }
95              builder.append(")");
96              builder.append(i < FastMath.max(columnDoodson, columnHf) ? "\\p{Space}+" : "\\p{Space}*");
97          }
98          builder.append('$');
99          this.regularLinePattern = Pattern.compile(builder.toString());
100 
101         this.supportedNames = supportedNames;
102         this.columnDoodson  = columnDoodson;
103         this.columnHf       = columnHf;
104         this.scale          = scale;
105 
106         this.amplitudesMap  = new HashMap<>();
107 
108     }
109 
110     /** Get the regular expression for supported files names.
111      * @return regular expression for supported files names
112      */
113     public String getSupportedNames() {
114         return supportedNames;
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public boolean stillAcceptsData() {
120         return amplitudesMap.isEmpty();
121     }
122 
123     /** {@inheritDoc} */
124     @Override
125     public void loadData(final InputStream input, final String name)
126         throws IOException {
127 
128         int lineNumber = 0;
129         String line = null;
130         // parse the file
131         try (BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
132 
133             for (line = r.readLine(); line != null; line = r.readLine()) {
134                 ++lineNumber;
135 
136                 // replace unicode minus sign ('−') by regular hyphen ('-') for parsing
137                 // such unicode characters occur in tables that are copy-pasted from PDF files
138                 line = line.replace('\u2212', '-');
139 
140                 final Matcher regularMatcher = regularLinePattern.matcher(line);
141                 if (regularMatcher.matches()) {
142                     // we have found a regular data line
143                     final int    doodson = Integer.parseInt(PATTERN.matcher(regularMatcher.group(columnDoodson)).replaceAll(""));
144                     final double hf      = scale * Double.parseDouble(regularMatcher.group(columnHf));
145                     amplitudesMap.put(doodson, hf);
146                 }
147             }
148 
149         } catch (NumberFormatException nfe) {
150             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
151                                       lineNumber, name, line);
152         }
153 
154         if (amplitudesMap.isEmpty()) {
155             throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
156         }
157 
158     }
159 
160     /** Get astronomical amplitudes map.
161      * @return an unmodifiable map containing astronomical amplitudes H<sub>f</sub>
162      * from a Doodson number key
163      */
164     public Map<Integer, Double> getAstronomicalAmplitudesMap() {
165         return Collections.unmodifiableMap(amplitudesMap);
166     }
167 
168 }