@saber-usa/node-common 1.7.7 → 1.7.8

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