1   /* Copyright 2002-2023 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.estimation.measurements.gnss;
18  
19  import java.io.IOException;
20  import java.security.NoSuchAlgorithmException;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.junit.jupiter.api.Assertions;
25  import org.junit.jupiter.api.BeforeEach;
26  import org.junit.jupiter.api.Test;
27  import org.orekit.Utils;
28  import org.orekit.data.DataSource;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitMessages;
31  import org.orekit.files.rinex.observation.ObservationData;
32  import org.orekit.files.rinex.observation.ObservationDataSet;
33  import org.orekit.files.rinex.observation.RinexObservationParser;
34  import org.orekit.gnss.Frequency;
35  import org.orekit.gnss.MeasurementType;
36  import org.orekit.gnss.ObservationType;
37  import org.orekit.gnss.SatInSystem;
38  import org.orekit.gnss.SatelliteSystem;
39  import org.orekit.utils.Constants;
40  
41  public class MeasurementCombinationFactoryTest {
42  
43      /** Threshold for test acceptance. */
44      private static double eps = 1.0e-4;
45  
46      /** First observation data. */
47      private ObservationData obs1;
48  
49      /** Satellite system used for the tests. */
50      private SatelliteSystem system;
51  
52      /** RINEX 2 Observation data set. */
53      private ObservationDataSet dataSetRinex2;
54  
55      /** RINEX 3 Observation data set. */
56      private ObservationDataSet dataSetRinex3;
57  
58      @BeforeEach
59      public void setUp() throws NoSuchAlgorithmException, IOException {
60          Utils.setDataRoot("gnss");
61          RinexObservationParser parser = new RinexObservationParser();
62  
63          // Observation data
64          obs1 = new ObservationData(ObservationType.L1, 2.25E7, 0, 0);
65  
66          // RINEX 2 Observation data set
67          final String name2 = "rinex/truncate-sbch0440.16o";
68          List<ObservationDataSet> parsed2 = parser.parse(new DataSource(name2,
69                                                                         () -> Utils.class.getClassLoader().getResourceAsStream(name2))).
70                                             getObservationDataSets();
71          dataSetRinex2 = parsed2.get(0);
72  
73          // RINEX 3 Observation data set
74          final String name3 = "rinex/aaaa0000.00o";
75          List<ObservationDataSet> parsed3 = parser.parse(new DataSource(name3,
76                                                                         () -> Utils.class.getClassLoader().getResourceAsStream(name3))).
77                                             getObservationDataSets();
78          dataSetRinex3 = parsed3.get(1);
79  
80          // Satellite system
81          system = dataSetRinex2.getSatellite().getSystem();
82      }
83  
84      @Test
85      public void testEmptyDataSetGeometryFree() {
86          doTestEmptyDataSet(MeasurementCombinationFactory.getGeometryFreeCombination(system));
87      }
88  
89      @Test
90      public void testEmptyDataSetIonoFree() {
91          doTestEmptyDataSet(MeasurementCombinationFactory.getIonosphereFreeCombination(system));
92      }
93  
94      @Test
95      public void testEmptyDataSetWideLane() {
96          doTestEmptyDataSet(MeasurementCombinationFactory.getWideLaneCombination(system));
97      }
98  
99      @Test
100     public void testEmptyDataSetNarrowLane() {
101         doTestEmptyDataSet(MeasurementCombinationFactory.getNarrowLaneCombination(system));
102     }
103 
104     @Test
105     public void testEmptyDataSetMelbourneWubbena() {
106         doTestEmptyDataSet(MeasurementCombinationFactory.getMelbourneWubbenaCombination(system));
107     }
108 
109     @Test
110     public void testEmptyDataSetPhaseMinusCode() {
111         doTestEmptyDataSet(MeasurementCombinationFactory.getPhaseMinusCodeCombination(system));
112     }
113 
114     @Test
115     public void testEmptyDataSetGRAPHIC() {
116         doTestEmptyDataSet(MeasurementCombinationFactory.getGRAPHICCombination(system));
117     }
118 
119     /**
120      * Test code stability if an empty observation data set is used.
121      */
122     private void doTestEmptyDataSet(final MeasurementCombination combination) {
123         // Build empty observation data set
124         final ObservationDataSet emptyDataSet = new ObservationDataSet(new SatInSystem(dataSetRinex2.getSatellite().getSystem(),
125                                                                                        dataSetRinex2.getSatellite()
126                                                                                         .getPRN()),
127                                                                        dataSetRinex2.getDate(), 0, dataSetRinex2.getRcvrClkOffset(),
128                                                                        new ArrayList<ObservationData>());
129         // Test first method signature
130         final CombinedObservationDataSet combinedData = combination.combine(emptyDataSet);
131         Assertions.assertEquals(0, combinedData.getObservationData().size());
132     }
133 
134     @Test
135     public void testExceptionsGeometryFree() {
136         doTestExceptionsDualFrequency(MeasurementCombinationFactory.getGeometryFreeCombination(system));
137     }
138 
139     @Test
140     public void testExceptionsIonoFree() {
141         doTestExceptionsDualFrequency(MeasurementCombinationFactory.getIonosphereFreeCombination(system));
142     }
143 
144     @Test
145     public void testExceptionsWideLane() {
146         doTestExceptionsDualFrequency(MeasurementCombinationFactory.getWideLaneCombination(system));
147     }
148 
149     @Test
150     public void testExceptionsNarrowLane() {
151         doTestExceptionsDualFrequency(MeasurementCombinationFactory.getNarrowLaneCombination(system));
152     }
153 
154     @Test
155     public void testExceptionsPhaseMinusCode() {
156         doTestExceptionsSingleFrequency(MeasurementCombinationFactory.getPhaseMinusCodeCombination(system));
157     }
158 
159     @Test
160     public void testExceptionsGRAPHIC() {
161         doTestExceptionsSingleFrequency(MeasurementCombinationFactory.getGRAPHICCombination(system));
162     }
163 
164     private void doTestExceptionsSingleFrequency(final AbstractSingleFrequencyCombination combination) {
165         // Test INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS exception
166         try {
167             final ObservationData observation = new ObservationData(ObservationType.L5, 12345678.0, 0, 0);
168             combination.combine(obs1, observation);
169             Assertions.fail("an exception should have been thrown");
170         } catch (OrekitException oe) {
171             Assertions.assertEquals(OrekitMessages.INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS, oe.getSpecifier());
172         }
173 
174         // Test INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS exception
175         try {
176             final ObservationData observation = new ObservationData(ObservationType.L1, 12345678.0, 0, 0);
177             combination.combine(obs1, observation);
178             Assertions.fail("an exception should have been thrown");
179         } catch (OrekitException oe) {
180             Assertions.assertEquals(OrekitMessages.INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS, oe.getSpecifier());
181         }
182     }
183 
184     /**
185      * Test exceptions.
186      */
187     private void doTestExceptionsDualFrequency(final AbstractDualFrequencyCombination combination) {
188         // Test INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS exception
189         try {
190             final ObservationData observation = new ObservationData(ObservationType.L1, 12345678.0, 0, 0);
191             combination.combine(obs1, observation);
192             Assertions.fail("an exception should have been thrown");
193         } catch (OrekitException oe) {
194             Assertions.assertEquals(OrekitMessages.INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS, oe.getSpecifier());
195         }
196 
197         // Test INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS exception
198         try {
199             final ObservationData observation = new ObservationData(ObservationType.D2, 12345678.0, 0, 0);
200             combination.combine(obs1, observation);
201             Assertions.fail("an exception should have been thrown");
202         } catch (OrekitException oe) {
203             Assertions.assertEquals(OrekitMessages.INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS, oe.getSpecifier());
204         }
205     }
206 
207     @Test
208     public void testRinex2GeometryFree() {
209         doTestRinexDualFrequency(MeasurementCombinationFactory.getGeometryFreeCombination(system),
210                      CombinationType.GEOMETRY_FREE, 6.953, 27534453.519,0.0,  Double.NaN, 2, 2);
211     }
212 
213     @Test
214     public void testRinex2IonoFree() {
215         doTestRinexDualFrequency(MeasurementCombinationFactory.getIonosphereFreeCombination(system),
216                      CombinationType.IONO_FREE, 23732467.5026, 3772223175.669, 0.0, 4658 * Frequency.F0, 2, 2);
217     }
218 
219     @Test
220     public void testRinex2WideLane() {
221         doTestRinexDualFrequency(MeasurementCombinationFactory.getWideLaneCombination(system),
222                      CombinationType.WIDE_LANE, 23732453.7100, 27534453.519, 0.0, 34 * Frequency.F0, 2, 2);
223     }
224 
225     @Test
226     public void testRinex2NarrowLane() {
227         doTestRinexDualFrequency(MeasurementCombinationFactory.getNarrowLaneCombination(system),
228                      CombinationType.NARROW_LANE, 23732481.2951, 221895659.955, 0.0, 274 * Frequency.F0, 2, 2);
229     }
230 
231     @Test
232     public void testRinex2MelbourneWubbena() {
233         doTestRinexDualFrequency(MeasurementCombinationFactory.getMelbourneWubbenaCombination(system),
234                      CombinationType.MELBOURNE_WUBBENA, 0.0, 0.0, 3801972.2239, 34 * Frequency.F0, 1, 2);
235     }
236 
237     @Test
238     public void testRinex2PhaseMinusCode() {
239         doTestRinex2SingleFrequency(MeasurementCombinationFactory.getPhaseMinusCodeCombination(system),
240                                     CombinationType.PHASE_MINUS_CODE, 73448118.300);
241     }
242 
243     @Test
244     public void testRinex2GRAPHIC() {
245         doTestRinex2SingleFrequency(MeasurementCombinationFactory.getGRAPHICCombination(system),
246                                     CombinationType.GRAPHIC, 60456544.068);
247     }
248 
249     private void doTestRinex2SingleFrequency(final MeasurementCombination combination, final CombinationType type,
250                                              final double expectedL2P2) {
251         // Perform combination on the observation data set depending the Rinex version
252         final CombinedObservationDataSet combinedDataSet = combination.combine(dataSetRinex2);
253         checkCombinedDataSet(combinedDataSet, 1);
254         Assertions.assertEquals(type.getName(), combination.getName());
255         // Verify the combined observation data
256         final List<CombinedObservationData> data = combinedDataSet.getObservationData();
257         // L2/P2
258         Assertions.assertEquals(expectedL2P2,       data.get(0).getValue(),                eps);
259         Assertions.assertEquals(120 * Frequency.F0, data.get(0).getCombinedMHzFrequency(), eps);
260     }
261 
262     @Test
263     public void testRinex3GeometryFree() {
264         doTestRinexDualFrequency(MeasurementCombinationFactory.getGeometryFreeCombination(system),
265                      CombinationType.GEOMETRY_FREE, 2.187, 3821708.096, 0.0, Double.NaN, 2, 3);
266     }
267 
268     @Test
269     public void testRinex3IonoFree() {
270         doTestRinexDualFrequency(MeasurementCombinationFactory.getIonosphereFreeCombination(system),
271                      CombinationType.IONO_FREE, 22399214.1934, 179620369.206, 0.0, 235 * Frequency.F0, 2, 3);
272     }
273 
274     @Test
275     public void testRinex3WideLane() {
276         doTestRinexDualFrequency(MeasurementCombinationFactory.getWideLaneCombination(system),
277                      CombinationType.WIDE_LANE, 22399239.8790, 3821708.096, 0.0, 5 * Frequency.F0, 2, 3);
278     }
279 
280     @Test
281     public void testRinex3NarrowLane() {
282         doTestRinexDualFrequency(MeasurementCombinationFactory.getNarrowLaneCombination(system),
283                     CombinationType.NARROW_LANE, 22399188.5078, 179620457.900, 0.0, 235 * Frequency.F0, 2, 3);
284     }
285 
286     @Test
287     public void testRinex3MelbourneWubbena() {
288         doTestRinexDualFrequency(MeasurementCombinationFactory.getMelbourneWubbenaCombination(system),
289                      CombinationType.MELBOURNE_WUBBENA, 0.0, 0.0, -18577480.4117, 5 * Frequency.F0, 1, 3);
290     }
291 
292     @Test
293     public void testRinex3PhaseMinusCode() {
294         doTestRinex3SingleFrequency(MeasurementCombinationFactory.getPhaseMinusCodeCombination(system),
295                                     CombinationType.PHASE_MINUS_CODE, 95309391.697, 69321899.401,
296                                     69321893.420, 65500187.511);
297     }
298 
299     @Test
300     public void testRinex3GRAPHIC() {
301         doTestRinex3SingleFrequency(MeasurementCombinationFactory.getGRAPHICCombination(system),
302                                     CombinationType.GRAPHIC, 70053877.7315, 57060139.2905,
303                                     57060136.2880, 55149281.1465);
304     }
305 
306     private void doTestRinex3SingleFrequency(final MeasurementCombination combination, final CombinationType type,
307                                              final double expected1C, final double expected2W,
308                                              final double expected2X, final double expected5X) {
309         // Perform combination on the observation data set depending the Rinex version
310         final CombinedObservationDataSet combinedDataSet = combination.combine(dataSetRinex3);
311         Assertions.assertEquals(type.getName(), combination.getName());
312         // Verify the combined observation data
313         final List<CombinedObservationData> data = combinedDataSet.getObservationData();
314         // L1C/C1C
315         Assertions.assertEquals(expected1C,         data.get(0).getValue(),                eps);
316         Assertions.assertEquals(154 * Frequency.F0, data.get(0).getCombinedMHzFrequency(), eps);
317         // L2W/C2W
318         Assertions.assertEquals(expected2W,         data.get(1).getValue(),                eps);
319         Assertions.assertEquals(120 * Frequency.F0, data.get(1).getCombinedMHzFrequency(), eps);
320         // L2X/C2X
321         Assertions.assertEquals(expected2X,         data.get(2).getValue(),                eps);
322         Assertions.assertEquals(120 * Frequency.F0, data.get(1).getCombinedMHzFrequency(), eps);
323         // L5X/C5X
324         Assertions.assertEquals(expected5X,         data.get(3).getValue(),                eps);
325         Assertions.assertEquals(115 * Frequency.F0, data.get(3).getCombinedMHzFrequency(), eps);
326     }
327 
328     /**
329      * Test if Rinex formats can be used for the combination of measurements
330      */
331     private void doTestRinexDualFrequency(final MeasurementCombination combination, final CombinationType expectedType,
332                              final double expectedRangeValue, final double expectedPhaseValue, final double expectedRangePhase,
333                              final double expectedFrequency, final int expectedSize, final int rinexVersion) {
334 
335         // Perform combination on the observation data set depending the Rinex version
336         final CombinedObservationDataSet combinedDataSet;
337         if (rinexVersion == 2) {
338             combinedDataSet = combination.combine(dataSetRinex2);
339             checkCombinedDataSet(combinedDataSet, expectedSize);
340         } else {
341             combinedDataSet = combination.combine(dataSetRinex3);
342             Assertions.assertEquals(expectedSize, combinedDataSet.getObservationData().size());
343         }
344 
345         Assertions.assertEquals(expectedType.getName(), combination.getName());
346 
347         // Verify the combined observation data
348         for (CombinedObservationData cod : combinedDataSet.getObservationData()) {
349 
350             if (cod.getMeasurementType() == MeasurementType.CARRIER_PHASE) {
351 
352                 Assertions.assertEquals(expectedPhaseValue, cod.getValue(),                eps);
353                 Assertions.assertEquals(expectedFrequency,  cod.getCombinedMHzFrequency(), eps);
354                 Assertions.assertEquals(expectedType,       cod.getCombinationType());
355 
356             } else if (cod.getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
357 
358                 Assertions.assertEquals(expectedRangeValue, cod.getValue(),                eps);
359                 Assertions.assertEquals(expectedFrequency,  cod.getCombinedMHzFrequency(), eps);
360                 Assertions.assertEquals(expectedType,       cod.getCombinationType());
361 
362             } else if (cod.getMeasurementType() == MeasurementType.COMBINED_RANGE_PHASE) {
363 
364                 Assertions.assertEquals(expectedRangePhase, cod.getValue(),                eps);
365                 Assertions.assertEquals(expectedFrequency,  cod.getCombinedMHzFrequency(), eps);
366                 Assertions.assertEquals(expectedType,       cod.getCombinationType());
367 
368             }
369 
370         }
371     }
372 
373     private void checkCombinedDataSet(final CombinedObservationDataSet combinedDataSet,
374                                       final int expectedSize) {
375         // Verify the number of combined data set
376         Assertions.assertEquals(expectedSize, combinedDataSet.getObservationData().size());
377         // Verify satellite data
378         Assertions.assertEquals(30, combinedDataSet.getPrnNumber());
379         Assertions.assertEquals(SatelliteSystem.GPS, combinedDataSet.getSatelliteSystem());
380         // Verify receiver clock
381         Assertions.assertEquals(0.0, combinedDataSet.getRcvrClkOffset(), eps);
382         // Verify date
383         Assertions.assertEquals("2016-02-13T00:49:43.000Z", combinedDataSet.getDate().toString());
384     }
385 
386     @Test
387     public void testIssue746() {
388 
389         // This test uses the example provided by Amir Allahvirdi-Zadeh in the Orekit issue tracker
390         // Source of the values: https://gitlab.orekit.org/orekit/orekit/-/issues/746
391 
392         // Build the observation data
393         final ObservationData obs1 = new ObservationData(ObservationType.L1, 1.17452520667E8, 0, 0);
394         final ObservationData obs2 = new ObservationData(ObservationType.L2, 9.1521434853E7, 0, 0);
395 
396         // Ionosphere-free measurement
397         final IonosphereFreeCombination ionoFree = MeasurementCombinationFactory.getIonosphereFreeCombination(SatelliteSystem.GPS);
398         final CombinedObservationData   combined = ionoFree.combine(obs1, obs2);
399 
400         // Combine data
401         final double wavelength         = Constants.SPEED_OF_LIGHT / (combined.getCombinedMHzFrequency() * 1.0e6);
402         final double combineValueMeters = combined.getValue() * wavelength;
403 
404         // Verify
405         Assertions.assertEquals(22350475.245, combineValueMeters, 0.001);
406 
407     }
408 
409 }