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