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.rinex.clock;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.SortedSet;
26  import java.util.TreeSet;
27  import java.util.function.Function;
28  
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitIllegalArgumentException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.files.rinex.AppliedDCBS;
33  import org.orekit.files.rinex.AppliedPCVS;
34  import org.orekit.frames.Frame;
35  import org.orekit.gnss.ObservationType;
36  import org.orekit.gnss.SatelliteSystem;
37  import org.orekit.gnss.TimeSystem;
38  import org.orekit.time.AbsoluteDate;
39  import org.orekit.time.ChronologicalComparator;
40  import org.orekit.time.ClockOffset;
41  import org.orekit.time.DateComponents;
42  import org.orekit.time.SampledClockModel;
43  import org.orekit.time.TimeComponents;
44  import org.orekit.time.TimeScale;
45  import org.orekit.utils.TimeSpanMap;
46  
47  /** Represents a parsed clock file from the IGS.
48   * <p> A time system should be specified in the file. However, if it is not, default time system will be chosen
49   * regarding the satellite system. If it is mixed or not specified, default time system will be UTC. </p>
50   * <p> Some fields might be null after parsing. It is expected because of the numerous kind of data that can be stored in clock data file. </p>
51   * <p> Caution, files with missing information in header can lead to wrong data dates and station positions.
52   * It is adviced to check the correctness and format compliance of the clock file to be parsed.
53   * Some values such as file time scale still can be set by user. </p>
54   * @see <a href="ftp://igs.org/pub/data/format/rinex_clock300.txt"> 3.00 clock file format</a>
55   * @see <a href="ftp://igs.org/pub/data/format/rinex_clock302.txt"> 3.02 clock file format</a>
56   * @see <a href="ftp://igs.org/pub/data/format/rinex_clock304.txt"> 3.04 clock file format</a>
57   *
58   * @author Thomas Paulet
59   * @since 11.0
60   */
61  public class RinexClock {
62  
63      /** Format version. */
64      private double formatVersion;
65  
66      /** Satellite system. */
67      private SatelliteSystem satelliteSystem;
68  
69      /** Name of the program creating current file. */
70      private String programName;
71  
72      /** Name of the agency creating the current file. */
73      private String agencyName;
74  
75      /** Date of the file creation as a string. */
76      private String creationDateString;
77  
78      /** Time of the file creation as a string. */
79      private String creationTimeString;
80  
81      /** Time zone of the file creation as a string. */
82      private String creationTimeZoneString;
83  
84      /** Creation date as absolute date. */
85      private AbsoluteDate creationDate;
86  
87      /** Comments. */
88      private String comments;
89  
90      /** Satellite system code. */
91      private final Map<SatelliteSystem, List<ObservationType>> systemObservationTypes;
92  
93      /** Time system. */
94      private TimeSystem timeSystem;
95  
96      /** Data time scale related to time system. */
97      private TimeScale timeScale;
98  
99      /** Number of leap seconds separating UTC and TAI (UTC = TAI - numberOfLeapSeconds). */
100     private int numberOfLeapSeconds;
101 
102     /** Number of leap seconds separating UTC and GNSS time systems. */
103     private int numberOfLeapSecondsGNSS;
104 
105     /** List of applied differential code bias corrections. */
106     private final List<AppliedDCBS> listAppliedDCBS;
107 
108     /** List of antenna center variation corrections. */
109     private final List<AppliedPCVS> listAppliedPCVS;
110 
111     /** List of the data types in the file. */
112     private final List<ClockDataType> clockDataTypes;
113 
114     /** Station name for calibration and discontinuity data. */
115     private String stationName;
116 
117     /** Station identifier for calibration and discontinuity data. */
118     private String stationIdentifier;
119 
120     /** External reference clock identifier for calibration. */
121     private String externalClockReference;
122 
123     /** Analysis center ID. */
124     private String analysisCenterID;
125 
126     /** Full analysis center name. */
127     private String analysisCenterName;
128 
129     /** Reference clocks. */
130     private final TimeSpanMap<List<ReferenceClock>> referenceClocks;
131 
132     /** Earth centered frame name as a string. */
133     private String frameName;
134 
135     /** Maps {@link #frameName} to a {@link Frame}. */
136     private final Function<? super String, ? extends Frame> frameBuilder;
137 
138     /** List of the receivers in the file. */
139     private final List<Receiver> receivers;
140 
141     /** List of the satellites in the file. */
142     private final List<String> satellites;
143 
144     /** A map containing receiver/satellite information. */
145     private final Map<String, List<ClockDataLine>> clockData;
146 
147     /** Earliest epoch.
148      * @since 12.1
149      */
150     private AbsoluteDate earliestEpoch;
151 
152     /** Latest epoch.
153      * @since 12.1
154      */
155     private AbsoluteDate latestEpoch;
156 
157     /** Constructor.
158      * @param frameBuilder for constructing a reference frame from the identifier
159      */
160     public RinexClock(final Function<? super String, ? extends Frame> frameBuilder) {
161         // Initialize fields with default data
162         this.systemObservationTypes  = new HashMap<>();
163         this.listAppliedDCBS         = new ArrayList<>();
164         this.listAppliedPCVS         = new ArrayList<>();
165         this.clockDataTypes          = new ArrayList<>();
166         this.receivers               = new ArrayList<>();
167         this.satellites              = new ArrayList<>();
168         this.clockData               = new HashMap<>();
169         this.agencyName              = "";
170         this.analysisCenterID        = "";
171         this.analysisCenterName      = "";
172         this.comments                = "";
173         this.creationDate            = null;
174         this.creationDateString      = "";
175         this.creationTimeString      = "";
176         this.creationTimeZoneString  = "";
177         this.externalClockReference  = "";
178         this.formatVersion           = 0.0;
179         this.frameBuilder            = frameBuilder;
180         this.frameName               = "";
181         this.numberOfLeapSeconds     = 0;
182         this.numberOfLeapSecondsGNSS = 0;
183         this.programName             = "";
184         this.referenceClocks         = new TimeSpanMap<>(null);
185         this.satelliteSystem         = null;
186         this.stationIdentifier       = "";
187         this.stationName             = "";
188         this.timeScale               = null;
189         this.timeSystem              = null;
190         this.earliestEpoch           = AbsoluteDate.FUTURE_INFINITY;
191         this.latestEpoch             = AbsoluteDate.PAST_INFINITY;
192     }
193 
194     /** Add a new satellite with a given identifier to the list of stored satellites.
195      * @param satId the satellite identifier
196      */
197     public void addSatellite(final String satId) {
198         // only add satellites which have not been added before
199         if (!satellites.contains(satId)) {
200             satellites.add(satId);
201         }
202     }
203 
204     /** Add a new receiver to the list of stored receivers.
205      * @param receiver the receiver
206      */
207     public void addReceiver(final Receiver receiver) {
208 
209         boolean notInList = true;
210         for (Receiver rec : receivers) {
211             if (rec.designator.equals(receiver.designator)) {
212                 notInList = false;
213                 break;
214             }
215         }
216         // only add satellites which have not been added before
217         if (notInList) {
218             receivers.add(receiver);
219         }
220     }
221 
222     /** Get the number of different clock data types in the file.
223      * @return the number of different clock data types
224      */
225     public int getNumberOfClockDataTypes() {
226         return clockDataTypes.size();
227     }
228 
229     /** Get the total number of complete data lines in the file.
230      * @return the total number of complete data lines in the file
231      */
232     public int getTotalNumberOfDataLines() {
233         int result = 0;
234         final Map<String, List<ClockDataLine>> data = getClockData();
235         for (final Map.Entry<String, List<ClockDataLine>> entry : data.entrySet()) {
236             result += entry.getValue().size();
237         }
238         return result;
239     }
240 
241     /** Get the number of observation types for a given system.
242      * @param system the satellite system to consider
243      * @return the number of observation types for a given system
244      */
245     public int numberOfObsTypes(final SatelliteSystem system) {
246         if (systemObservationTypes.containsKey(system)) {
247             return systemObservationTypes.get(system).size();
248         } else {
249             return 0;
250         }
251     }
252 
253     /** Get the number of receivers that are considered in the file.
254      * @return the number of receivers that are considered in the file
255      */
256     public int getNumberOfReceivers() {
257         return receivers.size();
258     }
259 
260     /** Get the number of satellites that are considered in the file.
261      * @return the number of satellites that are considered in the file
262      */
263     public int getNumberOfSatellites() {
264         return satellites.size();
265     }
266 
267     /** Getter for the format version.
268      * @return the format version
269      */
270     public double getFormatVersion() {
271         return formatVersion;
272     }
273 
274     /** Setter for the format version.
275      * @param formatVersion the format version to set
276      */
277     public void setFormatVersion(final double formatVersion) {
278         this.formatVersion = formatVersion;
279     }
280 
281     /** Getter for the satellite system.
282      * @return the satellite system
283      */
284     public SatelliteSystem getSatelliteSystem() {
285         return satelliteSystem;
286     }
287 
288     /** Setter for the satellite system.
289      * @param satelliteSystem the satellite system to set
290      */
291     public void setSatelliteSystem(final SatelliteSystem satelliteSystem) {
292         this.satelliteSystem = satelliteSystem;
293     }
294 
295     /** Getter for the program name.
296      * @return the program name
297      */
298     public String getProgramName() {
299         return programName;
300     }
301 
302     /** Setter for the program name.
303      * @param programName the program name to set
304      */
305     public void setProgramName(final String programName) {
306         this.programName = programName;
307     }
308 
309     /** Getter for the agency name.
310      * @return the agencyName
311      */
312     public String getAgencyName() {
313         return agencyName;
314     }
315 
316     /** Setter for the agency name.
317      * @param agencyName the agency name to set
318      */
319     public void setAgencyName(final String agencyName) {
320         this.agencyName = agencyName;
321     }
322 
323     /** Getter for the creation date of the file as a string.
324      * @return the creation date as a string
325      */
326     public String getCreationDateString() {
327         return creationDateString;
328     }
329 
330     /** Setter for the creation date as a string.
331      * @param creationDateString the creation date as a string to set
332      */
333     public void setCreationDateString(final String creationDateString) {
334         this.creationDateString = creationDateString;
335     }
336 
337     /** Getter for the creation time of the file as a string.
338      * @return the creation time as a string
339      */
340     public String getCreationTimeString() {
341         return creationTimeString;
342     }
343 
344     /** Setter for the creation time as a string.
345      * @param creationTimeString the creation time as a string to set
346      */
347     public void setCreationTimeString(final String creationTimeString) {
348         this.creationTimeString = creationTimeString;
349     }
350 
351     /** Getter for the creation time zone of the file as a string.
352      * @return the creation time zone as a string
353      */
354     public String getCreationTimeZoneString() {
355         return creationTimeZoneString;
356     }
357 
358     /** Setter for the creation time zone.
359      * @param creationTimeZoneString the creation time zone as a string to set
360      */
361     public void setCreationTimeZoneString(final String creationTimeZoneString) {
362         this.creationTimeZoneString = creationTimeZoneString;
363     }
364 
365     /** Getter for the creation date.
366      * @return the creation date
367      */
368     public AbsoluteDate getCreationDate() {
369         return creationDate;
370     }
371 
372     /** Setter for the creation date.
373      * @param creationDate the creation date to set
374      */
375     public void setCreationDate(final AbsoluteDate creationDate) {
376         this.creationDate = creationDate;
377     }
378 
379     /** Getter for the comments.
380      * @return the comments
381      */
382     public String getComments() {
383         return comments;
384     }
385 
386     /** Add a comment line.
387      * @param comment the comment line to add
388      */
389     public void addComment(final String comment) {
390         this.comments = comments.concat(comment + "\n");
391     }
392 
393     /** Getter for the different observation type for each satellite system.
394      * @return the map of the different observation type per satellite system
395      */
396     public Map<SatelliteSystem, List<ObservationType>> getSystemObservationTypes() {
397         return Collections.unmodifiableMap(systemObservationTypes);
398     }
399 
400     /** Add an observation type for a specified satellite system.
401      * @param satSystem the satellite system to add observation type
402      * @param observationType the system observation type to set
403      */
404     public void addSystemObservationType(final SatelliteSystem satSystem,
405                                          final ObservationType observationType) {
406         systemObservationTypes.
407             computeIfAbsent(satSystem, s -> new ArrayList<>()).
408             add(observationType);
409     }
410 
411     /** Getter for the file time system.
412      * @return the file time system
413      */
414     public TimeSystem getTimeSystem() {
415         return timeSystem;
416     }
417 
418     /** Setter for the file time system.
419      * @param timeSystem the file time system to set
420      */
421     public void setTimeSystem(final TimeSystem timeSystem) {
422         this.timeSystem = timeSystem;
423     }
424 
425     /** Getter for the data time scale.
426      * @return the data time scale
427      */
428     public TimeScale getTimeScale() {
429         return timeScale;
430     }
431 
432     /** Setter for the data time scale.
433      * @param timeScale the data time scale to set
434      */
435     public void setTimeScale(final TimeScale timeScale) {
436         this.timeScale = timeScale;
437     }
438 
439     /** Getter for the number of leap seconds.
440      * @return the number of leap seconds
441      */
442     public int getNumberOfLeapSeconds() {
443         return numberOfLeapSeconds;
444     }
445 
446     /** Setter for the number of leap seconds.
447      * @param numberOfLeapSeconds the number of leap seconds to set
448      */
449     public void setNumberOfLeapSeconds(final int numberOfLeapSeconds) {
450         this.numberOfLeapSeconds = numberOfLeapSeconds;
451     }
452 
453     /** Getter for the number of leap second for GNSS time scales.
454      * @return the number of leap seconds for GNSS time scales
455      */
456     public int getNumberOfLeapSecondsGNSS() {
457         return numberOfLeapSecondsGNSS;
458     }
459 
460     /** Setter for the number of leap seconds for GNSS time scales.
461      * @param numberOfLeapSecondsGNSS the number of leap seconds for GNSS time scales to set
462      */
463     public void setNumberOfLeapSecondsGNSS(final int numberOfLeapSecondsGNSS) {
464         this.numberOfLeapSecondsGNSS = numberOfLeapSecondsGNSS;
465     }
466 
467     /** Getter for the applied differential code bias corrections.
468      * @return the list of applied differential code bias corrections
469      */
470     public List<AppliedDCBS> getListAppliedDCBS() {
471         return Collections.unmodifiableList(listAppliedDCBS);
472     }
473 
474     /** Add an applied differencial code bias corrections.
475      * @param appliedDCBS the applied differencial code bias corrections to add
476      */
477     public void addAppliedDCBS(final AppliedDCBS appliedDCBS) {
478         listAppliedDCBS.add(appliedDCBS);
479     }
480 
481     /** Getter for the applied phase center variations.
482      * @return the list of the applied phase center variations
483      */
484     public List<AppliedPCVS> getListAppliedPCVS() {
485         return Collections.unmodifiableList(listAppliedPCVS);
486     }
487 
488     /** Add an applied phase center variations.
489      * @param appliedPCVS the phase center variations to add
490      */
491     public void addAppliedPCVS(final AppliedPCVS appliedPCVS) {
492         listAppliedPCVS.add(appliedPCVS);
493     }
494 
495     /** Getter for the different clock data types.
496      * @return the list of the different clock data types
497      */
498     public List<ClockDataType> getClockDataTypes() {
499         return Collections.unmodifiableList(clockDataTypes);
500     }
501 
502     /** Add a clock data types.
503      * @param clockDataType the clock data types to add
504      */
505     public void addClockDataType(final ClockDataType clockDataType) {
506         clockDataTypes.add(clockDataType);
507     }
508 
509     /** Getter for the station name.
510      * @return the station name
511      */
512     public String getStationName() {
513         return stationName;
514     }
515 
516     /** Setter for the station name.
517      * @param stationName the station name to set
518      */
519     public void setStationName(final String stationName) {
520         this.stationName = stationName;
521     }
522 
523     /** Getter for the station identifier.
524      * @return the station identifier
525      */
526     public String getStationIdentifier() {
527         return stationIdentifier;
528     }
529 
530     /** Setter for the station identifier.
531      * @param stationIdentifier the station identifier to set
532      */
533     public void setStationIdentifier(final String stationIdentifier) {
534         this.stationIdentifier = stationIdentifier;
535     }
536 
537     /** Getter for the external clock reference.
538      * @return the external clock reference
539      */
540     public String getExternalClockReference() {
541         return externalClockReference;
542     }
543 
544     /** Setter for the external clock reference.
545      * @param externalClockReference the external clock reference to set
546      */
547     public void setExternalClockReference(final String externalClockReference) {
548         this.externalClockReference = externalClockReference;
549     }
550 
551     /** Getter for the analysis center ID.
552      * @return the analysis center ID
553      */
554     public String getAnalysisCenterID() {
555         return analysisCenterID;
556     }
557 
558     /** Setter for the analysis center ID.
559      * @param analysisCenterID the analysis center ID to set
560      */
561     public void setAnalysisCenterID(final String analysisCenterID) {
562         this.analysisCenterID = analysisCenterID;
563     }
564 
565     /** Getter for the analysis center name.
566      * @return the analysis center name
567      */
568     public String getAnalysisCenterName() {
569         return analysisCenterName;
570     }
571 
572     /** Setter for the analysis center name.
573      * @param analysisCenterName the analysis center name to set
574      */
575     public void setAnalysisCenterName(final String analysisCenterName) {
576         this.analysisCenterName = analysisCenterName;
577     }
578 
579     /** Getter for the reference clocks.
580      * @return the time span map of the different refence clocks
581      */
582     public TimeSpanMap<List<ReferenceClock>> getReferenceClocks() {
583         return referenceClocks;
584     }
585 
586     /** Add a list of reference clocks which will be used after a specified date.
587      * If the reference map has not been already created, it will be.
588      * @param referenceClockList the reference clock list
589      * @param startDate the date the list will be valid after.
590      */
591     public void addReferenceClockList(final List<ReferenceClock> referenceClockList,
592                                       final AbsoluteDate startDate) {
593         referenceClocks.addValidAfter(referenceClockList, startDate, false);
594     }
595 
596     /** Getter for the frame name.
597      * @return the frame name
598      */
599     public String getFrameName() {
600         return frameName;
601     }
602 
603 
604     /** Setter for the frame name.
605      * @param frameName the frame name to set
606      */
607     public void setFrameName(final String frameName) {
608         this.frameName = frameName;
609     }
610 
611     /** Getter for the receivers.
612      * @return the list of the receivers
613      */
614     public List<Receiver> getReceivers() {
615         return Collections.unmodifiableList(receivers);
616     }
617 
618     /** Getter for the satellites.
619      * @return the list of the satellites
620      */
621     public List<String> getSatellites() {
622         return Collections.unmodifiableList(satellites);
623     }
624 
625     /** Get the reference frame for the station positions.
626      * @return the reference frame for station positions
627      */
628     public Frame getFrame() {
629         return frameBuilder.apply(frameName);
630     }
631 
632     /** Extract the clock model.
633      * @param name receiver/satellite name
634      * @param nbInterpolationPoints number of points to use in interpolation
635      * @return extracted clock model
636      * @since 12.1
637      */
638     public SampledClockModel extractClockModel(final String name,
639                                                final int nbInterpolationPoints) {
640         final List<ClockOffset> sample = new ArrayList<>();
641         clockData.
642             get(name).
643             forEach(c -> {
644                 final double offset       = c.clockBias;
645                 final double rate         = c.numberOfValues > 2 ? c.clockRate         : Double.NaN;
646                 final double acceleration = c.numberOfValues > 4 ? c.clockAcceleration : Double.NaN;
647                 sample.add(new ClockOffset(c.getEpoch(), offset, rate, acceleration));
648             });
649         return new SampledClockModel(sample, nbInterpolationPoints);
650     }
651 
652     /** Getter for an unmodifiable map of clock data.
653      * @return the clock data
654      */
655     public Map<String, List<ClockDataLine>> getClockData() {
656         return Collections.unmodifiableMap(clockData);
657     }
658 
659 
660     /** Add a clock data line to a specified receiver/satellite.
661      * @param id the satellite system to add observation type
662      * @param clockDataLine the clock data line to add
663      */
664     public void addClockData(final String id,
665                              final ClockDataLine clockDataLine) {
666         clockData.computeIfAbsent(id, i -> new ArrayList<>()).add(clockDataLine);
667         final AbsoluteDate epoch = clockDataLine.getEpoch();
668         if (epoch.isBefore(earliestEpoch)) {
669             earliestEpoch = epoch;
670         }
671         if (epoch.isAfter(latestEpoch)) {
672             latestEpoch = epoch;
673         }
674     }
675 
676     /** Get earliest epoch from the {@link #getClockData() clock data}.
677      * @return earliest epoch from the {@link #getClockData() clock data},
678      * or {@link AbsoluteDate#FUTURE_INFINITY} if no data has been added
679      * @since 12.1
680      */
681     public AbsoluteDate getEarliestEpoch() {
682         return earliestEpoch;
683     }
684 
685     /** Get latest epoch from the {@link #getClockData() clock data}.
686      * @return latest epoch from the {@link #getClockData() clock data},
687      * or {@link AbsoluteDate#PAST_INFINITY} if no data has been added
688      * @since 12.1
689      */
690     public AbsoluteDate getLatestEpoch() {
691         return latestEpoch;
692     }
693 
694     /** Splice several Rinex clock files together.
695      * <p>
696      * Splicing Rinex clock files is intended to be used when continuous computation
697      * covering more than one file is needed. The metadata (version number, agency, …)
698      * will be retrieved from the earliest file only. Receivers and satellites
699      * will be merged from all files. Some receivers or satellites may be missing
700      * in some files… Once sorted (which is done internally), if the gap between
701      * segments from two file is larger than {@code maxGap}, then an error
702      * will be triggered.
703      * </p>
704      * <p>
705      * The spliced file only contains the receivers and satellites that were present
706      * in all files. Receivers and satellites present in some files and absent from
707      * other files are silently dropped.
708      * </p>
709      * <p>
710      * Depending on producer, successive clock files either have a gap between the last
711      * entry of one file and the first entry of the next file (for example files with
712      * a 5 minutes epoch interval may end at 23:55 and the next file start at 00:00),
713      * or both files have one point exactly at the splicing date (i.e. 24:00 one day
714      * and 00:00 next day). In the later case, the last point of the early file is dropped
715      * and the first point of the late file takes precedence, hence only one point remains
716      * in the spliced file ; this design choice is made to enforce continuity and
717      * regular interpolation.
718      * </p>
719      * @param clocks clock files to merge
720      * @param maxGap maximum time gap between files
721      * @return merged clock file
722      * @since 12.1
723      */
724     public static RinexClock splice(final Collection<RinexClock> clocks,
725                                     final double maxGap) {
726 
727         // sort the files
728         final ChronologicalComparator comparator = new ChronologicalComparator();
729         final SortedSet<RinexClock> sorted =
730             new TreeSet<>((c1, c2) -> comparator.compare(c1.earliestEpoch, c2.earliestEpoch));
731         sorted.addAll(clocks);
732 
733         // prepare spliced file
734         final RinexClock first   = sorted.first();
735         final RinexClock spliced = new RinexClock(first.frameBuilder);
736         spliced.setFormatVersion(first.getFormatVersion());
737         spliced.setSatelliteSystem(first.satelliteSystem);
738         spliced.setProgramName(first.getProgramName());
739         spliced.setAgencyName(first.getAgencyName());
740         spliced.setCreationDateString(first.getCreationDateString());
741         spliced.setCreationTimeString(first.getCreationTimeString());
742         spliced.setCreationTimeZoneString(first.getCreationTimeZoneString());
743         spliced.setCreationDate(first.getCreationDate());
744         spliced.addComment(first.getComments());
745         first.
746             getSystemObservationTypes().
747             forEach((s, l) -> l.forEach(o -> spliced.addSystemObservationType(s, o)));
748         spliced.setTimeSystem(first.getTimeSystem());
749         spliced.setTimeScale(first.getTimeScale());
750         spliced.setNumberOfLeapSeconds(first.getNumberOfLeapSeconds());
751         spliced.setNumberOfLeapSecondsGNSS(first.getNumberOfLeapSecondsGNSS());
752         first.getListAppliedDCBS().forEach(spliced::addAppliedDCBS);
753         first.getListAppliedPCVS().forEach(spliced::addAppliedPCVS);
754         first.getClockDataTypes().forEach(spliced::addClockDataType);
755         spliced.setStationName(first.getStationName());
756         spliced.setStationIdentifier(first.getStationIdentifier());
757         spliced.setExternalClockReference(first.getExternalClockReference());
758         spliced.setAnalysisCenterID(first.getAnalysisCenterID());
759         spliced.setAnalysisCenterName(first.getAnalysisCenterName());
760         spliced.setFrameName(first.getFrameName());
761 
762         // merge reference clocks maps
763         sorted.forEach(rc -> {
764             TimeSpanMap.Span<List<ReferenceClock>> span = rc.getReferenceClocks().getFirstSpan();
765             while (span != null) {
766                 if (span.getData() != null) {
767                     spliced.addReferenceClockList(span.getData(), span.getStart());
768                 }
769                 span = span.next();
770             }
771         });
772 
773         final List<String> clockIds = new ArrayList<>();
774 
775         // identify the receivers that are present in all files
776         first.
777             getReceivers().
778             stream().
779             filter(r -> availableInAllFiles(r.getDesignator(), sorted)).
780             forEach(r -> {
781                 spliced.addReceiver(r);
782                 clockIds.add(r.getDesignator());
783             });
784 
785         // identify the satellites that are present in all files
786         first.
787             getSatellites().
788             stream().
789             filter(s -> availableInAllFiles(s, sorted)).
790             forEach(s -> {
791                 spliced.addSatellite(s);
792                 clockIds.add(s);
793             });
794 
795         // add the clock lines
796         for (final String clockId : clockIds) {
797             AbsoluteDate previous = null;
798             for (final RinexClock rc : sorted) {
799                 if (previous != null) {
800                     if (rc.getEarliestEpoch().durationFrom(previous) > maxGap) {
801                         throw new OrekitException(OrekitMessages.TOO_LONG_TIME_GAP_BETWEEN_DATA_POINTS,
802                                                   rc.getEarliestEpoch().durationFrom(previous));
803                     }
804                 }
805                 previous = rc.getLatestEpoch();
806                 rc.getClockData().get(clockId).forEach(cd -> spliced.addClockData(clockId, cd));
807             }
808         }
809 
810         return spliced;
811 
812     }
813 
814     /** Check if clock data is available in all files.
815      * @param clockId clock id
816      * @param files clock files
817      * @return true if clock is available in all files
818      */
819     private static boolean availableInAllFiles(final String clockId, final Collection<RinexClock> files) {
820         for (final RinexClock rc : files) {
821             if (!rc.getClockData().containsKey(clockId)) {
822                 return false;
823             }
824         }
825         return true;
826     }
827 
828     /** Clock data for a single station.
829      * <p> Data epoch is not linked to any time system in order to pars files with missing lines.
830      * Though, the default version of the getEpoch() method links the data time components with the clock file object time scale.
831      * The latter can be set with a default value (UTC). Caution is recommanded.
832      */
833     public class ClockDataLine {
834 
835         /** Clock data type. */
836         private final ClockDataType dataType;
837 
838         /** Receiver/Satellite name. */
839         private final String name;
840 
841         /** Epoch date components. */
842         private final DateComponents dateComponents;
843 
844         /** Epoch time components. */
845         private final TimeComponents timeComponents;
846 
847         /** Number of data values to follow.
848          * This number might not represent the non zero values in the line.
849          */
850         private final int numberOfValues;
851 
852         /** Clock bias (seconds). */
853         private final double clockBias;
854 
855         /** Clock bias sigma (seconds). */
856         private final double clockBiasSigma;
857 
858         /** Clock rate (dimensionless). */
859         private final double clockRate;
860 
861         /** Clock rate sigma (dimensionless). */
862         private final double clockRateSigma;
863 
864         /** Clock acceleration (seconds^-1). */
865         private final double clockAcceleration;
866 
867         /** Clock acceleration sigma (seconds^-1). */
868         private final double clockAccelerationSigma;
869 
870         /** Constructor.
871          * @param type the clock data type
872          * @param name the receiver/satellite name
873          * @param dateComponents the epoch date components
874          * @param timeComponents the epoch time components
875          * @param numberOfValues the number of values to follow
876          * @param clockBias the clock bias in seconds
877          * @param clockBiasSigma the clock bias sigma in seconds
878          * @param clockRate the clock rate
879          * @param clockRateSigma the clock rate sigma
880          * @param clockAcceleration the clock acceleration in seconds^-1
881          * @param clockAccelerationSigma the clock acceleration in seconds^-1
882          */
883         public ClockDataLine (final ClockDataType type, final String name,
884                               final DateComponents dateComponents,
885                               final TimeComponents timeComponents,
886                               final int numberOfValues,
887                               final double clockBias, final double clockBiasSigma,
888                               final double clockRate, final double clockRateSigma,
889                               final double clockAcceleration, final double clockAccelerationSigma) {
890 
891             this.dataType                = type;
892             this.name                    = name;
893             this.dateComponents          = dateComponents;
894             this.timeComponents          = timeComponents;
895             this.numberOfValues          = numberOfValues;
896             this.clockBias               = clockBias;
897             this.clockBiasSigma          = clockBiasSigma;
898             this.clockRate               = clockRate;
899             this.clockRateSigma          = clockRateSigma;
900             this.clockAcceleration       = clockAcceleration;
901             this.clockAccelerationSigma = clockAccelerationSigma;
902         }
903 
904         /** Getter for the clock data type.
905          * @return the clock data type
906          */
907         public ClockDataType getDataType() {
908             return dataType;
909         }
910 
911         /** Getter for the receiver/satellite name.
912          * @return the receiver/satellite name
913          */
914         public String getName() {
915             return name;
916         }
917 
918         /** Getter for the number of values to follow.
919          * @return the number of values to follow
920          */
921         public int getNumberOfValues() {
922             return numberOfValues;
923         }
924 
925         /** Get data line epoch.
926          * This method should be used if Time System ID line is present in the clock file.
927          * If it is missing, UTC time scale will be applied.
928          * To specify tim scale, use {@link #getEpoch(TimeScale) getEpoch(TimeScale)} method.
929          * @return the data line epoch
930          */
931         public AbsoluteDate getEpoch() {
932             return new AbsoluteDate(dateComponents, timeComponents, timeScale);
933         }
934 
935         /** Get data line epoch.
936          * This method should be used in case Time System ID line is missing.
937          * Otherwise, it is adviced to rather use {@link #getEpoch() getEpoch()} method.
938          * @param epochTimeScale the time scale in which the epoch is defined
939          * @return the data line epoch set in the specified time scale
940          */
941         public AbsoluteDate getEpoch(final TimeScale epochTimeScale) {
942             return new AbsoluteDate(dateComponents, timeComponents, epochTimeScale);
943         }
944 
945         /** Getter for the clock bias.
946          * @return the clock bias in seconds
947          */
948         public double getClockBias() {
949             return clockBias;
950         }
951 
952         /** Getter for the clock bias sigma.
953          * @return the clock bias sigma in seconds
954          */
955         public double getClockBiasSigma() {
956             return clockBiasSigma;
957         }
958 
959         /** Getter for the clock rate.
960          * @return the clock rate
961          */
962         public double getClockRate() {
963             return clockRate;
964         }
965 
966         /** Getter for the clock rate sigma.
967          * @return the clock rate sigma
968          */
969         public double getClockRateSigma() {
970             return clockRateSigma;
971         }
972 
973         /** Getter for the clock acceleration.
974          * @return the clock acceleration in seconds^-1
975          */
976         public double getClockAcceleration() {
977             return clockAcceleration;
978         }
979 
980         /** Getter for the clock acceleration sigma.
981          * @return the clock acceleration sigma in seconds^-1
982          */
983         public double getClockAccelerationSigma() {
984             return clockAccelerationSigma;
985         }
986 
987     }
988 
989     /** Represents a reference clock with its validity time span. */
990     public static class ReferenceClock {
991 
992         /** Receiver/satellite embedding the reference clock name. */
993         private final String referenceName;
994 
995         /** Clock ID. */
996         private final String clockID;
997 
998         /** A priori clock constraint (in seconds). */
999         private final double clockConstraint;
1000 
1001         /** Start date of the validity period. */
1002         private final AbsoluteDate startDate;
1003 
1004         /** End date of the validity period. */
1005         private final AbsoluteDate endDate;
1006 
1007         /** Constructor.
1008          * @param referenceName the name of the receiver/satellite embedding the reference clock
1009          * @param clockID the clock ID
1010          * @param clockConstraint the a priori clock constraint
1011          * @param startDate the validity period start date
1012          * @param endDate the validity period end date
1013          */
1014         public ReferenceClock (final String referenceName, final String clockID, final double clockConstraint,
1015                                final AbsoluteDate startDate, final AbsoluteDate endDate) {
1016             this.referenceName   = referenceName;
1017             this.clockID         = clockID;
1018             this.clockConstraint = clockConstraint;
1019             this.startDate       = startDate;
1020             this.endDate         = endDate;
1021         }
1022 
1023         /** Getter for the name of the receiver/satellite embedding the reference clock.
1024          * @return the name of the receiver/satellite embedding the reference clock
1025          */
1026         public String getReferenceName() {
1027             return referenceName;
1028         }
1029 
1030         /** Getter for the clock ID.
1031          * @return the clock ID
1032          */
1033         public String getClockID() {
1034             return clockID;
1035         }
1036 
1037         /** Getter for the clock constraint.
1038          * @return the clock constraint
1039          */
1040         public double getClockConstraint() {
1041             return clockConstraint;
1042         }
1043 
1044         /** Getter for the validity period start date.
1045          * @return the validity period start date
1046          */
1047         public AbsoluteDate getStartDate() {
1048             return startDate;
1049         }
1050 
1051         /** Getter for the validity period end date.
1052          * @return the validity period end date
1053          */
1054         public AbsoluteDate getEndDate() {
1055             return endDate;
1056         }
1057 
1058     }
1059 
1060     /** Represents a receiver or a satellite with its position in the considered frame. */
1061     public static class Receiver {
1062 
1063         /** Designator. */
1064         private final String designator;
1065 
1066         /** Receiver identifier. */
1067         private final String receiverIdentifier;
1068 
1069         /** X coordinates in file considered Earth centered frame (in meters). */
1070         private final double x;
1071 
1072         /** Y coordinates in file considered Earth centered frame (in meters). */
1073         private final double y;
1074 
1075         /** Z coordinates in file considered Earth centered frame (in meters). */
1076         private final double z;
1077 
1078         /** Constructor.
1079          * @param designator the designator
1080          * @param receiverIdentifier the receiver identifier
1081          * @param x the X coordinate in meters in considered Earth centered frame
1082          * @param y the Y coordinate in meters in considered Earth centered frame
1083          * @param z the Z coordinate in meters in considered Earth centered frame
1084          */
1085         public Receiver(final String designator, final String receiverIdentifier,
1086                         final double x, final double y, final double z) {
1087             this.designator         = designator;
1088             this.receiverIdentifier = receiverIdentifier;
1089             this.x                  = x;
1090             this.y                  = y;
1091             this.z                  = z;
1092         }
1093 
1094         /** Getter for the designator.
1095          * @return the designator
1096          */
1097         public String getDesignator() {
1098             return designator;
1099         }
1100 
1101         /** Getter for the receiver identifier.
1102          * @return the receiver identifier
1103          */
1104         public String getReceiverIdentifier() {
1105             return receiverIdentifier;
1106         }
1107 
1108         /** Getter for the X coordinate in meters in considered Earth centered frame.
1109          * @return  the X coordinate in meters in considered Earth centered frame
1110          */
1111         public double getX() {
1112             return x;
1113         }
1114 
1115         /** Getter for the Y coordinate in meters in considered Earth centered frame.
1116          * @return  the Y coordinate in meters in considered Earth centered frame
1117          */
1118         public double getY() {
1119             return y;
1120         }
1121 
1122         /** Getter for the Z coordinate in meters in considered Earth centered frame.
1123          * @return  the Z coordinate in meters in considered Earth centered frame
1124          */
1125         public double getZ() {
1126             return z;
1127         }
1128     }
1129 
1130     /** Clock data type.
1131      * In case of a DR type, clock data are in the sense of clock value after discontinuity minus prior.
1132      * In other cases, clock data are in the sense of reported station/satellite clock minus reference clock value. */
1133     public enum ClockDataType {
1134 
1135         /** Data analysis for receiver clocks. Clock Data are*/
1136         AR("AR"),
1137 
1138         /** Data analysis for satellite clocks. */
1139         AS("AS"),
1140 
1141         /** Calibration measurement for a single GPS receiver. */
1142         CR("CR"),
1143 
1144         /** Discontinuity measurements for a single GPS receiver. */
1145         DR("DR"),
1146 
1147         /** Monitor measurements for the broadcast satellite clocks. */
1148         MS("MS");
1149 
1150         /** Parsing map. */
1151         private static final Map<String, ClockDataType> KEYS_MAP = new HashMap<>();
1152         static {
1153             for (final ClockDataType timeSystem : values()) {
1154                 KEYS_MAP.put(timeSystem.getKey(), timeSystem);
1155             }
1156         }
1157 
1158         /** Key for the system. */
1159         private final String key;
1160 
1161         /** Simple constructor.
1162          * @param key key letter
1163          */
1164         ClockDataType(final String key) {
1165             this.key = key;
1166         }
1167 
1168         /** Get the key for the system.
1169          * @return key for the system
1170          */
1171         public String getKey() {
1172             return key;
1173         }
1174 
1175         /** Parse a string to get the time system.
1176          * <p>
1177          * The string must be the time system.
1178          * </p>
1179          * @param s string to parse
1180          * @return the time system
1181          * @exception OrekitIllegalArgumentException if the string does not correspond to a time system key
1182          */
1183         public static ClockDataType parseClockDataType(final String s)
1184             throws OrekitIllegalArgumentException {
1185             final ClockDataType clockDataType = KEYS_MAP.get(s);
1186             if (clockDataType == null) {
1187                 throw new OrekitIllegalArgumentException(OrekitMessages.UNKNOWN_CLOCK_DATA_TYPE, s);
1188             }
1189             return clockDataType;
1190         }
1191     }
1192 }