TLESeries.java

  1. /* Copyright 2002-2016 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.         previous = null;
  130.         next     = null;
  131.         DataProvidersManager.getInstance().feed(supportedNames, this);
  132.         if (tles.isEmpty()) {
  133.             throw new OrekitException(OrekitMessages.NO_TLE_DATA_AVAILABLE);
  134.         }

  135.     }

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

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

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

  168.             // load the data from the configured data providers
  169.             tles.clear();
  170.             previous = null;
  171.             next     = null;
  172.             DataProvidersManager.getInstance().feed(supportedNames, this);
  173.             if (tles.isEmpty()) {
  174.                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_OBJECT, satelliteNumber);
  175.             }
  176.         }

  177.     }

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

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

  204.             // load the data from the configured data providers
  205.             tles.clear();
  206.             previous = null;
  207.             next     = null;
  208.             DataProvidersManager.getInstance().feed(supportedNames, this);
  209.             if (tles.isEmpty()) {
  210.                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_LAUNCH_YEAR_NUMBER_PIECE,
  211.                                           launchYear, launchNumber, launchPiece);
  212.             }
  213.         }

  214.     }

  215.     /** {@inheritDoc} */
  216.     public boolean stillAcceptsData() {
  217.         return tles.isEmpty();
  218.     }

  219.     /** {@inheritDoc} */
  220.     public void loadData(final InputStream input, final String name)
  221.         throws IOException, OrekitException {

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

  224.             int lineNumber     = 0;
  225.             String pendingLine = null;
  226.             for (String line = r.readLine(); line != null; line = r.readLine()) {

  227.                 ++lineNumber;

  228.                 if (pendingLine == null) {

  229.                     // we must wait for the second line
  230.                     pendingLine = line;

  231.                 } else {

  232.                     // safety checks
  233.                     if (!TLE.isFormatOK(pendingLine, line)) {
  234.                         if (ignoreNonTLELines) {
  235.                             // just shift one line
  236.                             pendingLine = line;
  237.                             continue;
  238.                         } else {
  239.                             throw new OrekitException(OrekitMessages.NOT_TLE_LINES,
  240.                                                       lineNumber - 1, lineNumber, pendingLine, line);
  241.                         }
  242.                     }

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

  244.                     if (filterSatelliteNumber < 0) {
  245.                         if ((filterLaunchYear < 0) ||
  246.                             ((tle.getLaunchYear()   == filterLaunchYear) &&
  247.                              (tle.getLaunchNumber() == filterLaunchNumber) &&
  248.                              tle.getLaunchPiece().equals(filterLaunchPiece))) {
  249.                             // we now know the number of the object to load
  250.                             filterSatelliteNumber = tle.getSatelliteNumber();
  251.                         }
  252.                     }

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

  254.                     if (tle.getSatelliteNumber() == filterSatelliteNumber) {
  255.                         // accept this TLE
  256.                         tles.add(tle);
  257.                     }

  258.                     // we need to wait for two new lines
  259.                     pendingLine = null;

  260.                 }

  261.             }

  262.             if ((pendingLine != null) && !ignoreNonTLELines) {
  263.                 // there is an unexpected last line
  264.                 throw new OrekitException(OrekitMessages.MISSING_SECOND_TLE_LINE,
  265.                                           lineNumber, pendingLine);
  266.             }

  267.         } finally {
  268.             r.close();
  269.         }

  270.     }

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

  287.     /** Get the closest TLE to the selected date.
  288.      * @param date the date
  289.      * @return the TLE that will suit the most for propagation.
  290.      */
  291.     public TLE getClosestTLE(final AbsoluteDate date) {

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


  307.         if (headSet.isEmpty()) {
  308.             return (TLE) tailSet.first();
  309.         }
  310.         if (tailSet.isEmpty()) {
  311.             return (TLE) headSet.last();
  312.         }
  313.         previous = (TLE) headSet.last();
  314.         next = (TLE) tailSet.first();

  315.         if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
  316.             return previous;
  317.         } else {
  318.             return next;
  319.         }
  320.     }

  321.     /** Get the start date of the series.
  322.      * @return the first date
  323.      */
  324.     public AbsoluteDate getFirstDate() {
  325.         if (firstDate == null) {
  326.             firstDate = tles.first().getDate();
  327.         }
  328.         return firstDate;
  329.     }

  330.     /** Get the last date of the series.
  331.      * @return the end date
  332.      */
  333.     public AbsoluteDate getLastDate() {
  334.         if (lastDate == null) {
  335.             lastDate = tles.last().getDate();
  336.         }
  337.         return lastDate;
  338.     }

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

  345.     /** Get the last TLE.
  346.      * @return last TLE
  347.      */
  348.     public TLE getLast() {
  349.         return (TLE) tles.last();
  350.     }

  351. }