TLESeries.java

  1. /* Copyright 2002-2019 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.      * @see #loadTLEData(int)
  121.      * @see #loadTLEData(int, int, String)
  122.      */
  123.     public void loadTLEData() {

  124.         availableSatNums.clear();

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

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

  138.     }

  139.     /** Get the available satellite numbers.
  140.      * @return available satellite numbers
  141.      * file content is corrupted or no TLE data is available
  142.      */
  143.     public Set<Integer> getAvailableSatelliteNumbers() {
  144.         if (availableSatNums.isEmpty()) {
  145.             loadTLEData();
  146.         }
  147.         return availableSatNums;
  148.     }

  149.     /** Load TLE data for a specified object.
  150.      * <p>The TLE data already loaded in the instance will be discarded
  151.      * and replaced by the newly loaded data.</p>
  152.      * <p>Calling this method with the satellite number set to a negative value,
  153.      * is equivalent to call {@link #loadTLEData()}.</p>
  154.      * @param satelliteNumber satellite number
  155.      * @see #loadTLEData()
  156.      * @see #loadTLEData(int, int, String)
  157.      */
  158.     public void loadTLEData(final int satelliteNumber) {

  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.      * @see #loadTLEData()
  188.      * @see #loadTLEData(int)
  189.      */
  190.     public void loadTLEData(final int launchYear, final int launchNumber,
  191.                             final String launchPiece) {

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

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

  212.     }

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

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

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

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

  225.                 ++lineNumber;

  226.                 if (pendingLine == null) {

  227.                     // we must wait for the second line
  228.                     pendingLine = line;

  229.                 } else {

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

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

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

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

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

  256.                     // we need to wait for two new lines
  257.                     pendingLine = null;

  258.                 }

  259.             }

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

  265.         } finally {
  266.             r.close();
  267.         }

  268.     }

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

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

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


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

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

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

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

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

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

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

  371. }