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