@saber-usa/node-common 1.6.207

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