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