///|
/// Compute the local ENU observation of an Earth-fixed satellite state.
///
/// Azimuth is measured clockwise from north in degrees. Elevation is measured
/// above the local horizon. Range and range rate use kilometres and kilometres
/// per second, respectively.
pub fn observe_ecef(state : EcefState, station : GroundStation) -> Observation {
let station_position = ground_station_to_ecef(station)
let relative_position = state.position_km.sub(station_position)
let latitude = station.latitude_deg * @math.PI / 180.0
let longitude = station.longitude_deg * @math.PI / 180.0
let sine_latitude = @math.sin(latitude)
let cosine_latitude = @math.cos(latitude)
let sine_longitude = @math.sin(longitude)
let cosine_longitude = @math.cos(longitude)
// Rotate the station-relative ECEF vector into east, north, up axes.
let east = -sine_longitude * relative_position.x +
cosine_longitude * relative_position.y
let north = -sine_latitude * cosine_longitude * relative_position.x -
sine_latitude * sine_longitude * relative_position.y +
cosine_latitude * relative_position.z
let up = cosine_latitude * cosine_longitude * relative_position.x +
cosine_latitude * sine_longitude * relative_position.y +
sine_latitude * relative_position.z
let horizontal_range = (east * east + north * north).sqrt()
let range = (horizontal_range * horizontal_range + up * up).sqrt()
let angle_epsilon = 0.000000000001
let azimuth = if horizontal_range < angle_epsilon {
0.0
} else {
let raw_degrees = @math.atan2(east, north) * 180.0 / @math.PI
normalize_observation_degrees(raw_degrees)
}
let elevation = if range < angle_epsilon {
90.0
} else {
@math.atan2(up, horizontal_range) * 180.0 / @math.PI
}
let range_rate = if range < angle_epsilon {
0.0
} else {
relative_position.dot(state.velocity_km_s) / range
}
{
azimuth_deg: azimuth,
elevation_deg: elevation,
range_km: range,
range_rate_km_s: range_rate,
above_horizon: elevation >= 0.0,
}
}
///|
/// Compute an observation from an inertial state by applying the ECI/ECEF
/// transformation at the state's epoch first.
pub fn observe(state : OrbitState, station : GroundStation) -> Observation {
observe_ecef(eci_to_ecef_state(state), station)
}
///|
fn normalize_observation_degrees(angle : Double) -> Double {
let wrapped = angle % 360.0
if wrapped < 0.0 {
wrapped + 360.0
} else {
wrapped
}
}