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.io.Serializable;
20 import java.text.DecimalFormat;
21 import java.text.DecimalFormatSymbols;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Objects;
26
27 import org.hipparchus.CalculusFieldElement;
28 import org.hipparchus.Field;
29 import org.hipparchus.util.ArithmeticUtils;
30 import org.hipparchus.util.FastMath;
31 import org.hipparchus.util.MathUtils;
32 import org.orekit.annotation.DefaultDataContext;
33 import org.orekit.data.DataContext;
34 import org.orekit.errors.OrekitException;
35 import org.orekit.errors.OrekitInternalError;
36 import org.orekit.errors.OrekitMessages;
37 import org.orekit.propagation.FieldSpacecraftState;
38 import org.orekit.propagation.analytical.tle.generation.TleGenerationAlgorithm;
39 import org.orekit.time.DateComponents;
40 import org.orekit.time.DateTimeComponents;
41 import org.orekit.time.FieldAbsoluteDate;
42 import org.orekit.time.FieldTimeStamped;
43 import org.orekit.time.TimeComponents;
44 import org.orekit.time.TimeScale;
45 import org.orekit.utils.Constants;
46 import org.orekit.utils.ParameterDriver;
47 import org.orekit.utils.ParameterDriversProvider;
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 FieldTimeStamped<T>, Serializable, ParameterDriversProvider {
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 public static final String B_STAR = "BSTAR";
90
91
92
93
94
95
96
97 private static final double B_STAR_SCALE = FastMath.scalb(1.0, -20);
98
99
100 private static final String MEAN_MOTION = "meanMotion";
101
102
103 private static final String INCLINATION = "inclination";
104
105
106 private static final String ECCENTRICITY = "eccentricity";
107
108
109 private static final DecimalFormatSymbols SYMBOLS =
110 new DecimalFormatSymbols(Locale.US);
111
112
113 private static final long serialVersionUID = -1596648022319057689L;
114
115
116 private final int satelliteNumber;
117
118
119 private final char classification;
120
121
122 private final int launchYear;
123
124
125 private final int launchNumber;
126
127
128 private final String launchPiece;
129
130
131 private final int ephemerisType;
132
133
134 private final int elementNumber;
135
136
137 private final transient FieldAbsoluteDate<T> epoch;
138
139
140 private final T meanMotion;
141
142
143 private final T meanMotionFirstDerivative;
144
145
146 private final T meanMotionSecondDerivative;
147
148
149 private final T eccentricity;
150
151
152 private final T inclination;
153
154
155 private final T pa;
156
157
158 private final T raan;
159
160
161 private final T meanAnomaly;
162
163
164 private final int revolutionNumberAtEpoch;
165
166
167 private String line1;
168
169
170 private String line2;
171
172
173 private final TimeScale utc;
174
175
176 private final transient ParameterDriver bStarParameterDriver;
177
178
179
180
181
182
183
184
185
186
187
188 @DefaultDataContext
189 public FieldTLE(final Field<T> field, final String line1, final String line2) {
190 this(field, line1, line2, DataContext.getDefault().getTimeScales().getUTC());
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204 public FieldTLE(final Field<T> field, final String line1, final String line2, final TimeScale utc) {
205
206
207 final T zero = field.getZero();
208 final T pi = zero.getPi();
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
225 final int year = ParseUtils.parseYear(line1, 18);
226 final int dayInYear = ParseUtils.parseInteger(line1, 20, 3);
227 final long df = 27l * ParseUtils.parseInteger(line1, 24, 8);
228 final int secondsA = (int) (df / 31250l);
229 final double secondsB = (df % 31250l) / 31250.0;
230 epoch = new FieldAbsoluteDate<>(field, new DateComponents(year, dayInYear),
231 new TimeComponents(secondsA, secondsB),
232 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
711 public String toString() {
712 try {
713 return getLine1() + System.getProperty("line.separator") + getLine2();
714 } catch (OrekitException oe) {
715 throw new OrekitInternalError(oe);
716 }
717 }
718
719
720
721
722
723
724
725
726
727
728
729 public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
730 final TleGenerationAlgorithm generationAlgorithm) {
731 return generationAlgorithm.generate(state, templateTLE);
732 }
733
734
735
736
737
738
739
740 public static boolean isFormatOK(final String line1, final String line2) {
741 return TLE.isFormatOK(line1, line2);
742 }
743
744
745
746
747
748 private static int checksum(final CharSequence line) {
749 int sum = 0;
750 for (int j = 0; j < 68; j++) {
751 final char c = line.charAt(j);
752 if (Character.isDigit(c)) {
753 sum += Character.digit(c, 10);
754 } else if (c == '-') {
755 ++sum;
756 }
757 }
758 return sum % 10;
759 }
760
761
762
763
764
765 public TLE toTLE() {
766 final TLE regularTLE = new TLE(getSatelliteNumber(), getClassification(), getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getEphemerisType(),
767 getElementNumber(), getDate().toAbsoluteDate(), getMeanMotion().getReal(), getMeanMotionFirstDerivative().getReal(),
768 getMeanMotionSecondDerivative().getReal(), getE().getReal(), getI().getReal(), getPerigeeArgument().getReal(),
769 getRaan().getReal(), getMeanAnomaly().getReal(), getRevolutionNumberAtEpoch(), getBStar(), getUtc());
770
771 for (int k = 0; k < regularTLE.getParametersDrivers().size(); ++k) {
772 regularTLE.getParametersDrivers().get(k).setSelected(getParametersDrivers().get(k).isSelected());
773 }
774
775 return regularTLE;
776
777 }
778
779
780
781
782
783
784
785
786
787 @Override
788 public boolean equals(final Object o) {
789 if (o == this) {
790 return true;
791 }
792 if (!(o instanceof FieldTLE)) {
793 return false;
794 }
795 @SuppressWarnings("unchecked")
796 final FieldTLE<T> tle = (FieldTLE<T>) o;
797 return satelliteNumber == tle.satelliteNumber &&
798 classification == tle.classification &&
799 launchYear == tle.launchYear &&
800 launchNumber == tle.launchNumber &&
801 Objects.equals(launchPiece, tle.launchPiece) &&
802 ephemerisType == tle.ephemerisType &&
803 elementNumber == tle.elementNumber &&
804 Objects.equals(epoch, tle.epoch) &&
805 meanMotion.getReal() == tle.meanMotion.getReal() &&
806 meanMotionFirstDerivative.getReal() == tle.meanMotionFirstDerivative.getReal() &&
807 meanMotionSecondDerivative.getReal() == tle.meanMotionSecondDerivative.getReal() &&
808 eccentricity.getReal() == tle.eccentricity.getReal() &&
809 inclination.getReal() == tle.inclination.getReal() &&
810 pa.getReal() == tle.pa.getReal() &&
811 raan.getReal() == tle.raan.getReal() &&
812 meanAnomaly.getReal() == tle.meanAnomaly.getReal() &&
813 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
814 getBStar() == tle.getBStar();
815 }
816
817
818
819
820 @Override
821 public int hashCode() {
822 return Objects.hash(satelliteNumber,
823 classification,
824 launchYear,
825 launchNumber,
826 launchPiece,
827 ephemerisType,
828 elementNumber,
829 epoch,
830 meanMotion,
831 meanMotionFirstDerivative,
832 meanMotionSecondDerivative,
833 eccentricity,
834 inclination,
835 pa,
836 raan,
837 meanAnomaly,
838 revolutionNumberAtEpoch,
839 getBStar());
840 }
841
842 }