/* Copyright 2002-2017 CS Systèmes d'Information * Licensed to CS Systèmes d'Information (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.gnss; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import org.hipparchus.util.Pair; import org.orekit.data.DataLoader; import org.orekit.data.DataProvidersManager; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; /** * This class reads Yuma almanac files and provides {@link GPSAlmanac GPS almanacs}. * * <p>The definition of a Yuma almanac comes from the * <a href="http://www.navcen.uscg.gov/?pageName=gpsYuma">U.S. COAST GUARD NAVIGATION CENTER</a>.</p> * * <p>The format of the files holding Yuma almanacs is not precisely specified, * so the parsing rules have been deduced from the downloadable files at * <a href="http://www.navcen.uscg.gov/?pageName=gpsAlmanacs">NAVCEN</a> * and at <a href="http://celestrak.com/GPS/almanac/Yuma/">CelesTrak</a>.</p> * * @author Pascal Parraud * @since 8.0 * */ public class YUMAParser implements DataLoader { // Constants /** The source of the almanacs. */ private static final String SOURCE = "YUMA"; /** the useful keys in the YUMA file. */ private static final String[] KEY = { "id", // ID "health", // Health "eccentricity", // Eccentricity "time", // Time of Applicability(s) "orbital", // Orbital Inclination(rad) "rate", // Rate of Right Ascen(r/s) "sqrt", // SQRT(A) (m 1/2) "right", // Right Ascen at Week(rad) "argument", // Argument of Perigee(rad) "mean", // Mean Anom(rad) "af0", // Af0(s) "af1", // Af1(s/s) "week" // week }; /** Default supported files name pattern. */ private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.alm$"; // Fields /** Regular expression for supported files names. */ private final String supportedNames; /** the list of all the almanacs read from the file. */ private final List<GPSAlmanac> almanacs; /** the list of all the PRN numbers of all the almanacs read from the file. */ private final List<Integer> prnList; /** Simple constructor. * * <p>This constructor does not load any data by itself. Data must be loaded * later on by calling one of the {@link #loadData() loadData()} method or * the {@link #loadData(InputStream, String) loadData(inputStream, fileName)} * method.</p> * * <p>The supported files names are used when getting data from the * {@link #loadData() loadData()} method that relies on the * {@link DataProvidersManager data providers manager}. They are useless when * getting data from the {@link #loadData(InputStream, String) loadData(input, name)} * method.</p> * * @param supportedNames regular expression for supported files names * (if null, a default pattern matching files with a ".alm" extension will be used) * @see #loadData() */ public YUMAParser(final String supportedNames) { this.supportedNames = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames; this.almanacs = new ArrayList<GPSAlmanac>(); this.prnList = new ArrayList<Integer>(); } /** * Loads almanacs. * * <p>The almanacs already loaded in the instance will be discarded * and replaced by the newly loaded data.</p> * <p>This feature is useful when the file selection is already set up by * the {@link DataProvidersManager data providers manager} configuration.</p> * * @exception OrekitException if some data can't be read, some * file content is corrupted or no GPS almanac is available. */ public void loadData() throws OrekitException { // load the data from the configured data providers DataProvidersManager.getInstance().feed(supportedNames, this); if (almanacs.isEmpty()) { throw new OrekitException(OrekitMessages.NO_YUMA_ALMANAC_AVAILABLE); } } @Override public void loadData(final InputStream input, final String name) throws IOException, ParseException, OrekitException { // Clears the lists almanacs.clear(); prnList.clear(); // Creates the reader final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); try { // Gathers data to create one GPSAlmanac from 13 consecutive lines final List<Pair<String, String>> entries = new ArrayList<Pair<String, String>>(KEY.length); // Reads the data one line at a time for (String line = reader.readLine(); line != null; line = reader.readLine()) { // Try to split the line into 2 tokens as key:value final String[] token = line.trim().split(":"); // If the line is made of 2 tokens if (token.length == 2) { // Adds these tokens as an entry to the entries entries.add(new Pair<String, String>(token[0].trim(), token[1].trim())); } // If the number of entries equals the expected number if (entries.size() == KEY.length) { // Gets a GPSAlmanac from the entries final GPSAlmanac almanac = getAlmanac(entries, name); // Adds the GPSAlmanac to the list almanacs.add(almanac); // Adds the PRN number of the GPSAlmanac to the list prnList.add(almanac.getPRN()); // Clears the entries entries.clear(); } } } catch (IOException ioe) { throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE, name); } } @Override public boolean stillAcceptsData() { return almanacs.isEmpty(); } /** Get the supported names for data files. * @return regular expression for the supported names for data files */ public String getSupportedNames() { return supportedNames; } /** * Gets all the {@link GPSAlmanac GPS almanacs} read from the file. * * @return the list of {@link GPSAlmanac} from the file */ public List<GPSAlmanac> getAlmanacs() { return almanacs; } /** * Gets the PRN numbers of all the {@link GPSAlmanac GPS almanacs} read from the file. * * @return the PRN numbers of all the {@link GPSAlmanac GPS almanacs} read from the file */ public List<Integer> getPRNNumbers() { return prnList; } /** * Builds a {@link GPSAlmanac GPS almanac} from data read in the file. * * @param entries the data read from the file * @param name name of the file * @return a {@link GPSAlmanac GPS almanac} * @throws OrekitException if a GPSAlmanac can't be built from the gathered entries */ private GPSAlmanac getAlmanac(final List<Pair<String, String>> entries, final String name) throws OrekitException { try { // Initializes fields int prn = 0; int health = 0; int week = 0; double ecc = 0; double toa = 0; double inc = 0; double dom = 0; double sqa = 0; double om0 = 0; double aop = 0; double anom = 0; double af0 = 0; double af1 = 0; // Initializes checks final boolean[] checks = new boolean[KEY.length]; // Loop over entries for (Pair<String, String> entry: entries) { if (entry.getKey().toLowerCase().startsWith(KEY[0])) { // Gets the PRN of the SVN prn = Integer.parseInt(entry.getValue()); checks[0] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[1])) { // Gets the Health status health = Integer.parseInt(entry.getValue()); checks[1] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[2])) { // Gets the eccentricity ecc = Double.parseDouble(entry.getValue()); checks[2] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[3])) { // Gets the Time of Applicability toa = Double.parseDouble(entry.getValue()); checks[3] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[4])) { // Gets the Inclination inc = Double.parseDouble(entry.getValue()); checks[4] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[5])) { // Gets the Rate of Right Ascension dom = Double.parseDouble(entry.getValue()); checks[5] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[6])) { // Gets the square root of the semi-major axis sqa = Double.parseDouble(entry.getValue()); checks[6] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[7])) { // Gets the Right Ascension of Ascending Node om0 = Double.parseDouble(entry.getValue()); checks[7] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[8])) { // Gets the Argument of Perigee aop = Double.parseDouble(entry.getValue()); checks[8] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[9])) { // Gets the Mean Anomalie anom = Double.parseDouble(entry.getValue()); checks[9] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[10])) { // Gets the SV clock bias af0 = Double.parseDouble(entry.getValue()); checks[10] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[11])) { // Gets the SV clock Drift af1 = Double.parseDouble(entry.getValue()); checks[11] = true; } else if (entry.getKey().toLowerCase().startsWith(KEY[12])) { // Gets the week number week = Integer.parseInt(entry.getValue()); checks[12] = true; } else { // Unknown entry: the file is not a YUMA file throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE, name); } } // If all expected fields have been read if (readOK(checks)) { // Returns a GPSAlmanac built from the entries return new GPSAlmanac(SOURCE, prn, -1, week, toa, sqa, ecc, inc, om0, dom, aop, anom, af0, af1, health, -1, -1); } else { // The file is not a YUMA file throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE, name); } } catch (NumberFormatException nfe) { throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE, name); } } /** Checks if all expected fields have been read. * @param checks flags for read fields * @return true if all expected fields have been read, false if not */ private boolean readOK(final boolean[] checks) { for (boolean check: checks) { if (!check) { return false; } }; return true; } }