SinexLoader.java

  1. /* Copyright 2002-2022 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.files.sinex;

  18. import java.io.BufferedInputStream;
  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.text.ParseException;
  25. import java.util.Collections;
  26. import java.util.HashMap;
  27. import java.util.Map;
  28. import java.util.regex.Pattern;

  29. import org.hipparchus.exception.DummyLocalizable;
  30. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  31. import org.hipparchus.util.FastMath;
  32. import org.orekit.annotation.DefaultDataContext;
  33. import org.orekit.data.DataContext;
  34. import org.orekit.data.DataLoader;
  35. import org.orekit.data.DataProvidersManager;
  36. import org.orekit.data.DataSource;
  37. import org.orekit.errors.OrekitException;
  38. import org.orekit.errors.OrekitMessages;
  39. import org.orekit.files.sinex.Station.ReferenceSystem;
  40. import org.orekit.time.AbsoluteDate;
  41. import org.orekit.time.DateComponents;
  42. import org.orekit.time.TimeScale;
  43. import org.orekit.utils.Constants;

  44. /**
  45.  * Loader for Solution INdependent EXchange (SINEX) files.
  46.  * <p>
  47.  * For now only few keys are supported: SITE/ID, SITE/ECCENTRICITY, SOLUTION/EPOCHS and SOLUTION/ESTIMATE.
  48.  * They represent the minimum set of parameters that are interesting to consider in a SINEX file.
  49.  * </p>
  50.  * @author Bryan Cazabonne
  51.  * @since 10.3
  52.  */
  53. public class SinexLoader {

  54.     /** 00:000:00000 epoch. */
  55.     private static final String DEFAULT_EPOCH = "00:000:00000";

  56.     /** Pattern for delimiting regular expressions. */
  57.     private static final Pattern SEPARATOR = Pattern.compile(":");

  58.     /** Station data.
  59.      * Key: Site code
  60.      */
  61.     private final Map<String, Station> stations;

  62.     /** UTC time scale. */
  63.     private final TimeScale utc;

  64.     /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
  65.      * default data context}.
  66.      * @param supportedNames regular expression for supported files names
  67.      * @see #SinexLoader(String, DataProvidersManager, TimeScale)
  68.      */
  69.     @DefaultDataContext
  70.     public SinexLoader(final String supportedNames) {
  71.         this(supportedNames,
  72.              DataContext.getDefault().getDataProvidersManager(),
  73.              DataContext.getDefault().getTimeScales().getUTC());
  74.     }

  75.     /**
  76.      * Construct a loader by specifying the source of SINEX auxiliary data files.
  77.      * @param supportedNames regular expression for supported files names
  78.      * @param dataProvidersManager provides access to auxiliary data.
  79.      * @param utc UTC time scale
  80.      */
  81.     public SinexLoader(final String supportedNames,
  82.                        final DataProvidersManager dataProvidersManager,
  83.                        final TimeScale utc) {
  84.         this.utc = utc;
  85.         stations = new HashMap<>();
  86.         dataProvidersManager.feed(supportedNames, new Parser());
  87.     }

  88.     /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
  89.      * default data context}.
  90.      * @param source source for the RINEX data
  91.      * @see #SinexLoader(String, DataProvidersManager, TimeScale)
  92.      */
  93.     @DefaultDataContext
  94.     public SinexLoader(final DataSource source) {
  95.         this(source, DataContext.getDefault().getTimeScales().getUTC());
  96.     }

  97.     /**
  98.      * Loads SINEX from the given input stream using the specified auxiliary data.
  99.      * @param source source for the RINEX data
  100.      * @param utc UTC time scale
  101.      */
  102.     public SinexLoader(final DataSource source, final TimeScale utc) {
  103.         try {
  104.             this.utc = utc;
  105.             stations = new HashMap<>();
  106.             try (InputStream         is  = source.getOpener().openStreamOnce();
  107.                  BufferedInputStream bis = new BufferedInputStream(is)) {
  108.                 new Parser().loadData(bis, source.getName());
  109.             }
  110.         } catch (IOException | ParseException ioe) {
  111.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  112.         }
  113.     }

  114.     /**
  115.      * Get the parsed station data.
  116.      * @return unmodifiable view of parsed station data
  117.      */
  118.     public Map<String, Station> getStations() {
  119.         return Collections.unmodifiableMap(stations);
  120.     }

  121.     /**
  122.      * Get the station corresponding to the given site code.
  123.      * @param siteCode site code
  124.      * @return the corresponding station
  125.      */
  126.     public Station getStation(final String siteCode) {
  127.         return stations.get(siteCode);
  128.     }

  129.     /**
  130.      * Add a new entry to the map of stations.
  131.      * @param station station entry to add
  132.      */
  133.     private void addStation(final Station station) {
  134.         // Check if station already exists
  135.         if (stations.get(station.getSiteCode()) == null) {
  136.             stations.put(station.getSiteCode(), station);
  137.         }
  138.     }

  139.     /** Parser for SINEX files. */
  140.     private class Parser implements DataLoader {

  141.         /** Start character of a comment line. */
  142.         private static final String COMMENT = "*";

  143.         /** {@inheritDoc} */
  144.         @Override
  145.         public boolean stillAcceptsData() {
  146.             // We load all SINEX files we can find
  147.             return true;
  148.         }

  149.         /** {@inheritDoc} */
  150.         @Override
  151.         public void loadData(final InputStream input, final String name)
  152.             throws IOException, ParseException {

  153.             // Useful parameters
  154.             int lineNumber     = 0;
  155.             String line        = null;
  156.             boolean inId       = false;
  157.             boolean inEcc      = false;
  158.             boolean inEpoch    = false;
  159.             boolean inEstimate = false;
  160.             boolean firstEcc   = true;
  161.             Vector3D position  = Vector3D.ZERO;
  162.             Vector3D velocity  = Vector3D.ZERO;

  163.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

  164.                 // Loop on lines
  165.                 for (line = reader.readLine(); line != null; line = reader.readLine()) {
  166.                     ++lineNumber;
  167.                     // For now, only few keys are supported
  168.                     // They represent the minimum set of parameters that are interesting to consider in a SINEX file
  169.                     // Other keys can be added depending user needs
  170.                     switch(line.trim()) {
  171.                         case "+SITE/ID" :
  172.                             // Start of site id. data
  173.                             inId = true;
  174.                             break;
  175.                         case "-SITE/ID" :
  176.                             // End of site id. data
  177.                             inId = false;
  178.                             break;
  179.                         case "+SITE/ECCENTRICITY" :
  180.                             // Start of antenna eccentricities data
  181.                             inEcc = true;
  182.                             break;
  183.                         case "-SITE/ECCENTRICITY" :
  184.                             // End of antenna eccentricities data
  185.                             inEcc = false;
  186.                             break;
  187.                         case "+SOLUTION/EPOCHS" :
  188.                             // Start of epoch data
  189.                             inEpoch = true;
  190.                             break;
  191.                         case "-SOLUTION/EPOCHS" :
  192.                             // End of epoch data
  193.                             inEpoch = false;
  194.                             break;
  195.                         case "+SOLUTION/ESTIMATE" :
  196.                             // Start of coordinates data
  197.                             inEstimate = true;
  198.                             break;
  199.                         case "-SOLUTION/ESTIMATE" :
  200.                             // Start of coordinates data
  201.                             inEstimate = false;
  202.                             break;
  203.                         default:
  204.                             if (line.startsWith(COMMENT)) {
  205.                                 // ignore that line
  206.                             } else {
  207.                                 // parsing data
  208.                                 if (inId) {
  209.                                     // read site id. data
  210.                                     final Station station = new Station();
  211.                                     station.setSiteCode(parseString(line, 1, 4));
  212.                                     station.setDomes(parseString(line, 9, 9));
  213.                                     // add the station to the map
  214.                                     addStation(station);
  215.                                 } else if (inEcc) {

  216.                                     // read antenna eccentricities data
  217.                                     final Station station = getStation(parseString(line, 1, 4));

  218.                                     // check if it is the first eccentricity entry for this station
  219.                                     if (station.getEccentricitiesTimeSpanMap().getSpansNumber() == 1) {
  220.                                         // we are parsing eccentricity data for a new station
  221.                                         firstEcc = true;
  222.                                     }

  223.                                     // start and end of validity for the current entry
  224.                                     final AbsoluteDate start = stringEpochToAbsoluteDate(parseString(line, 16, 12));
  225.                                     final AbsoluteDate end   = stringEpochToAbsoluteDate(parseString(line, 29, 12));

  226.                                     // reference system UNE or XYZ
  227.                                     station.setEccRefSystem(ReferenceSystem.getEccRefSystem(parseString(line, 42, 3)));

  228.                                     // eccentricity vector
  229.                                     final Vector3D eccStation = new Vector3D(parseDouble(line, 46, 8),
  230.                                                                              parseDouble(line, 55, 8),
  231.                                                                              parseDouble(line, 64, 8));

  232.                                     // special implementation for the first entry
  233.                                     if (firstEcc) {
  234.                                         // we want null values outside validity limits of the station
  235.                                         station.addStationEccentricitiesValidBefore(eccStation, end);
  236.                                         station.addStationEccentricitiesValidBefore(null,       start);
  237.                                         // we parsed the first entry, set the flag to false
  238.                                         firstEcc = false;
  239.                                     } else {
  240.                                         station.addStationEccentricitiesValidBefore(eccStation, end);
  241.                                     }

  242.                                     // update the last known eccentricities entry
  243.                                     station.setEccentricities(eccStation);

  244.                                 } else if (inEpoch) {
  245.                                     // read epoch data
  246.                                     final Station station = getStation(parseString(line, 1, 4));
  247.                                     station.setValidFrom(stringEpochToAbsoluteDate(parseString(line, 16, 12)));
  248.                                     station.setValidUntil(stringEpochToAbsoluteDate(parseString(line, 29, 12)));
  249.                                 } else if (inEstimate) {
  250.                                     final Station station = getStation(parseString(line, 14, 4));
  251.                                     // check if this station exists
  252.                                     if (station != null) {
  253.                                         // switch on coordinates data
  254.                                         switch(parseString(line, 7, 6)) {
  255.                                             case "STAX":
  256.                                                 // station X coordinate
  257.                                                 final double x = parseDouble(line, 47, 22);
  258.                                                 position = new Vector3D(x, position.getY(), position.getZ());
  259.                                                 station.setPosition(position);
  260.                                                 break;
  261.                                             case "STAY":
  262.                                                 // station Y coordinate
  263.                                                 final double y = parseDouble(line, 47, 22);
  264.                                                 position = new Vector3D(position.getX(), y, position.getZ());
  265.                                                 station.setPosition(position);
  266.                                                 break;
  267.                                             case "STAZ":
  268.                                                 // station Z coordinate
  269.                                                 final double z = parseDouble(line, 47, 22);
  270.                                                 position = new Vector3D(position.getX(), position.getY(), z);
  271.                                                 station.setPosition(position);
  272.                                                 // set the reference epoch (identical for all coordinates)
  273.                                                 station.setEpoch(stringEpochToAbsoluteDate(parseString(line, 27, 12)));
  274.                                                 // reset position vector
  275.                                                 position = Vector3D.ZERO;
  276.                                                 break;
  277.                                             case "VELX":
  278.                                                 // station X velocity (value is in m/y)
  279.                                                 final double vx = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
  280.                                                 velocity = new Vector3D(vx, velocity.getY(), velocity.getZ());
  281.                                                 station.setVelocity(velocity);
  282.                                                 break;
  283.                                             case "VELY":
  284.                                                 // station Y velocity (value is in m/y)
  285.                                                 final double vy = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
  286.                                                 velocity = new Vector3D(velocity.getX(), vy, velocity.getZ());
  287.                                                 station.setVelocity(velocity);
  288.                                                 break;
  289.                                             case "VELZ":
  290.                                                 // station Z velocity (value is in m/y)
  291.                                                 final double vz = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
  292.                                                 velocity = new Vector3D(velocity.getX(), velocity.getY(), vz);
  293.                                                 station.setVelocity(velocity);
  294.                                                 // reset position vector
  295.                                                 velocity = Vector3D.ZERO;
  296.                                                 break;
  297.                                             default:
  298.                                                 // ignore that field
  299.                                                 break;
  300.                                         }
  301.                                     }

  302.                                 } else {
  303.                                     // not supported line, ignore it
  304.                                 }
  305.                             }
  306.                             break;
  307.                     }
  308.                 }

  309.             } catch (NumberFormatException nfe) {
  310.                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  311.                                           lineNumber, name, line);
  312.             }

  313.         }

  314.         /** Extract a string from a line.
  315.          * @param line to parse
  316.          * @param start start index of the string
  317.          * @param length length of the string
  318.          * @return parsed string
  319.          */
  320.         private String parseString(final String line, final int start, final int length) {
  321.             return line.substring(start, FastMath.min(line.length(), start + length)).trim();
  322.         }

  323.         /** Extract a double from a line.
  324.          * @param line to parse
  325.          * @param start start index of the real
  326.          * @param length length of the real
  327.          * @return parsed real
  328.          */
  329.         private double parseDouble(final String line, final int start, final int length) {
  330.             return Double.parseDouble(parseString(line, start, length));
  331.         }

  332.     }

  333.     /**
  334.      * Transform a String epoch to an AbsoluteDate.
  335.      * @param stringDate string epoch
  336.      * @return the corresponding AbsoluteDate
  337.      */
  338.     private AbsoluteDate stringEpochToAbsoluteDate(final String stringDate) {

  339.         // Deal with 00:000:00000 epochs
  340.         if (DEFAULT_EPOCH.equals(stringDate)) {
  341.             // Data is still available, return a dummy date at infinity in the future direction
  342.             return AbsoluteDate.FUTURE_INFINITY;
  343.         }

  344.         // Date components
  345.         final String[] fields = SEPARATOR.split(stringDate);

  346.         // Read fields
  347.         final int twoDigitsYear = Integer.parseInt(fields[0]);
  348.         final int day           = Integer.parseInt(fields[1]);
  349.         final int secInDay      = Integer.parseInt(fields[2]);

  350.         // Data year
  351.         final int year;
  352.         if (twoDigitsYear > 50) {
  353.             year = 1900 + twoDigitsYear;
  354.         } else {
  355.             year = 2000 + twoDigitsYear;
  356.         }

  357.         // Return an absolute date.
  358.         // Initialize to 1st January of the given year because
  359.         // sometimes day in equal to 0 in the file.
  360.         return new AbsoluteDate(new DateComponents(year, 1, 1), utc).
  361.                         shiftedBy(Constants.JULIAN_DAY * (day - 1)).
  362.                         shiftedBy(secInDay);

  363.     }

  364. }