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.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.orekit.files.rinex.observation.ObservationDataSet;
25  import org.orekit.gnss.GnssSignal;
26  import org.orekit.gnss.SatelliteSystem;
27  import org.orekit.time.AbsoluteDate;
28  
29  /**
30   * Base class for cycle-slip detectors.
31   * @author David Soulard
32   * @since 10.2
33   */
34  public abstract class AbstractCycleSlipDetector implements CycleSlipDetectors {
35  
36      /** Separator for satellite name. */
37      private static final String SEPARATOR = " - ";
38  
39      /** Minimum number of measurement needed before being able to figure out cycle-slip occurrence.*/
40      private final int minMeasurementNumber;
41  
42      /** Maximum time lapse between two measurements without considering a cycle-slip occurred [s]. */
43      private final double dt;
44  
45      /** List which contains all the info regarding the cycle slip. */
46      private final List<CycleSlipDetectorResults> data;
47  
48      /** List of all the things use for cycle-slip detections. */
49      private final List<Map<GnssSignal, DataForDetection>> stuff;
50  
51      /**
52       * Cycle-slip detector Abstract Constructor.
53       * @param dt time gap between two consecutive measurements in seconds
54       *        (if time between two consecutive measurement is greater than dt, a cycle slip is declared)
55       * @param n number of measures needed before starting test if a cycle-slip occurs
56       */
57      AbstractCycleSlipDetector(final double dt, final int n) {
58          this.minMeasurementNumber = n;
59          this.dt                   = dt;
60          this.data                 = new ArrayList<>();
61          this.stuff                = new ArrayList<>();
62      }
63  
64      /** {@inheritDoc} */
65      @Override
66      public List<CycleSlipDetectorResults> detect(final List<ObservationDataSet> observations) {
67          // Loop on observation data set
68          for (ObservationDataSet observation: observations) {
69              // Manage data
70              manageData(observation);
71          }
72          // Return the results of the cycle-slip detection
73          return getResults();
74      }
75  
76      /**
77       * The method is in charge of collecting the measurements, manage them, and call the detection method.
78       * @param observation observation data set
79       */
80      protected abstract void manageData(ObservationDataSet observation);
81  
82      /**
83       * Get the minimum number of measurement needed before being able to figure out cycle-slip occurrence.
84       * @return the minimum number of measurement needed before being able to figure out cycle-slip occurrence.
85       */
86      protected int getMinMeasurementNumber() {
87          return minMeasurementNumber;
88      }
89  
90      /**
91       * Get the maximum time lapse between 2 measurements without considering a cycle-slip has occurring between both.
92       * @return the maximum time lapse between 2 measurements
93       */
94      protected double getMaxTimeBeetween2Measurement() {
95          return dt;
96      }
97  
98      /**
99       * Get on all the results computed by the detector (e.g.: dates of cycle-slip).
100      * @return  all the results computed by the detector (e.g.: dates of cycle-slip).
101      */
102     protected List<CycleSlipDetectorResults> getResults() {
103         return data;
104     }
105 
106     /**
107      * Get the stuff (all the things needed for, the detector).
108      * @return return stuff
109      */
110     protected List<Map<GnssSignal, DataForDetection>> getStuffReference() {
111         return stuff;
112     }
113 
114     /** Set the data: collect data at the current Date, at the current frequency, for a given satellite, add it within the attributes data and stuff.
115      * @param nameSat name of the satellite (e.g. "GPS - 7")
116      * @param date date of the measurement
117      * @param value measurement at the current date
118      * @param signal signal used
119      */
120     protected void cycleSlipDataSet(final String nameSat, final AbsoluteDate date,
121                                     final double value, final GnssSignal signal)  {
122         // Check if cycle-slip data are empty
123         if (data.isEmpty()) {
124             data.add(new CycleSlipDetectorResults(nameSat, date, signal));
125             final Map<GnssSignal, DataForDetection> newMap = new HashMap<>();
126             newMap.put(signal, new DataForDetection(value, date));
127             stuff.add(newMap);
128         } else {
129             if (!alreadyExist(nameSat, signal)) {
130                 // As the couple satellite-frequency, first possibility is that the satellite already exist within the data but not at this frequency
131                 for (CycleSlipDetectorResults r: data) {
132                     if (r.getSatelliteName().compareTo(nameSat) == 0) {
133                         r.addAtOtherFrequency(signal, date);
134                         final Map<GnssSignal, DataForDetection> newMap = stuff.get(data.indexOf(r));
135                         newMap.put(signal, new DataForDetection(value, date));
136                         stuff.set(data.indexOf(r), newMap);
137                         return;
138                     }
139                 }
140                 //If w've reach this point is because the name does not exist, in this case another element in the two list should be added
141                 data.add(new CycleSlipDetectorResults(nameSat, date, signal));
142                 final Map<GnssSignal, DataForDetection> newMap = new HashMap<>();
143                 newMap.put(signal, new DataForDetection(value, date));
144                 stuff.add(newMap);
145             } else {
146                 // We add the value of the combination of measurements
147                 addValue(nameSat, date, value, signal);
148             }
149         }
150 
151     }
152 
153     /**
154      * Create the name of a satellite from its PRN number and satellite System it belongs to.
155      * @param numSat satellite PRN number
156      * @param sys Satellite System of the satellite
157      * @return the satellite name on a specified format (e.g.: "GPS - 7")
158      */
159     protected String setName(final int numSat, final SatelliteSystem sys) {
160         return sys.name() + SEPARATOR + numSat;
161     }
162 
163     /**
164      * Return true if the link (defined by a frequency and a satellite) has been already built.
165      * @param nameSat name of the satellite (e.g.: GPS - 07 for satelite 7 of GPS constellation).
166      * @param signal signal used in the link
167      * @return true if it already exists within attribute data
168      */
169     private boolean alreadyExist(final String nameSat, final GnssSignal signal) {
170         if (data != null) {
171             for (CycleSlipDetectorResults result: data) {
172                 if (result.getSatelliteName().compareTo(nameSat) == 0) {
173                     return result.getCycleSlipMap().containsKey(signal);
174                 }
175             }
176         }
177         return false;
178     }
179 
180     /**
181      * Add a value the data.
182      * @param nameSat name of the satellite (satellite system - PRN)
183      * @param date date of the measurement
184      * @param value phase measurement minus code measurement
185      * @param signal signal used
186      */
187     private void addValue(final String nameSat, final AbsoluteDate date,
188                           final double value, final GnssSignal signal) {
189         // Loop on cycle-slip data
190         for (CycleSlipDetectorResults result: data) {
191             // Find the good position to add the data
192             if (result.getSatelliteName().compareTo(nameSat) == 0 && result.getCycleSlipMap().containsKey(signal)) {
193                 // The date is not to far away from the last one
194                 final Map<GnssSignal, DataForDetection> valuesMap = stuff.get(data.indexOf(result));
195                 final DataForDetection detect = valuesMap.get(signal);
196                 detect.write                  = (detect.write + 1) % minMeasurementNumber;
197                 detect.figures[detect.write]  = new SlipComputationData(value, date);
198                 result.setDate(signal, date);
199                 detect.canBeComputed++;
200                 break;
201             }
202         }
203     }
204 
205     /**
206      * Container for computed if cycle-slip occurs.
207      * @author David Soulard
208      */
209     static class SlipComputationData {
210 
211         /** Value of the measurement. */
212         private final double value;
213 
214         /** Date of measurement. */
215         private final AbsoluteDate date;
216 
217         /**
218          * Simple constructor.
219          * @param value value of the measurement
220          * @param date date of the measurement
221          */
222         SlipComputationData(final double value, final AbsoluteDate date) {
223             this.value  = value;
224             this.date   = date;
225         }
226 
227         /**
228          * Get the value of the measurement.
229          * @return value of the measurement
230          */
231         protected double getValue() {
232             return value;
233         }
234 
235         /**
236          * Get the date of measurement saved within this.
237          * @return date of measurement saved within this
238          */
239         protected AbsoluteDate getDate() {
240             return date;
241         }
242     }
243 
244     /**
245      * Container for all the data need for doing cycle-slip detection.
246      * @author David Soulard
247      */
248     class DataForDetection {
249 
250         /** Array used to compute cycle slip. */
251         private SlipComputationData[] figures;
252 
253         /** Integer to make the array above circular. */
254         private int write;
255 
256         /** Integer to know how many data have been added since last cycle-slip. */
257         private int canBeComputed;
258 
259         /**
260          * Constructor.
261          * @param value measurement
262          * @param date date at which measurements are taken.
263          */
264         DataForDetection(final double value, final AbsoluteDate date) {
265             this.figures       = new SlipComputationData[minMeasurementNumber];
266             this.figures[0]    = new SlipComputationData(value, date);
267             this.canBeComputed = 1;
268             this.write         = 0;
269         }
270 
271         /**
272          * Get the array of values used for computation of cycle-slip detectors.
273          * @return SlipComputationDatat array
274          */
275         protected SlipComputationData[] getFiguresReference() {
276             return figures;
277         }
278 
279         /**
280          * Get the reference of the counter of position into the array.
281          * @return the position on which writing should occur within the circular array figures.
282          */
283         protected int getWrite() {
284             return write;
285         }
286 
287         /**
288          * Get the counter on the number of measurement which have been saved up to the current date.
289          * @return the number of measurement which have been saved up to the current date
290          */
291         protected int getCanBeComputed() {
292             return canBeComputed;
293         }
294 
295         /**
296          * Reset this to the initial value when a cycle slip occurs.
297          * The first element is already setting with a value and a date
298          * @param newF new SlipComputationData[] to be used within the detector
299          * @param value to be added in the first element of the array
300          * @param date at which the value is given.
301          */
302         protected void resetFigures(final SlipComputationData[] newF, final double value, final AbsoluteDate date) {
303             this.figures        = newF;
304             this.figures[0]     = new SlipComputationData(value, date);
305             this.write          = 0;
306             this.canBeComputed  = 1;
307         }
308 
309     }
310 }