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