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  
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      * @see #loadTLEData(int)
144      * @see #loadTLEData(int, int, String)
145      */
146     public void loadTLEData() {
147 
148         availableSatNums.clear();
149 
150         // set the filtering parameters
151         filterSatelliteNumber = -1;
152         filterLaunchYear      = -1;
153         filterLaunchNumber    = -1;
154         filterLaunchPiece     = null;
155 
156         // load the data from the configured data providers
157         tles.clear();
158         previous = null;
159         next     = null;
160         DataProvidersManager.getInstance().feed(supportedNames, this);
161         if (tles.isEmpty()) {
162             throw new OrekitException(OrekitMessages.NO_TLE_DATA_AVAILABLE);
163         }
164 
165     }
166 
167     /** Get the available satellite numbers.
168      * @return available satellite numbers
169      * file content is corrupted or no TLE data is available
170      */
171     public Set<Integer> getAvailableSatelliteNumbers() {
172         if (availableSatNums.isEmpty()) {
173             loadTLEData();
174         }
175         return availableSatNums;
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 the satellite number set to a negative value,
182      * is equivalent to call {@link #loadTLEData()}.</p>
183      * @param satelliteNumber satellite number
184      * @see #loadTLEData()
185      * @see #loadTLEData(int, int, String)
186      */
187     public void loadTLEData(final int satelliteNumber) {
188 
189         if (satelliteNumber < 0) {
190             // no filtering at all
191             loadTLEData();
192         } else {
193             // set the filtering parameters
194             filterSatelliteNumber = satelliteNumber;
195             filterLaunchYear      = -1;
196             filterLaunchNumber    = -1;
197             filterLaunchPiece     = null;
198 
199             // load the data from the configured data providers
200             tles.clear();
201             previous = null;
202             next     = null;
203             DataProvidersManager.getInstance().feed(supportedNames, this);
204             if (tles.isEmpty()) {
205                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_OBJECT, satelliteNumber);
206             }
207         }
208 
209     }
210 
211     /** Load TLE data for a specified object.
212      * <p>The TLE data already loaded in the instance will be discarded
213      * and replaced by the newly loaded data.</p>
214      * <p>Calling this method with either the launch year or the launch number
215      * set to a negative value, or the launch piece set to null or an empty
216      * string are all equivalent to call {@link #loadTLEData()}.</p>
217      * @param launchYear launch year (all digits)
218      * @param launchNumber launch number
219      * @param launchPiece launch piece
220      * @see #loadTLEData()
221      * @see #loadTLEData(int)
222      */
223     public void loadTLEData(final int launchYear, final int launchNumber,
224                             final String launchPiece) {
225 
226         if ((launchYear < 0) || (launchNumber < 0) ||
227             (launchPiece == null) || (launchPiece.length() == 0)) {
228             // no filtering at all
229             loadTLEData();
230         } else {
231             // set the filtering parameters
232             filterSatelliteNumber = -1;
233             filterLaunchYear      = launchYear;
234             filterLaunchNumber    = launchNumber;
235             filterLaunchPiece     = launchPiece;
236 
237             // load the data from the configured data providers
238             tles.clear();
239             previous = null;
240             next     = null;
241             DataProvidersManager.getInstance().feed(supportedNames, this);
242             if (tles.isEmpty()) {
243                 throw new OrekitException(OrekitMessages.NO_TLE_FOR_LAUNCH_YEAR_NUMBER_PIECE,
244                                           launchYear, launchNumber, launchPiece);
245             }
246         }
247 
248     }
249 
250     /** {@inheritDoc} */
251     public boolean stillAcceptsData() {
252         return tles.isEmpty();
253     }
254 
255     /** {@inheritDoc} */
256     public void loadData(final InputStream input, final String name)
257         throws IOException, OrekitException {
258 
259         final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
260         try {
261 
262             int lineNumber     = 0;
263             String pendingLine = null;
264             for (String line = r.readLine(); line != null; line = r.readLine()) {
265 
266                 ++lineNumber;
267 
268                 if (pendingLine == null) {
269 
270                     // we must wait for the second line
271                     pendingLine = line;
272 
273                 } else {
274 
275                     // safety checks
276                     if (!TLE.isFormatOK(pendingLine, line)) {
277                         if (ignoreNonTLELines) {
278                             // just shift one line
279                             pendingLine = line;
280                             continue;
281                         } else {
282                             throw new OrekitException(OrekitMessages.NOT_TLE_LINES,
283                                                       lineNumber - 1, lineNumber, pendingLine, line);
284                         }
285                     }
286 
287                     final TLEion/analytical/tle/TLE.html#TLE">TLE tle = new TLE(pendingLine, line);
288 
289                     if (filterSatelliteNumber < 0) {
290                         if ((filterLaunchYear < 0) ||
291                             ((tle.getLaunchYear()   == filterLaunchYear) &&
292                              (tle.getLaunchNumber() == filterLaunchNumber) &&
293                              tle.getLaunchPiece().equals(filterLaunchPiece))) {
294                             // we now know the number of the object to load
295                             filterSatelliteNumber = tle.getSatelliteNumber();
296                         }
297                     }
298 
299                     availableSatNums.add(tle.getSatelliteNumber());
300 
301                     if (tle.getSatelliteNumber() == filterSatelliteNumber) {
302                         // accept this TLE
303                         tles.add(tle);
304                     }
305 
306                     // we need to wait for two new lines
307                     pendingLine = null;
308 
309                 }
310 
311             }
312 
313             if ((pendingLine != null) && !ignoreNonTLELines) {
314                 // there is an unexpected last line
315                 throw new OrekitException(OrekitMessages.MISSING_SECOND_TLE_LINE,
316                                           lineNumber, pendingLine);
317             }
318 
319         } finally {
320             r.close();
321         }
322 
323     }
324 
325     /** Get the extrapolated position and velocity from an initial date.
326      * For a good precision, this date should not be too far from the range :
327      * [{@link #getFirstDate() first date} ; {@link #getLastDate() last date}].
328      * @param date the final date
329      * @return the final PVCoordinates
330      */
331     public PVCoordinates getPVCoordinates(final AbsoluteDate date) {
332         final TLE toExtrapolate = getClosestTLE(date);
333         if (toExtrapolate != lastTLE) {
334             lastTLE = toExtrapolate;
335             lastPropagator = TLEPropagator.selectExtrapolator(lastTLE);
336         }
337         return lastPropagator.getPVCoordinates(date);
338     }
339 
340     /** Get the closest TLE to the selected date.
341      * @param date the date
342      * @return the TLE that will suit the most for propagation.
343      */
344     public TLE getClosestTLE(final AbsoluteDate date) {
345 
346         //  don't search if the cached selection is fine
347         if ((previous != null) && (date.durationFrom(previous.getDate()) >= 0) &&
348             (next     != null) && (date.durationFrom(next.getDate())     <= 0)) {
349             // the current selection is already good
350             if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
351                 return previous;
352             } else {
353                 return next;
354             }
355         }
356         // reset the selection before the search phase
357         previous  = null;
358         next      = null;
359         final SortedSet<TimeStamped> headSet = tles.headSet(date);
360         final SortedSet<TimeStamped> tailSet = tles.tailSet(date);
361 
362 
363         if (headSet.isEmpty()) {
364             return (TLE) tailSet.first();
365         }
366         if (tailSet.isEmpty()) {
367             return (TLE) headSet.last();
368         }
369         previous = (TLE) headSet.last();
370         next = (TLE) tailSet.first();
371 
372         if (next.getDate().durationFrom(date) > date.durationFrom(previous.getDate())) {
373             return previous;
374         } else {
375             return next;
376         }
377     }
378 
379     /** Get the start date of the series.
380      * @return the first date
381      */
382     public AbsoluteDate getFirstDate() {
383         if (firstDate == null) {
384             firstDate = tles.first().getDate();
385         }
386         return firstDate;
387     }
388 
389     /** Get the last date of the series.
390      * @return the end date
391      */
392     public AbsoluteDate getLastDate() {
393         if (lastDate == null) {
394             lastDate = tles.last().getDate();
395         }
396         return lastDate;
397     }
398 
399     /** Get the first TLE.
400      * @return first TLE
401      */
402     public TLE getFirst() {
403         return (TLE) tles.first();
404     }
405 
406     /** Get the last TLE.
407      * @return last TLE
408      */
409     public TLE getLast() {
410         return (TLE) tles.last();
411     }
412 
413     /** Comparator allowing different TLEs at same date (see issue 411).
414      * @since 9.2
415      */
416     private static class TLEComparator implements Comparator<TimeStamped> {
417         /** {@inheritDoc} */
418         @Override
419         public int compare(final TimeStampedStamped">TimeStamped timeStamped1, final TimeStamped timeStamped2) {
420             final int dateCompare = timeStamped1.getDate().compareTo(timeStamped2.getDate());
421             if (dateCompare == 0 && timeStamped1 instanceof TLEkit/propagation/analytical/tle/TLE.html#TLE">TLE && timeStamped2 instanceof TLE) {
422                 try {
423                     final TLEref="../../../../../org/orekit/propagation/analytical/tle/TLE.html#TLE">TLE tle1 = (TLE) timeStamped1;
424                     final TLEref="../../../../../org/orekit/propagation/analytical/tle/TLE.html#TLE">TLE tle2 = (TLE) timeStamped2;
425                     final int line1Compare = tle1.getLine1().compareTo(tle2.getLine1());
426                     return (line1Compare == 0) ?
427                            tle1.getLine2().compareTo(tle2.getLine2()) :
428                            line1Compare;
429                 } catch (OrekitException oe) {
430                     // this should never happen
431                     throw new OrekitInternalError(oe);
432                 }
433             }
434             return dateCompare;
435         }
436     }
437 
438 }