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  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.util.Comparator;
24  import java.util.Set;
25  import java.util.SortedSet;
26  import java.util.TreeSet;
27  
28  import org.orekit.data.DataLoader;
29  import org.orekit.data.DataProvidersManager;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitInternalError;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.time.AbsoluteDate;
34  import org.orekit.time.TimeStamped;
35  import org.orekit.utils.PVCoordinates;
36  
37  /** This class reads and handles series of TLEs for one space object.
38   *  <p>
39   *  TLE data is read using the standard Orekit mechanism based on a configured
40   *  {@link DataProvidersManager DataProvidersManager}. This means TLE data may
41   *  be retrieved from many different storage media (local disk files, remote servers,
42   *  database ...).
43   *  </p>
44   *  <p>
45   *  This class provides bounded ephemerides by finding the best initial TLE to
46   *  propagate and then handling the propagation.
47   *  </p>
48   *
49   * @see TLE
50   * @see DataProvidersManager
51   * @author Fabien Maussion
52   * @author Luc Maisonobe
53   * @deprecated as of 9.0, this class is deprecated without replacement. The file format
54   * used was considered to be too specific and the API not really well designed. Users are
55   * encouraged to use their own parser for series of TLE
56   */
57  @Deprecated
58  public class TLESeries implements DataLoader {
59  
60      /** Default supported files name pattern. */
61      private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.tle$";
62  
63      /** Regular expression for supported files names. */
64      private final String supportedNames;
65  
66      /** Available satellite numbers. */
67      private final Set<Integer> availableSatNums;
68  
69      /** Set containing all TLE entries. */
70      private final SortedSet<TimeStamped> tles;
71  
72      /** Satellite number used for filtering. */
73      private int filterSatelliteNumber;
74  
75      /** Launch year used for filtering (all digits). */
76      private int filterLaunchYear;
77  
78      /** Launch number used for filtering. */
79      private int filterLaunchNumber;
80  
81      /** Launch piece used for filtering. */
82      private String filterLaunchPiece;
83  
84      /** Previous TLE in the cached selection. */
85      private TLE previous;
86  
87      /** Next TLE in the cached selection. */
88      private TLE next;
89  
90      /** Last used TLE. */
91      private TLE lastTLE;
92  
93      /** Associated propagator. */
94      private TLEPropagator lastPropagator;
95  
96      /** Date of the first TLE. */
97      private AbsoluteDate firstDate;
98  
99      /** Date of the last TLE. */
100     private AbsoluteDate lastDate;
101 
102     /** Indicator for non-TLE extra lines. */
103     private final boolean ignoreNonTLELines;
104 
105     /** Simple constructor with a TLE file.
106      * <p>This constructor does not load any data by itself. Data must be
107      * loaded later on by calling one of the {@link #loadTLEData()
108      * loadTLEData()} method, the {@link #loadTLEData(int)
109      * loadTLEData(filterSatelliteNumber)} method or the {@link #loadTLEData(int,
110      * int, String) loadTLEData(filterLaunchYear, filterLaunchNumber, filterLaunchPiece)} method.<p>
111      * @param supportedNames regular expression for supported files names
112      * (if null, a default pattern matching files with a ".tle" extension will be used)
113      * @param ignoreNonTLELines if true, extra non-TLE lines are silently ignored,
114      * if false an exception will be generated when such lines are encountered
115      * @see #loadTLEData()
116      * @see #loadTLEData(int)
117      * @see #loadTLEData(int, int, String)
118      */
119     public TLESeries(final String supportedNames, final boolean ignoreNonTLELines) {
120 
121         this.supportedNames    = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames;
122         availableSatNums       = new TreeSet<Integer>();
123         this.ignoreNonTLELines = ignoreNonTLELines;
124         filterSatelliteNumber  = -1;
125         filterLaunchYear       = -1;
126         filterLaunchNumber     = -1;
127         filterLaunchPiece      = null;
128 
129         tles     = new TreeSet<TimeStamped>(new TLEComparator());
130         previous = null;
131         next     = null;
132 
133     }
134 
135     /** Load TLE data for a specified object.
136      * <p>The TLE data already loaded in the instance will be discarded
137      * and replaced by the newly loaded data.</p>
138      * <p>The filtering values will be automatically set to the first loaded
139      * satellite. This feature is useful when the satellite selection is
140      * already set up by either the instance configuration (supported file
141      * names) or by the {@link DataProvidersManager data providers manager}
142      * configuration and the local filtering feature provided here can be ignored.</p>
143      * @exception OrekitException if some data can't be read, some
144      * file content is corrupted or no TLE data is available
145      * @see #loadTLEData(int)
146      * @see #loadTLEData(int, int, String)
147      */
148     public void loadTLEData() throws OrekitException {
149 
150         availableSatNums.clear();
151 
152         // set the filtering parameters
153         filterSatelliteNumber = -1;
154         filterLaunchYear      = -1;
155         filterLaunchNumber    = -1;
156         filterLaunchPiece     = null;
157 
158         // load the data from the configured data providers
159         tles.clear();
160         previous = null;
161         next     = null;
162         DataProvidersManager.getInstance().feed(supportedNames, this);
163         if (tles.isEmpty()) {
164             throw new OrekitException(OrekitMessages.NO_TLE_DATA_AVAILABLE);
165         }
166 
167     }
168 
169     /** Get the available satellite numbers.
170      * @return available satellite numbers
171      * @throws OrekitException if some data can't be read, some
172      * file content is corrupted or no TLE data is available
173      */
174     public Set<Integer> getAvailableSatelliteNumbers() throws OrekitException {
175         if (availableSatNums.isEmpty()) {
176             loadTLEData();
177         }
178         return availableSatNums;
179     }
180 
181     /** Load TLE data for a specified object.
182      * <p>The TLE data already loaded in the instance will be discarded
183      * and replaced by the newly loaded data.</p>
184      * <p>Calling this method with the satellite number set to a negative value,
185      * is equivalent to call {@link #loadTLEData()}.</p>
186      * @param satelliteNumber satellite number
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, int, String)
191      */
192     public void loadTLEData(final int satelliteNumber) throws OrekitException {
193 
194         if (satelliteNumber < 0) {
195             // no filtering at all
196             loadTLEData();
197         } else {
198             // set the filtering parameters
199             filterSatelliteNumber = satelliteNumber;
200             filterLaunchYear      = -1;
201             filterLaunchNumber    = -1;
202             filterLaunchPiece     = null;
203 
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_OBJECT, satelliteNumber);
211             }
212         }
213 
214     }
215 
216     /** Load TLE data for a specified object.
217      * <p>The TLE data already loaded in the instance will be discarded
218      * and replaced by the newly loaded data.</p>
219      * <p>Calling this method with either the launch year or the launch number
220      * set to a negative value, or the launch piece set to null or an empty
221      * string are all equivalent to call {@link #loadTLEData()}.</p>
222      * @param launchYear launch year (all digits)
223      * @param launchNumber launch number
224      * @param launchPiece launch piece
225      * @exception OrekitException if some data can't be read, some
226      * file content is corrupted or no TLE data is available for the selected object
227      * @see #loadTLEData()
228      * @see #loadTLEData(int)
229      */
230     public void loadTLEData(final int launchYear, final int launchNumber,
231                             final String launchPiece) throws OrekitException {
232 
233         if ((launchYear < 0) || (launchNumber < 0) ||
234             (launchPiece == null) || (launchPiece.length() == 0)) {
235             // no filtering at all
236             loadTLEData();
237         } else {
238             // set the filtering parameters
239             filterSatelliteNumber = -1;
240             filterLaunchYear      = launchYear;
241             filterLaunchNumber    = launchNumber;
242             filterLaunchPiece     = launchPiece;
243 
244             // load the data from the configured data providers
245             tles.clear();
246             previous = null;
247             next     = null;
248             DataProvidersManager.getInstance().feed(supportedNames, this);
249             if (tles.isEmpty()) {
250                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_LAUNCH_YEAR_NUMBER_PIECE,
251                                           launchYear, launchNumber, launchPiece);
252             }
253         }
254 
255     }
256 
257     /** {@inheritDoc} */
258     public boolean stillAcceptsData() {
259         return tles.isEmpty();
260     }
261 
262     /** {@inheritDoc} */
263     public void loadData(final InputStream input, final String name)
264         throws IOException, OrekitException {
265 
266         final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
267         try {
268 
269             int lineNumber     = 0;
270             String pendingLine = null;
271             for (String line = r.readLine(); line != null; line = r.readLine()) {
272 
273                 ++lineNumber;
274 
275                 if (pendingLine == null) {
276 
277                     // we must wait for the second line
278                     pendingLine = line;
279 
280                 } else {
281 
282                     // safety checks
283                     if (!TLE.isFormatOK(pendingLine, line)) {
284                         if (ignoreNonTLELines) {
285                             // just shift one line
286                             pendingLine = line;
287                             continue;
288                         } else {
289                             throw new OrekitException(OrekitMessages.NOT_TLE_LINES,
290                                                       lineNumber - 1, lineNumber, pendingLine, line);
291                         }
292                     }
293 
294                     final TLE tle = new TLE(pendingLine, line);
295 
296                     if (filterSatelliteNumber < 0) {
297                         if ((filterLaunchYear < 0) ||
298                             ((tle.getLaunchYear()   == filterLaunchYear) &&
299                              (tle.getLaunchNumber() == filterLaunchNumber) &&
300                              tle.getLaunchPiece().equals(filterLaunchPiece))) {
301                             // we now know the number of the object to load
302                             filterSatelliteNumber = tle.getSatelliteNumber();
303                         }
304                     }
305 
306                     availableSatNums.add(tle.getSatelliteNumber());
307 
308                     if (tle.getSatelliteNumber() == filterSatelliteNumber) {
309                         // accept this TLE
310                         tles.add(tle);
311                     }
312 
313                     // we need to wait for two new lines
314                     pendingLine = null;
315 
316                 }
317 
318             }
319 
320             if ((pendingLine != null) && !ignoreNonTLELines) {
321                 // there is an unexpected last line
322                 throw new OrekitException(OrekitMessages.MISSING_SECOND_TLE_LINE,
323                                           lineNumber, pendingLine);
324             }
325 
326         } finally {
327             r.close();
328         }
329 
330     }
331 
332     /** Get the extrapolated position and velocity from an initial date.
333      * For a good precision, this date should not be too far from the range :
334      * [{@link #getFirstDate() first date} ; {@link #getLastDate() last date}].
335      * @param date the final date
336      * @return the final PVCoordinates
337      * @exception OrekitException if the underlying propagator cannot be initialized
338      */
339     public PVCoordinates getPVCoordinates(final AbsoluteDate date)
340         throws OrekitException {
341         final TLE toExtrapolate = getClosestTLE(date);
342         if (toExtrapolate != lastTLE) {
343             lastTLE = toExtrapolate;
344             lastPropagator = TLEPropagator.selectExtrapolator(lastTLE);
345         }
346         return lastPropagator.getPVCoordinates(date);
347     }
348 
349     /** Get the closest TLE to the selected date.
350      * @param date the date
351      * @return the TLE that will suit the most for propagation.
352      */
353     public TLE getClosestTLE(final AbsoluteDate date) {
354 
355         //  don't search if the cached selection is fine
356         if ((previous != null) && (date.durationFrom(previous.getDate()) >= 0) &&
357             (next     != null) && (date.durationFrom(next.getDate())     <= 0)) {
358             // the current selection is already good
359             if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
360                 return previous;
361             } else {
362                 return next;
363             }
364         }
365         // reset the selection before the search phase
366         previous  = null;
367         next      = null;
368         final SortedSet<TimeStamped> headSet = tles.headSet(date);
369         final SortedSet<TimeStamped> tailSet = tles.tailSet(date);
370 
371 
372         if (headSet.isEmpty()) {
373             return (TLE) tailSet.first();
374         }
375         if (tailSet.isEmpty()) {
376             return (TLE) headSet.last();
377         }
378         previous = (TLE) headSet.last();
379         next = (TLE) tailSet.first();
380 
381         if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
382             return previous;
383         } else {
384             return next;
385         }
386     }
387 
388     /** Get the start date of the series.
389      * @return the first date
390      */
391     public AbsoluteDate getFirstDate() {
392         if (firstDate == null) {
393             firstDate = tles.first().getDate();
394         }
395         return firstDate;
396     }
397 
398     /** Get the last date of the series.
399      * @return the end date
400      */
401     public AbsoluteDate getLastDate() {
402         if (lastDate == null) {
403             lastDate = tles.last().getDate();
404         }
405         return lastDate;
406     }
407 
408     /** Get the first TLE.
409      * @return first TLE
410      */
411     public TLE getFirst() {
412         return (TLE) tles.first();
413     }
414 
415     /** Get the last TLE.
416      * @return last TLE
417      */
418     public TLE getLast() {
419         return (TLE) tles.last();
420     }
421 
422     /** Comparator allowing different TLEs at same date (see issue 411).
423      * @since 9.2
424      */
425     private static class TLEComparator implements Comparator<TimeStamped> {
426         /** {@inheritDoc} */
427         @Override
428         public int compare(final TimeStamped timeStamped1, final TimeStamped timeStamped2) {
429             final int dateCompare = timeStamped1.getDate().compareTo(timeStamped2.getDate());
430             if (dateCompare == 0 && timeStamped1 instanceof TLE && timeStamped2 instanceof TLE) {
431                 try {
432                     final TLE tle1 = (TLE) timeStamped1;
433                     final TLE tle2 = (TLE) timeStamped2;
434                     final int line1Compare = tle1.getLine1().compareTo(tle2.getLine1());
435                     return (line1Compare == 0) ?
436                            tle1.getLine2().compareTo(tle2.getLine2()) :
437                            line1Compare;
438                 } catch (OrekitException oe) {
439                     // this should never happen
440                     throw new OrekitInternalError(oe);
441                 }
442             }
443             return dateCompare;
444         }
445     }
446 
447 }