ITRFVersionLoader.java
/* Copyright 2002-2020 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.frames;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hipparchus.util.FastMath;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.data.DataLoader;
import org.orekit.data.DataProvidersManager;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.DateComponents;
/** Loader for ITRF version configuration file.
* <p>
* The ITRF version configuration file specifies
* the {@link ITRFVersion ITRF versions} that each
* type of Earth Orientation Parameter file contains
* for each date. This configuration file is used to
* interpret {@link EOPC04FilesLoader EOP C04} files,
* {@link BulletinAFilesLoader Bulletin A} files,
* {@link BulletinBFilesLoader Bulletin B} files,
* {@link RapidDataAndPredictionColumnsLoader rapid data
* and prediction files in columns format} files,
* {@link RapidDataAndPredictionXMLLoader rapid data
* and prediction files in XML format} files...
* </p>
* <p>This file is an Orekit-specific configuration file.
* </p>
* <p>
* This class is immutable and hence thread-safe
* </p>
* @see EOPC04FilesLoader
* @see BulletinAFilesLoader
* @see BulletinBFilesLoader
* @see RapidDataAndPredictionColumnsLoader
* @see RapidDataAndPredictionXMLLoader
* @author Luc Maisonobe
* @since 9.2
*/
public class ITRFVersionLoader implements ItrfVersionProvider {
/** Regular expression for supported files names. */
public static final String SUPPORTED_NAMES = "itrf-versions.conf";
/** Default entry to use if no suitable configuration is found. */
private static final ITRFVersionConfiguration DEFAULT =
new ITRFVersionConfiguration("", ITRFVersion.ITRF_2014,
Integer.MIN_VALUE, Integer.MAX_VALUE);
/** Configuration. */
private final List<ITRFVersionConfiguration> configurations;
/**
* Build a loader for ITRF version configuration file. This constructor uses the
* {@link DataContext#getDefault() default data context}.
*
* @param supportedNames regular expression for supported files names
* @see #ITRFVersionLoader(String, DataProvidersManager)
*/
@DefaultDataContext
public ITRFVersionLoader(final String supportedNames) {
this(supportedNames, DataContext.getDefault().getDataProvidersManager());
}
/**
* Build a loader for ITRF version configuration file.
*
* @param supportedNames regular expression for supported files names
* @param dataProvidersManager provides access to the {@code itrf-versions.conf}
* file.
*/
public ITRFVersionLoader(final String supportedNames,
final DataProvidersManager dataProvidersManager) {
this.configurations = new ArrayList<>();
dataProvidersManager.feed(supportedNames, new Parser());
}
/**
* Build a loader for ITRF version configuration file using the default name. This
* constructor uses the {@link DataContext#getDefault() default data context}.
*
* <p>This constructor uses the {@link DataContext#getDefault() default data context}.
*
* @see #ITRFVersionLoader(String)
* @see #ITRFVersionLoader(String, DataProvidersManager)
* @see #SUPPORTED_NAMES
*/
@DefaultDataContext
public ITRFVersionLoader() {
this(SUPPORTED_NAMES);
}
@Override
public ITRFVersionConfiguration getConfiguration(final String name, final int mjd) {
for (final ITRFVersionConfiguration configuration : configurations) {
if (configuration.appliesTo(name) && configuration.isValid(mjd)) {
// we have found a matching configuration
return configuration;
}
}
// no suitable configuration found, use the default value
return DEFAULT;
}
/** Internal class performing the parsing. */
private class Parser implements DataLoader {
/** Regular expression matching start of line. */
private static final String START = "^";
/** Regular expression matching a non-blank field (for names regexp). */
private static final String NON_BLANK_FIELD = "(\\S+)";
/** Regular expression matching a calendar date. */
private static final String CALENDAR_DATE = "\\s+(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)";
/** Regular expression matching a date at infinity. */
private static final String INFINITY_DATE = "\\s+-+";
/** Regular expression matching an ITRF version. */
private static final String ITRF = "\\s+(ITRF-\\d\\d(?:\\d\\d)?)";
/** Regular expression matching end of line. */
private static final String END = "$";
/** {@inheritDoc} */
public boolean stillAcceptsData() {
return configurations.isEmpty();
}
/** {@inheritDoc} */
public void loadData(final InputStream input, final String name)
throws IOException {
// regular expressions for date lines
final Pattern patternII = Pattern.compile(START + NON_BLANK_FIELD + INFINITY_DATE + INFINITY_DATE + ITRF + END);
final Pattern patternID = Pattern.compile(START + NON_BLANK_FIELD + INFINITY_DATE + CALENDAR_DATE + ITRF + END);
final Pattern patternDI = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + INFINITY_DATE + ITRF + END);
final Pattern patternDD = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + CALENDAR_DATE + ITRF + END);
int lineNumber = 0;
String line = null;
// set up a reader for line-oriented bulletin A files
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
for (line = reader.readLine(); line != null; line = reader.readLine()) {
lineNumber++;
line = line.trim();
if (!(line.startsWith("#") || line.isEmpty())) {
String prefix = null;
ITRFVersion version = null;
int validityStart = Integer.MIN_VALUE;
int validityEnd = Integer.MAX_VALUE;
final Matcher matcherII = patternII.matcher(line);
if (matcherII.matches()) {
// both start and end of validity are at infinity
// the ITRF version applies throughout history
prefix = matcherII.group(1);
version = ITRFVersion.getITRFVersion(matcherII.group(2));
} else {
final Matcher matcherID = patternID.matcher(line);
if (matcherID.matches()) {
// both start of validity is at infinity
// the ITRF version applies in the far past
prefix = matcherID.group(1);
validityEnd = new DateComponents(Integer.parseInt(matcherID.group(2)),
Integer.parseInt(matcherID.group(3)),
Integer.parseInt(matcherID.group(4))).getMJD();
version = ITRFVersion.getITRFVersion(matcherID.group(5));
} else {
final Matcher matcherDI = patternDI.matcher(line);
if (matcherDI.matches()) {
// both end of validity is at infinity
// the ITRF version applies to the upcoming future
prefix = matcherDI.group(1);
validityStart = new DateComponents(Integer.parseInt(matcherDI.group(2)),
Integer.parseInt(matcherDI.group(3)),
Integer.parseInt(matcherDI.group(4))).getMJD();
version = ITRFVersion.getITRFVersion(matcherDI.group(5));
} else {
final Matcher matcherDD = patternDD.matcher(line);
if (matcherDD.matches()) {
// the ITRF version applies during a limited range
prefix = matcherDD.group(1);
validityStart = new DateComponents(Integer.parseInt(matcherDD.group(2)),
Integer.parseInt(matcherDD.group(3)),
Integer.parseInt(matcherDD.group(4))).getMJD();
validityEnd = new DateComponents(Integer.parseInt(matcherDD.group(5)),
Integer.parseInt(matcherDD.group(6)),
Integer.parseInt(matcherDD.group(7))).getMJD();
version = ITRFVersion.getITRFVersion(matcherDD.group(8));
} else {
// data line was not recognized
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, name, line);
}
}
}
}
// error if prefix contains / or \ since these will never match
// CHECKSTYLE: stop MultipleStringLiterals check
if (prefix.contains("\\") || prefix.contains("/")) {
throw new OrekitException(
OrekitMessages.ITRF_VERSIONS_PREFIX_ONLY, prefix);
}
// CHECKSTYLE: resume MultipleStringLiterals check
// store the parsed entry
configurations.add(new ITRFVersionConfiguration(prefix, version, validityStart, validityEnd));
}
}
} catch (IllegalArgumentException e) {
throw new OrekitException(e,
OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, name, line);
}
}
}
/** ITRF version configuration entry. */
public static class ITRFVersionConfiguration {
/** File names to which this configuration applies. */
private final String prefix;
/** ITRF version. */
private final ITRFVersion version;
/** Start of validity. */
private final int validityStart;
/** End of validity. */
private final int validityEnd;
/** Simple constructor.
* @param prefix file names to which this configuration applies
* @param version ITRF version
* @param validityStart start of validity MJD (included)
* @param validityEnd end of validity MJD (excluded)
*/
public ITRFVersionConfiguration(final String prefix,
final ITRFVersion version,
final int validityStart,
final int validityEnd) {
this.prefix = prefix;
this.version = version;
this.validityStart = validityStart;
this.validityEnd = validityEnd;
}
/** Check if this entry applies to a file name.
* @param name file name to check
* @return true if the configuration applies to the specified file
*/
public boolean appliesTo(final String name) {
final int i = FastMath.max(name.lastIndexOf("/"), name.lastIndexOf("\\"));
return name.startsWith(prefix, i + 1);
}
/** Get ITRF version.
* @return ITRF version
*/
public ITRFVersion getVersion() {
return version;
}
/** Check if configuration entry is valid for a date.
* @param mjd date to check in modified Julian day
* @return true if entry is valid for the specified date
*/
public boolean isValid(final int mjd) {
return validityStart <= mjd && mjd < validityEnd;
}
}
}