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