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