SEMParser.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.gnss;

  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.text.ParseException;
  24. import java.util.ArrayList;
  25. import java.util.List;
  26. import java.util.regex.Pattern;

  27. import org.orekit.annotation.DefaultDataContext;
  28. import org.orekit.data.AbstractSelfFeedingLoader;
  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.propagation.analytical.gnss.data.GNSSConstants;
  35. import org.orekit.propagation.analytical.gnss.data.GPSAlmanac;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.GNSSDate;
  38. import org.orekit.time.TimeScales;


  39. /**
  40.  * This class reads SEM almanac files and provides {@link GPSAlmanac GPS almanacs}.
  41.  *
  42.  * <p>The definition of a SEM almanac comes from the
  43.  * <a href="http://www.navcen.uscg.gov/?pageName=gpsSem">U.S. COAST GUARD NAVIGATION CENTER</a>.</p>
  44.  *
  45.  * <p>The format of the files holding SEM almanacs is not precisely specified,
  46.  * so the parsing rules have been deduced from the downloadable files at
  47.  * <a href="http://www.navcen.uscg.gov/?pageName=gpsAlmanacs">NAVCEN</a>
  48.  * and at <a href="https://celestrak.com/GPS/almanac/SEM/">CelesTrak</a>.</p>
  49.  *
  50.  * @author Pascal Parraud
  51.  * @since 8.0
  52.  *
  53.  */
  54. public class SEMParser extends AbstractSelfFeedingLoader implements DataLoader {

  55.     // Constants
  56.     /** The source of the almanacs. */
  57.     private static final String SOURCE = "SEM";

  58.     /** the reference value for the inclination of GPS orbit: 0.30 semicircles. */
  59.     private static final double INC_REF = 0.30;

  60.     /** Default supported files name pattern. */
  61.     private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.al3$";

  62.     /** Separator for parsing. */
  63.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  64.     // Fields
  65.     /** the list of all the almanacs read from the file. */
  66.     private final List<GPSAlmanac> almanacs;

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

  69.     /** Set of time scales to use. */
  70.     private final TimeScales timeScales;

  71.     /** Simple constructor.
  72.      *
  73.      * <p>This constructor does not load any data by itself. Data must be loaded
  74.      * later on by calling one of the {@link #loadData() loadData()} method or
  75.      * the {@link #loadData(InputStream, String) loadData(inputStream, fileName)}
  76.      * method.</p>
  77.      *
  78.      * <p>The supported files names are used when getting data from the
  79.      * {@link #loadData() loadData()} method that relies on the
  80.      * {@link DataContext#getDefault() default data context}. They are useless when
  81.      * getting data from the {@link #loadData(InputStream, String) loadData(input, name)}
  82.      * method.</p>
  83.      *
  84.      * @param supportedNames regular expression for supported files names
  85.      * (if null, a default pattern matching files with a ".al3" extension will be used)
  86.      * @see #loadData()
  87.      * @see #SEMParser(String, DataProvidersManager, TimeScales)
  88.      */
  89.     @DefaultDataContext
  90.     public SEMParser(final String supportedNames) {
  91.         this(supportedNames,
  92.                 DataContext.getDefault().getDataProvidersManager(),
  93.                 DataContext.getDefault().getTimeScales());
  94.     }

  95.     /**
  96.      * Create a SEM loader/parser with the given source of SEM auxiliary data files.
  97.      *
  98.      * <p>This constructor does not load any data by itself. Data must be loaded
  99.      * later on by calling one of the {@link #loadData() loadData()} method or
  100.      * the {@link #loadData(InputStream, String) loadData(inputStream, fileName)}
  101.      * method.</p>
  102.      *
  103.      * <p>The supported files names are used when getting data from the
  104.      * {@link #loadData() loadData()} method that relies on the
  105.      * {@code dataProvidersManager}. They are useless when
  106.      * getting data from the {@link #loadData(InputStream, String) loadData(input, name)}
  107.      * method.</p>
  108.      *
  109.      * @param supportedNames regular expression for supported files names
  110.      * (if null, a default pattern matching files with a ".al3" extension will be used)
  111.      * @param dataProvidersManager provides access to auxiliary data.
  112.      * @param timeScales to use when parsing the GPS dates.
  113.      * @see #loadData()
  114.      * @since 10.1
  115.      */
  116.     public SEMParser(final String supportedNames,
  117.                      final DataProvidersManager dataProvidersManager,
  118.                      final TimeScales timeScales) {
  119.         super((supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames,
  120.                 dataProvidersManager);
  121.         this.almanacs = new ArrayList<>();
  122.         this.prnList = new ArrayList<>();
  123.         this.timeScales = timeScales;
  124.     }

  125.     /**
  126.      * Loads almanacs.
  127.      *
  128.      * <p>The almanacs already loaded in the instance will be discarded
  129.      * and replaced by the newly loaded data.</p>
  130.      * <p>This feature is useful when the file selection is already set up by
  131.      * the {@link DataProvidersManager data providers manager} configuration.</p>
  132.      *
  133.      */
  134.     public void loadData() {
  135.         // load the data from the configured data providers
  136.         feed(this);
  137.         if (almanacs.isEmpty()) {
  138.             throw new OrekitException(OrekitMessages.NO_SEM_ALMANAC_AVAILABLE);
  139.         }
  140.     }

  141.     @Override
  142.     public void loadData(final InputStream input, final String name)
  143.         throws IOException, ParseException, OrekitException {

  144.         // Clears the lists
  145.         almanacs.clear();
  146.         prnList.clear();

  147.         // Creates the reader
  148.         try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  149.             // Reads the number of almanacs in the file from the first line
  150.             String[] token = getTokens(reader);
  151.             final int almanacNb = Integer.parseInt(token[0].trim());

  152.             // Reads the week number and the time of applicability from the second line
  153.             token = getTokens(reader);
  154.             final int week = Integer.parseInt(token[0].trim());
  155.             final double toa = Double.parseDouble(token[1].trim());

  156.             // Loop over data blocks
  157.             for (int i = 0; i < almanacNb; i++) {
  158.                 // Reads the next lines to get one almanac from
  159.                 readAlmanac(reader, week, toa);
  160.             }
  161.         } catch (IndexOutOfBoundsException | IOException e) {
  162.             throw new OrekitException(e, OrekitMessages.NOT_A_SUPPORTED_SEM_ALMANAC_FILE, name);
  163.         }
  164.     }

  165.     @Override
  166.     public boolean stillAcceptsData() {
  167.         return almanacs.isEmpty();
  168.     }

  169.     /**
  170.      * Gets all the {@link GPSAlmanac GPS almanacs} read from the file.
  171.      *
  172.      * @return the list of {@link GPSAlmanac} from the file
  173.      */
  174.     public List<GPSAlmanac> getAlmanacs() {
  175.         return almanacs;
  176.     }

  177.     /**
  178.      * Gets the PRN numbers of all the {@link GPSAlmanac GPS almanacs} read from the file.
  179.      *
  180.      * @return the PRN numbers of all the {@link GPSAlmanac GPS almanacs} read from the file
  181.      */
  182.     public List<Integer> getPRNNumbers() {
  183.         return prnList;
  184.     }

  185.     @Override
  186.     public String getSupportedNames() {
  187.         return super.getSupportedNames();
  188.     }

  189.     /**
  190.      * Builds {@link GPSAlmanac GPS almanacs} from data read in the file.
  191.      *
  192.      * @param reader the reader
  193.      * @param week the GPS week
  194.      * @param toa the Time of Applicability
  195.      * @throws IOException if GPSAlmanacs can't be built from the file
  196.      */
  197.     private void readAlmanac(final BufferedReader reader, final int week, final double toa)
  198.         throws IOException {
  199.         // Skips the empty line
  200.         reader.readLine();

  201.         // Create an empty GPS almanac and set the source
  202.         final GPSAlmanac almanac = new GPSAlmanac();
  203.         almanac.setSource(SOURCE);

  204.         try {
  205.             // Reads the PRN number from the first line
  206.             String[] token = getTokens(reader);
  207.             almanac.setPRN(Integer.parseInt(token[0].trim()));

  208.             // Reads the SV number from the second line
  209.             token = getTokens(reader);
  210.             almanac.setSVN(Integer.parseInt(token[0].trim()));

  211.             // Reads the average URA number from the third line
  212.             token = getTokens(reader);
  213.             almanac.setURA(Integer.parseInt(token[0].trim()));

  214.             // Reads the fourth line to get ecc, inc and dom
  215.             token = getTokens(reader);
  216.             almanac.setE(Double.parseDouble(token[0].trim()));
  217.             almanac.setI0(getInclination(Double.parseDouble(token[1].trim())));
  218.             almanac.setOmegaDot(toRadians(Double.parseDouble(token[2].trim())));

  219.             // Reads the fifth line to get sqa, raan and aop
  220.             token = getTokens(reader);
  221.             almanac.setSqrtA(Double.parseDouble(token[0].trim()));
  222.             almanac.setOmega0(toRadians(Double.parseDouble(token[1].trim())));
  223.             almanac.setPa(toRadians(Double.parseDouble(token[2].trim())));

  224.             // Reads the sixth line to get anom, af0 and af1
  225.             token = getTokens(reader);
  226.             almanac.setM0(toRadians(Double.parseDouble(token[0].trim())));
  227.             almanac.setAf0(Double.parseDouble(token[1].trim()));
  228.             almanac.setAf1(Double.parseDouble(token[2].trim()));

  229.             // Reads the seventh line to get health
  230.             token = getTokens(reader);
  231.             almanac.setHealth(Integer.parseInt(token[0].trim()));

  232.             // Reads the eighth line to get Satellite Configuration
  233.             token = getTokens(reader);
  234.             almanac.setSatConfiguration(Integer.parseInt(token[0].trim()));

  235.             // Adds the almanac to the list
  236.             final AbsoluteDate date =
  237.                     new GNSSDate(week, toa * 1000, SatelliteSystem.GPS, timeScales)
  238.                             .getDate();
  239.             almanac.setDate(date);
  240.             almanac.setTime(toa);
  241.             almanac.setWeek(week);
  242.             almanacs.add(almanac);

  243.             // Adds the PRN to the list
  244.             prnList.add(almanac.getPRN());
  245.         } catch (IndexOutOfBoundsException aioobe) {
  246.             throw new IOException(aioobe);
  247.         }
  248.     }

  249.     /** Read a line and get tokens from.
  250.      *  @param reader the reader
  251.      *  @return the tokens from the read line
  252.      *  @throws IOException if the line is null
  253.      */
  254.     private String[] getTokens(final BufferedReader reader) throws IOException {
  255.         final String line = reader.readLine();
  256.         if (line != null) {
  257.             return SEPARATOR.split(line.trim());
  258.         } else {
  259.             throw new IOException();
  260.         }
  261.     }

  262.     /**
  263.      * Gets the inclination from the inclination offset.
  264.      *
  265.      * @param incOffset the inclination offset (semicircles)
  266.      * @return the inclination (rad)
  267.      */
  268.     private double getInclination(final double incOffset) {
  269.         return toRadians(INC_REF + incOffset);
  270.     }

  271.     /**
  272.      * Converts an angular value from semicircles to radians.
  273.      *
  274.      * @param semicircles the angular value in semicircles
  275.      * @return the angular value in radians
  276.      */
  277.     private double toRadians(final double semicircles) {
  278.         return GNSSConstants.GNSS_PI * semicircles;
  279.     }

  280. }