@saber-usa/node-common 1.7.7-alpha.2 → 1.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -41
- package/package.json +52 -52
- package/src/FrameConverter.js +1121 -1121
- package/src/LLA.js +179 -179
- package/src/LaunchNominalClass.js +753 -753
- package/src/NodeVector3D.js +71 -71
- package/src/OrbitUtils.js +309 -309
- package/src/PropagateUtils.js +100 -100
- package/src/ShadowGEOCalculator.js +203 -203
- package/src/TimeConverter.js +309 -309
- package/src/astro.js +3301 -3301
- package/src/ballisticPropagator.js +1037 -1037
- package/src/checkNetwork.cjs +20 -20
- package/src/constants.js +37 -37
- package/src/fixDate.js +62 -62
- package/src/index.js +47 -47
- package/src/launchNominal.js +208 -208
- package/src/loggerFactory.cjs +98 -98
- package/src/s3.js +59 -59
- package/src/transform.js +35 -35
- package/src/udl.js +116 -116
- package/src/utils.js +406 -406
package/src/OrbitUtils.js
CHANGED
|
@@ -1,309 +1,309 @@
|
|
|
1
|
-
import {MU, RAD2DEG} from "./constants.js";
|
|
2
|
-
import {cross, norm} from "mathjs";
|
|
3
|
-
import {wrapHalfRevUnsigned, wrapOneRevUnsigned} from "./utils.js";
|
|
4
|
-
|
|
5
|
-
const EARTH_MU_KM = MU; // km³/s²
|
|
6
|
-
|
|
7
|
-
class OrbitUtils {
|
|
8
|
-
/**
|
|
9
|
-
* Get inclination based on azimuth and latitude.
|
|
10
|
-
* @param {number} azimuthRad - Azimuth in radians
|
|
11
|
-
* @param {number} latitudeRad - Latitude in radians
|
|
12
|
-
* @return {number} Inclination in radians
|
|
13
|
-
*/
|
|
14
|
-
static getInclination(azimuthRad, latitudeRad) {
|
|
15
|
-
const inclination = Math.acos(Math.cos(latitudeRad) * Math.sin(azimuthRad));
|
|
16
|
-
return wrapHalfRevUnsigned(inclination);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the azimuth based on inclination and latitude.
|
|
21
|
-
* Two results will be possible, based on whether you are Ascending from South to North or Descending from North to South.
|
|
22
|
-
* @param {number} inclinationRad - Inclination in radians
|
|
23
|
-
* @param {number} latitudeRad - Latitude in radians
|
|
24
|
-
* @param {boolean} ascending - Ascending (true) or descending (false)
|
|
25
|
-
* @return {number} Azimuth in radians
|
|
26
|
-
*/
|
|
27
|
-
static getAzimuth(inclinationRad, latitudeRad, ascending = true) {
|
|
28
|
-
const azimuth = Math.asin(Math.cos(inclinationRad) / Math.cos(latitudeRad));
|
|
29
|
-
if (!ascending) {
|
|
30
|
-
return wrapOneRevUnsigned(Math.PI - azimuth);
|
|
31
|
-
}
|
|
32
|
-
return wrapOneRevUnsigned(azimuth);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Calculate the specific angular momentum (h) vector of an orbit.
|
|
37
|
-
* @param {Array} r - Position vector [x, y, z]
|
|
38
|
-
* @param {Array} v - Velocity vector [vx, vy, vz]
|
|
39
|
-
* @return {Array} Angular momentum vector
|
|
40
|
-
*/
|
|
41
|
-
static getAngularMomentum(r, v) {
|
|
42
|
-
return cross(r, v);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get mean motion (rad/sec).
|
|
47
|
-
* @param {number} a - Semi Major Axis (km)
|
|
48
|
-
* @param {number} mu - Standard gravitational parameter (km^3/s^2)
|
|
49
|
-
* @return {number} Mean motion in rad/sec
|
|
50
|
-
*/
|
|
51
|
-
static getMeanMotion(a, mu = EARTH_MU_KM) {
|
|
52
|
-
return Math.sqrt(mu / Math.pow(a, 3));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Get the period of an orbit in seconds.
|
|
57
|
-
* @param {number} a - Semi Major Axis (km)
|
|
58
|
-
* @param {number} mu - Standard gravitational parameter (km^3/s^2)
|
|
59
|
-
* @return {number} Period in seconds
|
|
60
|
-
*/
|
|
61
|
-
static getPeriod(a, mu = EARTH_MU_KM) {
|
|
62
|
-
if (a <= 0) return null;
|
|
63
|
-
return 2 * Math.PI * Math.sqrt(Math.pow(a, 3) / mu);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Transform an orbit to elliptical with target parameters using Brent's method.
|
|
68
|
-
* Accurate port of the C# TransformElliptical method.
|
|
69
|
-
* @param {Object} svInitial - Initial state vector {position: [x,y,z], velocity: [vx,vy,vz], epoch: Date}
|
|
70
|
-
* @param {number} incRad - Inclination in radians
|
|
71
|
-
* @param {number} raanRad - RAAN in radians
|
|
72
|
-
* @param {number} targetEcc - Target eccentricity
|
|
73
|
-
* @param {number} targetArgOfPeriapsisRad - Target argument of periapsis in radians
|
|
74
|
-
* @param {number} targetPerigeeKm - Target perigee in km
|
|
75
|
-
* @return {Object} Satellite object with new orbital elements
|
|
76
|
-
*/
|
|
77
|
-
static transformElliptical(svInitial, incRad, raanRad,
|
|
78
|
-
targetEcc, targetArgOfPeriapsisRad, targetPerigeeKm) {
|
|
79
|
-
// Normalize the position vector
|
|
80
|
-
const rNorm = norm(svInitial.position);
|
|
81
|
-
const r = svInitial.position.map((x) => x / rNorm);
|
|
82
|
-
|
|
83
|
-
// Project the position vector onto the orbital plane
|
|
84
|
-
const projectedX = r[0] * Math.cos(raanRad) + r[1] * Math.sin(raanRad);
|
|
85
|
-
const projectedY = r[1] * Math.cos(raanRad) - r[0] * Math.sin(raanRad);
|
|
86
|
-
|
|
87
|
-
// Calculate the initial true anomaly using the projected coordinates and known argument of periapsis
|
|
88
|
-
let nu0 = Math.atan2(projectedY, projectedX) - targetArgOfPeriapsisRad;
|
|
89
|
-
nu0 = (nu0 + 2 * Math.PI) % (2 * Math.PI);
|
|
90
|
-
|
|
91
|
-
// Define the objective function that we want to minimize
|
|
92
|
-
const objectiveFunction = (nu) => {
|
|
93
|
-
nu = (nu + 2 * Math.PI) % (2 * Math.PI);
|
|
94
|
-
|
|
95
|
-
// Create orbital elements with the trial true anomaly
|
|
96
|
-
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
97
|
-
const elements = {
|
|
98
|
-
semiMajorAxis: semiMajorAxis,
|
|
99
|
-
eccentricity: targetEcc,
|
|
100
|
-
inclination: incRad,
|
|
101
|
-
raan: raanRad,
|
|
102
|
-
argOfPeriapsis: targetArgOfPeriapsisRad,
|
|
103
|
-
trueAnomaly: nu,
|
|
104
|
-
epoch: svInitial.epoch,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
// Convert elements to state vector
|
|
108
|
-
const trialState = this.elementsToStateVector(elements);
|
|
109
|
-
|
|
110
|
-
// Normalize the trial position vector
|
|
111
|
-
const trialRNorm = norm(trialState.position);
|
|
112
|
-
const trialR = trialState.position.map((x) => x / trialRNorm);
|
|
113
|
-
|
|
114
|
-
// Scale trial position to match initial position magnitude
|
|
115
|
-
const pScaled = trialR.map((x) => x * rNorm);
|
|
116
|
-
|
|
117
|
-
// Return the distance between initial and trial positions
|
|
118
|
-
return norm(svInitial.position.map((x, i) => x - pScaled[i]));
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Use Brent's method for minimization
|
|
122
|
-
const tolerance = 1e-6; // radians
|
|
123
|
-
const maxIterations = 1000;
|
|
124
|
-
|
|
125
|
-
// Initial bracket around the solution
|
|
126
|
-
let a = nu0 - Math.PI;
|
|
127
|
-
let b = nu0 + Math.PI;
|
|
128
|
-
let c = nu0;
|
|
129
|
-
|
|
130
|
-
let fa = objectiveFunction(a);
|
|
131
|
-
let fb = objectiveFunction(b);
|
|
132
|
-
let fc = objectiveFunction(c);
|
|
133
|
-
|
|
134
|
-
let d = 0; let e = 0; let min1; let min2;
|
|
135
|
-
let p; let q; let r1; let tol1; let xm;
|
|
136
|
-
|
|
137
|
-
for (let iter = 0; iter < maxIterations; iter++) {
|
|
138
|
-
if (Math.abs(b - a) < tolerance) {
|
|
139
|
-
// Found minimum
|
|
140
|
-
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
141
|
-
return [semiMajorAxis, targetEcc, incRad * RAD2DEG,
|
|
142
|
-
raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
xm = 0.5 * (a + b);
|
|
146
|
-
tol1 = tolerance * Math.abs(c) + 1e-10;
|
|
147
|
-
min1 = Math.abs(c - a);
|
|
148
|
-
min2 = Math.abs(b - c);
|
|
149
|
-
|
|
150
|
-
if (min1 < tol1 || min2 < tol1) {
|
|
151
|
-
// Found minimum
|
|
152
|
-
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
153
|
-
return [semiMajorAxis, targetEcc, incRad * RAD2DEG,
|
|
154
|
-
raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Construct a trial parabolic fit
|
|
158
|
-
if (Math.abs(e) >= tol1) {
|
|
159
|
-
r1 = (c - a) * (fb - fc);
|
|
160
|
-
q = (c - b) * (fa - fc);
|
|
161
|
-
p = (c - b) * q - (c - a) * r1;
|
|
162
|
-
q = 2.0 * (q - r1);
|
|
163
|
-
if (q > 0.0) p = -p;
|
|
164
|
-
q = Math.abs(q);
|
|
165
|
-
min1 = 3.0 * q * Math.abs(xm);
|
|
166
|
-
min2 = Math.abs(e * q);
|
|
167
|
-
if (2.0 * p < (min1 < min2 ? min1 : min2)) {
|
|
168
|
-
e = d;
|
|
169
|
-
d = p / q;
|
|
170
|
-
} else {
|
|
171
|
-
d = xm;
|
|
172
|
-
e = d;
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
d = xm;
|
|
176
|
-
e = d;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
a = b;
|
|
180
|
-
fa = fb;
|
|
181
|
-
if (Math.abs(d) > tol1) {
|
|
182
|
-
b += d;
|
|
183
|
-
} else {
|
|
184
|
-
b += (xm >= 0 ? Math.abs(tol1) : -Math.abs(tol1));
|
|
185
|
-
}
|
|
186
|
-
fb = objectiveFunction(b);
|
|
187
|
-
if ((fb <= fc && c > b) || (fb >= fc && c < b)) {
|
|
188
|
-
c = a;
|
|
189
|
-
fc = fa;
|
|
190
|
-
a = b;
|
|
191
|
-
fa = fb;
|
|
192
|
-
b = c;
|
|
193
|
-
fb = fc;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
throw new Error("Did not converge within maximum iterations");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Convert orbital elements to state vector
|
|
202
|
-
* @param {Object} elements - Orbital elements
|
|
203
|
-
* @return {Object} State vector {position, velocity}
|
|
204
|
-
*/
|
|
205
|
-
static elementsToStateVector(elements) {
|
|
206
|
-
const {semiMajorAxis, eccentricity, inclination,
|
|
207
|
-
raan, argOfPeriapsis, trueAnomaly} = elements;
|
|
208
|
-
const mu = EARTH_MU_KM;
|
|
209
|
-
|
|
210
|
-
// Calculate radius
|
|
211
|
-
const rMag = semiMajorAxis * (1 - eccentricity * eccentricity)
|
|
212
|
-
/ (1 + eccentricity * Math.cos(trueAnomaly));
|
|
213
|
-
|
|
214
|
-
// Position in orbital plane
|
|
215
|
-
const rOrbital = [
|
|
216
|
-
rMag * Math.cos(trueAnomaly),
|
|
217
|
-
rMag * Math.sin(trueAnomaly),
|
|
218
|
-
0,
|
|
219
|
-
];
|
|
220
|
-
|
|
221
|
-
// Velocity in orbital plane
|
|
222
|
-
const h = Math.sqrt(mu * semiMajorAxis * (1 - eccentricity * eccentricity));
|
|
223
|
-
const vOrbital = [
|
|
224
|
-
-mu / h * Math.sin(trueAnomaly),
|
|
225
|
-
mu / h * (eccentricity + Math.cos(trueAnomaly)),
|
|
226
|
-
0,
|
|
227
|
-
];
|
|
228
|
-
|
|
229
|
-
// Rotation matrices
|
|
230
|
-
const cosW = Math.cos(argOfPeriapsis);
|
|
231
|
-
const sinW = Math.sin(argOfPeriapsis);
|
|
232
|
-
const cosO = Math.cos(raan);
|
|
233
|
-
const sinO = Math.sin(raan);
|
|
234
|
-
const cosI = Math.cos(inclination);
|
|
235
|
-
const sinI = Math.sin(inclination);
|
|
236
|
-
|
|
237
|
-
// Transform to inertial frame
|
|
238
|
-
const r = [
|
|
239
|
-
rOrbital[0] * (cosW * cosO - sinW * cosI * sinO)
|
|
240
|
-
- rOrbital[1] * (sinW * cosO + cosW * cosI * sinO),
|
|
241
|
-
rOrbital[0] * (cosW * sinO + sinW * cosI * cosO) + rOrbital[1]
|
|
242
|
-
* (cosW * cosI * cosO - sinW * sinO),
|
|
243
|
-
rOrbital[0] * (sinW * sinI) + rOrbital[1] * (cosW * sinI),
|
|
244
|
-
];
|
|
245
|
-
|
|
246
|
-
const v = [
|
|
247
|
-
vOrbital[0] * (cosW * cosO - sinW * cosI * sinO)
|
|
248
|
-
- vOrbital[1] * (sinW * cosO + cosW * cosI * sinO),
|
|
249
|
-
vOrbital[0] * (cosW * sinO + sinW * cosI * cosO)
|
|
250
|
-
+ vOrbital[1] * (cosW * cosI * cosO - sinW * sinO),
|
|
251
|
-
vOrbital[0] * (sinW * sinI) + vOrbital[1] * (cosW * sinI),
|
|
252
|
-
];
|
|
253
|
-
|
|
254
|
-
return {position: r, velocity: v};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Convert true anomaly to eccentric anomaly
|
|
259
|
-
* @param {number} nu - true anomaly
|
|
260
|
-
* @param {number} e - eccentricity
|
|
261
|
-
* @return {number} Eccentric Anomaly
|
|
262
|
-
*/
|
|
263
|
-
static trueAnomalyToEccentricAnomaly(nu, e) {
|
|
264
|
-
const cosE = (e + Math.cos(nu)) / (1 + e * Math.cos(nu));
|
|
265
|
-
const sinE = Math.sqrt(1 - e * e) * Math.sin(nu) / (1 + e * Math.cos(nu));
|
|
266
|
-
return Math.atan2(sinE, cosE);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Convert eccentric anomaly to mean anomaly
|
|
271
|
-
* @param {number} E - Eccentric anomaly
|
|
272
|
-
* @param {number} e - eccentricity
|
|
273
|
-
* @return {number} Mean Anomaly
|
|
274
|
-
*/
|
|
275
|
-
static eccentricAnomalyToMeanAnomaly(E, e) {
|
|
276
|
-
return E - e * Math.sin(E);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Convert mean anomaly to eccentric anomaly (Kepler's equation)
|
|
281
|
-
* @param {number} M - Mean anomaly
|
|
282
|
-
* @param {number} e - eccentricity
|
|
283
|
-
* @param {number} tolerance - numerical tolerance
|
|
284
|
-
* @return {number} Eccentric Anomaly
|
|
285
|
-
*/
|
|
286
|
-
static meanAnomalyToEccentricAnomaly(M, e, tolerance = 1e-8) {
|
|
287
|
-
let E = M;
|
|
288
|
-
let delta = 1;
|
|
289
|
-
while (Math.abs(delta) > tolerance) {
|
|
290
|
-
delta = (M - E + e * Math.sin(E)) / (1 - e * Math.cos(E));
|
|
291
|
-
E += delta;
|
|
292
|
-
}
|
|
293
|
-
return E;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Convert eccentric anomaly to true anomaly
|
|
298
|
-
* @param {number} E - Eccentric anomaly
|
|
299
|
-
* @param {number} e - eccentricity
|
|
300
|
-
* @return {number} true anomaly
|
|
301
|
-
*/
|
|
302
|
-
static eccentricAnomalyToTrueAnomaly(E, e) {
|
|
303
|
-
const cosNu = (Math.cos(E) - e) / (1 - e * Math.cos(E));
|
|
304
|
-
const sinNu = Math.sqrt(1 - e * e) * Math.sin(E) / (1 - e * Math.cos(E));
|
|
305
|
-
return Math.atan2(sinNu, cosNu);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export {OrbitUtils};
|
|
1
|
+
import {MU, RAD2DEG} from "./constants.js";
|
|
2
|
+
import {cross, norm} from "mathjs";
|
|
3
|
+
import {wrapHalfRevUnsigned, wrapOneRevUnsigned} from "./utils.js";
|
|
4
|
+
|
|
5
|
+
const EARTH_MU_KM = MU; // km³/s²
|
|
6
|
+
|
|
7
|
+
class OrbitUtils {
|
|
8
|
+
/**
|
|
9
|
+
* Get inclination based on azimuth and latitude.
|
|
10
|
+
* @param {number} azimuthRad - Azimuth in radians
|
|
11
|
+
* @param {number} latitudeRad - Latitude in radians
|
|
12
|
+
* @return {number} Inclination in radians
|
|
13
|
+
*/
|
|
14
|
+
static getInclination(azimuthRad, latitudeRad) {
|
|
15
|
+
const inclination = Math.acos(Math.cos(latitudeRad) * Math.sin(azimuthRad));
|
|
16
|
+
return wrapHalfRevUnsigned(inclination);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the azimuth based on inclination and latitude.
|
|
21
|
+
* Two results will be possible, based on whether you are Ascending from South to North or Descending from North to South.
|
|
22
|
+
* @param {number} inclinationRad - Inclination in radians
|
|
23
|
+
* @param {number} latitudeRad - Latitude in radians
|
|
24
|
+
* @param {boolean} ascending - Ascending (true) or descending (false)
|
|
25
|
+
* @return {number} Azimuth in radians
|
|
26
|
+
*/
|
|
27
|
+
static getAzimuth(inclinationRad, latitudeRad, ascending = true) {
|
|
28
|
+
const azimuth = Math.asin(Math.cos(inclinationRad) / Math.cos(latitudeRad));
|
|
29
|
+
if (!ascending) {
|
|
30
|
+
return wrapOneRevUnsigned(Math.PI - azimuth);
|
|
31
|
+
}
|
|
32
|
+
return wrapOneRevUnsigned(azimuth);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Calculate the specific angular momentum (h) vector of an orbit.
|
|
37
|
+
* @param {Array} r - Position vector [x, y, z]
|
|
38
|
+
* @param {Array} v - Velocity vector [vx, vy, vz]
|
|
39
|
+
* @return {Array} Angular momentum vector
|
|
40
|
+
*/
|
|
41
|
+
static getAngularMomentum(r, v) {
|
|
42
|
+
return cross(r, v);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get mean motion (rad/sec).
|
|
47
|
+
* @param {number} a - Semi Major Axis (km)
|
|
48
|
+
* @param {number} mu - Standard gravitational parameter (km^3/s^2)
|
|
49
|
+
* @return {number} Mean motion in rad/sec
|
|
50
|
+
*/
|
|
51
|
+
static getMeanMotion(a, mu = EARTH_MU_KM) {
|
|
52
|
+
return Math.sqrt(mu / Math.pow(a, 3));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the period of an orbit in seconds.
|
|
57
|
+
* @param {number} a - Semi Major Axis (km)
|
|
58
|
+
* @param {number} mu - Standard gravitational parameter (km^3/s^2)
|
|
59
|
+
* @return {number} Period in seconds
|
|
60
|
+
*/
|
|
61
|
+
static getPeriod(a, mu = EARTH_MU_KM) {
|
|
62
|
+
if (a <= 0) return null;
|
|
63
|
+
return 2 * Math.PI * Math.sqrt(Math.pow(a, 3) / mu);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Transform an orbit to elliptical with target parameters using Brent's method.
|
|
68
|
+
* Accurate port of the C# TransformElliptical method.
|
|
69
|
+
* @param {Object} svInitial - Initial state vector {position: [x,y,z], velocity: [vx,vy,vz], epoch: Date}
|
|
70
|
+
* @param {number} incRad - Inclination in radians
|
|
71
|
+
* @param {number} raanRad - RAAN in radians
|
|
72
|
+
* @param {number} targetEcc - Target eccentricity
|
|
73
|
+
* @param {number} targetArgOfPeriapsisRad - Target argument of periapsis in radians
|
|
74
|
+
* @param {number} targetPerigeeKm - Target perigee in km
|
|
75
|
+
* @return {Object} Satellite object with new orbital elements
|
|
76
|
+
*/
|
|
77
|
+
static transformElliptical(svInitial, incRad, raanRad,
|
|
78
|
+
targetEcc, targetArgOfPeriapsisRad, targetPerigeeKm) {
|
|
79
|
+
// Normalize the position vector
|
|
80
|
+
const rNorm = norm(svInitial.position);
|
|
81
|
+
const r = svInitial.position.map((x) => x / rNorm);
|
|
82
|
+
|
|
83
|
+
// Project the position vector onto the orbital plane
|
|
84
|
+
const projectedX = r[0] * Math.cos(raanRad) + r[1] * Math.sin(raanRad);
|
|
85
|
+
const projectedY = r[1] * Math.cos(raanRad) - r[0] * Math.sin(raanRad);
|
|
86
|
+
|
|
87
|
+
// Calculate the initial true anomaly using the projected coordinates and known argument of periapsis
|
|
88
|
+
let nu0 = Math.atan2(projectedY, projectedX) - targetArgOfPeriapsisRad;
|
|
89
|
+
nu0 = (nu0 + 2 * Math.PI) % (2 * Math.PI);
|
|
90
|
+
|
|
91
|
+
// Define the objective function that we want to minimize
|
|
92
|
+
const objectiveFunction = (nu) => {
|
|
93
|
+
nu = (nu + 2 * Math.PI) % (2 * Math.PI);
|
|
94
|
+
|
|
95
|
+
// Create orbital elements with the trial true anomaly
|
|
96
|
+
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
97
|
+
const elements = {
|
|
98
|
+
semiMajorAxis: semiMajorAxis,
|
|
99
|
+
eccentricity: targetEcc,
|
|
100
|
+
inclination: incRad,
|
|
101
|
+
raan: raanRad,
|
|
102
|
+
argOfPeriapsis: targetArgOfPeriapsisRad,
|
|
103
|
+
trueAnomaly: nu,
|
|
104
|
+
epoch: svInitial.epoch,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Convert elements to state vector
|
|
108
|
+
const trialState = this.elementsToStateVector(elements);
|
|
109
|
+
|
|
110
|
+
// Normalize the trial position vector
|
|
111
|
+
const trialRNorm = norm(trialState.position);
|
|
112
|
+
const trialR = trialState.position.map((x) => x / trialRNorm);
|
|
113
|
+
|
|
114
|
+
// Scale trial position to match initial position magnitude
|
|
115
|
+
const pScaled = trialR.map((x) => x * rNorm);
|
|
116
|
+
|
|
117
|
+
// Return the distance between initial and trial positions
|
|
118
|
+
return norm(svInitial.position.map((x, i) => x - pScaled[i]));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Use Brent's method for minimization
|
|
122
|
+
const tolerance = 1e-6; // radians
|
|
123
|
+
const maxIterations = 1000;
|
|
124
|
+
|
|
125
|
+
// Initial bracket around the solution
|
|
126
|
+
let a = nu0 - Math.PI;
|
|
127
|
+
let b = nu0 + Math.PI;
|
|
128
|
+
let c = nu0;
|
|
129
|
+
|
|
130
|
+
let fa = objectiveFunction(a);
|
|
131
|
+
let fb = objectiveFunction(b);
|
|
132
|
+
let fc = objectiveFunction(c);
|
|
133
|
+
|
|
134
|
+
let d = 0; let e = 0; let min1; let min2;
|
|
135
|
+
let p; let q; let r1; let tol1; let xm;
|
|
136
|
+
|
|
137
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
138
|
+
if (Math.abs(b - a) < tolerance) {
|
|
139
|
+
// Found minimum
|
|
140
|
+
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
141
|
+
return [semiMajorAxis, targetEcc, incRad * RAD2DEG,
|
|
142
|
+
raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
xm = 0.5 * (a + b);
|
|
146
|
+
tol1 = tolerance * Math.abs(c) + 1e-10;
|
|
147
|
+
min1 = Math.abs(c - a);
|
|
148
|
+
min2 = Math.abs(b - c);
|
|
149
|
+
|
|
150
|
+
if (min1 < tol1 || min2 < tol1) {
|
|
151
|
+
// Found minimum
|
|
152
|
+
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
153
|
+
return [semiMajorAxis, targetEcc, incRad * RAD2DEG,
|
|
154
|
+
raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Construct a trial parabolic fit
|
|
158
|
+
if (Math.abs(e) >= tol1) {
|
|
159
|
+
r1 = (c - a) * (fb - fc);
|
|
160
|
+
q = (c - b) * (fa - fc);
|
|
161
|
+
p = (c - b) * q - (c - a) * r1;
|
|
162
|
+
q = 2.0 * (q - r1);
|
|
163
|
+
if (q > 0.0) p = -p;
|
|
164
|
+
q = Math.abs(q);
|
|
165
|
+
min1 = 3.0 * q * Math.abs(xm);
|
|
166
|
+
min2 = Math.abs(e * q);
|
|
167
|
+
if (2.0 * p < (min1 < min2 ? min1 : min2)) {
|
|
168
|
+
e = d;
|
|
169
|
+
d = p / q;
|
|
170
|
+
} else {
|
|
171
|
+
d = xm;
|
|
172
|
+
e = d;
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
d = xm;
|
|
176
|
+
e = d;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
a = b;
|
|
180
|
+
fa = fb;
|
|
181
|
+
if (Math.abs(d) > tol1) {
|
|
182
|
+
b += d;
|
|
183
|
+
} else {
|
|
184
|
+
b += (xm >= 0 ? Math.abs(tol1) : -Math.abs(tol1));
|
|
185
|
+
}
|
|
186
|
+
fb = objectiveFunction(b);
|
|
187
|
+
if ((fb <= fc && c > b) || (fb >= fc && c < b)) {
|
|
188
|
+
c = a;
|
|
189
|
+
fc = fa;
|
|
190
|
+
a = b;
|
|
191
|
+
fa = fb;
|
|
192
|
+
b = c;
|
|
193
|
+
fb = fc;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
throw new Error("Did not converge within maximum iterations");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Convert orbital elements to state vector
|
|
202
|
+
* @param {Object} elements - Orbital elements
|
|
203
|
+
* @return {Object} State vector {position, velocity}
|
|
204
|
+
*/
|
|
205
|
+
static elementsToStateVector(elements) {
|
|
206
|
+
const {semiMajorAxis, eccentricity, inclination,
|
|
207
|
+
raan, argOfPeriapsis, trueAnomaly} = elements;
|
|
208
|
+
const mu = EARTH_MU_KM;
|
|
209
|
+
|
|
210
|
+
// Calculate radius
|
|
211
|
+
const rMag = semiMajorAxis * (1 - eccentricity * eccentricity)
|
|
212
|
+
/ (1 + eccentricity * Math.cos(trueAnomaly));
|
|
213
|
+
|
|
214
|
+
// Position in orbital plane
|
|
215
|
+
const rOrbital = [
|
|
216
|
+
rMag * Math.cos(trueAnomaly),
|
|
217
|
+
rMag * Math.sin(trueAnomaly),
|
|
218
|
+
0,
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
// Velocity in orbital plane
|
|
222
|
+
const h = Math.sqrt(mu * semiMajorAxis * (1 - eccentricity * eccentricity));
|
|
223
|
+
const vOrbital = [
|
|
224
|
+
-mu / h * Math.sin(trueAnomaly),
|
|
225
|
+
mu / h * (eccentricity + Math.cos(trueAnomaly)),
|
|
226
|
+
0,
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
// Rotation matrices
|
|
230
|
+
const cosW = Math.cos(argOfPeriapsis);
|
|
231
|
+
const sinW = Math.sin(argOfPeriapsis);
|
|
232
|
+
const cosO = Math.cos(raan);
|
|
233
|
+
const sinO = Math.sin(raan);
|
|
234
|
+
const cosI = Math.cos(inclination);
|
|
235
|
+
const sinI = Math.sin(inclination);
|
|
236
|
+
|
|
237
|
+
// Transform to inertial frame
|
|
238
|
+
const r = [
|
|
239
|
+
rOrbital[0] * (cosW * cosO - sinW * cosI * sinO)
|
|
240
|
+
- rOrbital[1] * (sinW * cosO + cosW * cosI * sinO),
|
|
241
|
+
rOrbital[0] * (cosW * sinO + sinW * cosI * cosO) + rOrbital[1]
|
|
242
|
+
* (cosW * cosI * cosO - sinW * sinO),
|
|
243
|
+
rOrbital[0] * (sinW * sinI) + rOrbital[1] * (cosW * sinI),
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const v = [
|
|
247
|
+
vOrbital[0] * (cosW * cosO - sinW * cosI * sinO)
|
|
248
|
+
- vOrbital[1] * (sinW * cosO + cosW * cosI * sinO),
|
|
249
|
+
vOrbital[0] * (cosW * sinO + sinW * cosI * cosO)
|
|
250
|
+
+ vOrbital[1] * (cosW * cosI * cosO - sinW * sinO),
|
|
251
|
+
vOrbital[0] * (sinW * sinI) + vOrbital[1] * (cosW * sinI),
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
return {position: r, velocity: v};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Convert true anomaly to eccentric anomaly
|
|
259
|
+
* @param {number} nu - true anomaly
|
|
260
|
+
* @param {number} e - eccentricity
|
|
261
|
+
* @return {number} Eccentric Anomaly
|
|
262
|
+
*/
|
|
263
|
+
static trueAnomalyToEccentricAnomaly(nu, e) {
|
|
264
|
+
const cosE = (e + Math.cos(nu)) / (1 + e * Math.cos(nu));
|
|
265
|
+
const sinE = Math.sqrt(1 - e * e) * Math.sin(nu) / (1 + e * Math.cos(nu));
|
|
266
|
+
return Math.atan2(sinE, cosE);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Convert eccentric anomaly to mean anomaly
|
|
271
|
+
* @param {number} E - Eccentric anomaly
|
|
272
|
+
* @param {number} e - eccentricity
|
|
273
|
+
* @return {number} Mean Anomaly
|
|
274
|
+
*/
|
|
275
|
+
static eccentricAnomalyToMeanAnomaly(E, e) {
|
|
276
|
+
return E - e * Math.sin(E);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Convert mean anomaly to eccentric anomaly (Kepler's equation)
|
|
281
|
+
* @param {number} M - Mean anomaly
|
|
282
|
+
* @param {number} e - eccentricity
|
|
283
|
+
* @param {number} tolerance - numerical tolerance
|
|
284
|
+
* @return {number} Eccentric Anomaly
|
|
285
|
+
*/
|
|
286
|
+
static meanAnomalyToEccentricAnomaly(M, e, tolerance = 1e-8) {
|
|
287
|
+
let E = M;
|
|
288
|
+
let delta = 1;
|
|
289
|
+
while (Math.abs(delta) > tolerance) {
|
|
290
|
+
delta = (M - E + e * Math.sin(E)) / (1 - e * Math.cos(E));
|
|
291
|
+
E += delta;
|
|
292
|
+
}
|
|
293
|
+
return E;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Convert eccentric anomaly to true anomaly
|
|
298
|
+
* @param {number} E - Eccentric anomaly
|
|
299
|
+
* @param {number} e - eccentricity
|
|
300
|
+
* @return {number} true anomaly
|
|
301
|
+
*/
|
|
302
|
+
static eccentricAnomalyToTrueAnomaly(E, e) {
|
|
303
|
+
const cosNu = (Math.cos(E) - e) / (1 - e * Math.cos(E));
|
|
304
|
+
const sinNu = Math.sqrt(1 - e * e) * Math.sin(E) / (1 - e * Math.cos(E));
|
|
305
|
+
return Math.atan2(sinNu, cosNu);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export {OrbitUtils};
|