1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.bodies;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.nio.charset.StandardCharsets;
22 import java.text.ParseException;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.atomic.AtomicReference;
31
32 import org.hipparchus.CalculusFieldElement;
33 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
34 import org.hipparchus.geometry.euclidean.threed.Vector3D;
35 import org.hipparchus.util.FastMath;
36 import org.orekit.annotation.DefaultDataContext;
37 import org.orekit.data.AbstractSelfFeedingLoader;
38 import org.orekit.data.DataContext;
39 import org.orekit.data.DataLoader;
40 import org.orekit.data.DataProvidersManager;
41 import org.orekit.errors.OrekitException;
42 import org.orekit.errors.OrekitInternalError;
43 import org.orekit.errors.OrekitMessages;
44 import org.orekit.errors.TimeStampedCacheException;
45 import org.orekit.frames.Frame;
46 import org.orekit.time.AbsoluteDate;
47 import org.orekit.time.ChronologicalComparator;
48 import org.orekit.time.DateComponents;
49 import org.orekit.time.FieldAbsoluteDate;
50 import org.orekit.time.TimeComponents;
51 import org.orekit.time.TimeOffset;
52 import org.orekit.time.TimeScale;
53 import org.orekit.time.TimeScales;
54 import org.orekit.utils.Constants;
55 import org.orekit.utils.FieldPVCoordinates;
56 import org.orekit.utils.OrekitConfiguration;
57 import org.orekit.utils.PVCoordinates;
58 import org.orekit.utils.GenericTimeStampedCache;
59 import org.orekit.utils.TimeStampedGenerator;
60 import org.orekit.utils.units.UnitsConverter;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 public class JPLEphemeridesLoader extends AbstractSelfFeedingLoader
81 implements CelestialBodyLoader {
82
83
84 public static final String DEFAULT_DE_SUPPORTED_NAMES = "^[lu]nx([mp](\\d\\d\\d\\d))+\\.(?:4\\d\\d)$";
85
86
87 public static final String DEFAULT_INPOP_SUPPORTED_NAMES = "^inpop.*\\.dat$";
88
89
90 private static final TimeOffset FIFTY_DAYS =
91 new TimeOffset(50, TimeUnit.DAYS);
92
93
94 private static final int INPOP_DE_NUMBER = 100;
95
96
97 private static final int CONSTANTS_MAX_NUMBER = 400;
98
99
100 private static final int HEADER_EPHEMERIS_TYPE_OFFSET = 2840;
101
102
103 private static final int HEADER_RECORD_SIZE_OFFSET = 2856;
104
105
106 private static final int HEADER_START_EPOCH_OFFSET = 2652;
107
108
109 private static final int HEADER_END_EPOCH_OFFSET = 2660;
110
111
112 private static final int HEADER_ASTRONOMICAL_UNIT_OFFSET = 2680;
113
114
115 private static final int HEADER_EM_RATIO_OFFSET = 2688;
116
117
118 private static final int HEADER_CHEBISHEV_INDICES_OFFSET = 2696;
119
120
121 private static final int HEADER_LIBRATION_INDICES_OFFSET = 2844;
122
123
124 private static final int HEADER_CHUNK_DURATION_OFFSET = 2668;
125
126
127 private static final int HEADER_CONSTANTS_NAMES_OFFSET = 252;
128
129
130 private static final int HEADER_CONSTANTS_VALUES_OFFSET = 0;
131
132
133 private static final int DATA_START_RANGE_OFFSET = 0;
134
135
136 private static final int DATE_END_RANGE_OFFSET = 8;
137
138
139 private static final String CONSTANT_AU = "AU";
140
141
142 private static final String CONSTANT_EMRAT = "EMRAT";
143
144
145 public enum EphemerisType {
146
147
148 SOLAR_SYSTEM_BARYCENTER,
149
150
151 SUN,
152
153
154 MERCURY,
155
156
157 VENUS,
158
159
160 EARTH_MOON,
161
162
163 EARTH,
164
165
166 MOON,
167
168
169 MARS,
170
171
172 JUPITER,
173
174
175 SATURN,
176
177
178 URANUS,
179
180
181 NEPTUNE,
182
183
184 PLUTO,
185
186
187 LIBRATION
188
189 }
190
191
192 public interface RawPVProvider {
193
194
195
196
197
198 PVCoordinates getRawPV(AbsoluteDate date);
199
200
201
202
203
204 default Vector3D getRawPosition(final AbsoluteDate date) {
205 return getRawPV(date).getPosition();
206 }
207
208
209
210
211
212
213 <T extends CalculusFieldElement<T>> FieldPVCoordinates<T> getRawPV(FieldAbsoluteDate<T> date);
214
215
216
217
218
219
220 default <T extends CalculusFieldElement<T>> FieldVector3D<T> getRawPosition(final FieldAbsoluteDate<T> date) {
221 return getRawPV(date).getPosition();
222 }
223
224 }
225
226
227 private final GenericTimeStampedCache<PosVelChebyshev> ephemerides;
228
229
230 private final AtomicReference<Map<String, Double>> constants;
231
232
233 private final EphemerisType generateType;
234
235
236 private final EphemerisType loadType;
237
238
239 private final TimeScales timeScales;
240
241
242 private final Frame gcrf;
243
244
245 private final CelestialBodies celestialBodies;
246
247
248 private AbsoluteDate startEpoch;
249
250
251 private AbsoluteDate finalEpoch;
252
253
254 private double maxChunksDuration;
255
256
257 private TimeOffset chunksDuration;
258
259
260 private int firstIndex;
261
262
263 private int coeffs;
264
265
266 private int chunks;
267
268
269 private int components;
270
271
272 private double positionUnit;
273
274
275 private TimeScale timeScale;
276
277
278 private boolean bigEndian;
279
280
281
282
283
284
285
286
287
288 @DefaultDataContext
289 public JPLEphemeridesLoader(final String supportedNames, final EphemerisType generateType) {
290 this(supportedNames, generateType,
291 DataContext.getDefault().getDataProvidersManager(),
292 DataContext.getDefault().getTimeScales(),
293 DataContext.getDefault().getFrames().getGCRF(),
294 DataContext.getDefault().getCelestialBodies());
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316 public JPLEphemeridesLoader(final String supportedNames,
317 final EphemerisType generateType,
318 final DataProvidersManager dataProvidersManager,
319 final TimeScales timeScales,
320 final Frame gcrf,
321 final CelestialBodies celestialBodies) {
322 super(supportedNames, dataProvidersManager);
323
324 this.timeScales = timeScales;
325 this.gcrf = gcrf;
326 this.celestialBodies = celestialBodies;
327 constants = new AtomicReference<>();
328
329 this.generateType = generateType;
330 if (generateType == EphemerisType.SOLAR_SYSTEM_BARYCENTER) {
331 loadType = EphemerisType.EARTH_MOON;
332 } else if (generateType == EphemerisType.EARTH_MOON) {
333 loadType = EphemerisType.MOON;
334 } else {
335 loadType = generateType;
336 }
337
338 ephemerides = new GenericTimeStampedCache<>(
339 2, OrekitConfiguration.getCacheSlotsNumber(),
340 Double.POSITIVE_INFINITY, FIFTY_DAYS.toDouble(),
341 new EphemerisParser());
342 maxChunksDuration = Double.NaN;
343 chunksDuration = null;
344
345 }
346
347
348
349
350
351 @Override
352 public CelestialBody loadCelestialBody(final String name) {
353
354 final double gm = getLoadedGravitationalCoefficient(generateType);
355 final IAUPole iauPole = PredefinedIAUPoles
356 .getIAUPole(generateType, timeScales);
357 final double scale;
358 final Frame definingFrameAlignedWithICRF;
359 final RawPVProvider rawPVProvider;
360 switch (generateType) {
361 case SOLAR_SYSTEM_BARYCENTER : {
362 scale = -1.0;
363 definingFrameAlignedWithICRF = celestialBodies
364 .getBody(CelestialBodyFactory.EARTH_MOON)
365 .getIcrfAlignedFrame();
366 rawPVProvider = new EphemerisRawPVProvider();
367 break;
368 }
369 case EARTH_MOON :
370 scale = 1.0 / (1.0 + getLoadedEarthMoonMassRatio());
371 definingFrameAlignedWithICRF = gcrf;
372 rawPVProvider = new EphemerisRawPVProvider();
373 break;
374 case EARTH :
375 scale = 1.0;
376 definingFrameAlignedWithICRF = gcrf;
377 rawPVProvider = new ZeroRawPVProvider();
378 break;
379 case MOON :
380 scale = 1.0;
381 definingFrameAlignedWithICRF = gcrf;
382 rawPVProvider = new EphemerisRawPVProvider();
383 break;
384 default : {
385 scale = 1.0;
386 definingFrameAlignedWithICRF = celestialBodies
387 .getBody(CelestialBodyFactory.SOLAR_SYSTEM_BARYCENTER)
388 .getIcrfAlignedFrame();
389 rawPVProvider = new EphemerisRawPVProvider();
390 }
391 }
392
393
394 return new JPLCelestialBody(name, getSupportedNames(), generateType, rawPVProvider,
395 gm, scale, iauPole, definingFrameAlignedWithICRF);
396
397 }
398
399
400
401
402
403 public JPLLibration loadLibration() {
404 final RawPVProvider rawPVProvider = new EphemerisRawPVProvider();
405
406
407 return new JPLLibration(rawPVProvider);
408 }
409
410
411
412
413 public double getLoadedAstronomicalUnit() {
414 return UnitsConverter.KILOMETRES_TO_METRES.convert(getLoadedConstant(CONSTANT_AU));
415 }
416
417
418
419
420 public double getLoadedEarthMoonMassRatio() {
421 return getLoadedConstant(CONSTANT_EMRAT);
422 }
423
424
425
426
427
428 public double getLoadedGravitationalCoefficient(final EphemerisType body) {
429
430
431 final double rawGM;
432 switch (body) {
433 case SOLAR_SYSTEM_BARYCENTER :
434 return getLoadedGravitationalCoefficient(EphemerisType.SUN) +
435 getLoadedGravitationalCoefficient(EphemerisType.MERCURY) +
436 getLoadedGravitationalCoefficient(EphemerisType.VENUS) +
437 getLoadedGravitationalCoefficient(EphemerisType.EARTH_MOON) +
438 getLoadedGravitationalCoefficient(EphemerisType.MARS) +
439 getLoadedGravitationalCoefficient(EphemerisType.JUPITER) +
440 getLoadedGravitationalCoefficient(EphemerisType.SATURN) +
441 getLoadedGravitationalCoefficient(EphemerisType.URANUS) +
442 getLoadedGravitationalCoefficient(EphemerisType.NEPTUNE) +
443 getLoadedGravitationalCoefficient(EphemerisType.PLUTO);
444 case SUN :
445 rawGM = getLoadedConstant("GMS", "GM_Sun");
446 break;
447 case MERCURY :
448 rawGM = getLoadedConstant("GM1", "GM_Mer");
449 break;
450 case VENUS :
451 rawGM = getLoadedConstant("GM2", "GM_Ven");
452 break;
453 case EARTH_MOON :
454 rawGM = getLoadedConstant("GMB", "GM_EMB");
455 break;
456 case EARTH :
457 return getLoadedEarthMoonMassRatio() *
458 getLoadedGravitationalCoefficient(EphemerisType.MOON);
459 case MOON :
460 return getLoadedGravitationalCoefficient(EphemerisType.EARTH_MOON) /
461 (1.0 + getLoadedEarthMoonMassRatio());
462 case MARS :
463 rawGM = getLoadedConstant("GM4", "GM_Mar");
464 break;
465 case JUPITER :
466 rawGM = getLoadedConstant("GM5", "GM_Jup");
467 break;
468 case SATURN :
469 rawGM = getLoadedConstant("GM6", "GM_Sat");
470 break;
471 case URANUS :
472 rawGM = getLoadedConstant("GM7", "GM_Ura");
473 break;
474 case NEPTUNE :
475 rawGM = getLoadedConstant("GM8", "GM_Nep");
476 break;
477 case PLUTO :
478 rawGM = getLoadedConstant("GM9", "GM_Plu");
479 break;
480 default :
481 throw new OrekitInternalError(null);
482 }
483
484 final double au = getLoadedAstronomicalUnit();
485 return rawGM * au * au * au / (Constants.JULIAN_DAY * Constants.JULIAN_DAY);
486
487 }
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502 public double getLoadedConstant(final String... names) {
503
504
505 Map<String, Double> map = constants.get();
506 if (map == null) {
507 final ConstantsParser parser = new ConstantsParser();
508 if (!feed(parser)) {
509 throw new OrekitException(OrekitMessages.NO_JPL_EPHEMERIDES_BINARY_FILES_FOUND);
510 }
511 map = parser.getConstants();
512 constants.compareAndSet(null, map);
513 }
514
515 for (final String name : names) {
516 if (map.containsKey(name)) {
517 return map.get(name);
518 }
519 }
520
521 return Double.NaN;
522
523 }
524
525
526
527
528 public double getMaxChunksDuration() {
529 return maxChunksDuration;
530 }
531
532
533
534
535
536 private void parseFirstHeaderRecord(final byte[] record, final String name) {
537
538
539 final int deNum = extractInt(record, HEADER_EPHEMERIS_TYPE_OFFSET);
540
541
542
543
544 components = 3;
545 positionUnit = UnitsConverter.KILOMETRES_TO_METRES.convert(1.0);
546 timeScale = timeScales.getTDB();
547
548 if (deNum == INPOP_DE_NUMBER) {
549
550 final double format = getLoadedConstant("FORMAT");
551 if (!Double.isNaN(format) && (int) FastMath.IEEEremainder(format, 10) != 1) {
552 components = 6;
553 }
554
555
556 final double unite = getLoadedConstant("UNITE");
557 if (!Double.isNaN(unite) && (int) unite == 0) {
558 positionUnit = getLoadedAstronomicalUnit();
559 }
560
561
562 final double timesc = getLoadedConstant("TIMESC");
563 if (!Double.isNaN(timesc) && (int) timesc == 1) {
564 timeScale = timeScales.getTCB();
565 }
566
567 }
568
569
570 startEpoch = extractDate(record, HEADER_START_EPOCH_OFFSET);
571 finalEpoch = extractDate(record, HEADER_END_EPOCH_OFFSET);
572 boolean ok = finalEpoch.compareTo(startEpoch) > 0;
573
574 if (loadType == EphemerisType.LIBRATION) {
575
576 firstIndex = extractInt(record, HEADER_LIBRATION_INDICES_OFFSET);
577 coeffs = extractInt(record, HEADER_LIBRATION_INDICES_OFFSET + 4);
578 chunks = extractInt(record, HEADER_LIBRATION_INDICES_OFFSET + 8);
579 ok = ok && firstIndex >= 0 && coeffs >= 0 && chunks >= 0;
580 positionUnit = 1.0;
581 }
582 else {
583
584 for (int i = 0; i < 12; ++i) {
585 final int row1 = extractInt(record, HEADER_CHEBISHEV_INDICES_OFFSET + 12 * i);
586 final int row2 = extractInt(record, HEADER_CHEBISHEV_INDICES_OFFSET + 4 + 12 * i);
587 final int row3 = extractInt(record, HEADER_CHEBISHEV_INDICES_OFFSET + 8 + 12 * i);
588 ok = ok && row1 >= 0 && row2 >= 0 && row3 >= 0;
589 if (i == 0 && loadType == EphemerisType.MERCURY ||
590 i == 1 && loadType == EphemerisType.VENUS ||
591 i == 2 && loadType == EphemerisType.EARTH_MOON ||
592 i == 3 && loadType == EphemerisType.MARS ||
593 i == 4 && loadType == EphemerisType.JUPITER ||
594 i == 5 && loadType == EphemerisType.SATURN ||
595 i == 6 && loadType == EphemerisType.URANUS ||
596 i == 7 && loadType == EphemerisType.NEPTUNE ||
597 i == 8 && loadType == EphemerisType.PLUTO ||
598 i == 9 && loadType == EphemerisType.MOON ||
599 i == 10 && loadType == EphemerisType.SUN) {
600 firstIndex = row1;
601 coeffs = row2;
602 chunks = row3;
603 }
604 }
605 }
606
607
608 final double timeSpan = extractDouble(record, HEADER_CHUNK_DURATION_OFFSET);
609 ok = ok && timeSpan > 0 && timeSpan < 100;
610 chunksDuration = new TimeOffset(timeSpan).divide(chunks).multiply(86400L);
611 if (Double.isNaN(maxChunksDuration)) {
612 maxChunksDuration = chunksDuration.toDouble();
613 } else {
614 maxChunksDuration = FastMath
615 .max(maxChunksDuration, chunksDuration.toDouble());
616 }
617
618
619 if (!ok) {
620 throw new OrekitException(OrekitMessages.NOT_A_JPL_EPHEMERIDES_BINARY_FILE, name);
621 }
622
623 }
624
625
626
627
628
629
630
631 private byte[] readFirstRecord(final InputStream input, final String name)
632 throws IOException {
633
634
635 final byte[] firstPart = new byte[HEADER_RECORD_SIZE_OFFSET + 4];
636 if (!readInRecord(input, firstPart, 0)) {
637 throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, name);
638 }
639
640
641 detectEndianess(firstPart);
642
643
644 final int deNum = extractInt(firstPart, HEADER_EPHEMERIS_TYPE_OFFSET);
645
646
647 final int recordSize;
648
649 if (deNum == INPOP_DE_NUMBER) {
650
651 recordSize = extractInt(firstPart, HEADER_RECORD_SIZE_OFFSET) << 3;
652 } else {
653
654 recordSize = computeRecordSize(firstPart, name);
655 }
656
657 if (recordSize <= 0) {
658 throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, name);
659 }
660
661
662 final int start = firstPart.length;
663 final byte[] record = new byte[recordSize];
664 System.arraycopy(firstPart, 0, record, 0, firstPart.length);
665 if (!readInRecord(input, record, start)) {
666 throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, name);
667 }
668
669 return record;
670
671 }
672
673
674
675
676
677
678 private Map<String, Double> parseConstants(final byte[] first, final byte[] second) {
679
680 final Map<String, Double> map = new HashMap<>();
681
682 for (int i = 0; i < CONSTANTS_MAX_NUMBER; ++i) {
683
684
685 final String constantName = extractString(first, HEADER_CONSTANTS_NAMES_OFFSET + i * 6, 6);
686 if (constantName.length() == 0) {
687
688 break;
689 }
690 final double constantValue = extractDouble(second, HEADER_CONSTANTS_VALUES_OFFSET + 8 * i);
691 map.put(constantName, constantValue);
692 }
693
694
695
696 if (!map.containsKey(CONSTANT_AU)) {
697 map.put(CONSTANT_AU, extractDouble(first, HEADER_ASTRONOMICAL_UNIT_OFFSET));
698 }
699
700 if (!map.containsKey(CONSTANT_EMRAT)) {
701 map.put(CONSTANT_EMRAT, extractDouble(first, HEADER_EM_RATIO_OFFSET));
702 }
703
704 return map;
705
706 }
707
708
709
710
711
712
713
714
715 private boolean readInRecord(final InputStream input, final byte[] record, final int start)
716 throws IOException {
717 int index = start;
718 while (index != record.length) {
719 final int n = input.read(record, index, record.length - index);
720 if (n < 0) {
721 return false;
722 }
723 index += n;
724 }
725 return true;
726 }
727
728
729
730
731
732 private void detectEndianess(final byte[] record) {
733
734
735 bigEndian = true;
736
737
738
739 final long deNum = extractInt(record, HEADER_EPHEMERIS_TYPE_OFFSET) & 0xffffffffL;
740
741
742
743 if (deNum > (1 << 15)) {
744 bigEndian = false;
745 }
746
747 }
748
749
750
751
752
753
754 private int computeRecordSize(final byte[] record, final String name) {
755
756 int recordSize = 0;
757 boolean ok = true;
758
759 final int nComp = 3;
760
761
762
763 for (int j = 0; j < 12; j++) {
764 final int nCompCur = (j == 11) ? 2 : nComp;
765
766
767 final int idx = HEADER_CHEBISHEV_INDICES_OFFSET + j * nComp * 4;
768 final int coeffPtr1 = extractInt(record, idx + 4);
769 final int coeffPtr2 = extractInt(record, idx + 8);
770
771
772 ok = ok && (coeffPtr1 >= 0 || coeffPtr2 >= 0);
773
774 recordSize += coeffPtr1 * coeffPtr2 * nCompCur;
775 }
776
777
778
779 final int libratPtr1 = extractInt(record, HEADER_LIBRATION_INDICES_OFFSET + 4);
780 final int libratPtr2 = extractInt(record, HEADER_LIBRATION_INDICES_OFFSET + 8);
781
782
783 ok = ok && (libratPtr1 >= 0 || libratPtr2 >= 0);
784
785 recordSize += libratPtr1 * libratPtr2 * nComp + 2;
786 recordSize <<= 3;
787
788 if (!ok || recordSize <= 0) {
789 throw new OrekitException(OrekitMessages.NOT_A_JPL_EPHEMERIDES_BINARY_FILE, name);
790 }
791
792 return recordSize;
793
794 }
795
796
797
798
799
800
801 private AbsoluteDate extractDate(final byte[] record, final int offset) {
802
803 final double t = extractDouble(record, offset);
804 int jDay = (int) FastMath.floor(t);
805 double seconds = (t + 0.5 - jDay) * Constants.JULIAN_DAY;
806 if (seconds >= Constants.JULIAN_DAY) {
807 ++jDay;
808 seconds -= Constants.JULIAN_DAY;
809 }
810 return new AbsoluteDate(new DateComponents(DateComponents.JULIAN_EPOCH, jDay),
811 new TimeComponents(seconds), timeScale);
812 }
813
814
815
816
817
818
819
820
821 private double extractDouble(final byte[] record, final int offset) {
822 final long l8 = ((long) record[offset + 0]) & 0xffl;
823 final long l7 = ((long) record[offset + 1]) & 0xffl;
824 final long l6 = ((long) record[offset + 2]) & 0xffl;
825 final long l5 = ((long) record[offset + 3]) & 0xffl;
826 final long l4 = ((long) record[offset + 4]) & 0xffl;
827 final long l3 = ((long) record[offset + 5]) & 0xffl;
828 final long l2 = ((long) record[offset + 6]) & 0xffl;
829 final long l1 = ((long) record[offset + 7]) & 0xffl;
830 final long l;
831 if (bigEndian) {
832 l = (l8 << 56) | (l7 << 48) | (l6 << 40) | (l5 << 32) |
833 (l4 << 24) | (l3 << 16) | (l2 << 8) | l1;
834 } else {
835 l = (l1 << 56) | (l2 << 48) | (l3 << 40) | (l4 << 32) |
836 (l5 << 24) | (l6 << 16) | (l7 << 8) | l8;
837 }
838 return Double.longBitsToDouble(l);
839 }
840
841
842
843
844
845
846 private int extractInt(final byte[] record, final int offset) {
847 final int l4 = ((int) record[offset + 0]) & 0xff;
848 final int l3 = ((int) record[offset + 1]) & 0xff;
849 final int l2 = ((int) record[offset + 2]) & 0xff;
850 final int l1 = ((int) record[offset + 3]) & 0xff;
851
852 if (bigEndian) {
853 return (l4 << 24) | (l3 << 16) | (l2 << 8) | l1;
854 } else {
855 return (l1 << 24) | (l2 << 16) | (l3 << 8) | l4;
856 }
857 }
858
859
860
861
862
863
864
865 private String extractString(final byte[] record, final int offset, final int length) {
866 return new String(record, offset, length, StandardCharsets.US_ASCII).trim();
867 }
868
869
870 private class ConstantsParser implements DataLoader {
871
872
873 private Map<String, Double> localConstants;
874
875
876
877
878 public Map<String, Double> getConstants() {
879 return localConstants;
880 }
881
882
883 public boolean stillAcceptsData() {
884 return localConstants == null;
885 }
886
887
888 public void loadData(final InputStream input, final String name)
889 throws IOException, ParseException, OrekitException {
890
891
892 final byte[] first = readFirstRecord(input, name);
893
894
895 final byte[] second = new byte[first.length];
896 if (!readInRecord(input, second, 0)) {
897 throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, name);
898 }
899
900 localConstants = parseConstants(first, second);
901
902 }
903
904 }
905
906
907 private class EphemerisParser implements DataLoader, TimeStampedGenerator<PosVelChebyshev> {
908
909
910 private final SortedSet<PosVelChebyshev> entries;
911
912
913 private AbsoluteDate start;
914
915
916 private AbsoluteDate end;
917
918
919
920 EphemerisParser() {
921 entries = new TreeSet<>(new ChronologicalComparator());
922 }
923
924
925 public List<PosVelChebyshev> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {
926 try {
927
928
929 entries.clear();
930 if (existingDate == null) {
931
932 start = date.shiftedBy(FIFTY_DAYS.negate());
933 end = date.shiftedBy(FIFTY_DAYS);
934 } else if (existingDate.compareTo(date) <= 0) {
935
936 start = existingDate;
937 end = date;
938 } else {
939
940 start = date;
941 end = existingDate;
942 }
943
944
945 if (!feed(this)) {
946 throw new OrekitException(OrekitMessages.NO_JPL_EPHEMERIDES_BINARY_FILES_FOUND);
947 }
948
949 return new ArrayList<>(entries);
950
951 } catch (OrekitException oe) {
952 throw new TimeStampedCacheException(oe);
953 }
954 }
955
956
957 public boolean stillAcceptsData() {
958
959
960 if (generateType == EphemerisType.EARTH) {
961 return false;
962 }
963
964
965
966 if (entries.isEmpty()) {
967 return true;
968 } else {
969
970 return !(entries.first().getDate().compareTo(start) < 0 &&
971 entries.last().getDate().compareTo(end) > 0);
972 }
973
974 }
975
976
977 public void loadData(final InputStream input, final String name)
978 throws IOException {
979
980
981 final byte[] first = readFirstRecord(input, name);
982
983
984 final byte[] second = new byte[first.length];
985 if (!readInRecord(input, second, 0)) {
986 throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, name);
987 }
988
989 if (constants.get() == null) {
990 constants.compareAndSet(null, parseConstants(first, second));
991 }
992
993
994 final double au = 1000 * extractDouble(first, HEADER_ASTRONOMICAL_UNIT_OFFSET);
995 if (au < 1.4e11 || au > 1.6e11) {
996 throw new OrekitException(OrekitMessages.NOT_A_JPL_EPHEMERIDES_BINARY_FILE, name);
997 }
998 if (FastMath.abs(getLoadedAstronomicalUnit() - au) >= 10.0) {
999 throw new OrekitException(OrekitMessages.INCONSISTENT_ASTRONOMICAL_UNIT_IN_FILES,
1000 getLoadedAstronomicalUnit(), au);
1001 }
1002
1003
1004 final double emRat = extractDouble(first, HEADER_EM_RATIO_OFFSET);
1005 if (emRat < 80 || emRat > 82) {
1006 throw new OrekitException(OrekitMessages.NOT_A_JPL_EPHEMERIDES_BINARY_FILE, name);
1007 }
1008 if (FastMath.abs(getLoadedEarthMoonMassRatio() - emRat) >= 1.0e-5) {
1009 throw new OrekitException(OrekitMessages.INCONSISTENT_EARTH_MOON_RATIO_IN_FILES,
1010 getLoadedEarthMoonMassRatio(), emRat);
1011 }
1012
1013
1014 parseFirstHeaderRecord(first, name);
1015
1016 if (startEpoch.compareTo(end) < 0 && finalEpoch.compareTo(start) > 0) {
1017
1018 final byte[] record = new byte[first.length];
1019 while (readInRecord(input, record, 0)) {
1020 final AbsoluteDate rangeStart = parseDataRecord(record);
1021 if (rangeStart.compareTo(end) > 0) {
1022
1023
1024 return;
1025 }
1026 }
1027 }
1028
1029 }
1030
1031
1032
1033
1034
1035 private AbsoluteDate parseDataRecord(final byte[] record) {
1036
1037
1038 final AbsoluteDate rangeStart = extractDate(record, DATA_START_RANGE_OFFSET);
1039 if (rangeStart.compareTo(startEpoch) < 0) {
1040 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE,
1041 rangeStart, startEpoch, finalEpoch, startEpoch.durationFrom(rangeStart));
1042 }
1043
1044 final AbsoluteDate rangeEnd = extractDate(record, DATE_END_RANGE_OFFSET);
1045 if (rangeEnd.compareTo(finalEpoch) > 0) {
1046 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_AFTER,
1047 rangeEnd, startEpoch, finalEpoch, rangeEnd.durationFrom(finalEpoch));
1048 }
1049
1050 if (rangeStart.compareTo(end) > 0 || rangeEnd.compareTo(start) < 0) {
1051
1052 return rangeEnd;
1053 }
1054
1055
1056 AbsoluteDate chunkEnd = rangeStart;
1057 final int nbChunks = chunks;
1058 final int nbCoeffs = coeffs;
1059 final int first = firstIndex;
1060 final TimeOffset duration = chunksDuration;
1061 for (int i = 0; i < nbChunks; ++i) {
1062
1063
1064 final AbsoluteDate chunkStart = chunkEnd;
1065 if (i == nbChunks - 1) {
1066 chunkEnd = rangeEnd;
1067 } else {
1068 chunkEnd = new AbsoluteDate(
1069 rangeStart,
1070 duration.multiply(i + 1),
1071 timeScale);
1072 }
1073
1074
1075
1076 final double[] xCoeffs = new double[nbCoeffs];
1077 final double[] yCoeffs = new double[nbCoeffs];
1078 final double[] zCoeffs = new double[nbCoeffs];
1079
1080 for (int k = 0; k < nbCoeffs; ++k) {
1081
1082
1083 final int index = first + components * i * nbCoeffs + k - 1;
1084 xCoeffs[k] = positionUnit * extractDouble(record, 8 * index);
1085 yCoeffs[k] = positionUnit * extractDouble(record, 8 * (index + nbCoeffs));
1086 zCoeffs[k] = positionUnit * extractDouble(record, 8 * (index + 2 * nbCoeffs));
1087 }
1088
1089
1090 entries.add(new PosVelChebyshev(chunkStart, timeScale,
1091 duration.toDouble(), xCoeffs, yCoeffs, zCoeffs));
1092
1093 }
1094
1095 return rangeStart;
1096
1097 }
1098
1099 }
1100
1101
1102 private class EphemerisRawPVProvider implements RawPVProvider {
1103
1104
1105
1106
1107
1108
1109 private PosVelChebyshev getChebyshev(final AbsoluteDate date) {
1110 PosVelChebyshev chebyshev;
1111 try {
1112 chebyshev = ephemerides.getNeighbors(date).findFirst().get();
1113 } catch (TimeStampedCacheException tce) {
1114
1115 chebyshev = ephemerides.getLatest();
1116 if (!chebyshev.inRange(date)) {
1117
1118 throw tce;
1119 }
1120 }
1121 return chebyshev;
1122 }
1123
1124
1125 public PVCoordinates getRawPV(final AbsoluteDate date) {
1126
1127
1128 final PosVelChebyshev chebyshev = getChebyshev(date);
1129
1130
1131 return chebyshev.getPositionVelocityAcceleration(date);
1132
1133 }
1134
1135
1136 public <T extends CalculusFieldElement<T>> FieldPVCoordinates<T> getRawPV(final FieldAbsoluteDate<T> date) {
1137
1138
1139 final PosVelChebyshev chebyshev = getChebyshev(date.toAbsoluteDate());
1140
1141
1142 return chebyshev.getPositionVelocityAcceleration(date);
1143
1144 }
1145
1146
1147 @Override
1148 public Vector3D getRawPosition(final AbsoluteDate date) {
1149
1150 final PosVelChebyshev chebyshev = getChebyshev(date);
1151
1152
1153 return chebyshev.getPosition(date);
1154 }
1155
1156
1157 @Override
1158 public <T extends CalculusFieldElement<T>> FieldVector3D<T> getRawPosition(final FieldAbsoluteDate<T> date) {
1159
1160 final PosVelChebyshev chebyshev = getChebyshev(date.toAbsoluteDate());
1161
1162
1163 return chebyshev.getPosition(date);
1164 }
1165
1166 }
1167
1168
1169 static class ZeroRawPVProvider implements RawPVProvider {
1170
1171
1172 public PVCoordinates getRawPV(final AbsoluteDate date) {
1173 return PVCoordinates.ZERO;
1174 }
1175
1176
1177 public <T extends CalculusFieldElement<T>> FieldPVCoordinates<T> getRawPV(final FieldAbsoluteDate<T> date) {
1178 return FieldPVCoordinates.getZero(date.getField());
1179 }
1180
1181 }
1182
1183 }