1use std::f64::consts::FRAC_PI_2;
10
11use crate::frames::{DynFrame, Iau, Icrf, TryRotateTo};
12use crate::propagators::Propagator;
13use crate::states::{DynState, State};
14use crate::trajectories::{DynTrajectory, Trajectory, TrajectoryError};
15use glam::{DMat3, DVec3};
16use lox_bodies::{DynOrigin, RotationalElements, Spheroid, TrySpheroid};
17use lox_math::types::units::Radians;
18use lox_time::time_scales::TryToScale;
19use lox_time::time_scales::{Tdb, TimeScale};
20use lox_time::ut1::DeltaUt1TaiProvider;
21use lox_time::{DynTime, Time};
22use thiserror::Error;
23
24#[derive(Clone, Debug)]
25pub struct Observables {
26 azimuth: Radians,
27 elevation: Radians,
28 range: f64,
29 range_rate: f64,
30}
31
32impl Observables {
33 pub fn new(azimuth: Radians, elevation: Radians, range: f64, range_rate: f64) -> Self {
34 Observables {
35 azimuth,
36 elevation,
37 range,
38 range_rate,
39 }
40 }
41 pub fn azimuth(&self) -> Radians {
42 self.azimuth
43 }
44
45 pub fn elevation(&self) -> Radians {
46 self.elevation
47 }
48
49 pub fn range(&self) -> f64 {
50 self.range
51 }
52
53 pub fn range_rate(&self) -> f64 {
54 self.range_rate
55 }
56}
57
58#[derive(Clone, Debug)]
59pub struct GroundLocation<B: TrySpheroid> {
60 longitude: f64,
61 latitude: f64,
62 altitude: f64,
63 body: B,
64}
65
66pub type DynGroundLocation = GroundLocation<DynOrigin>;
67
68impl<B: Spheroid> GroundLocation<B> {
69 pub fn new(longitude: f64, latitude: f64, altitude: f64, body: B) -> Self {
70 GroundLocation {
71 longitude,
72 latitude,
73 altitude,
74 body,
75 }
76 }
77}
78
79impl DynGroundLocation {
80 pub fn with_dynamic(
81 longitude: f64,
82 latitude: f64,
83 altitude: f64,
84 body: DynOrigin,
85 ) -> Result<Self, &'static str> {
86 if body.try_equatorial_radius().is_err() {
87 return Err("no spheroid");
88 }
89 Ok(GroundLocation {
90 longitude,
91 latitude,
92 altitude,
93 body,
94 })
95 }
96}
97
98impl<B: TrySpheroid> GroundLocation<B> {
99 pub fn origin(&self) -> B
100 where
101 B: Clone,
102 {
103 self.body.clone()
104 }
105
106 pub fn longitude(&self) -> f64 {
107 self.longitude
108 }
109
110 pub fn latitude(&self) -> f64 {
111 self.latitude
112 }
113
114 pub fn altitude(&self) -> f64 {
115 self.altitude
116 }
117
118 fn equatorial_radius(&self) -> f64 {
119 self.body
120 .try_equatorial_radius()
121 .expect("equatorial radius should be available")
122 }
123
124 fn flattening(&self) -> f64 {
125 self.body
126 .try_flattening()
127 .expect("flattening should be available")
128 }
129
130 pub fn body_fixed_position(&self) -> DVec3 {
131 let alt = self.altitude;
132 let (lon_sin, lon_cos) = self.longitude.sin_cos();
133 let (lat_sin, lat_cos) = self.latitude.sin_cos();
134 let f = self.flattening();
135 let r_eq = self.equatorial_radius();
136 let e = (2.0 * f - f.powi(2)).sqrt();
137 let c = r_eq / (1.0 - e.powi(2) * lat_sin.powi(2)).sqrt();
138 let s = c * (1.0 - e.powi(2));
139 let r_delta = (c + alt) * lat_cos;
140 let r_kappa = (s + alt) * lat_sin;
141 DVec3::new(r_delta * lon_cos, r_delta * lon_sin, r_kappa)
142 }
143
144 pub fn rotation_to_topocentric(&self) -> DMat3 {
145 let rot1 = DMat3::from_rotation_z(self.longitude()).transpose();
146 let rot2 = DMat3::from_rotation_y(FRAC_PI_2 - self.latitude()).transpose();
147 rot2 * rot1
148 }
149
150 pub fn observables<T: TimeScale + Clone>(&self, state: State<T, B, Iau<B>>) -> Observables
151 where
152 B: RotationalElements + Clone,
153 {
154 let rot = self.rotation_to_topocentric();
155 let position = rot * (state.position() - self.body_fixed_position());
156 let velocity = rot * state.velocity();
157 let range = position.length();
158 let range_rate = position.dot(velocity) / range;
159 let elevation = (position.z / range).asin();
160 let azimuth = position.y.atan2(-position.x);
161 Observables {
162 azimuth,
163 elevation,
164 range,
165 range_rate,
166 }
167 }
168
169 pub fn observables_dyn(&self, state: DynState) -> Observables {
170 let rot = self.rotation_to_topocentric();
171 let position = rot * (state.position() - self.body_fixed_position());
172 let velocity = rot * state.velocity();
173 let range = position.length();
174 let range_rate = position.dot(velocity) / range;
175 let elevation = (position.z / range).asin();
176 let azimuth = position.y.atan2(-position.x);
177 Observables {
178 azimuth,
179 elevation,
180 range,
181 range_rate,
182 }
183 }
184}
185
186#[derive(Debug, Error)]
187pub enum GroundPropagatorError {
188 #[error("frame transformation error: {0}")]
189 FrameTransformation(String),
190 #[error(transparent)]
191 Trajectory(#[from] TrajectoryError),
192}
193
194pub struct GroundPropagator<B: TrySpheroid, P> {
195 location: GroundLocation<B>,
196 provider: Option<P>,
198}
199
200pub type DynGroundPropagator<P> = GroundPropagator<DynOrigin, P>;
201
202impl<B, P> GroundPropagator<B, P>
203where
204 B: Spheroid,
205{
206 pub fn new(location: GroundLocation<B>, provider: Option<P>) -> Self {
207 GroundPropagator { location, provider }
208 }
209}
210
211impl<P: DeltaUt1TaiProvider> DynGroundPropagator<P> {
212 pub fn with_dynamic(location: DynGroundLocation, provider: Option<P>) -> Self {
213 GroundPropagator { location, provider }
214 }
215
216 pub fn propagate_dyn(&self, time: DynTime) -> Result<DynState, GroundPropagatorError> {
217 let s = State::new(
218 time,
219 self.location.body_fixed_position(),
220 DVec3::ZERO,
221 self.location.body,
222 DynFrame::Iau(self.location.body),
223 );
224 let rot = s
225 .reference_frame()
226 .try_rotation(DynFrame::Icrf, time, self.provider.as_ref())
227 .map_err(|err| GroundPropagatorError::FrameTransformation(err.to_string()))?;
228 let (r1, v1) = rot.rotate_state(s.position(), s.velocity());
229 Ok(State::new(time, r1, v1, self.location.body, DynFrame::Icrf))
230 }
231
232 pub(crate) fn propagate_all_dyn(
233 &self,
234 times: impl IntoIterator<Item = DynTime>,
235 ) -> Result<DynTrajectory, GroundPropagatorError> {
236 let mut states = vec![];
237 for time in times {
238 let state = self.propagate_dyn(time)?;
239 states.push(state);
240 }
241 Ok(Trajectory::new(&states)?)
242 }
243}
244
245impl<T, O, P> Propagator<T, O, Icrf> for GroundPropagator<O, P>
246where
247 T: TimeScale + TryToScale<Tdb, P> + Clone,
248 O: Spheroid + RotationalElements + Clone,
249{
250 type Error = GroundPropagatorError;
251
252 fn propagate(&self, time: Time<T>) -> Result<State<T, O, Icrf>, Self::Error> {
253 let s = State::new(
254 time.clone(),
255 self.location.body_fixed_position(),
256 DVec3::ZERO,
257 self.location.body.clone(),
258 Iau(self.location.body.clone()),
259 );
260 let rot = s
261 .reference_frame()
262 .try_rotation(Icrf, time.clone(), self.provider.as_ref())
263 .map_err(|err| GroundPropagatorError::FrameTransformation(err.to_string()))?;
264 let (r1, v1) = rot.rotate_state(s.position(), s.velocity());
265 Ok(State::new(time, r1, v1, self.location.body.clone(), Icrf))
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use float_eq::assert_float_eq;
272
273 use lox_bodies::Earth;
274 use lox_math::assert_close;
275 use lox_math::is_close::IsClose;
276 use lox_time::utc::Utc;
277 use lox_time::{Time, time, utc};
278
279 use super::*;
280
281 #[test]
282 fn test_ground_location_to_body_fixed() {
283 let longitude = -4.3676f64.to_radians();
284 let latitude = 40.4527f64.to_radians();
285 let location = GroundLocation::new(longitude, latitude, 0.0, Earth);
286 let expected = DVec3::new(4846.130017870638, -370.1328551351891, 4116.364272747229);
287 assert_close!(location.body_fixed_position(), expected);
288 }
289
290 #[test]
291 fn test_ground_location_rotation_to_topocentric() {
292 let longitude = -4.3676f64.to_radians();
293 let latitude = 40.4527f64.to_radians();
294 let location = GroundLocation::new(longitude, latitude, 0.0, Earth);
295 let rot = location.rotation_to_topocentric();
296 let expected = DMat3::from_cols(
297 DVec3::new(0.6469358921661584, 0.07615519584215287, 0.7587320591443464),
298 DVec3::new(
299 -0.049411020334552434,
300 0.9970959763965771,
301 -0.05794967578213965,
302 ),
303 DVec3::new(-0.7609418522440956, 0.0, 0.6488200809957448),
304 );
305 assert_close!(rot.x_axis, expected.x_axis);
306 assert_close!(rot.y_axis, expected.y_axis);
307 assert_close!(rot.z_axis, expected.z_axis);
308 }
309
310 #[test]
311 fn test_ground_location_observables() {
312 let longitude = -4f64.to_radians();
313 let latitude = 41f64.to_radians();
314 let location = GroundLocation::new(longitude, latitude, 0.0, Earth);
315 let position = DVec3::new(3359.927, -2398.072, 5153.0);
316 let velocity = DVec3::new(5.0657, 5.485, -0.744);
317 let time = time!(Tdb, 2012, 7, 1).unwrap();
318 let state = State::new(time, position, velocity, Earth, Iau(Earth));
319 let observables = location.observables(state);
320 let expected_range = 2707.7;
321 let expected_range_rate = -7.16;
322 let expected_azimuth = -53.418f64.to_radians();
323 let expected_elevation = -7.077f64.to_radians();
324 assert_float_eq!(observables.range, expected_range, rel <= 1e-2);
325 assert_float_eq!(observables.range_rate, expected_range_rate, rel <= 1e-2);
326 assert_float_eq!(observables.azimuth, expected_azimuth, rel <= 1e-2);
327 assert_float_eq!(observables.elevation, expected_elevation, rel <= 1e-2);
328 }
329
330 #[test]
331 fn test_ground_propagator() {
332 let longitude = -4.3676f64.to_radians();
333 let latitude = 40.4527f64.to_radians();
334 let location = GroundLocation::new(longitude, latitude, 0.0, Earth);
335 let propagator = GroundPropagator::new(location, None::<()>);
336 let time = utc!(2022, 1, 31, 23).unwrap().to_time();
337 let expected = DVec3::new(-1765.9535510583582, 4524.585984442561, 4120.189198495323);
338 let state = propagator.propagate(time).unwrap();
339 assert_close!(state.position(), expected);
340 }
341}