DualFrequencySmoother.java

  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.filtering;

  18. import java.util.ArrayList;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;

  22. import org.orekit.files.rinex.observation.ObservationData;
  23. import org.orekit.files.rinex.observation.ObservationDataSet;
  24. import org.orekit.gnss.MeasurementType;
  25. import org.orekit.gnss.ObservationType;
  26. import org.orekit.gnss.SatelliteSystem;
  27. import org.orekit.time.ChronologicalComparator;

  28. /**
  29.  * Handler to perform pseudo-range smoothing using Divergence-Free phase combinations.
  30.  *
  31.  * @author Louis Aucouturier
  32.  * @since 11.2
  33.  */
  34. public class DualFrequencySmoother {

  35.     /** Window size for the hatch filter. */
  36.     private final int N;

  37.     /** Threshold for the difference between smoothed and measured values. */
  38.     private final double threshold;

  39.     /**
  40.      * Map storing the filters for each observation type.
  41.      * Observation types should not overlap for a single RINEX file.
  42.      */
  43.     private final HashMap<ObservationType, DualFrequencyHatchFilter> mapFilters;


  44.     /**
  45.      * Map storing the filtered data for each observation type of pseudo range.
  46.      * The data is stored in the form of a list of ObservationDataSetUpdate, which itself
  47.      * stores a pseudo-range ObservationData object with the filtered value, and the initial ObservationDataSet,
  48.      * needed for further processing.
  49.      */
  50.     private final HashMap<ObservationType, List<SmoothedObservationDataSet>> mapFilteredData;

  51.     /**
  52.      * Simple constructor.
  53.      * @param threshold threshold for loss of lock detection
  54.      *                  (represents the maximum difference between smoothed
  55.      *                  and measured values for loss of lock detection)
  56.      * @param N         window size of the Hatch Filter
  57.      */
  58.     public DualFrequencySmoother(final double threshold, final int N) {
  59.         this.mapFilteredData = new HashMap<>();
  60.         this.mapFilters      = new HashMap<>();
  61.         this.N               = N;
  62.         this.threshold       = threshold;
  63.     }

  64.     /**
  65.      * Creates an Hatch filter given initial data.
  66.      *
  67.      * @param codeData    input code observation data
  68.      * @param phaseDataF1 input phase observation data for the first frequency
  69.      * @param phaseDataF2 input phase observation data for the second frequency
  70.      * @param satSystem   satellite system corresponding to the observations
  71.      * @return an Hatch filter for the input data
  72.      */
  73.     public DualFrequencyHatchFilter createFilter(final ObservationData codeData,
  74.                                                  final ObservationData phaseDataF1,
  75.                                                  final ObservationData phaseDataF2,
  76.                                                  final SatelliteSystem satSystem) {
  77.         // Wavelengths in meters
  78.         final double wavelengthF1 = phaseDataF1.getObservationType().getSignal(satSystem).getWavelength();
  79.         final double wavelengthF2 = phaseDataF2.getObservationType().getSignal(satSystem).getWavelength();
  80.         // Return a Dual Frequency Hatch Filter
  81.         return new DualFrequencyHatchFilter(codeData, phaseDataF1, phaseDataF2, wavelengthF1, wavelengthF2, threshold, N);
  82.     }

  83.     /**
  84.      * Get the map of the filtered data.
  85.      * @return a map containing the filtered data.
  86.      */
  87.     public Map<ObservationType, List<SmoothedObservationDataSet>> getFilteredDataMap() {
  88.         return mapFilteredData;
  89.     }

  90.     /**
  91.      * Get the map storing the filters for each observation type.
  92.      * @return the map storing the filters for each observation type
  93.      */
  94.     public final Map<ObservationType, DualFrequencyHatchFilter> getMapFilters() {
  95.         return mapFilters;
  96.     }

  97.     /**
  98.      * Copy an ObservationData object.
  99.      * @param obsData observation data to copy
  100.      * @return a copy of the input observation data
  101.      */
  102.     public ObservationData copyObservationData(final ObservationData obsData) {
  103.         return new ObservationData(obsData.getObservationType(), obsData.getValue(),
  104.                                    obsData.getLossOfLockIndicator(), obsData.getSignalStrength());
  105.     }

  106.     /**
  107.      * Applies a Dual Frequency Hatch filter to a list of {@link ObservationDataSet}.
  108.      *
  109.      * @param listODS input observation data sets
  110.      * @param satSystem satellite System from which to filter the pseudo-range values
  111.      * @param prnNumber PRN identifier to identify the satellite from which to filter the pseudo-range values
  112.      * @param obsTypeF1 observation type to be used as the first frequency for filtering
  113.      * @param obsTypeF2 observation type to be used as the second frequency for filtering
  114.      */
  115.     public void filterDataSet(final List<ObservationDataSet> listODS, final SatelliteSystem satSystem, final int prnNumber,
  116.                               final ObservationType obsTypeF1, final ObservationType obsTypeF2) {

  117.         // Sort the list in chronological way to ensure the filter work on time ordered data.
  118.         final List<ObservationDataSet> sortedListODS = new ArrayList<>(listODS);
  119.         sortedListODS.sort(new ChronologicalComparator());

  120.         // For each data set, work on those corresping to the PRN and Satellite system.
  121.         for (final ObservationDataSet obsSet : sortedListODS) {
  122.             if (obsSet.getSatellite().getSystem() == satSystem && obsSet.getSatellite().getPRN() == prnNumber) {
  123.                 // Get all observation data
  124.                 final List<ObservationData> listObsData = obsSet.getObservationData();
  125.                 // For each ObservationData check if usable (SNR and !(isNaN))
  126.                 for (ObservationData obsData : listObsData) {
  127.                     final double snr = obsData.getSignalStrength();
  128.                     if (!Double.isNaN(obsData.getValue()) && (snr == 0 || snr >= 4)) {

  129.                         // Check measurement type, and if range check for a phase carrier measurement of the chosen observationTypes
  130.                         final ObservationType obsTypeRange = obsData.getObservationType();
  131.                         if (obsTypeRange.getMeasurementType() == MeasurementType.PSEUDO_RANGE) {

  132.                             ObservationData obsDataPhaseF1 = null;
  133.                             ObservationData obsDataPhaseF2 = null;

  134.                             for (ObservationData obsDataPhase : listObsData) {

  135.                                 // Iterate to find the required carrier phases corresponding to the observation types.
  136.                                 // Then copy the observation data to store them.
  137.                                 final ObservationType obsTypePhase = obsDataPhase.getObservationType();

  138.                                 if (!Double.isNaN(obsDataPhase.getValue()) && obsTypePhase == obsTypeF1) {
  139.                                     obsDataPhaseF1 = copyObservationData(obsDataPhase);
  140.                                 }

  141.                                 if (!Double.isNaN(obsDataPhase.getValue()) && obsTypePhase == obsTypeF2) {
  142.                                     obsDataPhaseF2 = copyObservationData(obsDataPhase);
  143.                                 }
  144.                             }

  145.                             // Check if the filter exist in the filter map
  146.                             DualFrequencyHatchFilter filterObject = mapFilters.get(obsTypeRange);

  147.                             // If the filter does not exist and the phase object are not null, initialize a new filter and
  148.                             // store it in the map, initialize a new list of observationDataSetUpdate, and store it in the map.
  149.                             if (filterObject == null && obsDataPhaseF1 != null && obsDataPhaseF2 != null) {
  150.                                 filterObject = createFilter(obsData, obsDataPhaseF1, obsDataPhaseF2, satSystem);
  151.                                 mapFilters.put(obsTypeRange, filterObject);
  152.                                 final List<SmoothedObservationDataSet> odList = new ArrayList<>();
  153.                                 odList.add(new SmoothedObservationDataSet(obsData, obsSet));
  154.                                 mapFilteredData.put(obsTypeRange, odList);
  155.                             // If filter exist, check if a phase object is null, then reset the filter at the next step,
  156.                             // else, filter the data.
  157.                             } else if (filterObject != null) {
  158.                                 if (obsDataPhaseF1 == null || obsDataPhaseF2 == null) {
  159.                                     filterObject.resetFilterNext(obsData.getValue());
  160.                                 } else {
  161.                                     final ObservationData filteredRange = filterObject.filterData(obsData, obsDataPhaseF1, obsDataPhaseF2);
  162.                                     mapFilteredData.get(obsTypeRange).add(new SmoothedObservationDataSet(filteredRange, obsSet));
  163.                                 }
  164.                             } else {
  165.                                 // If the filter does not exist and one of the phase is equal to NaN or absent
  166.                                 // just skip to the next ObservationDataSet.
  167.                             }


  168.                         }
  169.                     }
  170.                 }
  171.             }

  172.         }
  173.     }
  174. }