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.TleGenerationUtil;
38 import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
39 import org.orekit.propagation.conversion.osc2mean.TLETheory;
40 import org.orekit.time.AbsoluteDate;
41 import org.orekit.time.DateComponents;
42 import org.orekit.time.DateTimeComponents;
43 import org.orekit.time.TimeComponents;
44 import org.orekit.time.TimeOffset;
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 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 final int year = ParseUtils.parseYear(line1, 18);
225 final int dayInYear = ParseUtils.parseInteger(line1, 20, 3);
226 final int dayFractionDigits = ParseUtils.parseInteger(line1, 24, 8);
227 final long nanoSecondsCount = dayFractionDigits * (long) Constants.JULIAN_DAY * 10;
228 final TimeOffset dayFraction = new TimeOffset(nanoSecondsCount, TimeOffset.NANOSECOND);
229 epoch = new AbsoluteDate(new DateComponents(year, dayInYear), new TimeComponents(dayFraction), utc);
230
231
232
233 meanMotion = ParseUtils.parseDouble(line2, 52, 11) * FastMath.PI / 43200.0;
234 meanMotionFirstDerivative = ParseUtils.parseDouble(line1, 33, 10) * FastMath.PI / 1.86624e9;
235 meanMotionSecondDerivative = Double.parseDouble((line1.substring(44, 45) + '.' +
236 line1.substring(45, 50) + 'e' +
237 line1.substring(50, 52)).replace(' ', '0')) *
238 FastMath.PI / 5.3747712e13;
239
240 eccentricity = Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0'));
241 inclination = FastMath.toRadians(ParseUtils.parseDouble(line2, 8, 8));
242 pa = FastMath.toRadians(ParseUtils.parseDouble(line2, 34, 8));
243 raan = FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0')));
244 meanAnomaly = FastMath.toRadians(ParseUtils.parseDouble(line2, 43, 8));
245
246 revolutionNumberAtEpoch = ParseUtils.parseInteger(line2, 63, 5);
247 final double bStarValue = Double.parseDouble((line1.substring(53, 54) + '.' +
248 line1.substring(54, 59) + 'e' +
249 line1.substring(59, 61)).replace(' ', '0'));
250
251
252 this.line1 = line1;
253 this.line2 = line2;
254 this.utc = utc;
255
256
257 this.bStarParameterDriver = new ParameterDriver(B_STAR, bStarValue, B_STAR_SCALE,
258 Double.NEGATIVE_INFINITY,
259 Double.POSITIVE_INFINITY);
260
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 @DefaultDataContext
305 public TLE(final int satelliteNumber, final char classification,
306 final int launchYear, final int launchNumber, final String launchPiece,
307 final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
308 final double meanMotion, final double meanMotionFirstDerivative,
309 final double meanMotionSecondDerivative, final double e, final double i,
310 final double pa, final double raan, final double meanAnomaly,
311 final int revolutionNumberAtEpoch, final double bStar) {
312 this(satelliteNumber, classification, launchYear, launchNumber, launchPiece,
313 ephemerisType, elementNumber, epoch, meanMotion,
314 meanMotionFirstDerivative, meanMotionSecondDerivative, e, i, pa, raan,
315 meanAnomaly, revolutionNumberAtEpoch, bStar,
316 DataContext.getDefault().getTimeScales().getUTC());
317 }
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 public TLE(final int satelliteNumber, final char classification,
361 final int launchYear, final int launchNumber, final String launchPiece,
362 final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
363 final double meanMotion, final double meanMotionFirstDerivative,
364 final double meanMotionSecondDerivative, final double e, final double i,
365 final double pa, final double raan, final double meanAnomaly,
366 final int revolutionNumberAtEpoch, final double bStar,
367 final TimeScale utc) {
368
369
370 this.satelliteNumber = satelliteNumber;
371 this.classification = classification;
372 this.launchYear = launchYear;
373 this.launchNumber = launchNumber;
374 this.launchPiece = launchPiece;
375 this.ephemerisType = ephemerisType;
376 this.elementNumber = elementNumber;
377
378
379 this.epoch = epoch;
380
381 this.meanMotion = meanMotion;
382 this.meanMotionFirstDerivative = meanMotionFirstDerivative;
383 this.meanMotionSecondDerivative = meanMotionSecondDerivative;
384
385
386 this.inclination = i;
387
388
389 this.raan = MathUtils.normalizeAngle(raan, FastMath.PI);
390
391
392 this.eccentricity = e;
393
394
395 this.pa = MathUtils.normalizeAngle(pa, FastMath.PI);
396
397
398 this.meanAnomaly = MathUtils.normalizeAngle(meanAnomaly, FastMath.PI);
399
400 this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
401
402
403
404 this.line1 = null;
405 this.line2 = null;
406 this.utc = utc;
407
408
409 this.bStarParameterDriver = new ParameterDriver(B_STAR, bStar, B_STAR_SCALE,
410 Double.NEGATIVE_INFINITY,
411 Double.POSITIVE_INFINITY);
412
413 }
414
415
416
417
418
419
420 public TimeScale getUtc() {
421 return utc;
422 }
423
424
425
426
427 public String getLine1() {
428 if (line1 == null) {
429 buildLine1();
430 }
431 return line1;
432 }
433
434
435
436
437 public String getLine2() {
438 if (line2 == null) {
439 buildLine2();
440 }
441 return line2;
442 }
443
444
445
446 private void buildLine1() {
447
448 final StringBuilder buffer = new StringBuilder();
449
450 buffer.append('1');
451
452 buffer.append(' ');
453 buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-1"));
454 buffer.append(classification);
455
456 buffer.append(' ');
457 buffer.append(ParseUtils.addPadding("launchYear", launchYear % 100, '0', 2, true, satelliteNumber));
458 buffer.append(ParseUtils.addPadding("launchNumber", launchNumber, '0', 3, true, satelliteNumber));
459 buffer.append(ParseUtils.addPadding("launchPiece", launchPiece, ' ', 3, false, satelliteNumber));
460
461 buffer.append(' ');
462 DateTimeComponents dtc = epoch.getComponents(utc);
463 int fraction = (int) FastMath.rint(31250 * dtc.getTime().getSecondsInUTCDay() / 27.0);
464 if (fraction >= 100000000) {
465 dtc = epoch.shiftedBy(Constants.JULIAN_DAY).getComponents(utc);
466 fraction -= 100000000;
467 }
468 buffer.append(ParseUtils.addPadding("year", dtc.getDate().getYear() % 100, '0', 2, true, satelliteNumber));
469 buffer.append(ParseUtils.addPadding("day", dtc.getDate().getDayOfYear(), '0', 3, true, satelliteNumber));
470 buffer.append('.');
471
472
473 buffer.append(ParseUtils.addPadding("fraction", fraction, '0', 8, true, satelliteNumber));
474
475 buffer.append(' ');
476 final double n1 = meanMotionFirstDerivative * 1.86624e9 / FastMath.PI;
477 final String sn1 = ParseUtils.addPadding("meanMotionFirstDerivative",
478 new DecimalFormat(".00000000", SYMBOLS).format(n1),
479 ' ', 10, true, satelliteNumber);
480 buffer.append(sn1);
481
482 buffer.append(' ');
483 final double n2 = meanMotionSecondDerivative * 5.3747712e13 / FastMath.PI;
484 buffer.append(formatExponentMarkerFree("meanMotionSecondDerivative", n2, 5, ' ', 8, true));
485
486 buffer.append(' ');
487 buffer.append(formatExponentMarkerFree("B*", getBStar(), 5, ' ', 8, true));
488
489 buffer.append(' ');
490 buffer.append(ephemerisType);
491
492 buffer.append(' ');
493 buffer.append(ParseUtils.addPadding("elementNumber", elementNumber, ' ', 4, true, satelliteNumber));
494
495 buffer.append(checksum(buffer));
496
497 line1 = buffer.toString();
498
499 }
500
501
502
503
504
505
506
507
508
509
510
511 private String formatExponentMarkerFree(final String name, final double d, final int mantissaSize,
512 final char c, final int size, final boolean rightJustified) {
513 final double dAbs = FastMath.abs(d);
514 int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
515 long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
516 if (mantissa == 0) {
517 exponent = 0;
518 } else if (mantissa > (ArithmeticUtils.pow(10, mantissaSize) - 1)) {
519
520
521
522 exponent++;
523 mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
524 }
525 final String sMantissa = ParseUtils.addPadding(name, (int) mantissa, '0', mantissaSize, true, satelliteNumber);
526 final String sExponent = Integer.toString(FastMath.abs(exponent));
527 final String formatted = (d < 0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;
528
529 return ParseUtils.addPadding(name, formatted, c, size, rightJustified, satelliteNumber);
530
531 }
532
533
534
535 private void buildLine2() {
536
537 final StringBuilder buffer = new StringBuilder();
538 final DecimalFormat f34 = new DecimalFormat("##0.0000", SYMBOLS);
539 final DecimalFormat f211 = new DecimalFormat("#0.00000000", SYMBOLS);
540
541 buffer.append('2');
542
543 buffer.append(' ');
544 buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-2"));
545
546 buffer.append(' ');
547 buffer.append(ParseUtils.addPadding(INCLINATION, f34.format(FastMath.toDegrees(inclination)), ' ', 8, true, satelliteNumber));
548 buffer.append(' ');
549 buffer.append(ParseUtils.addPadding("raan", f34.format(FastMath.toDegrees(raan)), ' ', 8, true, satelliteNumber));
550 buffer.append(' ');
551 buffer.append(ParseUtils.addPadding(ECCENTRICITY, (int) FastMath.rint(eccentricity * 1.0e7), '0', 7, true, satelliteNumber));
552 buffer.append(' ');
553 buffer.append(ParseUtils.addPadding("pa", f34.format(FastMath.toDegrees(pa)), ' ', 8, true, satelliteNumber));
554 buffer.append(' ');
555 buffer.append(ParseUtils.addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly)), ' ', 8, true, satelliteNumber));
556
557 buffer.append(' ');
558 buffer.append(ParseUtils.addPadding(MEAN_MOTION, f211.format(meanMotion * 43200.0 / FastMath.PI), ' ', 11, true, satelliteNumber));
559 buffer.append(ParseUtils.addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch, ' ', 5, true, satelliteNumber));
560
561 buffer.append(checksum(buffer));
562
563 line2 = buffer.toString();
564
565 }
566
567
568
569
570 public int getSatelliteNumber() {
571 return satelliteNumber;
572 }
573
574
575
576
577 public char getClassification() {
578 return classification;
579 }
580
581
582
583
584 public int getLaunchYear() {
585 return launchYear;
586 }
587
588
589
590
591 public int getLaunchNumber() {
592 return launchNumber;
593 }
594
595
596
597
598 public String getLaunchPiece() {
599 return launchPiece;
600 }
601
602
603
604
605
606 public int getEphemerisType() {
607 return ephemerisType;
608 }
609
610
611
612
613 public int getElementNumber() {
614 return elementNumber;
615 }
616
617
618
619
620 public AbsoluteDate getDate() {
621 return epoch;
622 }
623
624
625
626
627 public double getMeanMotion() {
628 return meanMotion;
629 }
630
631
632
633
634 public double getMeanMotionFirstDerivative() {
635 return meanMotionFirstDerivative;
636 }
637
638
639
640
641 public double getMeanMotionSecondDerivative() {
642 return meanMotionSecondDerivative;
643 }
644
645
646
647
648 public double getE() {
649 return eccentricity;
650 }
651
652
653
654
655 public double getI() {
656 return inclination;
657 }
658
659
660
661
662 public double getPerigeeArgument() {
663 return pa;
664 }
665
666
667
668
669 public double getRaan() {
670 return raan;
671 }
672
673
674
675
676 public double getMeanAnomaly() {
677 return meanAnomaly;
678 }
679
680
681
682
683 public int getRevolutionNumberAtEpoch() {
684 return revolutionNumberAtEpoch;
685 }
686
687
688
689
690 public double getBStar() {
691 return bStarParameterDriver.getValue(getDate());
692 }
693
694
695
696
697
698 public double getBStar(final AbsoluteDate date) {
699 return bStarParameterDriver.getValue(date);
700 }
701
702
703
704
705 public double computeSemiMajorAxis() {
706 return FastMath.cbrt(TLEConstants.MU / (meanMotion * meanMotion));
707 }
708
709
710
711
712
713
714 public String toString() {
715 return getLine1() + System.getProperty("line.separator") + getLine2();
716 }
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732 public static TLE stateToTLE(final SpacecraftState state,
733 final TLE templateTLE,
734 final OsculatingToMeanConverter converter,
735 final DataContext dataContext) {
736 converter.setMeanTheory(new TLETheory(templateTLE, dataContext));
737 final KeplerianOrbit mean = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(converter.convertToMean(state.getOrbit()));
738 final TLE tle = TleGenerationUtil.newTLE(mean, templateTLE, templateTLE.getBStar(mean.getDate()),
739 dataContext.getTimeScales().getUTC());
740
741 for (final ParameterDriver templateDrivers : templateTLE.getParametersDrivers()) {
742 if (templateDrivers.isSelected()) {
743
744 tle.getParameterDriver(templateDrivers.getName()).setSelected(true);
745 }
746 }
747 return tle;
748 }
749
750
751
752
753
754
755
756 public static boolean isFormatOK(final String line1, final String line2) {
757
758 if (line1 == null || line1.length() != 69 ||
759 line2 == null || line2.length() != 69) {
760 return false;
761 }
762
763 if (!(LINE_1_PATTERN.matcher(line1).matches() &&
764 LINE_2_PATTERN.matcher(line2).matches())) {
765 return false;
766 }
767
768
769 final int checksum1 = checksum(line1);
770 if (Integer.parseInt(line1.substring(68)) != (checksum1 % 10)) {
771 throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
772 1, Integer.toString(checksum1 % 10), line1.substring(68), line1);
773 }
774
775 final int checksum2 = checksum(line2);
776 if (Integer.parseInt(line2.substring(68)) != (checksum2 % 10)) {
777 throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
778 2, Integer.toString(checksum2 % 10), line2.substring(68), line2);
779 }
780
781 return true;
782
783 }
784
785
786
787
788
789 private static int checksum(final CharSequence line) {
790 int sum = 0;
791 for (int j = 0; j < 68; j++) {
792 final char c = line.charAt(j);
793 if (Character.isDigit(c)) {
794 sum += Character.digit(c, 10);
795 } else if (c == '-') {
796 ++sum;
797 }
798 }
799 return sum % 10;
800 }
801
802
803
804
805
806
807
808
809
810
811 public static int parseSatelliteNumber(final String satNumberString) {
812 return ParseUtils.parseSatelliteNumber(satNumberString, 0, satNumberString.length());
813 }
814
815
816
817
818
819
820
821
822
823 @Override
824 public boolean equals(final Object o) {
825 if (o == this) {
826 return true;
827 }
828 if (!(o instanceof TLE)) {
829 return false;
830 }
831 final TLE tle = (TLE) o;
832 return satelliteNumber == tle.satelliteNumber &&
833 classification == tle.classification &&
834 launchYear == tle.launchYear &&
835 launchNumber == tle.launchNumber &&
836 Objects.equals(launchPiece, tle.launchPiece) &&
837 ephemerisType == tle.ephemerisType &&
838 elementNumber == tle.elementNumber &&
839 Objects.equals(epoch, tle.epoch) &&
840 meanMotion == tle.meanMotion &&
841 meanMotionFirstDerivative == tle.meanMotionFirstDerivative &&
842 meanMotionSecondDerivative == tle.meanMotionSecondDerivative &&
843 eccentricity == tle.eccentricity &&
844 inclination == tle.inclination &&
845 pa == tle.pa &&
846 raan == tle.raan &&
847 meanAnomaly == tle.meanAnomaly &&
848 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
849 getBStar() == tle.getBStar();
850 }
851
852
853
854
855 @Override
856 public int hashCode() {
857 return Objects.hash(satelliteNumber,
858 classification,
859 launchYear,
860 launchNumber,
861 launchPiece,
862 ephemerisType,
863 elementNumber,
864 epoch,
865 meanMotion,
866 meanMotionFirstDerivative,
867 meanMotionSecondDerivative,
868 eccentricity,
869 inclination,
870 pa,
871 raan,
872 meanAnomaly,
873 revolutionNumberAtEpoch,
874 getBStar());
875 }
876
877
878
879
880 public List<ParameterDriver> getParametersDrivers() {
881 return Collections.singletonList(bStarParameterDriver);
882 }
883
884 }