spv_rs/
lib.rs

1//! # Welcome to `spv-rs`!
2//!
3//! [![crates](https://img.shields.io/crates/v/spv-rs)](https://crates.io/crates/spv-rs)
4//! [![Rust](https://github.com/AlbinSjoegren/SPV/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/AlbinSjoegren/SPV/actions/workflows/rust.yml)
5//! ![License](https://img.shields.io/github/license/AlbinSjoegren/SPV)
6//! [![Discord](https://img.shields.io/discord/831904736219365417)](https://discord.gg/x2vwWx9SsS)
7//! [![DOI](https://zenodo.org/badge/416674887.svg)](https://zenodo.org/badge/latestdoi/416674887)
8//!
9//! [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S77U98I)
10//!
11//! This crate is a set of functions for either extracting or manipulating astronomcial data.
12//! However it is (at least for now) mainly focused on position and velocity data.
13//!
14//! ### Examples
15//! First if you want to see a calculator like application usecase please check the sourcecode for the `SPV` gui utility at [SPV github repo](https://github.com/AlbinSjoegren/SPV/tree/main/SPV).
16//!
17//! Now we will look at the position function as an example:
18//! For the [`position::position`] function we use three input variables. `Parallax` for the distance to the object, you can read more about parallax [here](https://en.wikipedia.org/wiki/Stellar_parallax).
19//! `Right ascension` and `Declination` is basically a dots position on a sphere where the distance we get from the `Parllax` is the radius of the sphere.
20//!
21//! One easy way to use this function if you had the required variables would be like this:
22//!
23//! ```rust
24//! use spv_rs::position::position;
25//! use glam::f64::DVec3;
26//!
27//! fn main() {
28//!     let parallax = 1.5_f64;
29//!     let right_ascension = 35.8_f64;
30//!     let declination = 67.3_f64;
31//!
32//!     let body_name = "x";
33//!
34//!     let position = position(parallax, right_ascension, declination).to_array();
35//!
36//!     println!("The body {} was at the position x: {}, y: {}, z: {} at epoch J2000",
37//!     body_name, position[0], position[1], position[2]);
38//! }
39//! ```
40//! The same general principles apply to most functions.
41//!
42//! ### Extra
43//!
44//! Feel free to propose additions/changes, file issues and or help with the project over on [GitHub](https://github.com/AlbinSjoegren/SPV)!
45
46/// Set of functions to calculate the position of either primary or companion bodies for diffrent usecases.
47/// All outputs are in the cartesian coordinate system.
48pub mod position {
49    use super::common::semi_parameter;
50    use super::common::true_anomaly;
51    use super::coordinate_transforms::euler_angle_transformations;
52    use glam::f32::Vec3;
53    use glam::f64::{DVec2, DVec3};
54
55    /// Position of a single celestial object relative to the sun.
56    /// Can be used in conjuction with companion functions to place a twobody system relative to the sun.
57    /// parallax is in mas (milliarcseconds), right_ascension is in degrees and declination in degrees.
58    /// Output is a 3-dimensional vector with x, y and z in that order all in meters.
59    pub fn position(parallax: f64, right_ascension: f64, declination: f64) -> DVec3 {
60        let distance = 1. / (parallax / 1000.);
61
62        let distnace_si = distance * (3.0856778570831 * 10_f64.powf(16.));
63
64        let right_ascension_rad = right_ascension.to_radians();
65        let declination_rad = (declination + 90.).to_radians();
66
67        let x = distnace_si * right_ascension_rad.cos() * declination_rad.sin();
68
69        let y = distnace_si * right_ascension_rad.sin() * declination_rad.sin();
70
71        let z = distnace_si * declination_rad.cos();
72
73        DVec3::new(x, y, z)
74    }
75
76    /// Same as [position::position] but with a f32 vector returned if you need that.
77    pub fn position_f32(parallax: f32, right_ascension: f32, declination: f32) -> Vec3 {
78        let distance = 1. / (parallax / 1000.);
79
80        let distnace_si = distance * (3.0856778570831 * 10_f32.powf(16.));
81
82        let right_ascension_rad = right_ascension.to_radians();
83        let declination_rad = (declination + 90.).to_radians();
84
85        let x = distnace_si * right_ascension_rad.cos() * declination_rad.sin();
86
87        let y = distnace_si * right_ascension_rad.sin() * declination_rad.sin();
88
89        let z = distnace_si * declination_rad.cos();
90
91        Vec3::new(x, y, z)
92    }
93
94    /// Position on the surface of a sphere with radius in meters.
95    pub fn position_surface(radius: f64, right_ascension: f64, declination: f64) -> DVec3 {
96        let right_ascension_rad = right_ascension.to_radians();
97        let declination_rad = (declination + 90.).to_radians();
98
99        let x = radius * right_ascension_rad.cos() * declination_rad.sin();
100
101        let y = radius * right_ascension_rad.sin() * declination_rad.sin();
102
103        let z = radius * declination_rad.cos();
104
105        DVec3::new(x, y, z)
106    }
107
108    /// Position of the companion star in a twobody system with no rotation applied.
109    /// a is semi major-axis in au, e is eccentricity, period is in years and t_p is time since periastron in years.
110    /// Output is a 2-dimensional vector with x and y in that order all in meters. We only need a 2-dimensional vector here
111    /// due to the fact that everything is on a plane in 2D.
112    pub fn companion_position(a: f64, e: f64, period: f64, t_p: f64) -> DVec2 {
113        //Prep Values
114        let p = semi_parameter(a, e);
115        let v = true_anomaly(e, period, t_p);
116
117        //Position of Companion in ellipse base
118        let x = (p * v.cos()) / (1. + e * v.cos());
119        let y = (p * v.sin()) / (1. + e * v.cos());
120
121        DVec2::new(x, y)
122    }
123
124    /// Position of the companion star in a twobody system with rotation relative to the earth/sun plane applied.
125    /// a is semi major-axis in au, e is eccentricity, period is in years, t_p is time since periastron in years,
126    /// lotn is Longitude of the node (Omega) in degrees, aop is Argument of periastron (omega) in degrees and finally i is the Inclination in degrees.
127    /// Output is a 3-dimensional vector with x, y and z in that order all in meters.
128    pub fn companion_relative_position(
129        a: f64,
130        e: f64,
131        period: f64,
132        t_p: f64,
133        lotn: f64,
134        aop: f64,
135        i: f64,
136    ) -> DVec3 {
137        //Prep Values
138        let p = semi_parameter(a, e);
139        let v = true_anomaly(e, period, t_p);
140
141        //Position of Companion in ellipse base
142        let x = (p * v.cos()) / (1. + e * v.cos());
143        let y = (p * v.sin()) / (1. + e * v.cos());
144
145        //Ellipse base
146        let euler_angle_transformations = euler_angle_transformations(lotn, aop, i).to_cols_array();
147        let x1 = euler_angle_transformations[0];
148        let x2 = euler_angle_transformations[1];
149        let x3 = euler_angle_transformations[2];
150
151        let y1 = euler_angle_transformations[3];
152        let y2 = euler_angle_transformations[4];
153        let y3 = euler_angle_transformations[5];
154
155        //Position in original base
156        let companion_position_x = (x1 * x) + (y1 * y);
157        let companion_position_y = (x2 * x) + (y2 * y);
158        let companion_position_z = (x3 * x) + (y3 * y);
159
160        DVec3::new(
161            companion_position_x,
162            companion_position_y,
163            companion_position_z,
164        )
165    }
166}
167
168/// Set of functions to calculate the velocity of either primary or companion bodies for diffrent usecases.
169/// All outputs are in the cartesian coordinate system.
170pub mod velocity {
171    use super::common::radius;
172    use super::common::semi_parameter;
173    use super::common::specific_mechanical_energy;
174    use super::common::standard_gravitational_parameter;
175    use super::common::true_anomaly;
176    use super::coordinate_transforms::euler_angle_transformations;
177    use super::position::position;
178    use glam::f64::{DVec2, DVec3};
179
180    /// Velocity of a single celestial object relative to the sun.
181    /// Can be used in conjuction with companion functions to place a twobody system relative to the sun.
182    /// parallax is in mas (milliarcseconds), right_ascension is in degrees and declination in degrees,
183    /// proper_motion_ra is the right ascension part of the proper motion variable in as (arcseconds),
184    /// proper_motion_dec is the declination part of the proper motion variable in as (arcseconds) and
185    /// radial_velocity is in km/s.
186    /// Output is a 3-dimensional vector with x, y and z in that order all in meters/second.
187    pub fn velocity(
188        parallax: f64,
189        right_ascension: f64,
190        declination: f64,
191        proper_motion_ra: f64,
192        proper_motion_dec: f64,
193        radial_velocity: f64,
194    ) -> DVec3 {
195        let distance = 1. / (parallax / 1000.);
196
197        //SI
198        let distnace_si = distance * (3.0856778570831 * 10_f64.powf(16.));
199        let radial_velocity_si = radial_velocity * 1000.;
200
201        let proper_motion_x = distnace_si
202            * (((right_ascension + ((proper_motion_ra * 0.00027777777777778) / 31556926.))
203                .to_radians())
204            .cos())
205            * ((((declination + ((proper_motion_dec * 0.00027777777777778) / 31556926.)) + 90.)
206                .to_radians())
207            .sin());
208
209        let proper_motion_y = distnace_si
210            * (((right_ascension + ((proper_motion_ra * 0.00027777777777778) / 31556926.))
211                .to_radians())
212            .sin())
213            * ((((declination + ((proper_motion_dec * 0.00027777777777778) / 31556926.)) + 90.)
214                .to_radians())
215            .sin());
216
217        let proper_motion_z = distnace_si
218            * ((((declination + ((proper_motion_dec * 0.00027777777777778) / 31556926.)) + 90.)
219                .to_radians())
220            .cos());
221
222        let position = position(parallax, right_ascension, declination).to_array();
223
224        let x = position[0];
225        let y = position[1];
226        let z = position[2];
227
228        let proper_motion_vector_x = proper_motion_x - x;
229        let proper_motion_vector_y = proper_motion_y - y;
230        let proper_motion_vector_z = proper_motion_z - z;
231
232        let normalized_vector_x = x / (x.powf(2.) + y.powf(2.) + z.powf(2.)).sqrt();
233        let normalized_vector_y = y / (x.powf(2.) + y.powf(2.) + z.powf(2.)).sqrt();
234        let normalized_vector_z = z / (x.powf(2.) + y.powf(2.) + z.powf(2.)).sqrt();
235
236        let radial_velocity_vector_x = normalized_vector_x * radial_velocity_si;
237        let radial_velocity_vector_y = normalized_vector_y * radial_velocity_si;
238        let radial_velocity_vector_z = normalized_vector_z * radial_velocity_si;
239
240        let x_v = radial_velocity_vector_x + proper_motion_vector_x;
241        let y_v = radial_velocity_vector_y + proper_motion_vector_y;
242        let z_v = radial_velocity_vector_z + proper_motion_vector_z;
243
244        DVec3::new(x_v, y_v, z_v)
245    }
246
247    /// Velocity of the companion star in a twobody system with no rotation applied.
248    /// a is semi major-axis in au, e is eccentricity, period is in years and t_p is time since periastron in years.
249    /// Output is a 2-dimensional vector with x and y in that order all in meters/second. We only need a 2-dimensional vector here
250    /// due to the fact that everything is on a plane in 2D.
251    pub fn companion_velocity(a: f64, e: f64, period: f64, t_p: f64) -> DVec2 {
252        //Prep Values
253        let mu = standard_gravitational_parameter(a, period);
254        let p = semi_parameter(a, e);
255        let v = true_anomaly(e, period, t_p);
256
257        //Velocity of Companion in ellipse base
258        let x_v = (0. - ((mu / p).sqrt())) * (v.sin());
259        let y_v = ((mu / p).sqrt()) * (e + (v.cos()));
260
261        DVec2::new(x_v, y_v)
262    }
263
264    /// Velocity of the companion star in a twobody system with rotation relative to the earth/sun plane applied.
265    /// a is semi major-axis in au, e is eccentricity, period is in years, t_p is time since periastron in years,
266    /// lotn is Longitude of the node (Omega) in degrees, aop is Argument of periastron (omega) in degrees and finally i is the Inclination in degrees.
267    /// Output is a 3-dimensional vector with x, y and z in that order all in meters/second.
268    pub fn companion_relative_velocity(
269        a: f64,
270        e: f64,
271        period: f64,
272        t_p: f64,
273        lotn: f64,
274        aop: f64,
275        i: f64,
276    ) -> DVec3 {
277        //Prep Values
278        let mu = standard_gravitational_parameter(a, period);
279        let p = semi_parameter(a, e);
280        let v = true_anomaly(e, period, t_p);
281
282        //Velocity of Companion in ellipse base
283        let x_v = (0. - ((mu / p).sqrt())) * (v.sin());
284        let y_v = ((mu / p).sqrt()) * (e + (v.cos()));
285
286        //Ellipse base
287        let euler_angle_transformations = euler_angle_transformations(lotn, aop, i).to_cols_array();
288        let x1 = euler_angle_transformations[0];
289        let x2 = euler_angle_transformations[1];
290        let x3 = euler_angle_transformations[2];
291
292        let y1 = euler_angle_transformations[3];
293        let y2 = euler_angle_transformations[4];
294        let y3 = euler_angle_transformations[5];
295
296        //Velocity in original base
297        let companion_velocity_x = (x1 * x_v) + (y1 * y_v);
298        let companion_velocity_y = (x2 * x_v) + (y2 * y_v);
299        let companion_velocity_z = (x3 * x_v) + (y3 * y_v);
300
301        DVec3::new(
302            companion_velocity_x,
303            companion_velocity_y,
304            companion_velocity_z,
305        )
306    }
307
308    /// Just the companion velocity but as a value and not coordinates.
309    pub fn companion_velocity_value(a: f64, e: f64, period: f64, t_p: f64) -> f64 {
310        let mu = standard_gravitational_parameter(a, period);
311        let epsilon = specific_mechanical_energy(a, e);
312        let r = radius(a, e, period, t_p);
313
314        (2. * ((mu / r) + epsilon)).sqrt()
315    }
316}
317
318/// Set of common functions used by `spv-rs` exposed if you want to used them for your own calculations.
319pub mod common {
320    use super::coordinate_transforms::euler_angle_transformations;
321    use super::position::companion_relative_position;
322    use super::velocity::companion_relative_velocity;
323    use glam::f64::DVec3;
324
325    /// Takes a in as (arcseconds) and parllax in mas (milliarcsecond) and outputs a in au.
326    pub fn a_to_au(parallax: f64, a: f64) -> f64 {
327        a * parallax_to_parsec(parallax)
328    }
329
330    /// Calculates total declination in degrees with declination_degree, declination_min and declination_s in degrees, minutes and seconds respectively.
331    pub fn declination_total(
332        declination_degree: f64,
333        declination_min: f64,
334        declination_s: f64,
335    ) -> f64 {
336        declination_degree + (declination_min / 60.) + (declination_s / 3600.)
337    }
338
339    /// Calculates total right ascension in degrees with right_ascension_h, right_ascension_min and right_ascension_s in hours, minutes and seconds respectively.
340    pub fn right_ascension_total(
341        right_ascension_h: f64,
342        right_ascension_min: f64,
343        right_ascension_s: f64,
344    ) -> f64 {
345        (right_ascension_h * 15.)
346            + (right_ascension_min * (1. / 4.))
347            + (right_ascension_s * (1. / 240.))
348    }
349
350    /// Calculates r min or the minimum distance between the primary and companion boides in a twobody system also known as perigee
351    /// (suffix may change depending on what object it reffers to).
352    /// Output is just the x coordinate in the ellipses plane in au.
353    pub fn perigee(a: f64, e: f64) -> f64 {
354        a * (1. - e)
355    }
356
357    /// Calculates r max or the maximum distance between the primary and companion boides in a twobody system also known as apogee
358    /// (suffix may change depending on what object it reffers to).
359    /// Output is just the x coordinate in the ellipses plane in au.
360    pub fn apogee(a: f64, e: f64) -> f64 {
361        a * (1. + e)
362    }
363
364    /// Calculates r min or the minimum distance between the primary and companion boides in a twobody system also known as perigee
365    /// (suffix may change depending on what object it reffers to).
366    /// Output is 3-dimensional vector that represents the coordinates for perigee rotated to be relative to the earth/sun plane in meters.
367    pub fn relative_perigee(a: f64, e: f64, lotn: f64, aop: f64, i: f64) -> DVec3 {
368        let a_si = au_to_m(a);
369        let x = a_si * (1. - e);
370
371        let euler_angle_transformations = euler_angle_transformations(lotn, aop, i).to_cols_array();
372        let x1 = euler_angle_transformations[0];
373        let x2 = euler_angle_transformations[1];
374        let x3 = euler_angle_transformations[2];
375
376        DVec3::new(x * x1, x * x2, x * x3)
377    }
378
379    /// Calculates r max or the maximum distance between the primary and companion boides in a twobody system also known as apogee
380    /// (suffix may change depending on what object it reffers to).
381    /// Output is 3-dimensional vector that represents the coordinates for apogee rotated to be relative to the earth/sun plane in meters.
382    pub fn relative_apogee(a: f64, e: f64, lotn: f64, aop: f64, i: f64) -> DVec3 {
383        let a_si = au_to_m(a);
384        let x = a_si * (1. + e);
385
386        let euler_angle_transformations = euler_angle_transformations(lotn, aop, i).to_cols_array();
387        let x1 = euler_angle_transformations[0];
388        let x2 = euler_angle_transformations[1];
389        let x3 = euler_angle_transformations[2];
390
391        DVec3::new(x * x1, x * x2, x * x3)
392    }
393
394    /// Calculates the eccentric anomaly in degrees
395    pub fn eccentric_anomaly(e: f64, period: f64, t_p: f64) -> f64 {
396        //SI units
397        let p_si = period * 31557600.;
398        let t_p_si = t_p * 31557600.;
399
400        //Defining angles
401        let mean_anom = std::f64::consts::PI * 2. * t_p_si / p_si;
402        let mut ecc_anom = mean_anom;
403        for _i in (0..=20).step_by(1) {
404            ecc_anom = mean_anom + (e * ecc_anom.sin());
405        }
406
407        ecc_anom
408    }
409
410    /// Calculates the true anomaly in degrees
411    pub fn true_anomaly(e: f64, period: f64, t_p: f64) -> f64 {
412        //SI units
413        let p_si = period * 31557600.;
414        let t_p_si = t_p * 31557600.;
415
416        //Defining angles
417        let mean_anom = std::f64::consts::PI * 2. * t_p_si / p_si;
418        let mut ecc_anom = mean_anom;
419        for _i in (0..=20).step_by(1) {
420            ecc_anom = mean_anom + (e * ecc_anom.sin());
421        }
422
423        2. * (((1. + e) / (1. - e)).sqrt() * (ecc_anom * 0.5).tan()).atan()
424    }
425
426    /// Calculates the flight path angle for the companion body in degrees
427    pub fn flight_path_angle(e: f64, period: f64, t_p: f64) -> f64 {
428        //SI units
429        let p_si = period * 31557600.;
430        let t_p_si = t_p * 31557600.;
431
432        //Defining angles
433        let mean_anom = std::f64::consts::PI * 2. * t_p_si / p_si;
434        let mut ecc_anom = mean_anom;
435        for _i in (0..=20).step_by(1) {
436            ecc_anom = mean_anom + (e * ecc_anom.sin());
437        }
438
439        ((e * ecc_anom.sin()) / ((1. - ((e.powf(2.)) * (ecc_anom.cos().powf(2.)))).sqrt()))
440            .asin()
441            .to_degrees()
442    }
443
444    /// Calculates the semi parameter for a twobody system and returns it in meters.
445    pub fn semi_parameter(a: f64, e: f64) -> f64 {
446        let a_si = au_to_m(a);
447        let b_si = semi_minor_axis(a, e);
448
449        (b_si.powf(2.)) / a_si
450    }
451
452    /// Calculates the semi minor axis for a twobody system and returns it in meters.
453    pub fn semi_minor_axis(a: f64, e: f64) -> f64 {
454        let a_si = au_to_m(a);
455
456        a_si * ((1. - e.powf(2.)).sqrt())
457    }
458
459    /// Calculates the total radius for a twobody system
460    pub fn radius(a: f64, e: f64, period: f64, t_p: f64) -> f64 {
461        let nu = true_anomaly(e, period, t_p);
462        let p = semi_parameter(a, e);
463
464        p / (1. + (e * nu.cos()))
465    }
466
467    /// Calculates the specific angular momentum value
468    pub fn specific_angular_momentum_value(a: f64, e: f64, period: f64) -> f64 {
469        let p = semi_parameter(a, e);
470        let mu = standard_gravitational_parameter(a, period);
471
472        (mu * p).sqrt()
473    }
474
475    /// Calculates the specific angular momentum coordinates
476    pub fn specific_angular_momentum_coordinates(
477        a: f64,
478        e: f64,
479        period: f64,
480        t_p: f64,
481        lotn: f64,
482        aop: f64,
483        i: f64,
484    ) -> DVec3 {
485        let r = companion_relative_position(a, e, period, t_p, lotn, aop, i);
486        let v = companion_relative_velocity(a, e, period, t_p, lotn, aop, i);
487
488        DVec3::cross(r, v)
489    }
490
491    /// Calculates the stadard gravitational parameter
492    pub fn standard_gravitational_parameter(a: f64, period: f64) -> f64 {
493        let period_si = period * 31557600.;
494        let a_si = au_to_m(a);
495
496        ((a_si.powf(3.)) * 4. * (std::f64::consts::PI.powf(2.))) / (period_si.powf(2.))
497    }
498
499    /// Specific mechanical energy (used by other equation but exposed here if you need it)
500    pub fn specific_mechanical_energy(a: f64, period: f64) -> f64 {
501        let a_si = au_to_m(a);
502        let mu = standard_gravitational_parameter(a, period);
503
504        0. - (mu / (2. * a_si))
505    }
506
507    /// If you dind't have the period already
508    pub fn period(a: f64, mu: f64) -> f64 {
509        let a_si = au_to_m(a);
510
511        2. * std::f64::consts::PI * (((a_si.powf(3.)) / mu).sqrt())
512    }
513
514    /// If you for some reason had these parameters and not a then here ya go
515    pub fn semi_major_axis(
516        standard_gravitational_parameter: f64,
517        specific_mechanical_energy: f64,
518    ) -> f64 {
519        0. - (standard_gravitational_parameter / (2. * specific_mechanical_energy))
520    }
521
522    /// Mean motion or n
523    pub fn mean_motion(a: f64, period: f64) -> f64 {
524        let a_si = au_to_m(a);
525        let mu = standard_gravitational_parameter(a, period);
526
527        (mu / (a_si.powf(3.))).sqrt()
528    }
529
530    /// If you for some reason had these parameters and not e then here ya go.
531    pub fn eccentricity(
532        standard_gravitational_parameter: f64,
533        specific_mechanical_energy: f64,
534        specific_angular_momentum_value: f64,
535    ) -> f64 {
536        (1. - ((2. * specific_mechanical_energy * (specific_angular_momentum_value.powf(2.)))
537            / (standard_gravitational_parameter.powf(2.))))
538        .sqrt()
539    }
540
541    /// Distance between one foci and the center of the ellipse in au.
542    pub fn linear_eccentricity(a: f64, e: f64) -> f64 {
543        a * e
544    }
545
546    /// Flattening is another way to defining eccentricity for an ellipse.
547    pub fn flattening(a: f64, e: f64) -> f64 {
548        let a_si = au_to_m(a);
549        let b = semi_minor_axis(a_si, e);
550
551        (a_si - b) / a_si
552    }
553
554    /// Parallax to parsec convert function where the parallax parameter is in mas (milliarcseconds).
555    pub fn parallax_to_parsec(parallax: f64) -> f64 {
556        1. / (parallax / 1000.)
557    }
558
559    /// Simple function motsly used by `spv-rs` to convert au to meters (used for semi major-axis in most cases but can be used for other stuff too).
560    pub fn au_to_m(a: f64) -> f64 {
561        a * 1000. * 149597870.7
562    }
563
564    ///
565    pub fn apparent_mag_to_absolute_mag(parallax: f64, apparent_magnitude: f64) -> f64 {
566        apparent_magnitude + 5. * (parallax.log10() + 1.)
567    }
568
569    ///
570    pub fn temperature(b_v_index: f64) -> f64 {
571        4600. * ((1. / ((0.92 * b_v_index) + 1.7)) + (1. / ((0.92 * b_v_index) + 0.62)))
572    }
573}
574
575/// Transform fucntions used by `spv-rs` but exposed her if you want to use them yourself.
576pub mod coordinate_transforms {
577    use glam::f64::{DMat3, DVec3};
578
579    /// Method for getting base manipulation matrix that is used to rotate the companion star in a twobody system
580    /// relative to the earth/sun plane.
581    /// lotn is Longitude of the node (Omega) in degrees, aop is Argument of periastron (omega) in degrees and finally i is the Inclination in degrees.
582    /// Output is a 3-dimensional matrix with x1, x2 and x3 in the first collum, y1, y2 and y3 in the second collum and z1, z2 and z3 in the third collum.
583    pub fn euler_angle_transformations(lotn: f64, aop: f64, i: f64) -> DMat3 {
584        //In rad
585        let lotn_rad = lotn.to_radians();
586        let aop_rad = aop.to_radians();
587        let i_rad = i.to_radians();
588
589        //Ellipse base
590        let x1 = (lotn_rad.cos() * aop_rad.cos()) - (lotn_rad.sin() * i_rad.cos() * aop_rad.sin());
591        let x2 = (lotn_rad.sin() * aop_rad.cos()) + (lotn_rad.cos() * i_rad.cos() * aop_rad.sin());
592        let x3 = i_rad.sin() * aop_rad.sin();
593
594        let y1 = ((0. - lotn_rad.cos()) * aop_rad.sin())
595            - (lotn_rad.sin() * i_rad.cos() * aop_rad.cos());
596        let y2 = ((0. - lotn_rad.sin()) * aop_rad.sin())
597            + (lotn_rad.cos() * i_rad.cos() * aop_rad.cos());
598        let y3 = i_rad.sin() * aop_rad.cos();
599
600        let z1 = i_rad.sin() * lotn_rad.sin();
601        let z2 = (0. - i_rad.sin()) * lotn_rad.cos();
602        let z3 = i_rad.cos();
603
604        DMat3::from_cols(
605            DVec3::new(x1, x2, x3),
606            DVec3::new(y1, y2, y3),
607            DVec3::new(z1, z2, z3),
608        )
609    }
610}
611
612/// Basic csv parsing for extracting real world data or any old data table you want to parse really.
613/// To get a csv if you got some other format from something like [Vizier](https://vizier.cds.unistra.fr/viz-bin/VizieR)
614/// I would recomend a tool like [Topcat](http://www.star.bris.ac.uk/~mbt/topcat/).
615pub mod input_data {
616    use csv::{ReaderBuilder, Terminator};
617    use std::error::Error;
618
619    /// General usecase parsing function for csv files.
620    pub fn parse_csv<T: for<'de> serde::Deserialize<'de>>(
621        filename: &str,
622        has_headers: bool,
623        cols_split: u8,
624        row_split: u8,
625    ) -> Result<std::vec::Vec<T>, Box<dyn Error>> {
626        let mut vec = vec![];
627        let mut rdr = ReaderBuilder::new()
628            .delimiter(cols_split)
629            .terminator(Terminator::Any(row_split))
630            .has_headers(has_headers)
631            .from_path(filename)?;
632        for result in rdr.deserialize() {
633            let record: T = result?;
634            vec.push(record);
635        }
636        Ok(vec)
637    }
638}
639
640/// Basic csv writing utility for saving large datasets to file.
641pub mod output_data {
642    use csv::{Terminator, WriterBuilder};
643    use std::error::Error;
644
645    /// General usecase writing function for csv files.
646    pub fn write_csv<T: serde::Serialize>(
647        output_filename: &str,
648        has_headers: bool,
649        cols_split: u8,
650        row_split: u8,
651        vec: std::vec::Vec<T>,
652    ) -> Result<(), Box<dyn Error>> {
653        let mut writer = WriterBuilder::new()
654            .delimiter(cols_split)
655            .terminator(Terminator::Any(row_split))
656            .has_headers(has_headers)
657            .from_path(output_filename)?;
658
659        for n in vec {
660            writer.serialize(n)?;
661        }
662
663        Ok(())
664    }
665}
666
667/// Specific usecase functions for NBSS
668pub mod nbss {
669    use serde::Deserialize;
670    use serde::Serialize;
671
672    /// NBSS Input struct
673    #[derive(Debug, Deserialize)]
674    #[serde(rename_all = "PascalCase")]
675    pub struct NBSSInputCollums {
676        pub name: String,
677        pub mass: f64,
678        pub radius: f64,
679        pub reference_body: String,
680        pub a: f64,
681        pub e: f64,
682        pub period: f64,
683        pub time_since_periapsis: f64,
684        pub lotn: f64,
685        pub aop: f64,
686        pub i: f64,
687    }
688
689    /// NBSS Output struct
690    #[derive(Serialize)]
691    #[serde(rename_all = "PascalCase")]
692    pub struct NBSSOutputCollums {
693        pub name: String,
694        pub mass: f64,
695        pub radius: f64,
696        pub reference_body: String,
697        pub e: f64,
698        pub semi_parameter: f64,
699        pub position_x: f64,
700        pub position_y: f64,
701        pub position_z: f64,
702        pub velocity_x: f64,
703        pub velocity_y: f64,
704        pub velocity_z: f64,
705    }
706
707    /// NBSS Calc struct (can be used as an example of what you can do with SPV)
708    pub fn position_and_velocity_twobody_serialized(input_filename: &str, output_filename: &str) {
709        let mut file: std::vec::Vec<NBSSInputCollums> = vec![];
710        match super::input_data::parse_csv(input_filename, false, b',', b'\n') {
711            Ok(vec) => file = vec,
712            Err(ex) => {
713                println!("ERROR -> {}", ex);
714            }
715        };
716
717        let mut vec = vec![];
718
719        for n in file {
720            if n.a == 0. && n.e == 0. {
721                let row = NBSSOutputCollums {
722                    name: n.name,
723                    mass: n.mass,
724                    radius: n.radius,
725                    reference_body: n.reference_body,
726                    e: 0.,
727                    semi_parameter: 0.,
728                    position_x: 0.,
729                    position_y: 0.,
730                    position_z: 0.,
731                    velocity_x: 0.,
732                    velocity_y: 0.,
733                    velocity_z: 0.,
734                };
735
736                vec.push(row);
737            } else {
738                let pos = super::position::companion_relative_position(
739                    n.a,
740                    n.e,
741                    n.period,
742                    n.time_since_periapsis,
743                    n.lotn,
744                    n.aop,
745                    n.i,
746                )
747                .to_array();
748                let vel = super::velocity::companion_relative_velocity(
749                    n.a,
750                    n.e,
751                    n.period,
752                    n.time_since_periapsis,
753                    n.lotn,
754                    n.aop,
755                    n.i,
756                )
757                .to_array();
758                let semi_parameter = super::common::semi_parameter(n.a, n.e);
759
760                let row = NBSSOutputCollums {
761                    name: n.name,
762                    mass: n.mass,
763                    radius: n.radius,
764                    reference_body: n.reference_body,
765                    e: n.e,
766                    semi_parameter,
767                    position_x: pos[0],
768                    position_y: pos[1],
769                    position_z: pos[2],
770                    velocity_x: vel[0],
771                    velocity_y: vel[1],
772                    velocity_z: vel[2],
773                };
774
775                vec.push(row);
776            }
777        }
778
779        match super::output_data::write_csv(output_filename, false, b',', b'\n', vec) {
780            Ok(_) => (),
781            Err(ex) => {
782                println!("ERROR -> {}", ex);
783            }
784        };
785    }
786
787    //WIP
788    /*
789    pub fn position_and_velocity_relative_twobody(input_filename: &str, output_filename: &str) {
790
791    }
792
793    pub fn position_and_velocity_relative(input_filename: &str, output_filename: &str) {
794
795    }
796    */
797}
798
799// WIP
800/*
801pub fn above(
802    radius_primary: f64,
803    observers_longitude_primary: f64,
804    observers_latitude_primary: f64,
805    obliquity_of_the_ecliptic_primary: f64,
806    rotation_rate_primary: f64,
807    lotn_primary: f64,
808    aop_primary: f64,
809    i_primary: f64,
810    distance: f64,
811    a_companion: f64,
812    e_companion: f64,
813    period_companion: f64,
814    t_p_companion: f64,
815    lotn_companion: f64,
816    aop_companion: f64,
817    i_companion: f64,
818
819) {
820
821    let observer_declination =  ((observers_latitude_primary.sin() * obliquity_of_the_ecliptic_primary.cos()) + (observers_latitude_primary.cos() * obliquity_of_the_ecliptic_primary.sin() * observers_longitude_primary.sin())).asin();
822    let observer_right_ascension = ((observers_latitude_primary.cos() * observers_longitude_primary.cos()) / observer_declination.cos()).acos();
823
824    let position_surface = position_surface(radius_primary, observer_right_ascension, observer_declination);
825
826    let primary_rotation = euler_angle_transformations(lotn_primary, aop_primary, i_primary);
827
828    let companion_rotation = euler_angle_transformations(lotn_companion, aop_companion, i_companion);
829
830    let new_companion_rotation = companion_rotation - primary_rotation;
831
832    let companion_position = companion_relative_position(a_companion, e_companion, period_companion, t_p_companion, lotn_companion, aop_companion, i_companion);
833
834    let primary_velocity = (2. * std::f64::consts::PI * radius_primary) / rotation_rate_primary;
835}
836*/