1   /* Copyright 2002-2026 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.Locale;
23  import java.util.SortedSet;
24  import java.util.TreeSet;
25  import java.util.regex.Pattern;
26  
27  import org.hipparchus.util.FastMath;
28  import org.orekit.annotation.DefaultDataContext;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.ChronologicalComparator;
31  import org.orekit.time.TimeScalesFactory;
32  import org.orekit.time.TimeStamped;
33  import org.orekit.utils.ImmutableTimeStampedCache;
34  
35  /**
36   * This class stores all the information of the Consolidated laser ranging Data Format (CRD) parsed
37   * by CRDParser. It contains the header and a list of data records.
38   * @author Bryan Cazabonne
39   * @author Rongwang Li
40   * @since 10.3
41   */
42  public class CRD {
43  
44      /** Value of 'not available' or 'not applicable' or 'no information'. */
45      public static final String STR_VALUE_NOT_AVAILABLE = "na";
46  
47      /** String of "NaN". */
48      public static final String STR_NAN = "NaN";
49  
50      /** Pattern of "NaN". */
51      public static final Pattern PATTERN_NAN = Pattern.compile(STR_NAN);
52  
53      /** List of comments contained in the file. */
54      private final List<String> comments;
55  
56      /** List of data blocks contain in the CDR file. */
57      private final List<CRDDataBlock> dataBlocks;
58  
59      /**
60       * Constructor.
61       */
62      public CRD() {
63          // Initialise empty lists
64          this.comments   = new ArrayList<>();
65          this.dataBlocks = new ArrayList<>();
66      }
67  
68      /**
69       * Format the integer value as a string, or the string <code>VALUE_NOT_AVAILABLE</code>.
70       * @param value the value
71       * @param valueNotAvailable the value means not available
72       * @return a string
73       * @since 12.0
74       */
75      public static String formatIntegerOrNaN(final int value, final int valueNotAvailable) {
76          return value == valueNotAvailable ? STR_VALUE_NOT_AVAILABLE : String.format(Locale.US, "%d", value);
77      }
78  
79      /**
80       * Replace all " NaN" with " na".
81       * @param crdString the original string
82       * @return the string
83       * @since 12.0
84       */
85      public static String handleNaN(final String crdString) {
86          return PATTERN_NAN.matcher(crdString).replaceAll(STR_VALUE_NOT_AVAILABLE);
87      }
88  
89      /**
90       * Add a data block to the current list of data blocks.
91       * @param dataBlock data block to add
92       */
93      public void addDataBlock(final CRDDataBlock dataBlock) {
94          dataBlocks.add(dataBlock);
95      }
96  
97      /**
98       * Get the comments contained in the file.
99       * @return the comments contained in the file
100      */
101     public List<String> getComments() {
102         return comments;
103     }
104 
105     /**
106      * Get the data blocks contain in the file.
107      * @return the data blocks contain in the file
108      */
109     public List<CRDDataBlock> getDataBlocks() {
110         return Collections.unmodifiableList(dataBlocks);
111     }
112 
113     /**
114      * Data block containing a set of data contain in the CRD file.
115      * <p>
116      * A data block consists of a header, configuration data and
117      * recorded data (range, angles, meteorological, etc.).
118      * </p>
119      */
120     public static class CRDDataBlock {
121 
122         /** Data block header. */
123         private CRDHeader header;
124 
125         /** Configuration record. */
126         private CRDConfiguration configurationRecords;
127 
128         /** Range records. */
129         private final List<RangeMeasurement> rangeData;
130 
131         /** Meteorological records. */
132         private final SortedSet<MeteorologicalMeasurement> meteoData;
133 
134         /** Pointing angles records. */
135         private final List<AnglesMeasurement> anglesData;
136 
137         /** RangeSupplement records. */
138         private final List<RangeSupplement> rangeSupplementData;
139 
140         /** Session statistics record(s). */
141         private final List<SessionStatistics> sessionStatisticsData;
142 
143         /** Calibration Record(s). */
144         private final List<Calibration> calibrationData;
145 
146         /** Calibration detail record(s). */
147         private final List<CalibrationDetail> calibrationDetailData;
148 
149         /**
150          * Constructor.
151          */
152         public CRDDataBlock() {
153             // Initialise empty lists
154             this.rangeData  = new ArrayList<>();
155             this.meteoData  = new TreeSet<>(new ChronologicalComparator());
156             this.anglesData = new ArrayList<>();
157             this.rangeSupplementData = new ArrayList<>();
158             this.sessionStatisticsData = new ArrayList<>();
159             this.calibrationData = new ArrayList<>();
160             this.calibrationDetailData = new ArrayList<>();
161         }
162 
163         /**
164          * Get the header of the current data block.
165          * @return the header of the current data block
166          */
167         public CRDHeader getHeader() {
168             return header;
169         }
170 
171         /**
172          * Set the header for the current data block.
173          * @param header the header to set
174          */
175         public void setHeader(final CRDHeader header) {
176             this.header = header;
177         }
178 
179         /**
180          * Get the system configuration records.
181          * @return the system configuration records
182          */
183         public CRDConfiguration getConfigurationRecords() {
184             return configurationRecords;
185         }
186 
187         /**
188          * Set the configuration records for the current data block.
189          * @param configurationRecords the configuration records to set
190          */
191         public void setConfigurationRecords(final CRDConfiguration configurationRecords) {
192             this.configurationRecords = configurationRecords;
193         }
194 
195         /**
196          * Add an entry to the list of range data.
197          * @param range entry to add
198          */
199         public void addRangeData(final RangeMeasurement range) {
200             rangeData.add(range);
201         }
202 
203         /**
204          * Add an entry to the list of meteorological data.
205          * @param meteorologicalMeasurement entry to add
206          */
207         public void addMeteoData(final MeteorologicalMeasurement meteorologicalMeasurement) {
208             meteoData.add(meteorologicalMeasurement);
209         }
210 
211         /**
212          * Add an entry to the list of angles data.
213          * @param angles entry to add
214          */
215         public void addAnglesData(final AnglesMeasurement angles) {
216             anglesData.add(angles);
217         }
218 
219         /**
220          * Get the range data for the data block.
221          * @return an unmodifiable list of range data
222          */
223         public List<RangeMeasurement> getRangeData() {
224             return Collections.unmodifiableList(rangeData);
225         }
226 
227         /**
228          * Get the angles data for the data block.
229          * @return an unmodifiable list of angles data
230          */
231         public List<AnglesMeasurement> getAnglesData() {
232             return Collections.unmodifiableList(anglesData);
233         }
234 
235         /**
236          * Get the meteorological data for the data block.
237          * @return an unmodifiable list of meteorological data
238          */
239         public Meteo getMeteoData() {
240             return new Meteo(meteoData);
241         }
242 
243         /**
244          * Add an entry to the list of range supplement data.
245          * @param rangeSupplement entry to add
246          * @since 12.0
247          */
248         public void addRangeSupplementData(final RangeSupplement rangeSupplement) {
249             rangeSupplementData.add(rangeSupplement);
250         }
251 
252         /**
253          * Get the range supplement data for the data block.
254          * @return an unmodifiable list of range supplement data
255          * @since 12.0
256          */
257         public List<RangeSupplement> getRangeSupplementData() {
258             return Collections.unmodifiableList(rangeSupplementData);
259         }
260 
261         /**
262          * Add an entry to the list of session statistics data.
263          * @param sessionStatistics entry to add
264          * @since 12.0
265          */
266         public void addSessionStatisticsData(final SessionStatistics sessionStatistics) {
267             sessionStatisticsData.add(sessionStatistics);
268         }
269 
270         /**
271          * Get the session statistics data for the data block.
272          * @return an unmodifiable list of session statistics data
273          * @since 12.0
274          */
275         public List<SessionStatistics> getSessionStatisticsData() {
276             return Collections.unmodifiableList(sessionStatisticsData);
277         }
278 
279         /**
280          * Get the default (the first if there are many records) SessionStat record.
281          * @return the default (the first if there are many records) session statistics record
282          * @since 12.0
283          */
284         public SessionStatistics getSessionStatisticsRecord() {
285             return getSessionStatisticsRecord(null);
286         }
287 
288         /**
289          * Get the session statistics record related to the systemConfigurationId.
290          * @param systemConfigurationId system configuration ID
291          * @return the session statistics record
292          * @since 12.0
293          */
294         public SessionStatistics getSessionStatisticsRecord(final String systemConfigurationId) {
295             if (sessionStatisticsData.isEmpty()) {
296                 return null;
297             }
298 
299             if (systemConfigurationId == null) {
300                 // default (the first one)
301                 return sessionStatisticsData.getFirst();
302             }
303 
304             // Loop to find the appropriate one
305             for (SessionStatistics sessionStatistics : sessionStatisticsData) {
306                 if (systemConfigurationId.equalsIgnoreCase(sessionStatistics.getSystemConfigurationId())) {
307                     return sessionStatistics;
308                 }
309             }
310 
311             return null;
312         }
313 
314         /**
315          * Add an entry to the list of calibration data.
316          * @param cal entry to add
317          * @since 12.0
318          */
319         public void addCalibrationData(final Calibration cal) {
320             calibrationData.add(cal);
321         }
322 
323         /**
324          * Get the calibration data for the data block.
325          * @return an unmodifiable list of calibration data
326          * @since 12.0
327          */
328         public List<Calibration> getCalibrationData() {
329             return Collections.unmodifiableList(calibrationData);
330         }
331 
332         /**
333          * Get the Calibration record(s) related to the default system configuration id.
334          * @return the Calibration record(s) related to the default system configuration id
335          * @since 12.0
336          */
337         public List<Calibration> getCalibrationRecords() {
338             return getCalibrationRecords(null);
339         }
340 
341         /**
342          * Get the Calibration record(s) related to the given systemConfigurationId.
343          * @param systemConfigurationId system configuration ID
344          * @return the Calibration record(s)
345          * @since 12.0
346          */
347         public List<Calibration> getCalibrationRecords(final String systemConfigurationId) {
348             if (calibrationData.isEmpty()) {
349                 return null;
350             }
351 
352             final String systemConfigId = systemConfigurationId == null ? getConfigurationRecords().getSystemRecord().getConfigurationId() : systemConfigurationId;
353 
354             final List<Calibration> list = new ArrayList<>();
355             // Loop to find the appropriate one
356             for (Calibration calibration : calibrationData) {
357                 if (systemConfigId.equalsIgnoreCase(calibration.getSystemConfigurationId())) {
358                     list.add(calibration);
359                 }
360             }
361 
362             return list;
363         }
364 
365         /**
366          * Add an entry to the list of calibration detail data.
367          * @param cal entry to add
368          * @since 12.0
369          */
370         public void addCalibrationDetailData(final CalibrationDetail cal) {
371             calibrationDetailData.add(cal);
372         }
373 
374         /**
375          * Get the calibration detail data for the data block.
376          * @return an unmodifiable list of calibration detail data
377          * @since 12.0
378          */
379         public List<CalibrationDetail> getCalibrationDetailData() {
380             return Collections.unmodifiableList(calibrationDetailData);
381         }
382 
383         /**
384          * Get the CalibrationDetail record(s) related to the default system configuration id.
385          * @return the CalibrationDetail record(s) related to the default system configuration id
386          * @since 12.0
387          */
388         public List<CalibrationDetail> getCalibrationDetailRecords() {
389             return getCalibrationDetailRecords(null);
390         }
391 
392         /**
393          * Get the CalibrationDetail record(s) related to the given systemConfigurationId.
394          * @param systemConfigurationId system configuration ID
395          * @return the CalibrationDetail record(s)
396          * @since 12.0
397          */
398         public List<CalibrationDetail> getCalibrationDetailRecords(final String systemConfigurationId) {
399             if (calibrationDetailData.isEmpty()) {
400                 return null;
401             }
402 
403             final String systemConfigId = systemConfigurationId == null ? getConfigurationRecords().getSystemRecord().getConfigurationId() : systemConfigurationId;
404 
405             final List<CalibrationDetail> list = new ArrayList<>();
406             // Loop to find the appropriate one
407             for (CalibrationDetail calibration : calibrationDetailData) {
408                 if (systemConfigId.equalsIgnoreCase(calibration.getSystemConfigurationId())) {
409                     list.add(calibration);
410                 }
411             }
412 
413             return list;
414         }
415 
416         /**
417          * Get the wavelength related to the given RangeMeasurement.
418          *
419          * @param range a RangeMeasurement
420          * @return the wavelength related to the given RangeMeasurement.
421          * @since 12.0
422          */
423         public double getWavelength(final RangeMeasurement range) {
424             return getConfigurationRecords().getSystemRecord(range.getSystemConfigurationId()).getWavelength();
425         }
426 
427     }
428 
429     /** Range record. */
430     public static class RangeMeasurement implements TimeStamped {
431 
432         /** Data epoch. */
433         private final AbsoluteDate date;
434 
435         /** Time of flight [s]. */
436         private final double timeOfFlight;
437 
438         /** System configuration ID. */
439         private final String systemConfigurationId;
440 
441         /** Time event reference indicator.
442          * 0 = ground receive time (at SRP) (two-way)
443          * 1 = spacecraft bounce time (two-way)
444          * 2 = ground transmit time (at SRP) (two-way)
445          * 3 = spacecraft receive time (one-way)
446          * 4 = spacecraft transmit time (one-way)
447          * 5 = ground transmit time (at SRP) and spacecraft receive time (one-way)
448          * 6 = spacecraft transmit time and ground receive time (at SRP) (one-way)
449          * Currently, only 1 and 2 are used for laser ranging data.
450          */
451         private final int epochEvent;
452 
453         /** Signal to noise ration. */
454         private final double snr;
455 
456         /**
457          * Constructor.
458          * @param date data epoch
459          * @param timeOfFlight time of flight in seconds
460          * @param epochEvent indicates the time event reference
461          */
462         public RangeMeasurement(final AbsoluteDate date,
463                                 final double timeOfFlight,
464                                 final int epochEvent) {
465             this(date, timeOfFlight, epochEvent, Double.NaN);
466         }
467 
468         /**
469          * Constructor.
470          * @param date data epoch
471          * @param timeOfFlight time of flight in seconds
472          * @param epochEvent indicates the time event reference
473          * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
474          */
475         public RangeMeasurement(final AbsoluteDate date,
476                                 final double timeOfFlight,
477                                 final int epochEvent, final double snr) {
478             this(date, timeOfFlight, epochEvent, snr, null);
479         }
480 
481         /**
482          * Constructor.
483          * @param date data epoch
484          * @param timeOfFlight time of flight in seconds
485          * @param epochEvent indicates the time event reference
486          * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
487          * @param systemConfigurationId system configuration id
488          * @since 12.0
489          */
490         public RangeMeasurement(final AbsoluteDate date,
491                                 final double timeOfFlight, final int epochEvent,
492                                 final double snr,
493                                 final String systemConfigurationId) {
494             this.date                  = date;
495             this.timeOfFlight          = timeOfFlight;
496             this.epochEvent            = epochEvent;
497             this.snr                   = snr;
498             this.systemConfigurationId = systemConfigurationId;
499         }
500 
501         /**
502          * Get the time-of-flight.
503          * @return the time-of-flight in seconds
504          */
505         public double getTimeOfFlight() {
506             return timeOfFlight;
507         }
508 
509         /**
510          * Get the indicator for the time event reference.
511          * <ul>
512          * <li>0 = ground receive time (at SRP) (two-way)</li>
513          * <li>1 = spacecraft bounce time (two-way)</li>
514          * <li>2 = ground transmit time (at SRP) (two-way)</li>
515          * <li>3 = spacecraft receive time (one-way)</li>
516          * <li>4 = spacecraft transmit time (one-way)</li>
517          * <li>5 = ground transmit time (at SRP) and spacecraft receive time (one-way)</li>
518          * <li>6 = spacecraft transmit time and ground receive time (at SRP) (one-way)</li>
519          * </ul>
520          * Currently, only 1 and 2 are used for laser ranging data
521          * @return the indicator for the time event reference
522          */
523         public int getEpochEvent() {
524             return epochEvent;
525         }
526 
527         /**
528          * Get the signal to noise ratio.
529          * @return the signal to noise ratio
530          */
531         public double getSnr() {
532             return snr;
533         }
534 
535         /** {@inheritDoc} */
536         @Override
537         public AbsoluteDate getDate() {
538             return date;
539         }
540 
541         /**
542          * Get the system configuration id.
543          * @return the system configuration id
544          * @since 12.0
545          */
546         public String getSystemConfigurationId() {
547             return systemConfigurationId;
548         }
549 
550         /**
551          * Get a string representation of the instance in the CRD format.
552          * @return a string representation of the instance, in the CRD format.
553          * @since 12.0
554          */
555         public String toCrdString() {
556             return "00 not supported. use NptRangeMeasurement or FrRangeMeasurement instead.";
557         }
558     }
559 
560     /**
561      * Range record -- Full rate, Sampled Engineering/Quicklook.
562      * @since 12.0
563      */
564     public static class FrRangeMeasurement extends RangeMeasurement {
565 
566         /** Filter flag. **/
567         private final int filterFlag;
568 
569         /** Detector channel. **/
570         private final int detectorChannel;
571 
572         /** Stop number (in multiple-stop system). **/
573         private final int stopNumber;
574 
575         /** Receive amplitude - a positive linear scale value. **/
576         private final int receiveAmplitude;
577 
578         /** Transmit amplitude - a positive linear scale value. **/
579         private final int transmitAmplitude;
580 
581         /**
582          * Constructor.
583          * @param date data epoch
584          * @param timeOfFlight time of flight in seconds
585          * @param epochEvent indicates the time event reference
586          * @param systemConfigurationId system configuration id
587          * @param filterFlag filter flag
588          * @param detectorChannel detector channel
589          * @param stopNumber stop number
590          * @param receiveAmplitude receive amplitude
591          * @param transmitAmplitude transmit amplitude
592          */
593         public FrRangeMeasurement(final AbsoluteDate date,
594                                   final double timeOfFlight,
595                                   final int epochEvent,
596                                   final String systemConfigurationId,
597                                   final int filterFlag,
598                                   final int detectorChannel,
599                                   final int stopNumber,
600                                   final int receiveAmplitude,
601                                   final int transmitAmplitude) {
602             super(date, timeOfFlight, epochEvent, Double.NaN, systemConfigurationId);
603             this.filterFlag        = filterFlag;
604             this.detectorChannel   = detectorChannel;
605             this.stopNumber        = stopNumber;
606             this.receiveAmplitude  = receiveAmplitude;
607             this.transmitAmplitude = transmitAmplitude;
608         }
609 
610         /**
611          * Get the filter flag.
612          * @return the filter flag
613          */
614         public int getFilterFlag() {
615             return filterFlag;
616         }
617 
618         /**
619          * Get the detector channel.
620          * @return the detector channel
621          */
622         public int getDetectorChannel() {
623             return detectorChannel;
624         }
625 
626         /**
627          * Get the stop number.
628          * @return the stop number
629          */
630         public int getStopNumber() {
631             return stopNumber;
632         }
633 
634         /**
635          * Get the receive amplitude.
636          * @return the receive amplitude, -1 if not measured
637          */
638         public int getReceiveAmplitude() {
639             return receiveAmplitude;
640         }
641 
642         /**
643          * Get the transmit amplitude.
644          * @return the transmit amplitude, -1 if not measured
645          */
646         public int getTransmitAmplitude() {
647             return transmitAmplitude;
648         }
649 
650         /** {@inheritDoc} */
651         @Override
652         @DefaultDataContext
653         public String toCrdString() {
654             return String.format(Locale.US, "10 %s", toString());
655         }
656 
657         @Override
658         @DefaultDataContext
659         public String toString() {
660             // CRD suggested format, excluding the record type
661             // 'local' is already utc.
662             // Seconds of day (sod) is typically to 1 milllisec precision.
663             // receiveAmplitude, transmitAmplitude: -1 if not available
664             final double sod = getDate().
665                                getComponents(TimeScalesFactory.getUTC()).
666                                roundIfNeeded(60, 12).
667                                getTime().
668                                getSecondsInLocalDay();
669 
670             final String str = String.format(Locale.US,
671                     "%18.12f %18.12f %4s %1d %1d %1d %1d %5s %5s", sod,
672                     getTimeOfFlight(), getSystemConfigurationId(),
673                     getEpochEvent(), filterFlag, detectorChannel, stopNumber,
674                     formatIntegerOrNaN(receiveAmplitude, -1),
675                     formatIntegerOrNaN(transmitAmplitude, -1));
676             return handleNaN(str).replace(',', '.');
677         }
678 
679     }
680 
681     /**
682      * Range record -- Normal Point.
683      * @since 12.0
684      */
685     public static class NptRangeMeasurement extends RangeMeasurement {
686 
687         /** Normal point window length [s]. */
688         private final double windowLength;
689 
690         /** Number of raw ranges (after editing) compressed into the normal point. */
691         private final int numberOfRawRanges;
692 
693         /** Bin RMS from the mean of raw accepted time-of-flight values minus the trend function. */
694         private final double binRms;
695 
696         /** Bin skew from the mean of raw accepted time-of-flight values minus the trend function. */
697         private final double binSkew;
698 
699         /** Bin kurtosis from the mean of raw accepted time-of-flight values minus the trend function. */
700         private final double binKurtosis;
701 
702         /** Bin peak - mean value. */
703         private final double binPeakMinusMean;
704 
705         /** Return rate [%]. */
706         private final double returnRate;
707 
708         /** Detector channel. */
709         private final int detectorChannel;
710 
711         /**
712          * Constructor.
713          * @param date data epoch
714          * @param timeOfFlight time of flight in seconds
715          * @param epochEvent indicates the time event reference
716          * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
717          * @param systemConfigurationId System configuration id
718          */
719         public NptRangeMeasurement(final AbsoluteDate date,
720                                    final double timeOfFlight,
721                                    final int epochEvent, final double snr,
722                                    final String systemConfigurationId) {
723             this(date, timeOfFlight, epochEvent, snr, systemConfigurationId, -1,
724                     -1, Double.NaN, Double.NaN, Double.NaN, Double.NaN,
725                     Double.NaN, 0);
726         }
727 
728         /**
729          * Constructor.
730          * @param date data epoch
731          * @param timeOfFlight time of flight in seconds
732          * @param epochEvent indicates the time event reference
733          * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
734          * @param systemConfigurationId System configuration id
735          * @param windowLength normal point window length
736          * @param numberOfRawRanges number of raw ranges (after editing) compressed into the normal point
737          * @param binRms Bin RMS from the mean of raw accepted time-of-flight values minus the trend function
738          * @param binSkew Bin skew from the mean of raw accepted time-of-flight values minus the trend function
739          * @param binKurtosis Bin kurtosis from the mean of raw accepted time-of-flight values minus the trend function
740          * @param binPeakMinusMean Bin peak - mean value
741          * @param returnRate Return rate [%]
742          * @param detectorChannel detector channel
743          */
744         public NptRangeMeasurement(final AbsoluteDate date,
745                                    final double timeOfFlight,
746                                    final int epochEvent, final double snr,
747                                    final String systemConfigurationId,
748                                    final double windowLength,
749                                    final int numberOfRawRanges,
750                                    final double binRms, final double binSkew,
751                                    final double binKurtosis,
752                                    final double binPeakMinusMean,
753                                    final double returnRate,
754                                    final int detectorChannel) {
755             super(date, timeOfFlight, epochEvent, snr, systemConfigurationId);
756 
757             this.windowLength      = windowLength;
758             this.numberOfRawRanges = numberOfRawRanges;
759             this.binSkew           = binSkew;
760             this.binKurtosis       = binKurtosis;
761             this.binPeakMinusMean  = binPeakMinusMean;
762             this.detectorChannel   = detectorChannel;
763             this.binRms            = binRms == -1.0e-12 ? Double.NaN : binRms; // -1=na, ps --> s
764             this.returnRate        = returnRate == -1 ? Double.NaN : returnRate; // -1=na
765         }
766 
767         /**
768          * Get the normal point window length.
769          * @return the normal point window length
770          */
771         public double getWindowLength() {
772             return windowLength;
773         }
774 
775         /**
776          * Get the umber of raw ranges (after editing) compressed into the normal point.
777          * @return the umber of raw ranges
778          */
779         public int getNumberOfRawRanges() {
780             return numberOfRawRanges;
781         }
782 
783         /**
784          * Get the bin RMS from the mean of raw accepted time-of-flight values minus the trend function.
785          * @return the bin RMS
786          */
787         public double getBinRms() {
788             return binRms;
789         }
790 
791         /**
792          * Get the bin skew from the mean of raw accepted time-of-flight values minus the trend function.
793          * @return the bin skew
794          */
795         public double getBinSkew() {
796             return binSkew;
797         }
798 
799         /**
800          * Get the bin kurtosis from the mean of raw accepted time-of-flight values minus the trend function.
801          * @return the bin kurtosis
802          */
803         public double getBinKurtosis() {
804             return binKurtosis;
805         }
806 
807         /**
808          * Get the bin peak - mean value.
809          * @return the bin peak - mean value
810          */
811         public double getBinPeakMinusMean() {
812             return binPeakMinusMean;
813         }
814 
815         /**
816          * Get the return rate.
817          * @return the return rate
818          */
819         public double getReturnRate() {
820             return returnRate;
821         }
822 
823         /**
824          * Get the detector channel.
825          * @return the detector channel
826          */
827         public int getDetectorChannel() {
828             return detectorChannel;
829         }
830 
831         /** {@inheritDoc} */
832         @Override
833         @DefaultDataContext
834         public String toCrdString() {
835             return String.format(Locale.US, "11 %s", toString());
836         }
837 
838         @Override
839         @DefaultDataContext
840         public String toString() {
841             // CRD suggested format, excluding the record type
842             // binRms, binPeakMinusMean: s --> ps
843             // 'local' is already utc.
844             // Seconds of day (sod) is typically to 1 milllisec precision.
845             final double sod = getDate().
846                                getComponents(TimeScalesFactory.getUTC()).
847                                roundIfNeeded(60, 12).
848                                getTime().
849                                getSecondsInLocalDay();
850 
851             final String str = String.format(Locale.US,
852                     "%18.12f %18.12f %4s %1d %6.1f %6d %9.1f %7.3f %7.3f %9.1f %5.2f %1d %5.1f",
853                     sod, getTimeOfFlight(), getSystemConfigurationId(),
854                     getEpochEvent(), windowLength, numberOfRawRanges,
855                     binRms * 1e12, binSkew, binKurtosis,
856                     binPeakMinusMean * 1e12, returnRate, detectorChannel,
857                     getSnr());
858             return handleNaN(str).replace(',', '.');
859         }
860 
861     }
862 
863     /**
864      * Range Supplement Record.
865      * @since 12.0
866      */
867     public static class RangeSupplement implements TimeStamped {
868 
869         /** Data epoch. */
870         private final AbsoluteDate date;
871 
872         /** System configuration ID. */
873         private final String systemConfigurationId;
874 
875         /** Tropospheric refraction correction (one-way). */
876         private final double troposphericRefractionCorrection;
877 
878         /** Target center of mass correction (one-way). */
879         private final double centerOfMassCorrection;
880 
881         /** Neutral density (ND) filter value. */
882         private final double ndFilterValue;
883 
884         /** Time bias applied. */
885         private final double timeBiasApplied;
886 
887         /** Range rate. */
888         private final double rangeRate;
889 
890         /**
891          * Constructor.
892          * @param date data epoch
893          * @param systemConfigurationId system configuration ID
894          * @param troposphericRefractionCorrection tropospheric refraction correction (one-way)
895          * @param centerOfMassCorrection target center of mass correction (one-way)
896          * @param ndFilterValue Neutral density (ND) filter value
897          * @param timeBiasApplied Time bias applied
898          * @param rangeRate Range rate
899          */
900         public RangeSupplement(final AbsoluteDate date,
901                                final String systemConfigurationId,
902                                final double troposphericRefractionCorrection,
903                                final double centerOfMassCorrection,
904                                final double ndFilterValue,
905                                final double timeBiasApplied,
906                                final double rangeRate) {
907             this.date                             = date;
908             this.systemConfigurationId            = systemConfigurationId;
909             this.troposphericRefractionCorrection = troposphericRefractionCorrection;
910             this.centerOfMassCorrection           = centerOfMassCorrection;
911             this.ndFilterValue                    = ndFilterValue;
912             this.timeBiasApplied                  = timeBiasApplied;
913             this.rangeRate                        = rangeRate;
914         }
915 
916         @Override
917         public AbsoluteDate getDate() {
918             return date;
919         }
920 
921         /**
922          * Get the system configuration id.
923          * @return the system configuration id
924          */
925         public String getSystemConfigurationId() {
926             return systemConfigurationId;
927         }
928 
929         /**
930          * Get the tropospheric refraction correction.
931          * @return the tropospheric refraction correction
932          */
933         public double getTroposphericRefractionCorrection() {
934             return troposphericRefractionCorrection;
935         }
936 
937         /**
938          * Get the target center of mass.
939          * @return the target center of mass
940          */
941         public double getCenterOfMassCorrection() {
942             return centerOfMassCorrection;
943         }
944 
945         /**
946          * Get the neutral density (ND) filter value.
947          * @return the neutral density (ND) filter value
948          */
949         public double getNdFilterValue() {
950             return ndFilterValue;
951         }
952 
953         /**
954          * Get the time bias applied.
955          * @return the time bias applied
956          */
957         public double getTimeBiasApplied() {
958             return timeBiasApplied;
959         }
960 
961         /**
962          * Get the range rate.
963          * @return the range rate
964          */
965         public double getRangeRate() {
966             return rangeRate;
967         }
968 
969         /**
970          * Get a string representation of the instance in the CRD format.
971          * @return a string representation of the instance, in the CRD format.
972          */
973         @DefaultDataContext
974         public String toCrdString() {
975             return String.format(Locale.US, "12 %s", toString());
976         }
977 
978         @Override
979         @DefaultDataContext
980         public String toString() {
981             // CRD suggested format, excluding the record type
982             // troposphericRefractionCorrection: s --> ps
983             // 'local' is already utc.
984             // Seconds of day (sod) is typically to 1 milllisec precision.
985             final double sod = getDate().
986                                getComponents(TimeScalesFactory.getUTC()).
987                                roundIfNeeded(60, 12).
988                                getTime().
989                                getSecondsInLocalDay();
990 
991             final String str = String.format(Locale.US,
992                     "%18.12f %4s %6.1f %6.4f %5.2f %8.4f %f", sod,
993                     getSystemConfigurationId(),
994                     troposphericRefractionCorrection * 1e12,
995                     centerOfMassCorrection, ndFilterValue, timeBiasApplied,
996                     rangeRate);
997             return handleNaN(str).replace(',', '.');
998         }
999 
1000     }
1001 
1002     /** This data record contains a minimal set of meteorological data. */
1003     public static class MeteorologicalMeasurement implements TimeStamped {
1004 
1005         /** Data epoch. */
1006         private final AbsoluteDate date;
1007 
1008         /** Surface pressure [bar]. */
1009         private final double pressure;
1010 
1011         /** Surface temperature [K]. */
1012         private final double temperature;
1013 
1014         /** Relative humidity at the surface [%]. */
1015         private final double humidity;
1016 
1017         /** Origin of values.
1018          * 0=measured values, 1=interpolated values
1019          */
1020         private final int originOfValues;
1021 
1022         /**
1023          * Constructor.
1024          * @param date data epoch
1025          * @param pressure the surface pressure in bars
1026          * @param temperature the surface temperature in degrees Kelvin
1027          * @param humidity the relative humidity at the surface in percents
1028          */
1029         public MeteorologicalMeasurement(final AbsoluteDate date,
1030                                          final double pressure, final double temperature,
1031                                          final double humidity) {
1032             this(date, pressure, temperature, humidity, 0);
1033         }
1034 
1035         /**
1036          * Constructor.
1037          * @param date data epoch
1038          * @param pressure the surface pressure in bars
1039          * @param temperature the surface temperature in degrees Kelvin
1040          * @param humidity the relative humidity at the surface in percents
1041          * @param originOfValues Origin of values
1042          */
1043         public MeteorologicalMeasurement(final AbsoluteDate date, final double pressure, final double temperature,
1044                                          final double humidity, final int originOfValues) {
1045             this.date           = date;
1046             this.pressure       = pressure;
1047             this.temperature    = temperature;
1048             this.humidity       = humidity;
1049             this.originOfValues = originOfValues;
1050         }
1051 
1052         /**
1053          * Get the surface pressure.
1054          * @return the surface pressure in bars
1055          */
1056         public double getPressure() {
1057             return pressure;
1058         }
1059 
1060         /**
1061          * Get the surface temperature.
1062          * @return the surface temperature in degrees Kelvin
1063          */
1064         public double getTemperature() {
1065             return temperature;
1066         }
1067 
1068         /**
1069          * Get the relative humidity at the surface.
1070          * @return the relative humidity at the surface in percents
1071          */
1072         public double getHumidity() {
1073             return humidity;
1074         }
1075 
1076         /** {@inheritDoc} */
1077         @Override
1078         public AbsoluteDate getDate() {
1079             return date;
1080         }
1081 
1082         /** Get the origin of values.
1083          * 0=measure values
1084          * 1=interpolated values
1085          * @return the origin of values
1086          * @since 12.0
1087          */
1088         public int getOriginOfValues() {
1089             return originOfValues;
1090         }
1091 
1092         /**
1093          * Get a string representation of the instance in the CRD format.
1094          * @return a string representation of the instance, in the CRD format.
1095          * @since 12.0
1096          */
1097         @DefaultDataContext
1098         public String toCrdString() {
1099             return String.format(Locale.US, "20 %s", toString());
1100         }
1101 
1102         @Override
1103         @DefaultDataContext
1104         public String toString() {
1105             // CRD suggested format, excluding the record type
1106             // pressure: bar --> mbar
1107             // 'local' is already utc.
1108             // Seconds of day (sod) is typically to 1 milllisec precision.
1109             final double sod = getDate().
1110                                getComponents(TimeScalesFactory.getUTC()).
1111                                roundIfNeeded(60, 3).
1112                                getTime().
1113                                getSecondsInLocalDay();
1114 
1115             final String str = String.format(Locale.US, "%9.3f %7.2f %6.2f %4.0f %1d",
1116                                              sod, pressure * 1e3, temperature, humidity, originOfValues);
1117             return handleNaN(str).replace(',', '.');
1118         }
1119     }
1120 
1121     /** Pointing angles record. */
1122     public static class AnglesMeasurement implements TimeStamped {
1123 
1124         /** Data epoch. */
1125         private final AbsoluteDate date;
1126 
1127         /** Azimuth [rad]. */
1128         private final double azimuth;
1129 
1130         /** Elevation [rad]. */
1131         private final double elevation;
1132 
1133         /** Direction flag (0 = transmit &#38; receive ; 1 = transmit ; 2 = receive). */
1134         private final int directionFlag;
1135 
1136         /** Angle origin indicator.
1137          * 0 = unknown
1138          * 1 = computed
1139          * 2 = commanded (from predictions)
1140          * 3 = measured (from encoders)
1141          */
1142         private final int originIndicator;
1143 
1144         /** Refraction corrected. */
1145         private final boolean refractionCorrected;
1146 
1147         /** Azimuth rate [rad/sec]. */
1148         private final double azimuthRate;
1149 
1150         /** Elevation rate [rad/sec]. */
1151         private final double elevationRate;
1152 
1153         /**
1154          * Constructor.
1155          * @param date data epoch
1156          * @param azimuth azimuth angle in radians
1157          * @param elevation elevation angle in radians
1158          * @param directionFlag direction flag
1159          * @param originIndicator angle origin indicator
1160          * @param refractionCorrected flag to indicate if the refraction is corrected
1161          * @param azimuthRate azimuth rate in radians per second (equal to Double.NaN if unknown)
1162          * @param elevationRate elevation rate in radians per second (equal to Double.NaN if unknown)
1163          */
1164         public AnglesMeasurement(final AbsoluteDate date, final double azimuth,
1165                                  final double elevation, final int directionFlag,
1166                                  final int originIndicator,
1167                                  final boolean refractionCorrected,
1168                                  final double azimuthRate, final double elevationRate) {
1169             this.date                = date;
1170             this.azimuth             = azimuth;
1171             this.elevation           = elevation;
1172             this.directionFlag       = directionFlag;
1173             this.originIndicator     = originIndicator;
1174             this.refractionCorrected = refractionCorrected;
1175             this.azimuthRate         = azimuthRate;
1176             this.elevationRate       = elevationRate;
1177         }
1178 
1179         /**
1180          * Get the azimuth angle.
1181          * @return the azimuth angle in radians
1182          */
1183         public double getAzimuth() {
1184             return azimuth;
1185         }
1186 
1187         /**
1188          * Get the elevation angle.
1189          * @return the elevation angle in radians
1190          */
1191         public double getElevation() {
1192             return elevation;
1193         }
1194 
1195         /**
1196          * Get the direction flag (0 = transmit &#38; receive ; 1 = transmit ; 2 = receive).
1197          * @return the direction flag
1198          */
1199         public int getDirectionFlag() {
1200             return directionFlag;
1201         }
1202 
1203         /**
1204          * Get the angle origin indicator.
1205          * <p>
1206          * 0 = unknown;
1207          * 1 = computed;
1208          * 2 = commanded (from predictions);
1209          * 3 = measured (from encoders)
1210          * </p>
1211          * @return the angle origin indicator
1212          */
1213         public int getOriginIndicator() {
1214             return originIndicator;
1215         }
1216 
1217         /**
1218          * Get the flag indicating if the refraction is corrected.
1219          * @return true if refraction is corrected
1220          */
1221         public boolean isRefractionCorrected() {
1222             return refractionCorrected;
1223         }
1224 
1225         /**
1226          * Get the azimuth rate.
1227          * <p>
1228          * Is equal to Double.NaN if the value is unknown.
1229          * </p>
1230          * @return the azimuth rate in radians per second
1231          */
1232         public double getAzimuthRate() {
1233             return azimuthRate;
1234         }
1235 
1236         /**
1237          * Get the elevation rate.
1238          * <p>
1239          * Is equal to Double.NaN if the value is unknown.
1240          * </p>
1241          * @return the elevation rate in radians per second
1242          */
1243         public double getElevationRate() {
1244             return elevationRate;
1245         }
1246 
1247         /** {@inheritDoc} */
1248         @Override
1249         public AbsoluteDate getDate() {
1250             return date;
1251         }
1252 
1253         /**
1254          * Get a string representation of the instance in the CRD format.
1255          * @return a string representation of the instance, in the CRD format.
1256          * @since 12.0
1257          */
1258         @DefaultDataContext
1259         public String toCrdString() {
1260             return String.format(Locale.US, "30 %s", toString());
1261         }
1262 
1263         @Override
1264         @DefaultDataContext
1265         public String toString() {
1266             // CRD suggested format, excluding the record type
1267             // azimuth, elevation: rad --> deg
1268             // azimuthRate, elevationRate: rad/s --> deg/s
1269             // 'local' is already utc.
1270             // Seconds of day (sod) is typically to 1 milllisec precision.
1271             final double sod = getDate().
1272                                getComponents(TimeScalesFactory.getUTC()).
1273                                roundIfNeeded(60, 3).
1274                                getTime().
1275                                getSecondsInLocalDay();
1276 
1277             final String str = String.format(Locale.US,
1278                                              "%9.3f %8.4f %8.4f %1d %1d %1d %10.7f %10.7f",
1279                                              sod,
1280                                              FastMath.toDegrees(azimuth), FastMath.toDegrees(elevation),
1281                                              directionFlag, originIndicator, refractionCorrected ? 1 : 0,
1282                                              FastMath.toDegrees(azimuthRate),
1283                                              FastMath.toDegrees(elevationRate));
1284             return handleNaN(str).replace(',', '.');
1285         }
1286     }
1287 
1288     /** Meteorological data. */
1289     public static class Meteo {
1290 
1291         /** Number of neighbors for meteo data interpolation. */
1292         private static final int N_NEIGHBORS = 2;
1293 
1294         /** First available date. */
1295         private final AbsoluteDate firstDate;
1296 
1297         /** Last available date. */
1298         private final AbsoluteDate lastDate;
1299 
1300         /** Previous set of meteorological parameters. */
1301         private MeteorologicalMeasurement previousParam;
1302 
1303         /** Next set of solar meteorological parameters. */
1304         private MeteorologicalMeasurement nextParam;
1305 
1306         /** List of meteo data. */
1307         private final ImmutableTimeStampedCache<MeteorologicalMeasurement> meteo;
1308 
1309         /**
1310          * Constructor.
1311          * @param meteoData list of meteo data
1312          */
1313         public Meteo(final SortedSet<MeteorologicalMeasurement> meteoData) {
1314 
1315             // Size
1316             final int neighborsSize = (meteoData.size() < 2) ? meteoData.size() : N_NEIGHBORS;
1317 
1318             // Check neighbors size
1319             if (neighborsSize == 0) {
1320 
1321                 // Meteo data -> empty cache
1322                 this.meteo = ImmutableTimeStampedCache.emptyCache();
1323 
1324                 // Null epochs (will ne be used)
1325                 this.firstDate = null;
1326                 this.lastDate  = null;
1327 
1328             } else {
1329 
1330                 // Meteo data
1331                 this.meteo = new ImmutableTimeStampedCache<>(neighborsSize, meteoData);
1332 
1333                 // Initialize first and last available dates
1334                 this.firstDate = meteoData.getFirst().getDate();
1335                 this.lastDate  = meteoData.getLast().getDate();
1336 
1337             }
1338 
1339         }
1340 
1341         /** Get an unmodifiable view of the tabulated meteorological data.
1342          * @return unmodifiable view of the tabulated meteorological data
1343          * @since 11.0
1344          */
1345         public List<MeteorologicalMeasurement> getData() {
1346             return meteo.getAll();
1347         }
1348 
1349         /**
1350          * Get the meteorological parameters at a given date.
1351          * @param date date when user wants the meteorological parameters
1352          * @return the meteorological parameters at date (can be null if
1353          *         meteorological data are empty).
1354          */
1355         public MeteorologicalMeasurement getMeteo(final AbsoluteDate date) {
1356 
1357             // Check if meteorological data are available
1358             if (meteo.getMaxNeighborsSize() == 0) {
1359                 return null;
1360             }
1361 
1362             // Interpolating two neighboring meteorological parameters
1363             bracketDate(date);
1364             if (date.durationFrom(firstDate) <= 0 || date.durationFrom(lastDate) > 0) {
1365                 // Date is outside file range
1366                 return previousParam;
1367             } else {
1368                 // Perform interpolations
1369                 final double pressure    = getLinearInterpolation(date, previousParam.getPressure(), nextParam.getPressure());
1370                 final double temperature = getLinearInterpolation(date, previousParam.getTemperature(), nextParam.getTemperature());
1371                 final double humidity    = getLinearInterpolation(date, previousParam.getHumidity(), nextParam.getHumidity());
1372                 return new MeteorologicalMeasurement(date, pressure, temperature, humidity);
1373             }
1374 
1375         }
1376 
1377         /**
1378          * Find the data bracketing a specified date.
1379          * @param date date to bracket
1380          */
1381         private void bracketDate(final AbsoluteDate date) {
1382 
1383             // don't search if the cached selection is fine
1384             if (previousParam != null &&
1385                 date.durationFrom(previousParam.getDate()) > 0 &&
1386                 date.durationFrom(nextParam.getDate()) <= 0) {
1387                 return;
1388             }
1389 
1390             // Initialize previous and next parameters
1391             if (date.durationFrom(firstDate) <= 0) {
1392                 // Current date is before the first date
1393                 previousParam = meteo.getEarliest();
1394                 nextParam     = previousParam;
1395             } else if (date.durationFrom(lastDate) > 0) {
1396                 // Current date is after the last date
1397                 previousParam = meteo.getLatest();
1398                 nextParam     = previousParam;
1399             } else {
1400                 // Current date is between first and last date
1401                 final List<MeteorologicalMeasurement> neighbors = meteo.getNeighbors(date).toList();
1402                 previousParam = neighbors.getFirst();
1403                 nextParam     = neighbors.get(1);
1404             }
1405 
1406         }
1407 
1408         /**
1409          * Performs a linear interpolation between two values The weights are computed
1410          * from the time delta between previous date, current date, next date.
1411          * @param date the current date
1412          * @param previousValue the value at previous date
1413          * @param nextValue the value at next date
1414          * @return the value interpolated for the current date
1415          */
1416         private double getLinearInterpolation(final AbsoluteDate date,
1417                                               final double previousValue,
1418                                               final double nextValue) {
1419             // Perform a linear interpolation
1420             final AbsoluteDate previousDate = previousParam.getDate();
1421             final AbsoluteDate currentDate = nextParam.getDate();
1422             final double dt = currentDate.durationFrom(previousDate);
1423             final double previousWeight = currentDate.durationFrom(date) / dt;
1424             final double nextWeight = date.durationFrom(previousDate) / dt;
1425 
1426             // Returns the data interpolated at the date
1427             return previousValue * previousWeight + nextValue * nextWeight;
1428         }
1429 
1430     }
1431 
1432     /**
1433      * Calibration Record.
1434      * @since 12.0
1435      */
1436     public static class Calibration implements TimeStamped {
1437 
1438         /** Data epoch. */
1439         private final AbsoluteDate date;
1440 
1441         /**
1442          * Type of data.
1443          * 0=station combined transmit and receive calibration (“normal” SLR/LLR)
1444          * 1=station transmit calibration (e.g., one-way ranging to transponders)
1445          * 2=station receive calibration
1446          * 3=target combined transmit and receive calibrations
1447          * 4=target transmit calibration
1448          * 5=target receive calibration
1449          */
1450         private final int typeOfData;
1451 
1452         /** System configuration ID. */
1453         private final String systemConfigurationId;
1454 
1455         /** Number of data points recorded. */
1456         private final int numberOfPointsRecorded;
1457 
1458         /** Number of data points used. */
1459         private final int numberOfPointsUsed;
1460 
1461         /** One-way target distance (meters, nominal). */
1462         private final double oneWayDistance;
1463 
1464         /** Calibration System Delay. */
1465         private final double systemDelay;
1466 
1467         /** Calibration Delay Shift - a measure of calibration stability. */
1468         private final double delayShift;
1469 
1470         /** RMS of raw system delay. */
1471         private final double rms;
1472 
1473         /** Skew of raw system delay values from the mean. */
1474         private final double skew;
1475 
1476         /** Kurtosis of raw system delay values from the mean. */
1477         private final double kurtosis;
1478 
1479         /** System delay peak – mean value. */
1480         private final double peakMinusMean;
1481 
1482         /**
1483          * Calibration Type Indicator.
1484          * 0=not used or undefined
1485          * 1=nominal (from once off assessment)
1486          * 2=external calibrations
1487          * 3=internal calibrations – telescope
1488          * 4=internal calibrations – building
1489          * 5=burst calibrations
1490          * 6=other
1491          */
1492         private final int typeIndicator;
1493 
1494         /**
1495          * Calibration Shift Type Indicator.
1496          * 0=not used or undefined
1497          * 1=nominal (from once off assessment)
1498          * 2=pre- to post- Shift
1499          * 3=minimum to maximum
1500          * 4=other
1501          */
1502         private final int shiftTypeIndicator;
1503 
1504         /** Detector Channel.
1505          * 0=not applicable or “all”
1506          * 1-4 for quadrant
1507          * 1-n for many channels
1508          */
1509         private final int detectorChannel;
1510 
1511         /**
1512          * Calibration Span.
1513          * 0 = not applicable (e.g. Calibration type indicator is “nominal”)
1514          * 1 = Pre-calibration only
1515          * 2 = Post-calibration only
1516          * 3 = Combined (pre- and post-calibrations or multiple)
1517          * 4 = Real-time calibration (data taken while ranging to a satellite)
1518          */
1519         private final int span;
1520 
1521         /** Return Rate (%). */
1522         private final double returnRate;
1523 
1524         /**
1525          * Constructor.
1526          * @param date data epoch
1527          * @param typeOfData type of data
1528          * @param systemConfigurationId system configuration id
1529          * @param numberOfPointsRecorded number of data points recorded
1530          * @param numberOfPointsUsed number of data points used
1531          * @param oneWayDistance one-way target distance (nominal)
1532          * @param systemDelay calibration system delay
1533          * @param delayShift calibration delay shift - a measure of calibration stability
1534          * @param rms RMS of raw system delay
1535          * @param skew skew of raw system delay values from the mean.
1536          * @param kurtosis kurtosis of raw system delay values from the mean.
1537          * @param peakMinusMean system delay peak – mean value
1538          * @param typeIndicator calibration type indicator
1539          * @param shiftTypeIndicator calibration shift type indicator
1540          * @param detectorChannel detector channel
1541          * @param span calibration span
1542          * @param returnRate return rate (%)
1543          */
1544         public Calibration(final AbsoluteDate date, final int typeOfData,
1545                            final String systemConfigurationId,
1546                            final int numberOfPointsRecorded,
1547                            final int numberOfPointsUsed,
1548                            final double oneWayDistance,
1549                            final double systemDelay, final double delayShift,
1550                            final double rms, final double skew,
1551                            final double kurtosis, final double peakMinusMean,
1552                            final int typeIndicator, final int shiftTypeIndicator,
1553                            final int detectorChannel, final int span,
1554                            final double returnRate) {
1555             this.date                   = date;
1556             this.typeOfData             = typeOfData;
1557             this.systemConfigurationId  = systemConfigurationId;
1558             this.numberOfPointsRecorded = numberOfPointsRecorded;
1559             this.numberOfPointsUsed     = numberOfPointsUsed;
1560             this.systemDelay            = systemDelay;
1561             this.delayShift             = delayShift;
1562             this.rms                    = rms;
1563             this.skew                   = skew;
1564             this.kurtosis               = kurtosis;
1565             this.peakMinusMean          = peakMinusMean;
1566             this.typeIndicator          = typeIndicator;
1567             this.shiftTypeIndicator     = shiftTypeIndicator;
1568             this.detectorChannel        = detectorChannel;
1569             this.span                   = span;
1570             this.returnRate             = returnRate;
1571             this.oneWayDistance         = oneWayDistance == -1 ? Double.NaN : oneWayDistance; // -1=na
1572         }
1573 
1574         @Override
1575         public AbsoluteDate getDate() {
1576             return date;
1577         }
1578 
1579         /**
1580          * Get the type of data.
1581          *
1582          * <ul>
1583          * <li>0=station combined transmit and receive calibration (“normal” SLR/LLR)
1584          * <li>1=station transmit calibration (e.g., one-way ranging to transponders)
1585          * <li>2=station receive calibration
1586          * <li>3=target combined transmit and receive calibrations
1587          * <li>4=target transmit calibration
1588          * <li>5=target receive calibration
1589          * </ul>
1590          * @return the type of data
1591          */
1592         public int getTypeOfData() {
1593             return typeOfData;
1594         }
1595 
1596         /**
1597          * Get the system configuration id.
1598          * @return the system configuration id
1599          */
1600         public String getSystemConfigurationId() {
1601             return systemConfigurationId;
1602         }
1603 
1604         /**
1605          * Get the number of data points recorded.
1606          * @return the number of data points recorded, -1 if no information
1607          */
1608         public int getNumberOfPointsRecorded() {
1609             return numberOfPointsRecorded;
1610         }
1611 
1612         /**
1613          * Get the number of data points used.
1614          * @return the number of data points used, -1 if no information
1615          */
1616         public int getNumberOfPointsUsed() {
1617             return numberOfPointsUsed;
1618         }
1619 
1620         /**
1621          * Get the one-way target distance (nominal).
1622          * @return the one-way target distance (nominal)
1623          */
1624         public double getOneWayDistance() {
1625             return oneWayDistance;
1626         }
1627 
1628         /**
1629          * Get the calibration system delay.
1630          * @return the calibration system delay
1631          */
1632         public double getSystemDelay() {
1633             return systemDelay;
1634         }
1635 
1636         /**
1637          * Get the calibration delay shift.
1638          * @return the calibration delay shift
1639          */
1640         public double getDelayShift() {
1641             return delayShift;
1642         }
1643 
1644         /**
1645          * Get the rms of raw system delay.
1646          * @return the rms of raw system delay
1647          */
1648         public double getRms() {
1649             return rms;
1650         }
1651 
1652         /**
1653          * Get the skew of raw system delay values from the mean.
1654          * @return the skew of raw system delay values from the mean.
1655          */
1656         public double getSkew() {
1657             return skew;
1658         }
1659 
1660         /**
1661          * Get the kurtosis of raw system delay values from the mean.
1662          * @return the kurtosis of raw system delay values from the mean.
1663          */
1664         public double getKurtosis() {
1665             return kurtosis;
1666         }
1667 
1668         /**
1669          * Get the system delay peak – mean value.
1670          * @return the system delay peak – mean value
1671          */
1672         public double getPeakMinusMean() {
1673             return peakMinusMean;
1674         }
1675 
1676         /**
1677          * Get the calibration type indicator.
1678          *
1679          * <ul>
1680          * <li>0=not used or undefined
1681          * <li>1=nominal (from once off assessment)
1682          * <li>2=external calibrations
1683          * <li>3=internal calibrations – telescope
1684          * <li>4=internal calibrations – building
1685          * <li>5=burst calibrations
1686          * <li>6=other
1687          * </ul>
1688          * @return the calibration type indicator
1689          */
1690         public int getTypeIndicator() {
1691             return typeIndicator;
1692         }
1693 
1694         /**
1695          * Get the calibration shift type indicator.
1696          *
1697          * <ul>
1698          * <li>0=not used or undefined
1699          * <li>1=nominal (from once off assessment)
1700          * <li>2=pre- to post- Shift
1701          * <li>3=minimum to maximum
1702          * <li>4=other
1703          * </ul>
1704          * @return the calibration shift type indicator
1705          */
1706         public int getShiftTypeIndicator() {
1707             return shiftTypeIndicator;
1708         }
1709 
1710         /**
1711          * Get the detector channel.
1712          *
1713          * <ul>
1714          * <li>0=not applicable or “all”
1715          * <li>1-4 for quadrant
1716          * <li>1-n for many channels
1717          * </ul>
1718          * @return the detector channel
1719          */
1720         public int getDetectorChannel() {
1721             return detectorChannel;
1722         }
1723 
1724         /**
1725          * Get the calibration span.
1726          *
1727          * <ul>
1728          * <li>0 = not applicable (e.g. Calibration type indicator is “nominal”)
1729          * <li>1 = Pre-calibration only
1730          * <li>2 = Post-calibration only
1731          * <li>3 = Combined (pre- and post-calibrations or multiple)
1732          * <li>4 = Real-time calibration (data taken while ranging to a satellite)
1733          * </ul>
1734          * @return the calibration span
1735          */
1736         public int getSpan() {
1737             return span;
1738         }
1739 
1740         /**
1741          * Get the return rate.
1742          * @return the return rate
1743          */
1744         public double getReturnRate() {
1745             return returnRate;
1746         }
1747 
1748         /**
1749          * Get a string representation of the instance in the CRD format.
1750          * @return a string representation of the instance, in the CRD format.
1751          */
1752         @DefaultDataContext
1753         public String toCrdString() {
1754             return String.format(Locale.US, "40 %s", toString());
1755         }
1756 
1757         @Override
1758         @DefaultDataContext
1759         public String toString() {
1760             // CRD suggested format, excluding the record type
1761             // systemDelay, delayShift: s --> ps
1762             // rms, peakMinusMean: s --> ps
1763             // 'local' is already utc.
1764             // Seconds of day (sod) is typically to 1 milllisec precision.
1765             final double sod = getDate().
1766                                getComponents(TimeScalesFactory.getUTC()).
1767                                roundIfNeeded(60, 12).
1768                                getTime().
1769                                getSecondsInLocalDay();
1770 
1771             final String str = "%18.12f %1d %4s %8s %8s %8.4f %10.1f %8.1f %6.1f %7.3f %7.3f %6.1f %1d %1d %1d %1d %5.1f".formatted(
1772                     sod, typeOfData, systemConfigurationId,
1773                     formatIntegerOrNaN(numberOfPointsRecorded, -1),
1774                     formatIntegerOrNaN(numberOfPointsUsed, -1), oneWayDistance,
1775                     systemDelay * 1e12, delayShift * 1e12, rms * 1e12, skew,
1776                     kurtosis, peakMinusMean * 1e12, typeIndicator,
1777                     shiftTypeIndicator, detectorChannel, span, returnRate);
1778             return handleNaN(str).replace(',', '.');
1779         }
1780 
1781     }
1782 
1783     /**
1784      * Calibration Detail Record.
1785      * @since 12.0
1786      */
1787     public static class CalibrationDetail extends Calibration {
1788         // same as Calibration record except that the record type is '41' rather than '40'.
1789 
1790         /**
1791          * Constructor.
1792          * @param date data epoch
1793          * @param typeOfData type of data
1794          * @param systemConfigurationId system configuration id
1795          * @param numberOfPointsRecorded number of data points recorded
1796          * @param numberOfPointsUsed number of data points used
1797          * @param oneWayDistance one-way target distance (nominal)
1798          * @param systemDelay calibration system delay
1799          * @param delayShift calibration delay shift - a measure of calibration stability
1800          * @param rms RMS of raw system delay
1801          * @param skew skew of raw system delay values from the mean.
1802          * @param kurtosis kurtosis of raw system delay values from the mean.
1803          * @param peakMinusMean system delay peak – mean value
1804          * @param typeIndicator calibration type indicator
1805          * @param shiftTypeIndicator calibration shift type indicator
1806          * @param detectorChannel detector channel
1807          * @param span calibration span
1808          * @param returnRate return rate (%)
1809          */
1810         public CalibrationDetail(final AbsoluteDate date, final int typeOfData,
1811                                  final String systemConfigurationId,
1812                                  final int numberOfPointsRecorded,
1813                                  final int numberOfPointsUsed, final double oneWayDistance,
1814                                  final double systemDelay, final double delayShift,
1815                                  final double rms, final double skew, final double kurtosis,
1816                                  final double peakMinusMean, final int typeIndicator,
1817                                  final int shiftTypeIndicator, final int detectorChannel,
1818                                  final int span, final double returnRate) {
1819             super(date, typeOfData, systemConfigurationId, numberOfPointsRecorded,
1820                     numberOfPointsUsed, oneWayDistance, systemDelay, delayShift, rms, skew,
1821                     kurtosis, peakMinusMean, typeIndicator, shiftTypeIndicator,
1822                     detectorChannel, span, returnRate);
1823         }
1824 
1825         /**
1826          * Get a string representation of the instance in the CRD format.
1827          * @return a string representation of the instance, in the CRD format.
1828          */
1829         @DefaultDataContext
1830         @Override
1831         public String toCrdString() {
1832             return String.format(Locale.US, "41 %s", toString());
1833         }
1834 
1835     }
1836 
1837     /**
1838      * Session (Pass) Statistics Record.
1839      * @since 12.0
1840      */
1841     public static class SessionStatistics {
1842 
1843         /** System configuration ID. */
1844         private final String systemConfigurationId;
1845 
1846         /** Session RMS from the mean of raw accepted time-of-flight values minus the trend function. */
1847         private final double rms;
1848 
1849         /** Session skewness from the mean of raw accepted time-of-flight values minus the trend function. */
1850         private final double skewness;
1851 
1852         /** Session kurtosis from the mean of raw accepted time-of-flight values minus the trend function. */
1853         private final double kurtosis;
1854 
1855         /** Session peak – mean value. */
1856         private final double peakMinusMean;
1857 
1858         /**
1859          * Data quality assessment indicator.
1860          * <ul>
1861          * <li>0=undefined or no comment</li>
1862          * <li>1=clear, easily filtered data, with little or no noise</li>
1863          * <li>2=clear data with some noise; filtering is slightly compromised by noise level</li>
1864          * <li>3=clear data with a significant amount of noise, or weak data with little noise. Data are certainly
1865          * present, but filtering is difficult.</li>
1866          * <li>4=unclear data; data appear marginally to be present, but are very difficult to separate from noise
1867          * during filtering. Signal to noise ratio can be less than 1:1.</li>
1868          * <li>5=no data apparent</li>
1869          * </ul>
1870          */
1871         private final int dataQulityIndicator;
1872 
1873         /**
1874          * Constructor.
1875          * @param systemConfigurationId system configuration ID
1876          * @param rms session RMS from the mean of raw accepted time-of-flight values minus the trend function
1877          * @param skewness session skewness from the mean of raw accepted time-of-flight values minus the trend function
1878          * @param kurtosis session kurtosis from the mean of raw accepted time-of-flight values minus the trend function
1879          * @param peakMinusMean session peak – mean value
1880          * @param dataQulityIndicator data quality assessment indicator
1881          */
1882         public SessionStatistics(final String systemConfigurationId,
1883                                  final double rms, final double skewness,
1884                                  final double kurtosis,
1885                                  final double peakMinusMean,
1886                                  final int dataQulityIndicator) {
1887             this.systemConfigurationId = systemConfigurationId;
1888             this.rms                   = rms;
1889             this.skewness              = skewness;
1890             this.kurtosis              = kurtosis;
1891             this.peakMinusMean         = peakMinusMean;
1892             this.dataQulityIndicator   = dataQulityIndicator;
1893         }
1894 
1895         /**
1896          * Get system configuration id.
1897          * @return the system configuration id
1898          */
1899         public String getSystemConfigurationId() {
1900             return systemConfigurationId;
1901         }
1902 
1903         /**
1904          * Get the session RMS from the mean of raw accepted time-of-flight values minus the trend function.
1905          * @return the session RMS
1906          */
1907         public double getRms() {
1908             return rms;
1909         }
1910 
1911         /**
1912          * Get the session skewness from the mean of raw accepted time-of-flight values minus the trend function.
1913          * @return the session skewness
1914          */
1915         public double getSkewness() {
1916             return skewness;
1917         }
1918 
1919         /**
1920          * Get the session kurtosis from the mean of raw accepted time-of-flight values minus the trend function.
1921          * @return the session kurtosis
1922          */
1923         public double getKurtosis() {
1924             return kurtosis;
1925         }
1926 
1927         /**
1928          * Get the session peak – mean value.
1929          * @return the session peak – mean value
1930          */
1931         public double getPeakMinusMean() {
1932             return peakMinusMean;
1933         }
1934 
1935         /**
1936          * Get the data quality assessment indicator
1937          * <ul>
1938          * <li>0=undefined or no comment</li>
1939          * <li>1=clear, easily filtered data, with little or no noise</li>
1940          * <li>2=clear data with some noise; filtering is slightly compromised by noise level</li>
1941          * <li>3=clear data with a significant amount of noise, or weak data with little noise. Data are certainly
1942          * present, but filtering is difficult.</li>
1943          * <li>4=unclear data; data appear marginally to be present, but are very difficult to separate from noise
1944          * during filtering. Signal to noise ratio can be less than 1:1.</li>
1945          * <li>5=no data apparent</li>
1946          * </ul>
1947          * @return the data quality assessment indicator
1948          */
1949         public int getDataQulityIndicator() {
1950             return dataQulityIndicator;
1951         }
1952 
1953         /**
1954          * Get a string representation of the instance in the CRD format.
1955          * @return a string representation of the instance, in the CRD format.
1956          */
1957         public String toCrdString() {
1958             return String.format(Locale.US, "50 %s", toString());
1959         }
1960 
1961         @Override
1962         public String toString() {
1963             // CRD suggested format, excluding the record type
1964             // rms, peakMinusMean: s --> ps
1965             final String str = String.format(Locale.US, "%4s %6.1f %7.3f %7.3f %6.1f %1d",
1966                     systemConfigurationId, rms * 1e12, skewness, kurtosis,
1967                     peakMinusMean * 1e12, dataQulityIndicator);
1968             return handleNaN(str).replace(',', '.');
1969         }
1970 
1971     }
1972 
1973 }