Measurement time

Hi all. I have been having a concern about how the time of measurement works in orekit with the measurement generation functionality. I am trying to simulate a radar station, and recently learned about the Measurement Generation aspect of Orekit.

This is very a very convenient way of doing what I need, as all the logic of when the satellite is in the FOV is taken care internally, and most importantly the measurements are realistic in that it takes into account the time of flight of the signal. At least in theory. The documentation states very clearly how the observed value is computed depending if it is a twoway measurement or not.

My question and confusion comes from the time obtained from the getDate method. I am generating three measurements: range, range-rate and azimuth-elevation. Each has its own Builder, and the EventBasedScheduler used in each has its own FixedStepSelector. Then, acording to the Range measurement documentation:

For two-way measurements, the measurement is considered to be a signal emitted from a ground station, reflected on spacecraft, and received on the same ground station. Its value is the elapsed time between emission and reception multiplied by c/2 where c is the speed of light.

The motion of both the station and the spacecraft during the signal flight time are taken into account. The date of the measurement corresponds to the reception on ground of the emitted or reflected signal.

After reading this I was expecting the range (and range-rate) measurements to be tagged for the reception of the signal. But all measurements are tagged at the signal emission instant, as they are all perfectly spaced by the fixed time of the step selector…

Obviously I have been searching the forums to better understand what is happening. This is I think a very relevant post: Variable Observation Time Tag Specification. Here @bcazabonne clearly specifies that:

The user must convert the observation epoch to be at signal reception

But here things get even more confusing to me, as the code example linked there uses this:

// Time of flight
                final double timeOfFlight = range.getTimeOfFlight();

But when I try that method on my Range measurement it does not exist :smiling_face_with_tear:. I am a bit lost in how to interpret the range and range-rate measurements I obtain with the generator, in particular the time tag.

This is the code of my radar (Matlab):

% Create the GENERATOR object for the measurements
myGenerator     = measurements.generation.Generator();

mySat           = myGenerator.addPropagator(myPropagator);

if isstring(radar_struct)
    radar_struct        = create_radar(radar_characterization);
myStation         = create_ground_station(radar_struct);

mySigma_R       = sqrt(radar_struct.errors.rad_covariance(1,1));
mySigma_RR   	= sqrt(radar_struct.errors.rad_covariance(4,4));
mySigma_AzEl   	= sqrt(diag(radar_struct.errors.rad_covariance(2:3,2:3)));
baseWeight      = 1; bWm = [1 1];

if use_noise
    % NOISE SOURCE (Only needed for generating imperfect measurements)
    GaussGenerator    = org.hipparchus.random.GaussianRandomGenerator( org.hipparchus.random.JDKRandomGenerator() );
    covMatrix_r    	= org.hipparchus.linear.Array2DRowRealMatrix(radar_struct.errors.rad_covariance(1,1));
    noiseSource_r 	= org.hipparchus.random.CorrelatedRandomVectorGenerator(covMatrix_r,        10,     GaussGenerator);
    covMatrix_rr  	= org.hipparchus.linear.Array2DRowRealMatrix(radar_struct.errors.rad_covariance(4,4));
    noiseSource_rr 	= org.hipparchus.random.CorrelatedRandomVectorGenerator(covMatrix_rr,       0.1,    GaussGenerator);
    covMatrix_azel 	= org.hipparchus.linear.DiagonalMatrix( diag(radar_struct.errors.rad_covariance(2:3,2:3)) );
    noiseSource_azel  = org.hipparchus.random.CorrelatedRandomVectorGenerator(covMatrix_azel, 1e-10,  GaussGenerator);

    myRangeBuilder      = measurements.generation.RangeBuilder(noiseSource_r, myStation, true,...
                            mySigma_R, baseWeight, mySat);
    myRangeRtBuilder    = measurements.generation.RangeRateBuilder(noiseSource_rr, myStation, true,...
                            mySigma_RR, baseWeight, mySat);
    myAzElBuilder       = measurements.generation.AngularAzElBuilder(noiseSource_azel, myStation,...
                            mySigma_AzEl, bWm, mySat);
    myRangeBuilder      = measurements.generation.RangeBuilder(null(1), myStation, true,...
                            mySigma_R, baseWeight, mySat);
    myRangeRtBuilder    = measurements.generation.RangeRateBuilder(null(1), myStation, true,...
                            mySigma_RR, baseWeight, mySat);
    myAzElBuilder       = measurements.generation.AngularAzElBuilder(null(1), myStation,...
                            mySigma_AzEl, bWm, mySat);
myPVBuilder             = measurements.generation.PVBuilder(null(1),...
                            0, 0, 1, mySat);


% Selector
updateTime              = radar_struct.measurement_rate;
fixedStepSelector_ran   = FixedStepSelector(updateTime,null(1));
fixedStepSelector_ranrt = FixedStepSelector(updateTime,null(1));
fixedStepSelector_azel  = FixedStepSelector(updateTime,null(1));
fixedStepSelector_pv    = FixedStepSelector(updateTime,null(1));

% EventDetector
handler                 = handlers.ContinueOnEvent(); 
logger                  = EventsLogger();
fov_detector            = GroundFieldOfViewDetector(myStation.getBaseFrame(), radar_struct.fov).withHandler(handler);
fov_detector            = fov_detector.withMaxCheck(10).withMaxIter(200).withThreshold(1e-12);
fov_detector            = logger.monitorDetector(fov_detector);
% signSemantic    = org.orekit.estimation.measurements.generation.SignSemantic.valueOf('FEASIBLE_MEASUREMENT_WHEN_POSITIVE');
signSemantic    = org.orekit.estimation.measurements.generation.SignSemantic.valueOf('FEASIBLE_MEASUREMENT_WHEN_NEGATIVE');

myScheduler_azel    = measurements.generation.EventBasedScheduler(myAzElBuilder,    fixedStepSelector_azel,     myPropagator, fov_detector, signSemantic);
myScheduler_ran     = measurements.generation.EventBasedScheduler(myRangeBuilder,   fixedStepSelector_ran,      myPropagator, fov_detector, signSemantic);
myScheduler_ranrt   = measurements.generation.EventBasedScheduler(myRangeRtBuilder, fixedStepSelector_ranrt,    myPropagator, fov_detector, signSemantic);
myScheduler_pv      = measurements.generation.EventBasedScheduler(myPVBuilder,      fixedStepSelector_pv,       myPropagator, fov_detector, signSemantic);

% Complete Generator configuration

myMeasurements  = myGenerator.generate(initDate,endDate).toArray();

Notice that I aways generate perfect Position-Velocity data, for debuging purposses. But I realiced I was doing something wrong when I compared that to the position estimated from Range-Azimuth-Elevation without noise. I was expecting it to be the same, but there is a difference of about 40 meters. After considering the problem of me not correctly interpreting the getDate information, I think this is normal. I should compare the Range-Azimuth-Elevation with the state of the spacecraft after .getTimeOfFlight() seconds, for example doing a shiftedBy of the true orbit (from the PV builder). The prolem is that I don’t know what is the time of flight, or how to access it.

As a side note, in case it is related: I am using a null alignmentTimeScale in the FixedStepSelector because when originaly I used UTC time scale I got a weird behaviour (from what I expected). There was a discontinuity in the step between measurements every day. More preciselly, every time a day finished, measurements between the previous day and the next were not separated by a multiple of the updateTime. Instead, new measurements where generated every updateTime seconds from the start of that day. Maybe this is the expected behaviour of the alligment functionality, but I guess I did not understand it at first :sweat_smile:.

Sorry for the long post, but this thing about the measurements time tags is driving me crazy.

Thanks in advance!