Skip to content

Commit e60eb15

Browse files
Merge pull request #326 from nyx-space/gh-314-line-of-sight
Fixing line of sight computation + SPICE validation of penumbra calculator
2 parents 3be3ca4 + 81b6402 commit e60eb15

File tree

15 files changed

+208
-41
lines changed

15 files changed

+208
-41
lines changed

.github/workflows/rust.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ jobs:
134134
./target/debug/anise-cli inspect data/gmat-hermite.bsp
135135
./target/debug/anise-cli inspect data/de440.bsp
136136
137+
- name: Rust-SPICE occultation validation
138+
run: cargo test validate_gh_283_multi_barycenter_and_los --release --workspace --exclude anise-gui --exclude anise-py -- --nocapture --include-ignored
139+
137140
- name: Rust-SPICE JPL DE validation
138141
run: RUST_BACKTRACE=1 cargo test validate_jplde --features spkezr_validation --release --workspace --exclude anise-gui --exclude anise-py -- --nocapture --include-ignored --test-threads 1
139142

@@ -213,6 +216,7 @@ jobs:
213216
cargo llvm-cov clean --workspace
214217
cargo llvm-cov test --no-report -- --test-threads=1
215218
cargo llvm-cov test --no-report --tests -- compile_fail
219+
cargo llvm-cov test --no-report validate_gh_283_multi_barycenter_and_los -- --nocapture --ignored
216220
cargo llvm-cov test --no-report validate_iau_rotation_to_parent -- --nocapture --ignored
217221
cargo llvm-cov test --no-report validate_bpc_to_iau_rotations -- --nocapture --ignored
218222
cargo llvm-cov test --no-report validate_jplde_de440s_no_aberration --features spkezr_validation -- --nocapture --ignored

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ resolver = "2"
33
members = ["anise", "anise-cli", "anise-gui", "anise-py"]
44

55
[workspace.package]
6-
version = "0.4.3"
6+
version = "0.4.4"
77
edition = "2021"
88
authors = ["Christopher Rabotin <christopher.rabotin@gmail.com>"]
99
description = "ANISE provides a toolkit and files for Attitude, Navigation, Instrument, Spacecraft, and Ephemeris data. It's a modern replacement of NAIF SPICE file."
@@ -51,7 +51,7 @@ serde = "1"
5151
serde_derive = "1"
5252
serde_dhall = "0.12"
5353

54-
anise = { version = "0.4.3", path = "anise", default-features = false }
54+
anise = { version = "0.4.4", path = "anise", default-features = false }
5555

5656
[profile.bench]
5757
debug = true

anise/src/almanac/aer.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use crate::{
1212
astro::{Aberration, AzElRange},
13+
constants::SPEED_OF_LIGHT_KM_S,
1314
ephemerides::{EphemerisError, EphemerisPhysicsSnafu},
1415
errors::{AlmanacError, EphemerisSnafu, PhysicsError},
1516
frames::Frame,
@@ -21,6 +22,7 @@ use crate::{
2122
use super::Almanac;
2223
use crate::errors::AlmanacResult;
2324

25+
use hifitime::TimeUnits;
2426
use log::warn;
2527

2628
use snafu::ResultExt;
@@ -33,6 +35,9 @@ impl Almanac {
3335
/// Computes the azimuth (in degrees), elevation (in degrees), and range (in kilometers) of the
3436
/// receiver state (`rx`) seen from the transmitter state (`tx`), once converted into the SEZ frame of the transmitter.
3537
///
38+
/// # Warning
39+
/// The obstructing body _should_ be a tri-axial ellipsoid body, e.g. IAU_MOON_FRAME.
40+
///
3641
/// # Algorithm
3742
/// 1. If any obstructing_bodies are provided, ensure that none of these are obstructing the line of sight between the receiver and transmitter.
3843
/// 2. Compute the SEZ (South East Zenith) frame of the transmitter.
@@ -120,6 +125,7 @@ impl Almanac {
120125
range_km: rho_sez.norm(),
121126
range_rate_km_s,
122127
obstructed_by,
128+
light_time: (rho_sez.norm() / SPEED_OF_LIGHT_KM_S).seconds(),
123129
})
124130
}
125131
}
@@ -239,7 +245,43 @@ mod ut_aer {
239245
),
240246
];
241247

242-
for (sno, state) in states.iter().enumerate() {
248+
for (sno, state) in states.iter().copied().enumerate() {
249+
// Rebuild the ground station at this new epoch
250+
let madrid = Orbit::try_latlongalt(
251+
latitude_deg,
252+
longitude_deg,
253+
height_km,
254+
MEAN_EARTH_ANGULAR_VELOCITY_DEG_S,
255+
state.epoch,
256+
iau_earth,
257+
)
258+
.unwrap();
259+
260+
let aer = almanac
261+
.azimuth_elevation_range_sez(state, madrid, None, None)
262+
.unwrap();
263+
264+
if sno == 0 {
265+
assert_eq!(
266+
format!("{aer}"),
267+
format!(
268+
"{}: az.: 133.599990 deg el.: 7.237568 deg range: 91457.271742 km range-rate: -12.396849 km/s obstruction: none",
269+
state.epoch
270+
)
271+
);
272+
}
273+
274+
let expect = gmat_ranges_km[sno];
275+
276+
// This only checks that our computation isn't total garbage.
277+
assert!((aer.range_km - expect).abs() < 5.0);
278+
}
279+
280+
// Ensure that if the state are in another frame, the results are identical.
281+
282+
let states = states.map(|state| almanac.transform_to(state, EARTH_ITRF93, None).unwrap());
283+
284+
for (sno, state) in states.iter().copied().enumerate() {
243285
// Rebuild the ground station at this new epoch
244286
let madrid = Orbit::try_latlongalt(
245287
latitude_deg,
@@ -252,7 +294,7 @@ mod ut_aer {
252294
.unwrap();
253295

254296
let aer = almanac
255-
.azimuth_elevation_range_sez(*state, madrid, None, None)
297+
.azimuth_elevation_range_sez(state, madrid, None, None)
256298
.unwrap();
257299

258300
if sno == 0 {

anise/src/almanac/transform.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use hifitime::{Epoch, Unit as TimeUnit};
1212
use snafu::ResultExt;
1313

1414
use crate::{
15+
constants::orientations::J2000,
1516
errors::{AlmanacResult, EphemerisSnafu, OrientationSnafu},
1617
math::{cartesian::CartesianState, units::LengthUnit, Vector3},
1718
orientations::OrientationPhysicsSnafu,
@@ -71,25 +72,29 @@ impl Almanac {
7172
#[allow(clippy::too_many_arguments)]
7273
pub fn transform_to(
7374
&self,
74-
state: CartesianState,
75+
mut state: CartesianState,
7576
observer_frame: Frame,
7677
ab_corr: Option<Aberration>,
7778
) -> AlmanacResult<CartesianState> {
78-
let state = self
79+
// If the input and final rotations differ, rotate into J2000 first
80+
state = if state.frame.orient_origin_match(observer_frame) {
81+
state
82+
} else {
83+
self.rotate_to(state, state.frame.with_orient(J2000))
84+
.context(OrientationSnafu {
85+
action: "transform state dcm",
86+
})?
87+
};
88+
89+
// Transform in the base frame (J2000) or the common frame
90+
state = self
7991
.translate_to(state, observer_frame, ab_corr)
8092
.context(EphemerisSnafu {
8193
action: "transform state",
8294
})?;
8395

84-
// Compute the frame rotation
85-
let dcm = self
86-
.rotate(state.frame, observer_frame, state.epoch)
87-
.context(OrientationSnafu {
88-
action: "transform state dcm",
89-
})?;
90-
91-
(dcm * state)
92-
.context(OrientationPhysicsSnafu {})
96+
// Rotate into the observer frame
97+
self.rotate_to(state, observer_frame)
9398
.context(OrientationSnafu {
9499
action: "transform state",
95100
})

anise/src/astro/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::fmt::Display;
1313
use crate::errors::PhysicsError;
1414
use crate::frames::Frame;
1515

16-
use hifitime::Epoch;
16+
use hifitime::{Duration, Epoch};
1717

1818
#[cfg(feature = "python")]
1919
use pyo3::exceptions::PyTypeError;
@@ -47,6 +47,7 @@ pub struct AzElRange {
4747
pub range_km: f64,
4848
pub range_rate_km_s: f64,
4949
pub obstructed_by: Option<Frame>,
50+
pub light_time: Duration,
5051
}
5152

5253
#[cfg_attr(feature = "python", pymethods)]
@@ -56,6 +57,11 @@ impl AzElRange {
5657
self.azimuth_deg.is_finite() && self.elevation_deg.is_finite() && self.range_km > 1e-6
5758
}
5859

60+
/// Returns whether there is an obstruction.
61+
pub const fn is_obstructed(&self) -> bool {
62+
self.obstructed_by.is_some()
63+
}
64+
5965
/// Initializes a new AzElRange instance
6066
#[cfg(feature = "python")]
6167
#[new]
@@ -67,13 +73,17 @@ impl AzElRange {
6773
range_rate_km_s: f64,
6874
obstructed_by: Option<Frame>,
6975
) -> Self {
76+
use crate::constants::SPEED_OF_LIGHT_KM_S;
77+
use hifitime::TimeUnits;
78+
7079
Self {
7180
epoch,
7281
azimuth_deg,
7382
elevation_deg,
7483
range_km,
7584
range_rate_km_s,
7685
obstructed_by,
86+
light_time: (range_km / SPEED_OF_LIGHT_KM_S).seconds(),
7787
}
7888
}
7989

anise/src/astro/occultation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use pyo3::prelude::*;
1919

2020
/// Stores the result of an occultation computation with the occulation percentage
2121
/// Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details.
22-
#[derive(Copy, Clone, PartialEq)]
22+
#[derive(Copy, Clone, Debug, PartialEq)]
2323
#[cfg_attr(feature = "python", pyclass)]
2424
#[cfg_attr(feature = "python", pyo3(module = "anise"))]
2525
#[cfg_attr(feature = "python", pyo3(get_all, set_all))]

anise/src/astro/orbit_geodetic.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,19 @@ impl CartesianState {
227227
Ok((lat_deg, long_deg, alt_km))
228228
}
229229

230-
/// Returns the geodetic longitude (λ) in degrees. Value is between 0 and 360 degrees.
230+
/// Returns the geodetic longitude (λ) in degrees. Value is between -180 and 180 degrees.
231231
///
232232
/// # Frame warning
233233
/// This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.
234234
pub fn longitude_deg(&self) -> f64 {
235+
between_pm_180(self.radius_km.y.atan2(self.radius_km.x).to_degrees())
236+
}
237+
238+
/// Returns the geodetic longitude (λ) in degrees. Value is between 0 and 360 degrees.
239+
///
240+
/// # Frame warning
241+
/// This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.
242+
pub fn longitude_360_deg(&self) -> f64 {
235243
between_0_360(self.radius_km.y.atan2(self.radius_km.x).to_degrees())
236244
}
237245

anise/src/frames/frame.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
use core::fmt;
1212
use core::fmt::Debug;
1313
use serde_derive::{Deserialize, Serialize};
14-
use serde_dhall::StaticType;
1514
use snafu::ResultExt;
1615

16+
#[cfg(feature = "metaload")]
17+
use serde_dhall::StaticType;
18+
1719
use crate::astro::PhysicsResult;
1820
use crate::constants::celestial_objects::{
1921
celestial_name_from_id, id_to_celestial_name, SOLAR_SYSTEM_BARYCENTER,
@@ -32,7 +34,8 @@ use pyo3::prelude::*;
3234
use pyo3::pyclass::CompareOp;
3335

3436
/// A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.
35-
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, StaticType)]
37+
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
38+
#[cfg_attr(feature = "metaload", derive(StaticType))]
3639
#[cfg_attr(feature = "python", pyclass)]
3740
#[cfg_attr(feature = "python", pyo3(get_all, set_all))]
3841
#[cfg_attr(feature = "python", pyo3(module = "anise.astro"))]
@@ -317,6 +320,7 @@ mod frame_ut {
317320
assert_eq!(format!("{EME2000:e}"), "Earth");
318321
}
319322

323+
#[cfg(feature = "metaload")]
320324
#[test]
321325
fn dhall_serde() {
322326
let serialized = serde_dhall::serialize(&EME2000)

anise/src/structure/planetocentric/ellipsoid.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use core::fmt;
1212
use der::{Decode, Encode, Reader, Writer};
1313
use serde_derive::{Deserialize, Serialize};
14+
15+
#[cfg(feature = "metaload")]
1416
use serde_dhall::StaticType;
1517

1618
#[cfg(feature = "python")]
@@ -30,7 +32,8 @@ use pyo3::pyclass::CompareOp;
3032
/// Example: Radii of the Earth.
3133
///
3234
/// BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )
33-
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, StaticType)]
35+
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
36+
#[cfg_attr(feature = "metaload", derive(StaticType))]
3437
#[cfg_attr(feature = "python", pyclass)]
3538
#[cfg_attr(feature = "python", pyo3(get_all, set_all))]
3639
#[cfg_attr(feature = "python", pyo3(module = "anise.astro"))]

anise/tests/astro/orbit.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,8 @@ fn verif_geodetic_vallado(almanac: Almanac) {
642642
f64_eq!(r.radius_km.z, rk, "r_k");
643643
let r = Orbit::from_position(ri, rj, rk, epoch, eme2k);
644644
f64_eq!(r.latitude_deg().unwrap(), lat_val, "latitude (φ)");
645-
f64_eq!(r.longitude_deg(), long, "longitude (λ)");
645+
f64_eq!(r.longitude_deg(), long - 360.0, "longitude (λ)");
646+
f64_eq!(r.longitude_360_deg(), long, "longitude (λ)");
646647
f64_eq!(r.height_km().unwrap(), height_val, "height");
647648

648649
// Check reciprocity near poles

0 commit comments

Comments
 (0)