SinexLoader.java
- /* Copyright 2002-2022 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.files.sinex;
- import java.io.BufferedInputStream;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.nio.charset.StandardCharsets;
- import java.text.ParseException;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.regex.Pattern;
- import org.hipparchus.exception.DummyLocalizable;
- import org.hipparchus.geometry.euclidean.threed.Vector3D;
- 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.data.DataSource;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.files.sinex.Station.ReferenceSystem;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.time.DateComponents;
- import org.orekit.time.TimeScale;
- import org.orekit.utils.Constants;
- /**
- * Loader for Solution INdependent EXchange (SINEX) files.
- * <p>
- * For now only few keys are supported: SITE/ID, SITE/ECCENTRICITY, SOLUTION/EPOCHS and SOLUTION/ESTIMATE.
- * They represent the minimum set of parameters that are interesting to consider in a SINEX file.
- * </p>
- * @author Bryan Cazabonne
- * @since 10.3
- */
- public class SinexLoader {
- /** 00:000:00000 epoch. */
- private static final String DEFAULT_EPOCH = "00:000:00000";
- /** Pattern for delimiting regular expressions. */
- private static final Pattern SEPARATOR = Pattern.compile(":");
- /** Station data.
- * Key: Site code
- */
- private final Map<String, Station> stations;
- /** UTC time scale. */
- private final TimeScale utc;
- /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
- * default data context}.
- * @param supportedNames regular expression for supported files names
- * @see #SinexLoader(String, DataProvidersManager, TimeScale)
- */
- @DefaultDataContext
- public SinexLoader(final String supportedNames) {
- this(supportedNames,
- DataContext.getDefault().getDataProvidersManager(),
- DataContext.getDefault().getTimeScales().getUTC());
- }
- /**
- * Construct a loader by specifying the source of SINEX auxiliary data files.
- * @param supportedNames regular expression for supported files names
- * @param dataProvidersManager provides access to auxiliary data.
- * @param utc UTC time scale
- */
- public SinexLoader(final String supportedNames,
- final DataProvidersManager dataProvidersManager,
- final TimeScale utc) {
- this.utc = utc;
- stations = new HashMap<>();
- dataProvidersManager.feed(supportedNames, new Parser());
- }
- /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
- * default data context}.
- * @param source source for the RINEX data
- * @see #SinexLoader(String, DataProvidersManager, TimeScale)
- */
- @DefaultDataContext
- public SinexLoader(final DataSource source) {
- this(source, DataContext.getDefault().getTimeScales().getUTC());
- }
- /**
- * Loads SINEX from the given input stream using the specified auxiliary data.
- * @param source source for the RINEX data
- * @param utc UTC time scale
- */
- public SinexLoader(final DataSource source, final TimeScale utc) {
- try {
- this.utc = utc;
- stations = new HashMap<>();
- try (InputStream is = source.getOpener().openStreamOnce();
- BufferedInputStream bis = new BufferedInputStream(is)) {
- new Parser().loadData(bis, source.getName());
- }
- } catch (IOException | ParseException ioe) {
- throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
- }
- }
- /**
- * Get the parsed station data.
- * @return unmodifiable view of parsed station data
- */
- public Map<String, Station> getStations() {
- return Collections.unmodifiableMap(stations);
- }
- /**
- * Get the station corresponding to the given site code.
- * @param siteCode site code
- * @return the corresponding station
- */
- public Station getStation(final String siteCode) {
- return stations.get(siteCode);
- }
- /**
- * Add a new entry to the map of stations.
- * @param station station entry to add
- */
- private void addStation(final Station station) {
- // Check if station already exists
- if (stations.get(station.getSiteCode()) == null) {
- stations.put(station.getSiteCode(), station);
- }
- }
- /** Parser for SINEX files. */
- private class Parser implements DataLoader {
- /** Start character of a comment line. */
- private static final String COMMENT = "*";
- /** {@inheritDoc} */
- @Override
- public boolean stillAcceptsData() {
- // We load all SINEX files we can find
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public void loadData(final InputStream input, final String name)
- throws IOException, ParseException {
- // Useful parameters
- int lineNumber = 0;
- String line = null;
- boolean inId = false;
- boolean inEcc = false;
- boolean inEpoch = false;
- boolean inEstimate = false;
- boolean firstEcc = true;
- Vector3D position = Vector3D.ZERO;
- Vector3D velocity = Vector3D.ZERO;
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
- // Loop on lines
- for (line = reader.readLine(); line != null; line = reader.readLine()) {
- ++lineNumber;
- // For now, only few keys are supported
- // They represent the minimum set of parameters that are interesting to consider in a SINEX file
- // Other keys can be added depending user needs
- switch(line.trim()) {
- case "+SITE/ID" :
- // Start of site id. data
- inId = true;
- break;
- case "-SITE/ID" :
- // End of site id. data
- inId = false;
- break;
- case "+SITE/ECCENTRICITY" :
- // Start of antenna eccentricities data
- inEcc = true;
- break;
- case "-SITE/ECCENTRICITY" :
- // End of antenna eccentricities data
- inEcc = false;
- break;
- case "+SOLUTION/EPOCHS" :
- // Start of epoch data
- inEpoch = true;
- break;
- case "-SOLUTION/EPOCHS" :
- // End of epoch data
- inEpoch = false;
- break;
- case "+SOLUTION/ESTIMATE" :
- // Start of coordinates data
- inEstimate = true;
- break;
- case "-SOLUTION/ESTIMATE" :
- // Start of coordinates data
- inEstimate = false;
- break;
- default:
- if (line.startsWith(COMMENT)) {
- // ignore that line
- } else {
- // parsing data
- if (inId) {
- // read site id. data
- final Station station = new Station();
- station.setSiteCode(parseString(line, 1, 4));
- station.setDomes(parseString(line, 9, 9));
- // add the station to the map
- addStation(station);
- } else if (inEcc) {
- // read antenna eccentricities data
- final Station station = getStation(parseString(line, 1, 4));
- // check if it is the first eccentricity entry for this station
- if (station.getEccentricitiesTimeSpanMap().getSpansNumber() == 1) {
- // we are parsing eccentricity data for a new station
- firstEcc = true;
- }
- // start and end of validity for the current entry
- final AbsoluteDate start = stringEpochToAbsoluteDate(parseString(line, 16, 12));
- final AbsoluteDate end = stringEpochToAbsoluteDate(parseString(line, 29, 12));
- // reference system UNE or XYZ
- station.setEccRefSystem(ReferenceSystem.getEccRefSystem(parseString(line, 42, 3)));
- // eccentricity vector
- final Vector3D eccStation = new Vector3D(parseDouble(line, 46, 8),
- parseDouble(line, 55, 8),
- parseDouble(line, 64, 8));
- // special implementation for the first entry
- if (firstEcc) {
- // we want null values outside validity limits of the station
- station.addStationEccentricitiesValidBefore(eccStation, end);
- station.addStationEccentricitiesValidBefore(null, start);
- // we parsed the first entry, set the flag to false
- firstEcc = false;
- } else {
- station.addStationEccentricitiesValidBefore(eccStation, end);
- }
- // update the last known eccentricities entry
- station.setEccentricities(eccStation);
- } else if (inEpoch) {
- // read epoch data
- final Station station = getStation(parseString(line, 1, 4));
- station.setValidFrom(stringEpochToAbsoluteDate(parseString(line, 16, 12)));
- station.setValidUntil(stringEpochToAbsoluteDate(parseString(line, 29, 12)));
- } else if (inEstimate) {
- final Station station = getStation(parseString(line, 14, 4));
- // check if this station exists
- if (station != null) {
- // switch on coordinates data
- switch(parseString(line, 7, 6)) {
- case "STAX":
- // station X coordinate
- final double x = parseDouble(line, 47, 22);
- position = new Vector3D(x, position.getY(), position.getZ());
- station.setPosition(position);
- break;
- case "STAY":
- // station Y coordinate
- final double y = parseDouble(line, 47, 22);
- position = new Vector3D(position.getX(), y, position.getZ());
- station.setPosition(position);
- break;
- case "STAZ":
- // station Z coordinate
- final double z = parseDouble(line, 47, 22);
- position = new Vector3D(position.getX(), position.getY(), z);
- station.setPosition(position);
- // set the reference epoch (identical for all coordinates)
- station.setEpoch(stringEpochToAbsoluteDate(parseString(line, 27, 12)));
- // reset position vector
- position = Vector3D.ZERO;
- break;
- case "VELX":
- // station X velocity (value is in m/y)
- final double vx = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
- velocity = new Vector3D(vx, velocity.getY(), velocity.getZ());
- station.setVelocity(velocity);
- break;
- case "VELY":
- // station Y velocity (value is in m/y)
- final double vy = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
- velocity = new Vector3D(velocity.getX(), vy, velocity.getZ());
- station.setVelocity(velocity);
- break;
- case "VELZ":
- // station Z velocity (value is in m/y)
- final double vz = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
- velocity = new Vector3D(velocity.getX(), velocity.getY(), vz);
- station.setVelocity(velocity);
- // reset position vector
- velocity = Vector3D.ZERO;
- break;
- default:
- // ignore that field
- break;
- }
- }
- } else {
- // not supported line, ignore it
- }
- }
- break;
- }
- }
- } catch (NumberFormatException nfe) {
- throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
- lineNumber, name, line);
- }
- }
- /** Extract a string from a line.
- * @param line to parse
- * @param start start index of the string
- * @param length length of the string
- * @return parsed string
- */
- private String parseString(final String line, final int start, final int length) {
- return line.substring(start, FastMath.min(line.length(), start + length)).trim();
- }
- /** Extract a double from a line.
- * @param line to parse
- * @param start start index of the real
- * @param length length of the real
- * @return parsed real
- */
- private double parseDouble(final String line, final int start, final int length) {
- return Double.parseDouble(parseString(line, start, length));
- }
- }
- /**
- * Transform a String epoch to an AbsoluteDate.
- * @param stringDate string epoch
- * @return the corresponding AbsoluteDate
- */
- private AbsoluteDate stringEpochToAbsoluteDate(final String stringDate) {
- // Deal with 00:000:00000 epochs
- if (DEFAULT_EPOCH.equals(stringDate)) {
- // Data is still available, return a dummy date at infinity in the future direction
- return AbsoluteDate.FUTURE_INFINITY;
- }
- // Date components
- final String[] fields = SEPARATOR.split(stringDate);
- // Read fields
- final int twoDigitsYear = Integer.parseInt(fields[0]);
- final int day = Integer.parseInt(fields[1]);
- final int secInDay = Integer.parseInt(fields[2]);
- // Data year
- final int year;
- if (twoDigitsYear > 50) {
- year = 1900 + twoDigitsYear;
- } else {
- year = 2000 + twoDigitsYear;
- }
- // Return an absolute date.
- // Initialize to 1st January of the given year because
- // sometimes day in equal to 0 in the file.
- return new AbsoluteDate(new DateComponents(year, 1, 1), utc).
- shiftedBy(Constants.JULIAN_DAY * (day - 1)).
- shiftedBy(secInDay);
- }
- }