1use crate::air::{air_index, d_air_index, Atmosphere};
2use crate::{flat, spherical, Path, PathStepper, RayState, RayStateDerivative};
3
4#[derive(Clone, Copy)]
6#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
7pub enum EarthShape {
8 Spherical { radius: f64 },
9 Flat,
10}
11
12#[derive(Clone)]
14#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
15pub struct Environment {
16 pub shape: EarthShape,
17 pub atmosphere: Atmosphere,
18 #[cfg_attr(feature = "serialization", serde(default = "default_wavelength"))]
19 pub wavelength: f64,
20}
21
22#[cfg(feature = "serialization")]
23fn default_wavelength() -> f64 {
24 530e-9
25}
26
27impl Environment {
28 pub fn n(&self, h: f64) -> f64 {
30 let pressure = self.atmosphere.pressure(h);
31 let temperature = self.atmosphere.temperature(h);
32 let rh = self.atmosphere.humidity(h);
33 air_index(self.wavelength, pressure, temperature, rh)
34 }
35
36 pub fn dn(&self, h: f64) -> f64 {
39 let pressure = self.atmosphere.pressure(h);
40 let temperature = self.atmosphere.temperature(h);
41 let rh = self.atmosphere.humidity(h);
42 let dp = self.atmosphere.dpressure(h);
43 let dt = self.atmosphere.dtemperature(h);
44 let drh = self.atmosphere.dhumidity(h);
45 d_air_index(self.wavelength, pressure, temperature, rh, dp, dt, drh)
46 }
47
48 pub fn radius(&self) -> Option<f64> {
50 match self.shape {
51 EarthShape::Spherical { radius } => Some(radius),
52 EarthShape::Flat => None,
53 }
54 }
55
56 pub(crate) fn calc_derivative_spherical(&self, state: &RayState) -> RayStateDerivative {
57 let radius = self.radius().unwrap();
58 let dh = state.dh * radius;
59 let h = state.h;
60
61 let nr = self.n(h);
62 let dnr = self.dn(h);
63
64 let r = h + radius;
65 let d2h = dh * dh * dnr / nr + r * r * dnr / nr + 2.0 * dh * dh / r + r;
66
67 RayStateDerivative {
68 dx: 1.0,
69 dh: state.dh,
70 d2h: d2h / radius / radius,
71 }
72 }
73
74 pub(crate) fn calc_derivative_flat(&self, state: &RayState) -> RayStateDerivative {
75 let dh = state.dh;
76 let h = state.h;
77
78 let nr = self.n(h);
79 let dnr = self.dn(h);
80
81 let d2h = dnr / nr * (1.0 + dh * dh);
82
83 RayStateDerivative { dx: 1.0, dh, d2h }
84 }
85
86 pub fn cast_ray<'a>(
95 &'a self,
96 start_h: f64,
97 start_ang: f64,
98 straight: bool,
99 ) -> Box<dyn Path<'a> + 'a> {
100 match (straight, self.shape) {
101 (true, EarthShape::Flat) => Box::new(flat::Line::from_h_ang(start_h, start_ang)),
102 (true, EarthShape::Spherical { .. }) => {
103 Box::new(spherical::Line::from_h_ang(self, start_h, start_ang))
104 }
105 (false, EarthShape::Flat) => Box::new(flat::Ray::from_h_ang(self, start_h, start_ang)),
106 (false, EarthShape::Spherical { .. }) => {
107 Box::new(spherical::Ray::from_h_ang(self, start_h, start_ang))
108 }
109 }
110 }
111
112 pub fn cast_ray_stepper<'a>(
121 &'a self,
122 start_h: f64,
123 start_ang: f64,
124 straight: bool,
125 ) -> Box<dyn PathStepper<Item = RayState> + 'a> {
126 match (straight, self.shape) {
127 (true, EarthShape::Flat) => {
128 flat::Line::from_h_ang(start_h, start_ang).into_path_stepper()
129 }
130 (true, EarthShape::Spherical { .. }) => {
131 spherical::Line::from_h_ang(self, start_h, start_ang).into_path_stepper()
132 }
133 (false, EarthShape::Flat) => {
134 flat::Ray::from_h_ang(self, start_h, start_ang).into_path_stepper()
135 }
136 (false, EarthShape::Spherical { .. }) => {
137 spherical::Ray::from_h_ang(self, start_h, start_ang).into_path_stepper()
138 }
139 }
140 }
141
142 pub fn cast_ray_target<'a>(
155 &'a self,
156 start_h: f64,
157 tgt_h: f64,
158 tgt_dist: f64,
159 straight: bool,
160 ) -> Box<dyn Path<'a> + 'a> {
161 if straight {
162 match self.shape {
163 EarthShape::Flat => {
164 Box::new(flat::Line::from_two_points(start_h, 0.0, tgt_h, tgt_dist))
165 }
166 EarthShape::Spherical { radius } => Box::new(spherical::Line::from_two_points(
167 self,
168 start_h,
169 0.0,
170 tgt_h,
171 tgt_dist / radius,
172 )),
173 }
174 } else {
175 let (mut min_ang, mut max_ang) = (-1.5, 1.5);
176 let epsilon = 1e-9;
177
178 while max_ang - min_ang > epsilon {
179 let cur_ang = 0.5 * (min_ang + max_ang);
180 let ray = self.cast_ray(start_h, cur_ang, straight);
181 let h = ray.h_at_dist(tgt_dist);
182 if h > tgt_h {
183 max_ang = cur_ang;
184 } else {
185 min_ang = cur_ang;
186 }
187 }
188
189 self.cast_ray(start_h, 0.5 * (min_ang + max_ang), straight)
190 }
191 }
192}