1#[cfg(all(test, sidereon_repo_tests))]
33mod tests;
34
35mod ocean;
36mod pole;
37pub use ocean::{
38 ocean_tide_loading, parse_ocean_loading_blq_block, parse_ocean_loading_blq_blocks,
39 OceanLoadingBlq, OceanLoadingBlqBlock, OceanTideConstituent, NUM_OCEAN_CONSTITUENTS,
40 OCEAN_LOADING_CONSTITUENTS,
41};
42pub use pole::solid_earth_pole_tide;
43
44use crate::astro::bodies::{sun_moon_ecef_with_polar_motion, SunMoonError};
45use crate::astro::constants::models::iers::SOLID_TIDE_EARTH_RADIUS_M;
46use crate::astro::constants::time::{
47 DAYS_PER_JULIAN_CENTURY, J2000_JD, SECONDS_PER_DAY, TT_MINUS_TAI_S,
48};
49use crate::astro::constants::units::{ARCSEC_TO_RAD, DEG_TO_RAD, KM_TO_M};
50use crate::astro::frames::transforms::{FrameTransformError, PolarMotion};
51use crate::astro::math::vec3::{dot3_ref as dot, norm3_ref as norm8};
52use crate::astro::time::{CoverageError, TimeScaleInputErrorKind, TimeScales};
53use crate::frame::{geodetic_to_itrf, ItrfPositionM, Wgs84Geodetic};
54use crate::validate::{self, FieldError};
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum TideInputErrorKind {
58 Missing,
59 NonFinite,
60 NotPositive,
61 Negative,
62 OutOfRange,
63 FloatParse,
64 IntParse,
65 InvalidCivilDate,
66 InvalidCivilTime,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum BlqParseErrorKind {
71 Empty,
72 MissingStation,
73 MissingCoefficientRows {
74 station: String,
75 expected: usize,
76 found: usize,
77 },
78 TooManyCoefficientRows {
79 station: String,
80 },
81 WrongColumnCount {
82 expected: usize,
83 found: usize,
84 },
85 InvalidNumber {
86 token: String,
87 },
88 NonFiniteNumber {
89 token: String,
90 },
91 UnsupportedConstituent {
92 constituent: String,
93 },
94 DuplicateConstituent {
95 constituent: String,
96 },
97 MultipleBlocks {
98 found: usize,
99 },
100}
101
102impl core::fmt::Display for BlqParseErrorKind {
103 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 match self {
105 Self::Empty => f.write_str("empty BLQ block"),
106 Self::MissingStation => f.write_str("missing station identifier"),
107 Self::MissingCoefficientRows {
108 station,
109 expected,
110 found,
111 } => write!(
112 f,
113 "station {station} has {found} coefficient rows, expected {expected}"
114 ),
115 Self::TooManyCoefficientRows { station } => {
116 write!(f, "station {station} has more than 6 coefficient rows")
117 }
118 Self::WrongColumnCount { expected, found } => {
119 write!(
120 f,
121 "coefficient row has {found} columns, expected {expected}"
122 )
123 }
124 Self::InvalidNumber { token } => write!(f, "invalid number {token:?}"),
125 Self::NonFiniteNumber { token } => write!(f, "non-finite number {token:?}"),
126 Self::UnsupportedConstituent { constituent } => {
127 write!(f, "unsupported constituent {constituent}")
128 }
129 Self::DuplicateConstituent { constituent } => {
130 write!(f, "duplicate constituent {constituent}")
131 }
132 Self::MultipleBlocks { found } => {
133 write!(f, "expected one BLQ station block, found {found}")
134 }
135 }
136 }
137}
138
139impl core::fmt::Display for TideInputErrorKind {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 f.write_str(match self {
142 Self::Missing => "missing",
143 Self::NonFinite => "not finite",
144 Self::NotPositive => "not positive",
145 Self::Negative => "negative",
146 Self::OutOfRange => "out of range",
147 Self::FloatParse => "invalid float",
148 Self::IntParse => "invalid integer",
149 Self::InvalidCivilDate => "invalid civil date",
150 Self::InvalidCivilTime => "invalid civil time",
151 })
152 }
153}
154
155impl From<&FieldError> for TideInputErrorKind {
156 fn from(error: &FieldError) -> Self {
157 match error {
158 FieldError::Missing { .. } => Self::Missing,
159 FieldError::NonFinite { .. } => Self::NonFinite,
160 FieldError::NotPositive { .. } => Self::NotPositive,
161 FieldError::Negative { .. } => Self::Negative,
162 FieldError::OutOfRange { .. } => Self::OutOfRange,
163 FieldError::FloatParse { .. } => Self::FloatParse,
164 FieldError::IntParse { .. } => Self::IntParse,
165 FieldError::InvalidCivilDate { .. } => Self::InvalidCivilDate,
166 FieldError::InvalidCivilTime { .. } => Self::InvalidCivilTime,
167 }
168 }
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
172pub enum TideError {
173 #[error("invalid solid-earth tide input {field}: {kind}")]
174 InvalidInput {
175 field: &'static str,
176 kind: TideInputErrorKind,
177 },
178 #[error("station displacement time-scale conversion failed: {0}")]
179 TimeScale(#[from] CoverageError),
180 #[error("station displacement frame transform failed: {0}")]
181 FrameTransform(#[from] FrameTransformError),
182 #[error("station displacement Sun/Moon evaluation failed: {0}")]
183 SunMoon(#[from] SunMoonError),
184 #[error("missing station displacement input {field}")]
185 MissingInput { field: &'static str },
186 #[error("invalid BLQ block at line {line}: {kind}")]
187 BlqParse {
188 line: usize,
189 kind: BlqParseErrorKind,
190 },
191}
192
193fn invalid_tide_input(error: FieldError) -> TideError {
194 TideError::InvalidInput {
195 field: error.field(),
196 kind: (&error).into(),
197 }
198}
199
200fn map_time_input(error: CoverageError) -> TideError {
201 match error {
202 CoverageError::InvalidInput { field, kind } => TideError::InvalidInput {
203 field,
204 kind: tide_kind_from_time_kind(kind),
205 },
206 other => TideError::TimeScale(other),
207 }
208}
209
210fn tide_kind_from_time_kind(kind: TimeScaleInputErrorKind) -> TideInputErrorKind {
211 match kind {
212 TimeScaleInputErrorKind::Missing => TideInputErrorKind::Missing,
213 TimeScaleInputErrorKind::NonFinite => TideInputErrorKind::NonFinite,
214 TimeScaleInputErrorKind::NotPositive => TideInputErrorKind::NotPositive,
215 TimeScaleInputErrorKind::Negative => TideInputErrorKind::Negative,
216 TimeScaleInputErrorKind::OutOfRange => TideInputErrorKind::OutOfRange,
217 TimeScaleInputErrorKind::FloatParse => TideInputErrorKind::FloatParse,
218 TimeScaleInputErrorKind::IntParse => TideInputErrorKind::IntParse,
219 TimeScaleInputErrorKind::InvalidCivilDate => TideInputErrorKind::InvalidCivilDate,
220 TimeScaleInputErrorKind::InvalidCivilTime => TideInputErrorKind::InvalidCivilTime,
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq)]
226pub enum StationDisplacementPosition {
227 Ecef(ItrfPositionM),
229 Geodetic(Wgs84Geodetic),
232}
233
234impl From<ItrfPositionM> for StationDisplacementPosition {
235 fn from(value: ItrfPositionM) -> Self {
236 Self::Ecef(value)
237 }
238}
239
240impl From<Wgs84Geodetic> for StationDisplacementPosition {
241 fn from(value: Wgs84Geodetic) -> Self {
242 Self::Geodetic(value)
243 }
244}
245
246impl StationDisplacementPosition {
247 pub fn from_ecef_m(position_m: [f64; 3]) -> Result<Self, TideError> {
249 let position =
250 ItrfPositionM::new(position_m[0], position_m[1], position_m[2]).map_err(|error| {
251 match error {
252 crate::frame::FrameValueError::InvalidInput { field, reason: _ } => {
253 TideError::InvalidInput {
254 field,
255 kind: TideInputErrorKind::NonFinite,
256 }
257 }
258 }
259 })?;
260 Ok(Self::Ecef(position))
261 }
262
263 fn ecef_m(self) -> Result<[f64; 3], TideError> {
264 match self {
265 Self::Ecef(position) => Ok(position.as_array()),
266 Self::Geodetic(position) => Ok(geodetic_to_itrf(position)?.as_array()),
267 }
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq)]
273pub struct StationPolarMotion {
274 pub xp_arcsec: f64,
275 pub yp_arcsec: f64,
276}
277
278impl StationPolarMotion {
279 pub const fn from_arcseconds(xp_arcsec: f64, yp_arcsec: f64) -> Self {
280 Self {
281 xp_arcsec,
282 yp_arcsec,
283 }
284 }
285
286 fn polar_motion(self) -> Result<PolarMotion, TideError> {
287 Ok(PolarMotion::from_radians(
288 self.xp_arcsec * ARCSEC_TO_RAD,
289 self.yp_arcsec * ARCSEC_TO_RAD,
290 )?)
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq)]
296pub struct StationDisplacementEpoch {
297 pub year: i32,
298 pub month: u8,
299 pub day: u8,
300 pub hour: u8,
301 pub minute: u8,
302 pub second: f64,
303 pub polar_motion: Option<StationPolarMotion>,
306}
307
308impl StationDisplacementEpoch {
309 pub const fn from_utc(
310 year: i32,
311 month: u8,
312 day: u8,
313 hour: u8,
314 minute: u8,
315 second: f64,
316 ) -> Self {
317 Self {
318 year,
319 month,
320 day,
321 hour,
322 minute,
323 second,
324 polar_motion: None,
325 }
326 }
327
328 pub const fn with_polar_motion_arcsec(mut self, xp_arcsec: f64, yp_arcsec: f64) -> Self {
329 self.polar_motion = Some(StationPolarMotion::from_arcseconds(xp_arcsec, yp_arcsec));
330 self
331 }
332
333 fn time_scales(self) -> Result<TimeScales, TideError> {
334 TimeScales::from_utc(
335 self.year,
336 i32::from(self.month),
337 i32::from(self.day),
338 i32::from(self.hour),
339 i32::from(self.minute),
340 self.second,
341 )
342 .map_err(map_time_input)
343 }
344
345 fn validate_utc(self) -> Result<(), TideError> {
346 validate::civil_datetime_with_second_policy(
347 i64::from(self.year),
348 i64::from(self.month),
349 i64::from(self.day),
350 i64::from(self.hour),
351 i64::from(self.minute),
352 self.second,
353 validate::CivilSecondPolicy::Continuous,
354 )
355 .map(|_| ())
356 .map_err(invalid_tide_input)
357 }
358
359 fn fractional_hour(self) -> f64 {
360 f64::from(self.hour) + f64::from(self.minute) / 60.0 + self.second / 3600.0
361 }
362}
363
364#[derive(Debug, Clone, Copy, PartialEq)]
366pub struct StationDisplacementOptions<'a> {
367 pub solid_earth_tide: bool,
369 pub pole_tide: bool,
372 pub ocean_loading: Option<&'a OceanLoadingBlq>,
374}
375
376impl Default for StationDisplacementOptions<'_> {
377 fn default() -> Self {
378 Self {
379 solid_earth_tide: true,
380 pole_tide: false,
381 ocean_loading: None,
382 }
383 }
384}
385
386#[derive(Debug, Clone, Copy, PartialEq)]
388pub struct StationDisplacement {
389 pub ecef_m: [f64; 3],
391 pub solid_earth_tide_ecef_m: Option<[f64; 3]>,
392 pub pole_tide_ecef_m: Option<[f64; 3]>,
393 pub ocean_loading_ecef_m: Option<[f64; 3]>,
394}
395
396impl StationDisplacement {
397 fn zero() -> Self {
398 Self {
399 ecef_m: [0.0; 3],
400 solid_earth_tide_ecef_m: None,
401 pole_tide_ecef_m: None,
402 ocean_loading_ecef_m: None,
403 }
404 }
405
406 fn add_component(total: &mut [f64; 3], component: [f64; 3]) {
407 for i in 0..3 {
408 total[i] += component[i];
409 }
410 }
411}
412
413pub fn station_displacement_ecef_m(
426 position: StationDisplacementPosition,
427 epoch: StationDisplacementEpoch,
428 options: StationDisplacementOptions<'_>,
429) -> Result<StationDisplacement, TideError> {
430 let receiver_ecef_m = position.ecef_m()?;
431 epoch.validate_utc()?;
432 let fhr = epoch.fractional_hour();
433 let mut displacement = StationDisplacement::zero();
434
435 if options.solid_earth_tide {
436 let ts = epoch.time_scales()?;
437 let polar_motion = epoch
438 .polar_motion
439 .map(StationPolarMotion::polar_motion)
440 .transpose()?
441 .unwrap_or_default();
442 let sun_moon = sun_moon_ecef_with_polar_motion(&ts, polar_motion)?;
443 let solid = solid_earth_tide(
444 &receiver_ecef_m,
445 epoch.year,
446 i32::from(epoch.month),
447 i32::from(epoch.day),
448 fhr,
449 &sun_moon.sun,
450 &sun_moon.moon,
451 )?;
452 StationDisplacement::add_component(&mut displacement.ecef_m, solid);
453 displacement.solid_earth_tide_ecef_m = Some(solid);
454 }
455
456 if options.pole_tide {
457 let polar = epoch.polar_motion.ok_or(TideError::MissingInput {
458 field: "polar motion",
459 })?;
460 let pole = solid_earth_pole_tide(
461 &receiver_ecef_m,
462 epoch.year,
463 i32::from(epoch.month),
464 i32::from(epoch.day),
465 fhr,
466 polar.xp_arcsec,
467 polar.yp_arcsec,
468 )?;
469 StationDisplacement::add_component(&mut displacement.ecef_m, pole);
470 displacement.pole_tide_ecef_m = Some(pole);
471 }
472
473 if let Some(blq) = options.ocean_loading {
474 let ocean = ocean_tide_loading(
475 &receiver_ecef_m,
476 epoch.year,
477 i32::from(epoch.month),
478 i32::from(epoch.day),
479 fhr,
480 blq,
481 )?;
482 StationDisplacement::add_component(&mut displacement.ecef_m, ocean);
483 displacement.ocean_loading_ecef_m = Some(ocean);
484 }
485
486 Ok(displacement)
487}
488
489pub fn station_displacement_ecef_m_batch(
493 position: StationDisplacementPosition,
494 epochs: &[StationDisplacementEpoch],
495 options: StationDisplacementOptions<'_>,
496) -> Vec<Result<StationDisplacement, TideError>> {
497 epochs
498 .iter()
499 .map(|&epoch| station_displacement_ecef_m(position, epoch, options))
500 .collect()
501}
502
503pub fn solid_earth_tide(
519 xsta: &[f64; 3],
520 year: i32,
521 month: i32,
522 day: i32,
523 fhr: f64,
524 xsun: &[f64; 3],
525 xmon: &[f64; 3],
526) -> Result<[f64; 3], TideError> {
527 validate_tide_domain(xsta, year, month, day, fhr, xsun, xmon)?;
528 Ok(solid_earth_tide_unchecked(
529 xsta, year, month, day, fhr, xsun, xmon,
530 ))
531}
532
533fn validate_tide_domain(
534 xsta: &[f64; 3],
535 year: i32,
536 month: i32,
537 day: i32,
538 fhr: f64,
539 xsun: &[f64; 3],
540 xmon: &[f64; 3],
541) -> Result<(), TideError> {
542 validate::finite_vec3(*xsta, "station position").map_err(invalid_tide_input)?;
543 validate::civil_datetime_with_second_policy(
544 i64::from(year),
545 i64::from(month),
546 i64::from(day),
547 0,
548 0,
549 0.0,
550 validate::CivilSecondPolicy::Continuous,
551 )
552 .map_err(invalid_tide_input)?;
553 validate::finite_in_range_exclusive_upper(fhr, 0.0, 24.0, "fractional hour")
554 .map_err(invalid_tide_input)?;
555 validate::finite_vec3(*xsun, "sun position").map_err(invalid_tide_input)?;
556 validate::finite_vec3(*xmon, "moon position").map_err(invalid_tide_input)?;
557
558 validate::finite_positive(norm8(xsta), "station radius").map_err(invalid_tide_input)?;
559 let station_horizontal_radius = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt();
560 validate::finite_positive(station_horizontal_radius, "station horizontal radius")
561 .map_err(invalid_tide_input)?;
562 validate::finite_positive(norm8(xsun), "sun radius").map_err(invalid_tide_input)?;
563 validate::finite_positive(norm8(xmon), "moon radius").map_err(invalid_tide_input)?;
564
565 Ok(())
566}
567
568fn solid_earth_tide_unchecked(
569 xsta: &[f64; 3],
570 year: i32,
571 month: i32,
572 day: i32,
573 fhr: f64,
574 xsun: &[f64; 3],
575 xmon: &[f64; 3],
576) -> [f64; 3] {
577 const H20: f64 = 0.6078;
579 const L20: f64 = 0.0847;
580 const H3: f64 = 0.292;
581 const L3: f64 = 0.015;
582
583 let rsta = norm8(xsta);
585 let rsun = norm8(xsun);
586 let rmon = norm8(xmon);
587 let scs = dot(xsta, xsun);
588 let scm = dot(xsta, xmon);
589 let scsun = scs / rsta / rsun;
590 let scmon = scm / rsta / rmon;
591
592 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
594 let h2 = H20 - 0.0006 * (1.0 - 3.0 / 2.0 * cosphi * cosphi);
595 let l2 = L20 + 0.0002 * (1.0 - 3.0 / 2.0 * cosphi * cosphi);
596
597 let p2sun = 3.0 * (h2 / 2.0 - l2) * scsun * scsun - h2 / 2.0;
599 let p2mon = 3.0 * (h2 / 2.0 - l2) * scmon * scmon - h2 / 2.0;
600
601 let p3sun = 5.0 / 2.0 * (H3 - 3.0 * L3) * scsun.powi(3) + 3.0 / 2.0 * (L3 - H3) * scsun;
603 let p3mon = 5.0 / 2.0 * (H3 - 3.0 * L3) * scmon.powi(3) + 3.0 / 2.0 * (L3 - H3) * scmon;
604
605 let x2sun = 3.0 * l2 * scsun;
607 let x2mon = 3.0 * l2 * scmon;
608 let x3sun = 3.0 * L3 / 2.0 * (5.0 * scsun * scsun - 1.0);
609 let x3mon = 3.0 * L3 / 2.0 * (5.0 * scmon * scmon - 1.0);
610
611 const MASS_RATIO_SUN: f64 = 332946.0482;
613 const MASS_RATIO_MOON: f64 = 0.0123000371;
614 const RE: f64 = SOLID_TIDE_EARTH_RADIUS_M;
615 let fac2sun = MASS_RATIO_SUN * RE * (RE / rsun).powi(3);
616 let fac2mon = MASS_RATIO_MOON * RE * (RE / rmon).powi(3);
617 let fac3sun = fac2sun * (RE / rsun);
618 let fac3mon = fac2mon * (RE / rmon);
619
620 let mut dxtide = [0.0_f64; 3];
622 for i in 0..3 {
623 dxtide[i] = fac2sun * (x2sun * xsun[i] / rsun + p2sun * xsta[i] / rsta)
624 + fac2mon * (x2mon * xmon[i] / rmon + p2mon * xsta[i] / rsta)
625 + fac3sun * (x3sun * xsun[i] / rsun + p3sun * xsta[i] / rsta)
626 + fac3mon * (x3mon * xmon[i] / rmon + p3mon * xsta[i] / rsta);
627 }
628
629 let c = st1idiu(xsta, xsun, xmon, fac2sun, fac2mon);
631 for i in 0..3 {
632 dxtide[i] += c[i];
633 }
634 let c = st1isem(xsta, xsun, xmon, fac2sun, fac2mon);
635 for i in 0..3 {
636 dxtide[i] += c[i];
637 }
638 let c = st1l1(xsta, xsun, xmon, fac2sun, fac2mon);
639 for i in 0..3 {
640 dxtide[i] += c[i];
641 }
642
643 let (jjm0, jjm1) = cal2jd(year, month, day);
645 let fhrd = fhr / 24.0;
646 let mut t = ((jjm0 - J2000_JD) + jjm1 + fhrd) / DAYS_PER_JULIAN_CENTURY;
647 let dtt = dat(year, month, day) + TT_MINUS_TAI_S;
648 t += dtt / (SECONDS_PER_DAY * DAYS_PER_JULIAN_CENTURY);
649
650 let c = step2diu(xsta, fhr, t);
651 for i in 0..3 {
652 dxtide[i] += c[i];
653 }
654 let c = step2lon(xsta, t);
655 for i in 0..3 {
656 dxtide[i] += c[i];
657 }
658
659 dxtide
663}
664
665fn st1idiu(
667 xsta: &[f64; 3],
668 xsun: &[f64; 3],
669 xmon: &[f64; 3],
670 fac2sun: f64,
671 fac2mon: f64,
672) -> [f64; 3] {
673 const DHI: f64 = -0.0025;
674 const DLI: f64 = -0.0007;
675 let rsta = norm8(xsta);
676 let sinphi = xsta[2] / rsta;
677 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
678 let cos2phi = cosphi * cosphi - sinphi * sinphi;
679 let sinla = xsta[1] / cosphi / rsta;
680 let cosla = xsta[0] / cosphi / rsta;
681 let rmon = norm8(xmon);
682 let rsun = norm8(xsun);
683
684 let drsun =
685 -3.0 * DHI * sinphi * cosphi * fac2sun * xsun[2] * (xsun[0] * sinla - xsun[1] * cosla)
686 / (rsun * rsun);
687 let drmon =
688 -3.0 * DHI * sinphi * cosphi * fac2mon * xmon[2] * (xmon[0] * sinla - xmon[1] * cosla)
689 / (rmon * rmon);
690 let dnsun = -3.0 * DLI * cos2phi * fac2sun * xsun[2] * (xsun[0] * sinla - xsun[1] * cosla)
691 / (rsun * rsun);
692 let dnmon = -3.0 * DLI * cos2phi * fac2mon * xmon[2] * (xmon[0] * sinla - xmon[1] * cosla)
693 / (rmon * rmon);
694 let desun = -3.0 * DLI * sinphi * fac2sun * xsun[2] * (xsun[0] * cosla + xsun[1] * sinla)
695 / (rsun * rsun);
696 let demon = -3.0 * DLI * sinphi * fac2mon * xmon[2] * (xmon[0] * cosla + xmon[1] * sinla)
697 / (rmon * rmon);
698
699 let dr = drsun + drmon;
700 let dn = dnsun + dnmon;
701 let de = desun + demon;
702
703 [
704 dr * cosla * cosphi - de * sinla - dn * sinphi * cosla,
705 dr * sinla * cosphi + de * cosla - dn * sinphi * sinla,
706 dr * sinphi + dn * cosphi,
707 ]
708}
709
710fn st1isem(
712 xsta: &[f64; 3],
713 xsun: &[f64; 3],
714 xmon: &[f64; 3],
715 fac2sun: f64,
716 fac2mon: f64,
717) -> [f64; 3] {
718 const DHI: f64 = -0.0022;
719 const DLI: f64 = -0.0007;
720 let rsta = norm8(xsta);
721 let sinphi = xsta[2] / rsta;
722 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
723 let sinla = xsta[1] / cosphi / rsta;
724 let cosla = xsta[0] / cosphi / rsta;
725 let costwola = cosla * cosla - sinla * sinla;
726 let sintwola = 2.0 * cosla * sinla;
727 let rmon = norm8(xmon);
728 let rsun = norm8(xsun);
729
730 let drsun = -3.0 / 4.0
731 * DHI
732 * cosphi
733 * cosphi
734 * fac2sun
735 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * sintwola - 2.0 * xsun[0] * xsun[1] * costwola)
736 / (rsun * rsun);
737 let drmon = -3.0 / 4.0
738 * DHI
739 * cosphi
740 * cosphi
741 * fac2mon
742 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * sintwola - 2.0 * xmon[0] * xmon[1] * costwola)
743 / (rmon * rmon);
744 let dnsun = 3.0 / 2.0
745 * DLI
746 * sinphi
747 * cosphi
748 * fac2sun
749 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * sintwola - 2.0 * xsun[0] * xsun[1] * costwola)
750 / (rsun * rsun);
751 let dnmon = 3.0 / 2.0
752 * DLI
753 * sinphi
754 * cosphi
755 * fac2mon
756 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * sintwola - 2.0 * xmon[0] * xmon[1] * costwola)
757 / (rmon * rmon);
758 let desun = -3.0 / 2.0
759 * DLI
760 * cosphi
761 * fac2sun
762 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * costwola + 2.0 * xsun[0] * xsun[1] * sintwola)
763 / (rsun * rsun);
764 let demon = -3.0 / 2.0
765 * DLI
766 * cosphi
767 * fac2mon
768 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * costwola + 2.0 * xmon[0] * xmon[1] * sintwola)
769 / (rmon * rmon);
770
771 let dr = drsun + drmon;
772 let dn = dnsun + dnmon;
773 let de = desun + demon;
774
775 [
776 dr * cosla * cosphi - de * sinla - dn * sinphi * cosla,
777 dr * sinla * cosphi + de * cosla - dn * sinphi * sinla,
778 dr * sinphi + dn * cosphi,
779 ]
780}
781
782fn st1l1(
784 xsta: &[f64; 3],
785 xsun: &[f64; 3],
786 xmon: &[f64; 3],
787 fac2sun: f64,
788 fac2mon: f64,
789) -> [f64; 3] {
790 const L1D: f64 = 0.0012;
791 const L1SD: f64 = 0.0024;
792 let rsta = norm8(xsta);
793 let sinphi = xsta[2] / rsta;
794 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
795 let sinla = xsta[1] / cosphi / rsta;
796 let cosla = xsta[0] / cosphi / rsta;
797 let rmon = norm8(xmon);
798 let rsun = norm8(xsun);
799
800 let mut l1 = L1D;
802 let dnsun = -l1 * sinphi * sinphi * fac2sun * xsun[2] * (xsun[0] * cosla + xsun[1] * sinla)
803 / (rsun * rsun);
804 let dnmon = -l1 * sinphi * sinphi * fac2mon * xmon[2] * (xmon[0] * cosla + xmon[1] * sinla)
805 / (rmon * rmon);
806 let desun = l1
807 * sinphi
808 * (cosphi * cosphi - sinphi * sinphi)
809 * fac2sun
810 * xsun[2]
811 * (xsun[0] * sinla - xsun[1] * cosla)
812 / (rsun * rsun);
813 let demon = l1
814 * sinphi
815 * (cosphi * cosphi - sinphi * sinphi)
816 * fac2mon
817 * xmon[2]
818 * (xmon[0] * sinla - xmon[1] * cosla)
819 / (rmon * rmon);
820
821 let de = 3.0 * (desun + demon);
822 let dn = 3.0 * (dnsun + dnmon);
823
824 let mut xcorsta = [
825 -de * sinla - dn * sinphi * cosla,
826 de * cosla - dn * sinphi * sinla,
827 dn * cosphi,
828 ];
829
830 l1 = L1SD;
832 let costwola = cosla * cosla - sinla * sinla;
833 let sintwola = 2.0 * cosla * sinla;
834
835 let dnsun = -l1 / 2.0
836 * sinphi
837 * cosphi
838 * fac2sun
839 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * costwola + 2.0 * xsun[0] * xsun[1] * sintwola)
840 / (rsun * rsun);
841 let dnmon = -l1 / 2.0
842 * sinphi
843 * cosphi
844 * fac2mon
845 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * costwola + 2.0 * xmon[0] * xmon[1] * sintwola)
846 / (rmon * rmon);
847 let desun = -l1 / 2.0
848 * sinphi
849 * sinphi
850 * cosphi
851 * fac2sun
852 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * sintwola - 2.0 * xsun[0] * xsun[1] * costwola)
853 / (rsun * rsun);
854 let demon = -l1 / 2.0
855 * sinphi
856 * sinphi
857 * cosphi
858 * fac2mon
859 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * sintwola - 2.0 * xmon[0] * xmon[1] * costwola)
860 / (rmon * rmon);
861
862 let de = 3.0 * (desun + demon);
863 let dn = 3.0 * (dnsun + dnmon);
864
865 xcorsta[0] += -de * sinla - dn * sinphi * cosla;
866 xcorsta[1] += de * cosla - dn * sinphi * sinla;
867 xcorsta[2] += dn * cosphi;
868 xcorsta
869}
870
871fn step2diu(xsta: &[f64; 3], fhr: f64, t: f64) -> [f64; 3] {
874 #[rustfmt::skip]
876 const DATDI: [[f64; 9]; 31] = [
877 [-3.0, 0.0, 2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
878 [-3.0, 2.0, 0.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
879 [-2.0, 0.0, 1.0, -1.0, 0.0, -0.02, 0.0, 0.0, 0.0],
880 [-2.0, 0.0, 1.0, 0.0, 0.0, -0.08, 0.0, -0.01, 0.01],
881 [-2.0, 2.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
882 [-1.0, 0.0, 0.0, -1.0, 0.0, -0.10, 0.0, 0.0, 0.0],
883 [-1.0, 0.0, 0.0, 0.0, 0.0, -0.51, 0.0, -0.02, 0.03],
884 [-1.0, 2.0, 0.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
885 [0.0, -2.0, 1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
886 [0.0, 0.0, -1.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0],
887 [0.0, 0.0, 1.0, 0.0, 0.0, 0.06, 0.0, 0.0, 0.0],
888 [0.0, 0.0, 1.0, 1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
889 [0.0, 2.0, -1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
890 [1.0, -3.0, 0.0, 0.0, 1.0, -0.06, 0.0, 0.0, 0.0],
891 [1.0, -2.0, 0.0, -1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
892 [1.0, -2.0, 0.0, 0.0, 0.0, -1.23, -0.07, 0.06, 0.01],
893 [1.0, -1.0, 0.0, 0.0, -1.0, 0.02, 0.0, 0.0, 0.0],
894 [1.0, -1.0, 0.0, 0.0, 1.0, 0.04, 0.0, 0.0, 0.0],
895 [1.0, 0.0, 0.0, -1.0, 0.0, -0.22, 0.01, 0.01, 0.0],
896 [1.0, 0.0, 0.0, 0.0, 0.0, 12.00, -0.80, -0.67, -0.03],
897 [1.0, 0.0, 0.0, 1.0, 0.0, 1.73, -0.12, -0.10, 0.0],
898 [1.0, 0.0, 0.0, 2.0, 0.0, -0.04, 0.0, 0.0, 0.0],
899 [1.0, 1.0, 0.0, 0.0, -1.0, -0.50, -0.01, 0.03, 0.0],
900 [1.0, 1.0, 0.0, 0.0, 1.0, 0.01, 0.0, 0.0, 0.0],
901 [0.0, 1.0, 0.0, 1.0, -1.0, -0.01, 0.0, 0.0, 0.0],
902 [1.0, 2.0, -2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
903 [1.0, 2.0, 0.0, 0.0, 0.0, -0.11, 0.01, 0.01, 0.0],
904 [2.0, -2.0, 1.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
905 [2.0, 0.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
906 [3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
907 [3.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
908 ];
909 let mut s = 218.31664563 + (481267.88194 + (-0.0014663889 + 0.00000185139 * t) * t) * t;
910 let mut tau = fhr * 15.0
911 + 280.4606184
912 + (36000.7700536 + (0.00038793 + -0.0000000258 * t) * t) * t
913 + (-s);
914 let pr = (1.396971278 + (0.000308889 + (0.000000021 + 0.000000007 * t) * t) * t) * t;
915 s += pr;
916 let mut h = 280.46645
917 + (36000.7697489 + (0.00030322222 + (0.000000020 + -0.00000000654 * t) * t) * t) * t;
918 let mut p = 83.35324312
919 + (4069.01363525 + (-0.01032172222 + (-0.0000124991 + 0.00000005263 * t) * t) * t) * t;
920 let mut zns = 234.95544499
921 + (1934.13626197 + (-0.00207561111 + (-0.00000213944 + 0.00000001650 * t) * t) * t) * t;
922 let mut ps = 282.93734098
923 + (1.71945766667 + (0.00045688889 + (-0.00000001778 + -0.00000000334 * t) * t) * t) * t;
924
925 s = s.rem_euclid(360.0);
926 tau = tau.rem_euclid(360.0);
927 h = h.rem_euclid(360.0);
928 p = p.rem_euclid(360.0);
929 zns = zns.rem_euclid(360.0);
930 ps = ps.rem_euclid(360.0);
931
932 let rsta = (xsta[0] * xsta[0] + xsta[1] * xsta[1] + xsta[2] * xsta[2]).sqrt();
933 let sinphi = xsta[2] / rsta;
934 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
935 let cosla = xsta[0] / cosphi / rsta;
936 let sinla = xsta[1] / cosphi / rsta;
937 let zla = xsta[1].atan2(xsta[0]);
938
939 let mut xcorsta = [0.0_f64; 3];
940 for w in &DATDI {
941 let thetaf = (tau + w[0] * s + w[1] * h + w[2] * p + w[3] * zns + w[4] * ps) * DEG_TO_RAD;
942 let dr = w[5] * 2.0 * sinphi * cosphi * (thetaf + zla).sin()
943 + w[6] * 2.0 * sinphi * cosphi * (thetaf + zla).cos();
944 let dn = w[7] * (cosphi * cosphi - sinphi * sinphi) * (thetaf + zla).sin()
945 + w[8] * (cosphi * cosphi - sinphi * sinphi) * (thetaf + zla).cos();
946 let de = w[7] * sinphi * (thetaf + zla).cos() - w[8] * sinphi * (thetaf + zla).sin();
947
948 xcorsta[0] += dr * cosla * cosphi - de * sinla - dn * sinphi * cosla;
949 xcorsta[1] += dr * sinla * cosphi + de * cosla - dn * sinphi * sinla;
950 xcorsta[2] += dr * sinphi + dn * cosphi;
951 }
952 for v in &mut xcorsta {
953 *v /= KM_TO_M;
954 }
955 xcorsta
956}
957
958fn step2lon(xsta: &[f64; 3], t: f64) -> [f64; 3] {
961 #[rustfmt::skip]
962 const DATDI: [[f64; 9]; 5] = [
963 [0.0, 0.0, 0.0, 1.0, 0.0, 0.47, 0.23, 0.16, 0.07],
964 [0.0, 2.0, 0.0, 0.0, 0.0, -0.20, -0.12, -0.11, -0.05],
965 [1.0, 0.0, -1.0, 0.0, 0.0, -0.11, -0.08, -0.09, -0.04],
966 [2.0, 0.0, 0.0, 0.0, 0.0, -0.13, -0.11, -0.15, -0.07],
967 [2.0, 0.0, 0.0, 1.0, 0.0, -0.05, -0.05, -0.06, -0.03],
968 ];
969 let mut s = 218.31664563 + (481267.88194 + (-0.0014663889 + 0.00000185139 * t) * t) * t;
970 let pr = (1.396971278 + (0.000308889 + (0.000000021 + 0.000000007 * t) * t) * t) * t;
971 s += pr;
972 let mut h = 280.46645
973 + (36000.7697489 + (0.00030322222 + (0.000000020 + -0.00000000654 * t) * t) * t) * t;
974 let mut p = 83.35324312
975 + (4069.01363525 + (-0.01032172222 + (-0.0000124991 + 0.00000005263 * t) * t) * t) * t;
976 let mut zns = 234.95544499
977 + (1934.13626197 + (-0.00207561111 + (-0.00000213944 + 0.00000001650 * t) * t) * t) * t;
978 let mut ps = 282.93734098
979 + (1.71945766667 + (0.00045688889 + (-0.00000001778 + -0.00000000334 * t) * t) * t) * t;
980
981 let rsta = (xsta[0] * xsta[0] + xsta[1] * xsta[1] + xsta[2] * xsta[2]).sqrt();
982 let sinphi = xsta[2] / rsta;
983 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
984 let cosla = xsta[0] / cosphi / rsta;
985 let sinla = xsta[1] / cosphi / rsta;
986
987 s = s.rem_euclid(360.0);
988 h = h.rem_euclid(360.0);
989 p = p.rem_euclid(360.0);
990 zns = zns.rem_euclid(360.0);
991 ps = ps.rem_euclid(360.0);
992
993 let mut xcorsta = [0.0_f64; 3];
994 for w in &DATDI {
995 let thetaf = (w[0] * s + w[1] * h + w[2] * p + w[3] * zns + w[4] * ps) * DEG_TO_RAD;
996 let dr = w[5] * (3.0 * sinphi * sinphi - 1.0) / 2.0 * thetaf.cos()
997 + w[7] * (3.0 * sinphi * sinphi - 1.0) / 2.0 * thetaf.sin();
998 let dn = w[6] * (cosphi * sinphi * 2.0) * thetaf.cos()
999 + w[8] * (cosphi * sinphi * 2.0) * thetaf.sin();
1000 let de = 0.0;
1001
1002 xcorsta[0] += dr * cosla * cosphi - de * sinla - dn * sinphi * cosla;
1003 xcorsta[1] += dr * sinla * cosphi + de * cosla - dn * sinphi * sinla;
1004 xcorsta[2] += dr * sinphi + dn * cosphi;
1005 }
1006 for v in &mut xcorsta {
1007 *v /= KM_TO_M;
1008 }
1009 xcorsta
1010}
1011
1012fn cal2jd(iy: i32, im: i32, id: i32) -> (f64, f64) {
1022 let my = (im - 14) / 12;
1023 let iypmy = iy + my;
1024 let djm0 = 2400000.5;
1025 let djm = ((1461 * (iypmy + 4800)) / 4 + (367 * (im - 2 - 12 * my)) / 12
1026 - (3 * ((iypmy + 4900) / 100)) / 4
1027 + id
1028 - 2432076) as f64;
1029 (djm0, djm)
1030}
1031
1032fn dat(iy: i32, im: i32, _id: i32) -> f64 {
1036 const IDAT: [(i32, i32, f64); 28] = [
1038 (1972, 1, 10.0),
1039 (1972, 7, 11.0),
1040 (1973, 1, 12.0),
1041 (1974, 1, 13.0),
1042 (1975, 1, 14.0),
1043 (1976, 1, 15.0),
1044 (1977, 1, 16.0),
1045 (1978, 1, 17.0),
1046 (1979, 1, 18.0),
1047 (1980, 1, 19.0),
1048 (1981, 7, 20.0),
1049 (1982, 7, 21.0),
1050 (1983, 7, 22.0),
1051 (1985, 7, 23.0),
1052 (1988, 1, 24.0),
1053 (1990, 1, 25.0),
1054 (1991, 1, 26.0),
1055 (1992, 7, 27.0),
1056 (1993, 7, 28.0),
1057 (1994, 7, 29.0),
1058 (1996, 1, 30.0),
1059 (1997, 7, 31.0),
1060 (1999, 1, 32.0),
1061 (2006, 1, 33.0),
1062 (2009, 1, 34.0),
1063 (2012, 7, 35.0),
1064 (2015, 7, 36.0),
1065 (2017, 1, 37.0),
1066 ];
1067 let m = 12 * iy + im;
1068 let mut da = IDAT[0].2;
1069 for &(y, mo, d) in &IDAT {
1070 if m >= 12 * y + mo {
1071 da = d;
1072 }
1073 }
1074 da
1075}