FDOA.java

  1. /* Copyright 2002-2025 Mark Rutten
  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;

  18. import java.util.Arrays;

  19. import org.hipparchus.analysis.differentiation.Gradient;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.orekit.frames.FieldTransform;
  23. import org.orekit.frames.Transform;
  24. import org.orekit.propagation.SpacecraftState;
  25. import org.orekit.time.AbsoluteDate;
  26. import org.orekit.time.FieldAbsoluteDate;
  27. import org.orekit.utils.Constants;
  28. import org.orekit.utils.ParameterDriver;
  29. import org.orekit.utils.TimeSpanMap.Span;
  30. import org.orekit.utils.TimeStampedFieldPVCoordinates;
  31. import org.orekit.utils.TimeStampedPVCoordinates;

  32. /** Class modeling a Frequency Difference of Arrival measurement with a satellite as emitter
  33.  * and two ground stations as receivers.
  34.  * <p>
  35.  * FDOA measures the difference in signal arrival frequency between the emitter and receivers,
  36.  * corresponding to a difference in range-rate from the two receivers to the emitter.
  37.  * </p><p>
  38.  * The date of the measurement corresponds to the reception of the signal by the prime station.
  39.  * The measurement corresponds to the frequency of the signal received at the prime station at
  40.  * the date of the measurement minus the frequency of the signal received at the second station:
  41.  * <code>fdoa = f<sub>1</sub> - f<sub>2</sub></code>
  42.  * </p><p>
  43.  * The motion of the stations and the satellite during the signal flight time are taken into account.
  44.  * </p>
  45.  * @author Mark Rutten
  46.  * @since 12.0
  47.  */
  48. public class FDOA extends GroundReceiverMeasurement<FDOA> {

  49.     /** Type of the measurement. */
  50.     public static final String MEASUREMENT_TYPE = "FDOA";

  51.     /** Centre frequency of the signal emitted from the satellite. */
  52.     private final double centreFrequency;

  53.     /** Second ground station, the one that gives the measurement, i.e. the delay. */
  54.     private final GroundStation secondStation;

  55.     /** Simple constructor.
  56.      * @param primeStation ground station that gives the date of the measurement
  57.      * @param secondStation ground station that gives the measurement
  58.      * @param centreFrequency satellite emitter frequency (Hz)
  59.      * @param date date of the measurement
  60.      * @param fdoa observed value (Hz)
  61.      * @param sigma theoretical standard deviation
  62.      * @param baseWeight base weight
  63.      * @param satellite satellite related to this measurement
  64.      */
  65.     public FDOA(final GroundStation primeStation, final GroundStation secondStation,
  66.                 final double centreFrequency,
  67.                 final AbsoluteDate date, final double fdoa, final double sigma,
  68.                 final double baseWeight, final ObservableSatellite satellite) {
  69.         super(primeStation, false, date, fdoa, sigma, baseWeight, satellite);

  70.         // add parameter drivers for the secondary station
  71.         addParameterDriver(secondStation.getClockOffsetDriver());
  72.         addParameterDriver(secondStation.getEastOffsetDriver());
  73.         addParameterDriver(secondStation.getNorthOffsetDriver());
  74.         addParameterDriver(secondStation.getZenithOffsetDriver());
  75.         addParameterDriver(secondStation.getPrimeMeridianOffsetDriver());
  76.         addParameterDriver(secondStation.getPrimeMeridianDriftDriver());
  77.         addParameterDriver(secondStation.getPolarOffsetXDriver());
  78.         addParameterDriver(secondStation.getPolarDriftXDriver());
  79.         addParameterDriver(secondStation.getPolarOffsetYDriver());
  80.         addParameterDriver(secondStation.getPolarDriftYDriver());
  81.         this.secondStation = secondStation;
  82.         this.centreFrequency = centreFrequency;
  83.     }

  84.     /** Get the prime ground station, the one that gives the date of the measurement.
  85.      * @return prime ground station
  86.      */
  87.     public GroundStation getPrimeStation() {
  88.         return getStation();
  89.     }

  90.     /** Get the second ground station, the one that gives the measurement.
  91.      * @return second ground station
  92.      */
  93.     public GroundStation getSecondStation() {
  94.         return secondStation;
  95.     }

  96.     /** {@inheritDoc} */
  97.     @Override
  98.     protected EstimatedMeasurementBase<FDOA> theoreticalEvaluationWithoutDerivatives(final int iteration, final int evaluation,
  99.                                                                                      final SpacecraftState[] states) {

  100.         final GroundReceiverCommonParametersWithoutDerivatives common = computeCommonParametersWithout(states[0]);
  101.         final TimeStampedPVCoordinates emitterPV = common.getTransitPV();
  102.         final AbsoluteDate emitterDate = emitterPV.getDate();

  103.         // Approximate second location at transit time
  104.         final Transform secondToInertial =
  105.                 getSecondStation().getOffsetToInertial(common.getState().getFrame(), emitterDate, true);
  106.         final TimeStampedPVCoordinates secondApprox =
  107.                 secondToInertial.transformPVCoordinates(new TimeStampedPVCoordinates(emitterDate,
  108.                         Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO));

  109.         // Time of flight from emitter to second station
  110.         final double tau2 = TDOA.forwardSignalTimeOfFlight(secondApprox, emitterPV.getPosition(), emitterDate);

  111.         // Secondary station PV in inertial frame at receive at second station
  112.         final TimeStampedPVCoordinates secondPV = secondApprox.shiftedBy(tau2);

  113.         // The measured TDOA is (tau1 + clockOffset1) - (tau2 + clockOffset2)
  114.         final double offset1 = getPrimeStation().getClockOffsetDriver().getValue(emitterDate);
  115.         final double offset2 = getSecondStation().getClockOffsetDriver().getValue(emitterDate);
  116.         final double tdoa = (common.getTauD() + offset1) - (tau2 + offset2);

  117.         // Evaluate the FDOA value
  118.         // -------------------------------------------
  119.         final EstimatedMeasurement<FDOA> estimated =
  120.                 new EstimatedMeasurement<>(this, iteration, evaluation,
  121.                         new SpacecraftState[] {
  122.                             common.getTransitState()
  123.                         },
  124.                         new TimeStampedPVCoordinates[] {
  125.                             emitterPV,
  126.                             tdoa > 0.0 ? secondPV : common.getStationDownlink(),
  127.                             tdoa > 0.0 ? common.getStationDownlink() : secondPV
  128.                         });

  129.         // Range-rate components
  130.         final Vector3D primeDirection = common.getStationDownlink().getPosition()
  131.                 .subtract(emitterPV.getPosition()).normalize();
  132.         final Vector3D secondDirection = secondPV.getPosition()
  133.                 .subtract(emitterPV.getPosition()).normalize();

  134.         final Vector3D primeVelocity = common.getStationDownlink().getVelocity()
  135.                 .subtract(emitterPV.getVelocity());
  136.         final Vector3D secondVelocity = secondPV.getVelocity()
  137.                 .subtract(emitterPV.getVelocity());

  138.         // range rate difference
  139.         final double rangeRateDifference = Vector3D.dotProduct(primeDirection, primeVelocity) -
  140.                 Vector3D.dotProduct(secondDirection, secondVelocity);

  141.         // set FDOA value
  142.         final double rangeRateToHz = -centreFrequency / Constants.SPEED_OF_LIGHT;
  143.         estimated.setEstimatedValue(rangeRateDifference * rangeRateToHz);

  144.         return estimated;
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     protected EstimatedMeasurement<FDOA> theoreticalEvaluation(final int iteration, final int evaluation,
  149.                                                                final SpacecraftState[] states) {

  150.         final SpacecraftState state = states[0];

  151.         // FDOA derivatives are computed with respect to spacecraft state in inertial frame
  152.         // and station parameters
  153.         // ----------------------
  154.         //
  155.         // Parameters:
  156.         //  - 0..2 - Position of the spacecraft in inertial frame
  157.         //  - 3..5 - Velocity of the spacecraft in inertial frame
  158.         //  - 6..n - measurements parameters (clock offset, station offsets, pole, prime meridian, sat clock offset...)
  159.         final GroundReceiverCommonParametersWithDerivatives common = computeCommonParametersWithDerivatives(state);
  160.         final int nbParams = common.getTauD().getFreeParameters();
  161.         final TimeStampedFieldPVCoordinates<Gradient> emitterPV = common.getTransitPV();
  162.         final FieldAbsoluteDate<Gradient> emitterDate = emitterPV.getDate();

  163.         // Approximate secondary location (at emission time)
  164.         final FieldVector3D<Gradient> zero = FieldVector3D.getZero(common.getTauD().getField());
  165.         final FieldTransform<Gradient> secondToInertial =
  166.                 getSecondStation().getOffsetToInertial(state.getFrame(), emitterDate, nbParams, common.getIndices());
  167.         final TimeStampedFieldPVCoordinates<Gradient> secondApprox =
  168.                 secondToInertial.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(emitterDate,
  169.                         zero, zero, zero));

  170.         // Time of flight from emitter to second station
  171.         final Gradient tau2 = TDOA.forwardSignalTimeOfFlight(secondApprox, emitterPV.getPosition(), emitterDate);

  172.         // Second station coordinates at receive time
  173.         final TimeStampedFieldPVCoordinates<Gradient> secondPV = secondApprox.shiftedBy(tau2);

  174.         // The measured TDOA is (tau1 + clockOffset1) - (tau2 + clockOffset2)
  175.         final Gradient offset1 = getPrimeStation().getClockOffsetDriver()
  176.                 .getValue(nbParams, common.getIndices(), emitterDate.toAbsoluteDate());
  177.         final Gradient offset2 = getSecondStation().getClockOffsetDriver()
  178.                 .getValue(nbParams, common.getIndices(), emitterDate.toAbsoluteDate());
  179.         final Gradient tdoaG   = common.getTauD().add(offset1).subtract(tau2.add(offset2));
  180.         final double tdoa      = tdoaG.getValue();

  181.         // Evaluate the TDOA value and derivatives
  182.         // -------------------------------------------
  183.         final TimeStampedPVCoordinates pv1 = common.getStationDownlink().toTimeStampedPVCoordinates();
  184.         final TimeStampedPVCoordinates pv2 = secondPV.toTimeStampedPVCoordinates();
  185.         final EstimatedMeasurement<FDOA> estimated =
  186.                 new EstimatedMeasurement<>(this, iteration, evaluation,
  187.                         new SpacecraftState[] {
  188.                             common.getTransitState()
  189.                         },
  190.                         new TimeStampedPVCoordinates[] {
  191.                             emitterPV.toTimeStampedPVCoordinates(),
  192.                             tdoa > 0 ? pv2 : pv1,
  193.                             tdoa > 0 ? pv1 : pv2
  194.                         });

  195.         // Range-rate components
  196.         final FieldVector3D<Gradient> primeDirection = common.getStationDownlink().getPosition()
  197.                 .subtract(emitterPV.getPosition()).normalize();
  198.         final FieldVector3D<Gradient> secondDirection = secondPV.getPosition()
  199.                 .subtract(emitterPV.getPosition()).normalize();

  200.         final FieldVector3D<Gradient> primeVelocity = common.getStationDownlink().getVelocity()
  201.                 .subtract(emitterPV.getVelocity());
  202.         final FieldVector3D<Gradient> secondVelocity = secondPV.getVelocity()
  203.                 .subtract(emitterPV.getVelocity());

  204.         // range rate difference
  205.         final Gradient rangeRateDifference = FieldVector3D.dotProduct(primeDirection, primeVelocity)
  206.                 .subtract(FieldVector3D.dotProduct(secondDirection, secondVelocity));

  207.         // set FDOA value
  208.         final double rangeRateToHz = -centreFrequency / Constants.SPEED_OF_LIGHT;
  209.         final Gradient fdoa = rangeRateDifference.multiply(rangeRateToHz);
  210.         estimated.setEstimatedValue(fdoa.getValue());

  211.         // Range first order derivatives with respect to state
  212.         final double[] derivatives = fdoa.getGradient();
  213.         estimated.setStateDerivatives(0, Arrays.copyOfRange(derivatives, 0, 6));

  214.         // set first order derivatives with respect to parameters
  215.         for (final ParameterDriver driver : getParametersDrivers()) {
  216.             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  217.                 final Integer index = common.getIndices().get(span.getData());
  218.                 if (index != null) {
  219.                     estimated.setParameterDerivatives(driver, span.getStart(), derivatives[index]);
  220.                 }
  221.             }
  222.         }

  223.         return estimated;
  224.     }

  225. }