RinexObservation.java

  1. /* Copyright 2022-2025 Thales Alenia Space
  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.files.rinex.observation;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.Iterator;
  21. import java.util.List;

  22. import org.hipparchus.util.FastMath;
  23. import org.orekit.errors.OrekitIllegalArgumentException;
  24. import org.orekit.errors.OrekitMessages;
  25. import org.orekit.files.rinex.RinexFile;
  26. import org.orekit.time.AbsoluteDate;
  27. import org.orekit.time.ClockOffset;
  28. import org.orekit.time.SampledClockModel;

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

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

  36.     /** Simple constructor.
  37.      */
  38.     public RinexObservation() {
  39.         super(new RinexObservationHeader());
  40.         this.observations = new ArrayList<>();
  41.     }

  42.     /** Get an unmodifiable view of the observations.
  43.      * @return unmodifiable view of the observations
  44.      * @see #bundleByDates()
  45.      */
  46.     public List<ObservationDataSet> getObservationDataSets() {
  47.         return Collections.unmodifiableList(observations);
  48.     }

  49.     /** Get an iterable view of observations bundled by common date.
  50.      * <p>
  51.      * The observations are the same as the ones provided by {@link #getObservationDataSets()},
  52.      * but instead of one single list covering the whole Rinex file, several lists
  53.      * are made available, all observations withing each list sharing a common date
  54.      * </p>
  55.      * @return an iterable view of observations bundled by common date
  56.      * @see #getObservationDataSets()
  57.      * @since 13.0
  58.      */
  59.     public Iterable<List<ObservationDataSet>> bundleByDates() {
  60.         return BundlingIterator::new;
  61.     }

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

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

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

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

  92.         observations.add(observationsDataSet);

  93.     }

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

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

  117.     }

  118.     /** Iterator providing {@link ObservationDataSet} bundled by dates.
  119.      * @since 13.0
  120.      */
  121.     private class BundlingIterator implements Iterator<List<ObservationDataSet>> {

  122.         /** Ratio for dates comparisons tolerance. */
  123.         private static final double RATIO = 0.01;

  124.         /** Tolerance for dates comparisons. */
  125.         private final double tolerance;

  126.         /** Index of next bundle. */
  127.         private int next;

  128.         /** Build an iterator starting at first observations data set.
  129.          */
  130.         BundlingIterator() {
  131.             this.tolerance = RATIO * getHeader().getInterval();
  132.             this.next = 0;
  133.         }

  134.         /** {@inheritDoc} */
  135.         @Override
  136.         public boolean hasNext() {
  137.             return next < observations.size();
  138.         }

  139.         /** {@inheritDoc} */
  140.         @Override
  141.         public List<ObservationDataSet> next() {

  142.             // common date for all observation data sets in this bundle
  143.             final AbsoluteDate bundleDate = observations.get(next).getDate();

  144.             final int start = next;
  145.             while (next < observations.size() &&
  146.                    FastMath.abs(observations.get(next).getDate().durationFrom(bundleDate)) <= tolerance) {
  147.                 // we can include next observation in the current bundle
  148.                 ++next;
  149.             }

  150.             // return the bundle of observations that share the same date
  151.             return observations.subList(start, next);

  152.         }

  153.     }

  154. }