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.estimation.measurements.gnss;
18
19 import java.util.ArrayList;
20 import java.util.List;
21
22 import org.hipparchus.util.FastMath;
23 import org.orekit.files.rinex.observation.ObservationData;
24 import org.orekit.files.rinex.observation.ObservationDataSet;
25 import org.orekit.gnss.MeasurementType;
26 import org.orekit.gnss.SatelliteSystem;
27
28 /**
29 * Melbourne-Wübbena combination.
30 * <p>
31 * This combination allows, thanks to the wide-lane combination, a larger wavelength
32 * than each signal individually. Moreover, the measurement noise is reduced by the
33 * narrow-lane combination of code measurements.
34 * </p>
35 * <pre>
36 * mMW = ΦWL- RNL
37 * mMW = λWL * NWL+ b + ε
38 * </pre>
39 * With:
40 * <ul>
41 * <li>mMW : Melbourne-Wübbena measurement.</li>
42 * <li>ΦWL : Wide-Lane phase measurement.</li>
43 * <li>RNL : Narrow-Lane code measurement.</li>
44 * <li>λWL : Wide-Lane wavelength.</li>
45 * <li>NWL : Wide-Lane ambiguity (Nf1 - Nf2).</li>
46 * <li>b : Satellite and receiver instrumental delays.</li>
47 * <li>ε : Measurement noise.</li>
48 * </ul>
49 * <p>
50 * {@link NarrowLaneCombination Narrow-Lane} and {@link WideLaneCombination Wide-Lane}
51 * combinations shall be performed with the same pair of frequencies.
52 * </p>
53 *
54 * @see "Detector based in code and carrier phase data: The Melbourne-Wübbena combination,
55 * J. Sanz Subirana, J.M. Juan Zornoza and M. Hernández-Pajares, 2011"
56 *
57 * @author Bryan Cazabonne
58 * @since 10.1
59 */
60 public class MelbourneWubbenaCombination implements MeasurementCombination {
61
62 /** Threshold for frequency comparison. */
63 private static final double THRESHOLD = 1.0e-4;
64
65 /** Satellite system used for the combination. */
66 private final SatelliteSystem system;
67
68 /**
69 * Package private constructor for the factory.
70 * @param system satellite system for which the combination is applied
71 */
72 MelbourneWubbenaCombination(final SatelliteSystem system) {
73 this.system = system;
74 }
75
76 /** {@inheritDoc} */
77 @Override
78 public CombinedObservationDataSet combine(final ObservationDataSet observations) {
79
80 // Wide-Lane combination
81 final WideLaneCombination wideLane = MeasurementCombinationFactory.getWideLaneCombination(system);
82 final CombinedObservationDataSet combinedWL = wideLane.combine(observations);
83
84 // Narrow-Lane combination
85 final NarrowLaneCombination narrowLane = MeasurementCombinationFactory.getNarrowLaneCombination(system);
86 final CombinedObservationDataSet combinedNL = narrowLane.combine(observations);
87
88 // Initialize list of combined observation data
89 final List<CombinedObservationData> combined = new ArrayList<>();
90
91 // Loop on Wide-Lane measurements
92 for (CombinedObservationData odWL : combinedWL.getObservationData()) {
93 // Only consider combined phase measurements
94 if (odWL.getMeasurementType() == MeasurementType.CARRIER_PHASE) {
95 // Loop on Narrow-Lane measurements
96 for (CombinedObservationData odNL : combinedNL.getObservationData()) {
97 // Only consider combined range measurements
98 if (odNL.getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
99 // Verify if the combinations have used the same frequencies
100 final boolean isCombinationPossible = isCombinationPossible(odWL, odNL);
101 if (isCombinationPossible) {
102 // Combined value and frequency
103 final double combinedValue = odWL.getValue() - odNL.getValue();
104 final double combinedFrequency = odWL.getCombinedFrequency();
105 // Used observation data to build the Melbourn-Wübbena measurement
106 final List<ObservationData> usedData = new ArrayList<>(4);
107 usedData.add(0, odWL.getUsedObservationData().get(0));
108 usedData.add(1, odWL.getUsedObservationData().get(1));
109 usedData.add(2, odNL.getUsedObservationData().get(0));
110 usedData.add(3, odNL.getUsedObservationData().get(1));
111 // Update the combined observation data list
112 combined.add(new CombinedObservationData(combinedValue, combinedFrequency,
113 CombinationType.MELBOURNE_WUBBENA,
114 MeasurementType.COMBINED_RANGE_PHASE,
115 usedData));
116 }
117 }
118 }
119 }
120 }
121
122 return new CombinedObservationDataSet(observations.getSatellite().getSystem(),
123 observations.getSatellite().getPRN(),
124 observations.getDate(),
125 observations.getRcvrClkOffset(), combined);
126 }
127
128 /**
129 * Verifies if the Melbourne-Wübbena combination is possible between both combined observation data.
130 * <p>
131 * This method compares the frequencies of the combined measurement to decide
132 * if the combination of measurements is possible.
133 * The combination is possible if :
134 * <pre>
135 * abs(f1<sub>WL</sub> - f2<sub>WL</sub>) = abs(f1<sub>NL</sub> - f2<sub>NL</sub>)
136 * </pre>
137 * </p>
138 * @param odWL Wide-Lane measurement
139 * @param odNL Narrow-Lane measurement
140 * @return true if the Melbourne-Wübbena combination is possible
141 */
142 private boolean isCombinationPossible(final CombinedObservationData odWL, final CombinedObservationData odNL) {
143 // Frequencies
144 final double[] frequency = new double[4];
145 int j = 0;
146 for (int i = 0; i < odWL.getUsedObservationData().size(); i++) {
147 frequency[j++] = odWL.getUsedObservationData().get(i).getObservationType().getSignal(system).getFrequency();
148 frequency[j++] = odNL.getUsedObservationData().get(i).getObservationType().getSignal(system).getFrequency();
149 }
150 // Verify if used frequencies are the same.
151 // Possible numerical error is taken into account by using a threshold of acceptance
152 return (FastMath.abs(frequency[0] - frequency[2]) - FastMath.abs(frequency[1] - frequency[3])) < THRESHOLD;
153 }
154
155 /** {@inheritDoc} */
156 @Override
157 public String getName() {
158 return CombinationType.MELBOURNE_WUBBENA.getName();
159 }
160
161 }