gistools/space/propagation/
sgp4.rs1use crate::space::{
2 EARTH_RADIUS_KM, Method, Satellite,
3 propagation::{DpperOptions, DspaceOptions, dpper, dspace},
4 util::constants::{J2, J3_J2, VKMPERSEC, X2_3, XKE},
5};
6use alloc::{format, string::String};
7use core::f64::consts::{PI, TAU};
8use libm::{atan2, cos, fabs, pow, sin, sqrt};
9use s2json::VectorPoint;
10
11#[derive(Debug, Default, Clone, PartialEq)]
13pub struct SGP4ErrorOutput {
14 pub r#type: u64,
16 pub error: String,
18}
19
20#[derive(Debug, Default, Clone, PartialEq)]
22pub struct SGP4Output {
23 pub position: VectorPoint,
25 pub velocity: VectorPoint,
27}
28
29pub fn sgp4(sat: &Satellite, tsince: f64) -> Result<SGP4Output, SGP4ErrorOutput> {
52 let Satellite {
53 anomaly,
54 motion,
55 eccentricity,
56 inclination,
57 method,
58 drag,
59 mdot,
60 perigee,
61 argpdot,
62 ascension,
63 nodedot,
64 nodecf,
65 cc1,
66 cc4,
67 cc5,
68 t2cof,
69 isimp,
70 omgcof,
71 eta,
72 xmcof,
73 delmo,
74 d2,
75 d3,
76 d4,
77 sinmao,
78 t3cof,
79 t4cof,
80 t5cof,
81 irez,
82 d2201,
83 d2211,
84 d3210,
85 d3222,
86 d4410,
87 d4422,
88 d5220,
89 d5232,
90 d5421,
91 d5433,
92 dedt,
93 del1,
94 del2,
95 del3,
96 didt,
97 dmdt,
98 dnodt,
99 domdt,
100 gsto,
102 xfact,
103 xlamo,
104 atime,
105 xli,
106 xni,
107 mut aycof,
108 mut xlcof,
109 mut con41,
110 mut x1mth2,
111 mut x7thm1,
112 ..
113 } = *sat;
114
115 let mut coseo1 = 0.;
116 let mut sineo1 = 0.;
117 let mut cosip: f64;
118 let mut sinip: f64;
119 let cosisq: f64;
120 let delm: f64;
121 let delomg: f64;
122 let mut eo1: f64;
123 let mut argpm: f64;
124 let mut argpp: f64;
125 let mut su: f64;
126 let t3: f64;
127 let t4: f64;
128 let tc: f64;
129 let mut tem5: f64;
130 let mut temp: f64;
131 let mut tempa: f64;
132 let mut tempe: f64;
133 let mut templ: f64;
134 let mut inclm: f64;
135 let mut mm: f64;
136 let mut nm: f64;
137 let mut nodem: f64;
138 let mut xincp: f64;
139 let mut xlm: f64;
140 let mut mp: f64;
141 let mut nodep: f64;
142
143 let temp4 = 1.5e-12;
149
150 let xmdf = anomaly + mdot * tsince;
152 let argpdf = perigee + argpdot * tsince;
153 let nodedf = ascension + nodedot * tsince;
154 argpm = argpdf;
155 mm = xmdf;
156 let t2 = tsince * tsince;
157 nodem = nodedf + nodecf * t2;
158 tempa = 1.0 - cc1 * tsince;
159 tempe = drag * cc4 * tsince;
160 templ = t2cof * t2;
161
162 if isimp != 1. {
163 delomg = omgcof * tsince;
164 let delmtemp = 1.0 + eta * cos(xmdf);
166 delm = xmcof * (delmtemp * delmtemp * delmtemp - delmo);
167 temp = delomg + delm;
168 mm = xmdf + temp;
169 argpm = argpdf - temp;
170 t3 = t2 * tsince;
171 t4 = t3 * tsince;
172 tempa = tempa - d2 * t2 - d3 * t3 - d4 * t4;
173 tempe += drag * cc5 * (sin(mm) - sinmao);
174 templ = templ + t3cof * t3 + t4 * (t4cof + tsince * t5cof);
175 }
176 nm = motion;
177 let mut em = eccentricity;
178 inclm = inclination;
179 if method == Method::D {
180 tc = tsince;
181
182 let dspace_options = DspaceOptions {
183 irez,
184 d2201,
185 d2211,
186 d3210,
187 d3222,
188 d4410,
189 d4422,
190 d5220,
191 d5232,
192 d5421,
193 d5433,
194 dedt,
195 del1,
196 del2,
197 del3,
198 didt,
199 dmdt,
200 dnodt,
201 domdt,
202 argpo: perigee,
203 argpdot,
204 tc,
205 gsto,
206 xfact,
207 xlamo,
208 no: motion,
209 atime,
210 em,
211 argpm,
212 inclm,
213 xli,
214 mm,
215 xni,
216 nodem,
217 nm,
218 };
219
220 let dspace_result = dspace(dspace_options, tsince);
221 em = dspace_result.em;
222 argpm = dspace_result.argpm;
223 inclm = dspace_result.inclm;
224 mm = dspace_result.mm;
225 nodem = dspace_result.nodem;
226 nm = dspace_result.nm;
227 }
228
229 if nm <= 0.0 {
230 return Err(SGP4ErrorOutput { r#type: 2, error: format!("error nm {}", nm) });
232 }
233
234 let am = pow(XKE / nm, X2_3) * tempa * tempa;
236 nm = XKE / pow(am, 1.5);
238 em -= tempe;
239
240 if !(-0.001..1.0).contains(&em) {
243 return Err(SGP4ErrorOutput { r#type: 1, error: format!("error em {}", em) });
246 }
247
248 if em < 1.0e-6 {
250 em = 1.0e-6;
251 }
252 mm += motion * templ;
253 xlm = mm + argpm + nodem;
254
255 nodem %= TAU;
256 argpm %= TAU;
257 xlm %= TAU;
258 mm = (xlm - argpm - nodem) % TAU;
259
260 let sinim = sin(inclm);
262 let cosim = cos(inclm);
263
264 let mut ep = em;
266 xincp = inclm;
267 argpp = argpm;
268 nodep = nodem;
269 mp = mm;
270 sinip = sinim;
271 cosip = cosim;
272 if method == Method::D {
273 let dpper_parameters = DpperOptions { init: false, ep, inclp: xincp, nodep, argpp, mp };
274 let dpper_result = dpper(sat, dpper_parameters, tsince);
275 ep = dpper_result.ep;
276 nodep = dpper_result.nodep;
277 argpp = dpper_result.argpp;
278 mp = dpper_result.mp;
279
280 xincp = dpper_result.inclp;
281
282 if xincp < 0.0 {
283 xincp = -xincp;
284 nodep += PI;
285 argpp -= PI;
286 }
287 if !(0.0..=1.0).contains(&ep) {
288 return Err(SGP4ErrorOutput { r#type: 3, error: format!("error ep {}", ep) });
290 }
291 }
292
293 if method == Method::D {
295 sinip = sin(xincp);
296 cosip = cos(xincp);
297 aycof = -0.5 * J3_J2 * sinip;
298
299 if fabs(cosip + 1.0) > 1.5e-12 {
301 xlcof = (-0.25 * J3_J2 * sinip * (3.0 + 5.0 * cosip)) / (1.0 + cosip);
302 } else {
303 xlcof = (-0.25 * J3_J2 * sinip * (3.0 + 5.0 * cosip)) / temp4;
304 }
305 }
306
307 let axnl = ep * cos(argpp);
308 temp = 1.0 / (am * (1.0 - ep * ep));
309 let aynl = ep * sin(argpp) + temp * aycof;
310 let xl = mp + argpp + nodep + temp * xlcof * axnl;
311
312 let u = (xl - nodep) % TAU;
314 eo1 = u;
315 tem5 = 9999.9;
316 let mut ktr = 1;
317
318 while fabs(tem5) >= 1.0e-12 && ktr <= 10 {
321 sineo1 = sin(eo1);
322 coseo1 = cos(eo1);
323 tem5 = 1.0 - coseo1 * axnl - sineo1 * aynl;
324 tem5 = (u - aynl * coseo1 + axnl * sineo1 - eo1) / tem5;
325 if fabs(tem5) >= 0.95 {
326 if tem5 > 0.0 {
327 tem5 = 0.95;
328 } else {
329 tem5 = -0.95;
330 }
331 }
332 eo1 += tem5;
333 ktr += 1;
334 }
335
336 let ecose = axnl * coseo1 + aynl * sineo1;
338 let esine = axnl * sineo1 - aynl * coseo1;
339 let el2 = axnl * axnl + aynl * aynl;
340 let pl = am * (1.0 - el2);
341 if pl < 0.0 {
342 return Err(SGP4ErrorOutput { r#type: 4, error: format!("error pl {}", pl) });
344 }
345
346 let rl = am * (1.0 - ecose);
347 let rdotl = (sqrt(am) * esine) / rl;
348 let rvdotl = sqrt(pl) / rl;
349 let betal = sqrt(1.0 - el2);
350 temp = esine / (1.0 + betal);
351 let sinu = (am / rl) * (sineo1 - aynl - axnl * temp);
352 let cosu = (am / rl) * (coseo1 - axnl + aynl * temp);
353 su = atan2(sinu, cosu);
354 let sin2u = (cosu + cosu) * sinu;
355 let cos2u = 1.0 - 2.0 * sinu * sinu;
356 temp = 1.0 / pl;
357 let temp1 = 0.5 * J2 * temp;
358 let temp2 = temp1 * temp;
359
360 if method == Method::D {
362 cosisq = cosip * cosip;
363 con41 = 3.0 * cosisq - 1.0;
364 x1mth2 = 1.0 - cosisq;
365 x7thm1 = 7.0 * cosisq - 1.0;
366 }
367
368 let mrt = rl * (1.0 - 1.5 * temp2 * betal * con41) + 0.5 * temp1 * x1mth2 * cos2u;
369
370 if mrt < 1.0 {
372 return Err(SGP4ErrorOutput { r#type: 6, error: format!("decay condition {}", mrt) });
373 }
374
375 su -= 0.25 * temp2 * x7thm1 * sin2u;
376 let xnode = nodep + 1.5 * temp2 * cosip * sin2u;
377 let xinc = xincp + 1.5 * temp2 * cosip * sinip * cos2u;
378 let mvt = rdotl - (nm * temp1 * x1mth2 * sin2u) / XKE;
379 let rvdot = rvdotl + (nm * temp1 * (x1mth2 * cos2u + 1.5 * con41)) / XKE;
380
381 let sinsu = sin(su);
383 let cossu = cos(su);
384 let snod = sin(xnode);
385 let cnod = cos(xnode);
386 let sini = sin(xinc);
387 let cosi = cos(xinc);
388 let xmx = -snod * cosi;
389 let xmy = cnod * cosi;
390 let ux = xmx * sinsu + cnod * cossu;
391 let uy = xmy * sinsu + snod * cossu;
392 let uz = sini * sinsu;
393 let vx = xmx * cossu - cnod * sinsu;
394 let vy = xmy * cossu - snod * sinsu;
395 let vz = sini * cossu;
396
397 Ok(SGP4Output {
399 position: VectorPoint::new_xyz(
400 mrt * ux * EARTH_RADIUS_KM,
401 mrt * uy * EARTH_RADIUS_KM,
402 mrt * uz * EARTH_RADIUS_KM,
403 None,
404 ),
405 velocity: VectorPoint::new_xyz(
406 (mvt * ux + rvdot * vx) * VKMPERSEC,
407 (mvt * uy + rvdot * vy) * VKMPERSEC,
408 (mvt * uz + rvdot * vz) * VKMPERSEC,
409 None,
410 ),
411 })
412}