YUMAParser.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.gnss;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.text.ParseException;
  23. import java.util.ArrayList;
  24. import java.util.List;

  25. import org.hipparchus.util.Pair;
  26. import org.orekit.data.DataLoader;
  27. import org.orekit.data.DataProvidersManager;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.errors.OrekitMessages;


  30. /**
  31.  * This class reads Yuma almanac files and provides {@link GPSAlmanac GPS almanacs}.
  32.  *
  33.  * <p>The definition of a Yuma almanac comes from the
  34.  * <a href="http://www.navcen.uscg.gov/?pageName=gpsYuma">U.S. COAST GUARD NAVIGATION CENTER</a>.</p>
  35.  *
  36.  * <p>The format of the files holding Yuma almanacs is not precisely specified,
  37.  * so the parsing rules have been deduced from the downloadable files at
  38.  * <a href="http://www.navcen.uscg.gov/?pageName=gpsAlmanacs">NAVCEN</a>
  39.  * and at <a href="http://celestrak.com/GPS/almanac/Yuma/">CelesTrak</a>.</p>
  40.  *
  41.  * @author Pascal Parraud
  42.  * @since 8.0
  43.  *
  44.  */
  45. public class YUMAParser implements DataLoader {

  46.     // Constants
  47.     /** The source of the almanacs. */
  48.     private static final String SOURCE = "YUMA";

  49.     /** the useful keys in the YUMA file. */
  50.     private static final String[] KEY = {
  51.         "id", // ID
  52.         "health", // Health
  53.         "eccentricity", // Eccentricity
  54.         "time", // Time of Applicability(s)
  55.         "orbital", // Orbital Inclination(rad)
  56.         "rate", // Rate of Right Ascen(r/s)
  57.         "sqrt", // SQRT(A)  (m 1/2)
  58.         "right", // Right Ascen at Week(rad)
  59.         "argument", // Argument of Perigee(rad)
  60.         "mean", // Mean Anom(rad)
  61.         "af0", // Af0(s)
  62.         "af1", // Af1(s/s)
  63.         "week" // week
  64.     };

  65.     /** Default supported files name pattern. */
  66.     private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.alm$";

  67.     // Fields
  68.     /** Regular expression for supported files names. */
  69.     private final String supportedNames;

  70.     /** the list of all the almanacs read from the file. */
  71.     private final List<GPSAlmanac> almanacs;

  72.     /** the list of all the PRN numbers of all the almanacs read from the file. */
  73.     private final List<Integer> prnList;

  74.     /** Simple constructor.
  75.     *
  76.     * <p>This constructor does not load any data by itself. Data must be loaded
  77.     * later on by calling one of the {@link #loadData() loadData()} method or
  78.     * the {@link #loadData(InputStream, String) loadData(inputStream, fileName)}
  79.     * method.</p>
  80.      *
  81.      * <p>The supported files names are used when getting data from the
  82.      * {@link #loadData() loadData()} method that relies on the
  83.      * {@link DataProvidersManager data providers manager}. They are useless when
  84.      * getting data from the {@link #loadData(InputStream, String) loadData(input, name)}
  85.      * method.</p>
  86.      *
  87.      * @param supportedNames regular expression for supported files names
  88.      * (if null, a default pattern matching files with a ".alm" extension will be used)
  89.      * @see #loadData()
  90.     */
  91.     public YUMAParser(final String supportedNames) {
  92.         this.supportedNames = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames;
  93.         this.almanacs =  new ArrayList<GPSAlmanac>();
  94.         this.prnList = new ArrayList<Integer>();
  95.     }

  96.     /**
  97.      * Loads almanacs.
  98.      *
  99.      * <p>The almanacs already loaded in the instance will be discarded
  100.      * and replaced by the newly loaded data.</p>
  101.      * <p>This feature is useful when the file selection is already set up by
  102.      * the {@link DataProvidersManager data providers manager} configuration.</p>
  103.      *
  104.      * @exception OrekitException if some data can't be read, some
  105.      * file content is corrupted or no GPS almanac is available.
  106.      */
  107.     public void loadData() throws OrekitException {
  108.         // load the data from the configured data providers
  109.         DataProvidersManager.getInstance().feed(supportedNames, this);
  110.         if (almanacs.isEmpty()) {
  111.             throw new OrekitException(OrekitMessages.NO_YUMA_ALMANAC_AVAILABLE);
  112.         }
  113.     }

  114.     @Override
  115.     public void loadData(final InputStream input, final String name)
  116.         throws IOException, ParseException, OrekitException {

  117.         // Clears the lists
  118.         almanacs.clear();
  119.         prnList.clear();

  120.         // Creates the reader
  121.         final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));

  122.         try {
  123.             // Gathers data to create one GPSAlmanac from 13 consecutive lines
  124.             final List<Pair<String, String>> entries =
  125.                 new ArrayList<Pair<String, String>>(KEY.length);

  126.             // Reads the data one line at a time
  127.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  128.                 // Try to split the line into 2 tokens as key:value
  129.                 final String[] token = line.trim().split(":");
  130.                 // If the line is made of 2 tokens
  131.                 if (token.length == 2) {
  132.                     // Adds these tokens as an entry to the entries
  133.                     entries.add(new Pair<String, String>(token[0].trim(), token[1].trim()));
  134.                 }
  135.                 // If the number of entries equals the expected number
  136.                 if (entries.size() == KEY.length) {
  137.                     // Gets a GPSAlmanac from the entries
  138.                     final GPSAlmanac almanac = getAlmanac(entries, name);
  139.                     // Adds the GPSAlmanac to the list
  140.                     almanacs.add(almanac);
  141.                     // Adds the PRN number of the GPSAlmanac to the list
  142.                     prnList.add(almanac.getPRN());
  143.                     // Clears the entries
  144.                     entries.clear();
  145.                 }
  146.             }
  147.         } catch (IOException ioe) {
  148.             throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
  149.                                       name);
  150.         }
  151.     }

  152.     @Override
  153.     public boolean stillAcceptsData() {
  154.         return almanacs.isEmpty();
  155.     }

  156.     /** Get the supported names for data files.
  157.      * @return regular expression for the supported names for data files
  158.      */
  159.     public String getSupportedNames() {
  160.         return supportedNames;
  161.     }

  162.     /**
  163.      * Gets all the {@link GPSAlmanac GPS almanacs} read from the file.
  164.      *
  165.      * @return the list of {@link GPSAlmanac} from the file
  166.      */
  167.     public List<GPSAlmanac> getAlmanacs() {
  168.         return almanacs;
  169.     }

  170.     /**
  171.      * Gets the PRN numbers of all the {@link GPSAlmanac GPS almanacs} read from the file.
  172.      *
  173.      * @return the PRN numbers of all the {@link GPSAlmanac GPS almanacs} read from the file
  174.      */
  175.     public List<Integer> getPRNNumbers() {
  176.         return prnList;
  177.     }

  178.     /**
  179.      * Builds a {@link GPSAlmanac GPS almanac} from data read in the file.
  180.      *
  181.      * @param entries the data read from the file
  182.      * @param name name of the file
  183.      * @return a {@link GPSAlmanac GPS almanac}
  184.      * @throws OrekitException if a GPSAlmanac can't be built from the gathered entries
  185.      */
  186.     private GPSAlmanac getAlmanac(final List<Pair<String, String>> entries, final String name)
  187.         throws OrekitException {
  188.         try {
  189.             // Initializes fields
  190.             int prn = 0;
  191.             int health = 0;
  192.             int week = 0;
  193.             double ecc = 0;
  194.             double toa = 0;
  195.             double inc = 0;
  196.             double dom = 0;
  197.             double sqa = 0;
  198.             double om0 = 0;
  199.             double aop = 0;
  200.             double anom = 0;
  201.             double af0 = 0;
  202.             double af1 = 0;
  203.             // Initializes checks
  204.             final boolean[] checks = new boolean[KEY.length];
  205.             // Loop over entries
  206.             for (Pair<String, String> entry: entries) {
  207.                 if (entry.getKey().toLowerCase().startsWith(KEY[0])) {
  208.                     // Gets the PRN of the SVN
  209.                     prn = Integer.parseInt(entry.getValue());
  210.                     checks[0] = true;
  211.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[1])) {
  212.                     // Gets the Health status
  213.                     health = Integer.parseInt(entry.getValue());
  214.                     checks[1] = true;
  215.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[2])) {
  216.                     // Gets the eccentricity
  217.                     ecc = Double.parseDouble(entry.getValue());
  218.                     checks[2] = true;
  219.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[3])) {
  220.                     // Gets the Time of Applicability
  221.                     toa = Double.parseDouble(entry.getValue());
  222.                     checks[3] = true;
  223.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[4])) {
  224.                     // Gets the Inclination
  225.                     inc = Double.parseDouble(entry.getValue());
  226.                     checks[4] = true;
  227.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[5])) {
  228.                     // Gets the Rate of Right Ascension
  229.                     dom = Double.parseDouble(entry.getValue());
  230.                     checks[5] = true;
  231.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[6])) {
  232.                     // Gets the square root of the semi-major axis
  233.                     sqa = Double.parseDouble(entry.getValue());
  234.                     checks[6] = true;
  235.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[7])) {
  236.                     // Gets the Right Ascension of Ascending Node
  237.                     om0 = Double.parseDouble(entry.getValue());
  238.                     checks[7] = true;
  239.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[8])) {
  240.                     // Gets the Argument of Perigee
  241.                     aop = Double.parseDouble(entry.getValue());
  242.                     checks[8] = true;
  243.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[9])) {
  244.                     // Gets the Mean Anomalie
  245.                     anom = Double.parseDouble(entry.getValue());
  246.                     checks[9] = true;
  247.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[10])) {
  248.                     // Gets the SV clock bias
  249.                     af0 = Double.parseDouble(entry.getValue());
  250.                     checks[10] = true;
  251.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[11])) {
  252.                     // Gets the SV clock Drift
  253.                     af1 = Double.parseDouble(entry.getValue());
  254.                     checks[11] = true;
  255.                 } else if (entry.getKey().toLowerCase().startsWith(KEY[12])) {
  256.                     // Gets the week number
  257.                     week = Integer.parseInt(entry.getValue());
  258.                     checks[12] = true;
  259.                 } else {
  260.                     // Unknown entry: the file is not a YUMA file
  261.                     throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
  262.                                               name);
  263.                 }
  264.             }

  265.             // If all expected fields have been read
  266.             if (readOK(checks)) {
  267.                 // Returns a GPSAlmanac built from the entries
  268.                 return new GPSAlmanac(SOURCE, prn, -1, week, toa, sqa, ecc, inc, om0, dom,
  269.                                       aop, anom, af0, af1, health, -1, -1);
  270.             } else {
  271.                 // The file is not a YUMA file
  272.                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
  273.                                           name);
  274.             }
  275.         } catch (NumberFormatException nfe) {
  276.             throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
  277.                                       name);
  278.         }
  279.     }

  280.     /** Checks if all expected fields have been read.
  281.      * @param checks flags for read fields
  282.      * @return true if all expected fields have been read, false if not
  283.      */
  284.     private boolean readOK(final boolean[] checks) {
  285.         for (boolean check: checks) {
  286.             if (!check) {
  287.                 return false;
  288.             }
  289.         }
  290.         return true;
  291.     }
  292. }