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 }