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