1   /* Contributed in the public domain.
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 org.orekit.errors.OrekitException;
20  import org.orekit.errors.OrekitMessages;
21  
22  import java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.nio.charset.StandardCharsets;
27  import java.text.ParseException;
28  import java.util.regex.Pattern;
29  
30  /** Reader for the SHA gravity field format.
31   *
32   * <p> This format is used by some lunar gravity models distributed by
33   * NASA's Planetary Geology, Geophysics and Geochemistry Laboratory such as
34   * GRGM1200B and GRGM1200L. It is a simple ASCII format, described in
35   * <a href="https://pgda.gsfc.nasa.gov/products/75"> the GRGM1200B model product site</a>.
36   * The first line contains 4 constants: model GM, mean radius, maximum degree and maximum order.
37   * All other lines contain 6 entries: degree, order, Clm, Slm, sigma Clm and sigma Slm
38   * (formal errors of Clm and Slm).
39   *
40   * <p> The proper way to use this class is to call the {@link GravityFieldFactory}
41   *  which will determine which reader to use with the selected gravity field file.</p>
42   *
43   * @see GravityFields
44   * @author Rafael Ayala
45   */
46  public class SHAFormatReader extends PotentialCoefficientsReader {
47  
48      /** Default "0" text value. */
49      private static final String ZERO = "0.0";
50  
51      /** Default "1" text value. */
52      private static final String ONE = "1.0";
53  
54      /** Expression for multiple spaces. */
55      private static final String SPACES = "\\s+";
56  
57      /** Pattern for delimiting regular expressions. */
58      private static final Pattern SEPARATOR = Pattern.compile(SPACES);
59  
60      /** Pattern for real numbers. */
61      private static final Pattern REAL =  Pattern.compile("[-+]?\\d?\\.\\d+[eEdD][-+]\\d\\d");
62  
63      /** Pattern for header line. */
64      private static final Pattern HEADER_LINE =  Pattern.compile("^\\s*" + REAL + SPACES + REAL + "\\s+\\d+\\s+\\d+\\s*$");
65  
66      /** Pattern for data lines. */
67      private static final Pattern DATA_LINE = Pattern.compile("^\\s*\\d+\\s+\\d+\\s+" + REAL + SPACES + REAL + SPACES + REAL + SPACES + REAL + "\\s*$");
68  
69      /** Start degree and order for coefficients container. */
70      private static final int START_DEGREE_ORDER = 120;
71  
72      /** Simple constructor.
73       * @param supportedNames regular expression for supported files names
74       * @param missingCoefficientsAllowed if true, allows missing coefficients in the input data
75       * @since 12.2
76       */
77      public SHAFormatReader(final String supportedNames, final boolean missingCoefficientsAllowed) {
78          super(supportedNames, missingCoefficientsAllowed, null);
79      }
80  
81      /** {@inheritDoc} */
82      public void loadData(final InputStream input, final String name) throws IOException, ParseException, OrekitException {
83  
84          // reset the indicator before loading any data
85          setReadComplete(false);
86          setTideSystem(TideSystem.UNKNOWN);
87          int       lineNumber = 0;
88          int       maxDegree;
89          int       maxOrder;
90          String    line = null;
91          TemporaryCoefficientsContainer container = new TemporaryCoefficientsContainer(START_DEGREE_ORDER, START_DEGREE_ORDER,
92                                                                                        missingCoefficientsAllowed() ? 0.0 : Double.NaN);
93          try (BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
94              for (line = r.readLine(); line != null; line = r.readLine()) {
95                  ++lineNumber;
96                  if (lineNumber == 1) {
97                      // match the pattern of the header line
98                      if (!HEADER_LINE.matcher(line).matches()) {
99                          throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
100                                                   lineNumber, name, line);
101                     }
102                     final String[] headerFields = SEPARATOR.split(line.trim());
103                     setMu(Double.parseDouble(headerFields[0]));
104                     setAe(Double.parseDouble(headerFields[1]));
105                     maxDegree = Integer.parseInt(headerFields[2]);
106                     maxOrder = Integer.parseInt(headerFields[3]);
107                     container = container.resize(maxDegree, maxOrder);
108                     parseCoefficient(ONE, container.getFlattener(), container.getC(), 0, 0, "C", name);
109                     parseCoefficient(ZERO, container.getFlattener(), container.getS(), 0, 0, "S", name);
110                     parseCoefficient(ZERO, container.getFlattener(), container.getS(), 1, 0, "C", name);
111                     parseCoefficient(ZERO, container.getFlattener(), container.getS(), 1, 0, "S", name);
112                     parseCoefficient(ZERO, container.getFlattener(), container.getS(), 1, 1, "C", name);
113                     parseCoefficient(ZERO, container.getFlattener(), container.getS(), 1, 1, "S", name);
114                 } else if (lineNumber > 1) {
115                     // match the pattern of the data lines
116                     if (!DATA_LINE.matcher(line).matches()) {
117                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
118                                                 lineNumber, name, line);
119                     }
120                     final String[] dataFields = SEPARATOR.split(line.trim());
121                     // we want to assign the values of the data fields to the corresponding variables
122                     final int n = Integer.parseInt(dataFields[0]);
123                     final int m = Integer.parseInt(dataFields[1]);
124                     parseCoefficient(dataFields[2], container.getFlattener(), container.getC(), n, m, "C", name);
125                     parseCoefficient(dataFields[3], container.getFlattener(), container.getS(), n, m, "S", name);
126                 }
127             }
128         } catch (NumberFormatException nfe) {
129             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
130                                       lineNumber, name, line);
131         }
132         setRawCoefficients(true, container.getFlattener(), container.getC(), container.getS(), name);
133         setReadComplete(true);
134     }
135 
136     /** Provider for read spherical harmonics coefficients.
137      * Like EGM fields, SHA fields don't include time-dependent parts,
138      * so this method returns directly a constant provider.
139      * @param wantNormalized if true, the provider will provide normalized coefficients,
140      * otherwise it will provide un-normalized coefficients
141      * @param degree maximal degree
142      * @param order maximal order
143      * @return a new provider
144      * @since 12.2
145      */
146     public RawSphericalHarmonicsProvider getProvider(final boolean wantNormalized,
147                                                      final int degree, final int order) {
148         return getBaseProvider(wantNormalized, degree, order);
149     }
150 
151 }