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