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