RangeRate.java
/* Copyright 2002-2026 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;
import java.util.Map;
import org.hipparchus.Field;
import org.hipparchus.analysis.differentiation.Gradient;
import org.orekit.estimation.measurements.model.OneLeggedRangeRateModel;
import org.orekit.estimation.measurements.model.TwoLeggedRangeRateModel;
import org.orekit.frames.Frame;
import org.orekit.propagation.SpacecraftState;
import org.orekit.signal.FieldAdjustableEmitterSignalTimer;
import org.orekit.signal.FieldSignalReceptionCondition;
import org.orekit.signal.SignalReceptionCondition;
import org.orekit.signal.SignalTravelTimeModel;
import org.orekit.signal.TwoLeggedSignalTimer;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.Constants;
import org.orekit.utils.FieldPVCoordinatesProvider;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;
/** Class modeling one-way or two-way range rate measurement between two vehicles.
* One-way range rate (or Doppler) measurements generally apply to specific satellites
* (e.g. GNSS, DORIS), where a signal is transmitted from a satellite to a sensor.
* Two-way range rate measurements are applicable to any system. The signal is
* transmitted to the (non-spinning) satellite and returned by a transponder
* (or reflected back) to the same measuring sensor.
* The Doppler measurement can be obtained by multiplying the velocity by (fe/c), where
* fe is the emission frequency.
*
* @author Thierry Ceolin
* @author Joris Olympio
* @since 8.0
*/
public class RangeRate extends AbstractRangeRelatedMeasurement<RangeRate> {
/** Type of the measurement. */
public static final String MEASUREMENT_TYPE = "RangeRate";
/** Simple constructor.
* @param observer observer that performs the measurement
* @param date date of the measurement
* @param rangeRate observed value, m/s
* @param sigma theoretical standard deviation
* @param baseWeight base weight
* @param twoWay if true, this is a two-way measurement
* @param satellite satellite related to this measurement
* @since 9.3
*/
public RangeRate(final Observer observer, final AbsoluteDate date,
final double rangeRate, final double sigma, final double baseWeight,
final boolean twoWay, final ObservableSatellite satellite) {
this(observer, date, rangeRate, new MeasurementQuality(sigma, baseWeight), twoWay, new SignalTravelTimeModel(),
satellite);
}
/** Simple constructor.
* @param observer observer that performs the measurement
* @param date date of the measurement
* @param rangeRate observed value, m/s
* @param measurementQuality measurement quality data as used in orbit determination
* @param twoWay if true, this is a two-way measurement
* @param signalTravelTimeModel signal travel model
* @param satellite satellite related to this measurement
* @since 14.0
*/
public RangeRate(final Observer observer, final AbsoluteDate date,
final double rangeRate, final MeasurementQuality measurementQuality,
final boolean twoWay, final SignalTravelTimeModel signalTravelTimeModel,
final ObservableSatellite satellite) {
super(observer, date, rangeRate, measurementQuality, twoWay, signalTravelTimeModel, satellite);
}
/** {@inheritDoc} */
@Override
protected EstimatedMeasurementBase<RangeRate> theoreticalEvaluationWithoutDerivatives(final int iteration,
final int evaluation,
final SpacecraftState[] states) {
// compute reception date
final double clockOffset = getObserver().getOffsetValue(getDate());
final AbsoluteDate receptionDate = getDate().shiftedBy(-clockOffset);
if (isTwoWay()) {
return twoWayTheoreticalEvaluationWithoutDerivatives(iteration, evaluation, receptionDate, states[0]);
} else {
return oneWayTheoreticalEvaluationWithoutDerivatives(iteration, evaluation, receptionDate, states[0]);
}
}
/** Evaluate measurement in two-way without derivatives.
* @param iteration iteration number
* @param evaluation evaluations counter
* @param receptionDate signal final reception date
* @param state state
* @return theoretical value
* @since 14.0
*/
private EstimatedMeasurementBase<RangeRate> twoWayTheoreticalEvaluationWithoutDerivatives(final int iteration,
final int evaluation,
final AbsoluteDate receptionDate,
final SpacecraftState state) {
final EstimatedMeasurementBase<RangeRate> estimated = initializeTwoWayTheoreticalEvaluation(this, iteration, evaluation,
receptionDate, state);
// Compute range rate
final TwoLeggedSignalTimer twoLeggedSignalTimer = new TwoLeggedSignalTimer(getSignalTravelTimeModel().getWarmedUpModel());
final TimeStampedPVCoordinates[] participantsPV = estimated.getParticipants();
final AbsoluteDate transitDate = participantsPV[1].getDate();
final AbsoluteDate emissionDate = participantsPV[0].getDate();
final TwoLeggedRangeRateModel rangeRateModel = new TwoLeggedRangeRateModel(twoLeggedSignalTimer);
final PVCoordinatesProvider satellitePVProvider = AbstractParticipant.extractPVCoordinatesProvider(state, state.getPVCoordinates());
final double rangeRate = rangeRateModel.value(state.getFrame(), participantsPV[2], receptionDate,
satellitePVProvider, transitDate, getObserver().getPVCoordinatesProvider(), emissionDate) / 2.;
estimated.setEstimatedValue(rangeRate);
return estimated;
}
/** Evaluate measurement in one-way without derivatives.
* @param iteration iteration number
* @param evaluation evaluations counter
* @param receptionDate signal reception date
* @param state state
* @return theoretical value
* @since 14.0
*/
private EstimatedMeasurementBase<RangeRate> oneWayTheoreticalEvaluationWithoutDerivatives(final int iteration,
final int evaluation,
final AbsoluteDate receptionDate,
final SpacecraftState state) {
final EstimatedMeasurementBase<RangeRate> estimated = initializeOneWayTheoreticalEvaluation(this, iteration, evaluation,
receptionDate, state);
// physical range rate value
final OneLeggedRangeRateModel rangeRateModel = new OneLeggedRangeRateModel(getSignalTravelTimeModel().getWarmedUpModel());
final AbsoluteDate emissionDate = estimated.getParticipants()[0].getDate();
final PVCoordinates receiverPV = estimated.getParticipants()[1];
final SignalReceptionCondition receptionCondition = new SignalReceptionCondition(receptionDate,
receiverPV.getPosition(), state.getFrame());
double rangeRate = rangeRateModel.value(receptionCondition, receiverPV.getVelocity(),
AbstractParticipant.extractPVCoordinatesProvider(state, state.getPVCoordinates()), emissionDate);
// clock drifts, taken in account only in case of one way
final ObservableSatellite satellite = getSatellites().get(0);
final double dtsDot = satellite.getOffsetRate(emissionDate);
final double dtgDot = getObserver().getOffsetRate(receptionDate);
final double clockDriftBias = (dtgDot - dtsDot) * Constants.SPEED_OF_LIGHT;
rangeRate += clockDriftBias;
estimated.setEstimatedValue(rangeRate);
return estimated;
}
/** {@inheritDoc} */
@Override
protected EstimatedMeasurement<RangeRate> twoWayTheoreticalEvaluation(final int iteration, final int evaluation,
final FieldPVCoordinatesProvider<Gradient> satellitePVProvider,
final SpacecraftState state,
final Map<String, Integer> indices,
final int nbParams) {
// Compute light time delays
final Frame frame = state.getFrame();
final FieldPVCoordinatesProvider<Gradient> observerPVProvider = getObserver().getFieldPVCoordinatesProvider(nbParams, indices);
final FieldAbsoluteDate<Gradient> receptionDate = getCorrectedReceptionDateField(nbParams, indices);
final TimeStampedFieldPVCoordinates<Gradient> receiverPV = observerPVProvider.getPVCoordinates(receptionDate, frame);
final TwoLeggedSignalTimer twoLeggedSignalTimer = new TwoLeggedSignalTimer(getSignalTravelTimeModel());
final FieldSignalReceptionCondition<Gradient> receptionCondition = new FieldSignalReceptionCondition<>(receptionDate,
receiverPV.getPosition(), frame);
final Gradient[] delays = twoLeggedSignalTimer.computeDelays(receptionCondition,
satellitePVProvider, observerPVProvider);
// Prepare the evaluation
final FieldAbsoluteDate<Gradient> transitDate = receptionDate.shiftedBy(delays[1].negate());
final SpacecraftState transitState = state.shiftedBy(transitDate.toAbsoluteDate().durationFrom(state));
final FieldAbsoluteDate<Gradient> emissionDate = transitDate.shiftedBy(delays[0].negate());
final EstimatedMeasurement<RangeRate> estimated = new EstimatedMeasurement<>(this, iteration, evaluation,
new SpacecraftState[] { transitState },
new TimeStampedPVCoordinates[] {
getObserver().getPVCoordinatesProvider().getPVCoordinates(emissionDate.toAbsoluteDate(), frame),
transitState.getPVCoordinates(),
receiverPV.toTimeStampedPVCoordinates()});
// Compute range rate
final TwoLeggedRangeRateModel rangeRateModel = new TwoLeggedRangeRateModel(twoLeggedSignalTimer);
final Gradient rangeRate = rangeRateModel.value(receptionCondition, receiverPV.getVelocity(),
satellitePVProvider, transitDate, observerPVProvider, emissionDate).half();
fillEstimation(rangeRate, indices, estimated);
return estimated;
}
/** {@inheritDoc} */
@Override
protected EstimatedMeasurement<RangeRate> oneWayTheoreticalEvaluation(final int iteration, final int evaluation,
final FieldPVCoordinatesProvider<Gradient> satellitePVProvider,
final SpacecraftState state,
final Map<String, Integer> indices,
final int nbParams) {
// compute reception and emission dates
final FieldAbsoluteDate<Gradient> receptionDate = getCorrectedReceptionDateField(nbParams, indices);
final Frame frame = state.getFrame();
final Field<Gradient> field = receptionDate.getField();
final FieldAdjustableEmitterSignalTimer<Gradient> adjustableEmitter = getSignalTravelTimeModel().getFieldAdjustableEmitterComputer(
field, satellitePVProvider);
final FieldPVCoordinatesProvider<Gradient> observer = getObserver().getFieldPVCoordinatesProvider(nbParams, indices);
final TimeStampedFieldPVCoordinates<Gradient> observerPVAtReception = observer.getPVCoordinates(receptionDate, frame);
final FieldSignalReceptionCondition<Gradient> receptionCondition = new FieldSignalReceptionCondition<>(receptionDate,
observerPVAtReception.getPosition(), frame);
final Gradient delay = adjustableEmitter.computeDelay(receptionCondition);
// prepare the evaluation
final FieldAbsoluteDate<Gradient> emissionDate = receptionDate.shiftedBy(delay.negate());
final SpacecraftState emissionState = state.shiftedBy(emissionDate.toAbsoluteDate().durationFrom(state));
final EstimatedMeasurement<RangeRate> estimated =
new EstimatedMeasurement<>(this, iteration, evaluation,
new SpacecraftState[] { emissionState }, new TimeStampedPVCoordinates[] {
emissionState.getPVCoordinates(), observerPVAtReception.toTimeStampedPVCoordinates() });
// physical range rate value
final SignalTravelTimeModel warmedUpModel = getSignalTravelTimeModel().getWarmedUpModel();
final OneLeggedRangeRateModel rangeRateModel = new OneLeggedRangeRateModel(warmedUpModel);
Gradient rangeRate = rangeRateModel.value(receptionCondition, observerPVAtReception.getVelocity(),
satellitePVProvider, emissionDate);
// clock drifts, taken in account only in case of one way
final ObservableSatellite satellite = getSatellites().get(0);
final Gradient dtsDot = satellite.getFieldOffsetRate(nbParams, emissionState.getDate(), indices);
final Gradient dtgDot = getObserver().getFieldOffsetRate(nbParams, receptionDate.toAbsoluteDate(), indices);
final Gradient clockDriftBias = dtgDot.subtract(dtsDot).multiply(Constants.SPEED_OF_LIGHT);
rangeRate = rangeRate.add(clockDriftBias);
fillEstimation(rangeRate, indices, estimated);
return estimated;
}
}