1   /* Copyright 2002-2024 Thales Alenia Space
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.observation;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.hipparchus.util.FastMath;
24  import org.orekit.errors.OrekitIllegalArgumentException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.files.rinex.RinexFile;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.time.ClockOffset;
29  import org.orekit.time.SampledClockModel;
30  
31  /** Container for Rinex observation file.
32   * @author Luc Maisonobe
33   * @since 12.0
34   */
35  public class RinexObservation extends RinexFile<RinexObservationHeader> {
36  
37      /** Observations. */
38      private final List<ObservationDataSet> observations;
39  
40      /** Simple constructor.
41       */
42      public RinexObservation() {
43          super(new RinexObservationHeader());
44          this.observations = new ArrayList<>();
45      }
46  
47      /** Get an unmodifiable view of the observations.
48       * @return unmodifiable view of the observations
49       */
50      public List<ObservationDataSet> getObservationDataSets() {
51          return Collections.unmodifiableList(observations);
52      }
53  
54      /** Add an observations data set.
55       * <p>
56       * Observations must be added chronologically, within header date range, and separated
57       * by an integer multiple of the {@link RinexObservationHeader#getInterval() interval}
58       * (ideally one interval, but entries at same dates and missing entries are allowed so
59       * any non-negative integer is allowed).
60       * </p>
61       * @param observationsDataSet observations data set
62       */
63      public void addObservationDataSet(final ObservationDataSet observationsDataSet) {
64  
65          final RinexObservationHeader header  = getHeader();
66          final AbsoluteDate           current = observationsDataSet.getDate();
67  
68          // check interval from previous observation
69          if (!observations.isEmpty()) {
70              final AbsoluteDate previous   = observations.get(observations.size() - 1).getDate();
71              final double       factor     = current.durationFrom(previous) / header.getInterval();
72              final double       acceptable = FastMath.max(0.0, FastMath.rint(factor));
73              if (FastMath.abs(factor - acceptable) > 0.01) {
74                  throw new OrekitIllegalArgumentException(OrekitMessages.INCONSISTENT_SAMPLING_DATE,
75                                                           previous.shiftedBy(acceptable * header.getInterval()),
76                                                           current);
77              }
78          }
79  
80          // check global range
81          final AbsoluteDate first = header.getTFirstObs();
82          final AbsoluteDate last  = header.getTLastObs();
83          if (!current.isBetweenOrEqualTo(first, last)) {
84              throw new OrekitIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_DATE,
85                                                       current, first, last);
86          }
87  
88          observations.add(observationsDataSet);
89  
90      }
91  
92      /** Extract the receiver clock model.
93       * @param nbInterpolationPoints number of points to use in interpolation
94       * @return extracted clock model or null if all {@link
95       * ObservationDataSet#getRcvrClkOffset() clock offsets} are zero
96       * @since 12.1
97       */
98      public SampledClockModel extractClockModel(final int nbInterpolationPoints) {
99          final List<ClockOffset> sample = new ArrayList<>();
100         boolean someNonZero = false;
101         AbsoluteDate previous = null;
102         for (final ObservationDataSet ods : observations) {
103             if (previous == null || ods.getDate().durationFrom(previous) > 0.5 * getHeader().getInterval()) {
104                 // this is a new date
105                 sample.add(new ClockOffset(ods.getDate(), ods.getRcvrClkOffset(),
106                                            Double.NaN, Double.NaN));
107                 someNonZero |= ods.getRcvrClkOffset() != 0;
108             }
109             previous = ods.getDate();
110         }
111 
112         // build a clock model only if at least some non-zero offsets have been found
113         return someNonZero ?
114                new SampledClockModel(sample, nbInterpolationPoints) :
115                null;
116 
117     }
118 
119 }