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