RinexNavigationParser.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.gnss.navigation;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.hipparchus.util.FastMath;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.data.DataSource;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.gnss.SatelliteSystem;
import org.orekit.gnss.TimeSystem;
import org.orekit.gnss.navigation.RinexNavigation.TimeSystemCorrection;
import org.orekit.propagation.analytical.gnss.data.BeidouNavigationMessage;
import org.orekit.propagation.analytical.gnss.data.GLONASSNavigationMessage;
import org.orekit.propagation.analytical.gnss.data.GPSNavigationMessage;
import org.orekit.propagation.analytical.gnss.data.GalileoNavigationMessage;
import org.orekit.propagation.analytical.gnss.data.IRNSSNavigationMessage;
import org.orekit.propagation.analytical.gnss.data.QZSSNavigationMessage;
import org.orekit.propagation.analytical.gnss.data.SBASNavigationMessage;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.DateComponents;
import org.orekit.time.GNSSDate;
import org.orekit.time.TimeComponents;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScales;
import org.orekit.utils.Constants;
/**
* Parser for RINEX navigation messages files.
* <p>
* This parser handles RINEX version from 3.01 to 3.05. It is not adapted for RINEX 2.10 and 2.11 versions.
* </p>
* @see <a href="https://files.igs.org/pub/data/format/rinex301.pdf"> 3.01 navigation messages file format</a>
* @see <a href="https://files.igs.org/pub/data/format/rinex302.pdf"> 3.02 navigation messages file format</a>
* @see <a href="https://files.igs.org/pub/data/format/rinex303.pdf"> 3.03 navigation messages file format</a>
* @see <a href="https://files.igs.org/pub/data/format/rinex304.pdf"> 3.04 navigation messages file format</a>
* @see <a href="https://files.igs.org/pub/data/format/rinex305.pdf"> 3.05 navigation messages file format</a>
*
* @author Bryan Cazabonne
* @since 11.0
*
*/
public class RinexNavigationParser {
/** Handled clock file format versions. */
private static final List<Double> HANDLED_VERSIONS = Arrays.asList(3.01, 3.02, 3.03, 3.04, 3.05);
/** File Type. */
private static final String FILE_TYPE = "N";
/** Seconds to milliseconds converter. */
private static final Double SEC_TO_MILLI = 1000.0;
/** Kilometer to meters converter. */
private static final Double KM_TO_M = 1000.0;
/** Set of time scales. */
private final TimeScales timeScales;
/**
* Constructor.
* <p>This constructor uses the {@link DataContext#getDefault() default data context}.</p>
* @see #RinexNavigationParser(TimeScales)
*
*/
@DefaultDataContext
public RinexNavigationParser() {
this(DataContext.getDefault().getTimeScales());
}
/**
* Constructor.
* @param timeScales the set of time scales used for parsing dates.
*/
public RinexNavigationParser(final TimeScales timeScales) {
this.timeScales = timeScales;
}
/**
* Parse RINEX navigation messages.
* @param source source providing the data to parse
* @return a parsed RINEX navigation messages file
* @throws IOException if {@code reader} throws one
*/
public RinexNavigation parse(final DataSource source) throws IOException {
// initialize internal data structures
final ParseInfo pi = new ParseInfo();
int lineNumber = 0;
Stream<LineParser> candidateParsers = Stream.of(LineParser.HEADER_VERSION);
try (Reader reader = source.getOpener().openReaderOnce();
BufferedReader br = new BufferedReader(reader)) {
for (String line = br.readLine(); line != null; line = br.readLine()) {
++lineNumber;
final String l = line;
final Optional<LineParser> selected = candidateParsers.filter(p -> p.canHandle(l)).findFirst();
if (selected.isPresent()) {
try {
selected.get().parse(line, pi);
} catch (StringIndexOutOfBoundsException | NumberFormatException | InputMismatchException e) {
throw new OrekitException(e,
OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, source.getName(), line);
}
candidateParsers = selected.get().allowedNext();
} else {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, source.getName(), line);
}
}
}
return pi.file;
}
/**
* Parse a double value.
* @param line line to parse
* @param startIndex start index
* @param size size of the value
* @return the parsed value
*/
private static double parseDouble(final String line, final int startIndex, final int size) {
return Double.parseDouble(line.substring(startIndex, startIndex + size).replace('D', 'E').trim());
}
/**
* Parse an integer value.
* @param line line to parse
* @param startIndex start index
* @param size size of the value
* @return the parsed value
*/
private static int parseInt(final String line, final int startIndex, final int size) {
return Integer.parseInt(line.substring(startIndex, startIndex + size).trim());
}
/**
* Parse a string value.
* @param line line to parse
* @param startIndex start index
* @param size size of the value
* @return the parsed value
*/
private static String parseString(final String line, final int startIndex, final int size) {
return line.substring(startIndex, startIndex + size).trim();
}
/** Transient data used for parsing a RINEX navigation messages file. */
private class ParseInfo {
/** Set of time scales for parsing dates. */
private final TimeScales timeScales;
/** The corresponding navigation messages file object. */
private RinexNavigation file;
/** The version of the navigation file. */
private double version;
/** Flag indicating the distinction between "alpha" and "beta" ionospheric coefficients. */
private boolean isIonosphereAlphaInitialized;
/** Satellite system line parser. */
private SatelliteSystemLineParser systemLineParser;
/** Current line number of the navigation message. */
private int lineNumber;
/** Container for GPS navigation message. */
private GPSNavigationMessage gpsNav;
/** Container for Galileo navigation message. */
private GalileoNavigationMessage galileoNav;
/** Container for Beidou navigation message. */
private BeidouNavigationMessage beidouNav;
/** Container for QZSS navigation message. */
private QZSSNavigationMessage qzssNav;
/** Container for IRNSS navigation message. */
private IRNSSNavigationMessage irnssNav;
/** Container for GLONASS navigation message. */
private GLONASSNavigationMessage glonassNav;
/** Container for SBAS navigation message. */
private SBASNavigationMessage sbasNav;
/** Constructor, build the ParseInfo object. */
ParseInfo() {
// Initialize default values for fields
this.timeScales = RinexNavigationParser.this.timeScales;
this.version = 1.0;
this.isIonosphereAlphaInitialized = false;
this.file = new RinexNavigation();
this.systemLineParser = SatelliteSystemLineParser.GPS;
this.lineNumber = 0;
this.gpsNav = new GPSNavigationMessage();
this.galileoNav = new GalileoNavigationMessage();
this.beidouNav = new BeidouNavigationMessage();
this.qzssNav = new QZSSNavigationMessage();
this.irnssNav = new IRNSSNavigationMessage();
this.glonassNav = new GLONASSNavigationMessage();
this.sbasNav = new SBASNavigationMessage();
}
}
/** Parsers for specific lines. */
private enum LineParser {
/** Parser for version, file type and satellite system. */
HEADER_VERSION("^.+RINEX VERSION / TYPE( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Rinex version
pi.version = parseDouble(line, 0, 9);
// Throw exception if format version is not handled
if (!HANDLED_VERSIONS.contains(pi.version)) {
throw new OrekitException(OrekitMessages.NAVIGATION_FILE_UNSUPPORTED_VERSION, pi.version);
}
pi.file.setFormatVersion(pi.version);
// File type
pi.file.setFileType(FILE_TYPE);
// Satellite system
final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(parseString(line, 40, 1));
pi.file.setSatelliteSystem(system);
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(HEADER_PROGRAM);
}
},
/** Parser for generating program and emiting agency. */
HEADER_PROGRAM("^.+PGM / RUN BY / DATE( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Name of the generating program
final String programName = parseString(line, 0, 20);
pi.file.setProgramName(programName);
// Name of the emiting agency
final String agencyName = parseString(line, 20, 20);
pi.file.setAgencyName(agencyName);
// Date and time of file creation
final String date = parseString(line, 40, 8);
final String time = parseString(line, 49, 6);
final String timeZone = parseString(line, 56, 4);
pi.file.setCreationDateString(date);
pi.file.setCreationTimeString(time);
pi.file.setCreationTimeZoneString(timeZone);
// Convert date and time to an Orekit absolute date
final DateComponents dateComponents = new DateComponents(parseInt(date, 0, 4),
parseInt(date, 4, 2),
parseInt(date, 6, 2));
final TimeComponents timeComponents = new TimeComponents(parseInt(time, 0, 2),
parseInt(time, 2, 2),
parseInt(time, 4, 2));
pi.file.setCreationDate(new AbsoluteDate(dateComponents,
timeComponents,
TimeSystem.parseTimeSystem(timeZone).getTimeScale(pi.timeScales)));
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
}
},
/** Parser for comments. */
HEADER_COMMENT("^.+COMMENT( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
pi.file.addComment(parseString(line, 0, 60));
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
}
},
/** Parser for ionospheric correction parameters. */
HEADER_IONOSPHERIC("^.+IONOSPHERIC CORR( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Satellite system
final String ionoType = parseString(line, 0, 3);
pi.file.setIonosphericCorrectionType(ionoType);
// Read coefficients
final double[] parameters = new double[4];
parameters[0] = parseDouble(line, 5, 12);
parameters[1] = parseDouble(line, 17, 12);
parameters[2] = parseDouble(line, 29, 12);
parameters[3] = parseDouble(line, 41, 12);
// Verify if we are parsing Galileo ionospheric parameters
if ("GAL".equals(ionoType)) {
// We are parsing Galileo ionospheric parameters
pi.file.setNeQuickAlpha(parameters);
} else {
// We are parsing Klobuchar ionospheric parameters
// Verify if we are parsing "alpha" or "beta" ionospheric parameters
if (pi.isIonosphereAlphaInitialized) {
// Ionospheric "beta" parameters
pi.file.setKlobucharBeta(parameters);
} else {
// Ionospheric "alpha" parameters
pi.file.setKlobucharAlpha(parameters);
// Set the flag to true
pi.isIonosphereAlphaInitialized = true;
}
}
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
}
},
/** Parser for corrections to transform the system time to UTC or to other time systems. */
HEADER_TIME("^.+TIME SYSTEM CORR( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Read fields
final String type = parseString(line, 0, 4);
final double a0 = parseDouble(line, 5, 17);
final double a1 = parseDouble(line, 22, 16);
final int refTime = parseInt(line, 38, 7);
final int refWeek = parseInt(line, 46, 5);
// Add to the list
final TimeSystemCorrection tsc = new TimeSystemCorrection(type, a0, a1, refTime, refWeek);
pi.file.addTimeSystemCorrections(tsc);
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(HEADER_COMMENT, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
}
},
/** Parser for leap seconds. */
HEADER_LEAP_SECONDS("^.+LEAP SECONDS( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Current number of leap seconds
pi.file.setNumberOfLeapSeconds(parseInt(line, 0, 6));
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_END);
}
},
/** Parser for the end of header. */
HEADER_END("^.+END OF HEADER( )*$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// do nothing...
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(NAVIGATION_MESSAGE_FIRST);
}
},
/** Parser for navigation message first data line. */
NAVIGATION_MESSAGE_FIRST("(^G|^R|^E|^C|^I|^J|^S).+$") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Set the line number to 0
pi.lineNumber = 0;
// Current satellite system
final String key = parseString(line, 0, 1);
// Initialize parser
pi.systemLineParser = SatelliteSystemLineParser.getSatelliteSystemLineParser(key);
// Read first line
pi.systemLineParser.parseFirstLine(line, pi);
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(NAVIGATION_BROADCAST_ORBIT);
}
},
/** Parser for broadcast orbit. */
NAVIGATION_BROADCAST_ORBIT("^ .+") {
/** {@inheritDoc} */
@Override
public void parse(final String line, final ParseInfo pi) {
// Increment the line number
pi.lineNumber++;
// Read the corresponding line
if (pi.lineNumber == 1) {
// BROADCAST ORBIT – 1
pi.systemLineParser.parseFirstBroadcastOrbit(line, pi);
} else if (pi.lineNumber == 2) {
// BROADCAST ORBIT – 2
pi.systemLineParser.parseSecondBroadcastOrbit(line, pi);
} else if (pi.lineNumber == 3) {
// BROADCAST ORBIT – 3
pi.systemLineParser.parseThirdBroadcastOrbit(line, pi);
} else if (pi.lineNumber == 4) {
// BROADCAST ORBIT – 4
pi.systemLineParser.parseFourthBroadcastOrbit(line, pi);
} else if (pi.lineNumber == 5) {
// BROADCAST ORBIT – 5
pi.systemLineParser.parseFifthBroadcastOrbit(line, pi);
} else if (pi.lineNumber == 6) {
// BROADCAST ORBIT – 6
pi.systemLineParser.parseSixthBroadcastOrbit(line, pi);
} else {
// BROADCAST ORBIT – 7
pi.systemLineParser.parseSeventhBroadcastOrbit(line, pi);
}
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
return Stream.of(NAVIGATION_MESSAGE_FIRST, NAVIGATION_BROADCAST_ORBIT);
}
};
/** Pattern for identifying line. */
private final Pattern pattern;
/** Simple constructor.
* @param lineRegexp regular expression for identifying line
*/
LineParser(final String lineRegexp) {
pattern = Pattern.compile(lineRegexp);
}
/** Parse a line.
* @param line line to parse
* @param pi holder for transient data
*/
public abstract void parse(String line, ParseInfo pi);
/** Get the allowed parsers for next line.
* @return allowed parsers for next line
*/
public abstract Stream<LineParser> allowedNext();
/** Check if parser can handle line.
* @param line line to parse
* @return true if parser can handle the specified line
*/
public boolean canHandle(final String line) {
return pattern.matcher(line).matches();
}
}
/** Parsers for satellite system specific lines. */
private enum SatelliteSystemLineParser {
/** GPS. */
GPS("G") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.gpsNav.setPRN(parseInt(line, 1, 2));
// Toc
final int gpsTocYear = parseInt(line, 4, 4);
final int gpsTocMonth = parseInt(line, 9, 2);
final int gpsTocDay = parseInt(line, 12, 2);
final int gpsTocHours = parseInt(line, 15, 2);
final int gpsTocMin = parseInt(line, 18, 2);
final int gpsTocSec = parseInt(line, 21, 2);
pi.gpsNav.setEpochToc(new AbsoluteDate(gpsTocYear, gpsTocMonth, gpsTocDay, gpsTocHours,
gpsTocMin, gpsTocSec, pi.timeScales.getGPS()));
// Af0, Af1, and Af2
pi.gpsNav.setAf0(parseDouble(line, 23, 19));
pi.gpsNav.setAf1(parseDouble(line, 42, 19));
pi.gpsNav.setAf2(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// IODE
pi.gpsNav.setIODE(parseDouble(line, 4, 19));
// Crs
pi.gpsNav.setCrs(parseDouble(line, 23, 19));
// Delta n
pi.gpsNav.setDeltaN(parseDouble(line, 42, 19));
// M0
pi.gpsNav.setM0(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Cuc
pi.gpsNav.setCuc(parseDouble(line, 4, 19));
// e
pi.gpsNav.setE(parseDouble(line, 23, 19));
// Cus
pi.gpsNav.setCus(parseDouble(line, 42, 19));
// sqrt(A)
pi.gpsNav.setSqrtA(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Toe
pi.gpsNav.setTime(parseDouble(line, 4, 19));
// Cic
pi.gpsNav.setCic(parseDouble(line, 23, 19));
// Omega0
pi.gpsNav.setOmega0(parseDouble(line, 42, 19));
// Cis
pi.gpsNav.setCis(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// i0
pi.gpsNav.setI0(parseDouble(line, 4, 19));
// Crc
pi.gpsNav.setCrc(parseDouble(line, 23, 19));
// omega
pi.gpsNav.setPa(parseDouble(line, 42, 19));
// OMEGA DOT
pi.gpsNav.setOmegaDot(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// iDot
pi.gpsNav.setIDot(parseDouble(line, 4, 19));
// Codes on L2 channel (ignored)
// parseDouble(line, 23, 19)
// GPS week (to go with Toe)
pi.gpsNav.setWeek((int) parseDouble(line, 42, 19));
pi.gpsNav.setDate(new GNSSDate(pi.gpsNav.getWeek(),
SEC_TO_MILLI * pi.gpsNav.getTime(),
SatelliteSystem.GPS,
pi.timeScales).getDate());
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// SV accuracy
pi.gpsNav.setSvAccuracy(parseDouble(line, 4, 19));
// Health
pi.gpsNav.setSvHealth(parseDouble(line, 23, 19));
// TGD
pi.gpsNav.setTGD(parseDouble(line, 42, 19));
// IODC
pi.gpsNav.setIODC(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Add the navigation message to the file
pi.file.addGPSNavigationMessage(pi.gpsNav);
// Reinitialized the container for navigation data
pi.gpsNav = new GPSNavigationMessage();
}
},
/** Galileo. */
GALILEO("E") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.galileoNav.setPRN(parseInt(line, 1, 2));
// Toc
final int galileoTocYear = parseInt(line, 4, 4);
final int galileoTocMonth = parseInt(line, 9, 2);
final int galileoTocDay = parseInt(line, 12, 2);
final int galileoTocHours = parseInt(line, 15, 2);
final int galileoTocMin = parseInt(line, 18, 2);
final int galileoTocSec = parseInt(line, 21, 2);
pi.galileoNav.setEpochToc(new AbsoluteDate(galileoTocYear, galileoTocMonth, galileoTocDay, galileoTocHours,
galileoTocMin, galileoTocSec, pi.timeScales.getGST()));
// Af0, Af1, and Af2
pi.galileoNav.setAf0(parseDouble(line, 23, 19));
pi.galileoNav.setAf1(parseDouble(line, 42, 19));
pi.galileoNav.setAf2(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// IODNav
pi.galileoNav.setIODNav(parseDouble(line, 4, 19));
// Crs
pi.galileoNav.setCrs(parseDouble(line, 23, 19));
// Delta n
pi.galileoNav.setDeltaN(parseDouble(line, 42, 19));
// M0
pi.galileoNav.setM0(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Cuc
pi.galileoNav.setCuc(parseDouble(line, 4, 19));
// e
pi.galileoNav.setE(parseDouble(line, 23, 19));
// Cus
pi.galileoNav.setCus(parseDouble(line, 42, 19));
// sqrt(A)
pi.galileoNav.setSqrtA(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Toe
pi.galileoNav.setTime(parseDouble(line, 4, 19));
// Cic
pi.galileoNav.setCic(parseDouble(line, 23, 19));
// Omega0
pi.galileoNav.setOmega0(parseDouble(line, 42, 19));
// Cis
pi.galileoNav.setCis(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// i0
pi.galileoNav.setI0(parseDouble(line, 4, 19));
// Crc
pi.galileoNav.setCrc(parseDouble(line, 23, 19));
// omega
pi.galileoNav.setPa(parseDouble(line, 42, 19));
// OMEGA DOT
pi.galileoNav.setOmegaDot(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// iDot
pi.galileoNav.setIDot(parseDouble(line, 4, 19));
// Data sources (ignored)
// parseDouble(line, 23, 19)
// GAL week (to go with Toe)
pi.galileoNav.setWeek((int) parseDouble(line, 42, 19));
pi.galileoNav.setDate(new GNSSDate(pi.galileoNav.getWeek(),
SEC_TO_MILLI * pi.galileoNav.getTime(),
SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
pi.timeScales).getDate());
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// SISA
pi.galileoNav.setSisa(parseDouble(line, 4, 19));
// Health
pi.galileoNav.setSvHealth(parseDouble(line, 23, 19));
// E5a/E1 BGD
pi.galileoNav.setBGDE1E5a(parseDouble(line, 42, 19));
// E5b/E1 BGD
pi.galileoNav.setBGDE5bE1(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Add the navigation message to the file
pi.file.addGalileoNavigationMessage(pi.galileoNav);
// Reinitialized the container for navigation data
pi.galileoNav = new GalileoNavigationMessage();
}
},
/** Beidou. */
BEIDOU("C") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.beidouNav.setPRN(parseInt(line, 1, 2));
// Toc
final int beidouTocYear = parseInt(line, 4, 4);
final int beidouTocMonth = parseInt(line, 9, 2);
final int beidouTocDay = parseInt(line, 12, 2);
final int beidouTocHours = parseInt(line, 15, 2);
final int beidouTocMin = parseInt(line, 18, 2);
final int beidouTocSec = parseInt(line, 21, 2);
pi.beidouNav.setEpochToc(new AbsoluteDate(beidouTocYear, beidouTocMonth, beidouTocDay, beidouTocHours,
beidouTocMin, beidouTocSec, pi.timeScales.getBDT()));
// Af0, Af1, and Af2
pi.beidouNav.setAf0(parseDouble(line, 23, 19));
pi.beidouNav.setAf1(parseDouble(line, 42, 19));
pi.beidouNav.setAf2(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// AODE
pi.beidouNav.setAODE(parseDouble(line, 4, 19));
// Crs
pi.beidouNav.setCrs(parseDouble(line, 23, 19));
// Delta n
pi.beidouNav.setDeltaN(parseDouble(line, 42, 19));
// M0
pi.beidouNav.setM0(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Cuc
pi.beidouNav.setCuc(parseDouble(line, 4, 19));
// e
pi.beidouNav.setE(parseDouble(line, 23, 19));
// Cus
pi.beidouNav.setCus(parseDouble(line, 42, 19));
// sqrt(A)
pi.beidouNav.setSqrtA(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Toe
pi.beidouNav.setTime(parseDouble(line, 4, 19));
// Cic
pi.beidouNav.setCic(parseDouble(line, 23, 19));
// Omega0
pi.beidouNav.setOmega0(parseDouble(line, 42, 19));
// Cis
pi.beidouNav.setCis(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// i0
pi.beidouNav.setI0(parseDouble(line, 4, 19));
// Crc
pi.beidouNav.setCrc(parseDouble(line, 23, 19));
// omega
pi.beidouNav.setPa(parseDouble(line, 42, 19));
// OMEGA DOT
pi.beidouNav.setOmegaDot(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// iDot
pi.beidouNav.setIDot(parseDouble(line, 4, 19));
// BDT week (to go with Toe)
pi.beidouNav.setWeek((int) parseDouble(line, 42, 19));
pi.beidouNav.setDate(new GNSSDate(pi.beidouNav.getWeek(),
SEC_TO_MILLI * pi.beidouNav.getTime(),
SatelliteSystem.BEIDOU,
pi.timeScales).getDate());
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// SV accuracy
pi.beidouNav.setSvAccuracy(parseDouble(line, 4, 19));
// SatH1 (ignored)
// parseDouble(line, 23, 19)
// TGD1
pi.beidouNav.setTGD1(parseDouble(line, 42, 19));
// TGD2
pi.beidouNav.setTGD2(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Transmission time of message (ignored)
// parseDouble(line, 4, 19);
// AODC
pi.beidouNav.setAODC(parseDouble(line, 23, 19));
// Add the navigation message to the file
pi.file.addBeidouNavigationMessage(pi.beidouNav);
// Reinitialized the container for navigation data
pi.beidouNav = new BeidouNavigationMessage();
}
},
/** QZSS. */
QZSS("J") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.qzssNav.setPRN(parseInt(line, 1, 2));
// Toc
final int qzssTocYear = parseInt(line, 4, 4);
final int qzssTocMonth = parseInt(line, 9, 2);
final int qzssTocDay = parseInt(line, 12, 2);
final int qzssTocHours = parseInt(line, 15, 2);
final int qzssTocMin = parseInt(line, 18, 2);
final int qzssTocSec = parseInt(line, 21, 2);
pi.qzssNav.setEpochToc(new AbsoluteDate(qzssTocYear, qzssTocMonth, qzssTocDay, qzssTocHours,
qzssTocMin, qzssTocSec, pi.timeScales.getQZSS()));
// Af0, Af1, and Af2
pi.qzssNav.setAf0(parseDouble(line, 23, 19));
pi.qzssNav.setAf1(parseDouble(line, 42, 19));
pi.qzssNav.setAf2(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// IODE
pi.qzssNav.setIODE(parseDouble(line, 4, 19));
// Crs
pi.qzssNav.setCrs(parseDouble(line, 23, 19));
// Delta n
pi.qzssNav.setDeltaN(parseDouble(line, 42, 19));
// M0
pi.qzssNav.setM0(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Cuc
pi.qzssNav.setCuc(parseDouble(line, 4, 19));
// e
pi.qzssNav.setE(parseDouble(line, 23, 19));
// Cus
pi.qzssNav.setCus(parseDouble(line, 42, 19));
// sqrt(A)
pi.qzssNav.setSqrtA(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Toe
pi.qzssNav.setTime(parseDouble(line, 4, 19));
// Cic
pi.qzssNav.setCic(parseDouble(line, 23, 19));
// Omega0
pi.qzssNav.setOmega0(parseDouble(line, 42, 19));
// Cis
pi.qzssNav.setCis(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// i0
pi.qzssNav.setI0(parseDouble(line, 4, 19));
// Crc
pi.qzssNav.setCrc(parseDouble(line, 23, 19));
// omega
pi.qzssNav.setPa(parseDouble(line, 42, 19));
// OMEGA DOT
pi.qzssNav.setOmegaDot(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// iDot
pi.qzssNav.setIDot(parseDouble(line, 4, 19));
// Codes on L2 channel (ignored)
// parseDouble(line, 23, 19)
// GPS week (to go with Toe)
pi.qzssNav.setWeek((int) parseDouble(line, 42, 19));
pi.qzssNav.setDate(new GNSSDate(pi.qzssNav.getWeek(),
SEC_TO_MILLI * pi.qzssNav.getTime(),
SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
pi.timeScales).getDate());
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// SV accuracy
pi.qzssNav.setSvAccuracy(parseDouble(line, 4, 19));
// Health
pi.qzssNav.setSvHealth(parseDouble(line, 23, 19));
// TGD
pi.qzssNav.setTGD(parseDouble(line, 42, 19));
// IODC
pi.qzssNav.setIODC(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Add the navigation message to the file
pi.file.addQZSSNavigationMessage(pi.qzssNav);
// Reinitialized the container for navigation data
pi.qzssNav = new QZSSNavigationMessage();
}
},
/** IRNSS. */
IRNSS("I") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.irnssNav.setPRN(parseInt(line, 1, 2));
// Toc
final int irnssTocYear = parseInt(line, 4, 4);
final int irnssTocMonth = parseInt(line, 9, 2);
final int irnssTocDay = parseInt(line, 12, 2);
final int irnssTocHours = parseInt(line, 15, 2);
final int irnssTocMin = parseInt(line, 18, 2);
final int irnssTocSec = parseInt(line, 21, 2);
pi.irnssNav.setEpochToc(new AbsoluteDate(irnssTocYear, irnssTocMonth, irnssTocDay, irnssTocHours,
irnssTocMin, irnssTocSec, pi.timeScales.getIRNSS()));
// Af0, Af1, and Af2
pi.irnssNav.setAf0(parseDouble(line, 23, 19));
pi.irnssNav.setAf1(parseDouble(line, 42, 19));
pi.irnssNav.setAf2(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// IODEC
pi.irnssNav.setIODEC(parseDouble(line, 4, 19));
// Crs
pi.irnssNav.setCrs(parseDouble(line, 23, 19));
// Delta n
pi.irnssNav.setDeltaN(parseDouble(line, 42, 19));
// M0
pi.irnssNav.setM0(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Cuc
pi.irnssNav.setCuc(parseDouble(line, 4, 19));
// e
pi.irnssNav.setE(parseDouble(line, 23, 19));
// Cus
pi.irnssNav.setCus(parseDouble(line, 42, 19));
// sqrt(A)
pi.irnssNav.setSqrtA(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Toe
pi.irnssNav.setTime(parseDouble(line, 4, 19));
// Cic
pi.irnssNav.setCic(parseDouble(line, 23, 19));
// Omega0
pi.irnssNav.setOmega0(parseDouble(line, 42, 19));
// Cis
pi.irnssNav.setCis(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// i0
pi.irnssNav.setI0(parseDouble(line, 4, 19));
// Crc
pi.irnssNav.setCrc(parseDouble(line, 23, 19));
// omega
pi.irnssNav.setPa(parseDouble(line, 42, 19));
// OMEGA DOT
pi.irnssNav.setOmegaDot(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// iDot
pi.irnssNav.setIDot(parseDouble(line, 4, 19));
// IRNSS week (to go with Toe)
pi.irnssNav.setWeek((int) parseDouble(line, 42, 19));
pi.irnssNav.setDate(new GNSSDate(pi.irnssNav.getWeek(),
SEC_TO_MILLI * pi.irnssNav.getTime(),
SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
pi.timeScales).getDate());
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// SV accuracy
pi.irnssNav.setURA(parseDouble(line, 4, 19));
// Health
pi.irnssNav.setSvHealth(parseDouble(line, 23, 19));
// TGD
pi.irnssNav.setTGD(parseDouble(line, 42, 19));
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Add the navigation message to the file
pi.file.addIRNSSNavigationMessage(pi.irnssNav);
// Reinitialized the container for navigation data
pi.irnssNav = new IRNSSNavigationMessage();
}
},
/** Glonass. */
GLONASS("R") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.glonassNav.setPRN(parseInt(line, 1, 2));
// Toc
final int glonassTocYear = parseInt(line, 4, 4);
final int glonassTocMonth = parseInt(line, 9, 2);
final int glonassTocDay = parseInt(line, 12, 2);
final int glonassTocHours = parseInt(line, 15, 2);
final int glonassTocMin = parseInt(line, 18, 2);
final int glonassTocSec = parseInt(line, 21, 2);
final AbsoluteDate date = new AbsoluteDate(glonassTocYear, glonassTocMonth, glonassTocDay, glonassTocHours,
glonassTocMin, glonassTocSec, pi.timeScales.getUTC());
// Build a GPS date
final GNSSDate gpsEpoch = new GNSSDate(date, SatelliteSystem.GPS, pi.timeScales);
// Toc rounded by 15 min in UTC
final double secInWeek = FastMath.floor((0.001 * gpsEpoch.getMilliInWeek() + 450.0) / 900.0) * 900.0;
final AbsoluteDate rounded = new GNSSDate(gpsEpoch.getWeekNumber(),
SEC_TO_MILLI * secInWeek,
SatelliteSystem.GPS, pi.timeScales).getDate();
pi.glonassNav.setEpochToc(rounded);
// TauN (we read -TauN) and GammaN
pi.glonassNav.setTauN(-parseDouble(line, 23, 19));
pi.glonassNav.setGammaN(parseDouble(line, 42, 19));
// Date
pi.glonassNav.setDate(rounded);
// Time
pi.glonassNav.setTime(fmod(parseDouble(line, 61, 19), Constants.JULIAN_DAY));
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// X
pi.glonassNav.setX(parseDouble(line, 4, 19) * KM_TO_M);
// Vx
pi.glonassNav.setXDot(parseDouble(line, 23, 19) * KM_TO_M);
// Ax
pi.glonassNav.setXDotDot(parseDouble(line, 42, 19) * KM_TO_M);
// Health
pi.glonassNav.setHealth(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Y
pi.glonassNav.setY(parseDouble(line, 4, 19) * KM_TO_M);
// Vy
pi.glonassNav.setYDot(parseDouble(line, 23, 19) * KM_TO_M);
// Ay
pi.glonassNav.setYDotDot(parseDouble(line, 42, 19) * KM_TO_M);
// Frequency number
pi.glonassNav.setFrequencyNumber(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Z
pi.glonassNav.setZ(parseDouble(line, 4, 19) * KM_TO_M);
// Vz
pi.glonassNav.setZDot(parseDouble(line, 23, 19) * KM_TO_M);
// Az
pi.glonassNav.setZDotDot(parseDouble(line, 42, 19) * KM_TO_M);
// Add the navigation message to the file
pi.file.addGlonassNavigationMessage(pi.glonassNav);
// Reinitialized the container for navigation data
pi.glonassNav = new GLONASSNavigationMessage();
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for GLONASS
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for GLONASS
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for GLONASS
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for GLONASS
}
},
/** SBAS. */
SBAS("S") {
/** {@inheritDoc} */
@Override
public void parseFirstLine(final String line, final ParseInfo pi) {
// PRN
pi.sbasNav.setPRN(parseInt(line, 1, 2));
// Toc
final int sbasTocYear = parseInt(line, 4, 4);
final int sbasTocMonth = parseInt(line, 9, 2);
final int sbasTocDay = parseInt(line, 12, 2);
final int sbasTocHours = parseInt(line, 15, 2);
final int sbasTocMin = parseInt(line, 18, 2);
final int sbasTocSec = parseInt(line, 21, 2);
// Time scale (UTC for Rinex 3.01 and GPS for other RINEX versions)
final TimeScale timeScale = ((int) pi.version * 100 == 301) ? pi.timeScales.getUTC() : pi.timeScales.getGPS();
final AbsoluteDate refEpoch = new AbsoluteDate(sbasTocYear, sbasTocMonth, sbasTocDay, sbasTocHours,
sbasTocMin, sbasTocSec, timeScale);
pi.sbasNav.setEpochToc(refEpoch);
// AGf0 and AGf1
pi.sbasNav.setAGf0(parseDouble(line, 23, 19));
pi.sbasNav.setAGf1(parseDouble(line, 42, 19));
pi.sbasNav.setTime(parseDouble(line, 61, 19));
// Set the ephemeris epoch (same as time of clock epoch)
pi.sbasNav.setDate(refEpoch);
}
/** {@inheritDoc} */
@Override
public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
// X
pi.sbasNav.setX(parseDouble(line, 4, 19) * KM_TO_M);
// Vx
pi.sbasNav.setXDot(parseDouble(line, 23, 19) * KM_TO_M);
// Ax
pi.sbasNav.setXDotDot(parseDouble(line, 42, 19) * KM_TO_M);
// Health
pi.sbasNav.setHealth(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
// Y
pi.sbasNav.setY(parseDouble(line, 4, 19) * KM_TO_M);
// Vy
pi.sbasNav.setYDot(parseDouble(line, 23, 19) * KM_TO_M);
// Ay
pi.sbasNav.setYDotDot(parseDouble(line, 42, 19) * KM_TO_M);
// URA
pi.sbasNav.setURA(parseDouble(line, 61, 19));
}
/** {@inheritDoc} */
@Override
public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
// Z
pi.sbasNav.setZ(parseDouble(line, 4, 19) * KM_TO_M);
// Vz
pi.sbasNav.setZDot(parseDouble(line, 23, 19) * KM_TO_M);
// Az
pi.sbasNav.setZDotDot(parseDouble(line, 42, 19) * KM_TO_M);
// IODN
pi.sbasNav.setIODN(parseDouble(line, 61, 19));
// Add the navigation message to the file
pi.file.addSBASNavigationMessage(pi.sbasNav);
// Reinitialized the container for navigation data
pi.sbasNav = new SBASNavigationMessage();
}
/** {@inheritDoc} */
@Override
public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for SBAS
}
/** {@inheritDoc} */
@Override
public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for SBAS
}
/** {@inheritDoc} */
@Override
public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for SBAS
}
/** {@inheritDoc} */
@Override
public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
// Nothing to do for SBAS
}
};
/** Parsing map. */
private static final Map<String, SatelliteSystemLineParser> KEYS_MAP = new HashMap<>();
static {
for (final SatelliteSystemLineParser satelliteSystem : values()) {
KEYS_MAP.put(satelliteSystem.getKey(), satelliteSystem);
}
}
/** Satellite system key. */
private String key;
/**
* Constructor.
* @param key satellite system key
*/
SatelliteSystemLineParser(final String key) {
this.key = key;
}
/**
* Getter for the satellite system key.
* @return the satellite system key
*/
public String getKey() {
return key;
}
/** Parse a string to get the satellite system.
* <p>
* The string first character must be the satellite system.
* </p>
* @param s string to parse
* @return the satellite system
*/
public static SatelliteSystemLineParser getSatelliteSystemLineParser(final String s) {
return KEYS_MAP.get(s);
}
/**
* Parse the first line of the navigation message.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseFirstLine(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 1" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseFirstBroadcastOrbit(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 2" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseSecondBroadcastOrbit(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 3" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseThirdBroadcastOrbit(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 4" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseFourthBroadcastOrbit(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 5" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseFifthBroadcastOrbit(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 6" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseSixthBroadcastOrbit(String line, ParseInfo pi);
/**
* Parse the "BROADCASTORBIT - 7" line.
* @param line line to read
* @param pi holder for transient data
*/
public abstract void parseSeventhBroadcastOrbit(String line, ParseInfo pi);
/**
* Calculates the floating-point remainder of a / b.
* <p>
* fmod = a - x * b
* where x = (int) a / b
* </p>
* @param a numerator
* @param b denominator
* @return the floating-point remainder of a / b
*/
private static double fmod(final double a, final double b) {
final double x = (int) (a / b);
return a - x * b;
}
}
}