|
1 | 1 | use astronomy_engine_bindings::{ |
2 | 2 | Astronomy_Ecliptic, Astronomy_Equator, Astronomy_HelioVector, Astronomy_Horizon, |
3 | | - Astronomy_Illumination, astro_aberration_t_ABERRATION, astro_body_t_BODY_EARTH, |
4 | | - astro_body_t_BODY_JUPITER, astro_body_t_BODY_MARS, astro_body_t_BODY_MERCURY, |
5 | | - astro_body_t_BODY_MOON, astro_body_t_BODY_NEPTUNE, astro_body_t_BODY_SATURN, |
6 | | - astro_body_t_BODY_SUN, astro_body_t_BODY_URANUS, astro_body_t_BODY_VENUS, |
| 3 | + Astronomy_Illumination, Astronomy_SearchRiseSetEx, astro_aberration_t_ABERRATION, |
| 4 | + astro_body_t_BODY_EARTH, astro_body_t_BODY_JUPITER, astro_body_t_BODY_MARS, |
| 5 | + astro_body_t_BODY_MERCURY, astro_body_t_BODY_MOON, astro_body_t_BODY_NEPTUNE, |
| 6 | + astro_body_t_BODY_SATURN, astro_body_t_BODY_SUN, astro_body_t_BODY_URANUS, |
| 7 | + astro_body_t_BODY_VENUS, astro_direction_t_DIRECTION_RISE, astro_direction_t_DIRECTION_SET, |
7 | 8 | astro_equator_date_t_EQUATOR_OF_DATE, astro_observer_t, astro_refraction_t_REFRACTION_NORMAL, |
8 | | - astro_status_t_ASTRO_SUCCESS, |
| 9 | + astro_status_t_ASTRO_SUCCESS, astro_time_t, |
9 | 10 | }; |
10 | 11 | use chrono::{DateTime, Duration, TimeZone, Utc}; |
11 | 12 | use stellui::astro::{ |
@@ -236,8 +237,95 @@ pub struct AlmanacTrack { |
236 | 237 | pub name: &'static str, |
237 | 238 | pub symbol: &'static str, |
238 | 239 | pub color_rgb: (u8, u8, u8), |
239 | | - /// altitude in degrees (-90..90) for each step; index 0 = UTC midnight |
| 240 | + /// altitude in degrees (-90..90) for each step; index 0 = local midnight |
240 | 241 | pub altitudes: [f64; ALMANAC_STEPS], |
| 242 | + pub rise: Option<DateTime<Utc>>, |
| 243 | + pub transit: Option<DateTime<Utc>>, |
| 244 | + pub transit_alt: Option<f64>, |
| 245 | + pub set: Option<DateTime<Utc>>, |
| 246 | +} |
| 247 | + |
| 248 | +fn astro_time_to_utc(t: astro_time_t) -> DateTime<Utc> { |
| 249 | + // t.ut = days since J2000.0 (2000-01-01 12:00:00 UTC) |
| 250 | + use chrono::TimeZone; |
| 251 | + let j2000 = Utc.with_ymd_and_hms(2000, 1, 1, 12, 0, 0).unwrap(); |
| 252 | + let micros = (t.ut * 86_400.0 * 1_000_000.0) as i64; |
| 253 | + j2000 + Duration::microseconds(micros) |
| 254 | +} |
| 255 | + |
| 256 | +#[allow(clippy::type_complexity)] |
| 257 | +fn compute_rise_set_transit( |
| 258 | + body: i32, |
| 259 | + observer: astro_observer_t, |
| 260 | + day_start: DateTime<Utc>, |
| 261 | + altitudes: &[f64; ALMANAC_STEPS], |
| 262 | + height: f64, |
| 263 | +) -> (Option<DateTime<Utc>>, Option<DateTime<Utc>>, Option<DateTime<Utc>>, Option<f64>) { |
| 264 | + let all_up = altitudes.iter().all(|&a| a > 0.0); |
| 265 | + let all_down = altitudes.iter().all(|&a| a <= 0.0); |
| 266 | + |
| 267 | + // Transit: peak of altitude array with parabolic interpolation |
| 268 | + let peak_idx = altitudes |
| 269 | + .iter() |
| 270 | + .enumerate() |
| 271 | + .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()) |
| 272 | + .map(|(i, _)| i) |
| 273 | + .unwrap_or(0); |
| 274 | + |
| 275 | + let transit_alt = altitudes[peak_idx]; |
| 276 | + |
| 277 | + let offset = if peak_idx > 0 && peak_idx < ALMANAC_STEPS - 1 { |
| 278 | + let y0 = altitudes[peak_idx - 1]; |
| 279 | + let y1 = altitudes[peak_idx]; |
| 280 | + let y2 = altitudes[peak_idx + 1]; |
| 281 | + let denom = 2.0 * (2.0 * y1 - y0 - y2); |
| 282 | + if denom.abs() > 1e-9 { (y0 - y2) / denom } else { 0.0 } |
| 283 | + } else { |
| 284 | + 0.0 |
| 285 | + }; |
| 286 | + |
| 287 | + let transit_mins = (peak_idx as f64 + offset) * 15.0; |
| 288 | + let transit = Some(day_start + Duration::seconds((transit_mins * 60.0) as i64)); |
| 289 | + |
| 290 | + if all_up || all_down { |
| 291 | + return (None, transit, None, Some(transit_alt)); |
| 292 | + } |
| 293 | + |
| 294 | + let start_time = astro_time_from_datetime(day_start); |
| 295 | + |
| 296 | + let rise = unsafe { |
| 297 | + let result = Astronomy_SearchRiseSetEx( |
| 298 | + body, |
| 299 | + observer, |
| 300 | + astro_direction_t_DIRECTION_RISE, |
| 301 | + start_time, |
| 302 | + 1.0, |
| 303 | + height, |
| 304 | + ); |
| 305 | + if result.status == astro_status_t_ASTRO_SUCCESS { |
| 306 | + Some(astro_time_to_utc(result.time)) |
| 307 | + } else { |
| 308 | + None |
| 309 | + } |
| 310 | + }; |
| 311 | + |
| 312 | + let set = unsafe { |
| 313 | + let result = Astronomy_SearchRiseSetEx( |
| 314 | + body, |
| 315 | + observer, |
| 316 | + astro_direction_t_DIRECTION_SET, |
| 317 | + start_time, |
| 318 | + 1.0, |
| 319 | + height, |
| 320 | + ); |
| 321 | + if result.status == astro_status_t_ASTRO_SUCCESS { |
| 322 | + Some(astro_time_to_utc(result.time)) |
| 323 | + } else { |
| 324 | + None |
| 325 | + } |
| 326 | + }; |
| 327 | + |
| 328 | + (rise, transit, set, Some(transit_alt)) |
241 | 329 | } |
242 | 330 |
|
243 | 331 | pub struct AlmanacInfo { |
@@ -305,7 +393,9 @@ pub fn compute_almanac(lat: f64, lon: f64, height: f64, datetime: DateTime<Utc>, |
305 | 393 | } |
306 | 394 | }; |
307 | 395 | } |
308 | | - AlmanacTrack { name, symbol, color_rgb, altitudes } |
| 396 | + let (rise, transit, set, transit_alt) = |
| 397 | + compute_rise_set_transit(body, observer, day_start, &altitudes, height); |
| 398 | + AlmanacTrack { name, symbol, color_rgb, altitudes, rise, transit, transit_alt, set } |
309 | 399 | }).collect(); |
310 | 400 |
|
311 | 401 | AlmanacInfo { tracks, current_step } |
|
0 commit comments