STKEphemerisFileParser.java
/* Copyright 2002-2024 Andrew Goetz
* 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.files.stk;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.data.DataSource;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.general.EphemerisFileParser;
import org.orekit.files.stk.STKEphemerisFile.STKCoordinateSystem;
import org.orekit.files.stk.STKEphemerisFile.STKEphemeris;
import org.orekit.files.stk.STKEphemerisFile.STKEphemerisSegment;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.DateTimeComponents;
import org.orekit.time.Month;
import org.orekit.time.UTCScale;
import org.orekit.utils.CartesianDerivativesFilter;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;
/**
* Parser of {@link STKEphemerisFile}s.
*
* <p> The STK ephemeris file format specification is quite extensive and this implementation does not
* attempt (nor is it possible, given the lack of an STK scenario to provide context) to support all
* possible variations of the format. The following keywords are recognized (case-insensitive):
* <table>
* <caption>Recognized Keywords</caption>
* <thead>
* <tr>
* <th>Keyword</th>
* <th>Supported</th>
* <th>Comment</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>stk.v.*.*</td>
* <td>Yes</td>
* <td>STK version number</td>
* </tr>
* <tr>
* <td>BEGIN/END Ephemeris</td>
* <td>Yes</td>
* <td></td>
* </tr>
* <tr>
* <td>ScenarioEpoch</td>
* <td>Yes</td>
* <td>Gregorian UTC time format (<code>dd mmm yyyy hh:mm:ss.s</code>) assumed;
* the <code>TimeFormat</code> keyword is not recognized.</td>
* </tr>
* <tr>
* <td>CentralBody</td>
* <td>No</td>
* <td>Class constructors require gravitational parameter.</td>
* </tr>
* <tr>
* <td>CoordinateSystem</td>
* <td>Yes</td>
* <td>Implementation uses a frame mapping to map {@link STKCoordinateSystem}s to {@link Frame}s.</td>
* </tr>
* <tr>
* <td>DistanceUnit</td>
* <td>Yes</td>
* <td>Only <code>Meters</code> and <code>Kilometers</code> are supported.</td>
* </tr>
* <tr>
* <td>InterpolationMethod</td>
* <td>No</td>
* <td>The Orekit EphemerisSegmentPropagator class uses
* {@link org.orekit.utils.TimeStampedPVCoordinatesHermiteInterpolator#interpolate(AbsoluteDate, Stream)}
* to do Hermite interpolation, so the value of <code>InterpolationMethod</code>, if present, is
* ignored.</td>
* </tr>
* <tr>
* <td>InterpolationSamplesM1</td>
* <td>Yes</td>
* <td>Note that the <code>InterpolationMethod</code> keyword is ignored, but the value of
* <code>InterpolationSamplesM1</code> will be used to determine the number of sample points in the
* Hermite interpolator used by Orekit.</td>
* </tr>
* <tr>
* <td>NumberOfEphemerisPoints</td>
* <td>Yes</td>
* <td></td>
* </tr>
* <tr>
* <td>BEGIN/END SegmentBoundaryTimes</td>
* <td>Yes</td>
* <td></td>
* </tr>
* </tbody>
* </table>
*
* <p> Any keyword in the format specification which is not explicitly named in the above table is not recognized and
* will cause a parse exception. Those keywords that are listed above as recognized but not supported are simply
* ignored.
*
* <p> The following ephemeris formats are recognized and supported:
* <ul>
* <li>EphemerisTimePos</li>
* <li>EphemerisTimePosVel</li>
* <li>EphemerisTimePosVelAcc</li>
* </ul>
* Any ephemeris format in the format specification which is not explicitly named in the above list is not recognized
* and will cause an exception.
*
* @author Andrew Goetz
* @since 12.0
*/
public class STKEphemerisFileParser implements EphemerisFileParser<STKEphemerisFile> {
/** Pattern for delimiting regular expressions. */
private static final Pattern SEPARATOR = Pattern.compile("\\s+");
/** Pattern for ignorable lines. Comments are preceded by '#'. */
private static final Pattern IGNORABLE_LINE = Pattern.compile("^\\s*(#.*)?");
/** Regular expression that matches anything. */
private static final String MATCH_ANY_REGEX = ".*";
/** Recognized keywords. */
private static final List<LineParser> KEYWORDS = Arrays.asList(
LineParser.NUMBER_OF_EPHEMERIS_POINTS,
LineParser.SCENARIO_EPOCH,
LineParser.INTERPOLATION_METHOD,
LineParser.INTERPOLATION_SAMPLESM1,
LineParser.CENTRAL_BODY,
LineParser.COORDINATE_SYSTEM,
LineParser.BEGIN_SEGMENT_BOUNDARY_TIMES,
LineParser.EPHEMERIS_TIME_POS,
LineParser.EPHEMERIS_TIME_POS_VEL,
LineParser.EPHEMERIS_TIME_POS_VEL_ACC
);
/** Satellite id. */
private final String satelliteId;
/** Gravitational parameter (m^3/s^2). */
private final double mu;
/** UTC time scale. */
private final UTCScale utc;
/** Mapping of STK coordinate system to Orekit reference frame. */
private final Map<STKCoordinateSystem, Frame> frameMapping;
/**
* Constructs a {@link STKEphemerisFileParser} instance.
* @param satelliteId satellite id for satellites parsed by the parser
* @param mu gravitational parameter (m^3/s^2)
* @param utc UTC scale for parsed dates
* @param frameMapping mapping from STK coordinate system to Orekit frame
*/
public STKEphemerisFileParser(final String satelliteId, final double mu, final UTCScale utc,
final Map<STKCoordinateSystem, Frame> frameMapping) {
this.satelliteId = Objects.requireNonNull(satelliteId);
this.mu = mu;
this.utc = Objects.requireNonNull(utc);
this.frameMapping = Collections.unmodifiableMap(new EnumMap<>(frameMapping));
}
@Override
public STKEphemerisFile parse(final DataSource source) {
try (Reader reader = source.getOpener().openReaderOnce();
BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {
if (br == null) {
throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
}
// initialize internal data structures
final ParseInfo pi = new ParseInfo();
int lineNumber = 0;
Iterable<LineParser> parsers = Collections.singleton(LineParser.VERSION);
nextLine:
for (String line = br.readLine(); line != null; line = br.readLine()) {
++lineNumber;
if (pi.file != null) {
break;
} else if (IGNORABLE_LINE.matcher(line).matches()) {
continue;
}
for (final LineParser candidate : parsers) {
if (candidate.canHandle(line)) {
try {
candidate.parse(line, pi);
parsers = candidate.allowedNext();
continue nextLine;
} catch (StringIndexOutOfBoundsException | IllegalArgumentException e) {
throw new OrekitException(e, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber,
source.getName(), line);
}
}
}
// no parsers found for this line
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber, source.getName(),
line);
}
if (pi.file != null) {
return pi.file;
} else {
throw new OrekitException(OrekitMessages.STK_UNEXPECTED_END_OF_FILE, lineNumber);
}
} catch (IOException ioe) {
throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
}
}
/**
* Transient data used for parsing an STK ephemeris file. The data is kept in a
* separate data structure to make the parser thread-safe.
* <p>
* <b>Note</b>: The class intentionally does not provide accessor methods, as it
* is only used internally for parsing an STK ephemeris file.
* </p>
*/
private final class ParseInfo {
/** STK version. */
private String stkVersion;
/** Scenario epoch. */
private AbsoluteDate scenarioEpoch; // technically optional but required here b/c no STK scenario for context
/** Number of ephemeris points. */
private Integer numberOfEphemerisPoints;
/** One less than the number of points used in the interpolation. */
private int interpolationSamplesM1;
/** Cartesian derivatives filter for interpolation. */
private CartesianDerivativesFilter cartesianDerivativesFilter;
/** Coordinate system. */
private STKCoordinateSystem coordinateSystem;
/** Distance unit. */
private STKDistanceUnit distanceUnit;
/** Number of ephemeris points read. */
private int numberOfEphemerisPointsRead;
/** Segment boundary times. */
private SortedSet<Double> segmentBoundaryTimes;
/** Ephemeris segments. */
private List<STKEphemerisSegment> ephemerisSegments;
/** Last-saved ephemeris. */
private TimeStampedPVCoordinates lastSavedEphemeris;
/** Ephemeris for current segment. */
private List<TimeStampedPVCoordinates> segmentEphemeris;
/** Completely parsed ephemeris file. */
private STKEphemerisFile file;
/**
* Constructs a {@link ParseInfo} instance.
*/
private ParseInfo() {
// Set defaults.
this.distanceUnit = STKDistanceUnit.METERS;
this.interpolationSamplesM1 = 5;
this.coordinateSystem = STKCoordinateSystem.FIXED;
// Other initialization.
this.ephemerisSegments = new ArrayList<>();
this.segmentBoundaryTimes = new TreeSet<>();
this.segmentEphemeris = new ArrayList<>();
}
/**
* Returns the UTC scale.
* @return UTC scale
*/
private UTCScale getUTCScale() {
return utc;
}
/**
* Adds an ephemeris point.
* @param time time
* @param pvCoordinates position/velocity coordinates
*/
private void addEphemeris(final double time, final PVCoordinates pvCoordinates) {
if (numberOfEphemerisPoints != null && numberOfEphemerisPointsRead == numberOfEphemerisPoints) {
return;
}
final AbsoluteDate date = scenarioEpoch.shiftedBy(time);
final TimeStampedPVCoordinates timeStampedPVCoordinates = new TimeStampedPVCoordinates(date, pvCoordinates);
if (segmentBoundaryTimes.contains(time) && numberOfEphemerisPointsRead > 0) {
if (segmentEphemeris.isEmpty()) { // begin new segment
if (!date.equals(lastSavedEphemeris.getDate())) {
segmentEphemeris.add(lastSavedEphemeris); // no gaps allowed
}
segmentEphemeris.add(timeStampedPVCoordinates);
} else { // end segment
segmentEphemeris.add(timeStampedPVCoordinates);
ephemerisSegments.add(new STKEphemerisSegment(mu, getFrame(), 1 + interpolationSamplesM1,
cartesianDerivativesFilter, segmentEphemeris));
segmentEphemeris = new ArrayList<>();
}
} else {
segmentEphemeris.add(timeStampedPVCoordinates);
}
lastSavedEphemeris = timeStampedPVCoordinates;
++numberOfEphemerisPointsRead;
}
/**
* Returns the frame.
* @return frame
*/
private Frame getFrame() {
final STKCoordinateSystem stkCoordinateSystem = coordinateSystem == null ? STKCoordinateSystem.FIXED :
coordinateSystem;
final Frame frame = frameMapping.get(stkCoordinateSystem);
if (frame == null) {
throw new OrekitException(OrekitMessages.STK_UNMAPPED_COORDINATE_SYSTEM, stkCoordinateSystem);
}
return frame;
}
/**
* Completes parsing.
*/
private void complete() {
if (!segmentEphemeris.isEmpty()) {
ephemerisSegments.add(new STKEphemerisSegment(mu, getFrame(), 1 + interpolationSamplesM1,
cartesianDerivativesFilter, segmentEphemeris));
}
final STKEphemeris ephemeris = new STKEphemeris(satelliteId, mu, ephemerisSegments);
file = new STKEphemerisFile(stkVersion, satelliteId, ephemeris);
}
}
/** Parser for specific line. */
private enum LineParser {
/** STK version. */
VERSION("^stk\\.v\\.\\d+\\.\\d+$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.stkVersion = line;
}
@Override
public Iterable<LineParser> allowedNext() {
return Collections.singleton(BEGIN_EPHEMERIS);
}
},
/** BEGIN Ephemeris keyword. */
BEGIN_EPHEMERIS("^\\s*BEGIN Ephemeris\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
// nothing to do
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** NumberOfEphemerisPoints keyword. */
NUMBER_OF_EPHEMERIS_POINTS("^\\s*NumberOfEphemerisPoints\\s*\\d+\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.numberOfEphemerisPoints = Integer.parseInt(SEPARATOR.split(line.trim())[1]);
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** ScenarioEpoch keyword. */
SCENARIO_EPOCH("^\\s*ScenarioEpoch\\s* \\d{2} [a-zA-Z]{3} \\d{4} \\d{2}:\\d{2}:\\d{2}(\\.\\d*)?\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
final String[] tokens = SEPARATOR.split(line.trim());
final int dayOfMonth = Integer.parseInt(tokens[1]);
final Month month = Month.parseMonth(tokens[2]);
final int year = Integer.parseInt(tokens[3]);
final int hour = Integer.parseInt(tokens[4].substring(0, 2));
final int minute = Integer.parseInt(tokens[4].substring(3, 5));
final double seconds = Double.parseDouble(tokens[4].substring(6));
final DateTimeComponents dateTimeComponents = new DateTimeComponents(year, month, dayOfMonth, hour, minute, seconds);
pi.scenarioEpoch = new AbsoluteDate(dateTimeComponents, pi.getUTCScale());
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** InterpolationMethod keyword. */
INTERPOLATION_METHOD("^\\s*InterpolationMethod\\s+[a-zA-Z]+\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
// do nothing; this keyword is recognized, but ignored and unsupported
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** InterpolationSamplesM1 keyword. */
INTERPOLATION_SAMPLESM1("^\\s*InterpolationSamplesM1\\s+\\d+\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.interpolationSamplesM1 = Integer.parseInt(SEPARATOR.split(line.trim())[1]);
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** CentralBody keyword. */
CENTRAL_BODY("^\\s*CentralBody\\s+[a-zA-Z]+\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
// do nothing; this keyword is recognized, but ignored and unsupported; Earth
// assumed
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** CoordinateSystem keyword. */
COORDINATE_SYSTEM("^\\s*CoordinateSystem\\s+[a-zA-Z0-9]+\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.coordinateSystem = STKCoordinateSystem.parse(SEPARATOR.split(line.trim())[1]);
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** DistanceUnit keyword. */
DISTANCE_UNIT("^\\s*DistanceUnit\\s+[a-zA-Z0-9]+\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.distanceUnit = STKDistanceUnit.valueOf(SEPARATOR.split(line.trim())[1].toUpperCase());
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** BEGIN SegmentBoundaryTimes keyword. */
BEGIN_SEGMENT_BOUNDARY_TIMES("^\\s*BEGIN SegmentBoundaryTimes\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
// nothing to be done
}
@Override
public Iterable<LineParser> allowedNext() {
return Collections.singleton(SEGMENT_BOUNDARY_TIME);
}
},
/** Segment boundary time. */
SEGMENT_BOUNDARY_TIME(MATCH_ANY_REGEX) {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.segmentBoundaryTimes.add(Double.parseDouble(SEPARATOR.split(line.trim())[0]));
}
@Override
public Iterable<LineParser> allowedNext() {
return Arrays.asList(END_SEGMENT_BOUNDARY_TIMES, SEGMENT_BOUNDARY_TIME);
}
},
/** END SegmentBoundaryTimes keyword. */
END_SEGMENT_BOUNDARY_TIMES("^\\s*END SegmentBoundaryTimes\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
// nothing to be done
}
@Override
public Iterable<LineParser> allowedNext() {
return KEYWORDS;
}
},
/** EphemerisTimePos keyword. */
EPHEMERIS_TIME_POS("^\\s*EphemerisTimePos\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.cartesianDerivativesFilter = CartesianDerivativesFilter.USE_P;
}
@Override
public Iterable<LineParser> allowedNext() {
return Collections.singleton(EPHEMERIS_TIME_POS_DATUM);
}
},
/** EphemerisTimePos datum. */
EPHEMERIS_TIME_POS_DATUM(MATCH_ANY_REGEX) {
@Override
public void parse(final String line, final ParseInfo pi) {
final String[] tokens = SEPARATOR.split(line.trim());
final double time = Double.parseDouble(tokens[0]);
final double px = Double.parseDouble(tokens[1]) * pi.distanceUnit.conversionToMetersFactor;
final double py = Double.parseDouble(tokens[2]) * pi.distanceUnit.conversionToMetersFactor;
final double pz = Double.parseDouble(tokens[3]) * pi.distanceUnit.conversionToMetersFactor;
final Vector3D position = new Vector3D(px, py, pz);
final Vector3D velocity = Vector3D.ZERO;
pi.addEphemeris(time, new PVCoordinates(position, velocity));
}
@Override
public Iterable<LineParser> allowedNext() {
return Arrays.asList(END_EPHEMERIS, EPHEMERIS_TIME_POS_DATUM);
}
},
/** EphemerisTimePosVel keyword. */
EPHEMERIS_TIME_POS_VEL("^\\s*EphemerisTimePosVel\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.cartesianDerivativesFilter = CartesianDerivativesFilter.USE_PV;
}
@Override
public Iterable<LineParser> allowedNext() {
return Collections.singleton(EPHEMERIS_TIME_POS_VEL_DATUM);
}
},
/** EphemerisTimePosVel datum. */
EPHEMERIS_TIME_POS_VEL_DATUM(MATCH_ANY_REGEX) {
@Override
public void parse(final String line, final ParseInfo pi) {
final String[] tokens = SEPARATOR.split(line.trim());
final double time = Double.parseDouble(tokens[0]);
final double px = Double.parseDouble(tokens[1]) * pi.distanceUnit.conversionToMetersFactor;
final double py = Double.parseDouble(tokens[2]) * pi.distanceUnit.conversionToMetersFactor;
final double pz = Double.parseDouble(tokens[3]) * pi.distanceUnit.conversionToMetersFactor;
final double vx = Double.parseDouble(tokens[4]) * pi.distanceUnit.conversionToMetersFactor;
final double vy = Double.parseDouble(tokens[5]) * pi.distanceUnit.conversionToMetersFactor;
final double vz = Double.parseDouble(tokens[6]) * pi.distanceUnit.conversionToMetersFactor;
final Vector3D position = new Vector3D(px, py, pz);
final Vector3D velocity = new Vector3D(vx, vy, vz);
pi.addEphemeris(time, new PVCoordinates(position, velocity));
}
@Override
public Iterable<LineParser> allowedNext() {
return Arrays.asList(END_EPHEMERIS, EPHEMERIS_TIME_POS_VEL_DATUM);
}
},
/** EphemerisTimePosVelAcc keyword. */
EPHEMERIS_TIME_POS_VEL_ACC("^\\s*EphemerisTimePosVelAcc\\s*(#.*)?$") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.cartesianDerivativesFilter = CartesianDerivativesFilter.USE_PVA;
}
@Override
public Iterable<LineParser> allowedNext() {
return Collections.singleton(EPHEMERIS_TIME_POS_VEL_ACC_DATUM);
}
},
/** EphemerisTimePosVelAcc datum. */
EPHEMERIS_TIME_POS_VEL_ACC_DATUM(MATCH_ANY_REGEX) {
@Override
public void parse(final String line, final ParseInfo pi) {
final String[] tokens = SEPARATOR.split(line.trim());
final double time = Double.parseDouble(tokens[0]);
final double px = Double.parseDouble(tokens[1]) * pi.distanceUnit.conversionToMetersFactor;
final double py = Double.parseDouble(tokens[2]) * pi.distanceUnit.conversionToMetersFactor;
final double pz = Double.parseDouble(tokens[3]) * pi.distanceUnit.conversionToMetersFactor;
final double vx = Double.parseDouble(tokens[4]) * pi.distanceUnit.conversionToMetersFactor;
final double vy = Double.parseDouble(tokens[5]) * pi.distanceUnit.conversionToMetersFactor;
final double vz = Double.parseDouble(tokens[6]) * pi.distanceUnit.conversionToMetersFactor;
final double ax = Double.parseDouble(tokens[7]) * pi.distanceUnit.conversionToMetersFactor;
final double ay = Double.parseDouble(tokens[8]) * pi.distanceUnit.conversionToMetersFactor;
final double az = Double.parseDouble(tokens[9]) * pi.distanceUnit.conversionToMetersFactor;
final Vector3D position = new Vector3D(px, py, pz);
final Vector3D velocity = new Vector3D(vx, vy, vz);
final Vector3D acceleration = new Vector3D(ax, ay, az);
pi.addEphemeris(time, new PVCoordinates(position, velocity, acceleration));
}
@Override
public Iterable<LineParser> allowedNext() {
return Arrays.asList(END_EPHEMERIS, EPHEMERIS_TIME_POS_VEL_ACC_DATUM);
}
},
/** END Ephemeris keyword. */
END_EPHEMERIS("\\s*END Ephemeris\\s*(#.*)?") {
@Override
public void parse(final String line, final ParseInfo pi) {
pi.complete();
}
@Override
public Iterable<LineParser> allowedNext() {
return Collections.emptyList();
}
};
/** Pattern for identifying line. */
private final Pattern pattern;
/**
* Constructs a {@link LineParser} instance.
* @param regex regular expression for identifying line
*/
LineParser(final String regex) {
pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
}
/**
* Parses a line.
* @param line line to parse
* @param pi holder for transient data
*/
public abstract void parse(String line, ParseInfo pi);
/**
* Returns the allowed parsers for the next line.
* @return returns the allowed parsers for the next line
*/
public abstract Iterable<LineParser> allowedNext();
/**
* Checks if a 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();
}
}
/** STK distance unit. */
private enum STKDistanceUnit {
/** Kilometers. */
KILOMETERS(1000.0),
/** Meters. */
METERS(1.0);
/** Factor by which to multiply to convert the distance unit to meters. */
private final double conversionToMetersFactor;
/**
* Constructs a {@link STKDistanceUnit} instance.
* @param conversionToMetersFactor factor by which to multiply to
* convert the distance unit to meters
*/
STKDistanceUnit(final double conversionToMetersFactor) {
this.conversionToMetersFactor = conversionToMetersFactor;
}
}
}