@neilberkman/sidereon 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +530 -0
- package/README.md +64 -8
- package/THIRD-PARTY-NOTICES.md +3 -3
- package/package.json +5 -5
- package/pkg/sidereon.d.ts +11031 -2692
- package/pkg/sidereon.js +8934 -627
- package/pkg/sidereon_bg.wasm +0 -0
- package/pkg/sidereon_bg.wasm.d.ts +2515 -1949
- package/pkg-node/sidereon.d.ts +8291 -518
- package/pkg-node/sidereon.js +9040 -627
- package/pkg-node/sidereon_bg.wasm +0 -0
- package/pkg-node/sidereon_bg.wasm.d.ts +2515 -1949
- package/third_party_source/{sidereon-core-0.36.3 → sidereon-core-3.0.0}/tides/mod.rs +508 -89
- package/third_party_source/{sidereon-core-0.36.3 → sidereon-core-3.0.0}/tides/ocean.rs +391 -86
- package/types/sidereon-extra.d.ts +312 -1
- /package/third_party_source/{sidereon-core-0.36.3 → sidereon-core-3.0.0}/tides/pole.rs +0 -0
|
@@ -12,11 +12,33 @@
|
|
|
12
12
|
//! displacement, the out-of-phase corrections (`ST1IDIU`, `ST1ISEM`), the
|
|
13
13
|
//! latitude-dependence correction (`ST1L1`), and the frequency-dependent
|
|
14
14
|
//! step-2 diurnal/long-period band corrections (`STEP2DIU`, `STEP2LON`),
|
|
15
|
-
//! evaluating the identical Love/Shida numbers,
|
|
15
|
+
//! evaluating the identical Love/Shida numbers, long-period table and
|
|
16
16
|
//! leap-second table.
|
|
17
|
-
//! *
|
|
18
|
-
//!
|
|
19
|
-
//! the
|
|
17
|
+
//! * Its diurnal (Step 2) table has two variants, [`StationTideConstants`].
|
|
18
|
+
//! [`StationTideConstants::IersRoutine`] is the routine's `DATDI` array as
|
|
19
|
+
//! distributed. [`StationTideConstants::Conventions`], the default, corrects
|
|
20
|
+
//! three rows of it to the Conventions text and the physics it states:
|
|
21
|
+
//! - K1 out-of-phase radial amplitude -0.78 mm, as Table 7.3a prints it in
|
|
22
|
+
//! the 2010 edition and in the chapter update of 1 February 2018, and as
|
|
23
|
+
//! Equation (7.12c) gives it from the K1 row of Table 7.2 (h(0)I = 0.0030
|
|
24
|
+
//! against the nominal hI = -0.0025, with the Cartwright-Tayler-Edden
|
|
25
|
+
//! amplitude Hf = 0.368645 m of the IERS routine `ADMINT.F`: -0.783 mm).
|
|
26
|
+
//! The routine (revision of 19 December 2016) and RTKLIB use -0.80 mm.
|
|
27
|
+
//! - P1 out-of-phase radial amplitude +0.07 mm. Table 7.3a and the routine
|
|
28
|
+
//! both have -0.07 mm, but Equation (7.12c) with Table 7.2 (P1
|
|
29
|
+
//! h(0)I = -0.0011, Hf = -0.121995 m) gives +0.066 mm; the sign error was
|
|
30
|
+
//! identified by H. Krásná and is corrected in Orekit's copy of Table 7.3a.
|
|
31
|
+
//! - The 25th row is tide 166,564 (s multiplier 1), the tide Tables 6.5a and
|
|
32
|
+
//! 7.2 list between psi1 and 167,355; the routine's s multiplier 0 names
|
|
33
|
+
//! 156,564, out of the table's frequency order and in no Conventions table.
|
|
34
|
+
//!
|
|
35
|
+
//! The Conventions' version notes (v1.0.0 to v1.3.0) list no correction to
|
|
36
|
+
//! either source. The two variants differ by at most the sum of the three
|
|
37
|
+
//! changes, 0.02 + 0.14 + 0.02 mm, in the displacement.
|
|
38
|
+
//! * It applies the permanent part of the displacement: the routine's
|
|
39
|
+
//! commented-out "Step 3" permanent-tide removal stays out, so coordinates
|
|
40
|
+
//! corrected with it are "conventional tide free", the system of the ITRF
|
|
41
|
+
//! (IERS Conventions (2010), Section 7.1.1.2).
|
|
20
42
|
//! * The routine names are changed from the IERS originals (per the IERS
|
|
21
43
|
//! Conventions Software License), and the Fortran subroutine structure is
|
|
22
44
|
//! inlined into private helpers.
|
|
@@ -43,7 +65,8 @@ mod ocean;
|
|
|
43
65
|
mod pole;
|
|
44
66
|
pub use ocean::{
|
|
45
67
|
ocean_tide_loading, parse_ocean_loading_blq_block, parse_ocean_loading_blq_blocks,
|
|
46
|
-
OceanLoadingBlq, OceanLoadingBlqBlock,
|
|
68
|
+
write_ocean_loading_blq_blocks, OceanLoadingBlq, OceanLoadingBlqBlock, OceanLoadingBlqComment,
|
|
69
|
+
OceanLoadingBlqCommentPlacement, OceanTideConstituent, NUM_OCEAN_CONSTITUENTS,
|
|
47
70
|
OCEAN_LOADING_CONSTITUENTS,
|
|
48
71
|
};
|
|
49
72
|
pub use pole::solid_earth_pole_tide;
|
|
@@ -54,58 +77,214 @@ use crate::astro::constants::time::{
|
|
|
54
77
|
DAYS_PER_JULIAN_CENTURY, J2000_JD, SECONDS_PER_DAY, TT_MINUS_TAI_S,
|
|
55
78
|
};
|
|
56
79
|
use crate::astro::constants::units::{ARCSEC_TO_RAD, DEG_TO_RAD, KM_TO_M};
|
|
57
|
-
use crate::astro::frames::transforms::{FrameTransformError, PolarMotion};
|
|
80
|
+
use crate::astro::frames::transforms::{FrameTransformError, PolarMotion, Ut1Gate};
|
|
58
81
|
use crate::astro::math::vec3::{dot3_ref as dot, norm3_ref as norm8};
|
|
59
|
-
use crate::astro::time::{
|
|
82
|
+
use crate::astro::time::{
|
|
83
|
+
CoverageError, TimeScaleInputErrorKind, TimeScales, Validated, ValidityMode,
|
|
84
|
+
};
|
|
60
85
|
use crate::frame::{geodetic_to_itrf, ItrfPositionM, Wgs84Geodetic};
|
|
61
86
|
use crate::validate::{self, FieldError};
|
|
62
87
|
|
|
63
88
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
89
|
+
/// Validation categories carried by [`TideError::InvalidInput`].
|
|
90
|
+
///
|
|
91
|
+
/// The categories are converted from shared field and time-scale validation
|
|
92
|
+
/// errors before they are exposed by the tide APIs.
|
|
64
93
|
pub enum TideInputErrorKind {
|
|
94
|
+
/// A required input was not supplied.
|
|
65
95
|
Missing,
|
|
96
|
+
/// An input value or component was not finite.
|
|
66
97
|
NonFinite,
|
|
98
|
+
/// A value required to be positive was zero or negative.
|
|
67
99
|
NotPositive,
|
|
100
|
+
/// A value failed a negative-value check.
|
|
68
101
|
Negative,
|
|
102
|
+
/// A finite input was outside its permitted domain.
|
|
69
103
|
OutOfRange,
|
|
104
|
+
/// Text could not be parsed as a floating-point value.
|
|
70
105
|
FloatParse,
|
|
106
|
+
/// Text could not be parsed as an integer value.
|
|
71
107
|
IntParse,
|
|
108
|
+
/// A calendar date failed civil-date validation.
|
|
72
109
|
InvalidCivilDate,
|
|
110
|
+
/// A clock value failed civil-time validation.
|
|
73
111
|
InvalidCivilTime,
|
|
74
112
|
}
|
|
75
113
|
|
|
76
114
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
115
|
+
/// Failure details produced while parsing a BLQ station block.
|
|
116
|
+
///
|
|
117
|
+
/// Numeric rows and constituent headers use these variants to retain the
|
|
118
|
+
/// offending count, token, station, or constituent label.
|
|
77
119
|
pub enum BlqParseErrorKind {
|
|
120
|
+
/// The input contained no non-whitespace content.
|
|
78
121
|
Empty,
|
|
122
|
+
/// A numeric coefficient row appeared before a station identifier.
|
|
79
123
|
MissingStation,
|
|
124
|
+
/// A station ended before its six coefficient rows were complete.
|
|
80
125
|
MissingCoefficientRows {
|
|
126
|
+
/// Trimmed station identifier for the incomplete block.
|
|
81
127
|
station: String,
|
|
128
|
+
/// Required number of coefficient rows, which is six.
|
|
82
129
|
expected: usize,
|
|
130
|
+
/// Number of coefficient rows collected before end of input.
|
|
83
131
|
found: usize,
|
|
84
132
|
},
|
|
133
|
+
/// The active station accumulated more than six coefficient rows.
|
|
85
134
|
TooManyCoefficientRows {
|
|
135
|
+
/// Active station identifier when the extra row was read.
|
|
86
136
|
station: String,
|
|
87
137
|
},
|
|
138
|
+
/// A numeric row or recognized header did not contain eleven columns.
|
|
88
139
|
WrongColumnCount {
|
|
140
|
+
/// Required ARG2 column count, equal to `NUM_OCEAN_CONSTITUENTS`.
|
|
89
141
|
expected: usize,
|
|
142
|
+
/// Number of tokens or constituent labels found on the line.
|
|
90
143
|
found: usize,
|
|
91
144
|
},
|
|
145
|
+
/// A coefficient token could not be parsed as an `f64`.
|
|
92
146
|
InvalidNumber {
|
|
147
|
+
/// Original token, or the trimmed line reported as a numeric candidate.
|
|
93
148
|
token: String,
|
|
94
149
|
},
|
|
150
|
+
/// A token parsed as an `f64` but produced a non-finite value.
|
|
95
151
|
NonFiniteNumber {
|
|
152
|
+
/// Original token that produced the non-finite value.
|
|
96
153
|
token: String,
|
|
97
154
|
},
|
|
155
|
+
/// A constituent-like header label is absent from the supported table.
|
|
98
156
|
UnsupportedConstituent {
|
|
157
|
+
/// Normalized uppercase label rejected by the constituent lookup.
|
|
99
158
|
constituent: String,
|
|
100
159
|
},
|
|
160
|
+
/// A supported constituent occurred more than once in a header.
|
|
101
161
|
DuplicateConstituent {
|
|
162
|
+
/// Canonical label of the repeated constituent.
|
|
102
163
|
constituent: String,
|
|
103
164
|
},
|
|
165
|
+
/// The single-block parser found more than one complete station block.
|
|
104
166
|
MultipleBlocks {
|
|
167
|
+
/// Number of complete station blocks found.
|
|
105
168
|
found: usize,
|
|
106
169
|
},
|
|
107
170
|
}
|
|
108
171
|
|
|
172
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
173
|
+
/// Reasons a BLQ block cannot be written so that the parser reads it back
|
|
174
|
+
/// unchanged.
|
|
175
|
+
pub enum BlqWriteErrorKind {
|
|
176
|
+
/// The station identifier is empty.
|
|
177
|
+
EmptyStation,
|
|
178
|
+
/// The station identifier contains a line break.
|
|
179
|
+
StationLineBreak,
|
|
180
|
+
/// The station identifier has leading or trailing whitespace, which the
|
|
181
|
+
/// parser trims.
|
|
182
|
+
StationSurroundingWhitespace,
|
|
183
|
+
/// The station identifier starts with a comment marker (`$`, `#`, `!`).
|
|
184
|
+
StationReadsAsComment,
|
|
185
|
+
/// The station identifier would be read as a column-order header.
|
|
186
|
+
StationReadsAsHeader,
|
|
187
|
+
/// The station identifier would be read as a coefficient row.
|
|
188
|
+
StationReadsAsCoefficientRow,
|
|
189
|
+
/// A coefficient is NaN or infinite.
|
|
190
|
+
NonFiniteCoefficient {
|
|
191
|
+
/// Zero-based BLQ row: amplitudes radial, EW, NS, then phases.
|
|
192
|
+
row: usize,
|
|
193
|
+
/// Constituent of the value.
|
|
194
|
+
constituent: crate::tides::OceanTideConstituent,
|
|
195
|
+
},
|
|
196
|
+
/// A retained comment line contains a line break or ends with a carriage
|
|
197
|
+
/// return.
|
|
198
|
+
CommentLineBreak {
|
|
199
|
+
/// Index in [`crate::tides::OceanLoadingBlqBlock::comments`].
|
|
200
|
+
index: usize,
|
|
201
|
+
},
|
|
202
|
+
/// A retained line is blank or has no comment marker and is not a
|
|
203
|
+
/// column-order header, so it would not be read as a comment.
|
|
204
|
+
NotACommentLine {
|
|
205
|
+
/// Index in [`crate::tides::OceanLoadingBlqBlock::comments`].
|
|
206
|
+
index: usize,
|
|
207
|
+
},
|
|
208
|
+
/// A retained comment names a coefficient row after the sixth.
|
|
209
|
+
CommentPlacementOutOfRange {
|
|
210
|
+
/// Index in [`crate::tides::OceanLoadingBlqBlock::comments`].
|
|
211
|
+
index: usize,
|
|
212
|
+
},
|
|
213
|
+
/// A retained line is a column-order header the parser refuses.
|
|
214
|
+
InvalidHeader {
|
|
215
|
+
/// Index in [`crate::tides::OceanLoadingBlqBlock::comments`].
|
|
216
|
+
index: usize,
|
|
217
|
+
/// The parser's refusal.
|
|
218
|
+
kind: BlqParseErrorKind,
|
|
219
|
+
},
|
|
220
|
+
/// A retained comment is placed after the coefficient rows of a block
|
|
221
|
+
/// that is not the last one written; the parser reads such a line as part
|
|
222
|
+
/// of the next block.
|
|
223
|
+
AfterRowsBeforeAnotherBlock {
|
|
224
|
+
/// Index in [`crate::tides::OceanLoadingBlqBlock::comments`].
|
|
225
|
+
index: usize,
|
|
226
|
+
},
|
|
227
|
+
/// Retained comments are not grouped by placement in file order (before
|
|
228
|
+
/// the station, before rows 0 to 5, after the rows); the parser would read
|
|
229
|
+
/// them back in that order.
|
|
230
|
+
CommentsOutOfPlacementOrder {
|
|
231
|
+
/// Index of the first comment placed before its predecessor.
|
|
232
|
+
index: usize,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
impl core::fmt::Display for BlqWriteErrorKind {
|
|
237
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
238
|
+
match self {
|
|
239
|
+
Self::EmptyStation => f.write_str("empty station identifier"),
|
|
240
|
+
Self::StationLineBreak => f.write_str("station identifier contains a line break"),
|
|
241
|
+
Self::StationSurroundingWhitespace => {
|
|
242
|
+
f.write_str("station identifier has leading or trailing whitespace")
|
|
243
|
+
}
|
|
244
|
+
Self::StationReadsAsComment => {
|
|
245
|
+
f.write_str("station identifier starts with a comment marker")
|
|
246
|
+
}
|
|
247
|
+
Self::StationReadsAsHeader => {
|
|
248
|
+
f.write_str("station identifier reads as a column-order header")
|
|
249
|
+
}
|
|
250
|
+
Self::StationReadsAsCoefficientRow => {
|
|
251
|
+
f.write_str("station identifier reads as a coefficient row")
|
|
252
|
+
}
|
|
253
|
+
Self::NonFiniteCoefficient { row, constituent } => write!(
|
|
254
|
+
f,
|
|
255
|
+
"coefficient row {row} constituent {} is not finite",
|
|
256
|
+
constituent.label()
|
|
257
|
+
),
|
|
258
|
+
Self::CommentLineBreak { index } => {
|
|
259
|
+
write!(f, "comment {index} contains a line break")
|
|
260
|
+
}
|
|
261
|
+
Self::NotACommentLine { index } => {
|
|
262
|
+
write!(f, "comment {index} would not be read as a comment")
|
|
263
|
+
}
|
|
264
|
+
Self::CommentPlacementOutOfRange { index } => {
|
|
265
|
+
write!(
|
|
266
|
+
f,
|
|
267
|
+
"comment {index} is placed after the sixth coefficient row"
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
Self::InvalidHeader { index, kind } => {
|
|
271
|
+
write!(
|
|
272
|
+
f,
|
|
273
|
+
"comment {index} is a column-order header the parser refuses: {kind}"
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
Self::AfterRowsBeforeAnotherBlock { index } => write!(
|
|
277
|
+
f,
|
|
278
|
+
"comment {index} follows the coefficient rows of a block that is not the last"
|
|
279
|
+
),
|
|
280
|
+
Self::CommentsOutOfPlacementOrder { index } => write!(
|
|
281
|
+
f,
|
|
282
|
+
"comment {index} is placed before the comment preceding it"
|
|
283
|
+
),
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
109
288
|
impl core::fmt::Display for BlqParseErrorKind {
|
|
110
289
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
111
290
|
match self {
|
|
@@ -176,25 +355,50 @@ impl From<&FieldError> for TideInputErrorKind {
|
|
|
176
355
|
}
|
|
177
356
|
|
|
178
357
|
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
|
358
|
+
/// Errors returned by station displacement evaluators and BLQ parsers.
|
|
359
|
+
///
|
|
360
|
+
/// Validation failures carry normalized field and reason data; other variants
|
|
361
|
+
/// preserve the underlying time-scale, frame, ephemeris, or parser failure.
|
|
179
362
|
pub enum TideError {
|
|
363
|
+
/// A tide input failed validation.
|
|
180
364
|
#[error("invalid solid-earth tide input {field}: {kind}")]
|
|
181
365
|
InvalidInput {
|
|
366
|
+
/// Field label supplied by the originating validator or constructor.
|
|
182
367
|
field: &'static str,
|
|
368
|
+
/// Normalized validation category for the field.
|
|
183
369
|
kind: TideInputErrorKind,
|
|
184
370
|
},
|
|
371
|
+
/// Time-scale conversion failed for a non-input coverage or conversion reason.
|
|
185
372
|
#[error("station displacement time-scale conversion failed: {0}")]
|
|
186
373
|
TimeScale(#[from] CoverageError),
|
|
374
|
+
/// Geodetic, ECEF, or polar-motion frame conversion failed.
|
|
187
375
|
#[error("station displacement frame transform failed: {0}")]
|
|
188
376
|
FrameTransform(#[from] FrameTransformError),
|
|
377
|
+
/// Polar-motion-aware Sun/Moon evaluation failed.
|
|
189
378
|
#[error("station displacement Sun/Moon evaluation failed: {0}")]
|
|
190
379
|
SunMoon(#[from] SunMoonError),
|
|
380
|
+
/// A required high-level station-displacement input was not supplied.
|
|
191
381
|
#[error("missing station displacement input {field}")]
|
|
192
|
-
MissingInput {
|
|
382
|
+
MissingInput {
|
|
383
|
+
/// Missing-input label; the dispatcher uses `"polar motion"` here.
|
|
384
|
+
field: &'static str,
|
|
385
|
+
},
|
|
386
|
+
/// A BLQ parser rejected an input line or whole-input condition.
|
|
193
387
|
#[error("invalid BLQ block at line {line}: {kind}")]
|
|
194
388
|
BlqParse {
|
|
389
|
+
/// One-based offending line number, or zero for whole-input failures.
|
|
195
390
|
line: usize,
|
|
391
|
+
/// Detailed BLQ parsing failure and its source payload.
|
|
196
392
|
kind: BlqParseErrorKind,
|
|
197
393
|
},
|
|
394
|
+
/// A BLQ block could not be written so that it reads back unchanged.
|
|
395
|
+
#[error("cannot write BLQ block {block}: {kind}")]
|
|
396
|
+
BlqWrite {
|
|
397
|
+
/// Zero-based index of the block in the written sequence.
|
|
398
|
+
block: usize,
|
|
399
|
+
/// Reason the block was refused.
|
|
400
|
+
kind: BlqWriteErrorKind,
|
|
401
|
+
},
|
|
198
402
|
}
|
|
199
403
|
|
|
200
404
|
fn invalid_tide_input(error: FieldError) -> TideError {
|
|
@@ -278,11 +482,17 @@ impl StationDisplacementPosition {
|
|
|
278
482
|
/// IERS polar-motion coordinates of the epoch, in arcseconds.
|
|
279
483
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
280
484
|
pub struct StationPolarMotion {
|
|
485
|
+
/// IERS x-pole coordinate supplied in arcseconds.
|
|
281
486
|
pub xp_arcsec: f64,
|
|
487
|
+
/// IERS y-pole coordinate supplied in arcseconds.
|
|
282
488
|
pub yp_arcsec: f64,
|
|
283
489
|
}
|
|
284
490
|
|
|
285
491
|
impl StationPolarMotion {
|
|
492
|
+
/// Construct from the IERS x- and y-pole coordinates in arcseconds.
|
|
493
|
+
///
|
|
494
|
+
/// The coordinates are stored as supplied; tide evaluation performs the
|
|
495
|
+
/// conversion and validation required by its downstream model.
|
|
286
496
|
pub const fn from_arcseconds(xp_arcsec: f64, yp_arcsec: f64) -> Self {
|
|
287
497
|
Self {
|
|
288
498
|
xp_arcsec,
|
|
@@ -301,11 +511,17 @@ impl StationPolarMotion {
|
|
|
301
511
|
/// UTC epoch for station displacement evaluation.
|
|
302
512
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
303
513
|
pub struct StationDisplacementEpoch {
|
|
514
|
+
/// UTC calendar year passed to validation and tide models.
|
|
304
515
|
pub year: i32,
|
|
516
|
+
/// UTC calendar month passed to validation and tide models.
|
|
305
517
|
pub month: u8,
|
|
518
|
+
/// UTC calendar day passed to validation and tide models.
|
|
306
519
|
pub day: u8,
|
|
520
|
+
/// UTC hour used by the `fractional_hour` calculation.
|
|
307
521
|
pub hour: u8,
|
|
522
|
+
/// UTC minute used by the `fractional_hour` calculation.
|
|
308
523
|
pub minute: u8,
|
|
524
|
+
/// UTC seconds, including a fractional part, used in time conversion and fractional-hour calculation.
|
|
309
525
|
pub second: f64,
|
|
310
526
|
/// Optional IERS polar motion for pole tide and polar-motion-aware Sun/Moon
|
|
311
527
|
/// rotation.
|
|
@@ -313,6 +529,10 @@ pub struct StationDisplacementEpoch {
|
|
|
313
529
|
}
|
|
314
530
|
|
|
315
531
|
impl StationDisplacementEpoch {
|
|
532
|
+
/// Construct an epoch from UTC calendar and clock components.
|
|
533
|
+
///
|
|
534
|
+
/// The returned epoch has no polar motion until a caller adds it with
|
|
535
|
+
/// [`StationDisplacementEpoch::with_polar_motion_arcsec`].
|
|
316
536
|
pub const fn from_utc(
|
|
317
537
|
year: i32,
|
|
318
538
|
month: u8,
|
|
@@ -332,6 +552,7 @@ impl StationDisplacementEpoch {
|
|
|
332
552
|
}
|
|
333
553
|
}
|
|
334
554
|
|
|
555
|
+
/// Return this epoch with the supplied IERS polar motion in arcseconds.
|
|
335
556
|
pub const fn with_polar_motion_arcsec(mut self, xp_arcsec: f64, yp_arcsec: f64) -> Self {
|
|
336
557
|
self.polar_motion = Some(StationPolarMotion::from_arcseconds(xp_arcsec, yp_arcsec));
|
|
337
558
|
self
|
|
@@ -370,6 +591,7 @@ impl StationDisplacementEpoch {
|
|
|
370
591
|
|
|
371
592
|
/// Switches for the high-level station displacement entry.
|
|
372
593
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
594
|
+
#[non_exhaustive]
|
|
373
595
|
pub struct StationDisplacementOptions<'a> {
|
|
374
596
|
/// Apply the IERS solid Earth tide station displacement.
|
|
375
597
|
pub solid_earth_tide: bool,
|
|
@@ -378,6 +600,9 @@ pub struct StationDisplacementOptions<'a> {
|
|
|
378
600
|
pub pole_tide: bool,
|
|
379
601
|
/// Optional ocean-loading BLQ coefficients supplied by the caller.
|
|
380
602
|
pub ocean_loading: Option<&'a OceanLoadingBlq>,
|
|
603
|
+
/// Diurnal Step 2 constants of the solid Earth tide;
|
|
604
|
+
/// [`StationTideConstants::Conventions`] by default.
|
|
605
|
+
pub solid_earth_tide_constants: StationTideConstants,
|
|
381
606
|
}
|
|
382
607
|
|
|
383
608
|
impl Default for StationDisplacementOptions<'_> {
|
|
@@ -386,6 +611,7 @@ impl Default for StationDisplacementOptions<'_> {
|
|
|
386
611
|
solid_earth_tide: true,
|
|
387
612
|
pole_tide: false,
|
|
388
613
|
ocean_loading: None,
|
|
614
|
+
solid_earth_tide_constants: StationTideConstants::Conventions,
|
|
389
615
|
}
|
|
390
616
|
}
|
|
391
617
|
}
|
|
@@ -395,8 +621,11 @@ impl Default for StationDisplacementOptions<'_> {
|
|
|
395
621
|
pub struct StationDisplacement {
|
|
396
622
|
/// Sum of all enabled component displacements, in ITRF/ECEF metres.
|
|
397
623
|
pub ecef_m: [f64; 3],
|
|
624
|
+
/// Solid-Earth tide component, or `None` when that correction is disabled.
|
|
398
625
|
pub solid_earth_tide_ecef_m: Option<[f64; 3]>,
|
|
626
|
+
/// Pole-tide component, or `None` when that correction is disabled.
|
|
399
627
|
pub pole_tide_ecef_m: Option<[f64; 3]>,
|
|
628
|
+
/// Ocean-loading component, or `None` when no BLQ coefficients are supplied.
|
|
400
629
|
pub ocean_loading_ecef_m: Option<[f64; 3]>,
|
|
401
630
|
}
|
|
402
631
|
|
|
@@ -421,33 +650,55 @@ impl StationDisplacement {
|
|
|
421
650
|
/// metre components.
|
|
422
651
|
///
|
|
423
652
|
/// The solid Earth tide path uses IERS Conventions (2010), Chapter 7 station
|
|
424
|
-
/// displacement with
|
|
653
|
+
/// displacement with its permanent part applied. The low-level
|
|
425
654
|
/// [`solid_earth_tide`] routine ships the in-phase degree-2 and degree-3
|
|
426
655
|
/// displacement, the step-1 out-of-phase and latitude-dependence corrections,
|
|
427
656
|
/// and the step-2 diurnal/long-period frequency corrections; it leaves the
|
|
428
|
-
/// optional step-3 permanent-tide removal
|
|
657
|
+
/// optional step-3 permanent-tide removal out, so corrected coordinates are
|
|
658
|
+
/// conventional tide free, as the ITRF is. Sun/Moon
|
|
429
659
|
/// positions are generated through the same Earth-fixed analytic ephemeris path
|
|
430
660
|
/// used by the tide-force lane, including caller-supplied polar motion when the
|
|
431
661
|
/// epoch carries it.
|
|
662
|
+
///
|
|
663
|
+
/// The solid Earth tide rotates the Sun and Moon into ITRF with UT1, so it
|
|
664
|
+
/// refuses an epoch outside the UT1 table; see
|
|
665
|
+
/// [`station_displacement_ecef_m_with_validity`].
|
|
432
666
|
pub fn station_displacement_ecef_m(
|
|
433
667
|
position: StationDisplacementPosition,
|
|
434
668
|
epoch: StationDisplacementEpoch,
|
|
435
669
|
options: StationDisplacementOptions<'_>,
|
|
436
670
|
) -> Result<StationDisplacement, TideError> {
|
|
671
|
+
station_displacement_ecef_m_with_validity(position, epoch, options, ValidityMode::Strict)
|
|
672
|
+
.map(|validated| validated.value)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/// [`station_displacement_ecef_m`] under an explicit UT1 [`ValidityMode`].
|
|
676
|
+
///
|
|
677
|
+
/// Only the solid Earth tide reads UT1. [`ValidityMode::Strict`] refuses an
|
|
678
|
+
/// epoch outside the UT1 table when it is enabled;
|
|
679
|
+
/// [`ValidityMode::Permissive`] evaluates it with the long-term UT1 and
|
|
680
|
+
/// reports the departure in [`Validated::degraded`].
|
|
681
|
+
pub fn station_displacement_ecef_m_with_validity(
|
|
682
|
+
position: StationDisplacementPosition,
|
|
683
|
+
epoch: StationDisplacementEpoch,
|
|
684
|
+
options: StationDisplacementOptions<'_>,
|
|
685
|
+
mode: ValidityMode,
|
|
686
|
+
) -> Result<Validated<StationDisplacement>, TideError> {
|
|
437
687
|
let receiver_ecef_m = position.ecef_m()?;
|
|
438
688
|
epoch.validate_utc()?;
|
|
439
689
|
let fhr = epoch.fractional_hour();
|
|
440
690
|
let mut displacement = StationDisplacement::zero();
|
|
691
|
+
let gate = Ut1Gate::new(mode);
|
|
441
692
|
|
|
442
693
|
if options.solid_earth_tide {
|
|
443
|
-
let ts = epoch.time_scales()?;
|
|
694
|
+
let ts = gate.admit(epoch.time_scales()?)?;
|
|
444
695
|
let polar_motion = epoch
|
|
445
696
|
.polar_motion
|
|
446
697
|
.map(StationPolarMotion::polar_motion)
|
|
447
698
|
.transpose()?
|
|
448
699
|
.unwrap_or_default();
|
|
449
700
|
let sun_moon = sun_moon_ecef_with_polar_motion(&ts, polar_motion)?;
|
|
450
|
-
let solid =
|
|
701
|
+
let solid = solid_earth_tide_with_constants(
|
|
451
702
|
&receiver_ecef_m,
|
|
452
703
|
epoch.year,
|
|
453
704
|
i32::from(epoch.month),
|
|
@@ -455,6 +706,7 @@ pub fn station_displacement_ecef_m(
|
|
|
455
706
|
fhr,
|
|
456
707
|
&sun_moon.sun,
|
|
457
708
|
&sun_moon.moon,
|
|
709
|
+
options.solid_earth_tide_constants,
|
|
458
710
|
)?;
|
|
459
711
|
StationDisplacement::add_component(&mut displacement.ecef_m, solid);
|
|
460
712
|
displacement.solid_earth_tide_ecef_m = Some(solid);
|
|
@@ -490,7 +742,7 @@ pub fn station_displacement_ecef_m(
|
|
|
490
742
|
displacement.ocean_loading_ecef_m = Some(ocean);
|
|
491
743
|
}
|
|
492
744
|
|
|
493
|
-
Ok(displacement)
|
|
745
|
+
Ok(gate.finish(displacement)?)
|
|
494
746
|
}
|
|
495
747
|
|
|
496
748
|
/// Evaluate station displacement for many epochs. Each element is equivalent to
|
|
@@ -507,6 +759,20 @@ pub fn station_displacement_ecef_m_batch(
|
|
|
507
759
|
.collect()
|
|
508
760
|
}
|
|
509
761
|
|
|
762
|
+
/// [`station_displacement_ecef_m_batch`] under an explicit UT1
|
|
763
|
+
/// [`ValidityMode`]; each row is [`station_displacement_ecef_m_with_validity`].
|
|
764
|
+
pub fn station_displacement_ecef_m_batch_with_validity(
|
|
765
|
+
position: StationDisplacementPosition,
|
|
766
|
+
epochs: &[StationDisplacementEpoch],
|
|
767
|
+
options: StationDisplacementOptions<'_>,
|
|
768
|
+
mode: ValidityMode,
|
|
769
|
+
) -> Vec<Result<Validated<StationDisplacement>, TideError>> {
|
|
770
|
+
epochs
|
|
771
|
+
.iter()
|
|
772
|
+
.map(|&epoch| station_displacement_ecef_m_with_validity(position, epoch, options, mode))
|
|
773
|
+
.collect()
|
|
774
|
+
}
|
|
775
|
+
|
|
510
776
|
/// Solid-earth tide displacement of an ITRF station, in metres (ECEF).
|
|
511
777
|
///
|
|
512
778
|
/// Arguments mirror the IERS reference routine:
|
|
@@ -516,8 +782,12 @@ pub fn station_displacement_ecef_m_batch(
|
|
|
516
782
|
/// * `xsun` - geocentric Sun position (m, ECEF).
|
|
517
783
|
/// * `xmon` - geocentric Moon position (m, ECEF).
|
|
518
784
|
///
|
|
519
|
-
/// Returns the displacement vector `dxtide` (m, geocentric ITRF)
|
|
520
|
-
///
|
|
785
|
+
/// Returns the displacement vector `dxtide` (m, geocentric ITRF), permanent
|
|
786
|
+
/// part included, so corrected coordinates are conventional tide free, as the
|
|
787
|
+
/// ITRF is.
|
|
788
|
+
///
|
|
789
|
+
/// The diurnal Step 2 constants are [`StationTideConstants::Conventions`];
|
|
790
|
+
/// [`solid_earth_tide_with_constants`] chooses them.
|
|
521
791
|
///
|
|
522
792
|
/// Returns [`TideError`] when inputs are non-finite or geometrically
|
|
523
793
|
/// degenerate: the station vector must be non-zero and non-polar, and Sun/Moon
|
|
@@ -530,10 +800,36 @@ pub fn solid_earth_tide(
|
|
|
530
800
|
fhr: f64,
|
|
531
801
|
xsun: &[f64; 3],
|
|
532
802
|
xmon: &[f64; 3],
|
|
803
|
+
) -> Result<[f64; 3], TideError> {
|
|
804
|
+
solid_earth_tide_with_constants(
|
|
805
|
+
xsta,
|
|
806
|
+
year,
|
|
807
|
+
month,
|
|
808
|
+
day,
|
|
809
|
+
fhr,
|
|
810
|
+
xsun,
|
|
811
|
+
xmon,
|
|
812
|
+
StationTideConstants::Conventions,
|
|
813
|
+
)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/// [`solid_earth_tide`] with the diurnal Step 2 constants `constants`.
|
|
817
|
+
/// [`StationTideConstants::IersRoutine`] reproduces `DEHANTTIDEINEL.F` and
|
|
818
|
+
/// RTKLIB `tidedisp`.
|
|
819
|
+
#[allow(clippy::too_many_arguments)]
|
|
820
|
+
pub fn solid_earth_tide_with_constants(
|
|
821
|
+
xsta: &[f64; 3],
|
|
822
|
+
year: i32,
|
|
823
|
+
month: i32,
|
|
824
|
+
day: i32,
|
|
825
|
+
fhr: f64,
|
|
826
|
+
xsun: &[f64; 3],
|
|
827
|
+
xmon: &[f64; 3],
|
|
828
|
+
constants: StationTideConstants,
|
|
533
829
|
) -> Result<[f64; 3], TideError> {
|
|
534
830
|
validate_tide_domain(xsta, year, month, day, fhr, xsun, xmon)?;
|
|
535
831
|
Ok(solid_earth_tide_unchecked(
|
|
536
|
-
xsta, year, month, day, fhr, xsun, xmon,
|
|
832
|
+
xsta, year, month, day, fhr, xsun, xmon, constants,
|
|
537
833
|
))
|
|
538
834
|
}
|
|
539
835
|
|
|
@@ -572,6 +868,7 @@ fn validate_tide_domain(
|
|
|
572
868
|
Ok(())
|
|
573
869
|
}
|
|
574
870
|
|
|
871
|
+
#[allow(clippy::too_many_arguments)]
|
|
575
872
|
fn solid_earth_tide_unchecked(
|
|
576
873
|
xsta: &[f64; 3],
|
|
577
874
|
year: i32,
|
|
@@ -580,6 +877,7 @@ fn solid_earth_tide_unchecked(
|
|
|
580
877
|
fhr: f64,
|
|
581
878
|
xsun: &[f64; 3],
|
|
582
879
|
xmon: &[f64; 3],
|
|
880
|
+
constants: StationTideConstants,
|
|
583
881
|
) -> [f64; 3] {
|
|
584
882
|
// Nominal second- and third-degree Love and Shida numbers.
|
|
585
883
|
const H20: f64 = 0.6078;
|
|
@@ -606,8 +904,10 @@ fn solid_earth_tide_unchecked(
|
|
|
606
904
|
let p2mon = 3.0 * (h2 / 2.0 - l2) * scmon * scmon - h2 / 2.0;
|
|
607
905
|
|
|
608
906
|
// P3 term.
|
|
609
|
-
let
|
|
610
|
-
let
|
|
907
|
+
let scsun3 = scsun * scsun * scsun;
|
|
908
|
+
let scmon3 = scmon * scmon * scmon;
|
|
909
|
+
let p3sun = 5.0 / 2.0 * (H3 - 3.0 * L3) * scsun3 + 3.0 / 2.0 * (L3 - H3) * scsun;
|
|
910
|
+
let p3mon = 5.0 / 2.0 * (H3 - 3.0 * L3) * scmon3 + 3.0 / 2.0 * (L3 - H3) * scmon;
|
|
611
911
|
|
|
612
912
|
// Term in direction of Sun/Moon vector.
|
|
613
913
|
let x2sun = 3.0 * l2 * scsun;
|
|
@@ -619,8 +919,10 @@ fn solid_earth_tide_unchecked(
|
|
|
619
919
|
const MASS_RATIO_SUN: f64 = 332946.0482;
|
|
620
920
|
const MASS_RATIO_MOON: f64 = 0.0123000371;
|
|
621
921
|
const RE: f64 = SOLID_TIDE_EARTH_RADIUS_M;
|
|
622
|
-
let
|
|
623
|
-
let
|
|
922
|
+
let re_over_rsun = RE / rsun;
|
|
923
|
+
let re_over_rmon = RE / rmon;
|
|
924
|
+
let fac2sun = MASS_RATIO_SUN * RE * re_over_rsun * re_over_rsun * re_over_rsun;
|
|
925
|
+
let fac2mon = MASS_RATIO_MOON * RE * re_over_rmon * re_over_rmon * re_over_rmon;
|
|
624
926
|
let fac3sun = fac2sun * (RE / rsun);
|
|
625
927
|
let fac3mon = fac2mon * (RE / rmon);
|
|
626
928
|
|
|
@@ -651,10 +953,10 @@ fn solid_earth_tide_unchecked(
|
|
|
651
953
|
let (jjm0, jjm1) = gregorian_to_two_part_julian_date(year, month, day);
|
|
652
954
|
let fhrd = fhr / 24.0;
|
|
653
955
|
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;
|
|
956
|
+
let dtt = tai_minus_utc_seconds(year, month, day, fhrd) + TT_MINUS_TAI_S;
|
|
655
957
|
t += dtt / (SECONDS_PER_DAY * DAYS_PER_JULIAN_CENTURY);
|
|
656
958
|
|
|
657
|
-
let c = frequency_dependent_diurnal_correction(xsta, fhr, t);
|
|
959
|
+
let c = frequency_dependent_diurnal_correction(xsta, fhr, t, constants.diurnal_table());
|
|
658
960
|
for i in 0..3 {
|
|
659
961
|
dxtide[i] += c[i];
|
|
660
962
|
}
|
|
@@ -664,8 +966,8 @@ fn solid_earth_tide_unchecked(
|
|
|
664
966
|
}
|
|
665
967
|
|
|
666
968
|
// Step 3 of the IERS routine, the permanent (zero-frequency) tide removal,
|
|
667
|
-
//
|
|
668
|
-
//
|
|
969
|
+
// stays out as it does in the routine, so the displacement includes its
|
|
970
|
+
// permanent part and corrected coordinates are conventional tide free.
|
|
669
971
|
dxtide
|
|
670
972
|
}
|
|
671
973
|
|
|
@@ -875,44 +1177,117 @@ fn latitude_dependence_correction(
|
|
|
875
1177
|
xcorsta
|
|
876
1178
|
}
|
|
877
1179
|
|
|
1180
|
+
/// Constants of the diurnal-band frequency-dependent (Step 2) station tide.
|
|
1181
|
+
///
|
|
1182
|
+
/// See the module documentation for the three rows in which the variants
|
|
1183
|
+
/// differ and the sources for each.
|
|
1184
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
|
1185
|
+
pub enum StationTideConstants {
|
|
1186
|
+
/// The IERS Conventions (2010) Chapter 7 text: Table 7.3a with the P1
|
|
1187
|
+
/// sign it misprints corrected, and tide 166,564 as Tables 6.5a and 7.2
|
|
1188
|
+
/// list it.
|
|
1189
|
+
#[default]
|
|
1190
|
+
Conventions,
|
|
1191
|
+
/// The `DATDI` array of the IERS routine `DEHANTTIDEINEL.F` (`STEP2DIU`)
|
|
1192
|
+
/// as distributed, which RTKLIB also uses.
|
|
1193
|
+
IersRoutine,
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
impl StationTideConstants {
|
|
1197
|
+
fn diurnal_table(self) -> &'static [[f64; 9]; 31] {
|
|
1198
|
+
match self {
|
|
1199
|
+
Self::Conventions => &DIURNAL_BAND_CONVENTIONS,
|
|
1200
|
+
Self::IersRoutine => &DIURNAL_BAND_IERS_ROUTINE,
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Diurnal-band tables, 31 rows: multipliers of s, h, p, N', ps (tau's is 1),
|
|
1206
|
+
// then the radial in-phase, radial out-of-phase, transverse in-phase and
|
|
1207
|
+
// transverse out-of-phase amplitudes in mm, the columns of Table 7.3a.
|
|
1208
|
+
|
|
1209
|
+
/// `DATDI` of `STEP2DIU.F` as distributed.
|
|
1210
|
+
#[rustfmt::skip]
|
|
1211
|
+
const DIURNAL_BAND_IERS_ROUTINE: [[f64; 9]; 31] = [
|
|
1212
|
+
[-3.0, 0.0, 2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1213
|
+
[-3.0, 2.0, 0.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1214
|
+
[-2.0, 0.0, 1.0, -1.0, 0.0, -0.02, 0.0, 0.0, 0.0],
|
|
1215
|
+
[-2.0, 0.0, 1.0, 0.0, 0.0, -0.08, 0.0, -0.01, 0.01],
|
|
1216
|
+
[-2.0, 2.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
|
|
1217
|
+
[-1.0, 0.0, 0.0, -1.0, 0.0, -0.10, 0.0, 0.0, 0.0],
|
|
1218
|
+
[-1.0, 0.0, 0.0, 0.0, 0.0, -0.51, 0.0, -0.02, 0.03],
|
|
1219
|
+
[-1.0, 2.0, 0.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1220
|
+
[0.0, -2.0, 1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1221
|
+
[0.0, 0.0, -1.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0],
|
|
1222
|
+
[0.0, 0.0, 1.0, 0.0, 0.0, 0.06, 0.0, 0.0, 0.0],
|
|
1223
|
+
[0.0, 0.0, 1.0, 1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1224
|
+
[0.0, 2.0, -1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1225
|
+
[1.0, -3.0, 0.0, 0.0, 1.0, -0.06, 0.0, 0.0, 0.0],
|
|
1226
|
+
[1.0, -2.0, 0.0, -1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1227
|
+
[1.0, -2.0, 0.0, 0.0, 0.0, -1.23, -0.07, 0.06, 0.01],
|
|
1228
|
+
[1.0, -1.0, 0.0, 0.0, -1.0, 0.02, 0.0, 0.0, 0.0],
|
|
1229
|
+
[1.0, -1.0, 0.0, 0.0, 1.0, 0.04, 0.0, 0.0, 0.0],
|
|
1230
|
+
[1.0, 0.0, 0.0, -1.0, 0.0, -0.22, 0.01, 0.01, 0.0],
|
|
1231
|
+
[1.0, 0.0, 0.0, 0.0, 0.0, 12.00, -0.80, -0.67, -0.03],
|
|
1232
|
+
[1.0, 0.0, 0.0, 1.0, 0.0, 1.73, -0.12, -0.10, 0.0],
|
|
1233
|
+
[1.0, 0.0, 0.0, 2.0, 0.0, -0.04, 0.0, 0.0, 0.0],
|
|
1234
|
+
[1.0, 1.0, 0.0, 0.0, -1.0, -0.50, -0.01, 0.03, 0.0],
|
|
1235
|
+
[1.0, 1.0, 0.0, 0.0, 1.0, 0.01, 0.0, 0.0, 0.0],
|
|
1236
|
+
[0.0, 1.0, 0.0, 1.0, -1.0, -0.01, 0.0, 0.0, 0.0],
|
|
1237
|
+
[1.0, 2.0, -2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1238
|
+
[1.0, 2.0, 0.0, 0.0, 0.0, -0.11, 0.01, 0.01, 0.0],
|
|
1239
|
+
[2.0, -2.0, 1.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1240
|
+
[2.0, 0.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
|
|
1241
|
+
[3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
1242
|
+
[3.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
1243
|
+
];
|
|
1244
|
+
|
|
1245
|
+
/// `DATDI` with three rows corrected to the Conventions text: row 16 (P1)
|
|
1246
|
+
/// out-of-phase radial +0.07, row 20 (K1) out-of-phase radial -0.78, and
|
|
1247
|
+
/// row 25 tide 166,564 (s multiplier 1).
|
|
1248
|
+
#[rustfmt::skip]
|
|
1249
|
+
const DIURNAL_BAND_CONVENTIONS: [[f64; 9]; 31] = [
|
|
1250
|
+
[-3.0, 0.0, 2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1251
|
+
[-3.0, 2.0, 0.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1252
|
+
[-2.0, 0.0, 1.0, -1.0, 0.0, -0.02, 0.0, 0.0, 0.0],
|
|
1253
|
+
[-2.0, 0.0, 1.0, 0.0, 0.0, -0.08, 0.0, -0.01, 0.01],
|
|
1254
|
+
[-2.0, 2.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
|
|
1255
|
+
[-1.0, 0.0, 0.0, -1.0, 0.0, -0.10, 0.0, 0.0, 0.0],
|
|
1256
|
+
[-1.0, 0.0, 0.0, 0.0, 0.0, -0.51, 0.0, -0.02, 0.03],
|
|
1257
|
+
[-1.0, 2.0, 0.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1258
|
+
[0.0, -2.0, 1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1259
|
+
[0.0, 0.0, -1.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0],
|
|
1260
|
+
[0.0, 0.0, 1.0, 0.0, 0.0, 0.06, 0.0, 0.0, 0.0],
|
|
1261
|
+
[0.0, 0.0, 1.0, 1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1262
|
+
[0.0, 2.0, -1.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1263
|
+
[1.0, -3.0, 0.0, 0.0, 1.0, -0.06, 0.0, 0.0, 0.0],
|
|
1264
|
+
[1.0, -2.0, 0.0, -1.0, 0.0, 0.01, 0.0, 0.0, 0.0],
|
|
1265
|
+
[1.0, -2.0, 0.0, 0.0, 0.0, -1.23, 0.07, 0.06, 0.01],
|
|
1266
|
+
[1.0, -1.0, 0.0, 0.0, -1.0, 0.02, 0.0, 0.0, 0.0],
|
|
1267
|
+
[1.0, -1.0, 0.0, 0.0, 1.0, 0.04, 0.0, 0.0, 0.0],
|
|
1268
|
+
[1.0, 0.0, 0.0, -1.0, 0.0, -0.22, 0.01, 0.01, 0.0],
|
|
1269
|
+
[1.0, 0.0, 0.0, 0.0, 0.0, 12.00, -0.78, -0.67, -0.03],
|
|
1270
|
+
[1.0, 0.0, 0.0, 1.0, 0.0, 1.73, -0.12, -0.10, 0.0],
|
|
1271
|
+
[1.0, 0.0, 0.0, 2.0, 0.0, -0.04, 0.0, 0.0, 0.0],
|
|
1272
|
+
[1.0, 1.0, 0.0, 0.0, -1.0, -0.50, -0.01, 0.03, 0.0],
|
|
1273
|
+
[1.0, 1.0, 0.0, 0.0, 1.0, 0.01, 0.0, 0.0, 0.0],
|
|
1274
|
+
[1.0, 1.0, 0.0, 1.0, -1.0, -0.01, 0.0, 0.0, 0.0],
|
|
1275
|
+
[1.0, 2.0, -2.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1276
|
+
[1.0, 2.0, 0.0, 0.0, 0.0, -0.11, 0.01, 0.01, 0.0],
|
|
1277
|
+
[2.0, -2.0, 1.0, 0.0, 0.0, -0.01, 0.0, 0.0, 0.0],
|
|
1278
|
+
[2.0, 0.0, -1.0, 0.0, 0.0, -0.02, 0.0, 0.0, 0.0],
|
|
1279
|
+
[3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
1280
|
+
[3.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
1281
|
+
];
|
|
1282
|
+
|
|
878
1283
|
/// In-phase / out-of-phase frequency-dependent corrections, diurnal band
|
|
879
1284
|
/// (STEP2DIU). `fhr` is UTC fractional hour, `t` is Julian centuries (TT).
|
|
880
|
-
fn frequency_dependent_diurnal_correction(
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
];
|
|
1285
|
+
fn frequency_dependent_diurnal_correction(
|
|
1286
|
+
xsta: &[f64; 3],
|
|
1287
|
+
fhr: f64,
|
|
1288
|
+
t: f64,
|
|
1289
|
+
datdi: &[[f64; 9]; 31],
|
|
1290
|
+
) -> [f64; 3] {
|
|
916
1291
|
let mut s = 218.31664563 + (481267.88194 + (-0.0014663889 + 0.00000185139 * t) * t) * t;
|
|
917
1292
|
let mut tau = fhr * 15.0
|
|
918
1293
|
+ 280.4606184
|
|
@@ -929,28 +1304,31 @@ fn frequency_dependent_diurnal_correction(xsta: &[f64; 3], fhr: f64, t: f64) ->
|
|
|
929
1304
|
let mut ps = 282.93734098
|
|
930
1305
|
+ (1.71945766667 + (0.00045688889 + (-0.00000001778 + -0.00000000334 * t) * t) * t) * t;
|
|
931
1306
|
|
|
932
|
-
s
|
|
933
|
-
tau
|
|
934
|
-
h
|
|
935
|
-
p
|
|
936
|
-
zns
|
|
937
|
-
ps
|
|
1307
|
+
s %= 360.0;
|
|
1308
|
+
tau %= 360.0;
|
|
1309
|
+
h %= 360.0;
|
|
1310
|
+
p %= 360.0;
|
|
1311
|
+
zns %= 360.0;
|
|
1312
|
+
ps %= 360.0;
|
|
938
1313
|
|
|
939
1314
|
let rsta = (xsta[0] * xsta[0] + xsta[1] * xsta[1] + xsta[2] * xsta[2]).sqrt();
|
|
940
1315
|
let sinphi = xsta[2] / rsta;
|
|
941
1316
|
let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta;
|
|
942
1317
|
let cosla = xsta[0] / cosphi / rsta;
|
|
943
1318
|
let sinla = xsta[1] / cosphi / rsta;
|
|
944
|
-
let zla = xsta[1]
|
|
1319
|
+
let zla = libm::atan2(xsta[1], xsta[0]);
|
|
945
1320
|
|
|
946
1321
|
let mut xcorsta = [0.0_f64; 3];
|
|
947
|
-
for w in
|
|
1322
|
+
for w in datdi {
|
|
948
1323
|
let thetaf = (tau + w[0] * s + w[1] * h + w[2] * p + w[3] * zns + w[4] * ps) * DEG_TO_RAD;
|
|
949
|
-
let
|
|
950
|
-
|
|
951
|
-
let
|
|
952
|
-
|
|
953
|
-
|
|
1324
|
+
let angle = thetaf + zla;
|
|
1325
|
+
let sin_angle = libm::sin(angle);
|
|
1326
|
+
let cos_angle = libm::cos(angle);
|
|
1327
|
+
let dr =
|
|
1328
|
+
w[5] * 2.0 * sinphi * cosphi * sin_angle + w[6] * 2.0 * sinphi * cosphi * cos_angle;
|
|
1329
|
+
let dn = w[7] * (cosphi * cosphi - sinphi * sinphi) * sin_angle
|
|
1330
|
+
+ w[8] * (cosphi * cosphi - sinphi * sinphi) * cos_angle;
|
|
1331
|
+
let de = w[7] * sinphi * cos_angle - w[8] * sinphi * sin_angle;
|
|
954
1332
|
|
|
955
1333
|
xcorsta[0] += dr * cosla * cosphi - de * sinla - dn * sinphi * cosla;
|
|
956
1334
|
xcorsta[1] += dr * sinla * cosphi + de * cosla - dn * sinphi * sinla;
|
|
@@ -965,6 +1343,11 @@ fn frequency_dependent_diurnal_correction(xsta: &[f64; 3], fhr: f64, t: f64) ->
|
|
|
965
1343
|
/// In-phase / out-of-phase frequency-dependent corrections, long-period band
|
|
966
1344
|
/// (STEP2LON). `t` is Julian centuries (TT).
|
|
967
1345
|
fn frequency_dependent_long_period_correction(xsta: &[f64; 3], t: f64) -> [f64; 3] {
|
|
1346
|
+
// DATDI(9,5): multipliers of s, h, p, N', ps, then the radial in-phase,
|
|
1347
|
+
// transverse in-phase, radial out-of-phase and transverse out-of-phase
|
|
1348
|
+
// amplitudes in mm (Table 7.3b lists the same values in the order radial
|
|
1349
|
+
// in-phase, radial out-of-phase, transverse in-phase, transverse
|
|
1350
|
+
// out-of-phase).
|
|
968
1351
|
#[rustfmt::skip]
|
|
969
1352
|
const DATDI: [[f64; 9]; 5] = [
|
|
970
1353
|
[0.0, 0.0, 0.0, 1.0, 0.0, 0.47, 0.23, 0.16, 0.07],
|
|
@@ -991,19 +1374,21 @@ fn frequency_dependent_long_period_correction(xsta: &[f64; 3], t: f64) -> [f64;
|
|
|
991
1374
|
let cosla = xsta[0] / cosphi / rsta;
|
|
992
1375
|
let sinla = xsta[1] / cosphi / rsta;
|
|
993
1376
|
|
|
994
|
-
s
|
|
995
|
-
h
|
|
996
|
-
p
|
|
997
|
-
zns
|
|
998
|
-
ps
|
|
1377
|
+
s %= 360.0;
|
|
1378
|
+
h %= 360.0;
|
|
1379
|
+
p %= 360.0;
|
|
1380
|
+
zns %= 360.0;
|
|
1381
|
+
ps %= 360.0;
|
|
999
1382
|
|
|
1000
1383
|
let mut xcorsta = [0.0_f64; 3];
|
|
1001
1384
|
for w in &DATDI {
|
|
1002
1385
|
let thetaf = (w[0] * s + w[1] * h + w[2] * p + w[3] * zns + w[4] * ps) * DEG_TO_RAD;
|
|
1003
|
-
let
|
|
1004
|
-
|
|
1005
|
-
let
|
|
1006
|
-
+ w[
|
|
1386
|
+
let sin_theta = libm::sin(thetaf);
|
|
1387
|
+
let cos_theta = libm::cos(thetaf);
|
|
1388
|
+
let dr = w[5] * (3.0 * sinphi * sinphi - 1.0) / 2.0 * cos_theta
|
|
1389
|
+
+ w[7] * (3.0 * sinphi * sinphi - 1.0) / 2.0 * sin_theta;
|
|
1390
|
+
let dn =
|
|
1391
|
+
w[6] * (cosphi * sinphi * 2.0) * cos_theta + w[8] * (cosphi * sinphi * 2.0) * sin_theta;
|
|
1007
1392
|
let de = 0.0;
|
|
1008
1393
|
|
|
1009
1394
|
xcorsta[0] += dr * cosla * cosphi - de * sinla - dn * sinphi * cosla;
|
|
@@ -1036,10 +1421,30 @@ fn gregorian_to_two_part_julian_date(iy: i32, im: i32, id: i32) -> (f64, f64) {
|
|
|
1036
1421
|
(djm0, djm)
|
|
1037
1422
|
}
|
|
1038
1423
|
|
|
1039
|
-
/// TAI-UTC (Delta(AT)) in seconds for the given date
|
|
1040
|
-
///
|
|
1041
|
-
/// pre-
|
|
1042
|
-
|
|
1424
|
+
/// TAI-UTC (Delta(AT)) in seconds for the given UTC date and fraction of day
|
|
1425
|
+
/// `fd`, as the SOFA `DAT` routine distributed with DEHANTTIDEINEL returns it:
|
|
1426
|
+
/// 0 before 1960 (the routine's "pre-UTC year" warning path, whose result
|
|
1427
|
+
/// DEHANTTIDEINEL uses as is), the 1960-1971 offsets with their drift terms,
|
|
1428
|
+
/// and the leap-second table from 1972. Dates after the last entry keep its
|
|
1429
|
+
/// value.
|
|
1430
|
+
fn tai_minus_utc_seconds(iy: i32, im: i32, id: i32, fd: f64) -> f64 {
|
|
1431
|
+
// 1960-1971: (year, month, Delta(AT) seconds, reference MJD, drift s/day).
|
|
1432
|
+
const DRIFT: [(i32, i32, f64, f64, f64); 14] = [
|
|
1433
|
+
(1960, 1, 1.4178180, 37300.0, 0.001296),
|
|
1434
|
+
(1961, 1, 1.4228180, 37300.0, 0.001296),
|
|
1435
|
+
(1961, 8, 1.3728180, 37300.0, 0.001296),
|
|
1436
|
+
(1962, 1, 1.8458580, 37665.0, 0.0011232),
|
|
1437
|
+
(1963, 11, 1.9458580, 37665.0, 0.0011232),
|
|
1438
|
+
(1964, 1, 3.2401300, 38761.0, 0.001296),
|
|
1439
|
+
(1964, 4, 3.3401300, 38761.0, 0.001296),
|
|
1440
|
+
(1964, 9, 3.4401300, 38761.0, 0.001296),
|
|
1441
|
+
(1965, 1, 3.5401300, 38761.0, 0.001296),
|
|
1442
|
+
(1965, 3, 3.6401300, 38761.0, 0.001296),
|
|
1443
|
+
(1965, 7, 3.7401300, 38761.0, 0.001296),
|
|
1444
|
+
(1965, 9, 3.8401300, 38761.0, 0.001296),
|
|
1445
|
+
(1966, 1, 4.3131700, 39126.0, 0.002592),
|
|
1446
|
+
(1968, 2, 4.2131700, 39126.0, 0.002592),
|
|
1447
|
+
];
|
|
1043
1448
|
// Post-1972 leap-second table: (year, month, Delta(AT) seconds).
|
|
1044
1449
|
const IDAT: [(i32, i32, f64); 28] = [
|
|
1045
1450
|
(1972, 1, 10.0),
|
|
@@ -1071,12 +1476,26 @@ fn tai_minus_utc_seconds(iy: i32, im: i32, _id: i32) -> f64 {
|
|
|
1071
1476
|
(2015, 7, 36.0),
|
|
1072
1477
|
(2017, 1, 37.0),
|
|
1073
1478
|
];
|
|
1479
|
+
if iy < DRIFT[0].0 {
|
|
1480
|
+
return 0.0;
|
|
1481
|
+
}
|
|
1074
1482
|
let m = 12 * iy + im;
|
|
1075
|
-
let mut
|
|
1483
|
+
let mut leap = None;
|
|
1076
1484
|
for &(y, mo, d) in &IDAT {
|
|
1077
1485
|
if m >= 12 * y + mo {
|
|
1078
|
-
|
|
1486
|
+
leap = Some(d);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if let Some(da) = leap {
|
|
1490
|
+
return da;
|
|
1491
|
+
}
|
|
1492
|
+
let mut era = DRIFT[0];
|
|
1493
|
+
for entry in DRIFT {
|
|
1494
|
+
if m >= 12 * entry.0 + entry.1 {
|
|
1495
|
+
era = entry;
|
|
1079
1496
|
}
|
|
1080
1497
|
}
|
|
1081
|
-
|
|
1498
|
+
let (_, djm) = gregorian_to_two_part_julian_date(iy, im, id);
|
|
1499
|
+
let (_, _, da, reference_mjd, rate) = era;
|
|
1500
|
+
da + (djm + fd - reference_mjd) * rate
|
|
1082
1501
|
}
|