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 }