TLESeries.java
- /* Copyright 2002-2013 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.propagation.analytical.tle;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.util.Set;
- import java.util.SortedSet;
- import java.util.TreeSet;
- import org.orekit.data.DataLoader;
- import org.orekit.data.DataProvidersManager;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.time.ChronologicalComparator;
- import org.orekit.time.TimeStamped;
- import org.orekit.utils.PVCoordinates;
- /** This class reads and handles series of TLEs for one space object.
- * <p>
- * TLE data is read using the standard Orekit mechanism based on a configured
- * {@link DataProvidersManager DataProvidersManager}. This means TLE data may
- * be retrieved from many different storage media (local disk files, remote servers,
- * database ...).
- * </p>
- * <p>
- * This class provides bounded ephemerides by finding the best initial TLE to
- * propagate and then handling the propagation.
- * </p>
- *
- * @see TLE
- * @see DataProvidersManager
- * @author Fabien Maussion
- * @author Luc Maisonobe
- */
- public class TLESeries implements DataLoader {
- /** Default supported files name pattern. */
- private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.tle$";
- /** Regular expression for supported files names. */
- private final String supportedNames;
- /** Available satellite numbers. */
- private final Set<Integer> availableSatNums;
- /** Set containing all TLE entries. */
- private final SortedSet<TimeStamped> tles;
- /** Satellite number used for filtering. */
- private int filterSatelliteNumber;
- /** Launch year used for filtering (all digits). */
- private int filterLaunchYear;
- /** Launch number used for filtering. */
- private int filterLaunchNumber;
- /** Launch piece used for filtering. */
- private String filterLaunchPiece;
- /** Previous TLE in the cached selection. */
- private TLE previous;
- /** Next TLE in the cached selection. */
- private TLE next;
- /** Last used TLE. */
- private TLE lastTLE;
- /** Associated propagator. */
- private TLEPropagator lastPropagator;
- /** Date of the first TLE. */
- private AbsoluteDate firstDate;
- /** Date of the last TLE. */
- private AbsoluteDate lastDate;
- /** Indicator for non-TLE extra lines. */
- private final boolean ignoreNonTLELines;
- /** Simple constructor with a TLE file.
- * <p>This constructor does not load any data by itself. Data must be
- * loaded later on by calling one of the {@link #loadTLEData()
- * loadTLEData()} method, the {@link #loadTLEData(int)
- * loadTLEData(filterSatelliteNumber)} method or the {@link #loadTLEData(int,
- * int, String) loadTLEData(filterLaunchYear, filterLaunchNumber, filterLaunchPiece)} method.<p>
- * @param supportedNames regular expression for supported files names
- * (if null, a default pattern matching files with a ".tle" extension will be used)
- * @param ignoreNonTLELines if true, extra non-TLE lines are silently ignored,
- * if false an exception will be generated when such lines are encountered
- * @see #loadTLEData()
- * @see #loadTLEData(int)
- * @see #loadTLEData(int, int, String)
- */
- public TLESeries(final String supportedNames, final boolean ignoreNonTLELines) {
- this.supportedNames = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames;
- availableSatNums = new TreeSet<Integer>();
- this.ignoreNonTLELines = ignoreNonTLELines;
- filterSatelliteNumber = -1;
- filterLaunchYear = -1;
- filterLaunchNumber = -1;
- filterLaunchPiece = null;
- tles = new TreeSet<TimeStamped>(new ChronologicalComparator());
- previous = null;
- next = null;
- }
- /** Load TLE data for a specified object.
- * <p>The TLE data already loaded in the instance will be discarded
- * and replaced by the newly loaded data.</p>
- * <p>The filtering values will be automatically set to the first loaded
- * satellite. This feature is useful when the satellite selection is
- * already set up by either the instance configuration (supported file
- * names) or by the {@link DataProvidersManager data providers manager}
- * configuration and the local filtering feature provided here can be ignored.</p>
- * @exception OrekitException if some data can't be read, some
- * file content is corrupted or no TLE data is available
- * @see #loadTLEData(int)
- * @see #loadTLEData(int, int, String)
- */
- public void loadTLEData() throws OrekitException {
- availableSatNums.clear();
- // set the filtering parameters
- filterSatelliteNumber = -1;
- filterLaunchYear = -1;
- filterLaunchNumber = -1;
- filterLaunchPiece = null;
- // load the data from the configured data providers
- tles.clear();
- DataProvidersManager.getInstance().feed(supportedNames, this);
- if (tles.isEmpty()) {
- throw new OrekitException(OrekitMessages.NO_TLE_DATA_AVAILABLE);
- }
- }
- /** Get the available satellite numbers.
- * @return available satellite numbers
- * @throws OrekitException if some data can't be read, some
- * file content is corrupted or no TLE data is available
- */
- public Set<Integer> getAvailableSatelliteNumbers() throws OrekitException {
- if (availableSatNums.isEmpty()) {
- loadTLEData();
- }
- return availableSatNums;
- }
- /** Load TLE data for a specified object.
- * <p>The TLE data already loaded in the instance will be discarded
- * and replaced by the newly loaded data.</p>
- * <p>Calling this method with the satellite number set to a negative value,
- * is equivalent to call {@link #loadTLEData()}.</p>
- * @param satelliteNumber satellite number
- * @exception OrekitException if some data can't be read, some
- * file content is corrupted or no TLE data is available for the selected object
- * @see #loadTLEData()
- * @see #loadTLEData(int, int, String)
- */
- public void loadTLEData(final int satelliteNumber) throws OrekitException {
- if (satelliteNumber < 0) {
- // no filtering at all
- loadTLEData();
- } else {
- // set the filtering parameters
- filterSatelliteNumber = satelliteNumber;
- filterLaunchYear = -1;
- filterLaunchNumber = -1;
- filterLaunchPiece = null;
- // load the data from the configured data providers
- tles.clear();
- DataProvidersManager.getInstance().feed(supportedNames, this);
- if (tles.isEmpty()) {
- throw new OrekitException(OrekitMessages.NO_TLE_FOR_OBJECT, satelliteNumber);
- }
- }
- }
- /** Load TLE data for a specified object.
- * <p>The TLE data already loaded in the instance will be discarded
- * and replaced by the newly loaded data.</p>
- * <p>Calling this method with either the launch year or the launch number
- * set to a negative value, or the launch piece set to null or an empty
- * string are all equivalent to call {@link #loadTLEData()}.</p>
- * @param launchYear launch year (all digits)
- * @param launchNumber launch number
- * @param launchPiece launch piece
- * @exception OrekitException if some data can't be read, some
- * file content is corrupted or no TLE data is available for the selected object
- * @see #loadTLEData()
- * @see #loadTLEData(int)
- */
- public void loadTLEData(final int launchYear, final int launchNumber,
- final String launchPiece) throws OrekitException {
- if ((launchYear < 0) || (launchNumber < 0) ||
- (launchPiece == null) || (launchPiece.length() == 0)) {
- // no filtering at all
- loadTLEData();
- } else {
- // set the filtering parameters
- filterSatelliteNumber = -1;
- filterLaunchYear = launchYear;
- filterLaunchNumber = launchNumber;
- filterLaunchPiece = launchPiece;
- // load the data from the configured data providers
- tles.clear();
- DataProvidersManager.getInstance().feed(supportedNames, this);
- if (tles.isEmpty()) {
- throw new OrekitException(OrekitMessages.NO_TLE_FOR_LAUNCH_YEAR_NUMBER_PIECE,
- launchYear, launchNumber, launchPiece);
- }
- }
- }
- /** {@inheritDoc} */
- public boolean stillAcceptsData() {
- return tles.isEmpty();
- }
- /** {@inheritDoc} */
- public void loadData(final InputStream input, final String name)
- throws IOException, OrekitException {
- final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
- try {
- int lineNumber = 0;
- String pendingLine = null;
- for (String line = r.readLine(); line != null; line = r.readLine()) {
- ++lineNumber;
- if (pendingLine == null) {
- // we must wait for the second line
- pendingLine = line;
- } else {
- // safety checks
- if (!TLE.isFormatOK(pendingLine, line)) {
- if (ignoreNonTLELines) {
- // just shift one line
- pendingLine = line;
- continue;
- } else {
- throw new OrekitException(OrekitMessages.NOT_TLE_LINES,
- lineNumber - 1, lineNumber, pendingLine, line);
- }
- }
- final TLE tle = new TLE(pendingLine, line);
- if (filterSatelliteNumber < 0) {
- if ((filterLaunchYear < 0) ||
- ((tle.getLaunchYear() == filterLaunchYear) &&
- (tle.getLaunchNumber() == filterLaunchNumber) &&
- tle.getLaunchPiece().equals(filterLaunchPiece))) {
- // we now know the number of the object to load
- filterSatelliteNumber = tle.getSatelliteNumber();
- }
- }
- availableSatNums.add(tle.getSatelliteNumber());
- if (tle.getSatelliteNumber() == filterSatelliteNumber) {
- // accept this TLE
- tles.add(tle);
- }
- // we need to wait for two new lines
- pendingLine = null;
- }
- }
- if ((pendingLine != null) && !ignoreNonTLELines) {
- // there is an unexpected last line
- throw new OrekitException(OrekitMessages.MISSING_SECOND_TLE_LINE,
- lineNumber, pendingLine);
- }
- } finally {
- r.close();
- }
- }
- /** Get the extrapolated position and velocity from an initial date.
- * For a good precision, this date should not be too far from the range :
- * [{@link #getFirstDate() first date} ; {@link #getLastDate() last date}].
- * @param date the final date
- * @return the final PVCoordinates
- * @exception OrekitException if the underlying propagator cannot be initialized
- */
- public PVCoordinates getPVCoordinates(final AbsoluteDate date)
- throws OrekitException {
- final TLE toExtrapolate = getClosestTLE(date);
- if (toExtrapolate != lastTLE) {
- lastTLE = toExtrapolate;
- lastPropagator = TLEPropagator.selectExtrapolator(lastTLE);
- }
- return lastPropagator.getPVCoordinates(date);
- }
- /** Get the closest TLE to the selected date.
- * @param date the date
- * @return the TLE that will suit the most for propagation.
- */
- public TLE getClosestTLE(final AbsoluteDate date) {
- // don't search if the cached selection is fine
- if ((previous != null) && (date.durationFrom(previous.getDate()) >= 0) &&
- (next != null) && (date.durationFrom(next.getDate()) <= 0)) {
- // the current selection is already good
- if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
- return previous;
- } else {
- return next;
- }
- }
- // reset the selection before the search phase
- previous = null;
- next = null;
- final SortedSet<TimeStamped> headSet = tles.headSet(date);
- final SortedSet<TimeStamped> tailSet = tles.tailSet(date);
- if (headSet.isEmpty()) {
- return (TLE) tailSet.first();
- }
- if (tailSet.isEmpty()) {
- return (TLE) headSet.last();
- }
- previous = (TLE) headSet.last();
- next = (TLE) tailSet.first();
- if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
- return previous;
- } else {
- return next;
- }
- }
- /** Get the start date of the series.
- * @return the first date
- */
- public AbsoluteDate getFirstDate() {
- if (firstDate == null) {
- firstDate = tles.first().getDate();
- }
- return firstDate;
- }
- /** Get the last date of the series.
- * @return the end date
- */
- public AbsoluteDate getLastDate() {
- if (lastDate == null) {
- lastDate = tles.last().getDate();
- }
- return lastDate;
- }
- /** Get the first TLE.
- * @return first TLE
- */
- public TLE getFirst() {
- return (TLE) tles.first();
- }
- /** Get the last TLE.
- * @return last TLE
- */
- public TLE getLast() {
- return (TLE) tles.last();
- }
- }