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.frames;
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.DataContext;
32 import org.orekit.data.DataLoader;
33 import org.orekit.data.DataProvidersManager;
34 import org.orekit.errors.OrekitException;
35 import org.orekit.errors.OrekitMessages;
36 import org.orekit.time.DateComponents;
37
38 /** Loader for ITRF version configuration file.
39 * <p>
40 * The ITRF version configuration file specifies
41 * the {@link ITRFVersion ITRF versions} that each
42 * type of Earth Orientation Parameter file contains
43 * for each date. This configuration file is used to
44 * interpret {@link EopC04FilesLoader EOP C04} files,
45 * {@link BulletinAFilesLoader Bulletin A} files,
46 * {@link BulletinBFilesLoader Bulletin B} files,
47 * {@link RapidDataAndPredictionColumnsLoader rapid data
48 * and prediction files in columns format} files,
49 * {@link EopXmlLoader rapid data
50 * and prediction files in XML format} files...
51 * </p>
52 * <p>This file is an Orekit-specific configuration file.
53 * </p>
54 * <p>
55 * This class is immutable and hence thread-safe
56 * </p>
57 * @see EopC04FilesLoader
58 * @see BulletinAFilesLoader
59 * @see BulletinBFilesLoader
60 * @see RapidDataAndPredictionColumnsLoader
61 * @see EopXmlLoader
62 * @author Luc Maisonobe
63 * @since 9.2
64 */
65 public class ITRFVersionLoader implements ItrfVersionProvider {
66
67 /** Regular expression for supported files names. */
68 public static final String SUPPORTED_NAMES = "itrf-versions.conf";
69
70 /** Default entry to use if no suitable configuration is found. */
71 private static final ITRFVersionConfiguration DEFAULT =
72 new ITRFVersionConfiguration("", ITRFVersion.ITRF_2014,
73 Integer.MIN_VALUE, Integer.MAX_VALUE);
74
75 /** Configuration. */
76 private final List<ITRFVersionConfiguration> configurations;
77
78 /**
79 * Build a loader for ITRF version configuration file. This constructor uses the
80 * {@link DataContext#getDefault() default data context}.
81 *
82 * @param supportedNames regular expression for supported files names
83 * @see #ITRFVersionLoader(String, DataProvidersManager)
84 */
85 @DefaultDataContext
86 public ITRFVersionLoader(final String supportedNames) {
87 this(supportedNames, DataContext.getDefault().getDataProvidersManager());
88 }
89
90 /**
91 * Build a loader for ITRF version configuration file.
92 *
93 * @param supportedNames regular expression for supported files names
94 * @param dataProvidersManager provides access to the {@code itrf-versions.conf}
95 * file.
96 */
97 public ITRFVersionLoader(final String supportedNames,
98 final DataProvidersManager dataProvidersManager) {
99 this.configurations = new ArrayList<>();
100 dataProvidersManager.feed(supportedNames, new Parser());
101 }
102
103 /**
104 * Build a loader for ITRF version configuration file using the default name. This
105 * constructor uses the {@link DataContext#getDefault() default data context}.
106 *
107 * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
108 *
109 * @see #ITRFVersionLoader(String)
110 * @see #ITRFVersionLoader(String, DataProvidersManager)
111 * @see #SUPPORTED_NAMES
112 */
113 @DefaultDataContext
114 public ITRFVersionLoader() {
115 this(SUPPORTED_NAMES);
116 }
117
118 @Override
119 public ITRFVersionConfiguration getConfiguration(final String name, final int mjd) {
120
121 for (final ITRFVersionConfiguration configuration : configurations) {
122 if (configuration.appliesTo(name) && configuration.isValid(mjd)) {
123 // we have found a matching configuration
124 return configuration;
125 }
126 }
127
128 // no suitable configuration found, use the default value
129 return DEFAULT;
130
131 }
132
133 /** Internal class performing the parsing. */
134 private class Parser implements DataLoader {
135
136 /** Regular expression matching start of line. */
137 private static final String START = "^";
138
139 /** Regular expression matching a non-blank field (for names regexp). */
140 private static final String NON_BLANK_FIELD = "(\\S+)";
141
142 /** Regular expression matching a calendar date. */
143 private static final String CALENDAR_DATE = "\\s+(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)";
144
145 /** Regular expression matching a date at infinity. */
146 private static final String INFINITY_DATE = "\\s+-+";
147
148 /** Regular expression matching an ITRF version. */
149 private static final String ITRF = "\\s+([Ii][Tt][Rr][Ff][-_ ]?[0-9]{2,4})";
150
151 /** Regular expression matching end of line. */
152 private static final String END = "$";
153
154 /** {@inheritDoc} */
155 public boolean stillAcceptsData() {
156 return configurations.isEmpty();
157 }
158
159 /** {@inheritDoc} */
160 public void loadData(final InputStream input, final String name)
161 throws IOException {
162
163 // regular expressions for date lines
164 final Pattern patternII = Pattern.compile(START + NON_BLANK_FIELD + INFINITY_DATE + INFINITY_DATE + ITRF + END);
165 final Pattern patternID = Pattern.compile(START + NON_BLANK_FIELD + INFINITY_DATE + CALENDAR_DATE + ITRF + END);
166 final Pattern patternDI = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + INFINITY_DATE + ITRF + END);
167 final Pattern patternDD = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + CALENDAR_DATE + ITRF + END);
168
169
170 int lineNumber = 0;
171 String line = null;
172 // set up a reader for line-oriented bulletin A files
173 try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
174 for (line = reader.readLine(); line != null; line = reader.readLine()) {
175
176 lineNumber++;
177 line = line.trim();
178 if (!(line.startsWith("#") || line.isEmpty())) {
179 String prefix = null;
180 ITRFVersion version = null;
181 int validityStart = Integer.MIN_VALUE;
182 int validityEnd = Integer.MAX_VALUE;
183 final Matcher matcherII = patternII.matcher(line);
184 if (matcherII.matches()) {
185 // both start and end of validity are at infinity
186 // the ITRF version applies throughout history
187 prefix = matcherII.group(1);
188 version = ITRFVersion.getITRFVersion(matcherII.group(2));
189 } else {
190 final Matcher matcherID = patternID.matcher(line);
191 if (matcherID.matches()) {
192 // both start of validity is at infinity
193 // the ITRF version applies in the far past
194 prefix = matcherID.group(1);
195 validityEnd = new DateComponents(Integer.parseInt(matcherID.group(2)),
196 Integer.parseInt(matcherID.group(3)),
197 Integer.parseInt(matcherID.group(4))).getMJD();
198 version = ITRFVersion.getITRFVersion(matcherID.group(5));
199 } else {
200 final Matcher matcherDI = patternDI.matcher(line);
201 if (matcherDI.matches()) {
202 // both end of validity is at infinity
203 // the ITRF version applies to the upcoming future
204 prefix = matcherDI.group(1);
205 validityStart = new DateComponents(Integer.parseInt(matcherDI.group(2)),
206 Integer.parseInt(matcherDI.group(3)),
207 Integer.parseInt(matcherDI.group(4))).getMJD();
208 version = ITRFVersion.getITRFVersion(matcherDI.group(5));
209 } else {
210 final Matcher matcherDD = patternDD.matcher(line);
211 if (matcherDD.matches()) {
212 // the ITRF version applies during a limited range
213 prefix = matcherDD.group(1);
214 validityStart = new DateComponents(Integer.parseInt(matcherDD.group(2)),
215 Integer.parseInt(matcherDD.group(3)),
216 Integer.parseInt(matcherDD.group(4))).getMJD();
217 validityEnd = new DateComponents(Integer.parseInt(matcherDD.group(5)),
218 Integer.parseInt(matcherDD.group(6)),
219 Integer.parseInt(matcherDD.group(7))).getMJD();
220 version = ITRFVersion.getITRFVersion(matcherDD.group(8));
221 } else {
222 // data line was not recognized
223 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
224 lineNumber, name, line);
225 }
226 }
227 }
228 }
229 // error if prefix contains / or \ since these will never match
230 // CHECKSTYLE: stop MultipleStringLiterals check
231 if (prefix.contains("\\") || prefix.contains("/")) {
232 throw new OrekitException(
233 OrekitMessages.ITRF_VERSIONS_PREFIX_ONLY, prefix);
234 }
235 // CHECKSTYLE: resume MultipleStringLiterals check
236 // store the parsed entry
237 configurations.add(new ITRFVersionConfiguration(prefix, version, validityStart, validityEnd));
238
239 }
240
241 }
242 } catch (IllegalArgumentException e) {
243 throw new OrekitException(e,
244 OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
245 lineNumber, name, line);
246 }
247
248 }
249
250 }
251
252 /** ITRF version configuration entry. */
253 public static class ITRFVersionConfiguration {
254
255 /** File names to which this configuration applies. */
256 private final String prefix;
257
258 /** ITRF version. */
259 private final ITRFVersion version;
260
261 /** Start of validity. */
262 private final int validityStart;
263
264 /** End of validity. */
265 private final int validityEnd;
266
267 /** Simple constructor.
268 * @param prefix file names to which this configuration applies
269 * @param version ITRF version
270 * @param validityStart start of validity MJD (included)
271 * @param validityEnd end of validity MJD (excluded)
272 */
273 public ITRFVersionConfiguration(final String prefix,
274 final ITRFVersion version,
275 final int validityStart,
276 final int validityEnd) {
277 this.prefix = prefix;
278 this.version = version;
279 this.validityStart = validityStart;
280 this.validityEnd = validityEnd;
281 }
282
283 /** Check if this entry applies to a file name.
284 * @param name file name to check
285 * @return true if the configuration applies to the specified file
286 */
287 public boolean appliesTo(final String name) {
288 final int i = FastMath.max(name.lastIndexOf("/"), name.lastIndexOf("\\"));
289 return name.startsWith(prefix, i + 1);
290 }
291
292 /** Get ITRF version.
293 * @return ITRF version
294 */
295 public ITRFVersion getVersion() {
296 return version;
297 }
298
299 /** Check if configuration entry is valid for a date.
300 * @param mjd date to check in modified Julian day
301 * @return true if entry is valid for the specified date
302 */
303 public boolean isValid(final int mjd) {
304 return validityStart <= mjd && mjd < validityEnd;
305 }
306
307 }
308
309 }