TLESeries.java

  1. /* Copyright 2002-2013 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.propagation.analytical.tle;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.Set;
  23. import java.util.SortedSet;
  24. import java.util.TreeSet;

  25. import org.orekit.data.DataLoader;
  26. import org.orekit.data.DataProvidersManager;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.time.AbsoluteDate;
  30. import org.orekit.time.ChronologicalComparator;
  31. import org.orekit.time.TimeStamped;
  32. import org.orekit.utils.PVCoordinates;

  33. /** This class reads and handles series of TLEs for one space object.
  34.  *  <p>
  35.  *  TLE data is read using the standard Orekit mechanism based on a configured
  36.  *  {@link DataProvidersManager DataProvidersManager}. This means TLE data may
  37.  *  be retrieved from many different storage media (local disk files, remote servers,
  38.  *  database ...).
  39.  *  </p>
  40.  *  <p>
  41.  *  This class provides bounded ephemerides by finding the best initial TLE to
  42.  *  propagate and then handling the propagation.
  43.  *  </p>
  44.  *
  45.  * @see TLE
  46.  * @see DataProvidersManager
  47.  * @author Fabien Maussion
  48.  * @author Luc Maisonobe
  49.  */
  50. public class TLESeries implements DataLoader {

  51.     /** Default supported files name pattern. */
  52.     private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.tle$";

  53.     /** Regular expression for supported files names. */
  54.     private final String supportedNames;

  55.     /** Available satellite numbers. */
  56.     private final Set<Integer> availableSatNums;

  57.     /** Set containing all TLE entries. */
  58.     private final SortedSet<TimeStamped> tles;

  59.     /** Satellite number used for filtering. */
  60.     private int filterSatelliteNumber;

  61.     /** Launch year used for filtering (all digits). */
  62.     private int filterLaunchYear;

  63.     /** Launch number used for filtering. */
  64.     private int filterLaunchNumber;

  65.     /** Launch piece used for filtering. */
  66.     private String filterLaunchPiece;

  67.     /** Previous TLE in the cached selection. */
  68.     private TLE previous;

  69.     /** Next TLE in the cached selection. */
  70.     private TLE next;

  71.     /** Last used TLE. */
  72.     private TLE lastTLE;

  73.     /** Associated propagator. */
  74.     private TLEPropagator lastPropagator;

  75.     /** Date of the first TLE. */
  76.     private AbsoluteDate firstDate;

  77.     /** Date of the last TLE. */
  78.     private AbsoluteDate lastDate;

  79.     /** Indicator for non-TLE extra lines. */
  80.     private final boolean ignoreNonTLELines;

  81.     /** Simple constructor with a TLE file.
  82.      * <p>This constructor does not load any data by itself. Data must be
  83.      * loaded later on by calling one of the {@link #loadTLEData()
  84.      * loadTLEData()} method, the {@link #loadTLEData(int)
  85.      * loadTLEData(filterSatelliteNumber)} method or the {@link #loadTLEData(int,
  86.      * int, String) loadTLEData(filterLaunchYear, filterLaunchNumber, filterLaunchPiece)} method.<p>
  87.      * @param supportedNames regular expression for supported files names
  88.      * (if null, a default pattern matching files with a ".tle" extension will be used)
  89.      * @param ignoreNonTLELines if true, extra non-TLE lines are silently ignored,
  90.      * if false an exception will be generated when such lines are encountered
  91.      * @see #loadTLEData()
  92.      * @see #loadTLEData(int)
  93.      * @see #loadTLEData(int, int, String)
  94.      */
  95.     public TLESeries(final String supportedNames, final boolean ignoreNonTLELines) {

  96.         this.supportedNames    = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames;
  97.         availableSatNums       = new TreeSet<Integer>();
  98.         this.ignoreNonTLELines = ignoreNonTLELines;
  99.         filterSatelliteNumber  = -1;
  100.         filterLaunchYear       = -1;
  101.         filterLaunchNumber     = -1;
  102.         filterLaunchPiece      = null;

  103.         tles     = new TreeSet<TimeStamped>(new ChronologicalComparator());
  104.         previous = null;
  105.         next     = null;

  106.     }

  107.     /** Load TLE data for a specified object.
  108.      * <p>The TLE data already loaded in the instance will be discarded
  109.      * and replaced by the newly loaded data.</p>
  110.      * <p>The filtering values will be automatically set to the first loaded
  111.      * satellite. This feature is useful when the satellite selection is
  112.      * already set up by either the instance configuration (supported file
  113.      * names) or by the {@link DataProvidersManager data providers manager}
  114.      * configuration and the local filtering feature provided here can be ignored.</p>
  115.      * @exception OrekitException if some data can't be read, some
  116.      * file content is corrupted or no TLE data is available
  117.      * @see #loadTLEData(int)
  118.      * @see #loadTLEData(int, int, String)
  119.      */
  120.     public void loadTLEData() throws OrekitException {

  121.         availableSatNums.clear();

  122.         // set the filtering parameters
  123.         filterSatelliteNumber = -1;
  124.         filterLaunchYear      = -1;
  125.         filterLaunchNumber    = -1;
  126.         filterLaunchPiece     = null;

  127.         // load the data from the configured data providers
  128.         tles.clear();
  129.         DataProvidersManager.getInstance().feed(supportedNames, this);
  130.         if (tles.isEmpty()) {
  131.             throw new OrekitException(OrekitMessages.NO_TLE_DATA_AVAILABLE);
  132.         }

  133.     }

  134.     /** Get the available satellite numbers.
  135.      * @return available satellite numbers
  136.      * @throws OrekitException if some data can't be read, some
  137.      * file content is corrupted or no TLE data is available
  138.      */
  139.     public Set<Integer> getAvailableSatelliteNumbers() throws OrekitException {
  140.         if (availableSatNums.isEmpty()) {
  141.             loadTLEData();
  142.         }
  143.         return availableSatNums;
  144.     }

  145.     /** Load TLE data for a specified object.
  146.      * <p>The TLE data already loaded in the instance will be discarded
  147.      * and replaced by the newly loaded data.</p>
  148.      * <p>Calling this method with the satellite number set to a negative value,
  149.      * is equivalent to call {@link #loadTLEData()}.</p>
  150.      * @param satelliteNumber satellite number
  151.      * @exception OrekitException if some data can't be read, some
  152.      * file content is corrupted or no TLE data is available for the selected object
  153.      * @see #loadTLEData()
  154.      * @see #loadTLEData(int, int, String)
  155.      */
  156.     public void loadTLEData(final int satelliteNumber) throws OrekitException {

  157.         if (satelliteNumber < 0) {
  158.             // no filtering at all
  159.             loadTLEData();
  160.         } else {
  161.             // set the filtering parameters
  162.             filterSatelliteNumber = satelliteNumber;
  163.             filterLaunchYear      = -1;
  164.             filterLaunchNumber    = -1;
  165.             filterLaunchPiece     = null;

  166.             // load the data from the configured data providers
  167.             tles.clear();
  168.             DataProvidersManager.getInstance().feed(supportedNames, this);
  169.             if (tles.isEmpty()) {
  170.                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_OBJECT, satelliteNumber);
  171.             }
  172.         }

  173.     }

  174.     /** Load TLE data for a specified object.
  175.      * <p>The TLE data already loaded in the instance will be discarded
  176.      * and replaced by the newly loaded data.</p>
  177.      * <p>Calling this method with either the launch year or the launch number
  178.      * set to a negative value, or the launch piece set to null or an empty
  179.      * string are all equivalent to call {@link #loadTLEData()}.</p>
  180.      * @param launchYear launch year (all digits)
  181.      * @param launchNumber launch number
  182.      * @param launchPiece launch piece
  183.      * @exception OrekitException if some data can't be read, some
  184.      * file content is corrupted or no TLE data is available for the selected object
  185.      * @see #loadTLEData()
  186.      * @see #loadTLEData(int)
  187.      */
  188.     public void loadTLEData(final int launchYear, final int launchNumber,
  189.                             final String launchPiece) throws OrekitException {

  190.         if ((launchYear < 0) || (launchNumber < 0) ||
  191.             (launchPiece == null) || (launchPiece.length() == 0)) {
  192.             // no filtering at all
  193.             loadTLEData();
  194.         } else {
  195.             // set the filtering parameters
  196.             filterSatelliteNumber = -1;
  197.             filterLaunchYear      = launchYear;
  198.             filterLaunchNumber    = launchNumber;
  199.             filterLaunchPiece     = launchPiece;

  200.             // load the data from the configured data providers
  201.             tles.clear();
  202.             DataProvidersManager.getInstance().feed(supportedNames, this);
  203.             if (tles.isEmpty()) {
  204.                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_LAUNCH_YEAR_NUMBER_PIECE,
  205.                                           launchYear, launchNumber, launchPiece);
  206.             }
  207.         }

  208.     }

  209.     /** {@inheritDoc} */
  210.     public boolean stillAcceptsData() {
  211.         return tles.isEmpty();
  212.     }

  213.     /** {@inheritDoc} */
  214.     public void loadData(final InputStream input, final String name)
  215.         throws IOException, OrekitException {

  216.         final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
  217.         try {

  218.             int lineNumber     = 0;
  219.             String pendingLine = null;
  220.             for (String line = r.readLine(); line != null; line = r.readLine()) {

  221.                 ++lineNumber;

  222.                 if (pendingLine == null) {

  223.                     // we must wait for the second line
  224.                     pendingLine = line;

  225.                 } else {

  226.                     // safety checks
  227.                     if (!TLE.isFormatOK(pendingLine, line)) {
  228.                         if (ignoreNonTLELines) {
  229.                             // just shift one line
  230.                             pendingLine = line;
  231.                             continue;
  232.                         } else {
  233.                             throw new OrekitException(OrekitMessages.NOT_TLE_LINES,
  234.                                                       lineNumber - 1, lineNumber, pendingLine, line);
  235.                         }
  236.                     }

  237.                     final TLE tle = new TLE(pendingLine, line);

  238.                     if (filterSatelliteNumber < 0) {
  239.                         if ((filterLaunchYear < 0) ||
  240.                             ((tle.getLaunchYear()   == filterLaunchYear) &&
  241.                              (tle.getLaunchNumber() == filterLaunchNumber) &&
  242.                              tle.getLaunchPiece().equals(filterLaunchPiece))) {
  243.                             // we now know the number of the object to load
  244.                             filterSatelliteNumber = tle.getSatelliteNumber();
  245.                         }
  246.                     }

  247.                     availableSatNums.add(tle.getSatelliteNumber());

  248.                     if (tle.getSatelliteNumber() == filterSatelliteNumber) {
  249.                         // accept this TLE
  250.                         tles.add(tle);
  251.                     }

  252.                     // we need to wait for two new lines
  253.                     pendingLine = null;

  254.                 }

  255.             }

  256.             if ((pendingLine != null) && !ignoreNonTLELines) {
  257.                 // there is an unexpected last line
  258.                 throw new OrekitException(OrekitMessages.MISSING_SECOND_TLE_LINE,
  259.                                           lineNumber, pendingLine);
  260.             }

  261.         } finally {
  262.             r.close();
  263.         }

  264.     }

  265.     /** Get the extrapolated position and velocity from an initial date.
  266.      * For a good precision, this date should not be too far from the range :
  267.      * [{@link #getFirstDate() first date} ; {@link #getLastDate() last date}].
  268.      * @param date the final date
  269.      * @return the final PVCoordinates
  270.      * @exception OrekitException if the underlying propagator cannot be initialized
  271.      */
  272.     public PVCoordinates getPVCoordinates(final AbsoluteDate date)
  273.         throws OrekitException {
  274.         final TLE toExtrapolate = getClosestTLE(date);
  275.         if (toExtrapolate != lastTLE) {
  276.             lastTLE = toExtrapolate;
  277.             lastPropagator = TLEPropagator.selectExtrapolator(lastTLE);
  278.         }
  279.         return lastPropagator.getPVCoordinates(date);
  280.     }

  281.     /** Get the closest TLE to the selected date.
  282.      * @param date the date
  283.      * @return the TLE that will suit the most for propagation.
  284.      */
  285.     public TLE getClosestTLE(final AbsoluteDate date) {

  286.         //  don't search if the cached selection is fine
  287.         if ((previous != null) && (date.durationFrom(previous.getDate()) >= 0) &&
  288.             (next     != null) && (date.durationFrom(next.getDate())     <= 0)) {
  289.             // the current selection is already good
  290.             if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
  291.                 return previous;
  292.             } else {
  293.                 return next;
  294.             }
  295.         }
  296.         // reset the selection before the search phase
  297.         previous  = null;
  298.         next      = null;
  299.         final SortedSet<TimeStamped> headSet = tles.headSet(date);
  300.         final SortedSet<TimeStamped> tailSet = tles.tailSet(date);


  301.         if (headSet.isEmpty()) {
  302.             return (TLE) tailSet.first();
  303.         }
  304.         if (tailSet.isEmpty()) {
  305.             return (TLE) headSet.last();
  306.         }
  307.         previous = (TLE) headSet.last();
  308.         next = (TLE) tailSet.first();

  309.         if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
  310.             return previous;
  311.         } else {
  312.             return next;
  313.         }
  314.     }

  315.     /** Get the start date of the series.
  316.      * @return the first date
  317.      */
  318.     public AbsoluteDate getFirstDate() {
  319.         if (firstDate == null) {
  320.             firstDate = tles.first().getDate();
  321.         }
  322.         return firstDate;
  323.     }

  324.     /** Get the last date of the series.
  325.      * @return the end date
  326.      */
  327.     public AbsoluteDate getLastDate() {
  328.         if (lastDate == null) {
  329.             lastDate = tles.last().getDate();
  330.         }
  331.         return lastDate;
  332.     }

  333.     /** Get the first TLE.
  334.      * @return first TLE
  335.      */
  336.     public TLE getFirst() {
  337.         return (TLE) tles.first();
  338.     }

  339.     /** Get the last TLE.
  340.      * @return last TLE
  341.      */
  342.     public TLE getLast() {
  343.         return (TLE) tles.last();
  344.     }

  345. }