1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.propagation.analytical.tle;
18
19 import java.io.Serializable;
20 import java.text.DecimalFormat;
21 import java.text.DecimalFormatSymbols;
22 import java.util.Locale;
23 import java.util.Objects;
24 import java.util.regex.Pattern;
25
26 import org.hipparchus.util.ArithmeticUtils;
27 import org.hipparchus.util.FastMath;
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.DateComponents;
33 import org.orekit.time.DateTimeComponents;
34 import org.orekit.time.TimeComponents;
35 import org.orekit.time.TimeScalesFactory;
36 import org.orekit.time.TimeStamped;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public class TLE implements TimeStamped, Serializable {
55
56
57 public static final int DEFAULT = 0;
58
59
60 public static final int SGP = 1;
61
62
63 public static final int SGP4 = 2;
64
65
66 public static final int SDP4 = 3;
67
68
69 public static final int SGP8 = 4;
70
71
72 public static final int SDP8 = 5;
73
74
75 private static final Pattern LINE_1_PATTERN =
76 Pattern.compile("1 [ 0-9]{5}[A-Z] [ 0-9]{5}[ A-Z]{3} [ 0-9]{5}[.][ 0-9]{8} (?:(?:[ 0+-][.][ 0-9]{8})|(?: [ +-][.][ 0-9]{7})) " +
77 "[ +-][ 0-9]{5}[+-][ 0-9] [ +-][ 0-9]{5}[+-][ 0-9] [ 0-9] [ 0-9]{4}[ 0-9]");
78
79
80 private static final Pattern LINE_2_PATTERN =
81 Pattern.compile("2 [ 0-9]{5} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{7} " +
82 "[ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{2}[.][ 0-9]{13}[ 0-9]");
83
84
85 private static final DecimalFormatSymbols SYMBOLS =
86 new DecimalFormatSymbols(Locale.US);
87
88
89 private static final long serialVersionUID = -1596648022319057689L;
90
91
92 private final int satelliteNumber;
93
94
95 private final char classification;
96
97
98 private final int launchYear;
99
100
101 private final int launchNumber;
102
103
104 private final String launchPiece;
105
106
107 private final int ephemerisType;
108
109
110 private final int elementNumber;
111
112
113 private final AbsoluteDate epoch;
114
115
116 private final double meanMotion;
117
118
119 private final double meanMotionFirstDerivative;
120
121
122 private final double meanMotionSecondDerivative;
123
124
125 private final double eccentricity;
126
127
128 private final double inclination;
129
130
131 private final double pa;
132
133
134 private final double raan;
135
136
137 private final double meanAnomaly;
138
139
140 private final int revolutionNumberAtEpoch;
141
142
143 private final double bStar;
144
145
146 private String line1;
147
148
149 private String line2;
150
151
152
153
154
155
156
157
158 public TLE(final String line1, final String line2) throws OrekitException {
159
160
161 satelliteNumber = parseInteger(line1, 2, 5);
162 final int satNum2 = parseInteger(line2, 2, 5);
163 if (satelliteNumber != satNum2) {
164 throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
165 line1, line2);
166 }
167 classification = line1.charAt(7);
168 launchYear = parseYear(line1, 9);
169 launchNumber = parseInteger(line1, 11, 3);
170 launchPiece = line1.substring(14, 17).trim();
171 ephemerisType = parseInteger(line1, 62, 1);
172 elementNumber = parseInteger(line1, 64, 4);
173
174
175 final int year = parseYear(line1, 18);
176 final int dayInYear = parseInteger(line1, 20, 3);
177 final long df = 27l * parseInteger(line1, 24, 8);
178 final int secondsA = (int) (df / 31250l);
179 final double secondsB = (df % 31250l) / 31250.0;
180 epoch = new AbsoluteDate(new DateComponents(year, dayInYear),
181 new TimeComponents(secondsA, secondsB),
182 TimeScalesFactory.getUTC());
183
184
185
186 meanMotion = parseDouble(line2, 52, 11) * FastMath.PI / 43200.0;
187 meanMotionFirstDerivative = parseDouble(line1, 33, 10) * FastMath.PI / 1.86624e9;
188 meanMotionSecondDerivative = Double.parseDouble((line1.substring(44, 45) + '.' +
189 line1.substring(45, 50) + 'e' +
190 line1.substring(50, 52)).replace(' ', '0')) *
191 FastMath.PI / 5.3747712e13;
192
193 eccentricity = Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0'));
194 inclination = FastMath.toRadians(parseDouble(line2, 8, 8));
195 pa = FastMath.toRadians(parseDouble(line2, 34, 8));
196 raan = FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0')));
197 meanAnomaly = FastMath.toRadians(parseDouble(line2, 43, 8));
198
199 revolutionNumberAtEpoch = parseInteger(line2, 63, 5);
200 bStar = Double.parseDouble((line1.substring(53, 54) + '.' +
201 line1.substring(54, 59) + 'e' +
202 line1.substring(59, 61)).replace(' ', '0'));
203
204
205 this.line1 = line1;
206 this.line2 = line2;
207
208 }
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230 public TLE(final int satelliteNumber, final char classification,
231 final int launchYear, final int launchNumber, final String launchPiece,
232 final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
233 final double meanMotion, final double meanMotionFirstDerivative,
234 final double meanMotionSecondDerivative, final double e, final double i,
235 final double pa, final double raan, final double meanAnomaly,
236 final int revolutionNumberAtEpoch, final double bStar) {
237
238
239 this.satelliteNumber = satelliteNumber;
240 this.classification = classification;
241 this.launchYear = launchYear;
242 this.launchNumber = launchNumber;
243 this.launchPiece = launchPiece;
244 this.ephemerisType = ephemerisType;
245 this.elementNumber = elementNumber;
246
247
248 this.epoch = epoch;
249 this.meanMotion = meanMotion;
250 this.meanMotionFirstDerivative = meanMotionFirstDerivative;
251 this.meanMotionSecondDerivative = meanMotionSecondDerivative;
252 this.inclination = i;
253 this.raan = raan;
254 this.eccentricity = e;
255 this.pa = pa;
256 this.meanAnomaly = meanAnomaly;
257
258 this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
259 this.bStar = bStar;
260
261
262 this.line1 = null;
263 this.line2 = null;
264
265 }
266
267
268
269
270
271
272 public String getLine1()
273 throws OrekitException {
274 if (line1 == null) {
275 buildLine1();
276 }
277 return line1;
278 }
279
280
281
282
283
284 public String getLine2()
285 throws OrekitException {
286 if (line2 == null) {
287 buildLine2();
288 }
289 return line2;
290 }
291
292
293
294
295
296 private void buildLine1()
297 throws OrekitException {
298
299 final StringBuffer buffer = new StringBuffer();
300
301 buffer.append('1');
302
303 buffer.append(' ');
304 buffer.append(addPadding("satelliteNumber-1", satelliteNumber, '0', 5, true));
305 buffer.append(classification);
306
307 buffer.append(' ');
308 buffer.append(addPadding("launchYear", launchYear % 100, '0', 2, true));
309 buffer.append(addPadding("launchNumber", launchNumber, '0', 3, true));
310 buffer.append(addPadding("launchPiece", launchPiece, ' ', 3, false));
311
312 buffer.append(' ');
313 final DateTimeComponents dtc = epoch.getComponents(TimeScalesFactory.getUTC());
314 buffer.append(addPadding("year", dtc.getDate().getYear() % 100, '0', 2, true));
315 buffer.append(addPadding("day", dtc.getDate().getDayOfYear(), '0', 3, true));
316 buffer.append('.');
317
318 final int fraction = (int) FastMath.rint(31250 * dtc.getTime().getSecondsInUTCDay() / 27.0);
319 buffer.append(addPadding("fraction", fraction, '0', 8, true));
320
321 buffer.append(' ');
322 final double n1 = meanMotionFirstDerivative * 1.86624e9 / FastMath.PI;
323 final String sn1 = addPadding("meanMotionFirstDerivative",
324 new DecimalFormat(".00000000", SYMBOLS).format(n1), ' ', 10, true);
325 buffer.append(sn1);
326
327 buffer.append(' ');
328 final double n2 = meanMotionSecondDerivative * 5.3747712e13 / FastMath.PI;
329 buffer.append(formatExponentMarkerFree("meanMotionSecondDerivative", n2, 5, ' ', 8, true));
330
331 buffer.append(' ');
332 buffer.append(formatExponentMarkerFree("B*", bStar, 5, ' ', 8, true));
333
334 buffer.append(' ');
335 buffer.append(ephemerisType);
336
337 buffer.append(' ');
338 buffer.append(addPadding("elementNumber", elementNumber, ' ', 4, true));
339
340 buffer.append(Integer.toString(checksum(buffer)));
341
342 line1 = buffer.toString();
343
344 }
345
346
347
348
349
350
351
352
353
354
355
356
357 private String formatExponentMarkerFree(final String name, final double d, final int mantissaSize,
358 final char c, final int size, final boolean rightJustified)
359 throws OrekitException {
360 final double dAbs = FastMath.abs(d);
361 int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
362 long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
363 if (mantissa == 0) {
364 exponent = 0;
365 } else if (mantissa > (ArithmeticUtils.pow(10, mantissaSize) - 1)) {
366
367
368
369 exponent++;
370 mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
371 }
372 final String sMantissa = addPadding(name, (int) mantissa, '0', mantissaSize, true);
373 final String sExponent = Integer.toString(FastMath.abs(exponent));
374 final String formatted = (d < 0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;
375
376 return addPadding(name, formatted, c, size, rightJustified);
377
378 }
379
380
381
382
383 private void buildLine2() throws OrekitException {
384
385 final StringBuffer buffer = new StringBuffer();
386 final DecimalFormat f34 = new DecimalFormat("##0.0000", SYMBOLS);
387 final DecimalFormat f211 = new DecimalFormat("#0.00000000", SYMBOLS);
388
389 buffer.append('2');
390
391 buffer.append(' ');
392 buffer.append(addPadding("satelliteNumber-2", satelliteNumber, '0', 5, true));
393
394 buffer.append(' ');
395 buffer.append(addPadding("inclination", f34.format(FastMath.toDegrees(inclination)), ' ', 8, true));
396 buffer.append(' ');
397 buffer.append(addPadding("raan", f34.format(FastMath.toDegrees(raan)), ' ', 8, true));
398 buffer.append(' ');
399 buffer.append(addPadding("eccentricity", (int) FastMath.rint(eccentricity * 1.0e7), '0', 7, true));
400 buffer.append(' ');
401 buffer.append(addPadding("pa", f34.format(FastMath.toDegrees(pa)), ' ', 8, true));
402 buffer.append(' ');
403 buffer.append(addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly)), ' ', 8, true));
404
405 buffer.append(' ');
406 buffer.append(addPadding("meanMotion", f211.format(meanMotion * 43200.0 / FastMath.PI), ' ', 11, true));
407 buffer.append(addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch, ' ', 5, true));
408
409 buffer.append(Integer.toString(checksum(buffer)));
410
411 line2 = buffer.toString();
412
413 }
414
415
416
417
418
419
420
421
422
423
424
425 private String addPadding(final String name, final int k, final char c,
426 final int size, final boolean rightJustified)
427 throws OrekitException {
428 return addPadding(name, Integer.toString(k), c, size, rightJustified);
429 }
430
431
432
433
434
435
436
437
438
439
440
441 private String addPadding(final String name, final String string, final char c,
442 final int size, final boolean rightJustified)
443 throws OrekitException {
444
445 if (string.length() > size) {
446 throw new OrekitException(OrekitMessages.TLE_INVALID_PARAMETER,
447 satelliteNumber, name, string);
448 }
449
450 final StringBuffer padding = new StringBuffer();
451 for (int i = 0; i < size; ++i) {
452 padding.append(c);
453 }
454
455 if (rightJustified) {
456 final String concatenated = padding + string;
457 final int l = concatenated.length();
458 return concatenated.substring(l - size, l);
459 }
460
461 return (string + padding).substring(0, size);
462
463 }
464
465
466
467
468
469
470
471 private double parseDouble(final String line, final int start, final int length) {
472 final String field = line.substring(start, start + length).trim();
473 return field.length() > 0 ? Double.parseDouble(field.replace(' ', '0')) : 0;
474 }
475
476
477
478
479
480
481
482 private int parseInteger(final String line, final int start, final int length) {
483 final String field = line.substring(start, start + length).trim();
484 return field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
485 }
486
487
488
489
490
491
492 private int parseYear(final String line, final int start) {
493 final int year = 2000 + parseInteger(line, start, 2);
494 return (year > 2056) ? (year - 100) : year;
495 }
496
497
498
499
500 public int getSatelliteNumber() {
501 return satelliteNumber;
502 }
503
504
505
506
507 public char getClassification() {
508 return classification;
509 }
510
511
512
513
514 public int getLaunchYear() {
515 return launchYear;
516 }
517
518
519
520
521 public int getLaunchNumber() {
522 return launchNumber;
523 }
524
525
526
527
528 public String getLaunchPiece() {
529 return launchPiece;
530 }
531
532
533
534
535
536 public int getEphemerisType() {
537 return ephemerisType;
538 }
539
540
541
542
543 public int getElementNumber() {
544 return elementNumber;
545 }
546
547
548
549
550 public AbsoluteDate getDate() {
551 return epoch;
552 }
553
554
555
556
557 public double getMeanMotion() {
558 return meanMotion;
559 }
560
561
562
563
564 public double getMeanMotionFirstDerivative() {
565 return meanMotionFirstDerivative;
566 }
567
568
569
570
571 public double getMeanMotionSecondDerivative() {
572 return meanMotionSecondDerivative;
573 }
574
575
576
577
578 public double getE() {
579 return eccentricity;
580 }
581
582
583
584
585 public double getI() {
586 return inclination;
587 }
588
589
590
591
592 public double getPerigeeArgument() {
593 return pa;
594 }
595
596
597
598
599 public double getRaan() {
600 return raan;
601 }
602
603
604
605
606 public double getMeanAnomaly() {
607 return meanAnomaly;
608 }
609
610
611
612
613 public int getRevolutionNumberAtEpoch() {
614 return revolutionNumberAtEpoch;
615 }
616
617
618
619
620 public double getBStar() {
621 return bStar;
622 }
623
624
625
626
627
628
629 public String toString() {
630 try {
631 return getLine1() + System.getProperty("line.separator") + getLine2();
632 } catch (OrekitException oe) {
633 throw new OrekitInternalError(oe);
634 }
635 }
636
637
638
639
640
641
642
643
644 public static boolean isFormatOK(final String line1, final String line2)
645 throws OrekitException {
646
647 if (line1 == null || line1.length() != 69 ||
648 line2 == null || line2.length() != 69) {
649 return false;
650 }
651
652 if (!(LINE_1_PATTERN.matcher(line1).matches() &&
653 LINE_2_PATTERN.matcher(line2).matches())) {
654 return false;
655 }
656
657
658 final int checksum1 = checksum(line1);
659 if (Integer.parseInt(line1.substring(68)) != (checksum1 % 10)) {
660 throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
661 1, line1.substring(68), checksum1 % 10, line1);
662 }
663
664 final int checksum2 = checksum(line2);
665 if (Integer.parseInt(line2.substring(68)) != (checksum2 % 10)) {
666 throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
667 2, line2.substring(68), checksum2 % 10, line2);
668 }
669
670 return true;
671
672 }
673
674
675
676
677
678 private static int checksum(final CharSequence line) {
679 int sum = 0;
680 for (int j = 0; j < 68; j++) {
681 final char c = line.charAt(j);
682 if (Character.isDigit(c)) {
683 sum += Character.digit(c, 10);
684 } else if (c == '-') {
685 ++sum;
686 }
687 }
688 return sum % 10;
689 }
690
691
692
693
694
695
696
697
698
699 @Override
700 public boolean equals(final Object o) {
701 if (o == this) {
702 return true;
703 }
704 if (!(o instanceof TLE)) {
705 return false;
706 }
707 final TLE tle = (TLE) o;
708 return satelliteNumber == tle.satelliteNumber &&
709 classification == tle.classification &&
710 launchYear == tle.launchYear &&
711 launchNumber == tle.launchNumber &&
712 Objects.equals(launchPiece, tle.launchPiece) &&
713 ephemerisType == tle.ephemerisType &&
714 elementNumber == tle.elementNumber &&
715 Objects.equals(epoch, tle.epoch) &&
716 meanMotion == tle.meanMotion &&
717 meanMotionFirstDerivative == tle.meanMotionFirstDerivative &&
718 meanMotionSecondDerivative == tle.meanMotionSecondDerivative &&
719 eccentricity == tle.eccentricity &&
720 inclination == tle.inclination &&
721 pa == tle.pa &&
722 raan == tle.raan &&
723 meanAnomaly == tle.meanAnomaly &&
724 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
725 bStar == tle.bStar;
726 }
727
728
729
730
731 @Override
732 public int hashCode() {
733 return Objects.hash(satelliteNumber,
734 classification,
735 launchYear,
736 launchNumber,
737 launchPiece,
738 ephemerisType,
739 elementNumber,
740 epoch,
741 meanMotion,
742 meanMotionFirstDerivative,
743 meanMotionSecondDerivative,
744 eccentricity,
745 inclination,
746 pa,
747 raan,
748 meanAnomaly,
749 revolutionNumberAtEpoch,
750 bStar);
751 }
752
753 }