RinexObservation.java

/* Copyright 2002-2024 Thales Alenia Space
 * 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.files.rinex.observation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hipparchus.util.FastMath;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.rinex.RinexFile;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.ClockOffset;
import org.orekit.time.SampledClockModel;

/** Container for Rinex observation file.
 * @author Luc Maisonobe
 * @since 12.0
 */
public class RinexObservation extends RinexFile<RinexObservationHeader> {

    /** Observations. */
    private final List<ObservationDataSet> observations;

    /** Simple constructor.
     */
    public RinexObservation() {
        super(new RinexObservationHeader());
        this.observations = new ArrayList<>();
    }

    /** Get an unmodifiable view of the observations.
     * @return unmodifiable view of the observations
     */
    public List<ObservationDataSet> getObservationDataSets() {
        return Collections.unmodifiableList(observations);
    }

    /** Add an observations data set.
     * <p>
     * Observations must be added chronologically, within header date range, and separated
     * by an integer multiple of the {@link RinexObservationHeader#getInterval() interval}
     * (ideally one interval, but entries at same dates and missing entries are allowed so
     * any non-negative integer is allowed).
     * </p>
     * @param observationsDataSet observations data set
     */
    public void addObservationDataSet(final ObservationDataSet observationsDataSet) {

        final RinexObservationHeader header  = getHeader();
        final AbsoluteDate           current = observationsDataSet.getDate();

        // check interval from previous observation
        if (!observations.isEmpty()) {
            final AbsoluteDate previous   = observations.get(observations.size() - 1).getDate();
            final double       factor     = current.durationFrom(previous) / header.getInterval();
            final double       acceptable = FastMath.max(0.0, FastMath.rint(factor));
            if (FastMath.abs(factor - acceptable) > 0.01) {
                throw new OrekitIllegalArgumentException(OrekitMessages.INCONSISTENT_SAMPLING_DATE,
                                                         previous.shiftedBy(acceptable * header.getInterval()),
                                                         current);
            }
        }

        // check global range
        final AbsoluteDate first = header.getTFirstObs();
        final AbsoluteDate last  = header.getTLastObs();
        if (!current.isBetweenOrEqualTo(first, last)) {
            throw new OrekitIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_DATE,
                                                     current, first, last);
        }

        observations.add(observationsDataSet);

    }

    /** Extract the receiver clock model.
     * @param nbInterpolationPoints number of points to use in interpolation
     * @return extracted clock model or null if all {@link
     * ObservationDataSet#getRcvrClkOffset() clock offsets} are zero
     * @since 12.1
     */
    public SampledClockModel extractClockModel(final int nbInterpolationPoints) {
        final List<ClockOffset> sample = new ArrayList<>();
        boolean someNonZero = false;
        AbsoluteDate previous = null;
        for (final ObservationDataSet ods : observations) {
            if (previous == null || ods.getDate().durationFrom(previous) > 0.5 * getHeader().getInterval()) {
                // this is a new date
                sample.add(new ClockOffset(ods.getDate(), ods.getRcvrClkOffset(),
                                           Double.NaN, Double.NaN));
                someNonZero |= ods.getRcvrClkOffset() != 0;
            }
            previous = ods.getDate();
        }

        // build a clock model only if at least some non-zero offsets have been found
        return someNonZero ?
               new SampledClockModel(sample, nbInterpolationPoints) :
               null;

    }

}