Observer.java
/* Copyright 2002-2026 Brianna Aubin
* Licensed to Hawkeye 360 (HE360) 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.HashMap;
import java.util.List;
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.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.frames.FieldTransform;
import org.orekit.frames.Frame;
import org.orekit.frames.Transform;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.time.clocks.ClockOffset;
import org.orekit.time.clocks.FieldClockOffset;
import org.orekit.time.clocks.QuadraticClockModel;
import org.orekit.time.clocks.QuadraticFieldClockModel;
import org.orekit.utils.FieldPVCoordinatesProvider;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.TimeSpanMap.Span;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;
/** Abstract interface that contains those methods necessary
* for both space and ground-based satellite observers.
*
* @author Brianna Aubin
* @since 14.0
*/
public interface Observer {
enum ObserverType {
/** Indicates a ground-based observation station. */
GROUNDSTATION,
/** Indicates a space-based observer. */
SATELLITE;
}
/** Get the type of object being used in measurement observations.
* @return string value
* @since 14.0
*/
ObserverType getObserverType();
/** Get the Observer name.
* @return name for the object
* @since 12.1
*/
String getName();
/** Get the clock offset driver.
* @return clock offset driver
*/
ParameterDriver getClockOffsetDriver();
/** Get a quadratic clock model valid at some date.
* @return quadratic clock model
* @since 12.1
*/
QuadraticClockModel getQuadraticClockModel();
/** Get emitting satellite clock provider.
* @param freeParameters total number of free parameters in the gradient
* @param date time of computations
* @param indices indices of the differentiation parameters in derivatives computations,
* must be span name and not driver name
* @return emitting satellite clock provider
*/
QuadraticFieldClockModel<Gradient> getQuadraticFieldClock(int freeParameters,
AbsoluteDate date,
Map<String, Integer> indices);
/** Return the PVCoordinatesProvider.
* @return pos/vel coordinates provider
* @since 14.0
*/
PVCoordinatesProvider getPVCoordinatesProvider();
/** Return the FieldPVCoordinatesProvider.
* @param freeParameters number of estimated parameters
* @param parameterIndices indices of the estimated parameters in derivatives computations, must be driver
* @return pos/vel coordinates provider for values with Gradient field
* @since 14.0
*/
FieldPVCoordinatesProvider<Gradient> getFieldPVCoordinatesProvider(int freeParameters,
Map<String, Integer> parameterIndices);
/** Return the time-stamped PV coordinates.
* @param date date of output coordinates
* @param frame desired frame for output coordinates
* @return observer position vector
* @since 14.0
*/
default Vector3D getPosition(AbsoluteDate date, Frame frame) {
return getPVCoordinates(date, frame).getPosition();
}
/** Return the time-stamped PV coordinates.
* @param date date of output coordinates
* @param frame desired frame for output coordinates
* @return time-stamped observer pos/vel values
* @since 14.0
*/
default TimeStampedPVCoordinates getPVCoordinates(AbsoluteDate date, Frame frame) {
return getPVCoordinatesProvider().getPVCoordinates(date, frame);
}
/** Get the transform between offset frame and inertial frame.
* <p>
* The offset frame takes the <em>current</em> position offset,
* polar motion and the meridian shift into account. The frame
* returned is disconnected from later changes in the parameters.
* When the {@link ParameterDriver parameters} managing these
* offsets are changed, the method must be called again to retrieve
* a new offset frame.
* </p>
* @param inertial inertial frame to transform to
* @param date date of the transform
* @param clockOffsetAlreadyApplied if true, the specified {@code date} is as read
* by the ground station clock (i.e. clock offset <em>not</em> compensated), if false,
* the specified {@code date} was already compensated and is a physical absolute date
* @return transform between offset frame and inertial frame, at <em>real</em> measurement
* date (i.e. with clock, Earth and station offsets applied)
*/
Transform getOffsetToInertial(Frame inertial, AbsoluteDate date, boolean clockOffsetAlreadyApplied);
/** Get the transform between offset frame and inertial frame with derivatives.
* <p>
* As the East and North vectors are not well defined at pole, the derivatives
* of these two vectors diverge to infinity as we get closer to the pole.
* So this method should not be used for stations less than 0.0001 degree from
* either poles.
* </p>
* @param inertial inertial frame to transform to
* @param clockDate date of the transform, clock offset and its derivatives already compensated
* @param freeParameters total number of free parameters in the gradient
* @param indices indices of the estimated parameters in derivatives computations, must be driver
* span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
* @return transform between offset frame and inertial frame, at specified date
* @since 10.2
*/
default FieldTransform<Gradient> getOffsetToInertial(Frame inertial, AbsoluteDate clockDate,
int freeParameters, Map<String, Integer> indices) {
// take clock offset into account
final Gradient offset = getClockOffsetDriver().getValue(freeParameters, indices, clockDate);
final FieldAbsoluteDate<Gradient> offsetCompensatedDate = new FieldAbsoluteDate<>(clockDate, offset.negate());
return getOffsetToInertial(inertial, offsetCompensatedDate, freeParameters, indices);
}
/** Get the transform between offset frame and inertial frame with derivatives.
* <p>
* As the East and North vectors are not well defined at pole, the derivatives
* of these two vectors diverge to infinity as we get closer to the pole.
* So this method should not be used for stations less than 0.0001 degree from
* either poles.
* </p>
* @param inertial inertial frame to transform to
* @param offsetCompensatedDate date of the transform, clock offset and its derivatives already compensated
* @param freeParameters total number of free parameters in the gradient
* @param indices indices of the estimated parameters in derivatives computations, must be driver
* span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
* @return transform between offset frame and inertial frame, at specified date
* @since 10.2
*/
FieldTransform<Gradient> getOffsetToInertial(Frame inertial, FieldAbsoluteDate<Gradient> offsetCompensatedDate,
int freeParameters, Map<String, Integer> indices);
/** Create a map of the free parameter values.
* @param states list of ObservableSatellite measurement states
* @param parameterDrivers list of all parameter values for the measurement
* @return map of the free parameter values
* @since 14.0
*/
default Map<String, Integer> getParamaterIndices(SpacecraftState[] states,
List<ParameterDriver> parameterDrivers) {
// measurement derivatives are computed with respect to spacecraft state in inertial frame
// Parameters:
// - 6k..6k+2 - Position of spacecraft k (counting k from 0 to nbSat-1) in inertial frame
// - 6k+3..6k+5 - Velocity of spacecraft k (counting k from 0 to nbSat-1) in inertial frame
// - 6nbSat..n - measurements parameters (clock offset, etc)
int nbParams = 6 * states.length;
final Map<String, Integer> paramIndices = new HashMap<>();
for (ParameterDriver measurementDriver : parameterDrivers) {
if (measurementDriver.isSelected()) {
for (Span<String> span = measurementDriver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
paramIndices.put(span.getData(), nbParams++);
}
}
}
return paramIndices;
}
/** Compute common estimation parameters in case where measured object is the
* receiver of the signal value (e.g. GNSS to ObservableSatellite).
* @param states state(s) of all measured spacecraft
* @param localSat satellite whose state is being estimated
* @param measurementDate date when measurement was taken
* @param receiverClockOffsetAlreadyApplied if true, the specified {@code date} is as read
* by the receiver clock (i.e. clock offset <em>not</em> compensated), if false,
* the specified {@code date} was already compensated and is a physical absolute date
* @return common parameters
* @since 14.0
*/
default CommonParametersWithoutDerivatives computeLocalParametersWithout(SpacecraftState[] states,
ObservableSatellite localSat,
AbsoluteDate measurementDate,
boolean receiverClockOffsetAlreadyApplied) {
// Coordinates of the observed spacecraft
final Frame frame = states[0].getFrame();
final TimeStampedPVCoordinates pvaLocal = states[0].getPVCoordinates(frame);
// Clock values of the observed spacecraft and signal receiver
final ClockOffset localClock = localSat.getQuadraticClockModel().getOffset(measurementDate);
final double localClockOffset = localClock.getOffset();
// take clock offset of receiver (in this case, ObservableSatellite) into account
final AbsoluteDate arrivalDate = receiverClockOffsetAlreadyApplied ? measurementDate : measurementDate.shiftedBy(-localClockOffset);
// Coordinates provider of the Observer object providing the signal information
final PVCoordinatesProvider remotePV = getPVCoordinatesProvider();
// Downlink delay / determine time-of-emission of signal information from remote object
final double deltaT = arrivalDate.durationFrom(states[0]);
final TimeStampedPVCoordinates pvaDownlink = pvaLocal.shiftedBy(deltaT);
final SignalTravelTimeAdjustableEmitter signalTimeOfFlight = new SignalTravelTimeAdjustableEmitter(remotePV);
final double tauD = signalTimeOfFlight.compute(arrivalDate, pvaDownlink.getPosition(), arrivalDate, frame);
// Remote object pos/vel at time of signal emission
final AbsoluteDate emissionDate = arrivalDate.shiftedBy(-tauD);
final ClockOffset remoteClock = getQuadraticClockModel().getOffset(emissionDate);
return new CommonParametersWithoutDerivatives(states[0], tauD,
localClock, remoteClock,
states[0].shiftedBy(deltaT),
pvaDownlink,
remotePV.getPVCoordinates(emissionDate, frame));
}
/** Compute common estimation parameters with derivatives when the measured object is the
* receiver of the signal sent by the Observer.
* @param states state(s) of all measured spacecraft
* @param localSat satellite whose state is being estimated
* @param measurementDate date when measurement was taken
* @param receiverClockOffsetAlreadyApplied if true, the specified {@code date} is as read
* @param parameterDrivers list of parameter drivers associated with measurement
* by the receiver clock (i.e. clock offset <em>not</em> compensated), if false,
* the specified {@code date} was already compensated and is a physical absolute date
* @return common parameters
* @since 14.0
*/
default CommonParametersWithDerivatives computeLocalParametersWith(SpacecraftState[] states,
ObservableSatellite localSat,
AbsoluteDate measurementDate,
boolean receiverClockOffsetAlreadyApplied,
List<ParameterDriver> parameterDrivers) {
// Create the parameter indices map
final Frame frame = states[0].getFrame();
final Map<String, Integer> paramIndices = getParamaterIndices(states, parameterDrivers);
final int nbParams = 6 * states.length + paramIndices.size();
// Turn measurement date into FieldAbsoluteDate<Gradient>
final FieldAbsoluteDate<Gradient> gDate = new FieldAbsoluteDate<>(GradientField.getField(nbParams), measurementDate);
// Measured satellite object data
final TimeStampedFieldPVCoordinates<Gradient> pvaLocal = AbstractMeasurement.getCoordinates(states[0], 0, nbParams);
final QuadraticFieldClockModel<Gradient> localClock = localSat.getQuadraticClockModel().
toGradientModel(nbParams, paramIndices, measurementDate);
final FieldClockOffset<Gradient> localClockOffset = localClock.getOffset(gDate);
// take clock offset into account for arrival date
final FieldAbsoluteDate<Gradient> arrivalDate = receiverClockOffsetAlreadyApplied ?
gDate : gDate.shiftedBy(localClockOffset.getOffset().negate());
// Coords provider for observer object that is sending signal
final FieldPVCoordinatesProvider<Gradient> remotePV = getFieldPVCoordinatesProvider(nbParams, paramIndices);
// Downlink delay
final Gradient deltaT = arrivalDate.durationFrom(states[0].getDate());
final TimeStampedFieldPVCoordinates<Gradient> pvaDownlink = pvaLocal.shiftedBy(deltaT);
final FieldSignalTravelTimeAdjustableEmitter<Gradient> fieldComputer =
new FieldSignalTravelTimeAdjustableEmitter<>(remotePV);
final Gradient tauD = fieldComputer.compute(arrivalDate, pvaDownlink.getPosition(), arrivalDate, frame);
// Remote observer at signal emission time
final FieldAbsoluteDate<Gradient> emissionDate = arrivalDate.shiftedBy(tauD.negate());
final QuadraticFieldClockModel<Gradient> remoteClock = getQuadraticFieldClock(nbParams, emissionDate.toAbsoluteDate(), paramIndices);
final FieldClockOffset<Gradient> remoteClockOffset = remoteClock.getOffset(emissionDate);
return new CommonParametersWithDerivatives(states[0], paramIndices, tauD,
localClockOffset, remoteClockOffset,
states[0].shiftedBy(deltaT.getValue()),
pvaDownlink,
remotePV.getPVCoordinates(emissionDate, frame));
}
/** Compute common estimation parameters when remote object is the receiver
* of the signal (e.g. ObservableSatellite sends signal to measuring ground station).
* @param states state(s) of all measured spacecraft
* @param localSat satellite whose state is being estimated
* @param measurementDate date when measurement was taken
* @param receiverClockOffsetAlreadyApplied if true, the specified {@code date} is as read
* by the receiver clock (i.e. clock offset <em>not</em> compensated), if false,
* the specified {@code date} was already compensated and is a physical absolute date
* @return common parameters
* @since 14.0
*/
default CommonParametersWithoutDerivatives computeRemoteParametersWithout(SpacecraftState[] states,
ObservableSatellite localSat,
AbsoluteDate measurementDate,
boolean receiverClockOffsetAlreadyApplied) {
// Coordinates of the measured spacecraft
final Frame frame = states[0].getFrame();
final TimeStampedPVCoordinates pva = states[0].getPVCoordinates();
// transform between remote observer frame and inertial frame
final Transform offsetToInertialDownlink = getOffsetToInertial(frame, measurementDate, receiverClockOffsetAlreadyApplied);
final AbsoluteDate downlinkDate = offsetToInertialDownlink.getDate();
final ClockOffset localClockOffset = localSat.getQuadraticClockModel().getOffset(measurementDate);
// Observer position in inertial frame at end of the downlink leg
final TimeStampedPVCoordinates origin = new TimeStampedPVCoordinates(downlinkDate,
Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO);
final TimeStampedPVCoordinates satelliteDownlink = offsetToInertialDownlink.transformPVCoordinates(origin);
// Coordinates provider for emitting object (observed spacecraft)
final PVCoordinatesProvider pvCoordinatesProvider = MeasurementObject.extractPVCoordinatesProvider(states[0], pva);
// Downlink delay / determine time of emission of signal by ObservableSatellite
final SignalTravelTimeAdjustableEmitter signalTimeOfFlight = new SignalTravelTimeAdjustableEmitter(pvCoordinatesProvider);
final double tauD = signalTimeOfFlight.compute(pva.getDate(), satelliteDownlink.getPosition(), downlinkDate, frame);
// Transit state & Transit state (re)computed with gradients
final double delta = downlinkDate.durationFrom(states[0].getDate());
final double deltaMTauD = delta - tauD;
final SpacecraftState transitState = states[0].shiftedBy(deltaMTauD);
final ClockOffset remoteClockOffset = getQuadraticClockModel().getOffset(measurementDate);
return new CommonParametersWithoutDerivatives(states[0], tauD,
localClockOffset, remoteClockOffset,
transitState, transitState.getPVCoordinates(),
satelliteDownlink);
}
/** Compute common estimation parameters with derivative when remote object
* is the receiver of the signal.
* @param states state(s) of all measured spacecraft
* @param localSat satellite whose state is being estimated
* @param measurementDate date when measurement was taken
* @param receiverClockOffsetAlreadyApplied if true, the specified {@code date} is as read
* @param parameterDrivers list of parameter drivers associated with measurement
* @return common parameters
* @since 14.0
*/
default CommonParametersWithDerivatives computeRemoteParametersWith(SpacecraftState[] states,
ObservableSatellite localSat,
AbsoluteDate measurementDate,
boolean receiverClockOffsetAlreadyApplied,
List<ParameterDriver> parameterDrivers) {
// Create the parameter indices map
final Frame frame = states[0].getFrame();
final Map<String, Integer> paramIndices = getParamaterIndices(states, parameterDrivers);
final int nbParams = 6 * states.length + paramIndices.size();
// Coordinates of the spacecraft expressed as a gradient
final TimeStampedFieldPVCoordinates<Gradient> pva = AbstractMeasurement.getCoordinates(states[0], 0, nbParams);
// transform between Observer object and inertial frame, expressed as a gradient
// The components of the Observer's position in offset frame are the 3 last derivative parameters
final FieldTransform<Gradient> offsetToInertialDownlink =
getOffsetToInertial(frame, measurementDate, nbParams, paramIndices);
final FieldAbsoluteDate<Gradient> downlinkDate = offsetToInertialDownlink.getFieldDate();
// Get local satellite clock offset
final QuadraticFieldClockModel<Gradient> localClock = localSat.getQuadraticClockModel().
toGradientModel(nbParams, paramIndices, measurementDate);
final FieldClockOffset<Gradient> localClockOffset = localClock.getOffset(downlinkDate);
// Observer position in inertial frame at end of the downlink leg
final GradientField field = GradientField.getField(nbParams);
final FieldVector3D<Gradient> zero = FieldVector3D.getZero(field);
final TimeStampedFieldPVCoordinates<Gradient> satelliteDownlink =
offsetToInertialDownlink.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(downlinkDate,
zero, zero, zero));
// Form coordinates provider
final FieldPVCoordinatesProvider<Gradient> fieldPVCoordinatesProvider = MeasurementObject.extractFieldPVCoordinatesProvider(states[0], pva);
// Downlink delay
final FieldSignalTravelTimeAdjustableEmitter<Gradient> fieldComputer = new FieldSignalTravelTimeAdjustableEmitter<>(fieldPVCoordinatesProvider);
final Gradient tauD = fieldComputer.compute(pva.getDate(), satelliteDownlink.getPosition(), downlinkDate, frame);
// Transit state & Transit state (re)computed with gradients
final Gradient delta = downlinkDate.durationFrom(states[0].getDate());
final Gradient deltaMTauD = tauD.negate().add(delta);
final SpacecraftState transitState = states[0].shiftedBy(deltaMTauD.getValue());
final FieldAbsoluteDate<Gradient> fieldDate = new FieldAbsoluteDate<>(field, states[0].getDate()).shiftedBy(deltaMTauD);
final TimeStampedFieldPVCoordinates<Gradient> transitPV = fieldPVCoordinatesProvider.getPVCoordinates(fieldDate, frame);
// Get remote clock offset at time of measurement reception
final QuadraticFieldClockModel<Gradient> remoteClock = getQuadraticFieldClock(nbParams, downlinkDate.toAbsoluteDate(), paramIndices);
final FieldClockOffset<Gradient> remoteClockOffset = remoteClock.getOffset(downlinkDate);
return new CommonParametersWithDerivatives(states[0], paramIndices, tauD,
localClockOffset, remoteClockOffset,
transitState, transitPV, satelliteDownlink);
}
}