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