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 p3sun = 5.0 / 2.0 * (H3 - 3.0 * L3) * scsun.powi(3) + 3.0 / 2.0 * (L3 - H3) * scsun;
610 let p3mon = 5.0 / 2.0 * (H3 - 3.0 * L3) * scmon.powi(3) + 3.0 / 2.0 * (L3 - H3) * scmon;
611
612 let x2sun = 3.0 * l2 * scsun;
614 let x2mon = 3.0 * l2 * scmon;
615 let x3sun = 3.0 * L3 / 2.0 * (5.0 * scsun * scsun - 1.0);
616 let x3mon = 3.0 * L3 / 2.0 * (5.0 * scmon * scmon - 1.0);
617
618 const MASS_RATIO_SUN: f64 = 332946.0482;
620 const MASS_RATIO_MOON: f64 = 0.0123000371;
621 const RE: f64 = SOLID_TIDE_EARTH_RADIUS_M;
622 let fac2sun = MASS_RATIO_SUN * RE * (RE / rsun).powi(3);
623 let fac2mon = MASS_RATIO_MOON * RE * (RE / rmon).powi(3);
624 let fac3sun = fac2sun * (RE / rsun);
625 let fac3mon = fac2mon * (RE / rmon);
626
627 let mut dxtide = [0.0_f64; 3];
629 for i in 0..3 {
630 dxtide[i] = fac2sun * (x2sun * xsun[i] / rsun + p2sun * xsta[i] / rsta)
631 + fac2mon * (x2mon * xmon[i] / rmon + p2mon * xsta[i] / rsta)
632 + fac3sun * (x3sun * xsun[i] / rsun + p3sun * xsta[i] / rsta)
633 + fac3mon * (x3mon * xmon[i] / rmon + p3mon * xsta[i] / rsta);
634 }
635
636 let c = out_of_phase_diurnal_correction(xsta, xsun, xmon, fac2sun, fac2mon);
638 for i in 0..3 {
639 dxtide[i] += c[i];
640 }
641 let c = out_of_phase_semidiurnal_correction(xsta, xsun, xmon, fac2sun, fac2mon);
642 for i in 0..3 {
643 dxtide[i] += c[i];
644 }
645 let c = latitude_dependence_correction(xsta, xsun, xmon, fac2sun, fac2mon);
646 for i in 0..3 {
647 dxtide[i] += c[i];
648 }
649
650 let (jjm0, jjm1) = gregorian_to_two_part_julian_date(year, month, day);
652 let fhrd = fhr / 24.0;
653 let mut t = ((jjm0 - J2000_JD) + jjm1 + fhrd) / DAYS_PER_JULIAN_CENTURY;
654 let dtt = tai_minus_utc_seconds(year, month, day) + TT_MINUS_TAI_S;
655 t += dtt / (SECONDS_PER_DAY * DAYS_PER_JULIAN_CENTURY);
656
657 let c = frequency_dependent_diurnal_correction(xsta, fhr, t);
658 for i in 0..3 {
659 dxtide[i] += c[i];
660 }
661 let c = frequency_dependent_long_period_correction(xsta, t);
662 for i in 0..3 {
663 dxtide[i] += c[i];
664 }
665
666 dxtide
670}
671
672fn out_of_phase_diurnal_correction(
674 xsta: &[f64; 3],
675 xsun: &[f64; 3],
676 xmon: &[f64; 3],
677 fac2sun: f64,
678 fac2mon: f64,
679) -> [f64; 3] {
680 const DHI: f64 = -0.0025;
681 const DLI: f64 = -0.0007;
682 let rsta = norm8(xsta);
683 let sinphi = xsta[2] / rsta;
684 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
685 let cos2phi = cosphi * cosphi - sinphi * sinphi;
686 let sinla = xsta[1] / cosphi / rsta;
687 let cosla = xsta[0] / cosphi / rsta;
688 let rmon = norm8(xmon);
689 let rsun = norm8(xsun);
690
691 let drsun =
692 -3.0 * DHI * sinphi * cosphi * fac2sun * xsun[2] * (xsun[0] * sinla - xsun[1] * cosla)
693 / (rsun * rsun);
694 let drmon =
695 -3.0 * DHI * sinphi * cosphi * fac2mon * xmon[2] * (xmon[0] * sinla - xmon[1] * cosla)
696 / (rmon * rmon);
697 let dnsun = -3.0 * DLI * cos2phi * fac2sun * xsun[2] * (xsun[0] * sinla - xsun[1] * cosla)
698 / (rsun * rsun);
699 let dnmon = -3.0 * DLI * cos2phi * fac2mon * xmon[2] * (xmon[0] * sinla - xmon[1] * cosla)
700 / (rmon * rmon);
701 let desun = -3.0 * DLI * sinphi * fac2sun * xsun[2] * (xsun[0] * cosla + xsun[1] * sinla)
702 / (rsun * rsun);
703 let demon = -3.0 * DLI * sinphi * fac2mon * xmon[2] * (xmon[0] * cosla + xmon[1] * sinla)
704 / (rmon * rmon);
705
706 let dr = drsun + drmon;
707 let dn = dnsun + dnmon;
708 let de = desun + demon;
709
710 [
711 dr * cosla * cosphi - de * sinla - dn * sinphi * cosla,
712 dr * sinla * cosphi + de * cosla - dn * sinphi * sinla,
713 dr * sinphi + dn * cosphi,
714 ]
715}
716
717fn out_of_phase_semidiurnal_correction(
719 xsta: &[f64; 3],
720 xsun: &[f64; 3],
721 xmon: &[f64; 3],
722 fac2sun: f64,
723 fac2mon: f64,
724) -> [f64; 3] {
725 const DHI: f64 = -0.0022;
726 const DLI: f64 = -0.0007;
727 let rsta = norm8(xsta);
728 let sinphi = xsta[2] / rsta;
729 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
730 let sinla = xsta[1] / cosphi / rsta;
731 let cosla = xsta[0] / cosphi / rsta;
732 let costwola = cosla * cosla - sinla * sinla;
733 let sintwola = 2.0 * cosla * sinla;
734 let rmon = norm8(xmon);
735 let rsun = norm8(xsun);
736
737 let drsun = -3.0 / 4.0
738 * DHI
739 * cosphi
740 * cosphi
741 * fac2sun
742 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * sintwola - 2.0 * xsun[0] * xsun[1] * costwola)
743 / (rsun * rsun);
744 let drmon = -3.0 / 4.0
745 * DHI
746 * cosphi
747 * cosphi
748 * fac2mon
749 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * sintwola - 2.0 * xmon[0] * xmon[1] * costwola)
750 / (rmon * rmon);
751 let dnsun = 3.0 / 2.0
752 * DLI
753 * sinphi
754 * cosphi
755 * fac2sun
756 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * sintwola - 2.0 * xsun[0] * xsun[1] * costwola)
757 / (rsun * rsun);
758 let dnmon = 3.0 / 2.0
759 * DLI
760 * sinphi
761 * cosphi
762 * fac2mon
763 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * sintwola - 2.0 * xmon[0] * xmon[1] * costwola)
764 / (rmon * rmon);
765 let desun = -3.0 / 2.0
766 * DLI
767 * cosphi
768 * fac2sun
769 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * costwola + 2.0 * xsun[0] * xsun[1] * sintwola)
770 / (rsun * rsun);
771 let demon = -3.0 / 2.0
772 * DLI
773 * cosphi
774 * fac2mon
775 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * costwola + 2.0 * xmon[0] * xmon[1] * sintwola)
776 / (rmon * rmon);
777
778 let dr = drsun + drmon;
779 let dn = dnsun + dnmon;
780 let de = desun + demon;
781
782 [
783 dr * cosla * cosphi - de * sinla - dn * sinphi * cosla,
784 dr * sinla * cosphi + de * cosla - dn * sinphi * sinla,
785 dr * sinphi + dn * cosphi,
786 ]
787}
788
789fn latitude_dependence_correction(
791 xsta: &[f64; 3],
792 xsun: &[f64; 3],
793 xmon: &[f64; 3],
794 fac2sun: f64,
795 fac2mon: f64,
796) -> [f64; 3] {
797 const L1D: f64 = 0.0012;
798 const L1SD: f64 = 0.0024;
799 let rsta = norm8(xsta);
800 let sinphi = xsta[2] / rsta;
801 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
802 let sinla = xsta[1] / cosphi / rsta;
803 let cosla = xsta[0] / cosphi / rsta;
804 let rmon = norm8(xmon);
805 let rsun = norm8(xsun);
806
807 let mut l1 = L1D;
809 let dnsun = -l1 * sinphi * sinphi * fac2sun * xsun[2] * (xsun[0] * cosla + xsun[1] * sinla)
810 / (rsun * rsun);
811 let dnmon = -l1 * sinphi * sinphi * fac2mon * xmon[2] * (xmon[0] * cosla + xmon[1] * sinla)
812 / (rmon * rmon);
813 let desun = l1
814 * sinphi
815 * (cosphi * cosphi - sinphi * sinphi)
816 * fac2sun
817 * xsun[2]
818 * (xsun[0] * sinla - xsun[1] * cosla)
819 / (rsun * rsun);
820 let demon = l1
821 * sinphi
822 * (cosphi * cosphi - sinphi * sinphi)
823 * fac2mon
824 * xmon[2]
825 * (xmon[0] * sinla - xmon[1] * cosla)
826 / (rmon * rmon);
827
828 let de = 3.0 * (desun + demon);
829 let dn = 3.0 * (dnsun + dnmon);
830
831 let mut xcorsta = [
832 -de * sinla - dn * sinphi * cosla,
833 de * cosla - dn * sinphi * sinla,
834 dn * cosphi,
835 ];
836
837 l1 = L1SD;
839 let costwola = cosla * cosla - sinla * sinla;
840 let sintwola = 2.0 * cosla * sinla;
841
842 let dnsun = -l1 / 2.0
843 * sinphi
844 * cosphi
845 * fac2sun
846 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * costwola + 2.0 * xsun[0] * xsun[1] * sintwola)
847 / (rsun * rsun);
848 let dnmon = -l1 / 2.0
849 * sinphi
850 * cosphi
851 * fac2mon
852 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * costwola + 2.0 * xmon[0] * xmon[1] * sintwola)
853 / (rmon * rmon);
854 let desun = -l1 / 2.0
855 * sinphi
856 * sinphi
857 * cosphi
858 * fac2sun
859 * ((xsun[0] * xsun[0] - xsun[1] * xsun[1]) * sintwola - 2.0 * xsun[0] * xsun[1] * costwola)
860 / (rsun * rsun);
861 let demon = -l1 / 2.0
862 * sinphi
863 * sinphi
864 * cosphi
865 * fac2mon
866 * ((xmon[0] * xmon[0] - xmon[1] * xmon[1]) * sintwola - 2.0 * xmon[0] * xmon[1] * costwola)
867 / (rmon * rmon);
868
869 let de = 3.0 * (desun + demon);
870 let dn = 3.0 * (dnsun + dnmon);
871
872 xcorsta[0] += -de * sinla - dn * sinphi * cosla;
873 xcorsta[1] += de * cosla - dn * sinphi * sinla;
874 xcorsta[2] += dn * cosphi;
875 xcorsta
876}
877
878fn frequency_dependent_diurnal_correction(xsta: &[f64; 3], fhr: f64, t: f64) -> [f64; 3] {
881 #[rustfmt::skip]
883 const DATDI: [[f64; 9]; 31] = [
884 [-3.0, 0.0, 2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
885 [-3.0, 2.0, 0.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
886 [-2.0, 0.0, 1.0, -1.0, 0.0, -0.02, 0.0, 0.0, 0.0],
887 [-2.0, 0.0, 1.0, 0.0, 0.0, -0.08, 0.0, -0.01, 0.01],
888 [-2.0, 2.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
889 [-1.0, 0.0, 0.0, -1.0, 0.0, -0.10, 0.0, 0.0, 0.0],
890 [-1.0, 0.0, 0.0, 0.0, 0.0, -0.51, 0.0, -0.02, 0.03],
891 [-1.0, 2.0, 0.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
892 [0.0, -2.0, 1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
893 [0.0, 0.0, -1.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0],
894 [0.0, 0.0, 1.0, 0.0, 0.0, 0.06, 0.0, 0.0, 0.0],
895 [0.0, 0.0, 1.0, 1.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 [1.0, -3.0, 0.0, 0.0, 1.0, -0.06, 0.0, 0.0, 0.0],
898 [1.0, -2.0, 0.0, -1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
899 [1.0, -2.0, 0.0, 0.0, 0.0, -1.23, -0.07, 0.06, 0.01],
900 [1.0, -1.0, 0.0, 0.0, -1.0, 0.02, 0.0, 0.0, 0.0],
901 [1.0, -1.0, 0.0, 0.0, 1.0, 0.04, 0.0, 0.0, 0.0],
902 [1.0, 0.0, 0.0, -1.0, 0.0, -0.22, 0.01, 0.01, 0.0],
903 [1.0, 0.0, 0.0, 0.0, 0.0, 12.00, -0.80, -0.67, -0.03],
904 [1.0, 0.0, 0.0, 1.0, 0.0, 1.73, -0.12, -0.10, 0.0],
905 [1.0, 0.0, 0.0, 2.0, 0.0, -0.04, 0.0, 0.0, 0.0],
906 [1.0, 1.0, 0.0, 0.0, -1.0, -0.50, -0.01, 0.03, 0.0],
907 [1.0, 1.0, 0.0, 0.0, 1.0, 0.01, 0.0, 0.0, 0.0],
908 [0.0, 1.0, 0.0, 1.0, -1.0, -0.01, 0.0, 0.0, 0.0],
909 [1.0, 2.0, -2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
910 [1.0, 2.0, 0.0, 0.0, 0.0, -0.11, 0.01, 0.01, 0.0],
911 [2.0, -2.0, 1.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
912 [2.0, 0.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
913 [3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
914 [3.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
915 ];
916 let mut s = 218.31664563 + (481267.88194 + (-0.0014663889 + 0.00000185139 * t) * t) * t;
917 let mut tau = fhr * 15.0
918 + 280.4606184
919 + (36000.7700536 + (0.00038793 + -0.0000000258 * t) * t) * t
920 + (-s);
921 let pr = (1.396971278 + (0.000308889 + (0.000000021 + 0.000000007 * t) * t) * t) * t;
922 s += pr;
923 let mut h = 280.46645
924 + (36000.7697489 + (0.00030322222 + (0.000000020 + -0.00000000654 * t) * t) * t) * t;
925 let mut p = 83.35324312
926 + (4069.01363525 + (-0.01032172222 + (-0.0000124991 + 0.00000005263 * t) * t) * t) * t;
927 let mut zns = 234.95544499
928 + (1934.13626197 + (-0.00207561111 + (-0.00000213944 + 0.00000001650 * t) * t) * t) * t;
929 let mut ps = 282.93734098
930 + (1.71945766667 + (0.00045688889 + (-0.00000001778 + -0.00000000334 * t) * t) * t) * t;
931
932 s = s.rem_euclid(360.0);
933 tau = tau.rem_euclid(360.0);
934 h = h.rem_euclid(360.0);
935 p = p.rem_euclid(360.0);
936 zns = zns.rem_euclid(360.0);
937 ps = ps.rem_euclid(360.0);
938
939 let rsta = (xsta[0] * xsta[0] + xsta[1] * xsta[1] + xsta[2] * xsta[2]).sqrt();
940 let sinphi = xsta[2] / rsta;
941 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
942 let cosla = xsta[0] / cosphi / rsta;
943 let sinla = xsta[1] / cosphi / rsta;
944 let zla = xsta[1].atan2(xsta[0]);
945
946 let mut xcorsta = [0.0_f64; 3];
947 for w in &DATDI {
948 let thetaf = (tau + w[0] * s + w[1] * h + w[2] * p + w[3] * zns + w[4] * ps) * DEG_TO_RAD;
949 let dr = w[5] * 2.0 * sinphi * cosphi * (thetaf + zla).sin()
950 + w[6] * 2.0 * sinphi * cosphi * (thetaf + zla).cos();
951 let dn = w[7] * (cosphi * cosphi - sinphi * sinphi) * (thetaf + zla).sin()
952 + w[8] * (cosphi * cosphi - sinphi * sinphi) * (thetaf + zla).cos();
953 let de = w[7] * sinphi * (thetaf + zla).cos() - w[8] * sinphi * (thetaf + zla).sin();
954
955 xcorsta[0] += dr * cosla * cosphi - de * sinla - dn * sinphi * cosla;
956 xcorsta[1] += dr * sinla * cosphi + de * cosla - dn * sinphi * sinla;
957 xcorsta[2] += dr * sinphi + dn * cosphi;
958 }
959 for v in &mut xcorsta {
960 *v /= KM_TO_M;
961 }
962 xcorsta
963}
964
965fn frequency_dependent_long_period_correction(xsta: &[f64; 3], t: f64) -> [f64; 3] {
968 #[rustfmt::skip]
969 const DATDI: [[f64; 9]; 5] = [
970 [0.0, 0.0, 0.0, 1.0, 0.0, 0.47, 0.23, 0.16, 0.07],
971 [0.0, 2.0, 0.0, 0.0, 0.0, -0.20, -0.12, -0.11, -0.05],
972 [1.0, 0.0, -1.0, 0.0, 0.0, -0.11, -0.08, -0.09, -0.04],
973 [2.0, 0.0, 0.0, 0.0, 0.0, -0.13, -0.11, -0.15, -0.07],
974 [2.0, 0.0, 0.0, 1.0, 0.0, -0.05, -0.05, -0.06, -0.03],
975 ];
976 let mut s = 218.31664563 + (481267.88194 + (-0.0014663889 + 0.00000185139 * t) * t) * t;
977 let pr = (1.396971278 + (0.000308889 + (0.000000021 + 0.000000007 * t) * t) * t) * t;
978 s += pr;
979 let mut h = 280.46645
980 + (36000.7697489 + (0.00030322222 + (0.000000020 + -0.00000000654 * t) * t) * t) * t;
981 let mut p = 83.35324312
982 + (4069.01363525 + (-0.01032172222 + (-0.0000124991 + 0.00000005263 * t) * t) * t) * t;
983 let mut zns = 234.95544499
984 + (1934.13626197 + (-0.00207561111 + (-0.00000213944 + 0.00000001650 * t) * t) * t) * t;
985 let mut ps = 282.93734098
986 + (1.71945766667 + (0.00045688889 + (-0.00000001778 + -0.00000000334 * t) * t) * t) * t;
987
988 let rsta = (xsta[0] * xsta[0] + xsta[1] * xsta[1] + xsta[2] * xsta[2]).sqrt();
989 let sinphi = xsta[2] / rsta;
990 let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
991 let cosla = xsta[0] / cosphi / rsta;
992 let sinla = xsta[1] / cosphi / rsta;
993
994 s = s.rem_euclid(360.0);
995 h = h.rem_euclid(360.0);
996 p = p.rem_euclid(360.0);
997 zns = zns.rem_euclid(360.0);
998 ps = ps.rem_euclid(360.0);
999
1000 let mut xcorsta = [0.0_f64; 3];
1001 for w in &DATDI {
1002 let thetaf = (w[0] * s + w[1] * h + w[2] * p + w[3] * zns + w[4] * ps) * DEG_TO_RAD;
1003 let dr = w[5] * (3.0 * sinphi * sinphi - 1.0) / 2.0 * thetaf.cos()
1004 + w[7] * (3.0 * sinphi * sinphi - 1.0) / 2.0 * thetaf.sin();
1005 let dn = w[6] * (cosphi * sinphi * 2.0) * thetaf.cos()
1006 + w[8] * (cosphi * sinphi * 2.0) * thetaf.sin();
1007 let de = 0.0;
1008
1009 xcorsta[0] += dr * cosla * cosphi - de * sinla - dn * sinphi * cosla;
1010 xcorsta[1] += dr * sinla * cosphi + de * cosla - dn * sinphi * sinla;
1011 xcorsta[2] += dr * sinphi + dn * cosphi;
1012 }
1013 for v in &mut xcorsta {
1014 *v /= KM_TO_M;
1015 }
1016 xcorsta
1017}
1018
1019fn gregorian_to_two_part_julian_date(iy: i32, im: i32, id: i32) -> (f64, f64) {
1029 let my = (im - 14) / 12;
1030 let iypmy = iy + my;
1031 let djm0 = 2400000.5;
1032 let djm = ((1461 * (iypmy + 4800)) / 4 + (367 * (im - 2 - 12 * my)) / 12
1033 - (3 * ((iypmy + 4900) / 100)) / 4
1034 + id
1035 - 2432076) as f64;
1036 (djm0, djm)
1037}
1038
1039fn tai_minus_utc_seconds(iy: i32, im: i32, _id: i32) -> f64 {
1043 const IDAT: [(i32, i32, f64); 28] = [
1045 (1972, 1, 10.0),
1046 (1972, 7, 11.0),
1047 (1973, 1, 12.0),
1048 (1974, 1, 13.0),
1049 (1975, 1, 14.0),
1050 (1976, 1, 15.0),
1051 (1977, 1, 16.0),
1052 (1978, 1, 17.0),
1053 (1979, 1, 18.0),
1054 (1980, 1, 19.0),
1055 (1981, 7, 20.0),
1056 (1982, 7, 21.0),
1057 (1983, 7, 22.0),
1058 (1985, 7, 23.0),
1059 (1988, 1, 24.0),
1060 (1990, 1, 25.0),
1061 (1991, 1, 26.0),
1062 (1992, 7, 27.0),
1063 (1993, 7, 28.0),
1064 (1994, 7, 29.0),
1065 (1996, 1, 30.0),
1066 (1997, 7, 31.0),
1067 (1999, 1, 32.0),
1068 (2006, 1, 33.0),
1069 (2009, 1, 34.0),
1070 (2012, 7, 35.0),
1071 (2015, 7, 36.0),
1072 (2017, 1, 37.0),
1073 ];
1074 let m = 12 * iy + im;
1075 let mut da = IDAT[0].2;
1076 for &(y, mo, d) in &IDAT {
1077 if m >= 12 * y + mo {
1078 da = d;
1079 }
1080 }
1081 da
1082}