@saber-usa/node-common 1.7.7-alpha.2 → 1.7.7

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.
@@ -1,1121 +1,1121 @@
1
- // FrameConverter.js - Port from C# FrameConverter.cs
2
- import {Matrix3D, Vector3D} from "pious-squid";
3
- import {LLA} from "./LLA.js";
4
- import {TimeConverter, RaDec} from "./TimeConverter.js";
5
- import {norm, cross} from "mathjs";
6
- import {DEG2RAD} from "./constants.js";
7
-
8
-
9
- // Enums (assuming these exist elsewhere)
10
- const ReferenceFrame = {
11
- J2000: "J2000",
12
- TEME: "TEME",
13
- GCRF: "GCRF",
14
- ITRF: "ITRF",
15
- };
16
-
17
- // Constants (assuming these exist elsewhere)
18
- const Constants = {
19
- TwoPi: 2 * Math.PI,
20
- RadToDeg: 180 / Math.PI,
21
- DegToRad: Math.PI / 180,
22
- ArcsecToRad: Math.PI / (180 * 3600),
23
- SecondToRad: Math.PI / (180 * 3600),
24
- SecPerSolarDay: 86400,
25
- };
26
-
27
- // Earth constants (assuming these exist elsewhere)
28
- const Earth = {
29
- EquatorialRadiusKm: 6378.137,
30
- EccentricityWgs84: 0.0818191908426,
31
- SpinRateRadSec: 7.2921159e-5,
32
- };
33
-
34
- class FrameConverter {
35
- /**
36
- * Helper method to transform a vector using a Matrix3D
37
- * @param {Vector3D} vector - Vector to transform
38
- * @param {Matrix3D} matrix - Matrix to use for transformation
39
- * @return {Vector3D} Transformed vector
40
- */
41
- static transformVector(vector, matrix) {
42
- // Manual matrix-vector multiplication since pious-squid has issues
43
- const x = matrix.get(0, 0) * vector.x
44
- + matrix.get(0, 1) * vector.y + matrix.get(0, 2) * vector.z;
45
- const y = matrix.get(1, 0) * vector.x
46
- + matrix.get(1, 1) * vector.y + matrix.get(1, 2) * vector.z;
47
- const z = matrix.get(2, 0) * vector.x
48
- + matrix.get(2, 1) * vector.y + matrix.get(2, 2) * vector.z;
49
- return new Vector3D(x, y, z);
50
- }
51
-
52
- /**
53
- * Rotate a vector around an axis by an angle (Rodrigues' rotation formula)
54
- * @param {Vector3D} vector - Vector to rotate
55
- * @param {Vector3D} axis - Axis to rotate around (should be normalized)
56
- * @param {number} angleRad - Angle in radians
57
- * @return {Vector3D} Rotated vector
58
- */
59
- static rotateVector(vector, axis, angleRad) {
60
- // Ensure axis is normalized
61
- const k = axis.normalized();
62
- const v = vector;
63
- const cosTheta = Math.cos(angleRad);
64
- const sinTheta = Math.sin(angleRad);
65
-
66
- // Rodrigues' rotation formula:
67
- // v_rot = v*cos(θ) + (k × v)*sin(θ) + k*(k·v)*(1-cos(θ))
68
-
69
- // k × v (cross product)
70
- const kCrossV = new Vector3D(
71
- k.y * v.z - k.z * v.y,
72
- k.z * v.x - k.x * v.z,
73
- k.x * v.y - k.y * v.x,
74
- );
75
-
76
- // k·v (dot product)
77
- const kDotV = k.x * v.x + k.y * v.y + k.z * v.z;
78
-
79
- // Final rotation
80
- return new Vector3D(
81
- v.x * cosTheta + kCrossV.x * sinTheta + k.x * kDotV * (1 - cosTheta),
82
- v.y * cosTheta + kCrossV.y * sinTheta + k.y * kDotV * (1 - cosTheta),
83
- v.z * cosTheta + kCrossV.z * sinTheta + k.z * kDotV * (1 - cosTheta),
84
- );
85
- }
86
-
87
- /**
88
- * Helper to transpose a Matrix3D
89
- * @param {Matrix3D} m - The matrix to transpose
90
- * @return {Matrix3D} The transposed matrix
91
- */
92
- static transposeMatrix(m) {
93
- return new Matrix3D(
94
- m.get(0, 0), m.get(1, 0), m.get(2, 0),
95
- m.get(0, 1), m.get(1, 1), m.get(2, 1),
96
- m.get(0, 2), m.get(1, 2), m.get(2, 2),
97
- );
98
- }
99
-
100
- /**
101
- * Helper to invert a 3x3 matrix
102
- * @param {Matrix3D} m - The matrix to invert
103
- * @return {Matrix3D} The inverted matrix
104
- */
105
- static invertMatrix(m) {
106
- const a = m.get(0, 0); const b = m.get(0, 1); const c = m.get(0, 2);
107
- const d = m.get(1, 0); const e = m.get(1, 1); const f = m.get(1, 2);
108
- const g = m.get(2, 0); const h = m.get(2, 1); const i = m.get(2, 2);
109
-
110
- const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
111
-
112
- if (Math.abs(det) < 1e-10) {
113
- throw new Error("Matrix is not invertible");
114
- }
115
-
116
- const invDet = 1 / det;
117
-
118
- return new Matrix3D(
119
- new Vector3D(
120
- (e * i - f * h) * invDet,
121
- (c * h - b * i) * invDet,
122
- (b * f - c * e) * invDet,
123
- ),
124
- new Vector3D(
125
- (f * g - d * i) * invDet,
126
- (a * i - c * g) * invDet,
127
- (c * d - a * f) * invDet,
128
- ),
129
- new Vector3D(
130
- (d * h - e * g) * invDet,
131
- (b * g - a * h) * invDet,
132
- (a * e - b * d) * invDet,
133
- ),
134
- );
135
- }
136
-
137
- /**
138
- * Helper to multiply two matrices
139
- * @param {Matrix3D} m1 - The first matrix
140
- * @param {Matrix3D} m2 - The second matrix
141
- * @return {Matrix3D} The product of the two matrices
142
- */
143
- static multiplyMatrices(m1, m2) {
144
- // for (let i = 0; i < 3; i++) {
145
- // for (let j = 0; j < 3; j++) {
146
- // let sum = 0;
147
- // for (let k = 0; k < 3; k++) {
148
- // sum += m1.get(i, k) * m2.get(k, j);
149
- // }
150
- // // Matrix3D doesn't have a set method, so we need to create a new one
151
- // // This is a limitation - we'll work around it
152
- // }
153
- // }
154
- // Create new matrix with computed values
155
- return new Matrix3D(
156
- new Vector3D(
157
- m1.get(0, 0) * m2.get(0, 0)
158
- + m1.get(0, 1) * m2.get(1, 0) + m1.get(0, 2) * m2.get(2, 0),
159
- m1.get(0, 0) * m2.get(0, 1)
160
- + m1.get(0, 1) * m2.get(1, 1) + m1.get(0, 2) * m2.get(2, 1),
161
- m1.get(0, 0) * m2.get(0, 2)
162
- + m1.get(0, 1) * m2.get(1, 2) + m1.get(0, 2) * m2.get(2, 2)),
163
- new Vector3D(
164
- m1.get(1, 0) * m2.get(0, 0)
165
- + m1.get(1, 1) * m2.get(1, 0) + m1.get(1, 2) * m2.get(2, 0),
166
- m1.get(1, 0) * m2.get(0, 1)
167
- + m1.get(1, 1) * m2.get(1, 1) + m1.get(1, 2) * m2.get(2, 1),
168
- m1.get(1, 0) * m2.get(0, 2)
169
- + m1.get(1, 1) * m2.get(1, 2) + m1.get(1, 2) * m2.get(2, 2)),
170
- new Vector3D(
171
- m1.get(2, 0) * m2.get(0, 0)
172
- + m1.get(2, 1) * m2.get(1, 0) + m1.get(2, 2) * m2.get(2, 0),
173
- m1.get(2, 0) * m2.get(0, 1)
174
- + m1.get(2, 1) * m2.get(1, 1) + m1.get(2, 2) * m2.get(2, 1),
175
- m1.get(2, 0) * m2.get(0, 2)
176
- + m1.get(2, 1) * m2.get(1, 2) + m1.get(2, 2) * m2.get(2, 2)),
177
- );
178
- }
179
-
180
- /**
181
- * Get transform from a frame to a destination frame at a given time.
182
- * Apply this to a position vector using: vec3d.TransformBy(matrix)
183
- * @param {string} from - Source reference frame
184
- * @param {string} to - Destination reference frame
185
- * @param {Date} utc - UTC time for the transformation
186
- * @return {Matrix3D} The transformation matrix
187
- */
188
- static getTransform(from, to, utc) {
189
- switch (from) {
190
- case ReferenceFrame.J2000:
191
- switch (to) {
192
- case ReferenceFrame.J2000:
193
- break;
194
- case ReferenceFrame.TEME:
195
- return this.j2000ToTEME(utc);
196
- case ReferenceFrame.GCRF:
197
- return this.j2000ToGCRF();
198
- case ReferenceFrame.ITRF:
199
- return this.j2000ToITRF(utc);
200
- default:
201
- break;
202
- }
203
- break;
204
- case ReferenceFrame.TEME:
205
- switch (to) {
206
- case ReferenceFrame.J2000:
207
- return this.temeToJ2000(utc);
208
- case ReferenceFrame.TEME:
209
- break;
210
- case ReferenceFrame.GCRF:
211
- return this.multiplyMatrices(this.j2000ToGCRF(), this.temeToJ2000(utc));
212
- case ReferenceFrame.ITRF:
213
- return this.multiplyMatrices(this.j2000ToITRF(utc), this.temeToJ2000(utc));
214
- default:
215
- break;
216
- }
217
- break;
218
- case ReferenceFrame.GCRF:
219
- switch (to) {
220
- case ReferenceFrame.J2000:
221
- return this.gcrfToJ2000();
222
- case ReferenceFrame.TEME:
223
- return this.multiplyMatrices(this.j2000ToTEME(utc), this.gcrfToJ2000());
224
- case ReferenceFrame.GCRF:
225
- break;
226
- case ReferenceFrame.ITRF:
227
- return this.multiplyMatrices(this.j2000ToITRF(utc), this.gcrfToJ2000());
228
- default:
229
- break;
230
- }
231
- break;
232
- case ReferenceFrame.ITRF:
233
- switch (to) {
234
- case ReferenceFrame.J2000:
235
- return this.itrfToJ2000(utc);
236
- case ReferenceFrame.TEME:
237
- return this.multiplyMatrices(this.j2000ToTEME(utc), this.itrfToJ2000(utc));
238
- case ReferenceFrame.GCRF:
239
- return this.multiplyMatrices(this.j2000ToGCRF(), this.itrfToJ2000(utc));
240
- case ReferenceFrame.ITRF:
241
- break;
242
- default:
243
- break;
244
- }
245
- break;
246
- default:
247
- break; // TODO: custom frame xforms, RIC, etc
248
- }
249
-
250
- // Return identity matrix
251
- return new Matrix3D(
252
- new Vector3D(1, 0, 0),
253
- new Vector3D(0, 1, 0),
254
- new Vector3D(0, 0, 1),
255
- );
256
- }
257
-
258
- /**
259
- * Convert from ITRF/ECEF to azimuth, elevation, range.
260
- * Optional velocity component will yield AzElRange rates, however this will slow down the computation.
261
- * @param {LLA} site - Ground site in LLA
262
- * @param {Vector3D} r - Position vector in ECEF frame
263
- * @param {Vector3D} [v=null] - Velocity vector in ECEF frame
264
- * @param {number} [planetRadius=Earth.EquatorialRadiusKm] - Planet radius
265
- * @param {number} [planetEccentricity=Earth.EccentricityWgs84] - Planet eccentricity
266
- * @return {AzElRange} Azimuth, elevation, and range
267
- */
268
- static itrfToAzElRange(site, r, v = null,
269
- planetRadius = Earth.EquatorialRadiusKm, planetEccentricity = Earth.EccentricityWgs84) {
270
- // Transform
271
- const ecef2sez = this.ecefToSEZ(site);
272
-
273
- // To SEZ
274
- const siteEcef = this.llaToECEF(site, planetRadius, planetEccentricity);
275
- const rhoEcef = r.subtract(siteEcef);
276
- const rhoDotEcef = v === null ? new Vector3D(0, 0, 0) : v;
277
- const rhoSez = ecef2sez.multiplyVector3D(rhoEcef);
278
- const rhoDotSez = ecef2sez.multiplyVector3D(rhoDotEcef);
279
-
280
- // AER
281
- const rho = rhoSez.magnitude();
282
- let sinBeta; let cosBeta;
283
- const el = Math.asin(rhoSez.z / rho);
284
-
285
- if (el * Constants.RadToDeg !== 90 || v === null) {
286
- sinBeta = rhoSez.y / Math.sqrt(Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
287
- cosBeta = -rhoSez.x / Math.sqrt(Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
288
- } else { // use rates
289
- sinBeta = rhoDotSez.y / Math.sqrt(Math.pow(rhoDotSez.x, 2) + Math.pow(rhoDotSez.y, 2));
290
- cosBeta = -rhoDotSez.x / Math.sqrt(Math.pow(rhoDotSez.x, 2) + Math.pow(rhoDotSez.y, 2));
291
- }
292
-
293
- let az = Math.atan2(sinBeta, cosBeta);
294
- // Wrap to [0, 2π]
295
- while (az < 0) az += Constants.TwoPi;
296
- while (az >= Constants.TwoPi) az -= Constants.TwoPi;
297
-
298
- // Rates
299
- if (v !== null) {
300
- const rhoDot = rhoSez.dot(rhoDotSez) / rho;
301
- const azDot = (
302
- rhoDotSez.x * rhoSez.y - rhoDotSez.y * rhoSez.x) / (
303
- Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
304
- const elDot = (rhoDotSez.z - rhoDot * Math.sin(el)) / Math.sqrt(
305
- Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
306
-
307
- return {
308
- azimuth: az,
309
- elevation: el,
310
- range: rho,
311
- azimuthDot: azDot,
312
- elevationDot: elDot,
313
- rangeDot: rhoDot,
314
- };
315
- }
316
-
317
- return {
318
- azimuth: az,
319
- elevation: el,
320
- range: rho,
321
- };
322
- }
323
-
324
- /**
325
- * Convert from azimuth, elevation, and range to ITRF/ECEF.
326
- * Will return velocity if AzElRange rates are not zero.
327
- * @param {AzElRange} aer - Azimuth, elevation, and range
328
- * @param {LLA} site - Ground site in LLA
329
- * @param {number} [planetRadius=Earth.EquatorialRadiusKm] - Planet radius
330
- * @param {number} [planetEccentricity=Earth.EccentricityWgs84] - Planet eccentricity
331
- * @return {Object} Position and velocity in ECEF
332
- */
333
- static azElRangeToITRF(aer, site, planetRadius = Earth.EquatorialRadiusKm,
334
- planetEccentricity = Earth.EccentricityWgs84) {
335
- const Ce = planetRadius / Math.sqrt(
336
- 1 - Math.pow(planetEccentricity, 2)
337
- * Math.pow(Math.sin(site.Latitude.Radians), 2));
338
-
339
- const Se = Ce * (1 - Math.pow(planetEccentricity, 2));
340
- const rd = (Ce + site.AltitudeKm) * Math.cos(site.Latitude.Radians);
341
- const rK = (Se + site.AltitudeKm) * Math.sin(site.Latitude.Radians);
342
-
343
- const rSiteEcef = new Vector3D(
344
- rd * Math.cos(site.Longitude.Radians),
345
- rd * Math.sin(site.Longitude.Radians),
346
- rK);
347
-
348
- const rho = aer.RangeKm;
349
- const drho = aer.RangeRatePerSec || 0;
350
- const daz = aer.AzimuthRatePerSec ? aer.AzimuthRatePerSec.Radians : 0;
351
- const del = aer.ElevationRatePerSec ? aer.ElevationRatePerSec.Radians : 0;
352
- const saz = Math.sin(aer.Azimuth.Radians);
353
- const caz = Math.cos(aer.Azimuth.Radians);
354
- const sel = Math.sin(aer.Elevation.Radians);
355
- const cel = Math.cos(aer.Elevation.Radians);
356
-
357
- const rhoSez = new Vector3D(-rho * cel * caz, rho * cel * saz, rho * sel);
358
- const rhoDotSez = new Vector3D(
359
- -drho * cel * caz + rho * sel * caz * del + rho * cel * saz * daz,
360
- drho * cel * saz - rho * sel * saz * del + rho * cel * caz * daz,
361
- drho * sel + rho * cel * del);
362
-
363
- const s2e = this.sezToECEF(site);
364
-
365
- const rhoEcef = s2e.multiplyVector3D(rhoSez);
366
- const rhoDotEcef = s2e.multiplyVector3D(rhoDotSez);
367
- const rEcef = rhoEcef.add(rSiteEcef);
368
- const vEcef = rhoDotEcef;
369
-
370
- return {Position: rEcef, Velocity: vEcef};
371
- }
372
-
373
- /**
374
- * Convert position and velocity in J2000 to Azimuth, elevation, and range and each of their rates.
375
- * @param {LLA} site - Ground site in LLA
376
- * @param {Date} utc - UTC time
377
- * @param {Vector3D} r - Position vector in J2000
378
- * @param {Vector3D} [v=null] - Velocity vector in J2000
379
- * @return {AzElRange} Azimuth, elevation, and range and their rates
380
- */
381
- static J2000ToAzElRange(site, utc, r, v = null) {
382
- // Transform
383
- const j2k2itrf = this.j2000ToITRF(utc);
384
-
385
- // SEZ vectors
386
- const rEcef = j2k2itrf.multiplyVector3D(r);
387
- const vEcef = v === null ? new Vector3D(0, 0, 0) : j2k2itrf.multiplyVector3D(v);
388
-
389
- return this.itrfToAzElRange(site, rEcef, v === null ? v : vEcef);
390
- }
391
-
392
- /**
393
- * Convert Az, El, Range to J2000 position and velocity.
394
- * @param {AzElRange} aer - Azimuth, elevation, and range
395
- * @param {LLA} site - Ground site in LLA
396
- * @param {Date} utc - UTC time
397
- * @return {Object} Position and velocity in J2000
398
- */
399
- static AzElRangeToJ2000(aer, site, utc) {
400
- const {Position: rEcef, Velocity: vEcef} = this.azElRangeToITRF(aer, site);
401
-
402
- return this.itrfToJ2000(utc, rEcef, vEcef === null ? new Vector3D(0, 0, 0) : vEcef);
403
- }
404
-
405
- /**
406
- * Converts a TEME position vector from an SGP4 propagation to Lat, Lon, Alt.
407
- * @param {Vector3D} r - Position vector in TEME
408
- * @param {Date} utc - UTC time
409
- * @return {LLA} Latitude, longitude, and altitude
410
- */
411
- static TEMEToLLA(r, utc) {
412
- return this.j2000ToLLA(this.temeToJ2000(utc).multiplyVector3D(r), utc);
413
- }
414
-
415
- /**
416
- * Convert Lat, Lon, Alt to a TEME position vector.
417
- * @param {LLA} lla - Latitude, longitude, and altitude
418
- * @param {Date} utc - UTC time
419
- * @return {Vector3D} Position vector in TEME
420
- */
421
- static LLAToTEME(lla, utc) {
422
- return this.j2000ToTEME(utc).multiplyVector3D(this.llaToJ2000(lla, utc));
423
- }
424
-
425
- /**
426
- * Converts a J2000 position vector to Lat, Lon, Alt.
427
- * J2000 is the default reference frame of the UDL and many other applications.
428
- * @param {Vector3D} r - Position vector in J2000
429
- * @param {Date} utc - UTC time
430
- * @return {LLA} Latitude, longitude, and altitude
431
- */
432
- static j2000ToLLA(r, utc) {
433
- return this.ecefToLLA(this.j2000ToITRF(utc).multiplyVector3D(r));
434
- }
435
-
436
- /**
437
- * Converts Lat, Lon, Alt to J2000 position vector.
438
- * @param {LLA} lla - Latitude, longitude, and altitude
439
- * @param {Date} utc - UTC time
440
- * @return {Vector3D} Position vector in J2000
441
- */
442
- static llaToJ2000(lla, utc) {
443
- return this.itrfToJ2000(utc).multiplyVector3D(this.llaToECEF(lla));
444
- }
445
-
446
- /**
447
- * Convert ECEF coordinates to Lat, Lon, Alt.
448
- * ECEF includes ITRF and WGS frames.
449
- * Vallado Fundamentals, p. 172
450
- * @param {Vector3D} r The vector r must be in ITRF frame (see j2000ToITRF()).
451
- * @param {number} planetRadKm
452
- * @param {number} planetEcc
453
- * @param {number} tol
454
- * @return {LLA} Latitude, longitude, and altitude
455
- */
456
- static ecefToLLA(r, planetRadKm = Earth.EquatorialRadiusKm,
457
- planetEcc = Earth.EccentricityWgs84, tol = 1e-6) {
458
- const rdels = Math.sqrt(Math.pow(r.x, 2) + Math.pow(r.y, 2));
459
- const lambda = Math.acos(r.x / rdels) * Math.sign(Math.asin(r.y / rdels));
460
- let phigd = Math.atan2(r.z, rdels);
461
- const rdel = rdels;
462
- const phigdOld = phigd;
463
- let C;
464
-
465
- let iter = 0;
466
-
467
- do {
468
- C = planetRadKm / Math.sqrt(1 - Math.pow(planetEcc, 2) * Math.pow(Math.sin(phigd), 2));
469
- phigd = Math.atan2(r.z + C * Math.pow(planetEcc, 2) * Math.sin(phigd), rdel);
470
- iter++;
471
- } while (phigd - phigdOld > tol && iter < 10);
472
-
473
- const nearPoles = r.angle(new Vector3D(0, 0, 1)) * Constants.RadToDeg < 1
474
- || r.angle(new Vector3D(0, 0, -1)) * Constants.RadToDeg < 1;
475
- const S = C * (1 - Math.pow(planetEcc, 2));
476
- const hEllip = nearPoles ? r.z / Math.sin(phigd) - S : rdel / Math.cos(phigd) - C;
477
-
478
- return new LLA(phigd * Constants.RadToDeg, lambda * Constants.RadToDeg, hEllip);
479
- }
480
-
481
- /**
482
- * Convert Lat, Lon, Alt to ECEF position.
483
- * ECEF includes ITRF and WGS frames.
484
- * Vallado Fundamentals, p. 144
485
- * @param {LLA} lla - Latitude, longitude, and altitude
486
- * @param {number} [planetRad=Earth.EquatorialRadiusKm] - Planet radius
487
- * @param {number} [planetEcc=Earth.EccentricityWgs84] - Planet eccentricity
488
- * @return {Vector3D} Position vector in ECEF
489
- */
490
- static llaToECEF(lla, planetRad = Earth.EquatorialRadiusKm,
491
- planetEcc = Earth.EccentricityWgs84) {
492
- const phigd = lla.Latitude.Radians;
493
- const lamba = lla.Longitude.Radians;
494
- const h = lla.AltitudeKm;
495
-
496
- const C = planetRad / Math.sqrt(1
497
- - Math.pow(planetEcc, 2) * Math.pow(Math.sin(phigd), 2));
498
- const S = planetRad * (1
499
- - Math.pow(planetEcc, 2)) / Math.sqrt(1
500
- - Math.pow(planetEcc, 2) * Math.pow(Math.sin(phigd), 2));
501
-
502
- return new Vector3D(
503
- (C + h) * Math.cos(phigd) * Math.cos(lamba),
504
- (C + h) * Math.cos(phigd) * Math.sin(lamba),
505
- (S + h) * Math.sin(phigd));
506
- }
507
-
508
- /**
509
- * Converts Azimuth and Elevation (Topocentric Coordinates) to Right Ascension
510
- * and Declination (Spherical Coordinates) at a given time.
511
- * @param {Date} utc - The UTC time
512
- * @param {Site} site - The observer's location
513
- * @param {AzEl} azel - The azimuth and elevation angles
514
- * @return {RaDec} The right ascension and declination
515
- */
516
- static AzElToRaDec(utc, site, azel) {
517
- const t = new TimeConverter(utc);
518
- const latRads = site.Latitude.Radians;
519
- const azRads = azel.Azimuth.Radians;
520
- const elRads = azel.Elevation.Radians;
521
-
522
- const dec = Math.asin(Math.sin(elRads) * Math.sin(latRads)
523
- + Math.cos(elRads) * Math.cos(latRads) * Math.cos(azRads));
524
-
525
- // Get local siderial time from longitude
526
- const {lmst} = TimeConverter.getLMST(site.Longitude.Degrees, t.JulianUT1);
527
-
528
- // Get local hour angle, the angle between the zenith and north polar distance of the object
529
- const H = Math.atan2(
530
- -Math.sin(azRads) * Math.cos(elRads) * Math.cos(latRads),
531
- Math.sin(elRads) - Math.sin(latRads) * Math.sin(dec));
532
-
533
- const ra = (lmst - H) % Constants.TwoPi;
534
-
535
- return new RaDec(ra * Constants.RadToDeg, dec * Constants.RadToDeg);
536
- }
537
-
538
- /**
539
- * Convert ECEF to SEZ frame for a given ground site.
540
- * @param {Site} site - The observer's location
541
- * @return {Matrix3D} The transformation matrix from ECEF to SEZ
542
- */
543
- static ecefToSEZ(site) {
544
- const sinLat = Math.sin(site.Latitude.Radians);
545
- const cosLat = Math.cos(site.Latitude.Radians);
546
- const sinLon = Math.sin(site.Longitude.Radians);
547
- const cosLon = Math.cos(site.Longitude.Radians);
548
-
549
- return new Matrix3D(
550
- sinLat * cosLon, sinLat * sinLon, -cosLat,
551
- -sinLon, cosLon, 0,
552
- cosLat * cosLon, cosLat * sinLon, sinLat,
553
- );
554
- }
555
-
556
- /**
557
- * Convert SEZ to ECEF frame for a given ground site.
558
- * @param {Site} site - The observer's location
559
- * @return {Matrix3D} The transformation matrix from SEZ to ECEF
560
- */
561
- static sezToECEF(site) {
562
- // SEZ to ECEF is the transpose of ECEF to SEZ for orthogonal matrices
563
- const sinLat = Math.sin(site.Latitude.Radians);
564
- const cosLat = Math.cos(site.Latitude.Radians);
565
- const sinLon = Math.sin(site.Longitude.Radians);
566
- const cosLon = Math.cos(site.Longitude.Radians);
567
-
568
- // This is the transpose of ecefToSEZ matrix
569
- return new Matrix3D(
570
- new Vector3D(sinLat * cosLon, -sinLon, cosLat * cosLon),
571
- new Vector3D(sinLat * sinLon, cosLon, cosLat * sinLon),
572
- new Vector3D(-cosLat, 0, sinLat),
573
- );
574
- }
575
-
576
- /**
577
- * Convert GCRF to J2000 (frame bias).
578
- * @return {Matrix3D} The transformation matrix from GCRF to J2000
579
- */
580
- static gcrfToJ2000() {
581
- // https://github.com/skyfielders/python-skyfield/blob/master/skyfield/framelib.py
582
- // 'xi0', 'eta0', and 'da0' are ICRS frame biases in arcseconds taken
583
- // from IERS (2003) Conventions, Chapter 5.
584
- const xi0 = -0.0166170 * Constants.ArcsecToRad;
585
- const eta0 = -0.0068192 * Constants.ArcsecToRad;
586
- const da0 = -0.01460 * Constants.ArcsecToRad;
587
-
588
- // Compute elements of rotation matrix.
589
- const yx = -da0;
590
- const zx = xi0;
591
- const xy = da0;
592
- const zy = eta0;
593
- const xz = -xi0;
594
- const yz = -eta0;
595
-
596
- // Include second-order corrections to diagonal elements.
597
- const xx = 1 - 0.5 * (yx * yx + zx * zx);
598
- const yy = 1 - 0.5 * (yx * yx + zy * zy);
599
- const zz = 1 - 0.5 * (zy * zy + zx * zx);
600
-
601
- return new Matrix3D(
602
- xx, xy, xz,
603
- yx, yy, yz,
604
- zx, zy, zz,
605
- );
606
- }
607
-
608
- /**
609
- * Convert J2000 to GCRF (inverse frame bias).
610
- * Vallado Fundamentals
611
- * @return {Matrix3D} The transformation matrix from J2000 to GCRF
612
- */
613
- static j2000ToGCRF() {
614
- return this.invertMatrix(this.gcrfToJ2000());
615
- }
616
-
617
- /**
618
- * Convert ITRF to J2000 using IAU-1980 FK5 reduction. This only works for position vectors, not velocity.
619
- * Vallado Fundamentals
620
- * @param {number} utc - Julian centuries of TT
621
- * @return {Matrix3D} The transformation matrix from ITRF to J2000
622
- */
623
- static itrfToJ2000(utc) {
624
- const t = new TimeConverter(utc);
625
-
626
- const prc = this.precessionFk5(t.JulianCenturiesTT);
627
- const {nut, nutLon, raan, meanObliq} = this.nutationFk5(t.JulianCenturiesTT);
628
- const sid = this.siderealFk5(t.JulianUT1, nutLon, meanObliq, raan, 2);
629
- const pol = this.polarMotionFk5(TimeConverter.XP * Constants.SecondToRad,
630
- TimeConverter.YP * Constants.SecondToRad);
631
-
632
- return this.multiplyMatrices(this.multiplyMatrices(
633
- this.multiplyMatrices(prc, nut), sid), pol);
634
- }
635
-
636
- /**
637
- * ITRF to J2000 with velocity component.
638
- * @param {Date} utc - UTC time
639
- * @param {Vector3D} r - Position vector in ITRF
640
- * @param {Vector3D} v - Velocity vector in ITRF
641
- * @return {Object} Position and velocity in J2000
642
- */
643
- static itrfToJ2000WithVelocity(utc, r, v) {
644
- const t = new TimeConverter(utc);
645
-
646
- const omega = new Vector3D(0, 0, Earth.SpinRateRadSec * (1
647
- - TimeConverter.LOD / Constants.SecPerSolarDay));
648
-
649
- const prc = this.precessionFk5(t.JulianCenturiesTT);
650
- const {nut, raan} = this.nutationFk5(t.JulianCenturiesTT);
651
- const sid = this.siderealFk5(t.JulianUT1, nut.nutLon, nut.meanObliq, raan, 2);
652
- const pol = this.polarMotionFk5(TimeConverter.XP * Constants.SecondToRad,
653
- TimeConverter.YP * Constants.SecondToRad);
654
- const temp = this.multiplyMatrices(prc, nut);
655
- const trans = this.multiplyMatrices(temp, sid);
656
-
657
- const rpef = pol.multiplyVector3D(r);
658
- const vpef = pol.multiplyVector3D(v);
659
- const crossr = omega.cross(rpef);
660
- const reci = trans.multiplyVector3D(rpef);
661
- const veci = trans.multiplyVector3D(vpef.add(crossr));
662
-
663
- return {Position: reci, Velocity: veci};
664
- }
665
-
666
- /**
667
- * J2000 to ITRF with velocity component.
668
- * @param {Date} utc - UTC time
669
- * @param {Vector3D} r - Position vector in J2000
670
- * @param {Vector3D} v - Velocity vector in J2000
671
- * @return {Object} Position and velocity in ITRF
672
- */
673
- static j2000ToITRFWithVelocity(utc, r, v) {
674
- const t = new TimeConverter(utc);
675
-
676
- const omega = new Vector3D(0, 0, Earth.SpinRateRadSec * (1
677
- - TimeConverter.LOD / Constants.SecPerSolarDay));
678
-
679
- let prc = this.precessionFk5(t.JulianCenturiesTT);
680
- const {nut, raan} = this.nutationFk5(t.JulianCenturiesTT);
681
- let sid = this.siderealFk5(t.JulianUT1, nut.nutLon, nut.meanObliq, raan, 2);
682
- let pol = this.polarMotionFk5(TimeConverter.XP * Constants.SecondToRad,
683
- TimeConverter.YP * Constants.SecondToRad);
684
-
685
- prc = this.transposeMatrix(prc);
686
- const nutTranspose = this.transposeMatrix(nut.matrix);
687
- sid = this.transposeMatrix(sid);
688
- pol = this.transposeMatrix(pol);
689
-
690
- const temp = this.multiplyMatrices(sid, nutTranspose);
691
- const trans = this.multiplyMatrices(temp, prc);
692
-
693
- const rpef = trans.multiplyVector3D(r);
694
- const recef = pol.multiplyVector3D(rpef);
695
- const tempvec1 = trans.multiplyVector3D(v);
696
- const crossr = omega.cross(rpef);
697
- const vpef = tempvec1.subtract(crossr);
698
- const vecef = pol.multiplyVector3D(vpef);
699
-
700
- return {Position: recef, Velocity: vecef};
701
- }
702
-
703
- /**
704
- * Convert J2000 to ITRF using IAU-1980 FK5 reduction. This only works for position vectors, not velocity.
705
- * Vallado Fundamentals
706
- * @param {number} utc - Julian centuries of TT
707
- * @return {Matrix3D} The transformation matrix from J2000 to ITRF
708
- */
709
- static j2000ToITRF(utc) {
710
- return this.invertMatrix(this.itrfToJ2000(utc));
711
- }
712
-
713
- /**
714
- * Convert TEME to J2000 at some UTC time.
715
- * Vallado Fundamentals
716
- * @param {Date} utc - UTC time
717
- * @return {Matrix3D} The transformation matrix from TEME to J2000
718
- */
719
- static temeToJ2000(utc) {
720
- const t = new TimeConverter(utc);
721
- const ttt = t.JulianCenturiesTT;
722
-
723
- let eqeTemp = Matrix3D.zeros();
724
- let eqeg;
725
-
726
- const prec = this.precessionFk5(ttt);
727
- // const { nut } = this.nutationFk5(ttt);
728
- const nut = this.nutationFk5(ttt);
729
-
730
- // Rotate teme through just geometric terms
731
- eqeg = nut.nutLon * Math.cos(nut.meanObliq);
732
- eqeg %= Constants.TwoPi;
733
-
734
- eqeTemp = new Matrix3D(new Vector3D(Math.cos(eqeg), Math.sin(eqeg), 0),
735
- new Vector3D(-Math.sin(eqeg), Math.cos(eqeg), 0), new Vector3D(0, 0, 1));
736
-
737
- const eqe = new Matrix3D(eqeTemp);
738
- const eqep = eqe.matrix[0].transpose();
739
- const temp = this.multiplyMatrices(nut.nut, eqep);
740
- const returnMat = this.multiplyMatrices(prec, temp);
741
- return returnMat;
742
- // return prec.multiply(temp);
743
- }
744
-
745
- /**
746
- * Convert J2000 to TEME at some UTC time.
747
- * @param {Date} utc - UTC time
748
- * @return {Matrix3D} The transformation matrix from J2000 to TEME
749
- */
750
- static j2000ToTEME(utc) {
751
- return this.invertMatrix(this.temeToJ2000(utc));
752
- }
753
-
754
- /**
755
- * Get nutation transformation according to FK5.
756
- * This matrix is also TOD to MOD.
757
- * Vallado Fundamentals
758
- * @param {number} ttt - Julian centuries of TT
759
- * @return {Matrix3D} The transformation matrix from J2000 to ITRF
760
- *
761
- */
762
- static nutationFk5(ttt) {
763
- // Compute the mean obliquity of the ecliptic
764
- let me1980 = ((0.001813 * ttt - 0.00059) * ttt - 46.8150) * ttt + 84381.448;
765
- me1980 = me1980 / 3600 % 360;
766
- me1980 *= Constants.DegToRad;
767
-
768
- // Evaluate the Delaunay parameters associated with the Moon and the Sun in the interval [0,2π]°
769
- const oo3600 = 1 / 3600;
770
- let Mm = ((0.064 * ttt + 31.310) * ttt + 1717915922.6330) * ttt * oo3600 + 134.96298139;
771
- let Ms = (((-0.012) * ttt - 0.577) * ttt + 129596581.2240) * ttt * oo3600 + 357.52772333;
772
- let uMm = ((0.011 * ttt - 13.257) * ttt + 1739527263.1370) * ttt * oo3600 + 93.27191028;
773
- let Ds = ((0.019 * ttt - 6.891) * ttt + 1602961601.3280) * ttt * oo3600 + 297.85036306;
774
- let Omegam = ((0.008 * ttt + 7.455) * ttt - 6962890.5390) * ttt * oo3600 + 125.04452222;
775
-
776
- Mm = Mm % 360 * Constants.DegToRad;
777
- Ms = Ms % 360 * Constants.DegToRad;
778
- uMm = uMm % 360 * Constants.DegToRad;
779
- Ds = Ds % 360 * Constants.DegToRad;
780
- Omegam = Omegam % 360 * Constants.DegToRad;
781
-
782
- // Compute nutation in longitude and in obliquity
783
- let dPsi1980 = 0;
784
- let dEp1980 = 0;
785
-
786
- // Compute the nutation in the longitude and in obliquity
787
- for (const row of this.NUT_COEFF) {
788
- const ap = row[0] * Mm + row[1] * Ms + row[2] * uMm + row[3] * Ds + row[4] * Omegam;
789
- dPsi1980 += (row[5] + row[6] * ttt) * Math.sin(ap);
790
- dEp1980 += (row[7] + row[8] * ttt) * Math.cos(ap);
791
- }
792
-
793
- // The nutation coefficients lead to angles with unit 0.0001. Hence, we must convert to [rad]
794
- dPsi1980 *= 0.0001 / 3600 * Constants.DegToRad;
795
- dEp1980 *= 0.0001 / 3600 * Constants.DegToRad;
796
-
797
- const meanObliq = me1980;
798
- const nutObliq = dEp1980;
799
- const nutLon = dPsi1980;
800
- const raan = Omegam;
801
- const trueObliq = meanObliq + nutObliq;
802
-
803
- const cospsi = Math.cos(nutLon);
804
- const sinpsi = Math.sin(nutLon);
805
- const coseps = Math.cos(meanObliq);
806
- const sineps = Math.sin(meanObliq);
807
- const costrueeps = Math.cos(trueObliq);
808
- const sintrueeps = Math.sin(trueObliq);
809
-
810
- // const nut = new Matrix3D(
811
- // cospsi, costrueeps * sinpsi, sintrueeps * sinpsi,
812
- // -coseps * sinpsi, costrueeps * coseps * cospsi + sintrueeps * sineps, sintrueeps * coseps * cospsi - sineps * costrueeps,
813
- // -sineps * sinpsi, costrueeps * sineps * cospsi - sintrueeps * coseps, sintrueeps * sineps * cospsi + costrueeps * coseps
814
- // );
815
-
816
- const nut = new Matrix3D(
817
- new Vector3D(cospsi, costrueeps * sinpsi, sintrueeps * sinpsi),
818
- new Vector3D(-coseps * sinpsi, costrueeps * coseps * cospsi + sintrueeps * sineps,
819
- sintrueeps * coseps * cospsi - sineps * costrueeps),
820
- new Vector3D(-sineps * sinpsi, costrueeps * sineps * cospsi - sintrueeps * coseps,
821
- sintrueeps * sineps * cospsi + costrueeps * coseps),
822
- );
823
-
824
- return {
825
- nut,
826
- meanObliq,
827
- nutObliq,
828
- nutLon,
829
- trueObliq,
830
- raan,
831
- };
832
- }
833
-
834
- /**
835
- * Get precession transformation based on FK5.
836
- * This matrix is also MOD to GCRF.
837
- * Vallado Fundamentals
838
- * @param {number} ttt - Julian centuries of TT
839
- * @return {Matrix3D} Precession transformation matrix
840
- */
841
- static precessionFk5(ttt) {
842
- const convrt = Math.PI / (180 * 3600); // " to rad
843
-
844
- let zeta = ((0.017998 * ttt + 0.30188) * ttt + 2306.2181) * ttt; // "
845
- let theta = ((-0.041833 * ttt - 0.42665) * ttt + 2004.3109) * ttt;
846
- let z = ((0.018203 * ttt + 1.09468) * ttt + 2306.2181) * ttt;
847
-
848
- zeta *= convrt;
849
- theta *= convrt;
850
- z *= convrt;
851
-
852
- const coszeta = Math.cos(zeta);
853
- const sinzeta = Math.sin(zeta);
854
- const costheta = Math.cos(theta);
855
- const sintheta = Math.sin(theta);
856
- const cosz = Math.cos(z);
857
- const sinz = Math.sin(z);
858
-
859
- // // Form matrix mod to gcrf
860
- // return new Matrix3D(
861
- // coszeta * costheta * cosz - sinzeta * sinz, coszeta * costheta * sinz + sinzeta * cosz, coszeta * sintheta,
862
- // -sinzeta * costheta * cosz - coszeta * sinz, -sinzeta * costheta * sinz + coszeta * cosz, -sinzeta * sintheta,
863
- // -sintheta * cosz, -sintheta * sinz, costheta
864
- // );
865
-
866
- return new Matrix3D(
867
- new Vector3D(coszeta * costheta * cosz - sinzeta * sinz,
868
- coszeta * costheta * sinz + sinzeta * cosz, coszeta * sintheta),
869
- new Vector3D(-sinzeta * costheta * cosz - coszeta * sinz,
870
- -sinzeta * costheta * sinz + coszeta * cosz, -sinzeta * sintheta),
871
- new Vector3D(-sintheta * cosz, -sintheta * sinz, costheta));
872
- }
873
-
874
- /**
875
- * Get transformation matrix associated with sidereal time.
876
- * Thie matrix is also PEF/GTOD to TOD.
877
- * Vallado Fundamentals
878
- * @param {number} jdut1 - Julian date of UT1
879
- * @param {number} deltapsi - Nutation in longitude (rad)
880
- * @param {number} meaneps - Mean obliquity (rad)
881
- * @param {number} raan - Right ascension of ascending node (rad)
882
- * @param {number} eqeterms - Equatorial terms (rad)
883
- * @return {Matrix3D} Sidereal transformation matrix
884
- */
885
- static siderealFk5(jdut1, deltapsi, meaneps, raan, eqeterms) {
886
- // FK5 approach
887
- const gmst = TimeConverter.getGMST(jdut1).Radians;
888
-
889
- // Find mean ast
890
- let ast = (jdut1 > 2450449.5) && (eqeterms > 0)
891
- ? gmst + deltapsi * Math.cos(meaneps)
892
- + 0.00264 * Constants.SecondToRad * Math.sin(raan)
893
- + 0.000063 * Constants.SecondToRad * Math.sin(2 * raan)
894
- : gmst + deltapsi * Math.cos(meaneps);
895
-
896
- ast %= 2.0 * Math.PI;
897
-
898
- const sinast = Math.sin(ast);
899
- const cosast = Math.cos(ast);
900
-
901
- // return new Matrix3D(
902
- // cosast, -sinast, 0,
903
- // sinast, cosast, 0,
904
- // 0, 0, 1
905
- // );
906
-
907
- return new Matrix3D(
908
- new Vector3D(cosast, -sinast, 0), new Vector3D(sinast, cosast, 0),
909
- new Vector3D(0, 0, 1),
910
- );
911
- }
912
-
913
- /**
914
- * Get transformation associated with polar motion of Earth.
915
- * This matrix is also ITRF to PEF/GTOD.
916
- * xp and yp are the polar motion coefficients (rad).
917
- * Vallado Fundamentals
918
- * @param {number} xp - Polar motion coefficient in x (rad)
919
- * @param {number} yp - Polar motion coefficient in y (rad)
920
- * @return {Matrix3D} Polar motion transformation matrix
921
- */
922
- static polarMotionFk5(xp, yp) {
923
- const cosxp = Math.cos(xp);
924
- const sinxp = Math.sin(xp);
925
- const cosyp = Math.cos(yp);
926
- const sinyp = Math.sin(yp);
927
-
928
- // // FK5 approach
929
- // return new Matrix3D(
930
- // cosxp, 0, -sinxp,
931
- // sinxp * sinyp, cosyp, cosxp * sinyp,
932
- // sinxp * cosyp, -sinyp, cosxp * cosyp
933
- // );
934
-
935
- return new Matrix3D(
936
- new Vector3D(cosxp, 0, -sinxp),
937
- new Vector3D(sinxp * sinyp, cosyp, cosxp * sinyp),
938
- new Vector3D(sinxp * cosyp, -sinyp, cosxp * cosyp),
939
- );
940
- }
941
-
942
- /**
943
- * Check if a frame is inertially defined.
944
- * @param {ReferenceFrame} frame - The reference frame to check
945
- * @param {boolean} throwError - Whether to throw an error if the frame is non-inertial
946
- * @return {boolean} True if the frame is inertial, false otherwise
947
- */
948
- static CheckIfInertial(frame, throwError = false) {
949
- if (frame === ReferenceFrame.ITRF) {
950
- if (throwError) {
951
- throw new Error(
952
- "Cannot use a non-inertial frame for this method."
953
- + " Try converting to an ECI frame.");
954
- }
955
- return false;
956
- }
957
- return true;
958
- }
959
-
960
- static perifocalToInertial(keplerianElements) {
961
- const cosi = Math.cos(keplerianElements.i * DEG2RAD);
962
- const sini = Math.sin(keplerianElements.i * DEG2RAD);
963
- const cosRaan = Math.cos(keplerianElements.raan * DEG2RAD);
964
- const sinRaan = Math.sin(keplerianElements.raan * DEG2RAD);
965
- const cosw = Math.cos(keplerianElements.w * DEG2RAD);
966
- const sinw = Math.sin(keplerianElements.w * DEG2RAD);
967
-
968
- // The matrix elements
969
- const r11 = cosRaan * cosw - sinRaan * sinw * cosi;
970
- const r12 = -cosRaan * sinw - sinRaan * cosw * cosi;
971
- const r13 = sinRaan * sini;
972
-
973
- const r21 = sinRaan * cosw + cosRaan * sinw * cosi;
974
- const r22 = -sinRaan * sinw + cosRaan * cosw * cosi;
975
- const r23 = -cosRaan * sini;
976
-
977
- const r31 = sinw * sini;
978
- const r32 = cosw * sini;
979
- const r33 = cosi;
980
-
981
- // Packing
982
- return new Matrix3D(
983
- new Vector3D(r11, r12, r13),
984
- new Vector3D(r21, r22, r23),
985
- new Vector3D(r31, r32, r33),
986
- );
987
- }
988
-
989
- static eciToRtn(r, v) {
990
- const rTemp = [r.x, r.y, r.z];
991
- const vTemp = [v.x, v.y, v.z];
992
- const rHat = rTemp.map((component) => component / norm(rTemp));
993
- const vHat = vTemp.map((component) => component / norm(vTemp));
994
- const nHat = cross(rHat, vHat).map((component) => component / norm(cross(rHat, vHat)));
995
- const tHat = cross(nHat, rHat).map((component) => component / norm(cross(nHat, rHat)));
996
-
997
- return new Matrix3D(
998
- new Vector3D(rHat[0], rHat[1], rHat[2]),
999
- new Vector3D(tHat[0], tHat[1], tHat[2]),
1000
- new Vector3D(nHat[0], nHat[1], nHat[2]),
1001
- );
1002
- }
1003
-
1004
- static rtnToEci(r, v) {
1005
- const eciToRtn = this.eciToRtn(r, v);
1006
- return this.invertMatrix(eciToRtn);
1007
- }
1008
-
1009
- // 1980 IAU Theory of Nutation Coefficients
1010
- static NUT_COEFF = [
1011
- [0, 0, 0, 0, 1, -171996.0, -174.2, 92025.0, 8.9],
1012
- [0, 0, 2, -2, 2, -13187.0, -1.6, 5736.0, -3.1],
1013
- [0, 0, 2, 0, 2, -2274.0, -0.2, 977.0, -0.5],
1014
- [0, 0, 0, 0, 2, 2062.0, 0.2, -895.0, 0.5],
1015
- [0, 1, 0, 0, 0, 1426.0, -3.4, 54.0, -0.1],
1016
- [1, 0, 0, 0, 0, 712.0, 0.1, -7.0, 0.0],
1017
- [0, 1, 2, -2, 2, -517.0, 1.2, 224.0, -0.6],
1018
- [0, 0, 2, 0, 1, -386.0, -0.4, 200.0, 0.0],
1019
- [1, 0, 2, 0, 2, -301.0, 0.0, 129.0, -0.1],
1020
- [0, -1, 2, -2, 2, 217.0, -0.5, -95.0, 0.3],
1021
- [1, 0, 0, -2, 0, -158.0, 0.0, -1.0, 0.0],
1022
- [0, 0, 2, -2, 1, 129.0, 0.1, -70.0, 0.0],
1023
- [-1, 0, 2, 0, 2, 123.0, 0.0, -53.0, 0.0],
1024
- [1, 0, 0, 0, 1, 63.0, 0.1, -33.0, 0.0],
1025
- [0, 0, 0, 2, 0, 63.0, 0.0, -2.0, 0.0],
1026
- [-1, 0, 2, 2, 2, -59.0, 0.0, 26.0, 0.0],
1027
- [-1, 0, 0, 0, 1, -58.0, -0.1, 32.0, 0.0],
1028
- [1, 0, 2, 0, 1, -51.0, 0.0, 27.0, 0.0],
1029
- [2, 0, 0, -2, 0, 48.0, 0.0, 1.0, 0.0],
1030
- [-2, 0, 2, 0, 1, 46.0, 0.0, -24.0, 0.0],
1031
- [0, 0, 2, 2, 2, -38.0, 0.0, 16.0, 0.0],
1032
- [2, 0, 2, 0, 2, -31.0, 0.0, 13.0, 0.0],
1033
- [2, 0, 0, 0, 0, 29.0, 0.0, -1.0, 0.0],
1034
- [1, 0, 2, -2, 2, 29.0, 0.0, -12.0, 0.0],
1035
- [0, 0, 2, 0, 0, 26.0, 0.0, -1.0, 0.0],
1036
- [0, 0, 2, -2, 0, -22.0, 0.0, 0.0, 0.0],
1037
- [-1, 0, 2, 0, 1, 21.0, 0.0, -10.0, 0.0],
1038
- [0, 2, 0, 0, 0, 17.0, -0.1, 0.0, 0.0],
1039
- [0, 2, 2, -2, 2, -16.0, 0.1, 7.0, 0.0],
1040
- [-1, 0, 0, 2, 1, 16.0, 0.0, -8.0, 0.0],
1041
- [0, 1, 0, 0, 1, -15.0, 0.0, 9.0, 0.0],
1042
- [1, 0, 0, -2, 1, -13.0, 0.0, 7.0, 0.0],
1043
- [0, -1, 0, 0, 1, -12.0, 0.0, 6.0, 0.0],
1044
- [2, 0, -2, 0, 0, 11.0, 0.0, 0.0, 0.0],
1045
- [-1, 0, 2, 2, 1, -10.0, 0.0, 5.0, 0.0],
1046
- [1, 0, 2, 2, 2, -8.0, 0.0, 3.0, 0.0],
1047
- [0, -1, 2, 0, 2, -7.0, 0.0, 3.0, 0.0],
1048
- [0, 0, 2, 2, 1, -7.0, 0.0, 3.0, 0.0],
1049
- [1, 1, 0, -2, 0, -7.0, 0.0, 0.0, 0.0],
1050
- [0, 1, 2, 0, 2, 7.0, 0.0, -3.0, 0.0],
1051
- [-2, 0, 0, 2, 1, -6.0, 0.0, 3.0, 0.0],
1052
- [0, 0, 0, 2, 1, -6.0, 0.0, 3.0, 0.0],
1053
- [2, 0, 2, -2, 2, 6.0, 0.0, -3.0, 0.0],
1054
- [1, 0, 0, 2, 0, 6.0, 0.0, 0.0, 0.0],
1055
- [1, 0, 2, -2, 1, 6.0, 0.0, -3.0, 0.0],
1056
- [0, 0, 0, -2, 1, -5.0, 0.0, 3.0, 0.0],
1057
- [0, -1, 2, -2, 1, -5.0, 0.0, 3.0, 0.0],
1058
- [2, 0, 2, 0, 1, -5.0, 0.0, 3.0, 0.0],
1059
- [1, -1, 0, 0, 0, 5.0, 0.0, 0.0, 0.0],
1060
- [1, 0, 0, -1, 0, -4.0, 0.0, 0.0, 0.0],
1061
- [0, 0, 0, 1, 0, -4.0, 0.0, 0.0, 0.0],
1062
- [0, 1, 0, -2, 0, -4.0, 0.0, 0.0, 0.0],
1063
- [1, 0, -2, 0, 0, 4.0, 0.0, 0.0, 0.0],
1064
- [2, 0, 0, -2, 1, 4.0, 0.0, -2.0, 0.0],
1065
- [0, 1, 2, -2, 1, 4.0, 0.0, -2.0, 0.0],
1066
- [1, 1, 0, 0, 0, -3.0, 0.0, 0.0, 0.0],
1067
- [1, -1, 0, -1, 0, -3.0, 0.0, 0.0, 0.0],
1068
- [-1, -1, 2, 2, 2, -3.0, 0.0, 1.0, 0.0],
1069
- [0, -1, 2, 2, 2, -3.0, 0.0, 1.0, 0.0],
1070
- [1, -1, 2, 0, 2, -3.0, 0.0, 1.0, 0.0],
1071
- [3, 0, 2, 0, 2, -3.0, 0.0, 1.0, 0.0],
1072
- [-2, 0, 2, 0, 2, -3.0, 0.0, 1.0, 0.0],
1073
- [1, 0, 2, 0, 0, 3.0, 0.0, 0.0, 0.0],
1074
- [-1, 0, 2, 4, 2, -2.0, 0.0, 1.0, 0.0],
1075
- [1, 0, 0, 0, 2, -2.0, 0.0, 1.0, 0.0],
1076
- [-1, 0, 2, -2, 1, -2.0, 0.0, 1.0, 0.0],
1077
- [0, -2, 2, -2, 1, -2.0, 0.0, 1.0, 0.0],
1078
- [-2, 0, 0, 0, 1, -2.0, 0.0, 1.0, 0.0],
1079
- [2, 0, 0, 0, 1, 2.0, 0.0, -1.0, 0.0],
1080
- [3, 0, 0, 0, 0, 2.0, 0.0, 0.0, 0.0],
1081
- [1, 1, 2, 0, 2, 2.0, 0.0, -1.0, 0.0],
1082
- [0, 0, 2, 1, 2, 2.0, 0.0, -1.0, 0.0],
1083
- [1, 0, 0, 2, 1, -1.0, 0.0, 0.0, 0.0],
1084
- [1, 0, 2, 2, 1, -1.0, 0.0, 1.0, 0.0],
1085
- [1, 1, 0, -2, 1, -1.0, 0.0, 0.0, 0.0],
1086
- [0, 1, 0, 2, 0, -1.0, 0.0, 0.0, 0.0],
1087
- [0, 1, 2, -2, 0, -1.0, 0.0, 0.0, 0.0],
1088
- [0, 1, -2, 2, 0, -1.0, 0.0, 0.0, 0.0],
1089
- [1, 0, -2, 2, 0, -1.0, 0.0, 0.0, 0.0],
1090
- [1, 0, -2, -2, 0, -1.0, 0.0, 0.0, 0.0],
1091
- [1, 0, 2, -2, 0, -1.0, 0.0, 0.0, 0.0],
1092
- [1, 0, 0, -4, 0, -1.0, 0.0, 0.0, 0.0],
1093
- [2, 0, 0, -4, 0, -1.0, 0.0, 0.0, 0.0],
1094
- [0, 0, 2, 4, 2, -1.0, 0.0, 0.0, 0.0],
1095
- [0, 0, 2, -1, 2, -1.0, 0.0, 0.0, 0.0],
1096
- [-2, 0, 2, 4, 2, -1.0, 0.0, 1.0, 0.0],
1097
- [2, 0, 2, 2, 2, -1.0, 0.0, 0.0, 0.0],
1098
- [0, -1, 2, 0, 1, -1.0, 0.0, 0.0, 0.0],
1099
- [0, 0, -2, 0, 1, -1.0, 0.0, 0.0, 0.0],
1100
- [0, 0, 4, -2, 2, 1.0, 0.0, 0.0, 0.0],
1101
- [0, 1, 0, 0, 2, 1.0, 0.0, 0.0, 0.0],
1102
- [1, 1, 2, -2, 2, 1.0, 0.0, -1.0, 0.0],
1103
- [3, 0, 2, -2, 2, 1.0, 0.0, 0.0, 0.0],
1104
- [-2, 0, 2, 2, 2, 1.0, 0.0, -1.0, 0.0],
1105
- [-1, 0, 0, 0, 2, 1.0, 0.0, -1.0, 0.0],
1106
- [0, 0, -2, 2, 1, 1.0, 0.0, 0.0, 0.0],
1107
- [0, 1, 2, 0, 1, 1.0, 0.0, 0.0, 0.0],
1108
- [-1, 0, 4, 0, 2, 1.0, 0.0, 0.0, 0.0],
1109
- [2, 1, 0, -2, 0, 1.0, 0.0, 0.0, 0.0],
1110
- [2, 0, 0, 2, 0, 1.0, 0.0, 0.0, 0.0],
1111
- [2, 0, 2, -2, 1, 1.0, 0.0, -1.0, 0.0],
1112
- [2, 0, -2, 0, 1, 1.0, 0.0, 0.0, 0.0],
1113
- [1, -1, 0, -2, 0, 1.0, 0.0, 0.0, 0.0],
1114
- [-1, 0, 0, 1, 1, 1.0, 0.0, 0.0, 0.0],
1115
- [-1, -1, 0, 2, 1, 1.0, 0.0, 0.0, 0.0],
1116
- [0, 1, 0, 1, 0, 1.0, 0.0, 0.0, 0.0],
1117
- ];
1118
- }
1119
-
1120
- export {FrameConverter, ReferenceFrame};
1121
- export const EarthParams = Earth;
1
+ // FrameConverter.js - Port from C# FrameConverter.cs
2
+ import {Matrix3D, Vector3D} from "pious-squid";
3
+ import {LLA} from "./LLA.js";
4
+ import {TimeConverter, RaDec} from "./TimeConverter.js";
5
+ import {norm, cross} from "mathjs";
6
+ import {DEG2RAD} from "./constants.js";
7
+
8
+
9
+ // Enums (assuming these exist elsewhere)
10
+ const ReferenceFrame = {
11
+ J2000: "J2000",
12
+ TEME: "TEME",
13
+ GCRF: "GCRF",
14
+ ITRF: "ITRF",
15
+ };
16
+
17
+ // Constants (assuming these exist elsewhere)
18
+ const Constants = {
19
+ TwoPi: 2 * Math.PI,
20
+ RadToDeg: 180 / Math.PI,
21
+ DegToRad: Math.PI / 180,
22
+ ArcsecToRad: Math.PI / (180 * 3600),
23
+ SecondToRad: Math.PI / (180 * 3600),
24
+ SecPerSolarDay: 86400,
25
+ };
26
+
27
+ // Earth constants (assuming these exist elsewhere)
28
+ const Earth = {
29
+ EquatorialRadiusKm: 6378.137,
30
+ EccentricityWgs84: 0.0818191908426,
31
+ SpinRateRadSec: 7.2921159e-5,
32
+ };
33
+
34
+ class FrameConverter {
35
+ /**
36
+ * Helper method to transform a vector using a Matrix3D
37
+ * @param {Vector3D} vector - Vector to transform
38
+ * @param {Matrix3D} matrix - Matrix to use for transformation
39
+ * @return {Vector3D} Transformed vector
40
+ */
41
+ static transformVector(vector, matrix) {
42
+ // Manual matrix-vector multiplication since pious-squid has issues
43
+ const x = matrix.get(0, 0) * vector.x
44
+ + matrix.get(0, 1) * vector.y + matrix.get(0, 2) * vector.z;
45
+ const y = matrix.get(1, 0) * vector.x
46
+ + matrix.get(1, 1) * vector.y + matrix.get(1, 2) * vector.z;
47
+ const z = matrix.get(2, 0) * vector.x
48
+ + matrix.get(2, 1) * vector.y + matrix.get(2, 2) * vector.z;
49
+ return new Vector3D(x, y, z);
50
+ }
51
+
52
+ /**
53
+ * Rotate a vector around an axis by an angle (Rodrigues' rotation formula)
54
+ * @param {Vector3D} vector - Vector to rotate
55
+ * @param {Vector3D} axis - Axis to rotate around (should be normalized)
56
+ * @param {number} angleRad - Angle in radians
57
+ * @return {Vector3D} Rotated vector
58
+ */
59
+ static rotateVector(vector, axis, angleRad) {
60
+ // Ensure axis is normalized
61
+ const k = axis.normalized();
62
+ const v = vector;
63
+ const cosTheta = Math.cos(angleRad);
64
+ const sinTheta = Math.sin(angleRad);
65
+
66
+ // Rodrigues' rotation formula:
67
+ // v_rot = v*cos(θ) + (k × v)*sin(θ) + k*(k·v)*(1-cos(θ))
68
+
69
+ // k × v (cross product)
70
+ const kCrossV = new Vector3D(
71
+ k.y * v.z - k.z * v.y,
72
+ k.z * v.x - k.x * v.z,
73
+ k.x * v.y - k.y * v.x,
74
+ );
75
+
76
+ // k·v (dot product)
77
+ const kDotV = k.x * v.x + k.y * v.y + k.z * v.z;
78
+
79
+ // Final rotation
80
+ return new Vector3D(
81
+ v.x * cosTheta + kCrossV.x * sinTheta + k.x * kDotV * (1 - cosTheta),
82
+ v.y * cosTheta + kCrossV.y * sinTheta + k.y * kDotV * (1 - cosTheta),
83
+ v.z * cosTheta + kCrossV.z * sinTheta + k.z * kDotV * (1 - cosTheta),
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Helper to transpose a Matrix3D
89
+ * @param {Matrix3D} m - The matrix to transpose
90
+ * @return {Matrix3D} The transposed matrix
91
+ */
92
+ static transposeMatrix(m) {
93
+ return new Matrix3D(
94
+ m.get(0, 0), m.get(1, 0), m.get(2, 0),
95
+ m.get(0, 1), m.get(1, 1), m.get(2, 1),
96
+ m.get(0, 2), m.get(1, 2), m.get(2, 2),
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Helper to invert a 3x3 matrix
102
+ * @param {Matrix3D} m - The matrix to invert
103
+ * @return {Matrix3D} The inverted matrix
104
+ */
105
+ static invertMatrix(m) {
106
+ const a = m.get(0, 0); const b = m.get(0, 1); const c = m.get(0, 2);
107
+ const d = m.get(1, 0); const e = m.get(1, 1); const f = m.get(1, 2);
108
+ const g = m.get(2, 0); const h = m.get(2, 1); const i = m.get(2, 2);
109
+
110
+ const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
111
+
112
+ if (Math.abs(det) < 1e-10) {
113
+ throw new Error("Matrix is not invertible");
114
+ }
115
+
116
+ const invDet = 1 / det;
117
+
118
+ return new Matrix3D(
119
+ new Vector3D(
120
+ (e * i - f * h) * invDet,
121
+ (c * h - b * i) * invDet,
122
+ (b * f - c * e) * invDet,
123
+ ),
124
+ new Vector3D(
125
+ (f * g - d * i) * invDet,
126
+ (a * i - c * g) * invDet,
127
+ (c * d - a * f) * invDet,
128
+ ),
129
+ new Vector3D(
130
+ (d * h - e * g) * invDet,
131
+ (b * g - a * h) * invDet,
132
+ (a * e - b * d) * invDet,
133
+ ),
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Helper to multiply two matrices
139
+ * @param {Matrix3D} m1 - The first matrix
140
+ * @param {Matrix3D} m2 - The second matrix
141
+ * @return {Matrix3D} The product of the two matrices
142
+ */
143
+ static multiplyMatrices(m1, m2) {
144
+ // for (let i = 0; i < 3; i++) {
145
+ // for (let j = 0; j < 3; j++) {
146
+ // let sum = 0;
147
+ // for (let k = 0; k < 3; k++) {
148
+ // sum += m1.get(i, k) * m2.get(k, j);
149
+ // }
150
+ // // Matrix3D doesn't have a set method, so we need to create a new one
151
+ // // This is a limitation - we'll work around it
152
+ // }
153
+ // }
154
+ // Create new matrix with computed values
155
+ return new Matrix3D(
156
+ new Vector3D(
157
+ m1.get(0, 0) * m2.get(0, 0)
158
+ + m1.get(0, 1) * m2.get(1, 0) + m1.get(0, 2) * m2.get(2, 0),
159
+ m1.get(0, 0) * m2.get(0, 1)
160
+ + m1.get(0, 1) * m2.get(1, 1) + m1.get(0, 2) * m2.get(2, 1),
161
+ m1.get(0, 0) * m2.get(0, 2)
162
+ + m1.get(0, 1) * m2.get(1, 2) + m1.get(0, 2) * m2.get(2, 2)),
163
+ new Vector3D(
164
+ m1.get(1, 0) * m2.get(0, 0)
165
+ + m1.get(1, 1) * m2.get(1, 0) + m1.get(1, 2) * m2.get(2, 0),
166
+ m1.get(1, 0) * m2.get(0, 1)
167
+ + m1.get(1, 1) * m2.get(1, 1) + m1.get(1, 2) * m2.get(2, 1),
168
+ m1.get(1, 0) * m2.get(0, 2)
169
+ + m1.get(1, 1) * m2.get(1, 2) + m1.get(1, 2) * m2.get(2, 2)),
170
+ new Vector3D(
171
+ m1.get(2, 0) * m2.get(0, 0)
172
+ + m1.get(2, 1) * m2.get(1, 0) + m1.get(2, 2) * m2.get(2, 0),
173
+ m1.get(2, 0) * m2.get(0, 1)
174
+ + m1.get(2, 1) * m2.get(1, 1) + m1.get(2, 2) * m2.get(2, 1),
175
+ m1.get(2, 0) * m2.get(0, 2)
176
+ + m1.get(2, 1) * m2.get(1, 2) + m1.get(2, 2) * m2.get(2, 2)),
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Get transform from a frame to a destination frame at a given time.
182
+ * Apply this to a position vector using: vec3d.TransformBy(matrix)
183
+ * @param {string} from - Source reference frame
184
+ * @param {string} to - Destination reference frame
185
+ * @param {Date} utc - UTC time for the transformation
186
+ * @return {Matrix3D} The transformation matrix
187
+ */
188
+ static getTransform(from, to, utc) {
189
+ switch (from) {
190
+ case ReferenceFrame.J2000:
191
+ switch (to) {
192
+ case ReferenceFrame.J2000:
193
+ break;
194
+ case ReferenceFrame.TEME:
195
+ return this.j2000ToTEME(utc);
196
+ case ReferenceFrame.GCRF:
197
+ return this.j2000ToGCRF();
198
+ case ReferenceFrame.ITRF:
199
+ return this.j2000ToITRF(utc);
200
+ default:
201
+ break;
202
+ }
203
+ break;
204
+ case ReferenceFrame.TEME:
205
+ switch (to) {
206
+ case ReferenceFrame.J2000:
207
+ return this.temeToJ2000(utc);
208
+ case ReferenceFrame.TEME:
209
+ break;
210
+ case ReferenceFrame.GCRF:
211
+ return this.multiplyMatrices(this.j2000ToGCRF(), this.temeToJ2000(utc));
212
+ case ReferenceFrame.ITRF:
213
+ return this.multiplyMatrices(this.j2000ToITRF(utc), this.temeToJ2000(utc));
214
+ default:
215
+ break;
216
+ }
217
+ break;
218
+ case ReferenceFrame.GCRF:
219
+ switch (to) {
220
+ case ReferenceFrame.J2000:
221
+ return this.gcrfToJ2000();
222
+ case ReferenceFrame.TEME:
223
+ return this.multiplyMatrices(this.j2000ToTEME(utc), this.gcrfToJ2000());
224
+ case ReferenceFrame.GCRF:
225
+ break;
226
+ case ReferenceFrame.ITRF:
227
+ return this.multiplyMatrices(this.j2000ToITRF(utc), this.gcrfToJ2000());
228
+ default:
229
+ break;
230
+ }
231
+ break;
232
+ case ReferenceFrame.ITRF:
233
+ switch (to) {
234
+ case ReferenceFrame.J2000:
235
+ return this.itrfToJ2000(utc);
236
+ case ReferenceFrame.TEME:
237
+ return this.multiplyMatrices(this.j2000ToTEME(utc), this.itrfToJ2000(utc));
238
+ case ReferenceFrame.GCRF:
239
+ return this.multiplyMatrices(this.j2000ToGCRF(), this.itrfToJ2000(utc));
240
+ case ReferenceFrame.ITRF:
241
+ break;
242
+ default:
243
+ break;
244
+ }
245
+ break;
246
+ default:
247
+ break; // TODO: custom frame xforms, RIC, etc
248
+ }
249
+
250
+ // Return identity matrix
251
+ return new Matrix3D(
252
+ new Vector3D(1, 0, 0),
253
+ new Vector3D(0, 1, 0),
254
+ new Vector3D(0, 0, 1),
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Convert from ITRF/ECEF to azimuth, elevation, range.
260
+ * Optional velocity component will yield AzElRange rates, however this will slow down the computation.
261
+ * @param {LLA} site - Ground site in LLA
262
+ * @param {Vector3D} r - Position vector in ECEF frame
263
+ * @param {Vector3D} [v=null] - Velocity vector in ECEF frame
264
+ * @param {number} [planetRadius=Earth.EquatorialRadiusKm] - Planet radius
265
+ * @param {number} [planetEccentricity=Earth.EccentricityWgs84] - Planet eccentricity
266
+ * @return {AzElRange} Azimuth, elevation, and range
267
+ */
268
+ static itrfToAzElRange(site, r, v = null,
269
+ planetRadius = Earth.EquatorialRadiusKm, planetEccentricity = Earth.EccentricityWgs84) {
270
+ // Transform
271
+ const ecef2sez = this.ecefToSEZ(site);
272
+
273
+ // To SEZ
274
+ const siteEcef = this.llaToECEF(site, planetRadius, planetEccentricity);
275
+ const rhoEcef = r.subtract(siteEcef);
276
+ const rhoDotEcef = v === null ? new Vector3D(0, 0, 0) : v;
277
+ const rhoSez = ecef2sez.multiplyVector3D(rhoEcef);
278
+ const rhoDotSez = ecef2sez.multiplyVector3D(rhoDotEcef);
279
+
280
+ // AER
281
+ const rho = rhoSez.magnitude();
282
+ let sinBeta; let cosBeta;
283
+ const el = Math.asin(rhoSez.z / rho);
284
+
285
+ if (el * Constants.RadToDeg !== 90 || v === null) {
286
+ sinBeta = rhoSez.y / Math.sqrt(Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
287
+ cosBeta = -rhoSez.x / Math.sqrt(Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
288
+ } else { // use rates
289
+ sinBeta = rhoDotSez.y / Math.sqrt(Math.pow(rhoDotSez.x, 2) + Math.pow(rhoDotSez.y, 2));
290
+ cosBeta = -rhoDotSez.x / Math.sqrt(Math.pow(rhoDotSez.x, 2) + Math.pow(rhoDotSez.y, 2));
291
+ }
292
+
293
+ let az = Math.atan2(sinBeta, cosBeta);
294
+ // Wrap to [0, 2π]
295
+ while (az < 0) az += Constants.TwoPi;
296
+ while (az >= Constants.TwoPi) az -= Constants.TwoPi;
297
+
298
+ // Rates
299
+ if (v !== null) {
300
+ const rhoDot = rhoSez.dot(rhoDotSez) / rho;
301
+ const azDot = (
302
+ rhoDotSez.x * rhoSez.y - rhoDotSez.y * rhoSez.x) / (
303
+ Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
304
+ const elDot = (rhoDotSez.z - rhoDot * Math.sin(el)) / Math.sqrt(
305
+ Math.pow(rhoSez.x, 2) + Math.pow(rhoSez.y, 2));
306
+
307
+ return {
308
+ azimuth: az,
309
+ elevation: el,
310
+ range: rho,
311
+ azimuthDot: azDot,
312
+ elevationDot: elDot,
313
+ rangeDot: rhoDot,
314
+ };
315
+ }
316
+
317
+ return {
318
+ azimuth: az,
319
+ elevation: el,
320
+ range: rho,
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Convert from azimuth, elevation, and range to ITRF/ECEF.
326
+ * Will return velocity if AzElRange rates are not zero.
327
+ * @param {AzElRange} aer - Azimuth, elevation, and range
328
+ * @param {LLA} site - Ground site in LLA
329
+ * @param {number} [planetRadius=Earth.EquatorialRadiusKm] - Planet radius
330
+ * @param {number} [planetEccentricity=Earth.EccentricityWgs84] - Planet eccentricity
331
+ * @return {Object} Position and velocity in ECEF
332
+ */
333
+ static azElRangeToITRF(aer, site, planetRadius = Earth.EquatorialRadiusKm,
334
+ planetEccentricity = Earth.EccentricityWgs84) {
335
+ const Ce = planetRadius / Math.sqrt(
336
+ 1 - Math.pow(planetEccentricity, 2)
337
+ * Math.pow(Math.sin(site.Latitude.Radians), 2));
338
+
339
+ const Se = Ce * (1 - Math.pow(planetEccentricity, 2));
340
+ const rd = (Ce + site.AltitudeKm) * Math.cos(site.Latitude.Radians);
341
+ const rK = (Se + site.AltitudeKm) * Math.sin(site.Latitude.Radians);
342
+
343
+ const rSiteEcef = new Vector3D(
344
+ rd * Math.cos(site.Longitude.Radians),
345
+ rd * Math.sin(site.Longitude.Radians),
346
+ rK);
347
+
348
+ const rho = aer.RangeKm;
349
+ const drho = aer.RangeRatePerSec || 0;
350
+ const daz = aer.AzimuthRatePerSec ? aer.AzimuthRatePerSec.Radians : 0;
351
+ const del = aer.ElevationRatePerSec ? aer.ElevationRatePerSec.Radians : 0;
352
+ const saz = Math.sin(aer.Azimuth.Radians);
353
+ const caz = Math.cos(aer.Azimuth.Radians);
354
+ const sel = Math.sin(aer.Elevation.Radians);
355
+ const cel = Math.cos(aer.Elevation.Radians);
356
+
357
+ const rhoSez = new Vector3D(-rho * cel * caz, rho * cel * saz, rho * sel);
358
+ const rhoDotSez = new Vector3D(
359
+ -drho * cel * caz + rho * sel * caz * del + rho * cel * saz * daz,
360
+ drho * cel * saz - rho * sel * saz * del + rho * cel * caz * daz,
361
+ drho * sel + rho * cel * del);
362
+
363
+ const s2e = this.sezToECEF(site);
364
+
365
+ const rhoEcef = s2e.multiplyVector3D(rhoSez);
366
+ const rhoDotEcef = s2e.multiplyVector3D(rhoDotSez);
367
+ const rEcef = rhoEcef.add(rSiteEcef);
368
+ const vEcef = rhoDotEcef;
369
+
370
+ return {Position: rEcef, Velocity: vEcef};
371
+ }
372
+
373
+ /**
374
+ * Convert position and velocity in J2000 to Azimuth, elevation, and range and each of their rates.
375
+ * @param {LLA} site - Ground site in LLA
376
+ * @param {Date} utc - UTC time
377
+ * @param {Vector3D} r - Position vector in J2000
378
+ * @param {Vector3D} [v=null] - Velocity vector in J2000
379
+ * @return {AzElRange} Azimuth, elevation, and range and their rates
380
+ */
381
+ static J2000ToAzElRange(site, utc, r, v = null) {
382
+ // Transform
383
+ const j2k2itrf = this.j2000ToITRF(utc);
384
+
385
+ // SEZ vectors
386
+ const rEcef = j2k2itrf.multiplyVector3D(r);
387
+ const vEcef = v === null ? new Vector3D(0, 0, 0) : j2k2itrf.multiplyVector3D(v);
388
+
389
+ return this.itrfToAzElRange(site, rEcef, v === null ? v : vEcef);
390
+ }
391
+
392
+ /**
393
+ * Convert Az, El, Range to J2000 position and velocity.
394
+ * @param {AzElRange} aer - Azimuth, elevation, and range
395
+ * @param {LLA} site - Ground site in LLA
396
+ * @param {Date} utc - UTC time
397
+ * @return {Object} Position and velocity in J2000
398
+ */
399
+ static AzElRangeToJ2000(aer, site, utc) {
400
+ const {Position: rEcef, Velocity: vEcef} = this.azElRangeToITRF(aer, site);
401
+
402
+ return this.itrfToJ2000(utc, rEcef, vEcef === null ? new Vector3D(0, 0, 0) : vEcef);
403
+ }
404
+
405
+ /**
406
+ * Converts a TEME position vector from an SGP4 propagation to Lat, Lon, Alt.
407
+ * @param {Vector3D} r - Position vector in TEME
408
+ * @param {Date} utc - UTC time
409
+ * @return {LLA} Latitude, longitude, and altitude
410
+ */
411
+ static TEMEToLLA(r, utc) {
412
+ return this.j2000ToLLA(this.temeToJ2000(utc).multiplyVector3D(r), utc);
413
+ }
414
+
415
+ /**
416
+ * Convert Lat, Lon, Alt to a TEME position vector.
417
+ * @param {LLA} lla - Latitude, longitude, and altitude
418
+ * @param {Date} utc - UTC time
419
+ * @return {Vector3D} Position vector in TEME
420
+ */
421
+ static LLAToTEME(lla, utc) {
422
+ return this.j2000ToTEME(utc).multiplyVector3D(this.llaToJ2000(lla, utc));
423
+ }
424
+
425
+ /**
426
+ * Converts a J2000 position vector to Lat, Lon, Alt.
427
+ * J2000 is the default reference frame of the UDL and many other applications.
428
+ * @param {Vector3D} r - Position vector in J2000
429
+ * @param {Date} utc - UTC time
430
+ * @return {LLA} Latitude, longitude, and altitude
431
+ */
432
+ static j2000ToLLA(r, utc) {
433
+ return this.ecefToLLA(this.j2000ToITRF(utc).multiplyVector3D(r));
434
+ }
435
+
436
+ /**
437
+ * Converts Lat, Lon, Alt to J2000 position vector.
438
+ * @param {LLA} lla - Latitude, longitude, and altitude
439
+ * @param {Date} utc - UTC time
440
+ * @return {Vector3D} Position vector in J2000
441
+ */
442
+ static llaToJ2000(lla, utc) {
443
+ return this.itrfToJ2000(utc).multiplyVector3D(this.llaToECEF(lla));
444
+ }
445
+
446
+ /**
447
+ * Convert ECEF coordinates to Lat, Lon, Alt.
448
+ * ECEF includes ITRF and WGS frames.
449
+ * Vallado Fundamentals, p. 172
450
+ * @param {Vector3D} r The vector r must be in ITRF frame (see j2000ToITRF()).
451
+ * @param {number} planetRadKm
452
+ * @param {number} planetEcc
453
+ * @param {number} tol
454
+ * @return {LLA} Latitude, longitude, and altitude
455
+ */
456
+ static ecefToLLA(r, planetRadKm = Earth.EquatorialRadiusKm,
457
+ planetEcc = Earth.EccentricityWgs84, tol = 1e-6) {
458
+ const rdels = Math.sqrt(Math.pow(r.x, 2) + Math.pow(r.y, 2));
459
+ const lambda = Math.acos(r.x / rdels) * Math.sign(Math.asin(r.y / rdels));
460
+ let phigd = Math.atan2(r.z, rdels);
461
+ const rdel = rdels;
462
+ const phigdOld = phigd;
463
+ let C;
464
+
465
+ let iter = 0;
466
+
467
+ do {
468
+ C = planetRadKm / Math.sqrt(1 - Math.pow(planetEcc, 2) * Math.pow(Math.sin(phigd), 2));
469
+ phigd = Math.atan2(r.z + C * Math.pow(planetEcc, 2) * Math.sin(phigd), rdel);
470
+ iter++;
471
+ } while (phigd - phigdOld > tol && iter < 10);
472
+
473
+ const nearPoles = r.angle(new Vector3D(0, 0, 1)) * Constants.RadToDeg < 1
474
+ || r.angle(new Vector3D(0, 0, -1)) * Constants.RadToDeg < 1;
475
+ const S = C * (1 - Math.pow(planetEcc, 2));
476
+ const hEllip = nearPoles ? r.z / Math.sin(phigd) - S : rdel / Math.cos(phigd) - C;
477
+
478
+ return new LLA(phigd * Constants.RadToDeg, lambda * Constants.RadToDeg, hEllip);
479
+ }
480
+
481
+ /**
482
+ * Convert Lat, Lon, Alt to ECEF position.
483
+ * ECEF includes ITRF and WGS frames.
484
+ * Vallado Fundamentals, p. 144
485
+ * @param {LLA} lla - Latitude, longitude, and altitude
486
+ * @param {number} [planetRad=Earth.EquatorialRadiusKm] - Planet radius
487
+ * @param {number} [planetEcc=Earth.EccentricityWgs84] - Planet eccentricity
488
+ * @return {Vector3D} Position vector in ECEF
489
+ */
490
+ static llaToECEF(lla, planetRad = Earth.EquatorialRadiusKm,
491
+ planetEcc = Earth.EccentricityWgs84) {
492
+ const phigd = lla.Latitude.Radians;
493
+ const lamba = lla.Longitude.Radians;
494
+ const h = lla.AltitudeKm;
495
+
496
+ const C = planetRad / Math.sqrt(1
497
+ - Math.pow(planetEcc, 2) * Math.pow(Math.sin(phigd), 2));
498
+ const S = planetRad * (1
499
+ - Math.pow(planetEcc, 2)) / Math.sqrt(1
500
+ - Math.pow(planetEcc, 2) * Math.pow(Math.sin(phigd), 2));
501
+
502
+ return new Vector3D(
503
+ (C + h) * Math.cos(phigd) * Math.cos(lamba),
504
+ (C + h) * Math.cos(phigd) * Math.sin(lamba),
505
+ (S + h) * Math.sin(phigd));
506
+ }
507
+
508
+ /**
509
+ * Converts Azimuth and Elevation (Topocentric Coordinates) to Right Ascension
510
+ * and Declination (Spherical Coordinates) at a given time.
511
+ * @param {Date} utc - The UTC time
512
+ * @param {Site} site - The observer's location
513
+ * @param {AzEl} azel - The azimuth and elevation angles
514
+ * @return {RaDec} The right ascension and declination
515
+ */
516
+ static AzElToRaDec(utc, site, azel) {
517
+ const t = new TimeConverter(utc);
518
+ const latRads = site.Latitude.Radians;
519
+ const azRads = azel.Azimuth.Radians;
520
+ const elRads = azel.Elevation.Radians;
521
+
522
+ const dec = Math.asin(Math.sin(elRads) * Math.sin(latRads)
523
+ + Math.cos(elRads) * Math.cos(latRads) * Math.cos(azRads));
524
+
525
+ // Get local siderial time from longitude
526
+ const {lmst} = TimeConverter.getLMST(site.Longitude.Degrees, t.JulianUT1);
527
+
528
+ // Get local hour angle, the angle between the zenith and north polar distance of the object
529
+ const H = Math.atan2(
530
+ -Math.sin(azRads) * Math.cos(elRads) * Math.cos(latRads),
531
+ Math.sin(elRads) - Math.sin(latRads) * Math.sin(dec));
532
+
533
+ const ra = (lmst - H) % Constants.TwoPi;
534
+
535
+ return new RaDec(ra * Constants.RadToDeg, dec * Constants.RadToDeg);
536
+ }
537
+
538
+ /**
539
+ * Convert ECEF to SEZ frame for a given ground site.
540
+ * @param {Site} site - The observer's location
541
+ * @return {Matrix3D} The transformation matrix from ECEF to SEZ
542
+ */
543
+ static ecefToSEZ(site) {
544
+ const sinLat = Math.sin(site.Latitude.Radians);
545
+ const cosLat = Math.cos(site.Latitude.Radians);
546
+ const sinLon = Math.sin(site.Longitude.Radians);
547
+ const cosLon = Math.cos(site.Longitude.Radians);
548
+
549
+ return new Matrix3D(
550
+ sinLat * cosLon, sinLat * sinLon, -cosLat,
551
+ -sinLon, cosLon, 0,
552
+ cosLat * cosLon, cosLat * sinLon, sinLat,
553
+ );
554
+ }
555
+
556
+ /**
557
+ * Convert SEZ to ECEF frame for a given ground site.
558
+ * @param {Site} site - The observer's location
559
+ * @return {Matrix3D} The transformation matrix from SEZ to ECEF
560
+ */
561
+ static sezToECEF(site) {
562
+ // SEZ to ECEF is the transpose of ECEF to SEZ for orthogonal matrices
563
+ const sinLat = Math.sin(site.Latitude.Radians);
564
+ const cosLat = Math.cos(site.Latitude.Radians);
565
+ const sinLon = Math.sin(site.Longitude.Radians);
566
+ const cosLon = Math.cos(site.Longitude.Radians);
567
+
568
+ // This is the transpose of ecefToSEZ matrix
569
+ return new Matrix3D(
570
+ new Vector3D(sinLat * cosLon, -sinLon, cosLat * cosLon),
571
+ new Vector3D(sinLat * sinLon, cosLon, cosLat * sinLon),
572
+ new Vector3D(-cosLat, 0, sinLat),
573
+ );
574
+ }
575
+
576
+ /**
577
+ * Convert GCRF to J2000 (frame bias).
578
+ * @return {Matrix3D} The transformation matrix from GCRF to J2000
579
+ */
580
+ static gcrfToJ2000() {
581
+ // https://github.com/skyfielders/python-skyfield/blob/master/skyfield/framelib.py
582
+ // 'xi0', 'eta0', and 'da0' are ICRS frame biases in arcseconds taken
583
+ // from IERS (2003) Conventions, Chapter 5.
584
+ const xi0 = -0.0166170 * Constants.ArcsecToRad;
585
+ const eta0 = -0.0068192 * Constants.ArcsecToRad;
586
+ const da0 = -0.01460 * Constants.ArcsecToRad;
587
+
588
+ // Compute elements of rotation matrix.
589
+ const yx = -da0;
590
+ const zx = xi0;
591
+ const xy = da0;
592
+ const zy = eta0;
593
+ const xz = -xi0;
594
+ const yz = -eta0;
595
+
596
+ // Include second-order corrections to diagonal elements.
597
+ const xx = 1 - 0.5 * (yx * yx + zx * zx);
598
+ const yy = 1 - 0.5 * (yx * yx + zy * zy);
599
+ const zz = 1 - 0.5 * (zy * zy + zx * zx);
600
+
601
+ return new Matrix3D(
602
+ xx, xy, xz,
603
+ yx, yy, yz,
604
+ zx, zy, zz,
605
+ );
606
+ }
607
+
608
+ /**
609
+ * Convert J2000 to GCRF (inverse frame bias).
610
+ * Vallado Fundamentals
611
+ * @return {Matrix3D} The transformation matrix from J2000 to GCRF
612
+ */
613
+ static j2000ToGCRF() {
614
+ return this.invertMatrix(this.gcrfToJ2000());
615
+ }
616
+
617
+ /**
618
+ * Convert ITRF to J2000 using IAU-1980 FK5 reduction. This only works for position vectors, not velocity.
619
+ * Vallado Fundamentals
620
+ * @param {number} utc - Julian centuries of TT
621
+ * @return {Matrix3D} The transformation matrix from ITRF to J2000
622
+ */
623
+ static itrfToJ2000(utc) {
624
+ const t = new TimeConverter(utc);
625
+
626
+ const prc = this.precessionFk5(t.JulianCenturiesTT);
627
+ const {nut, nutLon, raan, meanObliq} = this.nutationFk5(t.JulianCenturiesTT);
628
+ const sid = this.siderealFk5(t.JulianUT1, nutLon, meanObliq, raan, 2);
629
+ const pol = this.polarMotionFk5(TimeConverter.XP * Constants.SecondToRad,
630
+ TimeConverter.YP * Constants.SecondToRad);
631
+
632
+ return this.multiplyMatrices(this.multiplyMatrices(
633
+ this.multiplyMatrices(prc, nut), sid), pol);
634
+ }
635
+
636
+ /**
637
+ * ITRF to J2000 with velocity component.
638
+ * @param {Date} utc - UTC time
639
+ * @param {Vector3D} r - Position vector in ITRF
640
+ * @param {Vector3D} v - Velocity vector in ITRF
641
+ * @return {Object} Position and velocity in J2000
642
+ */
643
+ static itrfToJ2000WithVelocity(utc, r, v) {
644
+ const t = new TimeConverter(utc);
645
+
646
+ const omega = new Vector3D(0, 0, Earth.SpinRateRadSec * (1
647
+ - TimeConverter.LOD / Constants.SecPerSolarDay));
648
+
649
+ const prc = this.precessionFk5(t.JulianCenturiesTT);
650
+ const {nut, raan} = this.nutationFk5(t.JulianCenturiesTT);
651
+ const sid = this.siderealFk5(t.JulianUT1, nut.nutLon, nut.meanObliq, raan, 2);
652
+ const pol = this.polarMotionFk5(TimeConverter.XP * Constants.SecondToRad,
653
+ TimeConverter.YP * Constants.SecondToRad);
654
+ const temp = this.multiplyMatrices(prc, nut);
655
+ const trans = this.multiplyMatrices(temp, sid);
656
+
657
+ const rpef = pol.multiplyVector3D(r);
658
+ const vpef = pol.multiplyVector3D(v);
659
+ const crossr = omega.cross(rpef);
660
+ const reci = trans.multiplyVector3D(rpef);
661
+ const veci = trans.multiplyVector3D(vpef.add(crossr));
662
+
663
+ return {Position: reci, Velocity: veci};
664
+ }
665
+
666
+ /**
667
+ * J2000 to ITRF with velocity component.
668
+ * @param {Date} utc - UTC time
669
+ * @param {Vector3D} r - Position vector in J2000
670
+ * @param {Vector3D} v - Velocity vector in J2000
671
+ * @return {Object} Position and velocity in ITRF
672
+ */
673
+ static j2000ToITRFWithVelocity(utc, r, v) {
674
+ const t = new TimeConverter(utc);
675
+
676
+ const omega = new Vector3D(0, 0, Earth.SpinRateRadSec * (1
677
+ - TimeConverter.LOD / Constants.SecPerSolarDay));
678
+
679
+ let prc = this.precessionFk5(t.JulianCenturiesTT);
680
+ const {nut, raan} = this.nutationFk5(t.JulianCenturiesTT);
681
+ let sid = this.siderealFk5(t.JulianUT1, nut.nutLon, nut.meanObliq, raan, 2);
682
+ let pol = this.polarMotionFk5(TimeConverter.XP * Constants.SecondToRad,
683
+ TimeConverter.YP * Constants.SecondToRad);
684
+
685
+ prc = this.transposeMatrix(prc);
686
+ const nutTranspose = this.transposeMatrix(nut.matrix);
687
+ sid = this.transposeMatrix(sid);
688
+ pol = this.transposeMatrix(pol);
689
+
690
+ const temp = this.multiplyMatrices(sid, nutTranspose);
691
+ const trans = this.multiplyMatrices(temp, prc);
692
+
693
+ const rpef = trans.multiplyVector3D(r);
694
+ const recef = pol.multiplyVector3D(rpef);
695
+ const tempvec1 = trans.multiplyVector3D(v);
696
+ const crossr = omega.cross(rpef);
697
+ const vpef = tempvec1.subtract(crossr);
698
+ const vecef = pol.multiplyVector3D(vpef);
699
+
700
+ return {Position: recef, Velocity: vecef};
701
+ }
702
+
703
+ /**
704
+ * Convert J2000 to ITRF using IAU-1980 FK5 reduction. This only works for position vectors, not velocity.
705
+ * Vallado Fundamentals
706
+ * @param {number} utc - Julian centuries of TT
707
+ * @return {Matrix3D} The transformation matrix from J2000 to ITRF
708
+ */
709
+ static j2000ToITRF(utc) {
710
+ return this.invertMatrix(this.itrfToJ2000(utc));
711
+ }
712
+
713
+ /**
714
+ * Convert TEME to J2000 at some UTC time.
715
+ * Vallado Fundamentals
716
+ * @param {Date} utc - UTC time
717
+ * @return {Matrix3D} The transformation matrix from TEME to J2000
718
+ */
719
+ static temeToJ2000(utc) {
720
+ const t = new TimeConverter(utc);
721
+ const ttt = t.JulianCenturiesTT;
722
+
723
+ let eqeTemp = Matrix3D.zeros();
724
+ let eqeg;
725
+
726
+ const prec = this.precessionFk5(ttt);
727
+ // const { nut } = this.nutationFk5(ttt);
728
+ const nut = this.nutationFk5(ttt);
729
+
730
+ // Rotate teme through just geometric terms
731
+ eqeg = nut.nutLon * Math.cos(nut.meanObliq);
732
+ eqeg %= Constants.TwoPi;
733
+
734
+ eqeTemp = new Matrix3D(new Vector3D(Math.cos(eqeg), Math.sin(eqeg), 0),
735
+ new Vector3D(-Math.sin(eqeg), Math.cos(eqeg), 0), new Vector3D(0, 0, 1));
736
+
737
+ const eqe = new Matrix3D(eqeTemp);
738
+ const eqep = eqe.matrix[0].transpose();
739
+ const temp = this.multiplyMatrices(nut.nut, eqep);
740
+ const returnMat = this.multiplyMatrices(prec, temp);
741
+ return returnMat;
742
+ // return prec.multiply(temp);
743
+ }
744
+
745
+ /**
746
+ * Convert J2000 to TEME at some UTC time.
747
+ * @param {Date} utc - UTC time
748
+ * @return {Matrix3D} The transformation matrix from J2000 to TEME
749
+ */
750
+ static j2000ToTEME(utc) {
751
+ return this.invertMatrix(this.temeToJ2000(utc));
752
+ }
753
+
754
+ /**
755
+ * Get nutation transformation according to FK5.
756
+ * This matrix is also TOD to MOD.
757
+ * Vallado Fundamentals
758
+ * @param {number} ttt - Julian centuries of TT
759
+ * @return {Matrix3D} The transformation matrix from J2000 to ITRF
760
+ *
761
+ */
762
+ static nutationFk5(ttt) {
763
+ // Compute the mean obliquity of the ecliptic
764
+ let me1980 = ((0.001813 * ttt - 0.00059) * ttt - 46.8150) * ttt + 84381.448;
765
+ me1980 = me1980 / 3600 % 360;
766
+ me1980 *= Constants.DegToRad;
767
+
768
+ // Evaluate the Delaunay parameters associated with the Moon and the Sun in the interval [0,2π]°
769
+ const oo3600 = 1 / 3600;
770
+ let Mm = ((0.064 * ttt + 31.310) * ttt + 1717915922.6330) * ttt * oo3600 + 134.96298139;
771
+ let Ms = (((-0.012) * ttt - 0.577) * ttt + 129596581.2240) * ttt * oo3600 + 357.52772333;
772
+ let uMm = ((0.011 * ttt - 13.257) * ttt + 1739527263.1370) * ttt * oo3600 + 93.27191028;
773
+ let Ds = ((0.019 * ttt - 6.891) * ttt + 1602961601.3280) * ttt * oo3600 + 297.85036306;
774
+ let Omegam = ((0.008 * ttt + 7.455) * ttt - 6962890.5390) * ttt * oo3600 + 125.04452222;
775
+
776
+ Mm = Mm % 360 * Constants.DegToRad;
777
+ Ms = Ms % 360 * Constants.DegToRad;
778
+ uMm = uMm % 360 * Constants.DegToRad;
779
+ Ds = Ds % 360 * Constants.DegToRad;
780
+ Omegam = Omegam % 360 * Constants.DegToRad;
781
+
782
+ // Compute nutation in longitude and in obliquity
783
+ let dPsi1980 = 0;
784
+ let dEp1980 = 0;
785
+
786
+ // Compute the nutation in the longitude and in obliquity
787
+ for (const row of this.NUT_COEFF) {
788
+ const ap = row[0] * Mm + row[1] * Ms + row[2] * uMm + row[3] * Ds + row[4] * Omegam;
789
+ dPsi1980 += (row[5] + row[6] * ttt) * Math.sin(ap);
790
+ dEp1980 += (row[7] + row[8] * ttt) * Math.cos(ap);
791
+ }
792
+
793
+ // The nutation coefficients lead to angles with unit 0.0001. Hence, we must convert to [rad]
794
+ dPsi1980 *= 0.0001 / 3600 * Constants.DegToRad;
795
+ dEp1980 *= 0.0001 / 3600 * Constants.DegToRad;
796
+
797
+ const meanObliq = me1980;
798
+ const nutObliq = dEp1980;
799
+ const nutLon = dPsi1980;
800
+ const raan = Omegam;
801
+ const trueObliq = meanObliq + nutObliq;
802
+
803
+ const cospsi = Math.cos(nutLon);
804
+ const sinpsi = Math.sin(nutLon);
805
+ const coseps = Math.cos(meanObliq);
806
+ const sineps = Math.sin(meanObliq);
807
+ const costrueeps = Math.cos(trueObliq);
808
+ const sintrueeps = Math.sin(trueObliq);
809
+
810
+ // const nut = new Matrix3D(
811
+ // cospsi, costrueeps * sinpsi, sintrueeps * sinpsi,
812
+ // -coseps * sinpsi, costrueeps * coseps * cospsi + sintrueeps * sineps, sintrueeps * coseps * cospsi - sineps * costrueeps,
813
+ // -sineps * sinpsi, costrueeps * sineps * cospsi - sintrueeps * coseps, sintrueeps * sineps * cospsi + costrueeps * coseps
814
+ // );
815
+
816
+ const nut = new Matrix3D(
817
+ new Vector3D(cospsi, costrueeps * sinpsi, sintrueeps * sinpsi),
818
+ new Vector3D(-coseps * sinpsi, costrueeps * coseps * cospsi + sintrueeps * sineps,
819
+ sintrueeps * coseps * cospsi - sineps * costrueeps),
820
+ new Vector3D(-sineps * sinpsi, costrueeps * sineps * cospsi - sintrueeps * coseps,
821
+ sintrueeps * sineps * cospsi + costrueeps * coseps),
822
+ );
823
+
824
+ return {
825
+ nut,
826
+ meanObliq,
827
+ nutObliq,
828
+ nutLon,
829
+ trueObliq,
830
+ raan,
831
+ };
832
+ }
833
+
834
+ /**
835
+ * Get precession transformation based on FK5.
836
+ * This matrix is also MOD to GCRF.
837
+ * Vallado Fundamentals
838
+ * @param {number} ttt - Julian centuries of TT
839
+ * @return {Matrix3D} Precession transformation matrix
840
+ */
841
+ static precessionFk5(ttt) {
842
+ const convrt = Math.PI / (180 * 3600); // " to rad
843
+
844
+ let zeta = ((0.017998 * ttt + 0.30188) * ttt + 2306.2181) * ttt; // "
845
+ let theta = ((-0.041833 * ttt - 0.42665) * ttt + 2004.3109) * ttt;
846
+ let z = ((0.018203 * ttt + 1.09468) * ttt + 2306.2181) * ttt;
847
+
848
+ zeta *= convrt;
849
+ theta *= convrt;
850
+ z *= convrt;
851
+
852
+ const coszeta = Math.cos(zeta);
853
+ const sinzeta = Math.sin(zeta);
854
+ const costheta = Math.cos(theta);
855
+ const sintheta = Math.sin(theta);
856
+ const cosz = Math.cos(z);
857
+ const sinz = Math.sin(z);
858
+
859
+ // // Form matrix mod to gcrf
860
+ // return new Matrix3D(
861
+ // coszeta * costheta * cosz - sinzeta * sinz, coszeta * costheta * sinz + sinzeta * cosz, coszeta * sintheta,
862
+ // -sinzeta * costheta * cosz - coszeta * sinz, -sinzeta * costheta * sinz + coszeta * cosz, -sinzeta * sintheta,
863
+ // -sintheta * cosz, -sintheta * sinz, costheta
864
+ // );
865
+
866
+ return new Matrix3D(
867
+ new Vector3D(coszeta * costheta * cosz - sinzeta * sinz,
868
+ coszeta * costheta * sinz + sinzeta * cosz, coszeta * sintheta),
869
+ new Vector3D(-sinzeta * costheta * cosz - coszeta * sinz,
870
+ -sinzeta * costheta * sinz + coszeta * cosz, -sinzeta * sintheta),
871
+ new Vector3D(-sintheta * cosz, -sintheta * sinz, costheta));
872
+ }
873
+
874
+ /**
875
+ * Get transformation matrix associated with sidereal time.
876
+ * Thie matrix is also PEF/GTOD to TOD.
877
+ * Vallado Fundamentals
878
+ * @param {number} jdut1 - Julian date of UT1
879
+ * @param {number} deltapsi - Nutation in longitude (rad)
880
+ * @param {number} meaneps - Mean obliquity (rad)
881
+ * @param {number} raan - Right ascension of ascending node (rad)
882
+ * @param {number} eqeterms - Equatorial terms (rad)
883
+ * @return {Matrix3D} Sidereal transformation matrix
884
+ */
885
+ static siderealFk5(jdut1, deltapsi, meaneps, raan, eqeterms) {
886
+ // FK5 approach
887
+ const gmst = TimeConverter.getGMST(jdut1).Radians;
888
+
889
+ // Find mean ast
890
+ let ast = (jdut1 > 2450449.5) && (eqeterms > 0)
891
+ ? gmst + deltapsi * Math.cos(meaneps)
892
+ + 0.00264 * Constants.SecondToRad * Math.sin(raan)
893
+ + 0.000063 * Constants.SecondToRad * Math.sin(2 * raan)
894
+ : gmst + deltapsi * Math.cos(meaneps);
895
+
896
+ ast %= 2.0 * Math.PI;
897
+
898
+ const sinast = Math.sin(ast);
899
+ const cosast = Math.cos(ast);
900
+
901
+ // return new Matrix3D(
902
+ // cosast, -sinast, 0,
903
+ // sinast, cosast, 0,
904
+ // 0, 0, 1
905
+ // );
906
+
907
+ return new Matrix3D(
908
+ new Vector3D(cosast, -sinast, 0), new Vector3D(sinast, cosast, 0),
909
+ new Vector3D(0, 0, 1),
910
+ );
911
+ }
912
+
913
+ /**
914
+ * Get transformation associated with polar motion of Earth.
915
+ * This matrix is also ITRF to PEF/GTOD.
916
+ * xp and yp are the polar motion coefficients (rad).
917
+ * Vallado Fundamentals
918
+ * @param {number} xp - Polar motion coefficient in x (rad)
919
+ * @param {number} yp - Polar motion coefficient in y (rad)
920
+ * @return {Matrix3D} Polar motion transformation matrix
921
+ */
922
+ static polarMotionFk5(xp, yp) {
923
+ const cosxp = Math.cos(xp);
924
+ const sinxp = Math.sin(xp);
925
+ const cosyp = Math.cos(yp);
926
+ const sinyp = Math.sin(yp);
927
+
928
+ // // FK5 approach
929
+ // return new Matrix3D(
930
+ // cosxp, 0, -sinxp,
931
+ // sinxp * sinyp, cosyp, cosxp * sinyp,
932
+ // sinxp * cosyp, -sinyp, cosxp * cosyp
933
+ // );
934
+
935
+ return new Matrix3D(
936
+ new Vector3D(cosxp, 0, -sinxp),
937
+ new Vector3D(sinxp * sinyp, cosyp, cosxp * sinyp),
938
+ new Vector3D(sinxp * cosyp, -sinyp, cosxp * cosyp),
939
+ );
940
+ }
941
+
942
+ /**
943
+ * Check if a frame is inertially defined.
944
+ * @param {ReferenceFrame} frame - The reference frame to check
945
+ * @param {boolean} throwError - Whether to throw an error if the frame is non-inertial
946
+ * @return {boolean} True if the frame is inertial, false otherwise
947
+ */
948
+ static CheckIfInertial(frame, throwError = false) {
949
+ if (frame === ReferenceFrame.ITRF) {
950
+ if (throwError) {
951
+ throw new Error(
952
+ "Cannot use a non-inertial frame for this method."
953
+ + " Try converting to an ECI frame.");
954
+ }
955
+ return false;
956
+ }
957
+ return true;
958
+ }
959
+
960
+ static perifocalToInertial(keplerianElements) {
961
+ const cosi = Math.cos(keplerianElements.i * DEG2RAD);
962
+ const sini = Math.sin(keplerianElements.i * DEG2RAD);
963
+ const cosRaan = Math.cos(keplerianElements.raan * DEG2RAD);
964
+ const sinRaan = Math.sin(keplerianElements.raan * DEG2RAD);
965
+ const cosw = Math.cos(keplerianElements.w * DEG2RAD);
966
+ const sinw = Math.sin(keplerianElements.w * DEG2RAD);
967
+
968
+ // The matrix elements
969
+ const r11 = cosRaan * cosw - sinRaan * sinw * cosi;
970
+ const r12 = -cosRaan * sinw - sinRaan * cosw * cosi;
971
+ const r13 = sinRaan * sini;
972
+
973
+ const r21 = sinRaan * cosw + cosRaan * sinw * cosi;
974
+ const r22 = -sinRaan * sinw + cosRaan * cosw * cosi;
975
+ const r23 = -cosRaan * sini;
976
+
977
+ const r31 = sinw * sini;
978
+ const r32 = cosw * sini;
979
+ const r33 = cosi;
980
+
981
+ // Packing
982
+ return new Matrix3D(
983
+ new Vector3D(r11, r12, r13),
984
+ new Vector3D(r21, r22, r23),
985
+ new Vector3D(r31, r32, r33),
986
+ );
987
+ }
988
+
989
+ static eciToRtn(r, v) {
990
+ const rTemp = [r.x, r.y, r.z];
991
+ const vTemp = [v.x, v.y, v.z];
992
+ const rHat = rTemp.map((component) => component / norm(rTemp));
993
+ const vHat = vTemp.map((component) => component / norm(vTemp));
994
+ const nHat = cross(rHat, vHat).map((component) => component / norm(cross(rHat, vHat)));
995
+ const tHat = cross(nHat, rHat).map((component) => component / norm(cross(nHat, rHat)));
996
+
997
+ return new Matrix3D(
998
+ new Vector3D(rHat[0], rHat[1], rHat[2]),
999
+ new Vector3D(tHat[0], tHat[1], tHat[2]),
1000
+ new Vector3D(nHat[0], nHat[1], nHat[2]),
1001
+ );
1002
+ }
1003
+
1004
+ static rtnToEci(r, v) {
1005
+ const eciToRtn = this.eciToRtn(r, v);
1006
+ return this.invertMatrix(eciToRtn);
1007
+ }
1008
+
1009
+ // 1980 IAU Theory of Nutation Coefficients
1010
+ static NUT_COEFF = [
1011
+ [0, 0, 0, 0, 1, -171996.0, -174.2, 92025.0, 8.9],
1012
+ [0, 0, 2, -2, 2, -13187.0, -1.6, 5736.0, -3.1],
1013
+ [0, 0, 2, 0, 2, -2274.0, -0.2, 977.0, -0.5],
1014
+ [0, 0, 0, 0, 2, 2062.0, 0.2, -895.0, 0.5],
1015
+ [0, 1, 0, 0, 0, 1426.0, -3.4, 54.0, -0.1],
1016
+ [1, 0, 0, 0, 0, 712.0, 0.1, -7.0, 0.0],
1017
+ [0, 1, 2, -2, 2, -517.0, 1.2, 224.0, -0.6],
1018
+ [0, 0, 2, 0, 1, -386.0, -0.4, 200.0, 0.0],
1019
+ [1, 0, 2, 0, 2, -301.0, 0.0, 129.0, -0.1],
1020
+ [0, -1, 2, -2, 2, 217.0, -0.5, -95.0, 0.3],
1021
+ [1, 0, 0, -2, 0, -158.0, 0.0, -1.0, 0.0],
1022
+ [0, 0, 2, -2, 1, 129.0, 0.1, -70.0, 0.0],
1023
+ [-1, 0, 2, 0, 2, 123.0, 0.0, -53.0, 0.0],
1024
+ [1, 0, 0, 0, 1, 63.0, 0.1, -33.0, 0.0],
1025
+ [0, 0, 0, 2, 0, 63.0, 0.0, -2.0, 0.0],
1026
+ [-1, 0, 2, 2, 2, -59.0, 0.0, 26.0, 0.0],
1027
+ [-1, 0, 0, 0, 1, -58.0, -0.1, 32.0, 0.0],
1028
+ [1, 0, 2, 0, 1, -51.0, 0.0, 27.0, 0.0],
1029
+ [2, 0, 0, -2, 0, 48.0, 0.0, 1.0, 0.0],
1030
+ [-2, 0, 2, 0, 1, 46.0, 0.0, -24.0, 0.0],
1031
+ [0, 0, 2, 2, 2, -38.0, 0.0, 16.0, 0.0],
1032
+ [2, 0, 2, 0, 2, -31.0, 0.0, 13.0, 0.0],
1033
+ [2, 0, 0, 0, 0, 29.0, 0.0, -1.0, 0.0],
1034
+ [1, 0, 2, -2, 2, 29.0, 0.0, -12.0, 0.0],
1035
+ [0, 0, 2, 0, 0, 26.0, 0.0, -1.0, 0.0],
1036
+ [0, 0, 2, -2, 0, -22.0, 0.0, 0.0, 0.0],
1037
+ [-1, 0, 2, 0, 1, 21.0, 0.0, -10.0, 0.0],
1038
+ [0, 2, 0, 0, 0, 17.0, -0.1, 0.0, 0.0],
1039
+ [0, 2, 2, -2, 2, -16.0, 0.1, 7.0, 0.0],
1040
+ [-1, 0, 0, 2, 1, 16.0, 0.0, -8.0, 0.0],
1041
+ [0, 1, 0, 0, 1, -15.0, 0.0, 9.0, 0.0],
1042
+ [1, 0, 0, -2, 1, -13.0, 0.0, 7.0, 0.0],
1043
+ [0, -1, 0, 0, 1, -12.0, 0.0, 6.0, 0.0],
1044
+ [2, 0, -2, 0, 0, 11.0, 0.0, 0.0, 0.0],
1045
+ [-1, 0, 2, 2, 1, -10.0, 0.0, 5.0, 0.0],
1046
+ [1, 0, 2, 2, 2, -8.0, 0.0, 3.0, 0.0],
1047
+ [0, -1, 2, 0, 2, -7.0, 0.0, 3.0, 0.0],
1048
+ [0, 0, 2, 2, 1, -7.0, 0.0, 3.0, 0.0],
1049
+ [1, 1, 0, -2, 0, -7.0, 0.0, 0.0, 0.0],
1050
+ [0, 1, 2, 0, 2, 7.0, 0.0, -3.0, 0.0],
1051
+ [-2, 0, 0, 2, 1, -6.0, 0.0, 3.0, 0.0],
1052
+ [0, 0, 0, 2, 1, -6.0, 0.0, 3.0, 0.0],
1053
+ [2, 0, 2, -2, 2, 6.0, 0.0, -3.0, 0.0],
1054
+ [1, 0, 0, 2, 0, 6.0, 0.0, 0.0, 0.0],
1055
+ [1, 0, 2, -2, 1, 6.0, 0.0, -3.0, 0.0],
1056
+ [0, 0, 0, -2, 1, -5.0, 0.0, 3.0, 0.0],
1057
+ [0, -1, 2, -2, 1, -5.0, 0.0, 3.0, 0.0],
1058
+ [2, 0, 2, 0, 1, -5.0, 0.0, 3.0, 0.0],
1059
+ [1, -1, 0, 0, 0, 5.0, 0.0, 0.0, 0.0],
1060
+ [1, 0, 0, -1, 0, -4.0, 0.0, 0.0, 0.0],
1061
+ [0, 0, 0, 1, 0, -4.0, 0.0, 0.0, 0.0],
1062
+ [0, 1, 0, -2, 0, -4.0, 0.0, 0.0, 0.0],
1063
+ [1, 0, -2, 0, 0, 4.0, 0.0, 0.0, 0.0],
1064
+ [2, 0, 0, -2, 1, 4.0, 0.0, -2.0, 0.0],
1065
+ [0, 1, 2, -2, 1, 4.0, 0.0, -2.0, 0.0],
1066
+ [1, 1, 0, 0, 0, -3.0, 0.0, 0.0, 0.0],
1067
+ [1, -1, 0, -1, 0, -3.0, 0.0, 0.0, 0.0],
1068
+ [-1, -1, 2, 2, 2, -3.0, 0.0, 1.0, 0.0],
1069
+ [0, -1, 2, 2, 2, -3.0, 0.0, 1.0, 0.0],
1070
+ [1, -1, 2, 0, 2, -3.0, 0.0, 1.0, 0.0],
1071
+ [3, 0, 2, 0, 2, -3.0, 0.0, 1.0, 0.0],
1072
+ [-2, 0, 2, 0, 2, -3.0, 0.0, 1.0, 0.0],
1073
+ [1, 0, 2, 0, 0, 3.0, 0.0, 0.0, 0.0],
1074
+ [-1, 0, 2, 4, 2, -2.0, 0.0, 1.0, 0.0],
1075
+ [1, 0, 0, 0, 2, -2.0, 0.0, 1.0, 0.0],
1076
+ [-1, 0, 2, -2, 1, -2.0, 0.0, 1.0, 0.0],
1077
+ [0, -2, 2, -2, 1, -2.0, 0.0, 1.0, 0.0],
1078
+ [-2, 0, 0, 0, 1, -2.0, 0.0, 1.0, 0.0],
1079
+ [2, 0, 0, 0, 1, 2.0, 0.0, -1.0, 0.0],
1080
+ [3, 0, 0, 0, 0, 2.0, 0.0, 0.0, 0.0],
1081
+ [1, 1, 2, 0, 2, 2.0, 0.0, -1.0, 0.0],
1082
+ [0, 0, 2, 1, 2, 2.0, 0.0, -1.0, 0.0],
1083
+ [1, 0, 0, 2, 1, -1.0, 0.0, 0.0, 0.0],
1084
+ [1, 0, 2, 2, 1, -1.0, 0.0, 1.0, 0.0],
1085
+ [1, 1, 0, -2, 1, -1.0, 0.0, 0.0, 0.0],
1086
+ [0, 1, 0, 2, 0, -1.0, 0.0, 0.0, 0.0],
1087
+ [0, 1, 2, -2, 0, -1.0, 0.0, 0.0, 0.0],
1088
+ [0, 1, -2, 2, 0, -1.0, 0.0, 0.0, 0.0],
1089
+ [1, 0, -2, 2, 0, -1.0, 0.0, 0.0, 0.0],
1090
+ [1, 0, -2, -2, 0, -1.0, 0.0, 0.0, 0.0],
1091
+ [1, 0, 2, -2, 0, -1.0, 0.0, 0.0, 0.0],
1092
+ [1, 0, 0, -4, 0, -1.0, 0.0, 0.0, 0.0],
1093
+ [2, 0, 0, -4, 0, -1.0, 0.0, 0.0, 0.0],
1094
+ [0, 0, 2, 4, 2, -1.0, 0.0, 0.0, 0.0],
1095
+ [0, 0, 2, -1, 2, -1.0, 0.0, 0.0, 0.0],
1096
+ [-2, 0, 2, 4, 2, -1.0, 0.0, 1.0, 0.0],
1097
+ [2, 0, 2, 2, 2, -1.0, 0.0, 0.0, 0.0],
1098
+ [0, -1, 2, 0, 1, -1.0, 0.0, 0.0, 0.0],
1099
+ [0, 0, -2, 0, 1, -1.0, 0.0, 0.0, 0.0],
1100
+ [0, 0, 4, -2, 2, 1.0, 0.0, 0.0, 0.0],
1101
+ [0, 1, 0, 0, 2, 1.0, 0.0, 0.0, 0.0],
1102
+ [1, 1, 2, -2, 2, 1.0, 0.0, -1.0, 0.0],
1103
+ [3, 0, 2, -2, 2, 1.0, 0.0, 0.0, 0.0],
1104
+ [-2, 0, 2, 2, 2, 1.0, 0.0, -1.0, 0.0],
1105
+ [-1, 0, 0, 0, 2, 1.0, 0.0, -1.0, 0.0],
1106
+ [0, 0, -2, 2, 1, 1.0, 0.0, 0.0, 0.0],
1107
+ [0, 1, 2, 0, 1, 1.0, 0.0, 0.0, 0.0],
1108
+ [-1, 0, 4, 0, 2, 1.0, 0.0, 0.0, 0.0],
1109
+ [2, 1, 0, -2, 0, 1.0, 0.0, 0.0, 0.0],
1110
+ [2, 0, 0, 2, 0, 1.0, 0.0, 0.0, 0.0],
1111
+ [2, 0, 2, -2, 1, 1.0, 0.0, -1.0, 0.0],
1112
+ [2, 0, -2, 0, 1, 1.0, 0.0, 0.0, 0.0],
1113
+ [1, -1, 0, -2, 0, 1.0, 0.0, 0.0, 0.0],
1114
+ [-1, 0, 0, 1, 1, 1.0, 0.0, 0.0, 0.0],
1115
+ [-1, -1, 0, 2, 1, 1.0, 0.0, 0.0, 0.0],
1116
+ [0, 1, 0, 1, 0, 1.0, 0.0, 0.0, 0.0],
1117
+ ];
1118
+ }
1119
+
1120
+ export {FrameConverter, ReferenceFrame};
1121
+ export const EarthParams = Earth;