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
26 import org.hipparchus.CalculusFieldElement;
27 import org.hipparchus.Field;
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.OrekitInternalError;
35 import org.orekit.errors.OrekitMessages;
36 import org.orekit.orbits.FieldKeplerianOrbit;
37 import org.orekit.orbits.OrbitType;
38 import org.orekit.propagation.FieldSpacecraftState;
39 import org.orekit.propagation.analytical.tle.generation.TleGenerationAlgorithm;
40 import org.orekit.propagation.analytical.tle.generation.TleGenerationUtil;
41 import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
42 import org.orekit.propagation.conversion.osc2mean.TLETheory;
43 import org.orekit.time.DateComponents;
44 import org.orekit.time.DateTimeComponents;
45 import org.orekit.time.FieldAbsoluteDate;
46 import org.orekit.time.FieldTimeStamped;
47 import org.orekit.time.TimeComponents;
48 import org.orekit.time.TimeScale;
49 import org.orekit.utils.Constants;
50 import org.orekit.utils.ParameterDriver;
51 import org.orekit.utils.ParameterDriversProvider;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 public class FieldTLE<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T>, ParameterDriversProvider {
73
74
75 public static final int DEFAULT = 0;
76
77
78 public static final int SGP = 1;
79
80
81 public static final int SGP4 = 2;
82
83
84 public static final int SDP4 = 3;
85
86
87 public static final int SGP8 = 4;
88
89
90 public static final int SDP8 = 5;
91
92
93 public static final String B_STAR = "BSTAR";
94
95
96
97
98
99
100
101 private static final double B_STAR_SCALE = FastMath.scalb(1.0, -20);
102
103
104 private static final String MEAN_MOTION = "meanMotion";
105
106
107 private static final String INCLINATION = "inclination";
108
109
110 private static final String ECCENTRICITY = "eccentricity";
111
112
113 private static final DecimalFormatSymbols SYMBOLS =
114 new DecimalFormatSymbols(Locale.US);
115
116
117 private final int satelliteNumber;
118
119
120 private final char classification;
121
122
123 private final int launchYear;
124
125
126 private final int launchNumber;
127
128
129 private final String launchPiece;
130
131
132 private final int ephemerisType;
133
134
135 private final int elementNumber;
136
137
138 private final FieldAbsoluteDate<T> epoch;
139
140
141 private final T meanMotion;
142
143
144 private final T meanMotionFirstDerivative;
145
146
147 private final T meanMotionSecondDerivative;
148
149
150 private final T eccentricity;
151
152
153 private final T inclination;
154
155
156 private final T pa;
157
158
159 private final T raan;
160
161
162 private final T meanAnomaly;
163
164
165 private final int revolutionNumberAtEpoch;
166
167
168 private String line1;
169
170
171 private String line2;
172
173
174 private final TimeScale utc;
175
176
177 private final ParameterDriver bStarParameterDriver;
178
179
180
181
182
183
184
185
186
187
188
189 @DefaultDataContext
190 public FieldTLE(final Field<T> field, final String line1, final String line2) {
191 this(field, line1, line2, DataContext.getDefault().getTimeScales().getUTC());
192 }
193
194
195
196
197
198
199
200
201
202
203
204
205 public FieldTLE(final Field<T> field, final String line1, final String line2, final TimeScale utc) {
206
207
208 final T zero = field.getZero();
209 final T pi = zero.getPi();
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
226 final int year = ParseUtils.parseYear(line1, 18);
227 final int dayInYear = ParseUtils.parseInteger(line1, 20, 3);
228 final long df = 27l * ParseUtils.parseInteger(line1, 24, 8);
229 final int secondsA = (int) (df / 31250l);
230 final double secondsB = (df % 31250l) / 31250.0;
231 epoch = new FieldAbsoluteDate<>(field, new DateComponents(year, dayInYear),
232 new TimeComponents(secondsA, secondsB),
233 utc);
234
235
236
237 meanMotion = pi.multiply(ParseUtils.parseDouble(line2, 52, 11)).divide(43200.0);
238 meanMotionFirstDerivative = pi.multiply(ParseUtils.parseDouble(line1, 33, 10)).divide(1.86624e9);
239 meanMotionSecondDerivative = pi.multiply(Double.parseDouble((line1.substring(44, 45) + '.' +
240 line1.substring(45, 50) + 'e' +
241 line1.substring(50, 52)).replace(' ', '0'))).divide(5.3747712e13);
242
243 eccentricity = zero.newInstance(Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0')));
244 inclination = zero.newInstance(FastMath.toRadians(ParseUtils.parseDouble(line2, 8, 8)));
245 pa = zero.newInstance(FastMath.toRadians(ParseUtils.parseDouble(line2, 34, 8)));
246 raan = zero.newInstance(FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0'))));
247 meanAnomaly = zero.newInstance(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 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
307 @DefaultDataContext
308 public FieldTLE(final int satelliteNumber, final char classification,
309 final int launchYear, final int launchNumber, final String launchPiece,
310 final int ephemerisType, final int elementNumber, final FieldAbsoluteDate<T> epoch,
311 final T meanMotion, final T meanMotionFirstDerivative,
312 final T meanMotionSecondDerivative, final T e, final T i,
313 final T pa, final T raan, final T 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 public FieldTLE(final int satelliteNumber, final char classification,
363 final int launchYear, final int launchNumber, final String launchPiece,
364 final int ephemerisType, final int elementNumber, final FieldAbsoluteDate<T> epoch,
365 final T meanMotion, final T meanMotionFirstDerivative,
366 final T meanMotionSecondDerivative, final T e, final T i,
367 final T pa, final T raan, final T meanAnomaly,
368 final int revolutionNumberAtEpoch, final double bStar,
369 final TimeScale utc) {
370
371
372 final T pi = e.getPi();
373
374
375 this.satelliteNumber = satelliteNumber;
376 this.classification = classification;
377 this.launchYear = launchYear;
378 this.launchNumber = launchNumber;
379 this.launchPiece = launchPiece;
380 this.ephemerisType = ephemerisType;
381 this.elementNumber = elementNumber;
382
383
384 this.epoch = epoch;
385
386 this.meanMotion = meanMotion;
387 this.meanMotionFirstDerivative = meanMotionFirstDerivative;
388 this.meanMotionSecondDerivative = meanMotionSecondDerivative;
389
390
391 this.inclination = i;
392
393
394 this.raan = MathUtils.normalizeAngle(raan, pi);
395
396
397 this.eccentricity = e;
398
399
400 this.pa = MathUtils.normalizeAngle(pa, pi);
401
402
403 this.meanAnomaly = MathUtils.normalizeAngle(meanAnomaly, pi);
404
405 this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
406 this.bStarParameterDriver = new ParameterDriver(B_STAR, bStar, B_STAR_SCALE,
407 Double.NEGATIVE_INFINITY,
408 Double.POSITIVE_INFINITY);
409
410
411 this.line1 = null;
412 this.line2 = null;
413 this.utc = utc;
414
415 }
416
417
418
419
420
421
422 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.divide(pa.getPi()).multiply(1.86624e9).getReal();
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.divide(pa.getPi()).multiply(5.3747712e13).getReal();
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(Integer.toString(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,
528 '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).getReal()), ' ', 8, true, satelliteNumber));
551 buffer.append(' ');
552 buffer.append(ParseUtils.addPadding("raan", f34.format(FastMath.toDegrees(raan).getReal()), ' ', 8, true, satelliteNumber));
553 buffer.append(' ');
554 buffer.append(ParseUtils.addPadding(ECCENTRICITY, (int) FastMath.rint(eccentricity.getReal() * 1.0e7), '0', 7, true, satelliteNumber));
555 buffer.append(' ');
556 buffer.append(ParseUtils.addPadding("pa", f34.format(FastMath.toDegrees(pa).getReal()), ' ', 8, true, satelliteNumber));
557 buffer.append(' ');
558 buffer.append(ParseUtils.addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly).getReal()), ' ', 8, true, satelliteNumber));
559
560 buffer.append(' ');
561 buffer.append(ParseUtils.addPadding(MEAN_MOTION, f211.format(meanMotion.divide(pa.getPi()).multiply(43200.0).getReal()), ' ', 11, true, satelliteNumber));
562 buffer.append(ParseUtils.addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch,
563 ' ', 5, true, satelliteNumber));
564
565 buffer.append(Integer.toString(checksum(buffer)));
566
567 line2 = buffer.toString();
568
569 }
570
571
572
573
574
575 @Override
576 public List<ParameterDriver> getParametersDrivers() {
577 return Collections.singletonList(bStarParameterDriver);
578 }
579
580
581
582
583 public int getSatelliteNumber() {
584 return satelliteNumber;
585 }
586
587
588
589
590 public char getClassification() {
591 return classification;
592 }
593
594
595
596
597 public int getLaunchYear() {
598 return launchYear;
599 }
600
601
602
603
604 public int getLaunchNumber() {
605 return launchNumber;
606 }
607
608
609
610
611 public String getLaunchPiece() {
612 return launchPiece;
613 }
614
615
616
617
618
619 public int getEphemerisType() {
620 return ephemerisType;
621 }
622
623
624
625
626 public int getElementNumber() {
627 return elementNumber;
628 }
629
630
631
632
633 public FieldAbsoluteDate<T> getDate() {
634 return epoch;
635 }
636
637
638
639
640 public T getMeanMotion() {
641 return meanMotion;
642 }
643
644
645
646
647 public T getMeanMotionFirstDerivative() {
648 return meanMotionFirstDerivative;
649 }
650
651
652
653
654 public T getMeanMotionSecondDerivative() {
655 return meanMotionSecondDerivative;
656 }
657
658
659
660
661 public T getE() {
662 return eccentricity;
663 }
664
665
666
667
668 public T getI() {
669 return inclination;
670 }
671
672
673
674
675 public T getPerigeeArgument() {
676 return pa;
677 }
678
679
680
681
682 public T getRaan() {
683 return raan;
684 }
685
686
687
688
689 public T getMeanAnomaly() {
690 return meanAnomaly;
691 }
692
693
694
695
696 public int getRevolutionNumberAtEpoch() {
697 return revolutionNumberAtEpoch;
698 }
699
700
701
702
703 public double getBStar() {
704 return bStarParameterDriver.getValue(getDate().toAbsoluteDate());
705 }
706
707
708
709
710
711 public T computeSemiMajorAxis() {
712 return FastMath.cbrt(meanMotion.square().reciprocal().multiply(TLEConstants.MU));
713 }
714
715
716
717
718
719
720 public String toString() {
721 try {
722 return getLine1() + System.getProperty("line.separator") + getLine2();
723 } catch (OrekitException oe) {
724 throw new OrekitInternalError(oe);
725 }
726 }
727
728
729
730
731
732
733
734
735
736
737
738
739 @Deprecated
740 public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
741 final TleGenerationAlgorithm generationAlgorithm) {
742 return generationAlgorithm.generate(state, templateTLE);
743 }
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762 @DefaultDataContext
763 public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
764 final OsculatingToMeanConverter converter) {
765 return stateToTLE(state, templateTLE, converter, DataContext.getDefault());
766 }
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783 public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
784 final OsculatingToMeanConverter converter,
785 final DataContext dataContext) {
786 converter.setMeanTheory(new TLETheory(templateTLE.toTLE(), dataContext));
787 final T bStar = state.getMass().getField().getZero().newInstance(templateTLE.getBStar());
788 final FieldKeplerianOrbit<T> mean = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(converter.convertToMean(state.getOrbit()));
789 final FieldTLE<T> tle = TleGenerationUtil.newTLE(mean, templateTLE, bStar, dataContext.getTimeScales().getUTC());
790
791 for (final ParameterDriver templateDrivers : templateTLE.getParametersDrivers()) {
792 if (templateDrivers.isSelected()) {
793
794 tle.getParameterDriver(templateDrivers.getName()).setSelected(true);
795 }
796 }
797 return tle;
798 }
799
800
801
802
803
804
805
806 public static boolean isFormatOK(final String line1, final String line2) {
807 return TLE.isFormatOK(line1, line2);
808 }
809
810
811
812
813
814 private static int checksum(final CharSequence line) {
815 int sum = 0;
816 for (int j = 0; j < 68; j++) {
817 final char c = line.charAt(j);
818 if (Character.isDigit(c)) {
819 sum += Character.digit(c, 10);
820 } else if (c == '-') {
821 ++sum;
822 }
823 }
824 return sum % 10;
825 }
826
827
828
829
830
831 public TLE toTLE() {
832 final TLE regularTLE = new TLE(getSatelliteNumber(), getClassification(), getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getEphemerisType(),
833 getElementNumber(), getDate().toAbsoluteDate(), getMeanMotion().getReal(), getMeanMotionFirstDerivative().getReal(),
834 getMeanMotionSecondDerivative().getReal(), getE().getReal(), getI().getReal(), getPerigeeArgument().getReal(),
835 getRaan().getReal(), getMeanAnomaly().getReal(), getRevolutionNumberAtEpoch(), getBStar(), getUtc());
836
837 for (int k = 0; k < regularTLE.getParametersDrivers().size(); ++k) {
838 regularTLE.getParametersDrivers().get(k).setSelected(getParametersDrivers().get(k).isSelected());
839 }
840
841 return regularTLE;
842
843 }
844
845
846
847
848
849
850
851
852
853 @Override
854 public boolean equals(final Object o) {
855 if (o == this) {
856 return true;
857 }
858 if (!(o instanceof FieldTLE)) {
859 return false;
860 }
861 @SuppressWarnings("unchecked")
862 final FieldTLE<T> tle = (FieldTLE<T>) o;
863 return satelliteNumber == tle.satelliteNumber &&
864 classification == tle.classification &&
865 launchYear == tle.launchYear &&
866 launchNumber == tle.launchNumber &&
867 Objects.equals(launchPiece, tle.launchPiece) &&
868 ephemerisType == tle.ephemerisType &&
869 elementNumber == tle.elementNumber &&
870 Objects.equals(epoch, tle.epoch) &&
871 meanMotion.getReal() == tle.meanMotion.getReal() &&
872 meanMotionFirstDerivative.getReal() == tle.meanMotionFirstDerivative.getReal() &&
873 meanMotionSecondDerivative.getReal() == tle.meanMotionSecondDerivative.getReal() &&
874 eccentricity.getReal() == tle.eccentricity.getReal() &&
875 inclination.getReal() == tle.inclination.getReal() &&
876 pa.getReal() == tle.pa.getReal() &&
877 raan.getReal() == tle.raan.getReal() &&
878 meanAnomaly.getReal() == tle.meanAnomaly.getReal() &&
879 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
880 getBStar() == tle.getBStar();
881 }
882
883
884
885
886 @Override
887 public int hashCode() {
888 return Objects.hash(satelliteNumber,
889 classification,
890 launchYear,
891 launchNumber,
892 launchPiece,
893 ephemerisType,
894 elementNumber,
895 epoch,
896 meanMotion,
897 meanMotionFirstDerivative,
898 meanMotionSecondDerivative,
899 eccentricity,
900 inclination,
901 pa,
902 raan,
903 meanAnomaly,
904 revolutionNumberAtEpoch,
905 getBStar());
906 }
907
908 }