/* 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.orekit.data.DataLoader; import org.orekit.data.DataProvidersManager; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; import org.orekit.propagation.analytical.gnss.GPSOrbitalElements; /** * This class reads SEM almanac files and provides {@link GPSAlmanac GPS almanacs}. * * <p>The definition of a SEM almanac comes from the * <a href="http://www.navcen.uscg.gov/?pageName=gpsSem">U.S. COAST GUARD NAVIGATION CENTER</a>.</p> * * <p>The format of the files holding SEM 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="https://celestrak.com/GPS/almanac/SEM/">CelesTrak</a>.</p> * * @author Pascal Parraud * @since 8.0 * */ public class SEMParser implements DataLoader { // Constants /** The source of the almanacs. */ private static final String SOURCE = "SEM"; /** the reference value for the inclination of GPS orbit: 0.30 semicircles. */ private static final double INC_REF = 0.30; /** Default supported files name pattern. */ private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.al3$"; /** Separator for parsing. */ private static final String SEPARATOR = "\\s+"; // 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 ".al3" extension will be used) * @see #loadData() */ public SEMParser(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_SEM_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 { // Reads the number of almanacs in the file from the first line String[] token = getTokens(reader); final int almanacNb = Integer.parseInt(token[0].trim()); // Reads the week number and the time of applicability from the second line token = getTokens(reader); final int week = Integer.parseInt(token[0].trim()); final double toa = Double.parseDouble(token[1].trim()); // Loop over data blocks for (int i = 0; i < almanacNb; i++) { // Reads the next lines to get one almanac from readAlmanac(reader, week, toa); } } catch (IndexOutOfBoundsException ioobe) { throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_SEM_ALMANAC_FILE, name); } catch (IOException ioe) { throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_SEM_ALMANAC_FILE, name); } } @Override public boolean stillAcceptsData() { return almanacs.isEmpty(); } /** * 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; } /** Get the supported names for data files. * @return regular expression for the supported names for data files */ public String getSupportedNames() { return supportedNames; } /** * Builds {@link GPSAlmanac GPS almanacs} from data read in the file. * * @param reader the reader * @param week the GPS week * @param toa the Time of Applicability * @throws IOException if GPSAlmanacs can't be built from the file */ private void readAlmanac(final BufferedReader reader, final int week, final double toa) throws IOException { // Skips the empty line reader.readLine(); try { // Reads the PRN number from the first line String[] token = getTokens(reader); final int prn = Integer.parseInt(token[0].trim()); // Reads the SV number from the second line token = getTokens(reader); final int svn = Integer.parseInt(token[0].trim()); // Reads the average URA number from the third line token = getTokens(reader); final int ura = Integer.parseInt(token[0].trim()); // Reads the fourth line to get ecc, inc and dom token = getTokens(reader); final double ecc = Double.parseDouble(token[0].trim()); final double inc = getInclination(Double.parseDouble(token[1].trim())); final double dom = toRadians(Double.parseDouble(token[2].trim())); // Reads the fifth line to get sqa, raan and aop token = getTokens(reader); final double sqa = Double.parseDouble(token[0].trim()); final double om0 = toRadians(Double.parseDouble(token[1].trim())); final double aop = toRadians(Double.parseDouble(token[2].trim())); // Reads the sixth line to get anom, af0 and af1 token = getTokens(reader); final double anom = toRadians(Double.parseDouble(token[0].trim())); final double af0 = Double.parseDouble(token[1].trim()); final double af1 = Double.parseDouble(token[2].trim()); // Reads the seventh line to get health token = getTokens(reader); final int health = Integer.parseInt(token[0].trim()); // Reads the eighth line to get Satellite Configuration token = getTokens(reader); final int conf = Integer.parseInt(token[0].trim()); // Adds the almanac to the list almanacs.add(new GPSAlmanac(SOURCE, prn, svn, week, toa, sqa, ecc, inc, om0, dom, aop, anom, af0, af1, health, ura, conf)); // Adds the PRN to the list prnList.add(prn); } catch (IndexOutOfBoundsException aioobe) { throw new IOException(); } } /** Read a line and get tokens from. * @param reader the reader * @return the tokens from the read line * @throws IOException if the line is null */ private String[] getTokens(final BufferedReader reader) throws IOException { final String line = reader.readLine(); if (line != null) { return line.trim().split(SEPARATOR); } else { throw new IOException(); } } /** * Gets the inclination from the inclination offset. * * @param incOffset the inclination offset (semicircles) * @return the inclination (rad) */ private double getInclination(final double incOffset) { return toRadians(INC_REF + incOffset); } /** * Converts an angular value from semicircles to radians. * * @param semicircles the angular value in semicircles * @return the angular value in radians */ private double toRadians(final double semicircles) { return GPSOrbitalElements.GPS_PI * semicircles; } }