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.Locale;
23 import java.util.Objects;
24 import java.util.regex.Pattern;
25
26 import org.hipparchus.util.ArithmeticUtils;
27 import org.hipparchus.util.FastMath;
28 import org.orekit.errors.OrekitException;
29 import org.orekit.errors.OrekitInternalError;
30 import org.orekit.errors.OrekitMessages;
31 import org.orekit.time.AbsoluteDate;
32 import org.orekit.time.DateComponents;
33 import org.orekit.time.DateTimeComponents;
34 import org.orekit.time.TimeComponents;
35 import org.orekit.time.TimeScalesFactory;
36 import org.orekit.time.TimeStamped;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public class TLE implements TimeStamped, Serializable {
55
56
57 public static final int DEFAULT = 0;
58
59
60 public static final int SGP = 1;
61
62
63 public static final int SGP4 = 2;
64
65
66 public static final int SDP4 = 3;
67
68
69 public static final int SGP8 = 4;
70
71
72 public static final int SDP8 = 5;
73
74
75 private static final Pattern LINE_1_PATTERN =
76 Pattern.compile("1 [ 0-9]{5}[A-Z] [ 0-9]{5}[ A-Z]{3} [ 0-9]{5}[.][ 0-9]{8} (?:(?:[ 0+-][.][ 0-9]{8})|(?: [ +-][.][ 0-9]{7})) " +
77 "[ +-][ 0-9]{5}[+-][ 0-9] [ +-][ 0-9]{5}[+-][ 0-9] [ 0-9] [ 0-9]{4}[ 0-9]");
78
79
80 private static final Pattern LINE_2_PATTERN =
81 Pattern.compile("2 [ 0-9]{5} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{7} " +
82 "[ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{2}[.][ 0-9]{13}[ 0-9]");
83
84
85 private static final DecimalFormatSymbols SYMBOLS =
86 new DecimalFormatSymbols(Locale.US);
87
88
89 private static final long serialVersionUID = -1596648022319057689L;
90
91
92 private final int satelliteNumber;
93
94
95 private final char classification;
96
97
98 private final int launchYear;
99
100
101 private final int launchNumber;
102
103
104 private final String launchPiece;
105
106
107 private final int ephemerisType;
108
109
110 private final int elementNumber;
111
112
113 private final AbsoluteDate epoch;
114
115
116 private final double meanMotion;
117
118
119 private final double meanMotionFirstDerivative;
120
121
122 private final double meanMotionSecondDerivative;
123
124
125 private final double eccentricity;
126
127
128 private final double inclination;
129
130
131 private final double pa;
132
133
134 private final double raan;
135
136
137 private final double meanAnomaly;
138
139
140 private final int revolutionNumberAtEpoch;
141
142
143 private final double bStar;
144
145
146 private String line1;
147
148
149 private String line2;
150
151
152
153
154
155
156
157 public TLE(final String line1, final String line2) {
158
159
160 satelliteNumber = parseInteger(line1, 2, 5);
161 final int satNum2 = parseInteger(line2, 2, 5);
162 if (satelliteNumber != satNum2) {
163 throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
164 line1, line2);
165 }
166 classification = line1.charAt(7);
167 launchYear = parseYear(line1, 9);
168 launchNumber = parseInteger(line1, 11, 3);
169 launchPiece = line1.substring(14, 17).trim();
170 ephemerisType = parseInteger(line1, 62, 1);
171 elementNumber = parseInteger(line1, 64, 4);
172
173
174 final int year = parseYear(line1, 18);
175 final int dayInYear = parseInteger(line1, 20, 3);
176 final long df = 27l * parseInteger(line1, 24, 8);
177 final int secondsA = (int) (df / 31250l);
178 final double secondsB = (df % 31250l) / 31250.0;
179 epoch = new AbsoluteDate(new DateComponents(year, dayInYear),
180 new TimeComponents(secondsA, secondsB),
181 TimeScalesFactory.getUTC());
182
183
184
185 meanMotion = parseDouble(line2, 52, 11) * FastMath.PI / 43200.0;
186 meanMotionFirstDerivative = parseDouble(line1, 33, 10) * FastMath.PI / 1.86624e9;
187 meanMotionSecondDerivative = Double.parseDouble((line1.substring(44, 45) + '.' +
188 line1.substring(45, 50) + 'e' +
189 line1.substring(50, 52)).replace(' ', '0')) *
190 FastMath.PI / 5.3747712e13;
191
192 eccentricity = Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0'));
193 inclination = FastMath.toRadians(parseDouble(line2, 8, 8));
194 pa = FastMath.toRadians(parseDouble(line2, 34, 8));
195 raan = FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0')));
196 meanAnomaly = FastMath.toRadians(parseDouble(line2, 43, 8));
197
198 revolutionNumberAtEpoch = parseInteger(line2, 63, 5);
199 bStar = Double.parseDouble((line1.substring(53, 54) + '.' +
200 line1.substring(54, 59) + 'e' +
201 line1.substring(59, 61)).replace(' ', '0'));
202
203
204 this.line1 = line1;
205 this.line2 = line2;
206
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 public TLE(final int satelliteNumber, final char classification,
230 final int launchYear, final int launchNumber, final String launchPiece,
231 final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
232 final double meanMotion, final double meanMotionFirstDerivative,
233 final double meanMotionSecondDerivative, final double e, final double i,
234 final double pa, final double raan, final double meanAnomaly,
235 final int revolutionNumberAtEpoch, final double bStar) {
236
237
238 this.satelliteNumber = satelliteNumber;
239 this.classification = classification;
240 this.launchYear = launchYear;
241 this.launchNumber = launchNumber;
242 this.launchPiece = launchPiece;
243 this.ephemerisType = ephemerisType;
244 this.elementNumber = elementNumber;
245
246
247 this.epoch = epoch;
248 this.meanMotion = meanMotion;
249 this.meanMotionFirstDerivative = meanMotionFirstDerivative;
250 this.meanMotionSecondDerivative = meanMotionSecondDerivative;
251 this.inclination = i;
252 this.raan = raan;
253 this.eccentricity = e;
254 this.pa = pa;
255 this.meanAnomaly = meanAnomaly;
256
257 this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
258 this.bStar = bStar;
259
260
261 this.line1 = null;
262 this.line2 = null;
263
264 }
265
266
267
268
269 public String getLine1() {
270 if (line1 == null) {
271 buildLine1();
272 }
273 return line1;
274 }
275
276
277
278
279 public String getLine2() {
280 if (line2 == null) {
281 buildLine2();
282 }
283 return line2;
284 }
285
286
287
288 private void buildLine1() {
289
290 final StringBuffer buffer = new StringBuffer();
291
292 buffer.append('1');
293
294 buffer.append(' ');
295 buffer.append(addPadding("satelliteNumber-1", satelliteNumber, '0', 5, true));
296 buffer.append(classification);
297
298 buffer.append(' ');
299 buffer.append(addPadding("launchYear", launchYear % 100, '0', 2, true));
300 buffer.append(addPadding("launchNumber", launchNumber, '0', 3, true));
301 buffer.append(addPadding("launchPiece", launchPiece, ' ', 3, false));
302
303 buffer.append(' ');
304 final DateTimeComponents dtc = epoch.getComponents(TimeScalesFactory.getUTC());
305 buffer.append(addPadding("year", dtc.getDate().getYear() % 100, '0', 2, true));
306 buffer.append(addPadding("day", dtc.getDate().getDayOfYear(), '0', 3, true));
307 buffer.append('.');
308
309 final int fraction = (int) FastMath.rint(31250 * dtc.getTime().getSecondsInUTCDay() / 27.0);
310 buffer.append(addPadding("fraction", fraction, '0', 8, true));
311
312 buffer.append(' ');
313 final double n1 = meanMotionFirstDerivative * 1.86624e9 / FastMath.PI;
314 final String sn1 = addPadding("meanMotionFirstDerivative",
315 new DecimalFormat(".00000000", SYMBOLS).format(n1), ' ', 10, true);
316 buffer.append(sn1);
317
318 buffer.append(' ');
319 final double n2 = meanMotionSecondDerivative * 5.3747712e13 / FastMath.PI;
320 buffer.append(formatExponentMarkerFree("meanMotionSecondDerivative", n2, 5, ' ', 8, true));
321
322 buffer.append(' ');
323 buffer.append(formatExponentMarkerFree("B*", bStar, 5, ' ', 8, true));
324
325 buffer.append(' ');
326 buffer.append(ephemerisType);
327
328 buffer.append(' ');
329 buffer.append(addPadding("elementNumber", elementNumber, ' ', 4, true));
330
331 buffer.append(Integer.toString(checksum(buffer)));
332
333 line1 = buffer.toString();
334
335 }
336
337
338
339
340
341
342
343
344
345
346
347 private String formatExponentMarkerFree(final String name, final double d, final int mantissaSize,
348 final char c, final int size, final boolean rightJustified) {
349 final double dAbs = FastMath.abs(d);
350 int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
351 long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
352 if (mantissa == 0) {
353 exponent = 0;
354 } else if (mantissa > (ArithmeticUtils.pow(10, mantissaSize) - 1)) {
355
356
357
358 exponent++;
359 mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
360 }
361 final String sMantissa = addPadding(name, (int) mantissa, '0', mantissaSize, true);
362 final String sExponent = Integer.toString(FastMath.abs(exponent));
363 final String formatted = (d < 0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;
364
365 return addPadding(name, formatted, c, size, rightJustified);
366
367 }
368
369
370
371 private void buildLine2() {
372
373 final StringBuffer buffer = new StringBuffer();
374 final DecimalFormat f34 = new DecimalFormat("##0.0000", SYMBOLS);
375 final DecimalFormat f211 = new DecimalFormat("#0.00000000", SYMBOLS);
376
377 buffer.append('2');
378
379 buffer.append(' ');
380 buffer.append(addPadding("satelliteNumber-2", satelliteNumber, '0', 5, true));
381
382 buffer.append(' ');
383 buffer.append(addPadding("inclination", f34.format(FastMath.toDegrees(inclination)), ' ', 8, true));
384 buffer.append(' ');
385 buffer.append(addPadding("raan", f34.format(FastMath.toDegrees(raan)), ' ', 8, true));
386 buffer.append(' ');
387 buffer.append(addPadding("eccentricity", (int) FastMath.rint(eccentricity * 1.0e7), '0', 7, true));
388 buffer.append(' ');
389 buffer.append(addPadding("pa", f34.format(FastMath.toDegrees(pa)), ' ', 8, true));
390 buffer.append(' ');
391 buffer.append(addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly)), ' ', 8, true));
392
393 buffer.append(' ');
394 buffer.append(addPadding("meanMotion", f211.format(meanMotion * 43200.0 / FastMath.PI), ' ', 11, true));
395 buffer.append(addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch, ' ', 5, true));
396
397 buffer.append(Integer.toString(checksum(buffer)));
398
399 line2 = buffer.toString();
400
401 }
402
403
404
405
406
407
408
409
410
411
412 private String addPadding(final String name, final int k, final char c,
413 final int size, final boolean rightJustified) {
414 return addPadding(name, Integer.toString(k), c, size, rightJustified);
415 }
416
417
418
419
420
421
422
423
424
425
426 private String addPadding(final String name, final String string, final char c,
427 final int size, final boolean rightJustified) {
428
429 if (string.length() > size) {
430 throw new OrekitException(OrekitMessages.TLE_INVALID_PARAMETER,
431 satelliteNumber, name, string);
432 }
433
434 final StringBuffer padding = new StringBuffer();
435 for (int i = 0; i < size; ++i) {
436 padding.append(c);
437 }
438
439 if (rightJustified) {
440 final String concatenated = padding + string;
441 final int l = concatenated.length();
442 return concatenated.substring(l - size, l);
443 }
444
445 return (string + padding).substring(0, size);
446
447 }
448
449
450
451
452
453
454
455 private double parseDouble(final String line, final int start, final int length) {
456 final String field = line.substring(start, start + length).trim();
457 return field.length() > 0 ? Double.parseDouble(field.replace(' ', '0')) : 0;
458 }
459
460
461
462
463
464
465
466 private int parseInteger(final String line, final int start, final int length) {
467 final String field = line.substring(start, start + length).trim();
468 return field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
469 }
470
471
472
473
474
475
476 private int parseYear(final String line, final int start) {
477 final int year = 2000 + parseInteger(line, start, 2);
478 return (year > 2056) ? (year - 100) : year;
479 }
480
481
482
483
484 public int getSatelliteNumber() {
485 return satelliteNumber;
486 }
487
488
489
490
491 public char getClassification() {
492 return classification;
493 }
494
495
496
497
498 public int getLaunchYear() {
499 return launchYear;
500 }
501
502
503
504
505 public int getLaunchNumber() {
506 return launchNumber;
507 }
508
509
510
511
512 public String getLaunchPiece() {
513 return launchPiece;
514 }
515
516
517
518
519
520 public int getEphemerisType() {
521 return ephemerisType;
522 }
523
524
525
526
527 public int getElementNumber() {
528 return elementNumber;
529 }
530
531
532
533
534 public AbsoluteDate getDate() {
535 return epoch;
536 }
537
538
539
540
541 public double getMeanMotion() {
542 return meanMotion;
543 }
544
545
546
547
548 public double getMeanMotionFirstDerivative() {
549 return meanMotionFirstDerivative;
550 }
551
552
553
554
555 public double getMeanMotionSecondDerivative() {
556 return meanMotionSecondDerivative;
557 }
558
559
560
561
562 public double getE() {
563 return eccentricity;
564 }
565
566
567
568
569 public double getI() {
570 return inclination;
571 }
572
573
574
575
576 public double getPerigeeArgument() {
577 return pa;
578 }
579
580
581
582
583 public double getRaan() {
584 return raan;
585 }
586
587
588
589
590 public double getMeanAnomaly() {
591 return meanAnomaly;
592 }
593
594
595
596
597 public int getRevolutionNumberAtEpoch() {
598 return revolutionNumberAtEpoch;
599 }
600
601
602
603
604 public double getBStar() {
605 return bStar;
606 }
607
608
609
610
611
612
613 public String toString() {
614 try {
615 return getLine1() + System.getProperty("line.separator") + getLine2();
616 } catch (OrekitException oe) {
617 throw new OrekitInternalError(oe);
618 }
619 }
620
621
622
623
624
625
626
627 public static boolean isFormatOK(final String line1, final String line2) {
628
629 if (line1 == null || line1.length() != 69 ||
630 line2 == null || line2.length() != 69) {
631 return false;
632 }
633
634 if (!(LINE_1_PATTERN.matcher(line1).matches() &&
635 LINE_2_PATTERN.matcher(line2).matches())) {
636 return false;
637 }
638
639
640 final int checksum1 = checksum(line1);
641 if (Integer.parseInt(line1.substring(68)) != (checksum1 % 10)) {
642 throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
643 1, Integer.toString(checksum1 % 10), line1.substring(68), line1);
644 }
645
646 final int checksum2 = checksum(line2);
647 if (Integer.parseInt(line2.substring(68)) != (checksum2 % 10)) {
648 throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
649 2, Integer.toString(checksum2 % 10), line2.substring(68), line2);
650 }
651
652 return true;
653
654 }
655
656
657
658
659
660 private static int checksum(final CharSequence line) {
661 int sum = 0;
662 for (int j = 0; j < 68; j++) {
663 final char c = line.charAt(j);
664 if (Character.isDigit(c)) {
665 sum += Character.digit(c, 10);
666 } else if (c == '-') {
667 ++sum;
668 }
669 }
670 return sum % 10;
671 }
672
673
674
675
676
677
678
679
680
681 @Override
682 public boolean equals(final Object o) {
683 if (o == this) {
684 return true;
685 }
686 if (!(o instanceof TLE)) {
687 return false;
688 }
689 final TLEhref="../../../../../org/orekit/propagation/analytical/tle/TLE.html#TLE">TLE tle = (TLE) o;
690 return satelliteNumber == tle.satelliteNumber &&
691 classification == tle.classification &&
692 launchYear == tle.launchYear &&
693 launchNumber == tle.launchNumber &&
694 Objects.equals(launchPiece, tle.launchPiece) &&
695 ephemerisType == tle.ephemerisType &&
696 elementNumber == tle.elementNumber &&
697 Objects.equals(epoch, tle.epoch) &&
698 meanMotion == tle.meanMotion &&
699 meanMotionFirstDerivative == tle.meanMotionFirstDerivative &&
700 meanMotionSecondDerivative == tle.meanMotionSecondDerivative &&
701 eccentricity == tle.eccentricity &&
702 inclination == tle.inclination &&
703 pa == tle.pa &&
704 raan == tle.raan &&
705 meanAnomaly == tle.meanAnomaly &&
706 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
707 bStar == tle.bStar;
708 }
709
710
711
712
713 @Override
714 public int hashCode() {
715 return Objects.hash(satelliteNumber,
716 classification,
717 launchYear,
718 launchNumber,
719 launchPiece,
720 ephemerisType,
721 elementNumber,
722 epoch,
723 meanMotion,
724 meanMotionFirstDerivative,
725 meanMotionSecondDerivative,
726 eccentricity,
727 inclination,
728 pa,
729 raan,
730 meanAnomaly,
731 revolutionNumberAtEpoch,
732 bStar);
733 }
734
735 }