1 /* Copyright 2022-2025 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.Iterator;
22 import java.util.List;
23
24 import org.hipparchus.util.FastMath;
25 import org.orekit.errors.OrekitIllegalArgumentException;
26 import org.orekit.errors.OrekitMessages;
27 import org.orekit.files.rinex.RinexFile;
28 import org.orekit.time.AbsoluteDate;
29 import org.orekit.time.ClockOffset;
30 import org.orekit.time.SampledClockModel;
31
32 /** Container for Rinex observation file.
33 * @author Luc Maisonobe
34 * @since 12.0
35 */
36 public class RinexObservation extends RinexFile<RinexObservationHeader> {
37
38 /** Observations. */
39 private final List<ObservationDataSet> observations;
40
41 /** Simple constructor.
42 */
43 public RinexObservation() {
44 super(new RinexObservationHeader());
45 this.observations = new ArrayList<>();
46 }
47
48 /** Get an unmodifiable view of the observations.
49 * @return unmodifiable view of the observations
50 * @see #bundleByDates()
51 */
52 public List<ObservationDataSet> getObservationDataSets() {
53 return Collections.unmodifiableList(observations);
54 }
55
56 /** Get an iterable view of observations bundled by common date.
57 * <p>
58 * The observations are the same as the ones provided by {@link #getObservationDataSets()},
59 * but instead of one single list covering the whole Rinex file, several lists
60 * are made available, all observations withing each list sharing a common date
61 * </p>
62 * @return an iterable view of observations bundled by common date
63 * @see #getObservationDataSets()
64 * @since 13.0
65 */
66 public Iterable<List<ObservationDataSet>> bundleByDates() {
67 return BundlingIterator::new;
68 }
69
70 /** Add an observations data set.
71 * <p>
72 * Observations must be added chronologically, within header date range, and separated
73 * by an integer multiple of the {@link RinexObservationHeader#getInterval() interval}
74 * (ideally one interval, but entries at same dates and missing entries are allowed so
75 * any non-negative integer is allowed).
76 * </p>
77 * @param observationsDataSet observations data set
78 */
79 public void addObservationDataSet(final ObservationDataSet observationsDataSet) {
80
81 final RinexObservationHeader header = getHeader();
82 final AbsoluteDate current = observationsDataSet.getDate();
83
84 // check interval from previous observation
85 if (!observations.isEmpty()) {
86 final AbsoluteDate previous = observations.get(observations.size() - 1).getDate();
87 final double factor = current.durationFrom(previous) / header.getInterval();
88 final double acceptable = FastMath.max(0.0, FastMath.rint(factor));
89 if (FastMath.abs(factor - acceptable) > 0.01) {
90 throw new OrekitIllegalArgumentException(OrekitMessages.INCONSISTENT_SAMPLING_DATE,
91 previous.shiftedBy(acceptable * header.getInterval()),
92 current);
93 }
94 }
95
96 // check global range
97 final AbsoluteDate first = header.getTFirstObs();
98 final AbsoluteDate last = header.getTLastObs();
99 if (!current.isBetweenOrEqualTo(first, last)) {
100 throw new OrekitIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_DATE,
101 current, first, last);
102 }
103
104 observations.add(observationsDataSet);
105
106 }
107
108 /** Extract the receiver clock model.
109 * @param nbInterpolationPoints number of points to use in interpolation
110 * @return extracted clock model or null if all {@link
111 * ObservationDataSet#getRcvrClkOffset() clock offsets} are zero
112 * @since 12.1
113 */
114 public SampledClockModel extractClockModel(final int nbInterpolationPoints) {
115 final List<ClockOffset> sample = new ArrayList<>();
116 boolean someNonZero = false;
117 AbsoluteDate previous = null;
118 for (final ObservationDataSet ods : observations) {
119 if (previous == null || ods.getDate().durationFrom(previous) > 0.5 * getHeader().getInterval()) {
120 // this is a new date
121 sample.add(new ClockOffset(ods.getDate(), ods.getRcvrClkOffset(),
122 Double.NaN, Double.NaN));
123 someNonZero |= ods.getRcvrClkOffset() != 0;
124 }
125 previous = ods.getDate();
126 }
127
128 // build a clock model only if at least some non-zero offsets have been found
129 return someNonZero ?
130 new SampledClockModel(sample, nbInterpolationPoints) :
131 null;
132
133 }
134
135 /** Iterator providing {@link ObservationDataSet} bundled by dates.
136 * @since 13.0
137 */
138 private class BundlingIterator implements Iterator<List<ObservationDataSet>> {
139
140 /** Ratio for dates comparisons tolerance. */
141 private static final double RATIO = 0.01;
142
143 /** Tolerance for dates comparisons. */
144 private final double tolerance;
145
146 /** Index of next bundle. */
147 private int next;
148
149 /** Build an iterator starting at first observations data set.
150 */
151 BundlingIterator() {
152 this.tolerance = RATIO * getHeader().getInterval();
153 this.next = 0;
154 }
155
156 /** {@inheritDoc} */
157 @Override
158 public boolean hasNext() {
159 return next < observations.size();
160 }
161
162 /** {@inheritDoc} */
163 @Override
164 public List<ObservationDataSet> next() {
165
166 // common date for all observation data sets in this bundle
167 final AbsoluteDate bundleDate = observations.get(next).getDate();
168
169 final int start = next;
170 while (next < observations.size() &&
171 FastMath.abs(observations.get(next).getDate().durationFrom(bundleDate)) <= tolerance) {
172 // we can include next observation in the current bundle
173 ++next;
174 }
175
176 // return the bundle of observations that share the same date
177 return observations.subList(start, next);
178
179 }
180
181 }
182
183 }