TLESeries.java

  1. /* Copyright 2002-2018 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.Comparator;
  23. import java.util.Set;
  24. import java.util.SortedSet;
  25. import java.util.TreeSet;

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

  34. /** This class reads and handles series of TLEs for one space object.
  35.  *  <p>
  36.  *  TLE data is read using the standard Orekit mechanism based on a configured
  37.  *  {@link DataProvidersManager DataProvidersManager}. This means TLE data may
  38.  *  be retrieved from many different storage media (local disk files, remote servers,
  39.  *  database ...).
  40.  *  </p>
  41.  *  <p>
  42.  *  This class provides bounded ephemerides by finding the best initial TLE to
  43.  *  propagate and then handling the propagation.
  44.  *  </p>
  45.  *
  46.  * @see TLE
  47.  * @see DataProvidersManager
  48.  * @author Fabien Maussion
  49.  * @author Luc Maisonobe
  50.  * @deprecated as of 9.0, this class is deprecated without replacement. The file format
  51.  * used was considered to be too specific and the API not really well designed. Users are
  52.  * encouraged to use their own parser for series of TLE
  53.  */
  54. @Deprecated
  55. public class TLESeries implements DataLoader {

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

  58.     /** Regular expression for supported files names. */
  59.     private final String supportedNames;

  60.     /** Available satellite numbers. */
  61.     private final Set<Integer> availableSatNums;

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

  64.     /** Satellite number used for filtering. */
  65.     private int filterSatelliteNumber;

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

  68.     /** Launch number used for filtering. */
  69.     private int filterLaunchNumber;

  70.     /** Launch piece used for filtering. */
  71.     private String filterLaunchPiece;

  72.     /** Previous TLE in the cached selection. */
  73.     private TLE previous;

  74.     /** Next TLE in the cached selection. */
  75.     private TLE next;

  76.     /** Last used TLE. */
  77.     private TLE lastTLE;

  78.     /** Associated propagator. */
  79.     private TLEPropagator lastPropagator;

  80.     /** Date of the first TLE. */
  81.     private AbsoluteDate firstDate;

  82.     /** Date of the last TLE. */
  83.     private AbsoluteDate lastDate;

  84.     /** Indicator for non-TLE extra lines. */
  85.     private final boolean ignoreNonTLELines;

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

  101.         this.supportedNames    = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames;
  102.         availableSatNums       = new TreeSet<Integer>();
  103.         this.ignoreNonTLELines = ignoreNonTLELines;
  104.         filterSatelliteNumber  = -1;
  105.         filterLaunchYear       = -1;
  106.         filterLaunchNumber     = -1;
  107.         filterLaunchPiece      = null;

  108.         tles     = new TreeSet<TimeStamped>(new TLEComparator());
  109.         previous = null;
  110.         next     = null;

  111.     }

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

  126.         availableSatNums.clear();

  127.         // set the filtering parameters
  128.         filterSatelliteNumber = -1;
  129.         filterLaunchYear      = -1;
  130.         filterLaunchNumber    = -1;
  131.         filterLaunchPiece     = null;

  132.         // load the data from the configured data providers
  133.         tles.clear();
  134.         previous = null;
  135.         next     = null;
  136.         DataProvidersManager.getInstance().feed(supportedNames, this);
  137.         if (tles.isEmpty()) {
  138.             throw new OrekitException(OrekitMessages.NO_TLE_DATA_AVAILABLE);
  139.         }

  140.     }

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

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

  164.         if (satelliteNumber < 0) {
  165.             // no filtering at all
  166.             loadTLEData();
  167.         } else {
  168.             // set the filtering parameters
  169.             filterSatelliteNumber = satelliteNumber;
  170.             filterLaunchYear      = -1;
  171.             filterLaunchNumber    = -1;
  172.             filterLaunchPiece     = null;

  173.             // load the data from the configured data providers
  174.             tles.clear();
  175.             previous = null;
  176.             next     = null;
  177.             DataProvidersManager.getInstance().feed(supportedNames, this);
  178.             if (tles.isEmpty()) {
  179.                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_OBJECT, satelliteNumber);
  180.             }
  181.         }

  182.     }

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

  199.         if ((launchYear < 0) || (launchNumber < 0) ||
  200.             (launchPiece == null) || (launchPiece.length() == 0)) {
  201.             // no filtering at all
  202.             loadTLEData();
  203.         } else {
  204.             // set the filtering parameters
  205.             filterSatelliteNumber = -1;
  206.             filterLaunchYear      = launchYear;
  207.             filterLaunchNumber    = launchNumber;
  208.             filterLaunchPiece     = launchPiece;

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

  219.     }

  220.     /** {@inheritDoc} */
  221.     public boolean stillAcceptsData() {
  222.         return tles.isEmpty();
  223.     }

  224.     /** {@inheritDoc} */
  225.     public void loadData(final InputStream input, final String name)
  226.         throws IOException, OrekitException {

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

  229.             int lineNumber     = 0;
  230.             String pendingLine = null;
  231.             for (String line = r.readLine(); line != null; line = r.readLine()) {

  232.                 ++lineNumber;

  233.                 if (pendingLine == null) {

  234.                     // we must wait for the second line
  235.                     pendingLine = line;

  236.                 } else {

  237.                     // safety checks
  238.                     if (!TLE.isFormatOK(pendingLine, line)) {
  239.                         if (ignoreNonTLELines) {
  240.                             // just shift one line
  241.                             pendingLine = line;
  242.                             continue;
  243.                         } else {
  244.                             throw new OrekitException(OrekitMessages.NOT_TLE_LINES,
  245.                                                       lineNumber - 1, lineNumber, pendingLine, line);
  246.                         }
  247.                     }

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

  249.                     if (filterSatelliteNumber < 0) {
  250.                         if ((filterLaunchYear < 0) ||
  251.                             ((tle.getLaunchYear()   == filterLaunchYear) &&
  252.                              (tle.getLaunchNumber() == filterLaunchNumber) &&
  253.                              tle.getLaunchPiece().equals(filterLaunchPiece))) {
  254.                             // we now know the number of the object to load
  255.                             filterSatelliteNumber = tle.getSatelliteNumber();
  256.                         }
  257.                     }

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

  259.                     if (tle.getSatelliteNumber() == filterSatelliteNumber) {
  260.                         // accept this TLE
  261.                         tles.add(tle);
  262.                     }

  263.                     // we need to wait for two new lines
  264.                     pendingLine = null;

  265.                 }

  266.             }

  267.             if ((pendingLine != null) && !ignoreNonTLELines) {
  268.                 // there is an unexpected last line
  269.                 throw new OrekitException(OrekitMessages.MISSING_SECOND_TLE_LINE,
  270.                                           lineNumber, pendingLine);
  271.             }

  272.         } finally {
  273.             r.close();
  274.         }

  275.     }

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

  292.     /** Get the closest TLE to the selected date.
  293.      * @param date the date
  294.      * @return the TLE that will suit the most for propagation.
  295.      */
  296.     public TLE getClosestTLE(final AbsoluteDate date) {

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


  312.         if (headSet.isEmpty()) {
  313.             return (TLE) tailSet.first();
  314.         }
  315.         if (tailSet.isEmpty()) {
  316.             return (TLE) headSet.last();
  317.         }
  318.         previous = (TLE) headSet.last();
  319.         next = (TLE) tailSet.first();

  320.         if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
  321.             return previous;
  322.         } else {
  323.             return next;
  324.         }
  325.     }

  326.     /** Get the start date of the series.
  327.      * @return the first date
  328.      */
  329.     public AbsoluteDate getFirstDate() {
  330.         if (firstDate == null) {
  331.             firstDate = tles.first().getDate();
  332.         }
  333.         return firstDate;
  334.     }

  335.     /** Get the last date of the series.
  336.      * @return the end date
  337.      */
  338.     public AbsoluteDate getLastDate() {
  339.         if (lastDate == null) {
  340.             lastDate = tles.last().getDate();
  341.         }
  342.         return lastDate;
  343.     }

  344.     /** Get the first TLE.
  345.      * @return first TLE
  346.      */
  347.     public TLE getFirst() {
  348.         return (TLE) tles.first();
  349.     }

  350.     /** Get the last TLE.
  351.      * @return last TLE
  352.      */
  353.     public TLE getLast() {
  354.         return (TLE) tles.last();
  355.     }

  356.     /** Comparator allowing different TLEs at same date (see issue 411).
  357.      * @since 9.2
  358.      */
  359.     private static class TLEComparator implements Comparator<TimeStamped> {
  360.         /** {@inheritDoc} */
  361.         @Override
  362.         public int compare(final TimeStamped timeStamped1, final TimeStamped timeStamped2) {
  363.             final int dateCompare = timeStamped1.getDate().compareTo(timeStamped2.getDate());
  364.             if (dateCompare == 0 && timeStamped1 instanceof TLE && timeStamped2 instanceof TLE) {
  365.                 try {
  366.                     final TLE tle1 = (TLE) timeStamped1;
  367.                     final TLE tle2 = (TLE) timeStamped2;
  368.                     final int line1Compare = tle1.getLine1().compareTo(tle2.getLine1());
  369.                     return (line1Compare == 0) ?
  370.                            tle1.getLine2().compareTo(tle2.getLine2()) :
  371.                            line1Compare;
  372.                 } catch (OrekitException oe) {
  373.                     // this should never happen
  374.                     throw new OrekitInternalError(oe);
  375.                 }
  376.             }
  377.             return dateCompare;
  378.         }
  379.     }

  380. }