1#![doc = include_str!("./README.md")]
2extern crate log;
3extern crate nyx_space as nyx;
4extern crate pretty_env_logger as pel;
5
6use anise::{
7 almanac::metaload::MetaFile,
8 constants::{
9 celestial_objects::{MOON, SUN},
10 frames::{EARTH_J2000, IAU_EARTH_FRAME},
11 },
12};
13use hifitime::{Epoch, Unit};
14use log::warn;
15use nyx::{
16 cosmic::{Mass, MetaAlmanac, Orbit, SRPData},
17 dynamics::{Harmonics, OrbitalDynamics, SolarPressure, SpacecraftDynamics},
18 io::{gravity::HarmonicsMem, ExportCfg},
19 od::GroundStation,
20 propagators::Propagator,
21 Spacecraft, State,
22};
23use polars::{
24 frame::column::ScalarColumn,
25 prelude::{df, AnyValue, ChunkCompareIneq, Column, DataType, Scalar},
26};
27
28use std::{error::Error, sync::Arc};
29
30fn main() -> Result<(), Box<dyn Error>> {
31 pel::init();
32 let almanac = Arc::new(MetaAlmanac::latest().map_err(Box::new)?);
42 let epoch = Epoch::from_gregorian_utc_hms(2024, 2, 29, 12, 13, 14);
44
45 let earth_j2000 = almanac.frame_from_uid(EARTH_J2000)?;
51
52 let orbit =
53 Orbit::try_keplerian_altitude(300.0, 0.015, 68.5, 65.2, 75.0, 0.0, epoch, earth_j2000)?;
54 println!("{orbit:x}");
56
57 let future_orbit_tb = orbit.at_epoch(epoch + Unit::Day * 3)?;
62 println!("{future_orbit_tb:x}");
63
64 println!(
66 "SMA changed by {:.3e} km",
67 orbit.sma_km()? - future_orbit_tb.sma_km()?
68 );
69 println!(
70 "ECC changed by {:.3e}",
71 orbit.ecc()? - future_orbit_tb.ecc()?
72 );
73 println!(
74 "INC changed by {:.3e} deg",
75 orbit.inc_deg()? - future_orbit_tb.inc_deg()?
76 );
77 println!(
78 "RAAN changed by {:.3e} deg",
79 orbit.raan_deg()? - future_orbit_tb.raan_deg()?
80 );
81 println!(
82 "AOP changed by {:.3e} deg",
83 orbit.aop_deg()? - future_orbit_tb.aop_deg()?
84 );
85 println!(
86 "TA changed by {:.3} deg",
87 orbit.ta_deg()? - future_orbit_tb.ta_deg()?
88 );
89
90 let sc = Spacecraft::builder()
96 .orbit(orbit)
97 .mass(Mass::from_dry_mass(9.60))
98 .srp(SRPData {
99 area_m2: 10e-4,
100 coeff_reflectivity: 1.1,
101 })
102 .build();
103 println!("{sc:x}");
104
105 let mut orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN]);
110
111 let mut jgm3_meta = MetaFile {
114 uri: "http://public-data.nyxspace.com/nyx/models/JGM3.cof.gz".to_string(),
115 crc32: Some(0xF446F027), };
117 jgm3_meta.process(true)?;
119
120 let harmonics_21x21 = Harmonics::from_stor(
124 almanac.frame_from_uid(IAU_EARTH_FRAME)?,
125 HarmonicsMem::from_cof(&jgm3_meta.uri, 21, 21, true).unwrap(),
126 );
127
128 orbital_dyn.accel_models.push(harmonics_21x21);
130
131 let srp_dyn = SolarPressure::default(EARTH_J2000, almanac.clone())?;
134
135 let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
138
139 println!("{dynamics}");
140
141 let (future_sc, trajectory) = Propagator::default(dynamics)
145 .with(sc, almanac.clone())
146 .until_epoch_with_traj(future_orbit_tb.epoch)?;
147
148 println!("=== High fidelity propagation ===");
149 println!(
150 "SMA changed by {:.3} km",
151 orbit.sma_km()? - future_sc.orbit.sma_km()?
152 );
153 println!(
154 "ECC changed by {:.6}",
155 orbit.ecc()? - future_sc.orbit.ecc()?
156 );
157 println!(
158 "INC changed by {:.3e} deg",
159 orbit.inc_deg()? - future_sc.orbit.inc_deg()?
160 );
161 println!(
162 "RAAN changed by {:.3} deg",
163 orbit.raan_deg()? - future_sc.orbit.raan_deg()?
164 );
165 println!(
166 "AOP changed by {:.3} deg",
167 orbit.aop_deg()? - future_sc.orbit.aop_deg()?
168 );
169 println!(
170 "TA changed by {:.3} deg",
171 orbit.ta_deg()? - future_sc.orbit.ta_deg()?
172 );
173
174 println!("{trajectory}");
176
177 trajectory.to_oem_file(
182 "./01_cubesat_hf_prop.oem",
183 ExportCfg::builder().step(Unit::Minute * 2).build(),
184 )?;
185
186 trajectory.to_parquet_with_cfg(
187 "./01_cubesat_hf_prop.parquet",
188 ExportCfg::builder().step(Unit::Minute * 2).build(),
189 almanac.clone(),
190 )?;
191
192 let boulder_station = GroundStation::from_point(
198 "Boulder, CO, USA".to_string(),
199 40.014984, -105.270546, 1.6550, almanac.frame_from_uid(IAU_EARTH_FRAME)?,
203 );
204
205 let mut offset_s = vec![];
207 let mut epoch_str = vec![];
208 let mut ric_x_km = vec![];
209 let mut ric_y_km = vec![];
210 let mut ric_z_km = vec![];
211 let mut ric_vx_km_s = vec![];
212 let mut ric_vy_km_s = vec![];
213 let mut ric_vz_km_s = vec![];
214
215 let mut azimuth_deg = vec![];
216 let mut elevation_deg = vec![];
217 let mut range_km = vec![];
218 let mut range_rate_km_s = vec![];
219 for state in trajectory.every(Unit::Minute * 2) {
220 let this_epoch = state.epoch();
226 match orbit.at_epoch(this_epoch) {
227 Ok(tb_then) => {
228 offset_s.push((this_epoch - orbit.epoch).to_seconds());
229 epoch_str.push(format!("{this_epoch}"));
230 let ric = state.orbit.ric_difference(&tb_then)?;
232 ric_x_km.push(ric.radius_km.x);
233 ric_y_km.push(ric.radius_km.y);
234 ric_z_km.push(ric.radius_km.z);
235 ric_vx_km_s.push(ric.velocity_km_s.x);
236 ric_vy_km_s.push(ric.velocity_km_s.y);
237 ric_vz_km_s.push(ric.velocity_km_s.z);
238
239 let aer = almanac.azimuth_elevation_range_sez(
241 state.orbit,
242 boulder_station.to_orbit(this_epoch, &almanac)?,
243 None,
244 None,
245 )?;
246 azimuth_deg.push(aer.azimuth_deg);
247 elevation_deg.push(aer.elevation_deg);
248 range_km.push(aer.range_km);
249 range_rate_km_s.push(aer.range_rate_km_s);
250 }
251 Err(e) => warn!("{} {e}", state.epoch()),
252 };
253 }
254
255 let ric_df = df!(
257 "Offset (s)" => offset_s.clone(),
258 "Epoch" => epoch_str.clone(),
259 "RIC X (km)" => ric_x_km,
260 "RIC Y (km)" => ric_y_km,
261 "RIC Z (km)" => ric_z_km,
262 "RIC VX (km/s)" => ric_vx_km_s,
263 "RIC VY (km/s)" => ric_vy_km_s,
264 "RIC VZ (km/s)" => ric_vz_km_s,
265 )?;
266
267 println!("RIC difference at start\n{}", ric_df.head(Some(10)));
268 println!("RIC difference at end\n{}", ric_df.tail(Some(10)));
269
270 let aer_df = df!(
271 "Offset (s)" => offset_s.clone(),
272 "Epoch" => epoch_str.clone(),
273 "azimuth (deg)" => azimuth_deg,
274 "elevation (deg)" => elevation_deg,
275 "range (km)" => range_km,
276 "range rate (km/s)" => range_rate_km_s,
277 )?;
278
279 let mask = aer_df
281 .column("elevation (deg)")?
282 .gt(&Column::Scalar(ScalarColumn::new(
283 "elevation mask (deg)".into(),
284 Scalar::new(DataType::Float64, AnyValue::Float64(15.0)),
285 offset_s.len(),
286 )))?;
287 let cubesat_visible = aer_df.filter(&mask)?;
288
289 println!("{cubesat_visible}");
290
291 Ok(())
292}