1 /* Copyright 2002-2018 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.time;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.text.ParseException;
24 import java.util.ArrayList;
25 import java.util.List;
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.data.DataProvidersManager;
32 import org.orekit.errors.OrekitException;
33 import org.orekit.errors.OrekitMessages;
34
35 /** Loader for UTC-TAI extracted from tai-utc.dat file from USNO.
36 * <p>
37 * This class is immutable and hence thread-safe
38 * </p>
39 * @author Luc Maisonobe
40 * @since 7.1
41 */
42 public class TAIUTCDatFilesLoader implements UTCTAIOffsetsLoader {
43
44 /** Default supported files name pattern. */
45 public static final String DEFAULT_SUPPORTED_NAMES = "^tai-utc\\.dat$";
46
47 /** Regular expression for supported files names. */
48 private final String supportedNames;
49
50 /** Build a loader for tai-utc.dat file from USNO.
51 * @param supportedNames regular expression for supported files names
52 */
53 public TAIUTCDatFilesLoader(final String supportedNames) {
54 this.supportedNames = supportedNames;
55 }
56
57 /** {@inheritDoc} */
58 @Override
59 public List<OffsetModel> loadOffsets() throws OrekitException {
60 final Parser parser = new Parser();
61 DataProvidersManager.getInstance().feed(supportedNames, parser);
62 return parser.getOffsets();
63 }
64
65 /** Internal class performing the parsing. */
66 private static class Parser implements DataLoader {
67
68 /** Regular expression for optional blanks. */
69 private static final String BLANKS = "\\p{Blank}*";
70
71 /** Regular expression for storage start. */
72 private static final String STORAGE_START = "(";
73
74 /** Regular expression for storage end. */
75 private static final String STORAGE_END = ")";
76
77 /** Regular expression for alternative. */
78 private static final String ALTERNATIVE = "|";
79
80 /** Regular expression matching blanks at start of line. */
81 private static final String LINE_START_REGEXP = "^" + BLANKS;
82
83 /** Regular expression matching blanks at end of line. */
84 private static final String LINE_END_REGEXP = BLANKS + "$";
85
86 /** Regular expression matching integers. */
87 private static final String INTEGER_REGEXP = "[-+]?\\p{Digit}+";
88
89 /** Regular expression matching real numbers. */
90 private static final String REAL_REGEXP = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
91
92 /** Regular expression matching an integer field to store. */
93 private static final String STORED_INTEGER_FIELD = BLANKS + STORAGE_START + INTEGER_REGEXP + STORAGE_END;
94
95 /** Regular expression matching a real field to store. */
96 private static final String STORED_REAL_FIELD = BLANKS + STORAGE_START + REAL_REGEXP + STORAGE_END;
97
98 /** Data lines pattern. */
99 private Pattern dataPattern;
100
101 /** UTC-TAI offsets. */
102 private List<OffsetModel> offsets;
103
104 /** Simple constructor.
105 */
106 Parser() {
107
108 // data lines read:
109 // 1965 SEP 1 =JD 2439004.5 TAI-UTC= 3.8401300 S + (MJD - 38761.) X 0.001296 S
110 // 1966 JAN 1 =JD 2439126.5 TAI-UTC= 4.3131700 S + (MJD - 39126.) X 0.002592 S
111 // 1968 FEB 1 =JD 2439887.5 TAI-UTC= 4.2131700 S + (MJD - 39126.) X 0.002592 S
112 // 1972 JAN 1 =JD 2441317.5 TAI-UTC= 10.0 S + (MJD - 41317.) X 0.0 S
113 // 1972 JUL 1 =JD 2441499.5 TAI-UTC= 11.0 S + (MJD - 41317.) X 0.0 S
114 // 1973 JAN 1 =JD 2441683.5 TAI-UTC= 12.0 S + (MJD - 41317.) X 0.0 S
115 // 1974 JAN 1 =JD 2442048.5 TAI-UTC= 13.0 S + (MJD - 41317.) X 0.0 S
116
117 // month as a three letters upper case abbreviation
118 final StringBuilder builder = new StringBuilder(BLANKS + STORAGE_START);
119 for (final Month month : Month.values()) {
120 builder.append(month.getUpperCaseAbbreviation());
121 builder.append(ALTERNATIVE);
122 }
123 builder.delete(builder.length() - 1, builder.length());
124 builder.append(STORAGE_END);
125 final String monthField = builder.toString();
126
127 dataPattern = Pattern.compile(LINE_START_REGEXP +
128 STORED_INTEGER_FIELD + monthField + STORED_INTEGER_FIELD +
129 "\\p{Blank}+=JD" + STORED_REAL_FIELD +
130 "\\p{Blank}+TAI-UTC=" + STORED_REAL_FIELD +
131 "\\p{Blank}+S\\p{Blank}+\\+\\p{Blank}+\\(MJD\\p{Blank}+-" + STORED_REAL_FIELD +
132 "\\p{Blank}*\\)\\p{Blank}+X" + STORED_REAL_FIELD +
133 "\\p{Blank}*S" + LINE_END_REGEXP);
134
135 offsets = new ArrayList<OffsetModel>();
136
137 }
138
139 /** Get the parsed offsets.
140 * @return parsed offsets
141 */
142 public List<OffsetModel> getOffsets() {
143 return offsets;
144 }
145
146 /** {@inheritDoc} */
147 public boolean stillAcceptsData() {
148 return offsets.isEmpty();
149 }
150
151 /** Load UTC-TAI offsets entries read from some file.
152 * <p>The time steps are extracted from some {@code tai-utc.dat} file.
153 * Since entries are stored in a {@link java.util.SortedMap SortedMap},
154 * they are chronologically sorted and only one entry remains for a given date.</p>
155 * @param input data input stream
156 * @param name name of the file (or zip entry)
157 * @exception IOException if data can't be read
158 * @exception ParseException if data can't be parsed
159 * @exception OrekitException if some data is missing
160 * or if some loader specific error occurs
161 */
162 public void loadData(final InputStream input, final String name)
163 throws OrekitException, IOException, ParseException {
164
165 offsets.clear();
166
167 // set up a reader for line-oriented file
168 final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
169
170 // read all file, ignoring not recognized lines
171 int lineNumber = 0;
172 DateComponents lastDate = null;
173 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
174 ++lineNumber;
175
176 // check matching for data lines
177 final Matcher matcher = dataPattern.matcher(line);
178 if (matcher.matches()) {
179
180 try {
181 // build an entry from the extracted fields
182 final DateComponents dc1 = new DateComponents(Integer.parseInt(matcher.group(1)),
183 Month.parseMonth(matcher.group(2)),
184 Integer.parseInt(matcher.group(3)));
185 final DateComponents dc2 = new DateComponents(DateComponents.JULIAN_EPOCH,
186 (int) FastMath.ceil(Double.parseDouble(matcher.group(4))));
187 if (!dc1.equals(dc2)) {
188 throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
189 name, dc1.getYear(), dc1.getMonth(), dc1.getDay(), dc2.getMJD());
190 }
191
192 if ((lastDate != null) && dc1.compareTo(lastDate) <= 0) {
193 throw new OrekitException(OrekitMessages.NON_CHRONOLOGICAL_DATES_IN_FILE,
194 name, lineNumber);
195 }
196 lastDate = dc1;
197
198 final double offset = Double.parseDouble(matcher.group(5));
199 final double mjdRef = Double.parseDouble(matcher.group(6));
200 final double slope = Double.parseDouble(matcher.group(7));
201 offsets.add(new OffsetModel(dc1, (int) FastMath.rint(mjdRef), offset, slope));
202
203 } catch (NumberFormatException nfe) {
204 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
205 lineNumber, name, line);
206 }
207 }
208 }
209
210 if (offsets.isEmpty()) {
211 throw new OrekitException(OrekitMessages.NO_ENTRIES_IN_IERS_UTC_TAI_HISTORY_FILE, name);
212 }
213
214 }
215
216 }
217
218 }