@saber-usa/node-common 1.7.2 → 1.7.3

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