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