Commit 68ce2cb9 authored by Bryan Cazabonne's avatar Bryan Cazabonne
Browse files

Merge branch 'release-11.2'

parents b11a5ca1 e1e1bbe7
Pipeline #2133 passed with stages
in 31 minutes and 38 seconds
......@@ -60,7 +60,7 @@ verify:warning:
# Cf. https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html
coverage-import:
stage: visualize
image: haynes/jacoco2cobertura:1.0.4
image: haynes/jacoco2cobertura:1.0.8
script:
# Display total coverage
- "grep -o '<tfoot.*tfoot>' target/site/jacoco/index.html | sed 's:<[^>]*>: :g'"
......@@ -75,7 +75,9 @@ coverage-import:
- verify
artifacts:
reports:
cobertura: target/site/cobertura.xml
coverage_report:
coverage_format: cobertura
path: target/site/cobertura.xml
coverage: '/Total.*?([0-9]{1,3})%/'
changelog:
......
......@@ -2,7 +2,7 @@
<project name="orekit" default="jar" basedir=".">
<property name="project.version" value="11.1.2" />
<property name="project.version" value="11.2" />
<property name="src.dir" location="src" />
<property name="main.src.dir" value="${src.dir}/main/java" />
......
......@@ -5,7 +5,7 @@
<groupId>org.orekit</groupId>
<artifactId>orekit</artifactId>
<packaging>jar</packaging>
<version>11.1.2</version>
<version>11.2</version>
<name>ORbit Extrapolation KIT</name>
<url>http://www.orekit.org/</url>
......@@ -200,6 +200,9 @@
<contributor>
<name>Daniel Aguilar Taboada</name>
</contributor>
<contributor>
<name>Louis Aucouturier</name>
</contributor>
<contributor>
<name>Lucian B&#259;rbulescu</name>
</contributor>
......
......@@ -20,6 +20,95 @@
<title>Orekit Changes</title>
</properties>
<body>
<release version="11.2" date="2022-06-20"
description="Version 11.2 is a minor release of Orekit.
It includes both new features and bug fixes. New features introduced
in 11.2 are: the Hatch filter for GNSS measurements smoothing, the parsing
and writing of CCSDS CDM in both KVN and XML formats, the parsing of SOLFSMY
and DTC data for JB2008 atmospheric model, the parsing of EOP in Sinex
files, new measurements for orbit determination: TDOA, bi-static range and
range rate, support for ITRF 2020 version, the computation of mean orbital
parameters in the sense of Eckstein-Hechler or Brouwer-Lyddane models. It
also includes an update of the CCSDS ODM format to latest draft version and an
improvement of the frame transformation.
See the list below for a full description of the changes.">
<action dev="bryan" type="update">
Added possibility to custom analytical mean parameters conversion.
</action>
<action dev="louis" type="add" issue="666">
Added Hatch filters for smoothing of GNSS measurements.
</action>
<action dev="bryan" type="update" issue="895">
Allowed parsing of SP3 files without EOF key.
</action>
<action dev="gc" type="add" issue="790">
Added writing of velocity record in CPF file writers.
</action>
<action dev="bryan" type="update" issue="804">
Added support for loading EOP from Sinex files.
</action>
<action dev="luc" type="fix" issue="936">
Raised a too stringent convergence threshold in Eackstein-Hechler model.
</action>
<action dev="bryan" type="add" issue="932">
Added a way to compute mean parameters in Brouwer-Lyddane model.
</action>
<action dev="markrutten" type="add" issue="922">
Added bistatic range measurement.
</action>
<action dev="luc" type="add" issue="933">
Added a way to compute mean parameters in Eckstein-Hechler model.
</action>
<action dev="luc" type="update" issue="934">
Updated CCSDS ODM to latest draft version (pink book).
</action>
<action dev="luc" type="fix" issue="930">
Prevents zero max check intervals in maneuvers triggers detectors.
</action>
<action dev="luc" type="add">
Added detection of non-positive max check interval and threshold.
</action>
<action dev="luc" type="add" issue="929">
Allow additional derivatives providers to update main state derivatives.
</action>
<action dev="luc" type="fix" issue="928">
Fixed indexing error when estimating a subset of orbital parameters.
</action>
<action dev="luc" type="update" issue="925">
Don't loose additional derivatives when generating ephemeris.
</action>
<action dev="gc" type="fix" issue="889">
Fixed unexpected behavior of two tests in OrekitMessagesTest.
</action>
<action dev="mvanel" type="add" issue="777">
Added support for parsing and writing CDM files in both KVN and XML formats.
</action>
<action dev="luc" type="add" issue="918">
Added support for ITRF-2020.
</action>
<action dev="pascal" type="add" issue="911">
Added TDOA and bistatic range rate measurements.
</action>
<action dev="bryan" type="add" issue="900">
Added init method in {Field}AdditionalStateProvider.
</action>
<action dev="louis" type="add" issue="888">
Added J2-contribution for relativistic clock correction.
</action>
<action dev="evan" type="update">
Allow creating Geoid without default data context.
</action>
<action dev="louis" type="add" issue="759">
Added data loaders for Space Environment's JB2008 data.
</action>
<action dev="bryan" type="add" issue="898">
Added static method to create a BodyFacade from a CenterName.
</action>
<action dev="evan" type="update" issue="903">
Added Frame.getStaticTransformTo(...) and supporting methods to improve
performance.
</action>
</release>
<release version="11.1.2" date="2022-04-27"
description="Version 11.1.2 is a patch release of Orekit.
It fixes issues related to the parsing and writing of CCSDS and ILRS files.
......
......@@ -31,6 +31,7 @@ import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.FieldTransform;
import org.orekit.frames.Frame;
import org.orekit.frames.StaticTransform;
import org.orekit.frames.Transform;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
......@@ -104,18 +105,24 @@ public class LofOffsetPointing extends GroundPointing {
final AbsoluteDate shifted = date.shiftedBy(i * h);
// transform from specified reference frame to spacecraft frame
final Transform refToSc =
new Transform(shifted,
new Transform(shifted, pvProv.getPVCoordinates(shifted, frame).negate()),
new Transform(shifted, attitudeLaw.getAttitude(pvProv, shifted, frame).getOrientation()));
final StaticTransform refToSc = StaticTransform.compose(
shifted,
StaticTransform.of(
shifted,
pvProv.getPVCoordinates(shifted, frame).getPosition().negate()),
StaticTransform.of(
shifted,
attitudeLaw.getAttitude(pvProv, shifted, frame).getRotation()));
// transform from specified reference frame to body frame
final Transform refToBody = frame.getTransformTo(shape.getBodyFrame(), shifted);
final StaticTransform refToBody;
if (i == 0) {
centralRefToBody = refToBody;
refToBody = centralRefToBody = frame.getTransformTo(shape.getBodyFrame(), shifted);
} else {
refToBody = frame.getStaticTransformTo(shape.getBodyFrame(), shifted);
}
sample.add(losIntersectionWithBody(new Transform(shifted, refToSc.getInverse(), refToBody)));
sample.add(losIntersectionWithBody(StaticTransform.compose(shifted, refToSc.getInverse(), refToBody)));
}
......@@ -170,7 +177,7 @@ public class LofOffsetPointing extends GroundPointing {
* @param scToBody transform from spacecraft frame to body frame
* @return intersection point in body frame (only the position is set!)
*/
private TimeStampedPVCoordinates losIntersectionWithBody(final Transform scToBody) {
private TimeStampedPVCoordinates losIntersectionWithBody(final StaticTransform scToBody) {
// compute satellite pointing axis and position/velocity in body frame
final Vector3D pointingBodyFrame = scToBody.transformVector(satPointingVector);
......
......@@ -27,6 +27,7 @@ import org.orekit.bodies.FieldGeodeticPoint;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.frames.FieldTransform;
import org.orekit.frames.Frame;
import org.orekit.frames.StaticTransform;
import org.orekit.frames.Transform;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
......@@ -74,11 +75,11 @@ public class NadirPointing extends GroundPointing {
// sample intersection points in current date neighborhood
final double h = 0.01;
final List<TimeStampedPVCoordinates> sample = new ArrayList<>();
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(-2 * h), frame), refToBody.shiftedBy(-2 * h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(-h), frame), refToBody.shiftedBy(-h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(-2 * h), frame), refToBody.staticShiftedBy(-2 * h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(-h), frame), refToBody.staticShiftedBy(-h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date, frame), refToBody));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(+h), frame), refToBody.shiftedBy(+h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(+2 * h), frame), refToBody.shiftedBy(+2 * h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(+h), frame), refToBody.staticShiftedBy(+h)));
sample.add(nadirRef(pvProv.getPVCoordinates(date.shiftedBy(+2 * h), frame), refToBody.staticShiftedBy(+2 * h)));
// use interpolation to compute properly the time-derivatives
return TimeStampedPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_P, sample);
......@@ -112,7 +113,8 @@ public class NadirPointing extends GroundPointing {
* @param refToBody transform from reference frame to body frame
* @return intersection point in body frame (only the position is set!)
*/
private TimeStampedPVCoordinates nadirRef(final TimeStampedPVCoordinates scRef, final Transform refToBody) {
private TimeStampedPVCoordinates nadirRef(final TimeStampedPVCoordinates scRef,
final StaticTransform refToBody) {
final Vector3D satInBodyFrame = refToBody.transformPosition(scRef.getPosition());
......
......@@ -32,6 +32,7 @@ import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.frames.FieldTransform;
import org.orekit.frames.Frame;
import org.orekit.frames.StaticTransform;
import org.orekit.frames.Transform;
import org.orekit.frames.TransformProvider;
import org.orekit.time.AbsoluteDate;
......@@ -219,6 +220,29 @@ class JPLCelestialBody implements CelestialBody {
}
@Override
public StaticTransform getStaticTransform(final AbsoluteDate date) {
// compute translation from parent frame to self
final PVCoordinates pv = getPVCoordinates(date, definingFrame);
// compute rotation from ICRF frame to self,
// as per the "Report of the IAU/IAG Working Group on Cartographic
// Coordinates and Rotational Elements of the Planets and Satellites"
// These definitions are common for all recent versions of this report
// published every three years, the precise values of pole direction
// and W angle coefficients may vary from publication year as models are
// adjusted. These coefficients are not in this class, they are in the
// specialized classes that do implement the getPole and getPrimeMeridianAngle
// methods
final Vector3D pole = iauPole.getPole(date);
final Vector3D qNode = iauPole.getNode(date);
final Rotation rotation =
new Rotation(pole, qNode, Vector3D.PLUS_K, Vector3D.PLUS_I);
// update transform from parent to self
return StaticTransform.of(date, pv.getPosition().negate(), rotation);
}
/** {@inheritDoc} */
public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
......
......@@ -31,6 +31,7 @@ import org.hipparchus.util.MathArrays;
import org.hipparchus.util.SinCos;
import org.orekit.frames.FieldTransform;
import org.orekit.frames.Frame;
import org.orekit.frames.StaticTransform;
import org.orekit.frames.Transform;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
......@@ -169,7 +170,8 @@ public class OneAxisEllipsoid extends Ellipsoid implements BodyShape {
final Frame frame, final AbsoluteDate date) {
// transform line and close to body frame
final Transform frameToBodyFrame = frame.getTransformTo(bodyFrame, date);
final StaticTransform frameToBodyFrame =
frame.getStaticTransformTo(bodyFrame, date);
final Line lineInBodyFrame = frameToBodyFrame.transformLine(line);
// compute some miscellaneous variables
......@@ -344,7 +346,7 @@ public class OneAxisEllipsoid extends Ellipsoid implements BodyShape {
public Vector3D projectToGround(final Vector3D point, final AbsoluteDate date, final Frame frame) {
// transform point to body frame
final Transform toBody = frame.getTransformTo(bodyFrame, date);
final StaticTransform toBody = frame.getStaticTransformTo(bodyFrame, date);
final Vector3D p = toBody.transformPosition(point);
final double z = p.getZ();
final double r = FastMath.hypot(p.getX(), p.getY());
......@@ -423,7 +425,8 @@ public class OneAxisEllipsoid extends Ellipsoid implements BodyShape {
public GeodeticPoint transform(final Vector3D point, final Frame frame, final AbsoluteDate date) {
// transform point to body frame
final Vector3D pointInBodyFrame = frame.getTransformTo(bodyFrame, date).transformPosition(point);
final Vector3D pointInBodyFrame = frame.getStaticTransformTo(bodyFrame, date)
.transformPosition(point);
final double r2 = pointInBodyFrame.getX() * pointInBodyFrame.getX() +
pointInBodyFrame.getY() * pointInBodyFrame.getY();
final double r = FastMath.sqrt(r2);
......
......@@ -333,7 +333,8 @@ public enum OrekitMessages implements Localizable {
FIND_ROOT("{0} failed to find root between {1} (g={2,number,0.0##############E0}) and {3} (g={4,number,0.0##############E0})\nLast iteration at {5} (g={6,number,0.0##############E0})"),
BACKWARD_PROPAGATION_NOT_ALLOWED("backward propagation not allowed here"),
NO_STATION_ECCENTRICITY_FOR_EPOCH("no station eccentricity values for the given epoch {0}, validity interval is between {1} and {2}"),
INCONSISTENT_SELECTION("inconsistent parameters selection between pairs {0}/{1} and {2}/{3}");
INCONSISTENT_SELECTION("inconsistent parameters selection between pairs {0}/{1} and {2}/{3}"),
NOT_STRICTLY_POSITIVE("value is not strictly positive: {0}");
// CHECKSTYLE: resume JavadocVariable check
......
......@@ -82,6 +82,11 @@ public abstract class AbstractBatchLSModel implements MultivariateJacobianFuncti
/** End columns for each estimated orbit. */
private final int[] orbitsEndColumns;
/** Indirection array in measurements jacobians.
* @since 11.2
*/
private final int[] orbitsJacobianColumns;
/** Map for propagation parameters columns. */
private final Map<String, Integer> propagationParameterColumns;
......@@ -167,13 +172,18 @@ public abstract class AbstractBatchLSModel implements MultivariateJacobianFuncti
rows += measurement.getDimension();
}
this.orbitsStartColumns = new int[builders.length];
this.orbitsEndColumns = new int[builders.length];
this.orbitsStartColumns = new int[builders.length];
this.orbitsEndColumns = new int[builders.length];
this.orbitsJacobianColumns = new int[builders.length * 6];
Arrays.fill(orbitsJacobianColumns, -1);
int columns = 0;
for (int i = 0; i < builders.length; ++i) {
this.orbitsStartColumns[i] = columns;
for (final ParameterDriver driver : builders[i].getOrbitalParametersDrivers().getDrivers()) {
if (driver.isSelected()) {
final List<ParameterDriversList.DelegatingDriver> orbitalParametersDrivers =
builders[i].getOrbitalParametersDrivers().getDrivers();
for (int j = 0; j < orbitalParametersDrivers.size(); ++j) {
if (orbitalParametersDrivers.get(j).isSelected()) {
orbitsJacobianColumns[columns] = j;
++columns;
}
}
......@@ -193,6 +203,7 @@ public abstract class AbstractBatchLSModel implements MultivariateJacobianFuncti
}
}
}
// Populate the map of propagation drivers' columns and update the total number of columns
propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
for (final String driverName : estimatedPropagationParametersNames) {
......@@ -457,11 +468,12 @@ public abstract class AbstractBatchLSModel implements MultivariateJacobianFuncti
final RealMatrix dYdY0 = harvesters[p].getStateTransitionMatrix(evaluationStates[k]);
final RealMatrix dMdY0 = dMdY.multiply(dYdY0);
for (int i = 0; i < dMdY0.getRowDimension(); ++i) {
int jOrb = orbitsStartColumns[p];
for (int j = 0; j < dMdY0.getColumnDimension(); ++j) {
final ParameterDriver driver = selectedOrbitalDrivers.getDrivers().get(j);
jacobian.setEntry(index + i, jOrb++,
weight[i] * dMdY0.getEntry(i, j) / sigma[i] * driver.getScale());
for (int j = orbitsStartColumns[p]; j < orbitsEndColumns[p]; ++j) {
final ParameterDriver driver =
selectedOrbitalDrivers.getDrivers().get(j - orbitsStartColumns[p]);
final double partial = dMdY0.getEntry(i, orbitsJacobianColumns[j]);
jacobian.setEntry(index + i, j,
weight[i] * partial / sigma[i] * driver.getScale());
}
}
}
......
/* Copyright 2002-2022 Mark Rutten
* 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.
* Mark Rutten 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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.hipparchus.analysis.differentiation.Gradient;
import org.hipparchus.analysis.differentiation.GradientField;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.orekit.frames.FieldTransform;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.Constants;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;
/**
* Class modeling a bistatic range measurement using
* an emitter ground station and a receiver ground station.
* <p>
* The measurement is considered to be a signal:
* <ul>
* <li>Emitted from the emitter ground station</li>
* <li>Reflected on the spacecraft</li>
* <li>Received on the receiver ground station</li>
* </ul>
* The date of the measurement corresponds to the reception on ground of the reflected signal.
* <p>
* The motion of the stations and the spacecraft during the signal flight time are taken into account.
* </p>
*
* @author Mark Rutten
* @since 11.2
*/
public class BistaticRange extends AbstractMeasurement<BistaticRange> {
/**
* Ground station from which transmission is made.
*/
private final GroundStation emitter;
/**
* Ground station from which measurement is performed.
*/
private final GroundStation receiver;
/**
* Simple constructor.
*
* @param emitter ground station from which transmission is performed
* @param receiver ground station from which measurement is performed
* @param date date of the measurement
* @param range observed value
* @param sigma theoretical standard deviation
* @param baseWeight base weight
* @param satellite satellite related to this measurement
* @since 11.2
*/
public BistaticRange(final GroundStation emitter, final GroundStation receiver, final AbsoluteDate date,
final double range, final double sigma, final double baseWeight,
final ObservableSatellite satellite) {
super(date, range, sigma, baseWeight, Collections.singletonList(satellite));
addParameterDriver(emitter.getClockOffsetDriver());
addParameterDriver(emitter.getEastOffsetDriver());
addParameterDriver(emitter.getNorthOffsetDriver());
addParameterDriver(emitter.getZenithOffsetDriver());
addParameterDriver(emitter.getPrimeMeridianOffsetDriver());
addParameterDriver(emitter.getPrimeMeridianDriftDriver());
addParameterDriver(emitter.getPolarOffsetXDriver());
addParameterDriver(emitter.getPolarDriftXDriver());
addParameterDriver(emitter.getPolarOffsetYDriver());
addParameterDriver(emitter.getPolarDriftYDriver());
addParameterDriver(receiver.getClockOffsetDriver());
addParameterDriver(receiver.getEastOffsetDriver());
addParameterDriver(receiver.getNorthOffsetDriver());
addParameterDriver(receiver.getZenithOffsetDriver());
addParameterDriver(receiver.getPrimeMeridianOffsetDriver());
addParameterDriver(receiver.getPrimeMeridianDriftDriver());
addParameterDriver(receiver.getPolarOffsetXDriver());
addParameterDriver(receiver.getPolarDriftXDriver());
addParameterDriver(receiver.getPolarOffsetYDriver());
addParameterDriver(receiver.getPolarDriftYDriver());
this.emitter = emitter;
this.receiver = receiver;
}
public GroundStation getEmitterStation() {
return emitter;
}
public GroundStation getReceiverStation() {
return receiver;
}
/**
* {@inheritDoc}
*/
@Override
protected EstimatedMeasurement<BistaticRange> theoreticalEvaluation(final int iteration,
final int evaluation,
final SpacecraftState[] states) {
final SpacecraftState state = states[0];
// Range derivatives are computed with respect to spacecraft state in inertial frame
// and station parameters
// ----------------------
//
// Parameters:
// - 0..2 - Position of the spacecraft in inertial frame
// - 3..5 - Velocity of the spacecraft in inertial frame
// - 6..n - measurements parameters (clock offset, station offsets, pole, prime meridian, sat clock offset...)
int nbParams = 6;
final Map<String, Integer> indices = new HashMap<>();
for (ParameterDriver driver : getParametersDrivers()) {
if (driver.isSelected()) {
indices.put(driver.getName(), nbParams++);
}
}
final FieldVector3D<Gradient> zero = FieldVector3D.getZero(GradientField.getField(nbParams));
// Coordinates of the spacecraft expressed as a gradient
final TimeStampedFieldPVCoordinates<Gradient> pvaDS = getCoordinates(state, 0, nbParams);
// transform between station and inertial frame, expressed as a gradient
// The components of station's position in offset frame are the 3 last derivative parameters
final FieldTransform<Gradient> offsetToInertialRx =
receiver.getOffsetToInertial(state.getFrame(), getDate(), nbParams, indices);
final FieldAbsoluteDate<Gradient> downlinkDateDS = offsetToInertialRx.getFieldDate();
// Station position in inertial frame at end of the downlink leg
final TimeStampedFieldPVCoordinates<Gradient> stationReceiver =
offsetToInertialRx.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(downlinkDateDS,
zero, zero, zero));
// Compute propagation times
// (if state has already been set up to pre-compensate propagation delay,
// we will have delta == tauD and transitState will be the same as state)
// Downlink delay
final Gradient tauD = signalTimeOfFlight(pvaDS, stationReceiver.getPosition(), downlinkDateDS);
// Transit state & Transit state (re)computed with gradients
final Gradient delta = downlinkDateDS.durationFrom(state.getDate());
final Gradient deltaMTauD = tauD.negate().add(delta);
final SpacecraftState transitState = state.shiftedBy(deltaMTauD.getValue());
final TimeStampedFieldPVCoordinates<Gradient> transitStateDS = pvaDS.shiftedBy(deltaMTauD);
// transform between secondary station topocentric frame (east-north-zenith) and inertial frame expressed as gradients
// The components of secondary station's position in offset frame are the 3 last derivative parameters
final FieldAbsoluteDate<Gradient> transitDate = downlinkDateDS.shiftedBy(tauD.negate());
final FieldTransform<Gradient> offsetToInertialTxApprox =
emitter.getOffsetToInertial(state.getFrame(), transitDate, nbParams, indices);
// Secondary station PV in inertial frame at transit time
final TimeStampedFieldPVCoordinates<Gradient> transmitApprox =
offsetToInertialTxApprox.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(transitDate,
zero, zero, zero));
// Uplink time of flight from secondary station to transit state of leg2
final Gradient tauU = signalTimeOfFlight(transmitApprox, transitStateDS.getPosition(), transitStateDS.getDate());
// Total time of flight
final Gradient tauTotal = deltaMTauD.negate().add(tauU);
// Absolute date of transmission
final FieldAbsoluteDate<Gradient> transmitDateDS = downlinkDateDS.shiftedBy(tauTotal);
final FieldTransform<Gradient> transmitToInert =
emitter.getOffsetToInertial(state.getFrame(), transmitDateDS, nbParams, indices);
// Secondary station PV in inertial frame at rebound date on secondary station
final TimeStampedFieldPVCoordinates<Gradient> stationTransmitter =
transmitToInert.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(transmitDateDS,
zero, zero, zero));
// Prepare the evaluation
final EstimatedMeasurement<BistaticRange> estimated = new EstimatedMeasurement<>(this,
iteration, evaluation,
new SpacecraftState[] {
transitState
},
new TimeStampedPVCoordinates[] {
stationReceiver.toTimeStampedPVCoordinates(),
transitStateDS.toTimeStampedPVCoordinates(),
stationTransmitter.toTimeStampedPVCoordinates()
});