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