1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.files.sp3;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.SortedSet;
27 import java.util.TreeSet;
28
29 import org.hipparchus.geometry.euclidean.threed.Vector3D;
30 import org.hipparchus.util.FastMath;
31 import org.hipparchus.util.Precision;
32 import org.orekit.errors.OrekitException;
33 import org.orekit.errors.OrekitMessages;
34 import org.orekit.files.general.EphemerisFile;
35 import org.orekit.frames.Frame;
36 import org.orekit.frames.Transform;
37 import org.orekit.gnss.IGSUtils;
38 import org.orekit.time.AbsoluteDate;
39 import org.orekit.time.ChronologicalComparator;
40 import org.orekit.utils.PVCoordinates;
41
42
43
44
45
46
47 public class SP3 implements EphemerisFile<SP3Coordinate, SP3Segment> {
48
49
50
51
52 private final SP3Header header;
53
54
55 private final double mu;
56
57
58 private final int interpolationSamples;
59
60
61 private final Frame frame;
62
63
64 private final Map<String, SP3Ephemeris> satellites;
65
66
67
68
69
70
71
72
73 public SP3(final double mu, final int interpolationSamples, final Frame frame) {
74 this(new SP3Header(), mu, interpolationSamples, frame);
75 }
76
77
78
79
80
81
82
83
84
85
86 public SP3(final SP3Header header,
87 final double mu, final int interpolationSamples, final Frame frame) {
88 this.header = header;
89 this.mu = mu;
90 this.interpolationSamples = interpolationSamples;
91 this.frame = frame;
92 this.satellites = new LinkedHashMap<>();
93 }
94
95
96
97
98
99
100
101
102 public void validate(final boolean parsing, final String fileName) throws OrekitException {
103
104
105 final SortedSet<AbsoluteDate> epochs = new TreeSet<>(new ChronologicalComparator());
106 boolean hasAccuracy = false;
107 for (final Map.Entry<String, SP3Ephemeris> entry : satellites.entrySet()) {
108 SP3Coordinate previous = null;
109 for (final SP3Segment segment : entry.getValue().getSegments()) {
110 for (final SP3Coordinate coordinate : segment.getCoordinates()) {
111 final AbsoluteDate previousDate = previous == null ? header.getEpoch() : previous.getDate();
112 final double nbSteps = coordinate.getDate().durationFrom(previousDate) / header.getEpochInterval();
113 if (FastMath.abs(nbSteps - FastMath.rint(nbSteps)) > 0.001) {
114
115 throw new OrekitException(OrekitMessages.INCONSISTENT_SAMPLING_DATE,
116 previousDate.shiftedBy(FastMath.rint(nbSteps) * header.getEpochInterval()),
117 coordinate.getDate());
118 }
119 epochs.add(coordinate.getDate());
120 previous = coordinate;
121 hasAccuracy |= !(coordinate.getPositionAccuracy() == null &&
122 coordinate.getVelocityAccuracy() == null &&
123 Double.isNaN(coordinate.getClockAccuracy()) &&
124 Double.isNaN(coordinate.getClockRateAccuracy()));
125 }
126 }
127 }
128
129
130 if (getSatelliteCount() > getMaxAllowedSatCount(parsing)) {
131 throw new OrekitException(OrekitMessages.SP3_TOO_MANY_SATELLITES_FOR_VERSION,
132 header.getVersion(), getMaxAllowedSatCount(parsing), getSatelliteCount(),
133 fileName);
134 }
135
136 header.validate(parsing, hasAccuracy, fileName);
137
138
139 if (epochs.size() != header.getNumberOfEpochs()) {
140 throw new OrekitException(OrekitMessages.SP3_NUMBER_OF_EPOCH_MISMATCH,
141 epochs.size(), fileName, header.getNumberOfEpochs());
142 }
143
144 }
145
146
147
148
149
150 public SP3Header getHeader() {
151 return header;
152 }
153
154
155
156
157
158
159
160
161 private int getMaxAllowedSatCount(final boolean parsing) {
162 return header.getVersion() < 'd' ? (parsing ? 99 : 85) : 999;
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 public static SP3 splice(final Collection<SP3> sp3) {
196
197
198 final ChronologicalComparator comparator = new ChronologicalComparator();
199 final SortedSet<SP3> sorted = new TreeSet<>((s1, s2) -> comparator.compare(s1.header.getEpoch(), s2.header.getEpoch()));
200 sorted.addAll(sp3);
201
202
203 final SP3 first = sorted.first();
204 final SP3 spliced = new SP3(first.mu, first.interpolationSamples, first.frame);
205 spliced.header.setVersion(first.header.getVersion());
206 spliced.header.setFilter(first.header.getFilter());
207 spliced.header.setType(first.header.getType());
208 spliced.header.setTimeSystem(first.header.getTimeSystem());
209 spliced.header.setDataUsed(first.header.getDataUsed());
210 spliced.header.setEpoch(first.header.getEpoch());
211 spliced.header.setGpsWeek(first.header.getGpsWeek());
212 spliced.header.setSecondsOfWeek(first.header.getSecondsOfWeek());
213 spliced.header.setModifiedJulianDay(first.header.getModifiedJulianDay());
214 spliced.header.setDayFraction(first.header.getDayFraction());
215 spliced.header.setEpochInterval(first.header.getEpochInterval());
216 spliced.header.setCoordinateSystem(first.header.getCoordinateSystem());
217 spliced.header.setOrbitTypeKey(first.header.getOrbitTypeKey());
218 spliced.header.setAgency(first.header.getAgency());
219 spliced.header.setPosVelBase(first.header.getPosVelBase());
220 spliced.header.setClockBase(first.header.getClockBase());
221 first.header.getComments().forEach(spliced.header::addComment);
222
223
224 final List<String> commonSats = new ArrayList<>(first.header.getSatIds());
225 for (final SP3 current : sorted) {
226 for (final Iterator<String> iter = commonSats.iterator(); iter.hasNext();) {
227 final String sat = iter.next();
228 if (!current.containsSatellite(sat)) {
229 iter.remove();
230 break;
231 }
232 }
233 }
234
235
236 for (final String sat : commonSats) {
237 spliced.addSatellite(sat);
238 }
239
240
241 for (int i = 0; i < commonSats.size(); ++i) {
242 final String sat = commonSats.get(i);
243 double accuracy = 0;
244 for (final SP3 current : sorted) {
245 accuracy = FastMath.max(accuracy, current.header.getAccuracy(sat));
246 }
247 spliced.header.setAccuracy(i, accuracy);
248 }
249
250
251 SP3 previous = null;
252 int epochCount = 0;
253 for (final SP3 current : sorted) {
254
255 epochCount += current.header.getNumberOfEpochs();
256 if (previous != null) {
257
258
259 final boolean dropLast = current.checkSplice(previous);
260 if (dropLast) {
261 --epochCount;
262 }
263
264
265 for (final Map.Entry<String, SP3Ephemeris> entry : previous.satellites.entrySet()) {
266 if (commonSats.contains(entry.getKey())) {
267 final SP3Ephemeris splicedEphemeris = spliced.getEphemeris(entry.getKey());
268 for (final SP3Segment segment : entry.getValue().getSegments()) {
269 final List<SP3Coordinate> coordinates = segment.getCoordinates();
270 for (int i = 0; i < coordinates.size() - (dropLast ? 1 : 0); ++i) {
271 splicedEphemeris.addCoordinate(coordinates.get(i), spliced.header.getEpochInterval());
272 }
273 }
274 }
275 }
276
277 }
278
279 previous = current;
280
281 }
282 spliced.header.setNumberOfEpochs(epochCount);
283
284
285 for (final Map.Entry<String, SP3Ephemeris> entry : previous.satellites.entrySet()) {
286 if (commonSats.contains(entry.getKey())) {
287 final SP3Ephemeris splicedEphemeris = spliced.getEphemeris(entry.getKey());
288 for (final SP3Segment segment : entry.getValue().getSegments()) {
289 for (final SP3Coordinate coordinate : segment.getCoordinates()) {
290 splicedEphemeris.addCoordinate(coordinate, spliced.header.getEpochInterval());
291 }
292 }
293 }
294 }
295
296 return spliced;
297
298 }
299
300
301
302
303
304
305
306 public static SP3 changeFrame(final SP3 original, final Frame newFrame) {
307
308
309 final SP3Header header = new SP3Header();
310 final SP3Header originalHeader = original.header;
311 header.setVersion(originalHeader.getVersion());
312 header.setFilter(originalHeader.getFilter());
313 header.setType(originalHeader.getType());
314 header.setTimeSystem(originalHeader.getTimeSystem());
315 header.setDataUsed(originalHeader.getDataUsed());
316 header.setEpoch(originalHeader.getEpoch());
317 header.setGpsWeek(originalHeader.getGpsWeek());
318 header.setSecondsOfWeek(originalHeader.getSecondsOfWeek());
319 header.setModifiedJulianDay(originalHeader.getModifiedJulianDay());
320 header.setDayFraction(originalHeader.getDayFraction());
321 header.setEpochInterval(originalHeader.getEpochInterval());
322 header.setOrbitTypeKey(originalHeader.getOrbitTypeKey());
323 header.setAgency(originalHeader.getAgency());
324 header.setPosVelBase(originalHeader.getPosVelBase());
325 header.setClockBase(originalHeader.getClockBase());
326 header.setNumberOfEpochs(originalHeader.getNumberOfEpochs());
327
328 originalHeader.getComments().forEach(header::addComment);
329
330
331 header.setCoordinateSystem(IGSUtils.frameName(newFrame));
332
333
334 final SP3 changed = new SP3(header,
335 original.mu,
336 original.interpolationSamples,
337 newFrame);
338
339
340 final List<String> ids = originalHeader.getSatIds();
341 ids.forEach(changed::addSatellite);
342
343
344
345
346 for (int i = 0; i < ids.size(); ++i) {
347 header.setAccuracy(i, originalHeader.getAccuracy(ids.get(i)));
348 }
349
350
351 for (final Map.Entry<String, SP3Ephemeris> entry : original.satellites.entrySet()) {
352 final SP3Ephemeris originalEphemeris = original.getEphemeris(entry.getKey());
353 final SP3Ephemeris changedEphemeris = changed.getEphemeris(entry.getKey());
354 for (final SP3Segment segment : originalEphemeris.getSegments()) {
355 for (SP3Coordinate c : segment.getCoordinates()) {
356 final Transform t = originalEphemeris.getFrame().getTransformTo(newFrame, c.getDate());
357 final PVCoordinates newPV = t.transformPVCoordinates(c);
358 final Vector3D newP = newPV.getPosition();
359 final Vector3D newPA = c.getPositionAccuracy() == null ?
360 null :
361 t.transformVector(c.getPositionAccuracy());
362 final Vector3D newV = c.getVelocity() == null ? Vector3D.ZERO : newPV.getVelocity();
363 final Vector3D newVA = c.getVelocityAccuracy() == null ?
364 null :
365 t.transformVector(c.getVelocityAccuracy());
366 final SP3Coordinate newC = new SP3Coordinate(c.getDate(),
367 newP, newPA, newV, newVA,
368 c.getClockCorrection(),
369 c.getClockAccuracy(),
370 c.getClockRateChange(),
371 c.getClockRateAccuracy(),
372 c.hasClockEvent(),
373 c.hasClockPrediction(),
374 c.hasOrbitManeuverEvent(),
375 c.hasOrbitPrediction());
376 changedEphemeris.addCoordinate(newC, originalHeader.getEpochInterval());
377 }
378 }
379 }
380
381 return changed;
382
383 }
384
385
386
387
388
389
390
391
392 private boolean checkSplice(final SP3 previous) throws OrekitException {
393
394 if (!(previous.header.getType() == header.getType() &&
395 previous.header.getTimeSystem() == header.getTimeSystem() &&
396 previous.header.getOrbitType() == header.getOrbitType() &&
397 previous.header.getCoordinateSystem().equals(header.getCoordinateSystem()) &&
398 previous.header.getDataUsed().equals(header.getDataUsed()) &&
399 previous.header.getAgency().equals(header.getAgency()))) {
400 throw new OrekitException(OrekitMessages.SP3_INCOMPATIBLE_FILE_METADATA);
401 }
402
403 boolean dropLast = false;
404 for (final Map.Entry<String, SP3Ephemeris> entry : previous.satellites.entrySet()) {
405 final SP3Ephemeris previousEphem = entry.getValue();
406 final SP3Ephemeris currentEphem = satellites.get(entry.getKey());
407 if (currentEphem != null) {
408 if (!(previousEphem.getAvailableDerivatives() == currentEphem.getAvailableDerivatives() &&
409 previousEphem.getFrame() == currentEphem.getFrame() &&
410 previousEphem.getInterpolationSamples() == currentEphem.getInterpolationSamples() &&
411 Precision.equals(previousEphem.getMu(), currentEphem.getMu(), 2))) {
412 throw new OrekitException(OrekitMessages.SP3_INCOMPATIBLE_SATELLITE_MEDATADA,
413 entry.getKey());
414 } else {
415 final double dt = currentEphem.getStart().durationFrom(previousEphem.getStop());
416 dropLast = dt < 0.001 * header.getEpochInterval();
417 }
418 }
419 }
420
421 return dropLast;
422
423 }
424
425
426
427
428
429 public void addSatellite(final String satId) {
430 header.addSatId(satId);
431 satellites.putIfAbsent(satId, new SP3Ephemeris(satId, mu, frame, interpolationSamples, header.getFilter()));
432 }
433
434 @Override
435 public Map<String, SP3Ephemeris> getSatellites() {
436 return Collections.unmodifiableMap(satellites);
437 }
438
439
440
441
442
443
444 public SP3Ephemeris getEphemeris(final int index) {
445 int n = index;
446 for (final Map.Entry<String, SP3Ephemeris> entry : satellites.entrySet()) {
447 if (n == 0) {
448 return entry.getValue();
449 }
450 n--;
451 }
452
453
454 throw new OrekitException(OrekitMessages.INVALID_SATELLITE_ID, index);
455
456 }
457
458
459
460
461
462
463 public SP3Ephemeris getEphemeris(final String satId) {
464 final SP3Ephemeris ephemeris = satellites.get(satId);
465 if (ephemeris == null) {
466 throw new OrekitException(OrekitMessages.INVALID_SATELLITE_ID, satId);
467 } else {
468 return ephemeris;
469 }
470 }
471
472
473
474
475 public int getSatelliteCount() {
476 return satellites.size();
477 }
478
479
480
481
482
483
484
485 public boolean containsSatellite(final String satId) {
486 return header.getSatIds().contains(satId);
487 }
488
489 }