SingleFrequencySmoother.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.estimation.measurements.filtering;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.orekit.files.rinex.observation.ObservationData;
import org.orekit.files.rinex.observation.ObservationDataSet;
import org.orekit.gnss.MeasurementType;
import org.orekit.gnss.ObservationType;
import org.orekit.gnss.SatelliteSystem;
import org.orekit.time.ChronologicalComparator;
/**
* Handler to perform pseudo-range smoothing using single frequency measurements.
*
* @author Louis Aucouturier
* @since 11.2
*/
public class SingleFrequencySmoother {
/** Window size for the hatch filter. */
private final int N;
/** Interval time between two measurements.*/
private final double integrationTime;
/** Threshold for the difference between smoothed and measured values. */
private final double threshold;
/** Type of the smoothing measurements. */
private final MeasurementType type;
/**
* Map storing the filters for each observation type.
* Observation types should not overlap for a single RINEX file.
*/
private final HashMap<ObservationType, SingleFrequencyHatchFilter> mapFilters;
/**
* Map storing the filtered data for each pseudo range.
* The data is stored in the form of a list of ObservationDataSetUpdate, which itself
* stores a pseudo-range ObservationData object with the filtered value, and the initial ObservationDataSet,
* needed for further processing.
*/
private final HashMap<ObservationType, List<SmoothedObservationDataSet>> mapFilteredData;
/**
* Simple constructor.
* @param type type of the smoothing measurements
* @param threshold threshold for loss of lock detection
* (represents the maximum difference between smoothed
* and measured values for loss of lock detection)
* @param N window size of the Hatch Filter
* @param integrationTime time interval between two measurements (s)
*/
public SingleFrequencySmoother(final MeasurementType type,
final double threshold, final int N,
final double integrationTime) {
this.mapFilteredData = new HashMap<>();
this.mapFilters = new HashMap<>();
this.type = type;
this.N = N;
this.integrationTime = integrationTime;
this.threshold = threshold;
}
/**
* Creates an Hatch filter given initial data.
* @param codeData input code observation data
* @param smoothingData input smoothing observation data
* @param system satellite system corresponding to the observations
* @return an Hatch filter for the input data
*/
public SingleFrequencyHatchFilter createFilter(final ObservationData codeData,
final ObservationData smoothingData,
final SatelliteSystem system) {
// Wavelength in meters
final double wavelength = smoothingData.getObservationType().getSignal(system).getWavelength();
// Return a Single Frequency Hatch Filter
return new SingleFrequencyHatchFilter(codeData, smoothingData, type, wavelength, threshold, N, integrationTime);
}
/**
* Get the map of the filtered data.
* @return a map containing the filtered data.
*/
public Map<ObservationType, List<SmoothedObservationDataSet>> getFilteredDataMap() {
return mapFilteredData;
}
/**
* Get the map storing the filters for each observation type.
* @return the map storing the filters for each observation type
*/
public final Map<ObservationType, SingleFrequencyHatchFilter> getMapFilters() {
return mapFilters;
}
/**
* Copy an ObservationData object.
* @param obsData observation data to copy
* @return a copy of the input observation data
*/
public ObservationData copyObservationData(final ObservationData obsData) {
return new ObservationData(obsData.getObservationType(), obsData.getValue(),
obsData.getLossOfLockIndicator(), obsData.getSignalStrength());
}
/**
* Applies a Single Frequency Hatch filter to a list of {@link ObservationDataSet}.
* @param listODS input observation data sets
* @param satSystem satellite System from which to filter the pseudo-range values
* @param prnNumber PRN identifier to identify the satellite from which to filter the pseudo-range values
* @param obsType observation type to use for filtering
*/
public void filterDataSet(final List<ObservationDataSet> listODS, final SatelliteSystem satSystem,
final int prnNumber, final ObservationType obsType) {
// Sort the list in chronological way to ensure the filter work on time ordered data.
final List<ObservationDataSet> sortedListODS = new ArrayList<>(listODS);
sortedListODS.sort(new ChronologicalComparator());
// For each data set, work on those corresponding to the PRN and Satellite system.
for (ObservationDataSet obsSet : sortedListODS) {
if (obsSet.getSatellite().getSystem() == satSystem && obsSet.getSatellite().getPRN() == prnNumber) {
// Get all observation data
final List<ObservationData> listObsData = obsSet.getObservationData();
// For each ObservationData check if usable (SNR and !(isNaN))
for (final ObservationData obsData : listObsData) {
final double snr = obsData.getSignalStrength();
if (!Double.isNaN(obsData.getValue()) && (snr == 0 || snr >= 4)) {
// Check measurement type, and if range check for the chosen smooting measurement
final ObservationType obsTypeData = obsData.getObservationType();
if (obsTypeData.getMeasurementType() == MeasurementType.PSEUDO_RANGE) {
ObservationData obsDataSmoothing = null;
for (final ObservationData obsDataSmoothingCurr : listObsData) {
// Iterate to find the required smoothing measurement corresponding to the observationType.
// Then copy the observation data to store them.
final ObservationType obsTypeSmoothingCurr = obsDataSmoothingCurr.getObservationType();
if (!Double.isNaN(obsDataSmoothingCurr.getValue()) && obsTypeSmoothingCurr == obsType) {
obsDataSmoothing = copyObservationData(obsDataSmoothingCurr);
}
}
// Check if the filter exist in the filter map
SingleFrequencyHatchFilter filterObject = mapFilters.get(obsTypeData);
// If the filter does not exist and the phase object are not null, initialize a new filter and
// store it in the map, initialize a new list of observationDataSetUpdate, and store it in the map.
if (filterObject == null && obsDataSmoothing != null) {
filterObject = createFilter(obsData, obsDataSmoothing, satSystem);
mapFilters.put(obsTypeData, filterObject);
final List<SmoothedObservationDataSet> odList = new ArrayList<>();
odList.add(new SmoothedObservationDataSet(obsData, obsSet));
mapFilteredData.put(obsTypeData, odList);
// If filter exist, check if a phase object is null, then reset the filter at the next step,
// else, filter the data.
} else if (filterObject != null) {
if (obsDataSmoothing == null ) {
filterObject.resetFilterNext(obsData.getValue());
} else {
final ObservationData filteredRange = filterObject.filterData(obsData, obsDataSmoothing);
mapFilteredData.get(obsTypeData).add(new SmoothedObservationDataSet(filteredRange, obsSet));
}
} else {
// If the filter does not exist and one of the phase is equal to NaN or absent
// just skip to the next ObservationDataSet.
}
}
}
}
}
}
}
}