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 }