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.Arrays;
21  import java.util.List;
22  
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.files.rinex.observation.ObservationData;
26  import org.orekit.files.rinex.observation.ObservationDataSet;
27  import org.orekit.gnss.GnssSignal;
28  import org.orekit.gnss.MeasurementType;
29  import org.orekit.gnss.ObservationType;
30  import org.orekit.gnss.SatelliteSystem;
31  import org.orekit.utils.Constants;
32  
33  /** Base class for dual frequency combination of measurements.
34   * @author Bryan Cazabonne
35   * @since 10.1
36   */
37  public abstract class AbstractDualFrequencyCombination implements MeasurementCombination {
38  
39      /** Type of combination of measurements. */
40      private final CombinationType type;
41  
42      /** Satellite system used for the combination. */
43      private final SatelliteSystem system;
44  
45      /**
46       * Constructor.
47       * @param type combination of measurements type
48       * @param system satellite system
49       */
50      protected AbstractDualFrequencyCombination(final CombinationType type, final SatelliteSystem system) {
51          this.type   = type;
52          this.system = system;
53      }
54  
55      /** {@inheritDoc} */
56      @Override
57      public String getName() {
58          return type.getName();
59      }
60  
61      /**
62       * Combines observation data using a dual frequency combination of measurements.
63       * @param od1 first observation data to combined
64       * @param od2 second observation data to combined
65       * @return a combined observation data
66       */
67      public CombinedObservationData combine(final ObservationData od1, final ObservationData od2) {
68  
69          // Observation types
70          final ObservationType obsType1 = od1.getObservationType();
71          final ObservationType obsType2 = od2.getObservationType();
72  
73          // Frequencies
74          final GnssSignal signal1 = obsType1.getSignal(system);
75          final GnssSignal signal2 = obsType2.getSignal(system);
76          // Check if the combination of measurements if performed for two different frequencies
77          if (signal1 == signal2) {
78              throw new OrekitException(OrekitMessages.INCOMPATIBLE_FREQUENCIES_FOR_COMBINATION_OF_MEASUREMENTS,
79                                        signal1.getFrequency(), signal2.getFrequency(), getName());
80          }
81  
82          // Measurements types
83          final MeasurementType measType1 = obsType1.getMeasurementType();
84          final MeasurementType measType2 = obsType2.getMeasurementType();
85  
86          // Check if measurement types are the same
87          if (measType1 != measType2) {
88              // If the measurement types are differents, an exception is thrown
89              throw new OrekitException(OrekitMessages.INVALID_MEASUREMENT_TYPES_FOR_COMBINATION_OF_MEASUREMENTS,
90                                        measType1, measType2, getName());
91          }
92  
93          // Combined frequency
94          final double combinedFrequency = getCombinedFrequency(signal1, signal2);
95  
96          // Combined value
97          final double combinedValue;
98          if (obsType1.getMeasurementType() == MeasurementType.CARRIER_PHASE && !Double.isNaN(combinedFrequency)) {
99              // Transform from cycle to meters measurements
100             final double obs1Meters = od1.getValue() * signal1.getWavelength();
101             final double obs2Meters = od2.getValue() * signal2.getWavelength();
102 
103             // Calculate the combined value and convert it in cycles using the combined frequency
104             combinedValue = getCombinedValue(obs1Meters, signal1, obs2Meters, signal2) * combinedFrequency / Constants.SPEED_OF_LIGHT;
105         } else {
106             combinedValue = getCombinedValue(od1.getValue(), signal1, od2.getValue(), signal2);
107         }
108 
109         // Combined observation data
110         return new CombinedObservationData(combinedValue, combinedFrequency, type, measType1, Arrays.asList(od1, od2));
111 
112     }
113 
114     /** {@inheritDoc} */
115     @Override
116     public CombinedObservationDataSet combine(final ObservationDataSet observations) {
117 
118         // Initialize list of measurements
119         final List<ObservationData> pseudoRanges = new ArrayList<>();
120         final List<ObservationData> phases       = new ArrayList<>();
121 
122         // Loop on observation data to fill lists
123         for (final ObservationData od : observations.getObservationData()) {
124             if (!Double.isNaN(od.getValue())) {
125                 if (od.getObservationType().getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
126                     pseudoRanges.add(od);
127                 } else if (od.getObservationType().getMeasurementType() == MeasurementType.CARRIER_PHASE) {
128                     phases.add(od);
129                 }
130             }
131         }
132 
133         // Initialize list of combined observation data
134         final List<CombinedObservationData> combined = new ArrayList<>();
135         // Combine pseudo-ranges
136         for (int i = 0; i < pseudoRanges.size() - 1; i++) {
137             for (int j = 1; j < pseudoRanges.size(); j++) {
138                 final boolean combine = isCombinationPossible(pseudoRanges.get(i), pseudoRanges.get(j));
139                 if (combine) {
140                     combined.add(combine(pseudoRanges.get(i), pseudoRanges.get(j)));
141                 }
142             }
143         }
144         // Combine carrier-phases
145         for (int i = 0; i < phases.size() - 1; i++) {
146             for (int j = 1; j < phases.size(); j++) {
147                 final boolean combine = isCombinationPossible(phases.get(i), phases.get(j));
148                 if (combine) {
149                     combined.add(combine(phases.get(i), phases.get(j)));
150                 }
151             }
152         }
153 
154         return new CombinedObservationDataSet(observations.getSatellite().getSystem(),
155                                               observations.getSatellite().getPRN(),
156                                               observations.getDate(),
157                                               observations.getRcvrClkOffset(), combined);
158     }
159 
160     /**
161      * Get the combined observed value of two measurements.
162      *
163      * @param obs1 observed value of the first measurement
164      * @param s1   frequency of the first measurement
165      * @param obs2 observed value of the second measurement
166      * @param s2   frequency of the second measurement
167      * @return combined observed value
168      */
169     protected abstract double getCombinedValue(double obs1, GnssSignal s1, double obs2, GnssSignal s2);
170 
171     /**
172      * Get the combined frequency of two measurements.
173      *
174      * @param s1 frequency of the first measurement
175      * @param s2 frequency of the second measurement
176      * @return combined frequency in Hz
177      */
178     protected abstract double getCombinedFrequency(GnssSignal s1, GnssSignal s2);
179 
180     /**
181      * Verifies if two observation data can be combined.
182      * @param data1 first observation data
183      * @param data2 second observation data
184      * @return true if observation data can be combined
185      */
186     private boolean isCombinationPossible(final ObservationData data1, final ObservationData data2) {
187         // Observation types
188         final ObservationType obsType1 = data1.getObservationType();
189         final ObservationType obsType2 = data2.getObservationType();
190         // Dual-frequency combination is possible only if observation code is the same and data frequencies are different
191         return obsType1.getSignal(system) != obsType2.getSignal(system) &&
192                obsType1.getSignalCode() == obsType2.getSignalCode();
193     }
194 
195 }