1   /* Copyright 2002-2024 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.ilrs;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.SortedSet;
23  import java.util.TreeSet;
24  import java.util.regex.Pattern;
25  import java.util.stream.Collectors;
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 List<String> comments;
55  
56      /** List of data blocks contain in the CDR file. */
57      private 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("%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 List<RangeMeasurement> rangeData;
130 
131         /** Meteorological records. */
132         private final SortedSet<MeteorologicalMeasurement> meteoData;
133 
134         /** Pointing angles records. */
135         private List<AnglesMeasurement> anglesData;
136 
137         /** RangeSupplement records. */
138         private List<RangeSupplement> rangeSupplementData;
139 
140         /** Session statistics record(s). */
141         private List<SessionStatistics> sessionStatisticsData;
142 
143         /** Calibration Record(s). */
144         private List<Calibration> calibrationData;
145 
146         /** Calibration detail record(s). */
147         private 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.get(0);
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<Calibration>();
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<CalibrationDetail>();
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 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("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()).getTime()
666                     .getSecondsInLocalDay();
667 
668             final String str = String.format(
669                     "%18.12f %18.12f %4s %1d %1d %1d %1d %5s %5s", sod,
670                     getTimeOfFlight(), getSystemConfigurationId(),
671                     getEpochEvent(), filterFlag, detectorChannel, stopNumber,
672                     formatIntegerOrNaN(receiveAmplitude, -1),
673                     formatIntegerOrNaN(transmitAmplitude, -1));
674             return handleNaN(str).replace(',', '.');
675         }
676 
677     }
678 
679     /**
680      * Range record -- Normal Point.
681      * @since 12.0
682      */
683     public static class NptRangeMeasurement extends RangeMeasurement {
684 
685         /** Normal point window length [s]. */
686         private final double windowLength;
687 
688         /** Number of raw ranges (after editing) compressed into the normal point. */
689         private final int numberOfRawRanges;
690 
691         /** Bin RMS from the mean of raw accepted time-of-flight values minus the trend function. */
692         private final double binRms;
693 
694         /** Bin skew from the mean of raw accepted time-of-flight values minus the trend function. */
695         private final double binSkew;
696 
697         /** Bin kurtosis from the mean of raw accepted time-of-flight values minus the trend function. */
698         private final double binKurtosis;
699 
700         /** Bin peak - mean value. */
701         private final double binPeakMinusMean;
702 
703         /** Return rate [%]. */
704         private final double returnRate;
705 
706         /** Detector channel. */
707         private final int detectorChannel;
708 
709         /**
710          * Constructor.
711          * @param date data epoch
712          * @param timeOfFlight time of flight in seconds
713          * @param epochEvent indicates the time event reference
714          * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
715          * @param systemConfigurationId System configuration id
716          */
717         public NptRangeMeasurement(final AbsoluteDate date,
718                                    final double timeOfFlight,
719                                    final int epochEvent, final double snr,
720                                    final String systemConfigurationId) {
721             this(date, timeOfFlight, epochEvent, snr, systemConfigurationId, -1,
722                     -1, Double.NaN, Double.NaN, Double.NaN, Double.NaN,
723                     Double.NaN, 0);
724         }
725 
726         /**
727          * Constructor.
728          * @param date data epoch
729          * @param timeOfFlight time of flight in seconds
730          * @param epochEvent indicates the time event reference
731          * @param snr signal to noise ratio (can be Double.NaN if unkonwn)
732          * @param systemConfigurationId System configuration id
733          * @param windowLength normal point window length
734          * @param numberOfRawRanges number of raw ranges (after editing) compressed into the normal point
735          * @param binRms Bin RMS from the mean of raw accepted time-of-flight values minus the trend function
736          * @param binSkew Bin skew from the mean of raw accepted time-of-flight values minus the trend function
737          * @param binKurtosis Bin kurtosis from the mean of raw accepted time-of-flight values minus the trend function
738          * @param binPeakMinusMean Bin peak - mean value
739          * @param returnRate Return rate [%]
740          * @param detectorChannel detector channel
741          */
742         public NptRangeMeasurement(final AbsoluteDate date,
743                                    final double timeOfFlight,
744                                    final int epochEvent, final double snr,
745                                    final String systemConfigurationId,
746                                    final double windowLength,
747                                    final int numberOfRawRanges,
748                                    final double binRms, final double binSkew,
749                                    final double binKurtosis,
750                                    final double binPeakMinusMean,
751                                    final double returnRate,
752                                    final int detectorChannel) {
753             super(date, timeOfFlight, epochEvent, snr, systemConfigurationId);
754 
755             this.windowLength      = windowLength;
756             this.numberOfRawRanges = numberOfRawRanges;
757             this.binSkew           = binSkew;
758             this.binKurtosis       = binKurtosis;
759             this.binPeakMinusMean  = binPeakMinusMean;
760             this.detectorChannel   = detectorChannel;
761             this.binRms            = binRms == -1.0e-12 ? Double.NaN : binRms; // -1=na, ps --> s
762             this.returnRate        = returnRate == -1 ? Double.NaN : returnRate; // -1=na
763         }
764 
765         /**
766          * Get the normal point window length.
767          * @return the normal point window length
768          */
769         public double getWindowLength() {
770             return windowLength;
771         }
772 
773         /**
774          * Get the umber of raw ranges (after editing) compressed into the normal point.
775          * @return the umber of raw ranges
776          */
777         public int getNumberOfRawRanges() {
778             return numberOfRawRanges;
779         }
780 
781         /**
782          * Get the bin RMS from the mean of raw accepted time-of-flight values minus the trend function.
783          * @return the bin RMS
784          */
785         public double getBinRms() {
786             return binRms;
787         }
788 
789         /**
790          * Get the bin skew from the mean of raw accepted time-of-flight values minus the trend function.
791          * @return the bin skew
792          */
793         public double getBinSkew() {
794             return binSkew;
795         }
796 
797         /**
798          * Get the bin kurtosis from the mean of raw accepted time-of-flight values minus the trend function.
799          * @return the bin kurtosis
800          */
801         public double getBinKurtosis() {
802             return binKurtosis;
803         }
804 
805         /**
806          * Get the bin peak - mean value.
807          * @return the bin peak - mean value
808          */
809         public double getBinPeakMinusMean() {
810             return binPeakMinusMean;
811         }
812 
813         /**
814          * Get the return rate.
815          * @return the return rate
816          */
817         public double getReturnRate() {
818             return returnRate;
819         }
820 
821         /**
822          * Get the detector channel.
823          * @return the detector channel
824          */
825         public int getDetectorChannel() {
826             return detectorChannel;
827         }
828 
829         /** {@inheritDoc} */
830         @Override
831         @DefaultDataContext
832         public String toCrdString() {
833             return String.format("11 %s", toString());
834         }
835 
836         @Override
837         @DefaultDataContext
838         public String toString() {
839             // CRD suggested format, excluding the record type
840             // binRms, binPeakMinusMean: s --> ps
841             // 'local' is already utc.
842             // Seconds of day (sod) is typically to 1 milllisec precision.
843             final double sod = getDate()
844                     .getComponents(TimeScalesFactory.getUTC()).getTime()
845                     .getSecondsInLocalDay();
846 
847             final String str = String.format(
848                     "%18.12f %18.12f %4s %1d %6.1f %6d %9.1f %7.3f %7.3f %9.1f %5.2f %1d %5.1f",
849                     sod, getTimeOfFlight(), getSystemConfigurationId(),
850                     getEpochEvent(), windowLength, numberOfRawRanges,
851                     binRms * 1e12, binSkew, binKurtosis,
852                     binPeakMinusMean * 1e12, returnRate, detectorChannel,
853                     getSnr());
854             return handleNaN(str).replace(',', '.');
855         }
856 
857     }
858 
859     /**
860      * Range Supplement Record.
861      * @since 12.0
862      */
863     public static class RangeSupplement implements TimeStamped {
864 
865         /** Data epoch. */
866         private AbsoluteDate date;
867 
868         /** System configuration ID. */
869         private final String systemConfigurationId;
870 
871         /** Tropospheric refraction correction (one-way). */
872         private final double troposphericRefractionCorrection;
873 
874         /** Target center of mass correction (one-way). */
875         private final double centerOfMassCorrection;
876 
877         /** Neutral density (ND) filter value. */
878         private final double ndFilterValue;
879 
880         /** Time bias applied. */
881         private final double timeBiasApplied;
882 
883         /** Range rate. */
884         private final double rangeRate;
885 
886         /**
887          * Constructor.
888          * @param date data epoch
889          * @param systemConfigurationId system configuration ID
890          * @param troposphericRefractionCorrection tropospheric refraction correction (one-way)
891          * @param centerOfMassCorrection target center of mass correction (one-way)
892          * @param ndFilterValue Neutral density (ND) filter value
893          * @param timeBiasApplied Time bias applied
894          * @param rangeRate Range rate
895          */
896         public RangeSupplement(final AbsoluteDate date,
897                                final String systemConfigurationId,
898                                final double troposphericRefractionCorrection,
899                                final double centerOfMassCorrection,
900                                final double ndFilterValue,
901                                final double timeBiasApplied,
902                                final double rangeRate) {
903             this.date                             = date;
904             this.systemConfigurationId            = systemConfigurationId;
905             this.troposphericRefractionCorrection = troposphericRefractionCorrection;
906             this.centerOfMassCorrection           = centerOfMassCorrection;
907             this.ndFilterValue                    = ndFilterValue;
908             this.timeBiasApplied                  = timeBiasApplied;
909             this.rangeRate                        = rangeRate;
910         }
911 
912         @Override
913         public AbsoluteDate getDate() {
914             return date;
915         }
916 
917         /**
918          * Get the system configuration id.
919          * @return the system configuration id
920          */
921         public String getSystemConfigurationId() {
922             return systemConfigurationId;
923         }
924 
925         /**
926          * Get the tropospheric refraction correction.
927          * @return the tropospheric refraction correction
928          */
929         public double getTroposphericRefractionCorrection() {
930             return troposphericRefractionCorrection;
931         }
932 
933         /**
934          * Get the target center of mass.
935          * @return the target center of mass
936          */
937         public double getCenterOfMassCorrection() {
938             return centerOfMassCorrection;
939         }
940 
941         /**
942          * Get the neutral density (ND) filter value.
943          * @return the neutral density (ND) filter value
944          */
945         public double getNdFilterValue() {
946             return ndFilterValue;
947         }
948 
949         /**
950          * Get the time bias applied.
951          * @return the time bias applied
952          */
953         public double getTimeBiasApplied() {
954             return timeBiasApplied;
955         }
956 
957         /**
958          * Get the range rate.
959          * @return the range rate
960          */
961         public double getRangeRate() {
962             return rangeRate;
963         }
964 
965         /**
966          * Get a string representation of the instance in the CRD format.
967          * @return a string representation of the instance, in the CRD format.
968          */
969         @DefaultDataContext
970         public String toCrdString() {
971             return String.format("12 %s", toString());
972         }
973 
974         @Override
975         @DefaultDataContext
976         public String toString() {
977             // CRD suggested format, excluding the record type
978             // troposphericRefractionCorrection: s --> ps
979             // 'local' is already utc.
980             // Seconds of day (sod) is typically to 1 milllisec precision.
981             final double sod = getDate()
982                     .getComponents(TimeScalesFactory.getUTC()).getTime()
983                     .getSecondsInLocalDay();
984 
985             final String str = String.format(
986                     "%18.12f %4s %6.1f %6.4f %5.2f %8.4f %f", sod,
987                     getSystemConfigurationId(),
988                     troposphericRefractionCorrection * 1e12,
989                     centerOfMassCorrection, ndFilterValue, timeBiasApplied,
990                     rangeRate);
991             return handleNaN(str).replace(',', '.');
992         }
993 
994     }
995 
996     /** This data record contains a minimal set of meteorological data. */
997     public static class MeteorologicalMeasurement implements TimeStamped {
998 
999         /** Data epoch. */
1000         private AbsoluteDate date;
1001 
1002         /** Surface pressure [bar]. */
1003         private final double pressure;
1004 
1005         /** Surface temperature [K]. */
1006         private final double temperature;
1007 
1008         /** Relative humidity at the surface [%]. */
1009         private final double humidity;
1010 
1011         /** Origin of values.
1012          * 0=measured values, 1=interpolated values
1013          */
1014         private final int originOfValues;
1015 
1016         /**
1017          * Constructor.
1018          * @param date data epoch
1019          * @param pressure the surface pressure in bars
1020          * @param temperature the surface temperature in degrees Kelvin
1021          * @param humidity the relative humidity at the surface in percents
1022          */
1023         public MeteorologicalMeasurement(final AbsoluteDate date,
1024                                          final double pressure, final double temperature,
1025                                          final double humidity) {
1026             this(date, pressure, temperature, humidity, 0);
1027         }
1028 
1029         /**
1030          * Constructor.
1031          * @param date data epoch
1032          * @param pressure the surface pressure in bars
1033          * @param temperature the surface temperature in degrees Kelvin
1034          * @param humidity the relative humidity at the surface in percents
1035          * @param originOfValues Origin of values
1036          */
1037         public MeteorologicalMeasurement(final AbsoluteDate date, final double pressure, final double temperature,
1038                                          final double humidity, final int originOfValues) {
1039             this.date           = date;
1040             this.pressure       = pressure;
1041             this.temperature    = temperature;
1042             this.humidity       = humidity;
1043             this.originOfValues = originOfValues;
1044         }
1045 
1046         /**
1047          * Get the surface pressure.
1048          * @return the surface pressure in bars
1049          */
1050         public double getPressure() {
1051             return pressure;
1052         }
1053 
1054         /**
1055          * Get the surface temperature.
1056          * @return the surface temperature in degrees Kelvin
1057          */
1058         public double getTemperature() {
1059             return temperature;
1060         }
1061 
1062         /**
1063          * Get the relative humidity at the surface.
1064          * @return the relative humidity at the surface in percents
1065          */
1066         public double getHumidity() {
1067             return humidity;
1068         }
1069 
1070         /** {@inheritDoc} */
1071         @Override
1072         public AbsoluteDate getDate() {
1073             return date;
1074         }
1075 
1076         /** Get the origin of values.
1077          * 0=measure values
1078          * 1=interpolated values
1079          * @return the origin of values
1080          * @since 12.0
1081          */
1082         public int getOriginOfValues() {
1083             return originOfValues;
1084         }
1085 
1086         /**
1087          * Get a string representation of the instance in the CRD format.
1088          * @return a string representation of the instance, in the CRD format.
1089          * @since 12.0
1090          */
1091         @DefaultDataContext
1092         public String toCrdString() {
1093             return String.format("20 %s", toString());
1094         }
1095 
1096         @Override
1097         @DefaultDataContext
1098         public String toString() {
1099             // CRD suggested format, excluding the record type
1100             // pressure: bar --> mbar
1101             // 'local' is already utc.
1102             // Seconds of day (sod) is typically to 1 milllisec precision.
1103             final double sod = getDate()
1104                     .getComponents(TimeScalesFactory.getUTC()).getTime()
1105                     .getSecondsInLocalDay();
1106 
1107             final String str = String.format("%9.3f %7.2f %6.2f %4.0f %1d", sod,
1108                     pressure * 1e3, temperature, humidity, originOfValues);
1109             return handleNaN(str).replace(',', '.');
1110         }
1111     }
1112 
1113     /** Pointing angles record. */
1114     public static class AnglesMeasurement implements TimeStamped {
1115 
1116         /** Data epoch. */
1117         private AbsoluteDate date;
1118 
1119         /** Azimuth [rad]. */
1120         private final double azimuth;
1121 
1122         /** Elevation [rad]. */
1123         private final double elevation;
1124 
1125         /** Direction flag (0 = transmit &#38; receive ; 1 = transmit ; 2 = receive). */
1126         private final int directionFlag;
1127 
1128         /** Angle origin indicator.
1129          * 0 = unknown
1130          * 1 = computed
1131          * 2 = commanded (from predictions)
1132          * 3 = measured (from encoders)
1133          */
1134         private final int originIndicator;
1135 
1136         /** Refraction corrected. */
1137         private final boolean refractionCorrected;
1138 
1139         /** Azimuth rate [rad/sec]. */
1140         private final double azimuthRate;
1141 
1142         /** Elevation rate [rad/sec]. */
1143         private final double elevationRate;
1144 
1145         /**
1146          * Constructor.
1147          * @param date data epoch
1148          * @param azimuth azimuth angle in radians
1149          * @param elevation elevation angle in radians
1150          * @param directionFlag direction flag
1151          * @param originIndicator angle origin indicator
1152          * @param refractionCorrected flag to indicate if the refraction is corrected
1153          * @param azimuthRate azimuth rate in radians per second (equal to Double.NaN if unknown)
1154          * @param elevationRate elevation rate in radians per second (equal to Double.NaN if unknown)
1155          */
1156         public AnglesMeasurement(final AbsoluteDate date, final double azimuth,
1157                                  final double elevation, final int directionFlag,
1158                                  final int originIndicator,
1159                                  final boolean refractionCorrected,
1160                                  final double azimuthRate, final double elevationRate) {
1161             this.date                = date;
1162             this.azimuth             = azimuth;
1163             this.elevation           = elevation;
1164             this.directionFlag       = directionFlag;
1165             this.originIndicator     = originIndicator;
1166             this.refractionCorrected = refractionCorrected;
1167             this.azimuthRate         = azimuthRate;
1168             this.elevationRate       = elevationRate;
1169         }
1170 
1171         /**
1172          * Get the azimuth angle.
1173          * @return the azimuth angle in radians
1174          */
1175         public double getAzimuth() {
1176             return azimuth;
1177         }
1178 
1179         /**
1180          * Get the elevation angle.
1181          * @return the elevation angle in radians
1182          */
1183         public double getElevation() {
1184             return elevation;
1185         }
1186 
1187         /**
1188          * Get the direction flag (0 = transmit &#38; receive ; 1 = transmit ; 2 = receive).
1189          * @return the direction flag
1190          */
1191         public int getDirectionFlag() {
1192             return directionFlag;
1193         }
1194 
1195         /**
1196          * Get the angle origin indicator.
1197          * <p>
1198          * 0 = unknown;
1199          * 1 = computed;
1200          * 2 = commanded (from predictions);
1201          * 3 = measured (from encoders)
1202          * </p>
1203          * @return the angle origin indicator
1204          */
1205         public int getOriginIndicator() {
1206             return originIndicator;
1207         }
1208 
1209         /**
1210          * Get the flag indicating if the refraction is corrected.
1211          * @return true if refraction is corrected
1212          */
1213         public boolean isRefractionCorrected() {
1214             return refractionCorrected;
1215         }
1216 
1217         /**
1218          * Get the azimuth rate.
1219          * <p>
1220          * Is equal to Double.NaN if the value is unknown.
1221          * </p>
1222          * @return the azimuth rate in radians per second
1223          */
1224         public double getAzimuthRate() {
1225             return azimuthRate;
1226         }
1227 
1228         /**
1229          * Get the elevation rate.
1230          * <p>
1231          * Is equal to Double.NaN if the value is unknown.
1232          * </p>
1233          * @return the elevation rate in radians per second
1234          */
1235         public double getElevationRate() {
1236             return elevationRate;
1237         }
1238 
1239         /** {@inheritDoc} */
1240         @Override
1241         public AbsoluteDate getDate() {
1242             return date;
1243         }
1244 
1245         /**
1246          * Get a string representation of the instance in the CRD format.
1247          * @return a string representation of the instance, in the CRD format.
1248          * @since 12.0
1249          */
1250         @DefaultDataContext
1251         public String toCrdString() {
1252             return String.format("30 %s", toString());
1253         }
1254 
1255         @Override
1256         @DefaultDataContext
1257         public String toString() {
1258             // CRD suggested format, excluding the record type
1259             // azimuth, elevation: rad --> deg
1260             // azimuthRate, elevationRate: rad/s --> deg/s
1261             // 'local' is already utc.
1262             // Seconds of day (sod) is typically to 1 milllisec precision.
1263             final double sod = getDate()
1264                     .getComponents(TimeScalesFactory.getUTC()).getTime()
1265                     .getSecondsInLocalDay();
1266 
1267             final String str = String.format(
1268                     "%9.3f %8.4f %8.4f %1d %1d %1d %10.7f %10.7f", sod,
1269                     FastMath.toDegrees(azimuth), FastMath.toDegrees(elevation),
1270                     directionFlag, originIndicator, refractionCorrected ? 1 : 0,
1271                     FastMath.toDegrees(azimuthRate),
1272                     FastMath.toDegrees(elevationRate));
1273             return handleNaN(str).replace(',', '.');
1274         }
1275     }
1276 
1277     /** Meteorological data. */
1278     public static class Meteo {
1279 
1280         /** Number of neighbors for meteo data interpolation. */
1281         private static final int N_NEIGHBORS = 2;
1282 
1283         /** First available date. */
1284         private final AbsoluteDate firstDate;
1285 
1286         /** Last available date. */
1287         private final AbsoluteDate lastDate;
1288 
1289         /** Previous set of meteorological parameters. */
1290         private transient MeteorologicalMeasurement previousParam;
1291 
1292         /** Next set of solar meteorological parameters. */
1293         private transient MeteorologicalMeasurement nextParam;
1294 
1295         /** List of meteo data. */
1296         private final transient ImmutableTimeStampedCache<MeteorologicalMeasurement> meteo;
1297 
1298         /**
1299          * Constructor.
1300          * @param meteoData list of meteo data
1301          */
1302         public Meteo(final SortedSet<MeteorologicalMeasurement> meteoData) {
1303 
1304             // Size
1305             final int neighborsSize = (meteoData.size() < 2) ? meteoData.size() : N_NEIGHBORS;
1306 
1307             // Check neighbors size
1308             if (neighborsSize == 0) {
1309 
1310                 // Meteo data -> empty cache
1311                 this.meteo = ImmutableTimeStampedCache.emptyCache();
1312 
1313                 // Null epochs (will ne be used)
1314                 this.firstDate = null;
1315                 this.lastDate  = null;
1316 
1317             } else {
1318 
1319                 // Meteo data
1320                 this.meteo = new ImmutableTimeStampedCache<MeteorologicalMeasurement>(neighborsSize, meteoData);
1321 
1322                 // Initialize first and last available dates
1323                 this.firstDate = meteoData.first().getDate();
1324                 this.lastDate  = meteoData.last().getDate();
1325 
1326             }
1327 
1328         }
1329 
1330         /** Get an unmodifiable view of the tabulated meteorological data.
1331          * @return unmodifiable view of the tabulated meteorological data
1332          * @since 11.0
1333          */
1334         public List<MeteorologicalMeasurement> getData() {
1335             return meteo.getAll();
1336         }
1337 
1338         /**
1339          * Get the meteorological parameters at a given date.
1340          * @param date date when user wants the meteorological parameters
1341          * @return the meteorological parameters at date (can be null if
1342          *         meteorological data are empty).
1343          */
1344         public MeteorologicalMeasurement getMeteo(final AbsoluteDate date) {
1345 
1346             // Check if meteorological data are available
1347             if (meteo.getMaxNeighborsSize() == 0) {
1348                 return null;
1349             }
1350 
1351             // Interpolating two neighboring meteorological parameters
1352             bracketDate(date);
1353             if (date.durationFrom(firstDate) <= 0 || date.durationFrom(lastDate) > 0) {
1354                 // Date is outside file range
1355                 return previousParam;
1356             } else {
1357                 // Perform interpolations
1358                 final double pressure    = getLinearInterpolation(date, previousParam.getPressure(), nextParam.getPressure());
1359                 final double temperature = getLinearInterpolation(date, previousParam.getTemperature(), nextParam.getTemperature());
1360                 final double humidity    = getLinearInterpolation(date, previousParam.getHumidity(), nextParam.getHumidity());
1361                 return new MeteorologicalMeasurement(date, pressure, temperature, humidity);
1362             }
1363 
1364         }
1365 
1366         /**
1367          * Find the data bracketing a specified date.
1368          * @param date date to bracket
1369          */
1370         private void bracketDate(final AbsoluteDate date) {
1371 
1372             // don't search if the cached selection is fine
1373             if (previousParam != null &&
1374                 date.durationFrom(previousParam.getDate()) > 0 &&
1375                 date.durationFrom(nextParam.getDate()) <= 0) {
1376                 return;
1377             }
1378 
1379             // Initialize previous and next parameters
1380             if (date.durationFrom(firstDate) <= 0) {
1381                 // Current date is before the first date
1382                 previousParam = meteo.getEarliest();
1383                 nextParam     = previousParam;
1384             } else if (date.durationFrom(lastDate) > 0) {
1385                 // Current date is after the last date
1386                 previousParam = meteo.getLatest();
1387                 nextParam     = previousParam;
1388             } else {
1389                 // Current date is between first and last date
1390                 final List<MeteorologicalMeasurement> neighbors = meteo.getNeighbors(date).collect(Collectors.toList());
1391                 previousParam = neighbors.get(0);
1392                 nextParam     = neighbors.get(1);
1393             }
1394 
1395         }
1396 
1397         /**
1398          * Performs a linear interpolation between two values The weights are computed
1399          * from the time delta between previous date, current date, next date.
1400          * @param date the current date
1401          * @param previousValue the value at previous date
1402          * @param nextValue the value at next date
1403          * @return the value interpolated for the current date
1404          */
1405         private double getLinearInterpolation(final AbsoluteDate date,
1406                                               final double previousValue,
1407                                               final double nextValue) {
1408             // Perform a linear interpolation
1409             final AbsoluteDate previousDate = previousParam.getDate();
1410             final AbsoluteDate currentDate = nextParam.getDate();
1411             final double dt = currentDate.durationFrom(previousDate);
1412             final double previousWeight = currentDate.durationFrom(date) / dt;
1413             final double nextWeight = date.durationFrom(previousDate) / dt;
1414 
1415             // Returns the data interpolated at the date
1416             return previousValue * previousWeight + nextValue * nextWeight;
1417         }
1418 
1419     }
1420 
1421     /**
1422      * Calibration Record.
1423      * @since 12.0
1424      */
1425     public static class Calibration implements TimeStamped {
1426 
1427         /** Data epoch. */
1428         private final AbsoluteDate date;
1429 
1430         /**
1431          * Type of data.
1432          *
1433          * 0=station combined transmit and receive calibration (“normal” SLR/LLR)
1434          * 1=station transmit calibration (e.g., one-way ranging to transponders)
1435          * 2=station receive calibration
1436          * 3=target combined transmit and receive calibrations
1437          * 4=target transmit calibration
1438          * 5=target receive calibration
1439          */
1440         private final int typeOfData;
1441 
1442         /** System configuration ID. */
1443         private final String systemConfigurationId;
1444 
1445         /** Number of data points recorded. */
1446         private final int numberOfPointsRecorded;
1447 
1448         /** Number of data points used. */
1449         private final int numberOfPointsUsed;
1450 
1451         /** One-way target distance (meters, nominal). */
1452         private final double oneWayDistance;
1453 
1454         /** Calibration System Delay. */
1455         private final double systemDelay;
1456 
1457         /** Calibration Delay Shift - a measure of calibration stability. */
1458         private final double delayShift;
1459 
1460         /** RMS of raw system delay. */
1461         private final double rms;
1462 
1463         /** Skew of raw system delay values from the mean. */
1464         private final double skew;
1465 
1466         /** Kurtosis of raw system delay values from the mean. */
1467         private final double kurtosis;
1468 
1469         /** System delay peak – mean value. */
1470         private final double peakMinusMean;
1471 
1472         /**
1473          * Calibration Type Indicator.
1474          *
1475          * 0=not used or undefined
1476          * 1=nominal (from once off assessment)
1477          * 2=external calibrations
1478          * 3=internal calibrations – telescope
1479          * 4=internal calibrations – building
1480          * 5=burst calibrations
1481          * 6=other
1482          */
1483         private final int typeIndicator;
1484 
1485         /**
1486          * Calibration Shift Type Indicator.
1487          *
1488          * 0=not used or undefined
1489          * 1=nominal (from once off assessment)
1490          * 2=pre- to post- Shift
1491          * 3=minimum to maximum
1492          * 4=other
1493          */
1494         private final int shiftTypeIndicator;
1495 
1496         /** Detector Channel.
1497          *
1498          * 0=not applicable or “all”
1499          * 1-4 for quadrant
1500          * 1-n for many channels
1501          */
1502         private final int detectorChannel;
1503 
1504         /**
1505          * Calibration Span.
1506          *
1507          * 0 = not applicable (e.g. Calibration type indicator is “nominal”)
1508          * 1 = Pre-calibration only
1509          * 2 = Post-calibration only
1510          * 3 = Combined (pre- and post-calibrations or multiple)
1511          * 4 = Real-time calibration (data taken while ranging to a satellite)
1512          */
1513         private final int span;
1514 
1515         /** Return Rate (%). */
1516         private final double returnRate;
1517 
1518         /**
1519          * Constructor.
1520          * @param date data epoch
1521          * @param typeOfData type of data
1522          * @param systemConfigurationId system configuration id
1523          * @param numberOfPointsRecorded number of data points recorded
1524          * @param numberOfPointsUsed number of data points used
1525          * @param oneWayDistance one-way target distance (nominal)
1526          * @param systemDelay calibration system delay
1527          * @param delayShift calibration delay shift - a measure of calibration stability
1528          * @param rms RMS of raw system delay
1529          * @param skew skew of raw system delay values from the mean.
1530          * @param kurtosis kurtosis of raw system delay values from the mean.
1531          * @param peakMinusMean system delay peak – mean value
1532          * @param typeIndicator calibration type indicator
1533          * @param shiftTypeIndicator calibration shift type indicator
1534          * @param detectorChannel detector channel
1535          * @param span calibration span
1536          * @param returnRate return rate (%)
1537          */
1538         public Calibration(final AbsoluteDate date, final int typeOfData,
1539                            final String systemConfigurationId,
1540                            final int numberOfPointsRecorded,
1541                            final int numberOfPointsUsed,
1542                            final double oneWayDistance,
1543                            final double systemDelay, final double delayShift,
1544                            final double rms, final double skew,
1545                            final double kurtosis, final double peakMinusMean,
1546                            final int typeIndicator, final int shiftTypeIndicator,
1547                            final int detectorChannel, final int span,
1548                            final double returnRate) {
1549             this.date                   = date;
1550             this.typeOfData             = typeOfData;
1551             this.systemConfigurationId  = systemConfigurationId;
1552             this.numberOfPointsRecorded = numberOfPointsRecorded;
1553             this.numberOfPointsUsed     = numberOfPointsUsed;
1554             this.systemDelay            = systemDelay;
1555             this.delayShift             = delayShift;
1556             this.rms                    = rms;
1557             this.skew                   = skew;
1558             this.kurtosis               = kurtosis;
1559             this.peakMinusMean          = peakMinusMean;
1560             this.typeIndicator          = typeIndicator;
1561             this.shiftTypeIndicator     = shiftTypeIndicator;
1562             this.detectorChannel        = detectorChannel;
1563             this.span                   = span;
1564             this.returnRate             = returnRate;
1565             this.oneWayDistance         = oneWayDistance == -1 ? Double.NaN : oneWayDistance; // -1=na
1566         }
1567 
1568         @Override
1569         public AbsoluteDate getDate() {
1570             return date;
1571         }
1572 
1573         /**
1574          * Get the type of data.
1575          *
1576          * <ul>
1577          * <li>0=station combined transmit and receive calibration (“normal” SLR/LLR)
1578          * <li>1=station transmit calibration (e.g., one-way ranging to transponders)
1579          * <li>2=station receive calibration
1580          * <li>3=target combined transmit and receive calibrations
1581          * <li>4=target transmit calibration
1582          * <li>5=target receive calibration
1583          * </ul>
1584          * @return the type of data
1585          */
1586         public int getTypeOfData() {
1587             return typeOfData;
1588         }
1589 
1590         /**
1591          * Get the system configuration id.
1592          * @return the system configuration id
1593          */
1594         public String getSystemConfigurationId() {
1595             return systemConfigurationId;
1596         }
1597 
1598         /**
1599          * Get the number of data points recorded.
1600          * @return the number of data points recorded, -1 if no information
1601          */
1602         public int getNumberOfPointsRecorded() {
1603             return numberOfPointsRecorded;
1604         }
1605 
1606         /**
1607          * Get the number of data points used.
1608          * @return the number of data points used, -1 if no information
1609          */
1610         public int getNumberOfPointsUsed() {
1611             return numberOfPointsUsed;
1612         }
1613 
1614         /**
1615          * Get the one-way target distance (nominal).
1616          * @return the one-way target distance (nominal)
1617          */
1618         public double getOneWayDistance() {
1619             return oneWayDistance;
1620         }
1621 
1622         /**
1623          * Get the calibration system delay.
1624          * @return the calibration system delay
1625          */
1626         public double getSystemDelay() {
1627             return systemDelay;
1628         }
1629 
1630         /**
1631          * Get the calibration delay shift.
1632          * @return the calibration delay shift
1633          */
1634         public double getDelayShift() {
1635             return delayShift;
1636         }
1637 
1638         /**
1639          * Get the rms of raw system delay.
1640          * @return the rms of raw system delay
1641          */
1642         public double getRms() {
1643             return rms;
1644         }
1645 
1646         /**
1647          * Get the skew of raw system delay values from the mean.
1648          * @return the skew of raw system delay values from the mean.
1649          */
1650         public double getSkew() {
1651             return skew;
1652         }
1653 
1654         /**
1655          * Get the kurtosis of raw system delay values from the mean.
1656          * @return the kurtosis of raw system delay values from the mean.
1657          */
1658         public double getKurtosis() {
1659             return kurtosis;
1660         }
1661 
1662         /**
1663          * Get the system delay peak – mean value.
1664          * @return the system delay peak – mean value
1665          */
1666         public double getPeakMinusMean() {
1667             return peakMinusMean;
1668         }
1669 
1670         /**
1671          * Get the calibration type indicator.
1672          *
1673          * <ul>
1674          * <li>0=not used or undefined
1675          * <li>1=nominal (from once off assessment)
1676          * <li>2=external calibrations
1677          * <li>3=internal calibrations – telescope
1678          * <li>4=internal calibrations – building
1679          * <li>5=burst calibrations
1680          * <li>6=other
1681          * </ul>
1682          * @return the calibration type indicator
1683          */
1684         public int getTypeIndicator() {
1685             return typeIndicator;
1686         }
1687 
1688         /**
1689          * Get the calibration shift type indicator.
1690          *
1691          * <ul>
1692          * <li>0=not used or undefined
1693          * <li>1=nominal (from once off assessment)
1694          * <li>2=pre- to post- Shift
1695          * <li>3=minimum to maximum
1696          * <li>4=other
1697          * </ul>
1698          * @return the calibration shift type indicator
1699          */
1700         public int getShiftTypeIndicator() {
1701             return shiftTypeIndicator;
1702         }
1703 
1704         /**
1705          * Get the detector channel.
1706          *
1707          * <ul>
1708          * <li>0=not applicable or “all”
1709          * <li>1-4 for quadrant
1710          * <li>1-n for many channels
1711          * </ul>
1712          * @return the detector channel
1713          */
1714         public int getDetectorChannel() {
1715             return detectorChannel;
1716         }
1717 
1718         /**
1719          * Get the calibration span.
1720          *
1721          * <ul>
1722          * <li>0 = not applicable (e.g. Calibration type indicator is “nominal”)
1723          * <li>1 = Pre-calibration only
1724          * <li>2 = Post-calibration only
1725          * <li>3 = Combined (pre- and post-calibrations or multiple)
1726          * <li>4 = Real-time calibration (data taken while ranging to a satellite)
1727          * </ul>
1728          * @return the calibration span
1729          */
1730         public int getSpan() {
1731             return span;
1732         }
1733 
1734         /**
1735          * Get the return rate.
1736          * @return the return rate
1737          */
1738         public double getReturnRate() {
1739             return returnRate;
1740         }
1741 
1742         /**
1743          * Get a string representation of the instance in the CRD format.
1744          * @return a string representation of the instance, in the CRD format.
1745          */
1746         @DefaultDataContext
1747         public String toCrdString() {
1748             return String.format("40 %s", toString());
1749         }
1750 
1751         @Override
1752         @DefaultDataContext
1753         public String toString() {
1754             // CRD suggested format, excluding the record type
1755             // systemDelay, delayShift: s --> ps
1756             // rms, peakMinusMean: s --> ps
1757             // 'local' is already utc.
1758             // Seconds of day (sod) is typically to 1 milllisec precision.
1759             final double sod = getDate()
1760                     .getComponents(TimeScalesFactory.getUTC()).getTime()
1761                     .getSecondsInLocalDay();
1762 
1763             final String str = String.format(
1764                     "%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",
1765                     sod, typeOfData, systemConfigurationId,
1766                     formatIntegerOrNaN(numberOfPointsRecorded, -1),
1767                     formatIntegerOrNaN(numberOfPointsUsed, -1), oneWayDistance,
1768                     systemDelay * 1e12, delayShift * 1e12, rms * 1e12, skew,
1769                     kurtosis, peakMinusMean * 1e12, typeIndicator,
1770                     shiftTypeIndicator, detectorChannel, span, returnRate);
1771             return handleNaN(str).replace(',', '.');
1772         }
1773 
1774     }
1775 
1776     /**
1777      * Calibration Detail Record.
1778      * @since 12.0
1779      */
1780     public static class CalibrationDetail extends Calibration {
1781         // same as Calibration record except that the record type is '41' rather than '40'.
1782 
1783         /**
1784          * Constructor.
1785          * @param date data epoch
1786          * @param typeOfData type of data
1787          * @param systemConfigurationId system configuration id
1788          * @param numberOfPointsRecorded number of data points recorded
1789          * @param numberOfPointsUsed number of data points used
1790          * @param oneWayDistance one-way target distance (nominal)
1791          * @param systemDelay calibration system delay
1792          * @param delayShift calibration delay shift - a measure of calibration stability
1793          * @param rms RMS of raw system delay
1794          * @param skew skew of raw system delay values from the mean.
1795          * @param kurtosis kurtosis of raw system delay values from the mean.
1796          * @param peakMinusMean system delay peak – mean value
1797          * @param typeIndicator calibration type indicator
1798          * @param shiftTypeIndicator calibration shift type indicator
1799          * @param detectorChannel detector channel
1800          * @param span calibration span
1801          * @param returnRate return rate (%)
1802          */
1803         public CalibrationDetail(final AbsoluteDate date, final int typeOfData,
1804                                  final String systemConfigurationId,
1805                                  final int numberOfPointsRecorded,
1806                                  final int numberOfPointsUsed, final double oneWayDistance,
1807                                  final double systemDelay, final double delayShift,
1808                                  final double rms, final double skew, final double kurtosis,
1809                                  final double peakMinusMean, final int typeIndicator,
1810                                  final int shiftTypeIndicator, final int detectorChannel,
1811                                  final int span, final double returnRate) {
1812             super(date, typeOfData, systemConfigurationId, numberOfPointsRecorded,
1813                     numberOfPointsUsed, oneWayDistance, systemDelay, delayShift, rms, skew,
1814                     kurtosis, peakMinusMean, typeIndicator, shiftTypeIndicator,
1815                     detectorChannel, span, returnRate);
1816         }
1817 
1818         /**
1819          * Get a string representation of the instance in the CRD format.
1820          * @return a string representation of the instance, in the CRD format.
1821          */
1822         @DefaultDataContext
1823         public String toCrdString() {
1824             return String.format("41 %s", toString());
1825         }
1826 
1827     }
1828 
1829     /**
1830      * Session (Pass) Statistics Record.
1831      * @since 12.0
1832      */
1833     public static class SessionStatistics {
1834 
1835         /** System configuration ID. */
1836         private final String systemConfigurationId;
1837 
1838         /** Session RMS from the mean of raw accepted time-of-flight values minus the trend function. */
1839         private final double rms;
1840 
1841         /** Session skewness from the mean of raw accepted time-of-flight values minus the trend function. */
1842         private final double skewness;
1843 
1844         /** Session kurtosis from the mean of raw accepted time-of-flight values minus the trend function. */
1845         private final double kurtosis;
1846 
1847         /** Session peak – mean value. */
1848         private final double peakMinusMean;
1849 
1850         /**
1851          * Data quality assessment indicator.
1852          * <ul>
1853          * <li>0=undefined or no comment</li>
1854          * <li>1=clear, easily filtered data, with little or no noise</li>
1855          * <li>2=clear data with some noise; filtering is slightly compromised by noise level</li>
1856          * <li>3=clear data with a significant amount of noise, or weak data with little noise. Data are certainly
1857          * present, but filtering is difficult.</li>
1858          * <li>4=unclear data; data appear marginally to be present, but are very difficult to separate from noise
1859          * during filtering. Signal to noise ratio can be less than 1:1.</li>
1860          * <li>5=no data apparent</li>
1861          * </ul>
1862          */
1863         private final int dataQulityIndicator;
1864 
1865         /**
1866          * Constructor.
1867          * @param systemConfigurationId system configuration ID
1868          * @param rms session RMS from the mean of raw accepted time-of-flight values minus the trend function
1869          * @param skewness session skewness from the mean of raw accepted time-of-flight values minus the trend function
1870          * @param kurtosis session kurtosis from the mean of raw accepted time-of-flight values minus the trend function
1871          * @param peakMinusMean session peak – mean value
1872          * @param dataQulityIndicator data quality assessment indicator
1873          */
1874         public SessionStatistics(final String systemConfigurationId,
1875                                  final double rms, final double skewness,
1876                                  final double kurtosis,
1877                                  final double peakMinusMean,
1878                                  final int dataQulityIndicator) {
1879             this.systemConfigurationId = systemConfigurationId;
1880             this.rms                   = rms;
1881             this.skewness              = skewness;
1882             this.kurtosis              = kurtosis;
1883             this.peakMinusMean         = peakMinusMean;
1884             this.dataQulityIndicator   = dataQulityIndicator;
1885         }
1886 
1887         /**
1888          * Get system configuration id.
1889          * @return the system configuration id
1890          */
1891         public String getSystemConfigurationId() {
1892             return systemConfigurationId;
1893         }
1894 
1895         /**
1896          * Get the session RMS from the mean of raw accepted time-of-flight values minus the trend function.
1897          * @return the session RMS
1898          */
1899         public double getRms() {
1900             return rms;
1901         }
1902 
1903         /**
1904          * Get the session skewness from the mean of raw accepted time-of-flight values minus the trend function.
1905          * @return the session skewness
1906          */
1907         public double getSkewness() {
1908             return skewness;
1909         }
1910 
1911         /**
1912          * Get the session kurtosis from the mean of raw accepted time-of-flight values minus the trend function.
1913          * @return the session kurtosis
1914          */
1915         public double getKurtosis() {
1916             return kurtosis;
1917         }
1918 
1919         /**
1920          * Get the session peak – mean value.
1921          * @return the session peak – mean value
1922          */
1923         public double getPeakMinusMean() {
1924             return peakMinusMean;
1925         }
1926 
1927         /**
1928          * Get the data quality assessment indicator
1929          * <ul>
1930          * <li>0=undefined or no comment</li>
1931          * <li>1=clear, easily filtered data, with little or no noise</li>
1932          * <li>2=clear data with some noise; filtering is slightly compromised by noise level</li>
1933          * <li>3=clear data with a significant amount of noise, or weak data with little noise. Data are certainly
1934          * present, but filtering is difficult.</li>
1935          * <li>4=unclear data; data appear marginally to be present, but are very difficult to separate from noise
1936          * during filtering. Signal to noise ratio can be less than 1:1.</li>
1937          * <li>5=no data apparent</li>
1938          * </ul>
1939          * @return the data quality assessment indicator
1940          */
1941         public int getDataQulityIndicator() {
1942             return dataQulityIndicator;
1943         }
1944 
1945         /**
1946          * Get a string representation of the instance in the CRD format.
1947          * @return a string representation of the instance, in the CRD format.
1948          */
1949         public String toCrdString() {
1950             return String.format("50 %s", toString());
1951         }
1952 
1953         @Override
1954         public String toString() {
1955             // CRD suggested format, excluding the record type
1956             // rms, peakMinusMean: s --> ps
1957             final String str = String.format("%4s %6.1f %7.3f %7.3f %6.1f %1d",
1958                     systemConfigurationId, rms * 1e12, skewness, kurtosis,
1959                     peakMinusMean * 1e12, dataQulityIndicator);
1960             return handleNaN(str).replace(',', '.');
1961         }
1962 
1963     }
1964 
1965 }