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