RinexClock.java

  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. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.SortedSet;
  25. import java.util.TreeSet;
  26. import java.util.function.Function;

  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitIllegalArgumentException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.files.rinex.AppliedDCBS;
  31. import org.orekit.files.rinex.AppliedPCVS;
  32. import org.orekit.frames.Frame;
  33. import org.orekit.gnss.ObservationType;
  34. import org.orekit.gnss.SatelliteSystem;
  35. import org.orekit.gnss.TimeSystem;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.ChronologicalComparator;
  38. import org.orekit.time.ClockOffset;
  39. import org.orekit.time.DateComponents;
  40. import org.orekit.time.SampledClockModel;
  41. import org.orekit.time.TimeComponents;
  42. import org.orekit.time.TimeScale;
  43. import org.orekit.utils.TimeSpanMap;

  44. /** Represents a parsed clock file from the IGS.
  45.  * <p> A time system should be specified in the file. However, if it is not, default time system will be chosen
  46.  * regarding the satellite system. If it is mixed or not specified, default time system will be UTC. </p>
  47.  * <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>
  48.  * <p> Caution, files with missing information in header can lead to wrong data dates and station positions.
  49.  * It is advised to check the correctness and format compliance of the clock file to be parsed.
  50.  * Some values such as file time scale still can be set by user. </p>
  51.  * @see <a href="https://files.igs.org/pub/data/format/rinex_clock300.txt"> 3.00 clock file format</a>
  52.  * @see <a href="https://files.igs.org/pub/data/format/rinex_clock302.txt"> 3.02 clock file format</a>
  53.  * @see <a href="https://files.igs.org/pub/data/format/rinex_clock304.txt"> 3.04 clock file format</a>
  54.  *
  55.  * @author Thomas Paulet
  56.  * @since 11.0
  57.  */
  58. public class RinexClock {

  59.     /** Format version. */
  60.     private double formatVersion;

  61.     /** Satellite system. */
  62.     private SatelliteSystem satelliteSystem;

  63.     /** Name of the program creating current file. */
  64.     private String programName;

  65.     /** Name of the agency creating the current file. */
  66.     private String agencyName;

  67.     /** Date of the file creation as a string. */
  68.     private String creationDateString;

  69.     /** Time of the file creation as a string. */
  70.     private String creationTimeString;

  71.     /** Time zone of the file creation as a string. */
  72.     private String creationTimeZoneString;

  73.     /** Creation date as absolute date. */
  74.     private AbsoluteDate creationDate;

  75.     /** Comments. */
  76.     private String comments;

  77.     /** Satellite system code. */
  78.     private final Map<SatelliteSystem, List<ObservationType>> systemObservationTypes;

  79.     /** Time system. */
  80.     private TimeSystem timeSystem;

  81.     /** Data time scale related to time system. */
  82.     private TimeScale timeScale;

  83.     /** Number of leap seconds separating UTC and TAI (UTC = TAI - numberOfLeapSeconds). */
  84.     private int numberOfLeapSeconds;

  85.     /** Number of leap seconds separating UTC and GNSS time systems. */
  86.     private int numberOfLeapSecondsGNSS;

  87.     /** List of applied differential code bias corrections. */
  88.     private final List<AppliedDCBS> listAppliedDCBS;

  89.     /** List of antenna center variation corrections. */
  90.     private final List<AppliedPCVS> listAppliedPCVS;

  91.     /** List of the data types in the file. */
  92.     private final List<ClockDataType> clockDataTypes;

  93.     /** Station name for calibration and discontinuity data. */
  94.     private String stationName;

  95.     /** Station identifier for calibration and discontinuity data. */
  96.     private String stationIdentifier;

  97.     /** External reference clock identifier for calibration. */
  98.     private String externalClockReference;

  99.     /** Analysis center ID. */
  100.     private String analysisCenterID;

  101.     /** Full analysis center name. */
  102.     private String analysisCenterName;

  103.     /** Reference clocks. */
  104.     private final TimeSpanMap<List<ReferenceClock>> referenceClocks;

  105.     /** Earth centered frame name as a string. */
  106.     private String frameName;

  107.     /** Maps {@link #frameName} to a {@link Frame}. */
  108.     private final Function<? super String, ? extends Frame> frameBuilder;

  109.     /** List of the receivers in the file. */
  110.     private final List<Receiver> receivers;

  111.     /** List of the satellites in the file. */
  112.     private final List<String> satellites;

  113.     /** A map containing receiver/satellite information. */
  114.     private final Map<String, List<ClockDataLine>> clockData;

  115.     /** Earliest epoch.
  116.      * @since 12.1
  117.      */
  118.     private AbsoluteDate earliestEpoch;

  119.     /** Latest epoch.
  120.      * @since 12.1
  121.      */
  122.     private AbsoluteDate latestEpoch;

  123.     /** Constructor.
  124.      * @param frameBuilder for constructing a reference frame from the identifier
  125.      */
  126.     public RinexClock(final Function<? super String, ? extends Frame> frameBuilder) {
  127.         // Initialize fields with default data
  128.         this.systemObservationTypes  = new HashMap<>();
  129.         this.listAppliedDCBS         = new ArrayList<>();
  130.         this.listAppliedPCVS         = new ArrayList<>();
  131.         this.clockDataTypes          = new ArrayList<>();
  132.         this.receivers               = new ArrayList<>();
  133.         this.satellites              = new ArrayList<>();
  134.         this.clockData               = new HashMap<>();
  135.         this.agencyName              = "";
  136.         this.analysisCenterID        = "";
  137.         this.analysisCenterName      = "";
  138.         this.comments                = "";
  139.         this.creationDate            = null;
  140.         this.creationDateString      = "";
  141.         this.creationTimeString      = "";
  142.         this.creationTimeZoneString  = "";
  143.         this.externalClockReference  = "";
  144.         this.formatVersion           = 0.0;
  145.         this.frameBuilder            = frameBuilder;
  146.         this.frameName               = "";
  147.         this.numberOfLeapSeconds     = 0;
  148.         this.numberOfLeapSecondsGNSS = 0;
  149.         this.programName             = "";
  150.         this.referenceClocks         = new TimeSpanMap<>(null);
  151.         this.satelliteSystem         = null;
  152.         this.stationIdentifier       = "";
  153.         this.stationName             = "";
  154.         this.timeScale               = null;
  155.         this.timeSystem              = null;
  156.         this.earliestEpoch           = AbsoluteDate.FUTURE_INFINITY;
  157.         this.latestEpoch             = AbsoluteDate.PAST_INFINITY;
  158.     }

  159.     /** Add a new satellite with a given identifier to the list of stored satellites.
  160.      * @param satId the satellite identifier
  161.      */
  162.     public void addSatellite(final String satId) {
  163.         // only add satellites which have not been added before
  164.         if (!satellites.contains(satId)) {
  165.             satellites.add(satId);
  166.         }
  167.     }

  168.     /** Add a new receiver to the list of stored receivers.
  169.      * @param receiver the receiver
  170.      */
  171.     public void addReceiver(final Receiver receiver) {

  172.         boolean notInList = true;
  173.         for (Receiver rec : receivers) {
  174.             if (rec.designator.equals(receiver.designator)) {
  175.                 notInList = false;
  176.                 break;
  177.             }
  178.         }
  179.         // only add satellites which have not been added before
  180.         if (notInList) {
  181.             receivers.add(receiver);
  182.         }
  183.     }

  184.     /** Get the number of different clock data types in the file.
  185.      * @return the number of different clock data types
  186.      */
  187.     public int getNumberOfClockDataTypes() {
  188.         return clockDataTypes.size();
  189.     }

  190.     /** Get the total number of complete data lines in the file.
  191.      * @return the total number of complete data lines in the file
  192.      */
  193.     public int getTotalNumberOfDataLines() {
  194.         int result = 0;
  195.         final Map<String, List<ClockDataLine>> data = getClockData();
  196.         for (final Map.Entry<String, List<ClockDataLine>> entry : data.entrySet()) {
  197.             result += entry.getValue().size();
  198.         }
  199.         return result;
  200.     }

  201.     /** Get the number of observation types for a given system.
  202.      * @param system the satellite system to consider
  203.      * @return the number of observation types for a given system
  204.      */
  205.     public int numberOfObsTypes(final SatelliteSystem system) {
  206.         if (systemObservationTypes.containsKey(system)) {
  207.             return systemObservationTypes.get(system).size();
  208.         } else {
  209.             return 0;
  210.         }
  211.     }

  212.     /** Get the number of receivers that are considered in the file.
  213.      * @return the number of receivers that are considered in the file
  214.      */
  215.     public int getNumberOfReceivers() {
  216.         return receivers.size();
  217.     }

  218.     /** Get the number of satellites that are considered in the file.
  219.      * @return the number of satellites that are considered in the file
  220.      */
  221.     public int getNumberOfSatellites() {
  222.         return satellites.size();
  223.     }

  224.     /** Getter for the format version.
  225.      * @return the format version
  226.      */
  227.     public double getFormatVersion() {
  228.         return formatVersion;
  229.     }

  230.     /** Setter for the format version.
  231.      * @param formatVersion the format version to set
  232.      */
  233.     public void setFormatVersion(final double formatVersion) {
  234.         this.formatVersion = formatVersion;
  235.     }

  236.     /** Getter for the satellite system.
  237.      * @return the satellite system
  238.      */
  239.     public SatelliteSystem getSatelliteSystem() {
  240.         return satelliteSystem;
  241.     }

  242.     /** Setter for the satellite system.
  243.      * @param satelliteSystem the satellite system to set
  244.      */
  245.     public void setSatelliteSystem(final SatelliteSystem satelliteSystem) {
  246.         this.satelliteSystem = satelliteSystem;
  247.     }

  248.     /** Getter for the program name.
  249.      * @return the program name
  250.      */
  251.     public String getProgramName() {
  252.         return programName;
  253.     }

  254.     /** Setter for the program name.
  255.      * @param programName the program name to set
  256.      */
  257.     public void setProgramName(final String programName) {
  258.         this.programName = programName;
  259.     }

  260.     /** Getter for the agency name.
  261.      * @return the agencyName
  262.      */
  263.     public String getAgencyName() {
  264.         return agencyName;
  265.     }

  266.     /** Setter for the agency name.
  267.      * @param agencyName the agency name to set
  268.      */
  269.     public void setAgencyName(final String agencyName) {
  270.         this.agencyName = agencyName;
  271.     }

  272.     /** Getter for the creation date of the file as a string.
  273.      * @return the creation date as a string
  274.      */
  275.     public String getCreationDateString() {
  276.         return creationDateString;
  277.     }

  278.     /** Setter for the creation date as a string.
  279.      * @param creationDateString the creation date as a string to set
  280.      */
  281.     public void setCreationDateString(final String creationDateString) {
  282.         this.creationDateString = creationDateString;
  283.     }

  284.     /** Getter for the creation time of the file as a string.
  285.      * @return the creation time as a string
  286.      */
  287.     public String getCreationTimeString() {
  288.         return creationTimeString;
  289.     }

  290.     /** Setter for the creation time as a string.
  291.      * @param creationTimeString the creation time as a string to set
  292.      */
  293.     public void setCreationTimeString(final String creationTimeString) {
  294.         this.creationTimeString = creationTimeString;
  295.     }

  296.     /** Getter for the creation time zone of the file as a string.
  297.      * @return the creation time zone as a string
  298.      */
  299.     public String getCreationTimeZoneString() {
  300.         return creationTimeZoneString;
  301.     }

  302.     /** Setter for the creation time zone.
  303.      * @param creationTimeZoneString the creation time zone as a string to set
  304.      */
  305.     public void setCreationTimeZoneString(final String creationTimeZoneString) {
  306.         this.creationTimeZoneString = creationTimeZoneString;
  307.     }

  308.     /** Getter for the creation date.
  309.      * @return the creation date
  310.      */
  311.     public AbsoluteDate getCreationDate() {
  312.         return creationDate;
  313.     }

  314.     /** Setter for the creation date.
  315.      * @param creationDate the creation date to set
  316.      */
  317.     public void setCreationDate(final AbsoluteDate creationDate) {
  318.         this.creationDate = creationDate;
  319.     }

  320.     /** Getter for the comments.
  321.      * @return the comments
  322.      */
  323.     public String getComments() {
  324.         return comments;
  325.     }

  326.     /** Add a comment line.
  327.      * @param comment the comment line to add
  328.      */
  329.     public void addComment(final String comment) {
  330.         this.comments = comments.concat(comment + "\n");
  331.     }

  332.     /** Getter for the different observation type for each satellite system.
  333.      * @return the map of the different observation type per satellite system
  334.      */
  335.     public Map<SatelliteSystem, List<ObservationType>> getSystemObservationTypes() {
  336.         return Collections.unmodifiableMap(systemObservationTypes);
  337.     }

  338.     /** Add an observation type for a specified satellite system.
  339.      * @param satSystem the satellite system to add observation type
  340.      * @param observationType the system observation type to set
  341.      */
  342.     public void addSystemObservationType(final SatelliteSystem satSystem,
  343.                                          final ObservationType observationType) {
  344.         final List<ObservationType> list;
  345.         synchronized (systemObservationTypes) {
  346.             list = systemObservationTypes.computeIfAbsent(satSystem, s -> new ArrayList<>());
  347.         }
  348.         list.add(observationType);
  349.     }

  350.     /** Getter for the file time system.
  351.      * @return the file time system
  352.      */
  353.     public TimeSystem getTimeSystem() {
  354.         return timeSystem;
  355.     }

  356.     /** Setter for the file time system.
  357.      * @param timeSystem the file time system to set
  358.      */
  359.     public void setTimeSystem(final TimeSystem timeSystem) {
  360.         this.timeSystem = timeSystem;
  361.     }

  362.     /** Getter for the data time scale.
  363.      * @return the data time scale
  364.      */
  365.     public TimeScale getTimeScale() {
  366.         return timeScale;
  367.     }

  368.     /** Setter for the data time scale.
  369.      * @param timeScale the data time scale to set
  370.      */
  371.     public void setTimeScale(final TimeScale timeScale) {
  372.         this.timeScale = timeScale;
  373.     }

  374.     /** Getter for the number of leap seconds.
  375.      * @return the number of leap seconds
  376.      */
  377.     public int getNumberOfLeapSeconds() {
  378.         return numberOfLeapSeconds;
  379.     }

  380.     /** Setter for the number of leap seconds.
  381.      * @param numberOfLeapSeconds the number of leap seconds to set
  382.      */
  383.     public void setNumberOfLeapSeconds(final int numberOfLeapSeconds) {
  384.         this.numberOfLeapSeconds = numberOfLeapSeconds;
  385.     }

  386.     /** Getter for the number of leap second for GNSS time scales.
  387.      * @return the number of leap seconds for GNSS time scales
  388.      */
  389.     public int getNumberOfLeapSecondsGNSS() {
  390.         return numberOfLeapSecondsGNSS;
  391.     }

  392.     /** Setter for the number of leap seconds for GNSS time scales.
  393.      * @param numberOfLeapSecondsGNSS the number of leap seconds for GNSS time scales to set
  394.      */
  395.     public void setNumberOfLeapSecondsGNSS(final int numberOfLeapSecondsGNSS) {
  396.         this.numberOfLeapSecondsGNSS = numberOfLeapSecondsGNSS;
  397.     }

  398.     /** Getter for the applied differential code bias corrections.
  399.      * @return the list of applied differential code bias corrections
  400.      */
  401.     public List<AppliedDCBS> getListAppliedDCBS() {
  402.         return Collections.unmodifiableList(listAppliedDCBS);
  403.     }

  404.     /** Add an applied differencial code bias corrections.
  405.      * @param appliedDCBS the applied differencial code bias corrections to add
  406.      */
  407.     public void addAppliedDCBS(final AppliedDCBS appliedDCBS) {
  408.         listAppliedDCBS.add(appliedDCBS);
  409.     }

  410.     /** Getter for the applied phase center variations.
  411.      * @return the list of the applied phase center variations
  412.      */
  413.     public List<AppliedPCVS> getListAppliedPCVS() {
  414.         return Collections.unmodifiableList(listAppliedPCVS);
  415.     }

  416.     /** Add an applied phase center variations.
  417.      * @param appliedPCVS the phase center variations to add
  418.      */
  419.     public void addAppliedPCVS(final AppliedPCVS appliedPCVS) {
  420.         listAppliedPCVS.add(appliedPCVS);
  421.     }

  422.     /** Getter for the different clock data types.
  423.      * @return the list of the different clock data types
  424.      */
  425.     public List<ClockDataType> getClockDataTypes() {
  426.         return Collections.unmodifiableList(clockDataTypes);
  427.     }

  428.     /** Add a clock data types.
  429.      * @param clockDataType the clock data types to add
  430.      */
  431.     public void addClockDataType(final ClockDataType clockDataType) {
  432.         clockDataTypes.add(clockDataType);
  433.     }

  434.     /** Getter for the station name.
  435.      * @return the station name
  436.      */
  437.     public String getStationName() {
  438.         return stationName;
  439.     }

  440.     /** Setter for the station name.
  441.      * @param stationName the station name to set
  442.      */
  443.     public void setStationName(final String stationName) {
  444.         this.stationName = stationName;
  445.     }

  446.     /** Getter for the station identifier.
  447.      * @return the station identifier
  448.      */
  449.     public String getStationIdentifier() {
  450.         return stationIdentifier;
  451.     }

  452.     /** Setter for the station identifier.
  453.      * @param stationIdentifier the station identifier to set
  454.      */
  455.     public void setStationIdentifier(final String stationIdentifier) {
  456.         this.stationIdentifier = stationIdentifier;
  457.     }

  458.     /** Getter for the external clock reference.
  459.      * @return the external clock reference
  460.      */
  461.     public String getExternalClockReference() {
  462.         return externalClockReference;
  463.     }

  464.     /** Setter for the external clock reference.
  465.      * @param externalClockReference the external clock reference to set
  466.      */
  467.     public void setExternalClockReference(final String externalClockReference) {
  468.         this.externalClockReference = externalClockReference;
  469.     }

  470.     /** Getter for the analysis center ID.
  471.      * @return the analysis center ID
  472.      */
  473.     public String getAnalysisCenterID() {
  474.         return analysisCenterID;
  475.     }

  476.     /** Setter for the analysis center ID.
  477.      * @param analysisCenterID the analysis center ID to set
  478.      */
  479.     public void setAnalysisCenterID(final String analysisCenterID) {
  480.         this.analysisCenterID = analysisCenterID;
  481.     }

  482.     /** Getter for the analysis center name.
  483.      * @return the analysis center name
  484.      */
  485.     public String getAnalysisCenterName() {
  486.         return analysisCenterName;
  487.     }

  488.     /** Setter for the analysis center name.
  489.      * @param analysisCenterName the analysis center name to set
  490.      */
  491.     public void setAnalysisCenterName(final String analysisCenterName) {
  492.         this.analysisCenterName = analysisCenterName;
  493.     }

  494.     /** Getter for the reference clocks.
  495.      * @return the time span map of the different refence clocks
  496.      */
  497.     public TimeSpanMap<List<ReferenceClock>> getReferenceClocks() {
  498.         return referenceClocks;
  499.     }

  500.     /** Add a list of reference clocks which will be used after a specified date.
  501.      * If the reference map has not been already created, it will be.
  502.      * @param referenceClockList the reference clock list
  503.      * @param startDate the date the list will be valid after.
  504.      */
  505.     public void addReferenceClockList(final List<ReferenceClock> referenceClockList,
  506.                                       final AbsoluteDate startDate) {
  507.         referenceClocks.addValidAfter(referenceClockList, startDate, false);
  508.     }

  509.     /** Getter for the frame name.
  510.      * @return the frame name
  511.      */
  512.     public String getFrameName() {
  513.         return frameName;
  514.     }


  515.     /** Setter for the frame name.
  516.      * @param frameName the frame name to set
  517.      */
  518.     public void setFrameName(final String frameName) {
  519.         this.frameName = frameName;
  520.     }

  521.     /** Getter for the receivers.
  522.      * @return the list of the receivers
  523.      */
  524.     public List<Receiver> getReceivers() {
  525.         return Collections.unmodifiableList(receivers);
  526.     }

  527.     /** Getter for the satellites.
  528.      * @return the list of the satellites
  529.      */
  530.     public List<String> getSatellites() {
  531.         return Collections.unmodifiableList(satellites);
  532.     }

  533.     /** Get the reference frame for the station positions.
  534.      * @return the reference frame for station positions
  535.      */
  536.     public Frame getFrame() {
  537.         return frameBuilder.apply(frameName);
  538.     }

  539.     /** Extract the clock model.
  540.      * @param name receiver/satellite name
  541.      * @param nbInterpolationPoints number of points to use in interpolation
  542.      * @return extracted clock model
  543.      * @since 12.1
  544.      */
  545.     public SampledClockModel extractClockModel(final String name,
  546.                                                final int nbInterpolationPoints) {
  547.         final List<ClockOffset> sample = new ArrayList<>();
  548.         clockData.
  549.             get(name).
  550.             forEach(c -> {
  551.                 final double offset       = c.clockBias;
  552.                 final double rate         = c.numberOfValues > 2 ? c.clockRate         : Double.NaN;
  553.                 final double acceleration = c.numberOfValues > 4 ? c.clockAcceleration : Double.NaN;
  554.                 sample.add(new ClockOffset(c.getEpoch(), offset, rate, acceleration));
  555.             });
  556.         return new SampledClockModel(sample, nbInterpolationPoints);
  557.     }

  558.     /** Getter for an unmodifiable map of clock data.
  559.      * @return the clock data
  560.      */
  561.     public Map<String, List<ClockDataLine>> getClockData() {
  562.         return Collections.unmodifiableMap(clockData);
  563.     }


  564.     /** Add a clock data line to a specified receiver/satellite.
  565.      * @param id the satellite system to add observation type
  566.      * @param clockDataLine the clock data line to add
  567.      */
  568.     public void addClockData(final String id,
  569.                              final ClockDataLine clockDataLine) {
  570.         final List<ClockDataLine> list;
  571.         synchronized (clockData) {
  572.             list = clockData.computeIfAbsent(id, i -> new ArrayList<>());
  573.         }
  574.         list.add(clockDataLine);
  575.         final AbsoluteDate epoch = clockDataLine.getEpoch();
  576.         if (epoch.isBefore(earliestEpoch)) {
  577.             earliestEpoch = epoch;
  578.         }
  579.         if (epoch.isAfter(latestEpoch)) {
  580.             latestEpoch = epoch;
  581.         }
  582.     }

  583.     /** Get earliest epoch from the {@link #getClockData() clock data}.
  584.      * @return earliest epoch from the {@link #getClockData() clock data},
  585.      * or {@link AbsoluteDate#FUTURE_INFINITY} if no data has been added
  586.      * @since 12.1
  587.      */
  588.     public AbsoluteDate getEarliestEpoch() {
  589.         return earliestEpoch;
  590.     }

  591.     /** Get latest epoch from the {@link #getClockData() clock data}.
  592.      * @return latest epoch from the {@link #getClockData() clock data},
  593.      * or {@link AbsoluteDate#PAST_INFINITY} if no data has been added
  594.      * @since 12.1
  595.      */
  596.     public AbsoluteDate getLatestEpoch() {
  597.         return latestEpoch;
  598.     }

  599.     /** Splice several Rinex clock files together.
  600.      * <p>
  601.      * Splicing Rinex clock files is intended to be used when continuous computation
  602.      * covering more than one file is needed. The metadata (version number, agency, …)
  603.      * will be retrieved from the earliest file only. Receivers and satellites
  604.      * will be merged from all files. Some receivers or satellites may be missing
  605.      * in some files… Once sorted (which is done internally), if the gap between
  606.      * segments from two file is larger than {@code maxGap}, then an error
  607.      * will be triggered.
  608.      * </p>
  609.      * <p>
  610.      * The spliced file only contains the receivers and satellites that were present
  611.      * in all files. Receivers and satellites present in some files and absent from
  612.      * other files are silently dropped.
  613.      * </p>
  614.      * <p>
  615.      * Depending on producer, successive clock files either have a gap between the last
  616.      * entry of one file and the first entry of the next file (for example files with
  617.      * a 5 minutes epoch interval may end at 23:55 and the next file start at 00:00),
  618.      * or both files have one point exactly at the splicing date (i.e. 24:00 one day
  619.      * and 00:00 next day). In the later case, the last point of the early file is dropped
  620.      * and the first point of the late file takes precedence, hence only one point remains
  621.      * in the spliced file ; this design choice is made to enforce continuity and
  622.      * regular interpolation.
  623.      * </p>
  624.      * @param clocks clock files to merge
  625.      * @param maxGap maximum time gap between files
  626.      * @return merged clock file
  627.      * @since 12.1
  628.      */
  629.     public static RinexClock splice(final Collection<RinexClock> clocks,
  630.                                     final double maxGap) {

  631.         // sort the files
  632.         final ChronologicalComparator comparator = new ChronologicalComparator();
  633.         final SortedSet<RinexClock> sorted =
  634.             new TreeSet<>((c1, c2) -> comparator.compare(c1.earliestEpoch, c2.earliestEpoch));
  635.         sorted.addAll(clocks);

  636.         // prepare spliced file
  637.         final RinexClock first   = sorted.first();
  638.         final RinexClock spliced = new RinexClock(first.frameBuilder);
  639.         spliced.setFormatVersion(first.getFormatVersion());
  640.         spliced.setSatelliteSystem(first.satelliteSystem);
  641.         spliced.setProgramName(first.getProgramName());
  642.         spliced.setAgencyName(first.getAgencyName());
  643.         spliced.setCreationDateString(first.getCreationDateString());
  644.         spliced.setCreationTimeString(first.getCreationTimeString());
  645.         spliced.setCreationTimeZoneString(first.getCreationTimeZoneString());
  646.         spliced.setCreationDate(first.getCreationDate());
  647.         spliced.addComment(first.getComments());
  648.         first.
  649.             getSystemObservationTypes().
  650.             forEach((s, l) -> l.forEach(o -> spliced.addSystemObservationType(s, o)));
  651.         spliced.setTimeSystem(first.getTimeSystem());
  652.         spliced.setTimeScale(first.getTimeScale());
  653.         spliced.setNumberOfLeapSeconds(first.getNumberOfLeapSeconds());
  654.         spliced.setNumberOfLeapSecondsGNSS(first.getNumberOfLeapSecondsGNSS());
  655.         first.getListAppliedDCBS().forEach(spliced::addAppliedDCBS);
  656.         first.getListAppliedPCVS().forEach(spliced::addAppliedPCVS);
  657.         first.getClockDataTypes().forEach(spliced::addClockDataType);
  658.         spliced.setStationName(first.getStationName());
  659.         spliced.setStationIdentifier(first.getStationIdentifier());
  660.         spliced.setExternalClockReference(first.getExternalClockReference());
  661.         spliced.setAnalysisCenterID(first.getAnalysisCenterID());
  662.         spliced.setAnalysisCenterName(first.getAnalysisCenterName());
  663.         spliced.setFrameName(first.getFrameName());

  664.         // merge reference clocks maps
  665.         sorted.forEach(rc -> {
  666.             TimeSpanMap.Span<List<ReferenceClock>> span = rc.getReferenceClocks().getFirstSpan();
  667.             while (span != null) {
  668.                 if (span.getData() != null) {
  669.                     spliced.addReferenceClockList(span.getData(), span.getStart());
  670.                 }
  671.                 span = span.next();
  672.             }
  673.         });

  674.         final List<String> clockIds = new ArrayList<>();

  675.         // identify the receivers that are present in all files
  676.         first.
  677.             getReceivers().
  678.             stream().
  679.             filter(r -> availableInAllFiles(r.getDesignator(), sorted)).
  680.             forEach(r -> {
  681.                 spliced.addReceiver(r);
  682.                 clockIds.add(r.getDesignator());
  683.             });

  684.         // identify the satellites that are present in all files
  685.         first.
  686.             getSatellites().
  687.             stream().
  688.             filter(s -> availableInAllFiles(s, sorted)).
  689.             forEach(s -> {
  690.                 spliced.addSatellite(s);
  691.                 clockIds.add(s);
  692.             });

  693.         // add the clock lines
  694.         for (final String clockId : clockIds) {
  695.             AbsoluteDate previous = null;
  696.             for (final RinexClock rc : sorted) {
  697.                 if (previous != null) {
  698.                     if (rc.getEarliestEpoch().durationFrom(previous) > maxGap) {
  699.                         throw new OrekitException(OrekitMessages.TOO_LONG_TIME_GAP_BETWEEN_DATA_POINTS,
  700.                                                   rc.getEarliestEpoch().durationFrom(previous));
  701.                     }
  702.                 }
  703.                 previous = rc.getLatestEpoch();
  704.                 rc.getClockData().get(clockId).forEach(cd -> spliced.addClockData(clockId, cd));
  705.             }
  706.         }

  707.         return spliced;

  708.     }

  709.     /** Check if clock data is available in all files.
  710.      * @param clockId clock id
  711.      * @param files clock files
  712.      * @return true if clock is available in all files
  713.      */
  714.     private static boolean availableInAllFiles(final String clockId, final Collection<RinexClock> files) {
  715.         for (final RinexClock rc : files) {
  716.             if (!rc.getClockData().containsKey(clockId)) {
  717.                 return false;
  718.             }
  719.         }
  720.         return true;
  721.     }

  722.     /** Clock data for a single station.
  723.      * <p> Data epoch is not linked to any time system in order to parse files with missing lines.
  724.      * Though, the default version of the getEpoch() method links the data time components with the clock file object time scale.
  725.      * The latter can be set with a default value (UTC). Caution is recommended.
  726.      */
  727.     public class ClockDataLine {

  728.         /** Clock data type. */
  729.         private final ClockDataType dataType;

  730.         /** Receiver/Satellite name. */
  731.         private final String name;

  732.         /** Epoch date components. */
  733.         private final DateComponents dateComponents;

  734.         /** Epoch time components. */
  735.         private final TimeComponents timeComponents;

  736.         /** Number of data values to follow.
  737.          * This number might not represent the non zero values in the line.
  738.          */
  739.         private final int numberOfValues;

  740.         /** Clock bias (seconds). */
  741.         private final double clockBias;

  742.         /** Clock bias sigma (seconds). */
  743.         private final double clockBiasSigma;

  744.         /** Clock rate (dimensionless). */
  745.         private final double clockRate;

  746.         /** Clock rate sigma (dimensionless). */
  747.         private final double clockRateSigma;

  748.         /** Clock acceleration (seconds^-1). */
  749.         private final double clockAcceleration;

  750.         /** Clock acceleration sigma (seconds^-1). */
  751.         private final double clockAccelerationSigma;

  752.         /** Constructor.
  753.          * @param type the clock data type
  754.          * @param name the receiver/satellite name
  755.          * @param dateComponents the epoch date components
  756.          * @param timeComponents the epoch time components
  757.          * @param numberOfValues the number of values to follow
  758.          * @param clockBias the clock bias in seconds
  759.          * @param clockBiasSigma the clock bias sigma in seconds
  760.          * @param clockRate the clock rate
  761.          * @param clockRateSigma the clock rate sigma
  762.          * @param clockAcceleration the clock acceleration in seconds^-1
  763.          * @param clockAccelerationSigma the clock acceleration in seconds^-1
  764.          */
  765.         public ClockDataLine (final ClockDataType type, final String name,
  766.                               final DateComponents dateComponents,
  767.                               final TimeComponents timeComponents,
  768.                               final int numberOfValues,
  769.                               final double clockBias, final double clockBiasSigma,
  770.                               final double clockRate, final double clockRateSigma,
  771.                               final double clockAcceleration, final double clockAccelerationSigma) {

  772.             this.dataType                = type;
  773.             this.name                    = name;
  774.             this.dateComponents          = dateComponents;
  775.             this.timeComponents          = timeComponents;
  776.             this.numberOfValues          = numberOfValues;
  777.             this.clockBias               = clockBias;
  778.             this.clockBiasSigma          = clockBiasSigma;
  779.             this.clockRate               = clockRate;
  780.             this.clockRateSigma          = clockRateSigma;
  781.             this.clockAcceleration       = clockAcceleration;
  782.             this.clockAccelerationSigma = clockAccelerationSigma;
  783.         }

  784.         /** Getter for the clock data type.
  785.          * @return the clock data type
  786.          */
  787.         public ClockDataType getDataType() {
  788.             return dataType;
  789.         }

  790.         /** Getter for the receiver/satellite name.
  791.          * @return the receiver/satellite name
  792.          */
  793.         public String getName() {
  794.             return name;
  795.         }

  796.         /** Getter for the number of values to follow.
  797.          * @return the number of values to follow
  798.          */
  799.         public int getNumberOfValues() {
  800.             return numberOfValues;
  801.         }

  802.         /** Get data line epoch.
  803.          * This method should be used if Time System ID line is present in the clock file.
  804.          * If it is missing, UTC time scale will be applied.
  805.          * To specify tim scale, use {@link #getEpoch(TimeScale) getEpoch(TimeScale)} method.
  806.          * @return the data line epoch
  807.          */
  808.         public AbsoluteDate getEpoch() {
  809.             return new AbsoluteDate(dateComponents, timeComponents, timeScale);
  810.         }

  811.         /** Get data line epoch.
  812.          * This method should be used in case Time System ID line is missing.
  813.          * Otherwise, it is adviced to rather use {@link #getEpoch() getEpoch()} method.
  814.          * @param epochTimeScale the time scale in which the epoch is defined
  815.          * @return the data line epoch set in the specified time scale
  816.          */
  817.         public AbsoluteDate getEpoch(final TimeScale epochTimeScale) {
  818.             return new AbsoluteDate(dateComponents, timeComponents, epochTimeScale);
  819.         }

  820.         /** Getter for the clock bias.
  821.          * @return the clock bias in seconds
  822.          */
  823.         public double getClockBias() {
  824.             return clockBias;
  825.         }

  826.         /** Getter for the clock bias sigma.
  827.          * @return the clock bias sigma in seconds
  828.          */
  829.         public double getClockBiasSigma() {
  830.             return clockBiasSigma;
  831.         }

  832.         /** Getter for the clock rate.
  833.          * @return the clock rate
  834.          */
  835.         public double getClockRate() {
  836.             return clockRate;
  837.         }

  838.         /** Getter for the clock rate sigma.
  839.          * @return the clock rate sigma
  840.          */
  841.         public double getClockRateSigma() {
  842.             return clockRateSigma;
  843.         }

  844.         /** Getter for the clock acceleration.
  845.          * @return the clock acceleration in seconds^-1
  846.          */
  847.         public double getClockAcceleration() {
  848.             return clockAcceleration;
  849.         }

  850.         /** Getter for the clock acceleration sigma.
  851.          * @return the clock acceleration sigma in seconds^-1
  852.          */
  853.         public double getClockAccelerationSigma() {
  854.             return clockAccelerationSigma;
  855.         }

  856.     }

  857.     /** Represents a reference clock with its validity time span. */
  858.     public static class ReferenceClock {

  859.         /** Receiver/satellite embedding the reference clock name. */
  860.         private final String referenceName;

  861.         /** Clock ID. */
  862.         private final String clockID;

  863.         /** A priori clock constraint (in seconds). */
  864.         private final double clockConstraint;

  865.         /** Start date of the validity period. */
  866.         private final AbsoluteDate startDate;

  867.         /** End date of the validity period. */
  868.         private final AbsoluteDate endDate;

  869.         /** Constructor.
  870.          * @param referenceName the name of the receiver/satellite embedding the reference clock
  871.          * @param clockID the clock ID
  872.          * @param clockConstraint the a priori clock constraint
  873.          * @param startDate the validity period start date
  874.          * @param endDate the validity period end date
  875.          */
  876.         public ReferenceClock (final String referenceName, final String clockID, final double clockConstraint,
  877.                                final AbsoluteDate startDate, final AbsoluteDate endDate) {
  878.             this.referenceName   = referenceName;
  879.             this.clockID         = clockID;
  880.             this.clockConstraint = clockConstraint;
  881.             this.startDate       = startDate;
  882.             this.endDate         = endDate;
  883.         }

  884.         /** Getter for the name of the receiver/satellite embedding the reference clock.
  885.          * @return the name of the receiver/satellite embedding the reference clock
  886.          */
  887.         public String getReferenceName() {
  888.             return referenceName;
  889.         }

  890.         /** Getter for the clock ID.
  891.          * @return the clock ID
  892.          */
  893.         public String getClockID() {
  894.             return clockID;
  895.         }

  896.         /** Getter for the clock constraint.
  897.          * @return the clock constraint
  898.          */
  899.         public double getClockConstraint() {
  900.             return clockConstraint;
  901.         }

  902.         /** Getter for the validity period start date.
  903.          * @return the validity period start date
  904.          */
  905.         public AbsoluteDate getStartDate() {
  906.             return startDate;
  907.         }

  908.         /** Getter for the validity period end date.
  909.          * @return the validity period end date
  910.          */
  911.         public AbsoluteDate getEndDate() {
  912.             return endDate;
  913.         }

  914.     }

  915.     /** Represents a receiver or a satellite with its position in the considered frame. */
  916.     public static class Receiver {

  917.         /** Designator. */
  918.         private final String designator;

  919.         /** Receiver identifier. */
  920.         private final String receiverIdentifier;

  921.         /** X coordinates in file considered Earth centered frame (in meters). */
  922.         private final double x;

  923.         /** Y coordinates in file considered Earth centered frame (in meters). */
  924.         private final double y;

  925.         /** Z coordinates in file considered Earth centered frame (in meters). */
  926.         private final double z;

  927.         /** Constructor.
  928.          * @param designator the designator
  929.          * @param receiverIdentifier the receiver identifier
  930.          * @param x the X coordinate in meters in considered Earth centered frame
  931.          * @param y the Y coordinate in meters in considered Earth centered frame
  932.          * @param z the Z coordinate in meters in considered Earth centered frame
  933.          */
  934.         public Receiver(final String designator, final String receiverIdentifier,
  935.                         final double x, final double y, final double z) {
  936.             this.designator         = designator;
  937.             this.receiverIdentifier = receiverIdentifier;
  938.             this.x                  = x;
  939.             this.y                  = y;
  940.             this.z                  = z;
  941.         }

  942.         /** Getter for the designator.
  943.          * @return the designator
  944.          */
  945.         public String getDesignator() {
  946.             return designator;
  947.         }

  948.         /** Getter for the receiver identifier.
  949.          * @return the receiver identifier
  950.          */
  951.         public String getReceiverIdentifier() {
  952.             return receiverIdentifier;
  953.         }

  954.         /** Getter for the X coordinate in meters in considered Earth centered frame.
  955.          * @return  the X coordinate in meters in considered Earth centered frame
  956.          */
  957.         public double getX() {
  958.             return x;
  959.         }

  960.         /** Getter for the Y coordinate in meters in considered Earth centered frame.
  961.          * @return  the Y coordinate in meters in considered Earth centered frame
  962.          */
  963.         public double getY() {
  964.             return y;
  965.         }

  966.         /** Getter for the Z coordinate in meters in considered Earth centered frame.
  967.          * @return  the Z coordinate in meters in considered Earth centered frame
  968.          */
  969.         public double getZ() {
  970.             return z;
  971.         }
  972.     }

  973.     /** Clock data type.
  974.      * In case of a DR type, clock data are in the sense of clock value after discontinuity minus prior.
  975.      * In other cases, clock data are in the sense of reported station/satellite clock minus reference clock value. */
  976.     public enum ClockDataType {

  977.         /** Data analysis for receiver clocks. Clock Data are*/
  978.         AR("AR"),

  979.         /** Data analysis for satellite clocks. */
  980.         AS("AS"),

  981.         /** Calibration measurement for a single GPS receiver. */
  982.         CR("CR"),

  983.         /** Discontinuity measurements for a single GPS receiver. */
  984.         DR("DR"),

  985.         /** Monitor measurements for the broadcast satellite clocks. */
  986.         MS("MS");

  987.         /** Parsing map. */
  988.         private static final Map<String, ClockDataType> KEYS_MAP = new HashMap<>();
  989.         static {
  990.             for (final ClockDataType timeSystem : values()) {
  991.                 KEYS_MAP.put(timeSystem.getKey(), timeSystem);
  992.             }
  993.         }

  994.         /** Key for the system. */
  995.         private final String key;

  996.         /** Simple constructor.
  997.          * @param key key letter
  998.          */
  999.         ClockDataType(final String key) {
  1000.             this.key = key;
  1001.         }

  1002.         /** Get the key for the system.
  1003.          * @return key for the system
  1004.          */
  1005.         public String getKey() {
  1006.             return key;
  1007.         }

  1008.         /** Parse a string to get the time system.
  1009.          * <p>
  1010.          * The string must be the time system.
  1011.          * </p>
  1012.          * @param s string to parse
  1013.          * @return the time system
  1014.          * @exception OrekitIllegalArgumentException if the string does not correspond to a time system key
  1015.          */
  1016.         public static ClockDataType parseClockDataType(final String s)
  1017.             throws OrekitIllegalArgumentException {
  1018.             final ClockDataType clockDataType = KEYS_MAP.get(s);
  1019.             if (clockDataType == null) {
  1020.                 throw new OrekitIllegalArgumentException(OrekitMessages.UNKNOWN_CLOCK_DATA_TYPE, s);
  1021.             }
  1022.             return clockDataType;
  1023.         }
  1024.     }
  1025. }