Spaces:
Configuration error
Configuration error
| // The TEME to pseudo-fixed rotation, as arithmetic on epoch milliseconds. | |
| // | |
| // Cesium's `Transforms.computeTemeToPseudoFixedMatrix` is a rotation about Z by a | |
| // single angle, and everything around it is overhead on six useful flops: a | |
| // `JulianDate` per sample, a leap-second lookup, a `Matrix3` allocated because the | |
| // call site passes no result, and a full 3x3 multiply. Measured over 5,000 | |
| // satellites of 241 samples, the transform half of a window fill: | |
| // | |
| // Cesium, as #applyChunk called it 220 ms | |
| // Cesium, with scratch objects 105 ms | |
| // this, rotation written inline 31 ms | |
| // | |
| // It is the same formula rather than an approximation of it — the constants below | |
| // are Cesium's — so the two agree to 0.001 mm over 42 anchors spanning three years, | |
| // including the midnight and noon crossings where the parameterisation changes | |
| // branch. The point is not a cheaper transform but a Cesium-free one: it can move | |
| // into the propagation worker, where the main thread stops paying for it at all. | |
| // | |
| // Deliberately *not* the GMST that satellite.js's `gstime` computes. That evaluates | |
| // the polynomial at the instant where this evaluates it at 0h and adds Earth's | |
| // rotation rate since; they differ by 1.5e-9 rad, which is 1 cm at LEO and 6 cm at | |
| // GEO. Immaterial on its own — the interpolation error in GridPositionProperty is | |
| // metres — but this way the stored positions stay bit-identical to what Cesium's own | |
| // transform produced, so the change is invisible to everything downstream. | |
| /** Cesium's GMST polynomial, in seconds, evaluated in Julian centuries from J2000. */ | |
| const GMST_C0 = 6 * 3600 + 41 * 60 + 50.54841; | |
| const GMST_C1 = 8640184.812866; | |
| const GMST_C2 = 0.093104; | |
| const GMST_C3 = -6.2e-6; | |
| /** Precession of the rotation rate, per day from J2000. */ | |
| const RATE_COEF = 1.1772758384668e-19; | |
| const WGS84_WR_PRECESSING = 7.2921158553e-5; | |
| const TWO_PI = Math.PI * 2; | |
| const SECONDS_PER_DAY = 86400; | |
| const TWO_PI_PER_SECONDS_PER_DAY = TWO_PI / SECONDS_PER_DAY; | |
| const MS_PER_DAY = 86_400_000; | |
| const J2000_DAY_NUMBER = 2451545; | |
| /** J2000 proper is noon, so the rate term is referred to the midnight before it. */ | |
| const J2000_MIDNIGHT = J2000_DAY_NUMBER - 0.5; | |
| /** | |
| * Cesium splits a Julian date this way, and the Unix epoch lands mid-day in it — | |
| * a Julian day starts at noon, so 00:00 UTC is half a day in. | |
| */ | |
| const UNIX_EPOCH_DAY_NUMBER = 2440587; | |
| const HALF_DAY_SECONDS = 43200; | |
| /** | |
| * Greenwich hour angle for a UTC instant, in radians. | |
| * | |
| * Unix time is already UTC, which is the frame Cesium reduces to before doing this | |
| * arithmetic — it converts UTC to TAI on the way in and subtracts `taiMinusUtc` back | |
| * off here — so taking epoch milliseconds skips a round trip rather than skipping a | |
| * correction. The one case where that is not merely equivalent is an interval | |
| * spanning a leap second, where TAI and Unix time disagree about how many seconds | |
| * elapsed; see the test, which pins the bound rather than assuming it away. | |
| */ | |
| export function greenwichHourAngle(epochMs: number): number { | |
| // The day is split off as an integer first. Carrying the whole Julian date in one | |
| // double puts the value near 2.44e6, whose ulp is about 47 us of Earth rotation — | |
| // measured as a 9.5 mm error before this was split. Keeping `secondsOfDay` small | |
| // keeps its resolution. | |
| const days = Math.floor(epochMs / MS_PER_DAY); | |
| const msIntoDay = epochMs - days * MS_PER_DAY; | |
| let dayNumber = UNIX_EPOCH_DAY_NUMBER + days; | |
| let secondsOfDay = HALF_DAY_SECONDS + msIntoDay / 1000; | |
| if (secondsOfDay >= SECONDS_PER_DAY) { | |
| secondsOfDay -= SECONDS_PER_DAY; | |
| dayNumber += 1; | |
| } | |
| // GMST is tabulated at 0h, so the half-day offset picks the tabulation this | |
| // instant belongs to. | |
| const centuries = (dayNumber - J2000_DAY_NUMBER + (secondsOfDay >= HALF_DAY_SECONDS ? 0.5 : -0.5)) / 36525; | |
| const gmstSeconds = GMST_C0 + centuries * (GMST_C1 + centuries * (GMST_C2 + centuries * GMST_C3)); | |
| const angleAt0h = (gmstSeconds * TWO_PI_PER_SECONDS_PER_DAY) % TWO_PI; | |
| const rotationRate = WGS84_WR_PRECESSING + RATE_COEF * (dayNumber - J2000_MIDNIGHT); | |
| const secondsSinceMidnight = (secondsOfDay + HALF_DAY_SECONDS) % SECONDS_PER_DAY; | |
| return angleAt0h + rotationRate * secondsSinceMidnight; | |
| } | |
| /** | |
| * The rotation for one instant, as the cosine and sine of the hour angle. | |
| * | |
| * Handed back as a pair rather than a matrix because seven of the nine entries are | |
| * constant: the fixed-frame position is | |
| * `(c*x + s*y, -s*x + c*y, z)`. | |
| */ | |
| export interface FixedRotation { | |
| cos: number; | |
| sin: number; | |
| } | |
| export function fixedRotationAt(epochMs: number, result: FixedRotation): FixedRotation { | |
| const gha = greenwichHourAngle(epochMs); | |
| result.cos = Math.cos(gha); | |
| result.sin = Math.sin(gha); | |
| return result; | |
| } | |