ITRFVersionLoader.java

  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. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.Pattern;

  27. import org.hipparchus.util.FastMath;
  28. import org.orekit.annotation.DefaultDataContext;
  29. import org.orekit.data.DataContext;
  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. import org.orekit.time.DateComponents;

  35. /** Loader for ITRF version configuration file.
  36.  * <p>
  37.  * The ITRF version configuration file specifies
  38.  * the {@link ITRFVersion ITRF versions} that each
  39.  * type of Earth Orientation Parameter file contains
  40.  * for each date. This configuration file is used to
  41.  * interpret {@link EopC04FilesLoader EOP C04} files,
  42.  * {@link BulletinAFilesLoader Bulletin A} files,
  43.  * {@link BulletinBFilesLoader Bulletin B} files,
  44.  * {@link RapidDataAndPredictionColumnsLoader rapid data
  45.  * and prediction files in columns format} files,
  46.  * {@link EopXmlLoader rapid data
  47.  * and prediction files in XML format} files...
  48.  * </p>
  49.  * <p>This file is an Orekit-specific configuration file.
  50.  * </p>
  51.  * <p>
  52.  * This class is immutable and hence thread-safe
  53.  * </p>
  54.  * @see EopC04FilesLoader
  55.  * @see BulletinAFilesLoader
  56.  * @see BulletinBFilesLoader
  57.  * @see RapidDataAndPredictionColumnsLoader
  58.  * @see EopXmlLoader
  59.  * @author Luc Maisonobe
  60.  * @since 9.2
  61.  */
  62. public class ITRFVersionLoader implements ItrfVersionProvider {

  63.     /** Regular expression for supported files names. */
  64.     public static final String SUPPORTED_NAMES = "itrf-versions.conf";

  65.     /** Default entry to use if no suitable configuration is found. */
  66.     private static final ITRFVersionConfiguration DEFAULT =
  67.                     new ITRFVersionConfiguration("", ITRFVersion.ITRF_2014,
  68.                                                  Integer.MIN_VALUE, Integer.MAX_VALUE);

  69.     /** Configuration. */
  70.     private final List<ITRFVersionConfiguration> configurations;

  71.     /**
  72.      * Build a loader for ITRF version configuration file. This constructor uses the
  73.      * {@link DataContext#getDefault() default data context}.
  74.      *
  75.      * @param supportedNames regular expression for supported files names
  76.      * @see #ITRFVersionLoader(String, DataProvidersManager)
  77.      */
  78.     @DefaultDataContext
  79.     public ITRFVersionLoader(final String supportedNames) {
  80.         this(supportedNames, DataContext.getDefault().getDataProvidersManager());
  81.     }

  82.     /**
  83.      * Build a loader for ITRF version configuration file.
  84.      *
  85.      * @param supportedNames       regular expression for supported files names
  86.      * @param dataProvidersManager provides access to the {@code itrf-versions.conf}
  87.      *                             file.
  88.      */
  89.     public ITRFVersionLoader(final String supportedNames,
  90.                              final DataProvidersManager dataProvidersManager) {
  91.         this.configurations = new ArrayList<>();
  92.         dataProvidersManager.feed(supportedNames, new Parser());
  93.     }

  94.     /**
  95.      * Build a loader for ITRF version configuration file using the default name. This
  96.      * constructor uses the {@link DataContext#getDefault() default data context}.
  97.      *
  98.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  99.      *
  100.      * @see #ITRFVersionLoader(String)
  101.      * @see #ITRFVersionLoader(String, DataProvidersManager)
  102.      * @see #SUPPORTED_NAMES
  103.      */
  104.     @DefaultDataContext
  105.     public ITRFVersionLoader() {
  106.         this(SUPPORTED_NAMES);
  107.     }

  108.     @Override
  109.     public ITRFVersionConfiguration getConfiguration(final String name, final int mjd) {

  110.         for (final ITRFVersionConfiguration configuration : configurations) {
  111.             if (configuration.appliesTo(name) && configuration.isValid(mjd)) {
  112.                 // we have found a matching configuration
  113.                 return configuration;
  114.             }
  115.         }

  116.         // no suitable configuration found, use the default value
  117.         return DEFAULT;

  118.     }

  119.     /** Internal class performing the parsing. */
  120.     private class Parser implements DataLoader {

  121.         /** Regular expression matching start of line. */
  122.         private static final String START  = "^";

  123.         /** Regular expression matching a non-blank field (for names regexp). */
  124.         private static final String NON_BLANK_FIELD = "(\\S+)";

  125.         /** Regular expression matching a calendar date. */
  126.         private static final String CALENDAR_DATE  = "\\s+(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)";

  127.         /** Regular expression matching a date at infinity. */
  128.         private static final String INFINITY_DATE  = "\\s+-+";

  129.         /** Regular expression matching an ITRF version. */
  130.         private static final String ITRF  = "\\s+([Ii][Tt][Rr][Ff][-_ ]?[0-9]{2,4})";

  131.         /** Regular expression matching end of line. */
  132.         private static final String END  = "$";

  133.         /** {@inheritDoc} */
  134.         public boolean stillAcceptsData() {
  135.             return configurations.isEmpty();
  136.         }

  137.         /** {@inheritDoc} */
  138.         public void loadData(final InputStream input, final String name)
  139.             throws IOException {

  140.             // regular expressions for date lines
  141.             final Pattern patternII = Pattern.compile(START + NON_BLANK_FIELD + INFINITY_DATE + INFINITY_DATE + ITRF + END);
  142.             final Pattern patternID = Pattern.compile(START + NON_BLANK_FIELD + INFINITY_DATE + CALENDAR_DATE + ITRF + END);
  143.             final Pattern patternDI = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + INFINITY_DATE + ITRF + END);
  144.             final Pattern patternDD = Pattern.compile(START + NON_BLANK_FIELD + CALENDAR_DATE + CALENDAR_DATE + ITRF + END);


  145.             int lineNumber =  0;
  146.             String line = null;
  147.             // set up a reader for line-oriented bulletin A files
  148.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  149.                 for (line = reader.readLine(); line != null; line = reader.readLine()) {

  150.                     lineNumber++;
  151.                     line = line.trim();
  152.                     if (!(line.startsWith("#") || line.isEmpty())) {
  153.                         String prefix       = null;
  154.                         ITRFVersion version = null;
  155.                         int validityStart   = Integer.MIN_VALUE;
  156.                         int validityEnd     = Integer.MAX_VALUE;
  157.                         final Matcher matcherII = patternII.matcher(line);
  158.                         if (matcherII.matches()) {
  159.                             // both start and end of validity are at infinity
  160.                             // the ITRF version applies throughout history
  161.                             prefix  = matcherII.group(1);
  162.                             version = ITRFVersion.getITRFVersion(matcherII.group(2));
  163.                         } else {
  164.                             final Matcher matcherID = patternID.matcher(line);
  165.                             if (matcherID.matches()) {
  166.                                 // both start of validity is at infinity
  167.                                 // the ITRF version applies in the far past
  168.                                 prefix      = matcherID.group(1);
  169.                                 validityEnd = new DateComponents(Integer.parseInt(matcherID.group(2)),
  170.                                                                  Integer.parseInt(matcherID.group(3)),
  171.                                                                  Integer.parseInt(matcherID.group(4))).getMJD();
  172.                                 version     = ITRFVersion.getITRFVersion(matcherID.group(5));
  173.                             } else {
  174.                                 final Matcher matcherDI = patternDI.matcher(line);
  175.                                 if (matcherDI.matches()) {
  176.                                     // both end of validity is at infinity
  177.                                     // the ITRF version applies to the upcoming future
  178.                                     prefix        = matcherDI.group(1);
  179.                                     validityStart = new DateComponents(Integer.parseInt(matcherDI.group(2)),
  180.                                                                        Integer.parseInt(matcherDI.group(3)),
  181.                                                                        Integer.parseInt(matcherDI.group(4))).getMJD();
  182.                                     version       = ITRFVersion.getITRFVersion(matcherDI.group(5));
  183.                                 } else {
  184.                                     final Matcher matcherDD = patternDD.matcher(line);
  185.                                     if (matcherDD.matches()) {
  186.                                         // the ITRF version applies during a limited range
  187.                                         prefix        = matcherDD.group(1);
  188.                                         validityStart = new DateComponents(Integer.parseInt(matcherDD.group(2)),
  189.                                                                            Integer.parseInt(matcherDD.group(3)),
  190.                                                                            Integer.parseInt(matcherDD.group(4))).getMJD();
  191.                                         validityEnd   = new DateComponents(Integer.parseInt(matcherDD.group(5)),
  192.                                                                            Integer.parseInt(matcherDD.group(6)),
  193.                                                                            Integer.parseInt(matcherDD.group(7))).getMJD();
  194.                                         version       = ITRFVersion.getITRFVersion(matcherDD.group(8));
  195.                                     } else {
  196.                                         // data line was not recognized
  197.                                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  198.                                                                   lineNumber, name, line);
  199.                                     }
  200.                                 }
  201.                             }
  202.                         }
  203.                         // error if prefix contains / or \ since these will never match
  204.                         // CHECKSTYLE: stop MultipleStringLiterals check
  205.                         if (prefix.contains("\\") || prefix.contains("/")) {
  206.                             throw new OrekitException(
  207.                                     OrekitMessages.ITRF_VERSIONS_PREFIX_ONLY, prefix);
  208.                         }
  209.                         // CHECKSTYLE: resume MultipleStringLiterals check
  210.                         // store the parsed entry
  211.                         configurations.add(new ITRFVersionConfiguration(prefix, version, validityStart, validityEnd));

  212.                     }

  213.                 }
  214.             } catch (IllegalArgumentException e) {
  215.                 throw new OrekitException(e,
  216.                                           OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  217.                                           lineNumber, name, line);
  218.             }

  219.         }

  220.     }

  221.     /** ITRF version configuration entry. */
  222.     public static class ITRFVersionConfiguration {

  223.         /** File names to which this configuration applies. */
  224.         private final String prefix;

  225.         /** ITRF version. */
  226.         private final ITRFVersion version;

  227.         /** Start of validity. */
  228.         private final int validityStart;

  229.         /** End of validity. */
  230.         private final int validityEnd;

  231.         /** Simple constructor.
  232.          * @param prefix file names to which this configuration applies
  233.          * @param version ITRF version
  234.          * @param validityStart start of validity MJD (included)
  235.          * @param validityEnd end of validity MJD (excluded)
  236.          */
  237.         public ITRFVersionConfiguration(final String prefix,
  238.                                         final ITRFVersion version,
  239.                                         final int validityStart,
  240.                                         final int validityEnd) {
  241.             this.prefix        = prefix;
  242.             this.version       = version;
  243.             this.validityStart = validityStart;
  244.             this.validityEnd   = validityEnd;
  245.         }

  246.         /** Check if this entry applies to a file name.
  247.          * @param name file name to check
  248.          * @return true if the configuration applies to the specified file
  249.          */
  250.         public boolean appliesTo(final String name) {
  251.             final int i = FastMath.max(name.lastIndexOf("/"), name.lastIndexOf("\\"));
  252.             return name.startsWith(prefix, i + 1);
  253.         }

  254.         /** Get ITRF version.
  255.          * @return ITRF version
  256.          */
  257.         public ITRFVersion getVersion() {
  258.             return version;
  259.         }

  260.         /** Check if configuration entry is valid for a date.
  261.          * @param mjd date to check in modified Julian day
  262.          * @return true if entry is valid for the specified date
  263.          */
  264.         public boolean isValid(final int mjd) {
  265.             return validityStart <= mjd && mjd < validityEnd;
  266.         }

  267.     }

  268. }