@saber-usa/node-common 1.7.3 → 1.7.4
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 +51 -51
- package/src/FrameConverter.js +1120 -1120
- package/src/LLA.js +179 -179
- package/src/LaunchNominalClass.js +774 -774
- package/src/NodeVector3D.js +71 -71
- package/src/OrbitUtils.js +490 -490
- package/src/PropagateUtils.js +100 -100
- package/src/ShadowGEOCalculator.js +203 -203
- package/src/TimeConverter.js +309 -309
- package/src/astro.js +3217 -3214
- package/src/ballisticPropagator.js +1037 -1037
- package/src/checkNetwork.cjs +20 -20
- package/src/constants.js +30 -30
- 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/utils.js
CHANGED
|
@@ -1,406 +1,406 @@
|
|
|
1
|
-
import {cross, dot, norm} from "mathjs";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Wrap angle to [0, 2π] range (0 to 360 degrees)
|
|
5
|
-
* @param {number} angleRad - Angle in radians
|
|
6
|
-
* @return {number} Wrapped angle in radians
|
|
7
|
-
*/
|
|
8
|
-
function wrapOneRevUnsigned(angleRad) {
|
|
9
|
-
let wrapped = angleRad % (2 * Math.PI);
|
|
10
|
-
if (wrapped < 0) {
|
|
11
|
-
wrapped += 2 * Math.PI;
|
|
12
|
-
}
|
|
13
|
-
return wrapped;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Wrap angle to [0, π] range (0 to 180 degrees)
|
|
18
|
-
* @param {number} angleRad - Angle in radians
|
|
19
|
-
* @return {number} Wrapped angle in radians
|
|
20
|
-
*/
|
|
21
|
-
function wrapHalfRevUnsigned(angleRad) {
|
|
22
|
-
let wrapped = angleRad % Math.PI;
|
|
23
|
-
if (wrapped < 0) {
|
|
24
|
-
wrapped += Math.PI;
|
|
25
|
-
}
|
|
26
|
-
return wrapped;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Wrap angle to [-π, π] range (-180 to 180 degrees)
|
|
31
|
-
* @param {number} angleRad - Angle in radians
|
|
32
|
-
* @return {number} Wrapped angle in radians
|
|
33
|
-
*/
|
|
34
|
-
function wrapHalfRev(angleRad) {
|
|
35
|
-
let wrapped = angleRad % (2 * Math.PI);
|
|
36
|
-
if (wrapped > Math.PI) {
|
|
37
|
-
wrapped -= 2 * Math.PI;
|
|
38
|
-
} else if (wrapped < -Math.PI) {
|
|
39
|
-
wrapped += 2 * Math.PI;
|
|
40
|
-
}
|
|
41
|
-
return wrapped;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Wrap angle to [-π/2, π/2] range (-90 to 90 degrees)
|
|
46
|
-
* @param {number} angleRad - Angle in radians
|
|
47
|
-
* @return {number} Wrapped angle in radians
|
|
48
|
-
*/
|
|
49
|
-
function wrapQuarterRev(angleRad) {
|
|
50
|
-
let wrapped = angleRad % Math.PI;
|
|
51
|
-
if (wrapped > Math.PI / 2) {
|
|
52
|
-
wrapped -= Math.PI;
|
|
53
|
-
} else if (wrapped < -Math.PI / 2) {
|
|
54
|
-
wrapped += Math.PI;
|
|
55
|
-
}
|
|
56
|
-
return wrapped;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Will convert a julian date to a gregorian calendar date.
|
|
61
|
-
* @param {Double} julianDay e.g 2460153.253704
|
|
62
|
-
* @return {Date} javascript date object e.g 2023-07-27 18:05:20
|
|
63
|
-
*/
|
|
64
|
-
function julianToGregorian(julianDay) {
|
|
65
|
-
const j = julianDay + 0.5;
|
|
66
|
-
const z = Math.floor(j);
|
|
67
|
-
const f = j - z - 0.5; // subtract 0.5 to account for the day starting at noon
|
|
68
|
-
const dayFraction = f + 0.5; // add 0.5 to account for the day starting at midnight
|
|
69
|
-
|
|
70
|
-
let a;
|
|
71
|
-
|
|
72
|
-
if (z < 2299161) {
|
|
73
|
-
a = z;
|
|
74
|
-
} else {
|
|
75
|
-
const alpha = Math.floor((z - 1867216.25) / 36524.25);
|
|
76
|
-
a = z + 1 + alpha - Math.floor(alpha / 4);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const b = a + 1524;
|
|
80
|
-
const c = Math.floor((b - 122.1) / 365.25);
|
|
81
|
-
const d = Math.floor(365.25 * c);
|
|
82
|
-
const e = Math.floor((b - d) / 30.6001);
|
|
83
|
-
|
|
84
|
-
const day = b - d - Math.floor(30.6001 * e);
|
|
85
|
-
const month = e < 14 ? e - 1 : e - 13;
|
|
86
|
-
const year = month > 2 ? c - 4716 : c - 4715;
|
|
87
|
-
|
|
88
|
-
// Calculate hours, minutes, and seconds
|
|
89
|
-
const hour = Math.floor(dayFraction * 24);
|
|
90
|
-
const minute = Math.floor((dayFraction * 24 - hour) * 60);
|
|
91
|
-
const second = Math.floor(((dayFraction * 24 - hour) * 60 - minute) * 60);
|
|
92
|
-
|
|
93
|
-
const ms = Math.floor((((dayFraction * 24 - hour) * 60 - minute) * 60 - second)*1000);
|
|
94
|
-
|
|
95
|
-
return new Date(Date.UTC(year, month - 1, day, hour, minute, second, ms));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Converts an epoch (days and year) to a date string
|
|
100
|
-
* @param {Number} days, number of days since the beginning of the year
|
|
101
|
-
* @param {Number} year, the year
|
|
102
|
-
* @return {String} a date string in the format YYYY-MM-DDTHH:MM:SSZ
|
|
103
|
-
*/
|
|
104
|
-
function epochToDate(days, year) {
|
|
105
|
-
const millisecondsPerDay = 24 * 60 * 60 * 1000; // Number of milliseconds in a day
|
|
106
|
-
|
|
107
|
-
// Create a Date object representing January 1st of the given year, in UTC
|
|
108
|
-
const startDate = new Date(Date.UTC(year, 0, 1));
|
|
109
|
-
|
|
110
|
-
// Calculate the total milliseconds from the start of the year
|
|
111
|
-
const totalMilliseconds = startDate.getTime() + (days - 1) * millisecondsPerDay;
|
|
112
|
-
|
|
113
|
-
// Create a new Date object using the total milliseconds, ensuring it's treated as UTC
|
|
114
|
-
const date = new Date(totalMilliseconds);
|
|
115
|
-
|
|
116
|
-
// Extract the individual components from the Date object, all in UTC
|
|
117
|
-
const yearString = date.getUTCFullYear().toString().padStart(4, "0");
|
|
118
|
-
const monthString = (date.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
119
|
-
const dayString = date.getUTCDate().toString().padStart(2, "0");
|
|
120
|
-
const hoursString = date.getUTCHours().toString().padStart(2, "0");
|
|
121
|
-
const minutesString = date.getUTCMinutes().toString().padStart(2, "0");
|
|
122
|
-
const secondsString = date.getUTCSeconds().toString().padStart(2, "0");
|
|
123
|
-
|
|
124
|
-
// Construct the date string in the desired format
|
|
125
|
-
const dateString = `${yearString}-${monthString}-${dayString}T${hoursString}:${minutesString}:${secondsString}Z`;
|
|
126
|
-
|
|
127
|
-
return dateString;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Calculates the magnitude given a vector object
|
|
132
|
-
*
|
|
133
|
-
* @param {Object} v Expects a vector object: v{x,y,z}
|
|
134
|
-
*
|
|
135
|
-
* @return {Object} Returns magnitude object
|
|
136
|
-
*/
|
|
137
|
-
const getMagnitude = function(v) {
|
|
138
|
-
return Math.sqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z));
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* The code normalizes the values of a vector by dividing them by their magnitude
|
|
143
|
-
*
|
|
144
|
-
* @param {Object} v Expects a vector object: v{x,y,z}
|
|
145
|
-
*
|
|
146
|
-
* @return {Object} Returns normalized vector v{x,y,z}
|
|
147
|
-
*/
|
|
148
|
-
const normalize = (v) => {
|
|
149
|
-
const mag = getMagnitude(v);
|
|
150
|
-
return {
|
|
151
|
-
x: v.x / mag,
|
|
152
|
-
y: v.y / mag,
|
|
153
|
-
z: v.z / mag,
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* [
|
|
159
|
-
[1,2,3],
|
|
160
|
-
[1,2,3],
|
|
161
|
-
[1,2,3],
|
|
162
|
-
]
|
|
163
|
-
Converts to:
|
|
164
|
-
[
|
|
165
|
-
[1,1,1],
|
|
166
|
-
[2,2,2],
|
|
167
|
-
[3,3,3],
|
|
168
|
-
]
|
|
169
|
-
* @param {*} Array2D
|
|
170
|
-
|
|
171
|
-
* @return {array} The transposed array
|
|
172
|
-
*/
|
|
173
|
-
const transpose = (Array2D) => {
|
|
174
|
-
return Array2D[0].map((_, colIndex) => Array2D.map((row) => row[colIndex]));
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Multiply a Vector by a Rotation Matrix and calculate a final 3D vector
|
|
179
|
-
*
|
|
180
|
-
* @param {Object} vector Vector object with x, y, z properties
|
|
181
|
-
* @param {Array} matrix Rotation Matrix
|
|
182
|
-
*
|
|
183
|
-
* @return {Object} Final 3D vector
|
|
184
|
-
*/
|
|
185
|
-
const multiplyVector = (vector, matrix) => {
|
|
186
|
-
const X = matrix[0][0] * vector.x
|
|
187
|
-
+ matrix[0][1] * vector.y
|
|
188
|
-
+ matrix[0][2] * vector.z;
|
|
189
|
-
|
|
190
|
-
const Y = matrix[1][0] * vector.x
|
|
191
|
-
+ matrix[1][1] * vector.y
|
|
192
|
-
+ matrix[1][2] * vector.z;
|
|
193
|
-
|
|
194
|
-
const Z = matrix[2][0] * vector.x
|
|
195
|
-
+ matrix[2][1] * vector.y
|
|
196
|
-
+ matrix[2][2] * vector.z;
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
x: X,
|
|
200
|
-
y: Y,
|
|
201
|
-
z: Z,
|
|
202
|
-
};
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Calculates the distance between two points
|
|
207
|
-
*
|
|
208
|
-
* @param {Object} p1 {x,y,z} coordinates
|
|
209
|
-
* @param {Object} p2 {x,y,z} coordinates
|
|
210
|
-
*
|
|
211
|
-
* @return {Number} Distance between the two points
|
|
212
|
-
*/
|
|
213
|
-
const dist = (p1, p2) => {
|
|
214
|
-
const a = p2.x - p1.x;
|
|
215
|
-
const b = p2.y - p1.y;
|
|
216
|
-
const c = p2.z - p1.z;
|
|
217
|
-
return Math.sqrt(a * a + b * b + c * c);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
/** Checks if the variable is a valid dataMode string.
|
|
221
|
-
*
|
|
222
|
-
* @param {String} dataMode The dataMode to evaluate
|
|
223
|
-
* @return {Boolean} True if input is a valid dataMode, false otherwise
|
|
224
|
-
*/
|
|
225
|
-
const isValidDataMode = (dataMode) => {
|
|
226
|
-
const validDataModes = ["REAL", "EXERCISE", "TEST", "SIMULATED"];
|
|
227
|
-
if (isNonEmptyString(dataMode)) {
|
|
228
|
-
return validDataModes.includes(dataMode);
|
|
229
|
-
}
|
|
230
|
-
return false;
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
/** Check if a variable is of boolean type
|
|
234
|
-
*
|
|
235
|
-
* @param {*} variable The variable to be tested
|
|
236
|
-
* @return {Boolean} True is the variable is boolean, false otherwise
|
|
237
|
-
*/
|
|
238
|
-
const isBoolean = (variable) => {
|
|
239
|
-
return typeof variable === "boolean";
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
/** Check if a variable is a valid AND non-empty string.
|
|
243
|
-
*
|
|
244
|
-
* @param {*} variable The variable to test
|
|
245
|
-
* @return {Boolean} True if it is valid and non-empty string, false otherwise.
|
|
246
|
-
*/
|
|
247
|
-
const isNonEmptyString = (variable) => {
|
|
248
|
-
return typeof variable === "string" && variable.trim() !== "";
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
/** A function that removes object properties that are null or undefined.
|
|
252
|
-
* It is not recursive, it only removes first level properties and not nested objects.
|
|
253
|
-
* It is ES6 compatible.
|
|
254
|
-
* https://stackoverflow.com/questions/286141/remove-blank-attributes-from-an-object-in-javascript
|
|
255
|
-
*
|
|
256
|
-
* @param {Object} obj The object to clean up from null and undefined properties.
|
|
257
|
-
* @return {Object} The mutated object with null and undefined properties removed.
|
|
258
|
-
*/
|
|
259
|
-
const removeNullUndefined = (obj) => {
|
|
260
|
-
Object.keys(obj).forEach((k) => obj[k] === null && delete obj[k]);
|
|
261
|
-
return obj;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const getTimeDifference = (datetimeString)=>{
|
|
265
|
-
// Parse the provided datetime string to a Date object
|
|
266
|
-
const providedDate = new Date(datetimeString).getTime();
|
|
267
|
-
|
|
268
|
-
// Get the current UTC date and time
|
|
269
|
-
const currentUTCDate = Date.now(); // use Date.now() for jest mock
|
|
270
|
-
|
|
271
|
-
// Calculate the time difference in milliseconds
|
|
272
|
-
const timeDifferenceInMilliseconds = (providedDate > currentUTCDate)
|
|
273
|
-
? providedDate - currentUTCDate
|
|
274
|
-
: currentUTCDate - providedDate;
|
|
275
|
-
|
|
276
|
-
// Calculate the time difference in hours, minutes, and seconds
|
|
277
|
-
let timeDifferenceInSeconds = Math.floor(timeDifferenceInMilliseconds / 1000);
|
|
278
|
-
const hours = Math.floor(timeDifferenceInSeconds / 3600);
|
|
279
|
-
timeDifferenceInSeconds %= 3600;
|
|
280
|
-
const minutes = Math.floor(timeDifferenceInSeconds / 60);
|
|
281
|
-
const seconds = timeDifferenceInSeconds % 60;
|
|
282
|
-
|
|
283
|
-
// Return the time difference
|
|
284
|
-
return ((hours > 0) ? hours + "h " : "")
|
|
285
|
-
+ ((minutes >0) ? minutes + "m " : "")
|
|
286
|
-
+ seconds+"s";
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
/** A function that properly checks if a variable is defined.
|
|
290
|
-
*
|
|
291
|
-
* @param {Object} v The variable to check
|
|
292
|
-
* @return {Boolean} true if the number is even, false otherwise.
|
|
293
|
-
*/
|
|
294
|
-
const isDefined = (v) => {
|
|
295
|
-
return v!== undefined && v!== null;
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
/** Compute the angular separation between two vectors.
|
|
299
|
-
*
|
|
300
|
-
* This method computes the angular separation between two vectors using the dot product
|
|
301
|
-
* for well separated vectors and the cross product for almost aligned vectors.
|
|
302
|
-
* This allows to have a good accuracy in all cases, even for vectors very close to each other.
|
|
303
|
-
*
|
|
304
|
-
* The function is a JavaScript adaptation of Java Hipparchus Math library function Vector3D.angle(...),
|
|
305
|
-
* which is frequently used in all of Orekit.
|
|
306
|
-
* @param {Array} v1 First vector
|
|
307
|
-
* @param {Array} v2 Second vector
|
|
308
|
-
* @return {Number} The angle between the two vectors, in radians
|
|
309
|
-
*/
|
|
310
|
-
const getAngle = (v1, v2) => {
|
|
311
|
-
const normProduct = norm(v1) * norm(v2);
|
|
312
|
-
if (normProduct === 0) {
|
|
313
|
-
throw new Error("One of the two vectors has zero norm!");
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const dotPr = dot(v1, v2);
|
|
317
|
-
const threshold = normProduct * 0.9999;
|
|
318
|
-
if ((dotPr < -threshold) || (dotPr > threshold)) {
|
|
319
|
-
// the vectors are almost aligned, compute using the sine
|
|
320
|
-
const v3 = cross(v1, v2);
|
|
321
|
-
if (dotPr >= 0) {
|
|
322
|
-
return Math.asin(norm(v3) / normProduct);
|
|
323
|
-
}
|
|
324
|
-
return Math.PI - Math.asin(norm(v3) / normProduct);
|
|
325
|
-
}
|
|
326
|
-
// the vectors are sufficiently separated to use the cosine
|
|
327
|
-
return Math.acos(dotPr / normProduct);
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Wraps a number to the specified range [min, max).
|
|
332
|
-
* @param {number} value - The number to wrap.
|
|
333
|
-
* @param {number} min - The minimum value of the range.
|
|
334
|
-
* @param {number} max - The maximum value of the range (exclusive).
|
|
335
|
-
* @return {number} The wrapped number within the specified range.
|
|
336
|
-
*/
|
|
337
|
-
const wrapToRange = (value, min, max) => {
|
|
338
|
-
const range = max - min;
|
|
339
|
-
return ((((value - min) % range) + range) % range) + min;
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Converts XYZ position object to array
|
|
344
|
-
* @param {Object} pos - Position object with x, y, and z values
|
|
345
|
-
* @return {Array} position array [x, y, z]
|
|
346
|
-
*/
|
|
347
|
-
const posToArray = (pos) => {
|
|
348
|
-
return [pos.x, pos.y, pos.z];
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
/** Returns the CW signed minimum difference between two angles on a ciscular buffer of 0 to 360.
|
|
352
|
-
*
|
|
353
|
-
* This function is commonly used to correctly compute longitude or azimuth differences.
|
|
354
|
-
*
|
|
355
|
-
* A positive value means that the minimum angle formed from the initial to the final position is CW.
|
|
356
|
-
* A negative value means that the minimum angle formed from the initial to the final position is CCW.
|
|
357
|
-
*
|
|
358
|
-
* @example
|
|
359
|
-
* // returns 20
|
|
360
|
-
* getAngleDiffSigned(350, 10);
|
|
361
|
-
* @example
|
|
362
|
-
* // returns -20
|
|
363
|
-
* globalNS.method(10, 350);
|
|
364
|
-
*
|
|
365
|
-
* @param {Number} initial The initial angle value in 0 to 360
|
|
366
|
-
* @param {Number} final The final angle value in 0 to 360
|
|
367
|
-
* @return {Number} The clock-wise signed minimum angle difference
|
|
368
|
-
*/
|
|
369
|
-
const getAngleDiffSigned = (initial, final) => {
|
|
370
|
-
const diff = final - initial;
|
|
371
|
-
const angle = Math.abs(diff);
|
|
372
|
-
const minAngle = Math.min(angle, 360 - angle);
|
|
373
|
-
let sign;
|
|
374
|
-
|
|
375
|
-
if (angle <= 180) {
|
|
376
|
-
sign = diff >= 0 ? 1 : -1;
|
|
377
|
-
} else {
|
|
378
|
-
sign = (diff - 360 * Math.sign(diff)) >= 0 ? 1 : -1;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return sign * minAngle;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
export {
|
|
385
|
-
wrapOneRevUnsigned,
|
|
386
|
-
wrapHalfRevUnsigned,
|
|
387
|
-
wrapHalfRev,
|
|
388
|
-
wrapQuarterRev,
|
|
389
|
-
julianToGregorian,
|
|
390
|
-
epochToDate,
|
|
391
|
-
getMagnitude,
|
|
392
|
-
normalize,
|
|
393
|
-
transpose,
|
|
394
|
-
multiplyVector,
|
|
395
|
-
dist,
|
|
396
|
-
isValidDataMode,
|
|
397
|
-
isBoolean,
|
|
398
|
-
isNonEmptyString,
|
|
399
|
-
removeNullUndefined,
|
|
400
|
-
getTimeDifference,
|
|
401
|
-
isDefined,
|
|
402
|
-
wrapToRange,
|
|
403
|
-
getAngle,
|
|
404
|
-
getAngleDiffSigned,
|
|
405
|
-
posToArray,
|
|
406
|
-
};
|
|
1
|
+
import {cross, dot, norm} from "mathjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrap angle to [0, 2π] range (0 to 360 degrees)
|
|
5
|
+
* @param {number} angleRad - Angle in radians
|
|
6
|
+
* @return {number} Wrapped angle in radians
|
|
7
|
+
*/
|
|
8
|
+
function wrapOneRevUnsigned(angleRad) {
|
|
9
|
+
let wrapped = angleRad % (2 * Math.PI);
|
|
10
|
+
if (wrapped < 0) {
|
|
11
|
+
wrapped += 2 * Math.PI;
|
|
12
|
+
}
|
|
13
|
+
return wrapped;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Wrap angle to [0, π] range (0 to 180 degrees)
|
|
18
|
+
* @param {number} angleRad - Angle in radians
|
|
19
|
+
* @return {number} Wrapped angle in radians
|
|
20
|
+
*/
|
|
21
|
+
function wrapHalfRevUnsigned(angleRad) {
|
|
22
|
+
let wrapped = angleRad % Math.PI;
|
|
23
|
+
if (wrapped < 0) {
|
|
24
|
+
wrapped += Math.PI;
|
|
25
|
+
}
|
|
26
|
+
return wrapped;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wrap angle to [-π, π] range (-180 to 180 degrees)
|
|
31
|
+
* @param {number} angleRad - Angle in radians
|
|
32
|
+
* @return {number} Wrapped angle in radians
|
|
33
|
+
*/
|
|
34
|
+
function wrapHalfRev(angleRad) {
|
|
35
|
+
let wrapped = angleRad % (2 * Math.PI);
|
|
36
|
+
if (wrapped > Math.PI) {
|
|
37
|
+
wrapped -= 2 * Math.PI;
|
|
38
|
+
} else if (wrapped < -Math.PI) {
|
|
39
|
+
wrapped += 2 * Math.PI;
|
|
40
|
+
}
|
|
41
|
+
return wrapped;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Wrap angle to [-π/2, π/2] range (-90 to 90 degrees)
|
|
46
|
+
* @param {number} angleRad - Angle in radians
|
|
47
|
+
* @return {number} Wrapped angle in radians
|
|
48
|
+
*/
|
|
49
|
+
function wrapQuarterRev(angleRad) {
|
|
50
|
+
let wrapped = angleRad % Math.PI;
|
|
51
|
+
if (wrapped > Math.PI / 2) {
|
|
52
|
+
wrapped -= Math.PI;
|
|
53
|
+
} else if (wrapped < -Math.PI / 2) {
|
|
54
|
+
wrapped += Math.PI;
|
|
55
|
+
}
|
|
56
|
+
return wrapped;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Will convert a julian date to a gregorian calendar date.
|
|
61
|
+
* @param {Double} julianDay e.g 2460153.253704
|
|
62
|
+
* @return {Date} javascript date object e.g 2023-07-27 18:05:20
|
|
63
|
+
*/
|
|
64
|
+
function julianToGregorian(julianDay) {
|
|
65
|
+
const j = julianDay + 0.5;
|
|
66
|
+
const z = Math.floor(j);
|
|
67
|
+
const f = j - z - 0.5; // subtract 0.5 to account for the day starting at noon
|
|
68
|
+
const dayFraction = f + 0.5; // add 0.5 to account for the day starting at midnight
|
|
69
|
+
|
|
70
|
+
let a;
|
|
71
|
+
|
|
72
|
+
if (z < 2299161) {
|
|
73
|
+
a = z;
|
|
74
|
+
} else {
|
|
75
|
+
const alpha = Math.floor((z - 1867216.25) / 36524.25);
|
|
76
|
+
a = z + 1 + alpha - Math.floor(alpha / 4);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const b = a + 1524;
|
|
80
|
+
const c = Math.floor((b - 122.1) / 365.25);
|
|
81
|
+
const d = Math.floor(365.25 * c);
|
|
82
|
+
const e = Math.floor((b - d) / 30.6001);
|
|
83
|
+
|
|
84
|
+
const day = b - d - Math.floor(30.6001 * e);
|
|
85
|
+
const month = e < 14 ? e - 1 : e - 13;
|
|
86
|
+
const year = month > 2 ? c - 4716 : c - 4715;
|
|
87
|
+
|
|
88
|
+
// Calculate hours, minutes, and seconds
|
|
89
|
+
const hour = Math.floor(dayFraction * 24);
|
|
90
|
+
const minute = Math.floor((dayFraction * 24 - hour) * 60);
|
|
91
|
+
const second = Math.floor(((dayFraction * 24 - hour) * 60 - minute) * 60);
|
|
92
|
+
|
|
93
|
+
const ms = Math.floor((((dayFraction * 24 - hour) * 60 - minute) * 60 - second)*1000);
|
|
94
|
+
|
|
95
|
+
return new Date(Date.UTC(year, month - 1, day, hour, minute, second, ms));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Converts an epoch (days and year) to a date string
|
|
100
|
+
* @param {Number} days, number of days since the beginning of the year
|
|
101
|
+
* @param {Number} year, the year
|
|
102
|
+
* @return {String} a date string in the format YYYY-MM-DDTHH:MM:SSZ
|
|
103
|
+
*/
|
|
104
|
+
function epochToDate(days, year) {
|
|
105
|
+
const millisecondsPerDay = 24 * 60 * 60 * 1000; // Number of milliseconds in a day
|
|
106
|
+
|
|
107
|
+
// Create a Date object representing January 1st of the given year, in UTC
|
|
108
|
+
const startDate = new Date(Date.UTC(year, 0, 1));
|
|
109
|
+
|
|
110
|
+
// Calculate the total milliseconds from the start of the year
|
|
111
|
+
const totalMilliseconds = startDate.getTime() + (days - 1) * millisecondsPerDay;
|
|
112
|
+
|
|
113
|
+
// Create a new Date object using the total milliseconds, ensuring it's treated as UTC
|
|
114
|
+
const date = new Date(totalMilliseconds);
|
|
115
|
+
|
|
116
|
+
// Extract the individual components from the Date object, all in UTC
|
|
117
|
+
const yearString = date.getUTCFullYear().toString().padStart(4, "0");
|
|
118
|
+
const monthString = (date.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
119
|
+
const dayString = date.getUTCDate().toString().padStart(2, "0");
|
|
120
|
+
const hoursString = date.getUTCHours().toString().padStart(2, "0");
|
|
121
|
+
const minutesString = date.getUTCMinutes().toString().padStart(2, "0");
|
|
122
|
+
const secondsString = date.getUTCSeconds().toString().padStart(2, "0");
|
|
123
|
+
|
|
124
|
+
// Construct the date string in the desired format
|
|
125
|
+
const dateString = `${yearString}-${monthString}-${dayString}T${hoursString}:${minutesString}:${secondsString}Z`;
|
|
126
|
+
|
|
127
|
+
return dateString;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Calculates the magnitude given a vector object
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} v Expects a vector object: v{x,y,z}
|
|
134
|
+
*
|
|
135
|
+
* @return {Object} Returns magnitude object
|
|
136
|
+
*/
|
|
137
|
+
const getMagnitude = function(v) {
|
|
138
|
+
return Math.sqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The code normalizes the values of a vector by dividing them by their magnitude
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} v Expects a vector object: v{x,y,z}
|
|
145
|
+
*
|
|
146
|
+
* @return {Object} Returns normalized vector v{x,y,z}
|
|
147
|
+
*/
|
|
148
|
+
const normalize = (v) => {
|
|
149
|
+
const mag = getMagnitude(v);
|
|
150
|
+
return {
|
|
151
|
+
x: v.x / mag,
|
|
152
|
+
y: v.y / mag,
|
|
153
|
+
z: v.z / mag,
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* [
|
|
159
|
+
[1,2,3],
|
|
160
|
+
[1,2,3],
|
|
161
|
+
[1,2,3],
|
|
162
|
+
]
|
|
163
|
+
Converts to:
|
|
164
|
+
[
|
|
165
|
+
[1,1,1],
|
|
166
|
+
[2,2,2],
|
|
167
|
+
[3,3,3],
|
|
168
|
+
]
|
|
169
|
+
* @param {*} Array2D
|
|
170
|
+
|
|
171
|
+
* @return {array} The transposed array
|
|
172
|
+
*/
|
|
173
|
+
const transpose = (Array2D) => {
|
|
174
|
+
return Array2D[0].map((_, colIndex) => Array2D.map((row) => row[colIndex]));
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Multiply a Vector by a Rotation Matrix and calculate a final 3D vector
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} vector Vector object with x, y, z properties
|
|
181
|
+
* @param {Array} matrix Rotation Matrix
|
|
182
|
+
*
|
|
183
|
+
* @return {Object} Final 3D vector
|
|
184
|
+
*/
|
|
185
|
+
const multiplyVector = (vector, matrix) => {
|
|
186
|
+
const X = matrix[0][0] * vector.x
|
|
187
|
+
+ matrix[0][1] * vector.y
|
|
188
|
+
+ matrix[0][2] * vector.z;
|
|
189
|
+
|
|
190
|
+
const Y = matrix[1][0] * vector.x
|
|
191
|
+
+ matrix[1][1] * vector.y
|
|
192
|
+
+ matrix[1][2] * vector.z;
|
|
193
|
+
|
|
194
|
+
const Z = matrix[2][0] * vector.x
|
|
195
|
+
+ matrix[2][1] * vector.y
|
|
196
|
+
+ matrix[2][2] * vector.z;
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
x: X,
|
|
200
|
+
y: Y,
|
|
201
|
+
z: Z,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Calculates the distance between two points
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} p1 {x,y,z} coordinates
|
|
209
|
+
* @param {Object} p2 {x,y,z} coordinates
|
|
210
|
+
*
|
|
211
|
+
* @return {Number} Distance between the two points
|
|
212
|
+
*/
|
|
213
|
+
const dist = (p1, p2) => {
|
|
214
|
+
const a = p2.x - p1.x;
|
|
215
|
+
const b = p2.y - p1.y;
|
|
216
|
+
const c = p2.z - p1.z;
|
|
217
|
+
return Math.sqrt(a * a + b * b + c * c);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** Checks if the variable is a valid dataMode string.
|
|
221
|
+
*
|
|
222
|
+
* @param {String} dataMode The dataMode to evaluate
|
|
223
|
+
* @return {Boolean} True if input is a valid dataMode, false otherwise
|
|
224
|
+
*/
|
|
225
|
+
const isValidDataMode = (dataMode) => {
|
|
226
|
+
const validDataModes = ["REAL", "EXERCISE", "TEST", "SIMULATED"];
|
|
227
|
+
if (isNonEmptyString(dataMode)) {
|
|
228
|
+
return validDataModes.includes(dataMode);
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/** Check if a variable is of boolean type
|
|
234
|
+
*
|
|
235
|
+
* @param {*} variable The variable to be tested
|
|
236
|
+
* @return {Boolean} True is the variable is boolean, false otherwise
|
|
237
|
+
*/
|
|
238
|
+
const isBoolean = (variable) => {
|
|
239
|
+
return typeof variable === "boolean";
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/** Check if a variable is a valid AND non-empty string.
|
|
243
|
+
*
|
|
244
|
+
* @param {*} variable The variable to test
|
|
245
|
+
* @return {Boolean} True if it is valid and non-empty string, false otherwise.
|
|
246
|
+
*/
|
|
247
|
+
const isNonEmptyString = (variable) => {
|
|
248
|
+
return typeof variable === "string" && variable.trim() !== "";
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/** A function that removes object properties that are null or undefined.
|
|
252
|
+
* It is not recursive, it only removes first level properties and not nested objects.
|
|
253
|
+
* It is ES6 compatible.
|
|
254
|
+
* https://stackoverflow.com/questions/286141/remove-blank-attributes-from-an-object-in-javascript
|
|
255
|
+
*
|
|
256
|
+
* @param {Object} obj The object to clean up from null and undefined properties.
|
|
257
|
+
* @return {Object} The mutated object with null and undefined properties removed.
|
|
258
|
+
*/
|
|
259
|
+
const removeNullUndefined = (obj) => {
|
|
260
|
+
Object.keys(obj).forEach((k) => obj[k] === null && delete obj[k]);
|
|
261
|
+
return obj;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const getTimeDifference = (datetimeString)=>{
|
|
265
|
+
// Parse the provided datetime string to a Date object
|
|
266
|
+
const providedDate = new Date(datetimeString).getTime();
|
|
267
|
+
|
|
268
|
+
// Get the current UTC date and time
|
|
269
|
+
const currentUTCDate = Date.now(); // use Date.now() for jest mock
|
|
270
|
+
|
|
271
|
+
// Calculate the time difference in milliseconds
|
|
272
|
+
const timeDifferenceInMilliseconds = (providedDate > currentUTCDate)
|
|
273
|
+
? providedDate - currentUTCDate
|
|
274
|
+
: currentUTCDate - providedDate;
|
|
275
|
+
|
|
276
|
+
// Calculate the time difference in hours, minutes, and seconds
|
|
277
|
+
let timeDifferenceInSeconds = Math.floor(timeDifferenceInMilliseconds / 1000);
|
|
278
|
+
const hours = Math.floor(timeDifferenceInSeconds / 3600);
|
|
279
|
+
timeDifferenceInSeconds %= 3600;
|
|
280
|
+
const minutes = Math.floor(timeDifferenceInSeconds / 60);
|
|
281
|
+
const seconds = timeDifferenceInSeconds % 60;
|
|
282
|
+
|
|
283
|
+
// Return the time difference
|
|
284
|
+
return ((hours > 0) ? hours + "h " : "")
|
|
285
|
+
+ ((minutes >0) ? minutes + "m " : "")
|
|
286
|
+
+ seconds+"s";
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** A function that properly checks if a variable is defined.
|
|
290
|
+
*
|
|
291
|
+
* @param {Object} v The variable to check
|
|
292
|
+
* @return {Boolean} true if the number is even, false otherwise.
|
|
293
|
+
*/
|
|
294
|
+
const isDefined = (v) => {
|
|
295
|
+
return v!== undefined && v!== null;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/** Compute the angular separation between two vectors.
|
|
299
|
+
*
|
|
300
|
+
* This method computes the angular separation between two vectors using the dot product
|
|
301
|
+
* for well separated vectors and the cross product for almost aligned vectors.
|
|
302
|
+
* This allows to have a good accuracy in all cases, even for vectors very close to each other.
|
|
303
|
+
*
|
|
304
|
+
* The function is a JavaScript adaptation of Java Hipparchus Math library function Vector3D.angle(...),
|
|
305
|
+
* which is frequently used in all of Orekit.
|
|
306
|
+
* @param {Array} v1 First vector
|
|
307
|
+
* @param {Array} v2 Second vector
|
|
308
|
+
* @return {Number} The angle between the two vectors, in radians
|
|
309
|
+
*/
|
|
310
|
+
const getAngle = (v1, v2) => {
|
|
311
|
+
const normProduct = norm(v1) * norm(v2);
|
|
312
|
+
if (normProduct === 0) {
|
|
313
|
+
throw new Error("One of the two vectors has zero norm!");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const dotPr = dot(v1, v2);
|
|
317
|
+
const threshold = normProduct * 0.9999;
|
|
318
|
+
if ((dotPr < -threshold) || (dotPr > threshold)) {
|
|
319
|
+
// the vectors are almost aligned, compute using the sine
|
|
320
|
+
const v3 = cross(v1, v2);
|
|
321
|
+
if (dotPr >= 0) {
|
|
322
|
+
return Math.asin(norm(v3) / normProduct);
|
|
323
|
+
}
|
|
324
|
+
return Math.PI - Math.asin(norm(v3) / normProduct);
|
|
325
|
+
}
|
|
326
|
+
// the vectors are sufficiently separated to use the cosine
|
|
327
|
+
return Math.acos(dotPr / normProduct);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Wraps a number to the specified range [min, max).
|
|
332
|
+
* @param {number} value - The number to wrap.
|
|
333
|
+
* @param {number} min - The minimum value of the range.
|
|
334
|
+
* @param {number} max - The maximum value of the range (exclusive).
|
|
335
|
+
* @return {number} The wrapped number within the specified range.
|
|
336
|
+
*/
|
|
337
|
+
const wrapToRange = (value, min, max) => {
|
|
338
|
+
const range = max - min;
|
|
339
|
+
return ((((value - min) % range) + range) % range) + min;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Converts XYZ position object to array
|
|
344
|
+
* @param {Object} pos - Position object with x, y, and z values
|
|
345
|
+
* @return {Array} position array [x, y, z]
|
|
346
|
+
*/
|
|
347
|
+
const posToArray = (pos) => {
|
|
348
|
+
return [pos.x, pos.y, pos.z];
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/** Returns the CW signed minimum difference between two angles on a ciscular buffer of 0 to 360.
|
|
352
|
+
*
|
|
353
|
+
* This function is commonly used to correctly compute longitude or azimuth differences.
|
|
354
|
+
*
|
|
355
|
+
* A positive value means that the minimum angle formed from the initial to the final position is CW.
|
|
356
|
+
* A negative value means that the minimum angle formed from the initial to the final position is CCW.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* // returns 20
|
|
360
|
+
* getAngleDiffSigned(350, 10);
|
|
361
|
+
* @example
|
|
362
|
+
* // returns -20
|
|
363
|
+
* globalNS.method(10, 350);
|
|
364
|
+
*
|
|
365
|
+
* @param {Number} initial The initial angle value in 0 to 360
|
|
366
|
+
* @param {Number} final The final angle value in 0 to 360
|
|
367
|
+
* @return {Number} The clock-wise signed minimum angle difference
|
|
368
|
+
*/
|
|
369
|
+
const getAngleDiffSigned = (initial, final) => {
|
|
370
|
+
const diff = final - initial;
|
|
371
|
+
const angle = Math.abs(diff);
|
|
372
|
+
const minAngle = Math.min(angle, 360 - angle);
|
|
373
|
+
let sign;
|
|
374
|
+
|
|
375
|
+
if (angle <= 180) {
|
|
376
|
+
sign = diff >= 0 ? 1 : -1;
|
|
377
|
+
} else {
|
|
378
|
+
sign = (diff - 360 * Math.sign(diff)) >= 0 ? 1 : -1;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return sign * minAngle;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export {
|
|
385
|
+
wrapOneRevUnsigned,
|
|
386
|
+
wrapHalfRevUnsigned,
|
|
387
|
+
wrapHalfRev,
|
|
388
|
+
wrapQuarterRev,
|
|
389
|
+
julianToGregorian,
|
|
390
|
+
epochToDate,
|
|
391
|
+
getMagnitude,
|
|
392
|
+
normalize,
|
|
393
|
+
transpose,
|
|
394
|
+
multiplyVector,
|
|
395
|
+
dist,
|
|
396
|
+
isValidDataMode,
|
|
397
|
+
isBoolean,
|
|
398
|
+
isNonEmptyString,
|
|
399
|
+
removeNullUndefined,
|
|
400
|
+
getTimeDifference,
|
|
401
|
+
isDefined,
|
|
402
|
+
wrapToRange,
|
|
403
|
+
getAngle,
|
|
404
|
+
getAngleDiffSigned,
|
|
405
|
+
posToArray,
|
|
406
|
+
};
|