1 /* Copyright 2002-2021 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.files.ilrs;
18
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.SortedSet;
23 import java.util.TreeSet;
24 import java.util.stream.Collectors;
25
26 import org.orekit.time.AbsoluteDate;
27 import org.orekit.time.ChronologicalComparator;
28 import org.orekit.time.TimeStamped;
29 import org.orekit.utils.ImmutableTimeStampedCache;
30
31 /**
32 * This class stores all the information of the Consolidated laser ranging Data Format (CRD) parsed
33 * by CRDParser. It contains the header and a list of data records.
34 * @author Bryan Cazabonne
35 * @since 10.3
36 */
37 public class CRD {
38
39 /** List of comments contained in the file. */
40 private List<String> comments;
41
42 /** List of data blocks contain in the CDR file. */
43 private List<CRDDataBlock> dataBlocks;
44
45 /**
46 * Constructor.
47 */
48 public CRD() {
49 // Initialise empty lists
50 this.comments = new ArrayList<>();
51 this.dataBlocks = new ArrayList<>();
52 }
53
54 /**
55 * Add a data block to the current list of data blocks.
56 * @param dataBlock data block to add
57 */
58 public void addDataBlock(final CRDDataBlock dataBlock) {
59 dataBlocks.add(dataBlock);
60 }
61
62 /**
63 * Get the comments contained in the file.
64 * @return the comments contained in the file
65 */
66 public List<String> getComments() {
67 return comments;
68 }
69
70 /**
71 * Get the data blocks contain in the file.
72 * @return the data blocks contain in the file
73 */
74 public List<CRDDataBlock> getDataBlocks() {
75 return Collections.unmodifiableList(dataBlocks);
76 }
77
78 /**
79 * Data block containing a set of data contain in the CRD file.
80 * <p>
81 * A data block consists of a header, configuration data and
82 * recorded data (range, angles, meteorological, etc.).
83 * </p>
84 */
85 public static class CRDDataBlock {
86
87 /** Data block header. */
88 private CRDHeader header;
89
90 /** Configuration record. */
91 private CRDConfiguration configurationRecords;
92
93 /** Range records. */
94 private List<RangeMeasurement> rangeData;
95
96 /** Meteorological records. */
97 private final SortedSet<MeteorologicalMeasurement> meteoData;
98
99 /** Pointing angles records. */
100 private List<AnglesMeasurement> anglesData;
101
102 /**
103 * Constructor.
104 */
105 public CRDDataBlock() {
106 // Initialise empty lists
107 this.rangeData = new ArrayList<>();
108 this.meteoData = new TreeSet<>(new ChronologicalComparator());
109 this.anglesData = new ArrayList<>();
110 }
111
112 /**
113 * Get the header of the current data block.
114 * @return the header of the current data block
115 */
116 public CRDHeader getHeader() {
117 return header;
118 }
119
120 /**
121 * Set the header for the current data block.
122 * @param header the header to set
123 */
124 public void setHeader(final CRDHeader header) {
125 this.header = header;
126 }
127
128 /**
129 * Get the system configuration records.
130 * @return the system configuration records
131 */
132 public CRDConfiguration getConfigurationRecords() {
133 return configurationRecords;
134 }
135
136 /**
137 * Set the configuration records for the current data block.
138 * @param configurationRecords the configuration records to set
139 */
140 public void setConfigurationRecords(final CRDConfiguration configurationRecords) {
141 this.configurationRecords = configurationRecords;
142 }
143
144 /**
145 * Add an entry to the list of range data.
146 * @param range entry to add
147 */
148 public void addRangeData(final RangeMeasurement range) {
149 rangeData.add(range);
150 }
151
152 /**
153 * Add an entry to the list of meteorological data.
154 * @param meteorologicalMeasurement entry to add
155 */
156 public void addMeteoData(final MeteorologicalMeasurement meteorologicalMeasurement) {
157 meteoData.add(meteorologicalMeasurement);
158 }
159
160 /**
161 * Add an entry to the list of angles data.
162 * @param angles entry to add
163 */
164 public void addAnglesData(final AnglesMeasurement angles) {
165 anglesData.add(angles);
166 }
167
168 /**
169 * Get the range data for the data block.
170 * @return an unmodifiable list of range data
171 */
172 public List<RangeMeasurement> getRangeData() {
173 return Collections.unmodifiableList(rangeData);
174 }
175
176 /**
177 * Get the angles data for the data block.
178 * @return an unmodifiable list of angles data
179 */
180 public List<AnglesMeasurement> getAnglesData() {
181 return Collections.unmodifiableList(anglesData);
182 }
183
184 /**
185 * Get the meteorological data for the data block.
186 * @return an unmodifiable list of meteorological data
187 */
188 public Meteo getMeteoData() {
189 return new Meteo(meteoData);
190 }
191
192 }
193
194 /** Range record. */
195 public static class RangeMeasurement implements TimeStamped {
196
197 /** Data epoch. */
198 private AbsoluteDate date;
199
200 /** Time of flight [s]. */
201 private final double timeOfFlight;
202
203 /** Time event reference indicator.
204 * 0 = ground receive time (at SRP) (two-way)
205 * 1 = spacecraft bounce time (two-way)
206 * 2 = ground transmit time (at SRP) (two-way)
207 * 3 = spacecraft receive time (one-way)
208 * 4 = spacecraft transmit time (one-way)
209 * 5 = ground transmit time (at SRP) and spacecraft receive time (one-way)
210 * 6 = spacecraft transmit time and ground receive time (at SRP) (one-way)
211 * Currently, only 1 and 2 are used for laser ranging data.
212 */
213 private final int epochEvent;
214
215 /** Signal to noise ration. */
216 private final double snr;
217
218 /**
219 * Constructor.
220 * @param date data epoch
221 * @param timeOfFlight time of flight in seconds
222 * @param epochEvent indicates the time event reference
223 */
224 public RangeMeasurement(final AbsoluteDate date,
225 final double timeOfFlight,
226 final int epochEvent) {
227 this(date, timeOfFlight, epochEvent, Double.NaN);
228 }
229
230 /**
231 * Constructor.
232 * @param date data epoch
233 * @param timeOfFlight time of flight in seconds
234 * @param epochEvent indicates the time event reference
235 * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
236 */
237 public RangeMeasurement(final AbsoluteDate date,
238 final double timeOfFlight,
239 final int epochEvent, final double snr) {
240 this.date = date;
241 this.timeOfFlight = timeOfFlight;
242 this.epochEvent = epochEvent;
243 this.snr = snr;
244 }
245
246 /**
247 * Get the time-of-flight.
248 * @return the time-of-flight in seconds
249 */
250 public double getTimeOfFlight() {
251 return timeOfFlight;
252 }
253
254 /**
255 * Get the indicator for the time event reference.
256 * <ul>
257 * <li>0 = ground receive time (at SRP) (two-way)</li>
258 * <li>1 = spacecraft bounce time (two-way)</li>
259 * <li>2 = ground transmit time (at SRP) (two-way)</li>
260 * <li>3 = spacecraft receive time (one-way)</li>
261 * <li>4 = spacecraft transmit time (one-way)</li>
262 * <li>5 = ground transmit time (at SRP) and spacecraft receive time (one-way)</li>
263 * <li>6 = spacecraft transmit time and ground receive time (at SRP) (one-way)</li>
264 * </ul>
265 * Currently, only 1 and 2 are used for laser ranging data
266 * @return the indicator for the time event reference
267 */
268 public int getEpochEvent() {
269 return epochEvent;
270 }
271
272 /**
273 * Get the signal to noise ratio.
274 * @return the signal to noise ratio
275 */
276 public double getSnr() {
277 return snr;
278 }
279
280 /** {@inheritDoc} */
281 @Override
282 public AbsoluteDate getDate() {
283 return date;
284 }
285
286 }
287
288 /** This data record contains a minimal set of meteorological data. */
289 public static class MeteorologicalMeasurement implements TimeStamped {
290
291 /** Data epoch. */
292 private AbsoluteDate date;
293
294 /** Surface pressure [bar]. */
295 private final double pressure;
296
297 /** Surface temperature [K]. */
298 private final double temperature;
299
300 /** Relative humidity at the surface [%]. */
301 private final double humidity;
302
303 /**
304 * Constructor.
305 * @param date data epoch
306 * @param pressure the surface pressure in bars
307 * @param temperature the surface temperature in degrees Kelvin
308 * @param humidity the relative humidity at the surface in percents
309 */
310 public MeteorologicalMeasurement(final AbsoluteDate date,
311 final double pressure, final double temperature,
312 final double humidity) {
313 this.date = date;
314 this.pressure = pressure;
315 this.temperature = temperature;
316 this.humidity = humidity;
317 }
318
319 /**
320 * Get the surface pressure.
321 * @return the surface pressure in bars
322 */
323 public double getPressure() {
324 return pressure;
325 }
326
327 /**
328 * Get the surface temperature.
329 * @return the surface temperature in degrees Kelvin
330 */
331 public double getTemperature() {
332 return temperature;
333 }
334
335 /**
336 * Get the relative humidity at the surface.
337 * @return the relative humidity at the surface in percents
338 */
339 public double getHumidity() {
340 return humidity;
341 }
342
343 /** {@inheritDoc} */
344 @Override
345 public AbsoluteDate getDate() {
346 return date;
347 }
348
349 }
350
351 /** Pointing angles record. */
352 public static class AnglesMeasurement implements TimeStamped {
353
354 /** Data epoch. */
355 private AbsoluteDate date;
356
357 /** Azimuth [rad]. */
358 private final double azimuth;
359
360 /** Elevation [rad]. */
361 private final double elevation;
362
363 /** Direction flag (0 = transmit & receive ; 1 = transmit ; 2 = receive). */
364 private final int directionFlag;
365
366 /** Angle origin indicator.
367 * 0 = unknown
368 * 1 = computed
369 * 2 = commanded (from predictions)
370 * 3 = measured (from encoders)
371 */
372 private final int originIndicator;
373
374 /** Refraction corrected. */
375 private final boolean refractionCorrected;
376
377 /** Azimuth rate [rad/sec]. */
378 private final double azimuthRate;
379
380 /** Elevation rate [rad/sec]. */
381 private final double elevationRate;
382
383 /**
384 * Constructor.
385 * @param date data epoch
386 * @param azimuth azimuth angle in radians
387 * @param elevation elevation angle in radians
388 * @param directionFlag direction flag
389 * @param originIndicator angle origin indicator
390 * @param refractionCorrected flag to indicate if the refraction is corrected
391 * @param azimuthRate azimuth rate in radians per second (equal to Double.NaN if unknown)
392 * @param elevationRate elevation rate in radians per second (equal to Double.NaN if unknown)
393 */
394 public AnglesMeasurement(final AbsoluteDate date, final double azimuth,
395 final double elevation, final int directionFlag,
396 final int originIndicator,
397 final boolean refractionCorrected,
398 final double azimuthRate, final double elevationRate) {
399 this.date = date;
400 this.azimuth = azimuth;
401 this.elevation = elevation;
402 this.directionFlag = directionFlag;
403 this.originIndicator = originIndicator;
404 this.refractionCorrected = refractionCorrected;
405 this.azimuthRate = azimuthRate;
406 this.elevationRate = elevationRate;
407 }
408
409 /**
410 * Get the azimuth angle.
411 * @return the azimuth angle in radians
412 */
413 public double getAzimuth() {
414 return azimuth;
415 }
416
417 /**
418 * Get the elevation angle.
419 * @return the elevation angle in radians
420 */
421 public double getElevation() {
422 return elevation;
423 }
424
425 /**
426 * Get the direction flag (0 = transmit & receive ; 1 = transmit ; 2 = receive).
427 * @return the direction flag
428 */
429 public int getDirectionFlag() {
430 return directionFlag;
431 }
432
433 /**
434 * Get the angle origin indicator.
435 * <p>
436 * 0 = unknown;
437 * 1 = computed;
438 * 2 = commanded (from predictions);
439 * 3 = measured (from encoders)
440 * </p>
441 * @return the angle origin indicator
442 */
443 public int getOriginIndicator() {
444 return originIndicator;
445 }
446
447 /**
448 * Get the flag indicating if the refraction is corrected.
449 * @return true if refraction is corrected
450 */
451 public boolean isRefractionCorrected() {
452 return refractionCorrected;
453 }
454
455 /**
456 * Get the azimuth rate.
457 * <p>
458 * Is equal to Double.NaN if the value is unknown.
459 * </p>
460 * @return the azimuth rate in radians per second
461 */
462 public double getAzimuthRate() {
463 return azimuthRate;
464 }
465
466 /**
467 * Get the elevation rate.
468 * <p>
469 * Is equal to Double.NaN if the value is unknown.
470 * </p>
471 * @return the elevation rate in radians per second
472 */
473 public double getElevationRate() {
474 return elevationRate;
475 }
476
477 /** {@inheritDoc} */
478 @Override
479 public AbsoluteDate getDate() {
480 return date;
481 }
482
483 }
484
485 /** Meteorological data. */
486 public static class Meteo {
487
488 /** Number of neighbors for meteo data interpolation. */
489 private static final int N_NEIGHBORS = 2;
490
491 /** First available date. */
492 private final AbsoluteDate firstDate;
493
494 /** Last available date. */
495 private final AbsoluteDate lastDate;
496
497 /** Previous set of meteorological parameters. */
498 private transient MeteorologicalMeasurement previousParam;
499
500 /** Next set of solar meteorological parameters. */
501 private transient MeteorologicalMeasurement nextParam;
502
503 /** List of meteo data. */
504 private final transient ImmutableTimeStampedCache<MeteorologicalMeasurement> meteo;
505
506 /**
507 * Constructor.
508 * @param meteoData list of meteo data
509 */
510 public Meteo(final SortedSet<MeteorologicalMeasurement> meteoData) {
511
512 // Size
513 final int neighborsSize = (meteoData.size() < 2) ? meteoData.size() : N_NEIGHBORS;
514
515 // Check neighbors size
516 if (neighborsSize == 0) {
517
518 // Meteo data -> empty cache
519 this.meteo = ImmutableTimeStampedCache.emptyCache();
520
521 // Null epochs (will ne be used)
522 this.firstDate = null;
523 this.lastDate = null;
524
525 } else {
526
527 // Meteo data
528 this.meteo = new ImmutableTimeStampedCache<MeteorologicalMeasurement>(neighborsSize, meteoData);
529
530 // Initialize first and last available dates
531 this.firstDate = meteoData.first().getDate();
532 this.lastDate = meteoData.last().getDate();
533
534 }
535
536 }
537
538 /** Get an unmodifiable view of the tabulated meteorological data.
539 * @return unmodifiable view of the tabulated meteorological data
540 * @since 11.0
541 */
542 public List<MeteorologicalMeasurement> getData() {
543 return meteo.getAll();
544 }
545
546 /**
547 * Get the meteorological parameters at a given date.
548 * @param date date when user wants the meteorological parameters
549 * @return the meteorological parameters at date (can be null if
550 * meteorological data are empty).
551 */
552 public MeteorologicalMeasurement getMeteo(final AbsoluteDate date) {
553
554 // Check if meteorological data are available
555 if (meteo.getNeighborsSize() == 0) {
556 return null;
557 }
558
559 // Interpolating two neighboring meteorological parameters
560 bracketDate(date);
561 if (date.durationFrom(firstDate) <= 0 || date.durationFrom(lastDate) > 0) {
562 // Date is outside file range
563 return previousParam;
564 } else {
565 // Perform interpolations
566 final double pressure = getLinearInterpolation(date, previousParam.getPressure(), nextParam.getPressure());
567 final double temperature = getLinearInterpolation(date, previousParam.getTemperature(), nextParam.getTemperature());
568 final double humidity = getLinearInterpolation(date, previousParam.getHumidity(), nextParam.getHumidity());
569 return new MeteorologicalMeasurement(date, pressure, temperature, humidity);
570 }
571
572 }
573
574 /**
575 * Find the data bracketing a specified date.
576 * @param date date to bracket
577 */
578 private void bracketDate(final AbsoluteDate date) {
579
580 // don't search if the cached selection is fine
581 if (previousParam != null &&
582 date.durationFrom(previousParam.getDate()) > 0 &&
583 date.durationFrom(nextParam.getDate()) <= 0) {
584 return;
585 }
586
587 // Initialize previous and next parameters
588 if (date.durationFrom(firstDate) <= 0) {
589 // Current date is before the first date
590 previousParam = meteo.getEarliest();
591 nextParam = previousParam;
592 } else if (date.durationFrom(lastDate) > 0) {
593 // Current date is after the last date
594 previousParam = meteo.getLatest();
595 nextParam = previousParam;
596 } else {
597 // Current date is between first and last date
598 final List<MeteorologicalMeasurement> neighbors = meteo.getNeighbors(date).collect(Collectors.toList());
599 previousParam = neighbors.get(0);
600 nextParam = neighbors.get(1);
601 }
602
603 }
604
605 /**
606 * Performs a linear interpolation between two values The weights are computed
607 * from the time delta between previous date, current date, next date.
608 * @param date the current date
609 * @param previousValue the value at previous date
610 * @param nextValue the value at next date
611 * @return the value interpolated for the current date
612 */
613 private double getLinearInterpolation(final AbsoluteDate date,
614 final double previousValue,
615 final double nextValue) {
616 // Perform a linear interpolation
617 final AbsoluteDate previousDate = previousParam.getDate();
618 final AbsoluteDate currentDate = nextParam.getDate();
619 final double dt = currentDate.durationFrom(previousDate);
620 final double previousWeight = currentDate.durationFrom(date) / dt;
621 final double nextWeight = date.durationFrom(previousDate) / dt;
622
623 // Returns the data interpolated at the date
624 return previousValue * previousWeight + nextValue * nextWeight;
625 }
626
627 }
628
629 }