1   /* Copyright 2002-2022 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 &#38; 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 &#38; 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 }