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