@saber-usa/node-common 1.7.7 → 1.7.9-alpha.1
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 +13 -42
- package/package.json +51 -52
- package/src/FrameConverter.js +1120 -1121
- package/src/LLA.js +179 -179
- package/src/LaunchNominalClass.js +772 -753
- package/src/NodeVector3D.js +71 -71
- package/src/OrbitUtils.js +490 -309
- package/src/PropagateUtils.js +100 -100
- package/src/ShadowGEOCalculator.js +203 -203
- package/src/TimeConverter.js +309 -309
- package/src/astro.js +3315 -3301
- package/src/ballisticPropagator.js +1037 -1037
- package/src/constants.js +35 -37
- package/src/fixDate.js +62 -62
- package/src/index.js +47 -47
- package/src/launchNominal.js +208 -208
- package/src/s3.js +59 -59
- package/src/transform.js +35 -35
- package/src/udl.js +231 -116
- package/src/utils.js +406 -406
|
@@ -1,753 +1,772 @@
|
|
|
1
|
-
import {degreesToRadians, radiansToDegrees} from "satellite.js";
|
|
2
|
-
import {Vector3D, J2000, EpochUTC} from "pious-squid";
|
|
3
|
-
import {NodeVector3D} from "./NodeVector3D.js";
|
|
4
|
-
import {WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
5
|
-
GRAV_CONST,
|
|
6
|
-
EARTH_MASS,
|
|
7
|
-
MU,
|
|
8
|
-
EARTH_RADIUS_KM
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
* @param {number}
|
|
43
|
-
* @param {number}
|
|
44
|
-
* @param {number}
|
|
45
|
-
* @
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
new
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
//
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
//
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
);
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
const
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
|
|
1
|
+
import {degreesToRadians, radiansToDegrees} from "satellite.js";
|
|
2
|
+
import {Vector3D, J2000, EpochUTC} from "pious-squid";
|
|
3
|
+
import {NodeVector3D} from "./NodeVector3D.js";
|
|
4
|
+
import {WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
5
|
+
GRAV_CONST,
|
|
6
|
+
EARTH_MASS,
|
|
7
|
+
MU,
|
|
8
|
+
EARTH_RADIUS_KM} from "./constants.js";
|
|
9
|
+
import {FrameConverter} from "./FrameConverter.js";
|
|
10
|
+
import {OrbitUtils} from "./OrbitUtils.js";
|
|
11
|
+
import {wrapOneRevUnsigned} from "./utils.js";
|
|
12
|
+
import {BallisticPropagator} from "./ballisticPropagator.js";
|
|
13
|
+
import {norm, cross, dot} from "mathjs";
|
|
14
|
+
|
|
15
|
+
// Reference frames enum
|
|
16
|
+
const ReferenceFrame = {
|
|
17
|
+
ITRF: "ITRF",
|
|
18
|
+
J2000: "J2000",
|
|
19
|
+
TEME: "TEME",
|
|
20
|
+
GCRF: "GCRF",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class LaunchNominalClass {
|
|
24
|
+
/** Get position at a given time in specified reference frame
|
|
25
|
+
*
|
|
26
|
+
* @param {Date} utc - UTC time
|
|
27
|
+
* @param {Vector3D} posITRF Position in ITRF frame
|
|
28
|
+
* @return {Vector3D} Position in J2000 frame
|
|
29
|
+
*/
|
|
30
|
+
static getPositionJ2000(utc, posITRF) {
|
|
31
|
+
const transformMatrix = FrameConverter.itrfToJ2000(utc);
|
|
32
|
+
|
|
33
|
+
return FrameConverter.transformVector(posITRF, transformMatrix);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Generate a circular launch nominal.
|
|
37
|
+
*
|
|
38
|
+
* @param {number} lat - The latitude of the ground site (degrees)
|
|
39
|
+
* @param {number} lon - The longitude of the ground site (degrees)
|
|
40
|
+
* @param {number} alt - The altitude of the ground site (km)
|
|
41
|
+
* @param {Date} T0 - The Liftoff Time
|
|
42
|
+
* @param {number} bearingDeg - The bearing the launch vehicle headed in (degrees)
|
|
43
|
+
* @param {number} leoAltKm - The LEO Parking Altitude (km)
|
|
44
|
+
* @param {number} flyoutTimeSecs - How long the launch vehicle takes to reach the leoAltKm (seconds)
|
|
45
|
+
* @return {Object} Satellite object
|
|
46
|
+
*/
|
|
47
|
+
static generateLaunchNominalCircular(lat, lon, alt, T0, bearingDeg, leoAltKm, flyoutTimeSecs) {
|
|
48
|
+
const llaVals = {
|
|
49
|
+
Latitude: {Degrees: lat, Radians: degreesToRadians(lat)},
|
|
50
|
+
Longitude: {Degrees: lon, Radians: degreesToRadians(lon)},
|
|
51
|
+
AltitudeKm: alt,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let bearingRad = degreesToRadians(bearingDeg);
|
|
55
|
+
bearingRad = wrapOneRevUnsigned(bearingRad);
|
|
56
|
+
|
|
57
|
+
// const latRad = degreesToRadians(this.groundSiteLat);
|
|
58
|
+
const latRad = llaVals.Latitude.Radians;
|
|
59
|
+
const targetInclination = OrbitUtils.getInclination(bearingRad, latRad);
|
|
60
|
+
|
|
61
|
+
// Convergence parameters
|
|
62
|
+
const inclinationThreshold = 0.001;
|
|
63
|
+
let inclinationDelta = Number.POSITIVE_INFINITY;
|
|
64
|
+
let oldInclinationDelta;
|
|
65
|
+
let stepSize = 0.1; // Initialize step size
|
|
66
|
+
const maxIter = 100;
|
|
67
|
+
|
|
68
|
+
// Time at orbit insertion
|
|
69
|
+
const orbitInsertionTime = new Date(T0.getTime() + flyoutTimeSecs * 1000);
|
|
70
|
+
|
|
71
|
+
const initialStateVector = this.generateStateOverGroundsite(
|
|
72
|
+
orbitInsertionTime,
|
|
73
|
+
radiansToDegrees(bearingRad),
|
|
74
|
+
leoAltKm,
|
|
75
|
+
ReferenceFrame.J2000,
|
|
76
|
+
llaVals,
|
|
77
|
+
);
|
|
78
|
+
// Trying something here
|
|
79
|
+
|
|
80
|
+
// const transformMatrix = FrameConverter.j2000ToTEME(orbitInsertionTime);
|
|
81
|
+
// const initialTemeState = {
|
|
82
|
+
// position: this.transformVector(
|
|
83
|
+
// new Vector3D(initialStateVector.position[0],
|
|
84
|
+
// initialStateVector.position[1], initialStateVector.position[2]),
|
|
85
|
+
// transformMatrix
|
|
86
|
+
// ),
|
|
87
|
+
// velocity: this.transformVector(
|
|
88
|
+
// new Vector3D(initialStateVector.velocity[0],
|
|
89
|
+
// initialStateVector.velocity[1], initialStateVector.velocity[2]),
|
|
90
|
+
// transformMatrix
|
|
91
|
+
// ),
|
|
92
|
+
// };
|
|
93
|
+
|
|
94
|
+
// End of something
|
|
95
|
+
const initialJ2000State = new J2000(
|
|
96
|
+
EpochUTC.fromDateString(orbitInsertionTime.toISOString()),
|
|
97
|
+
new Vector3D(initialStateVector.position[0],
|
|
98
|
+
initialStateVector.position[1], initialStateVector.position[2]),
|
|
99
|
+
new Vector3D(initialStateVector.velocity[0],
|
|
100
|
+
initialStateVector.velocity[1], initialStateVector.velocity[2]),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const initialTemeState = initialJ2000State.toTEME();
|
|
104
|
+
|
|
105
|
+
let sat = {
|
|
106
|
+
stateVector: initialStateVector,
|
|
107
|
+
elements: OrbitUtils.stateVectorToElements(
|
|
108
|
+
[initialTemeState.position.x, initialTemeState.position.y,
|
|
109
|
+
initialTemeState.position.z],
|
|
110
|
+
[initialTemeState.velocity.x, initialTemeState.velocity.y,
|
|
111
|
+
initialTemeState.velocity.z]),
|
|
112
|
+
epoch: orbitInsertionTime,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Create initial propagator
|
|
116
|
+
let propagator = null;
|
|
117
|
+
|
|
118
|
+
for (let i = 1; i <= maxIter; i++) {
|
|
119
|
+
if (inclinationDelta > inclinationThreshold) {
|
|
120
|
+
// Generate state vector at orbit insertion
|
|
121
|
+
const sv = this.generateStateOverGroundsite(
|
|
122
|
+
orbitInsertionTime,
|
|
123
|
+
radiansToDegrees(bearingRad),
|
|
124
|
+
leoAltKm,
|
|
125
|
+
ReferenceFrame.J2000,
|
|
126
|
+
llaVals,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Create J2000 state for pious-squid
|
|
130
|
+
const state = {
|
|
131
|
+
position: new NodeVector3D(sv.position[0], sv.position[1], sv.position[2]), // km
|
|
132
|
+
velocity: new NodeVector3D(sv.velocity[0], sv.velocity[1], sv.velocity[2]), // km/s
|
|
133
|
+
epochUtc: orbitInsertionTime,
|
|
134
|
+
referenceFrame: "J2000",
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Create propagator
|
|
138
|
+
propagator = new BallisticPropagator(state);
|
|
139
|
+
|
|
140
|
+
// Propagate to T0 to get the state at liftoff
|
|
141
|
+
const propagated = propagator.propagate(T0);
|
|
142
|
+
|
|
143
|
+
const propagatedJ2000State = new J2000(
|
|
144
|
+
EpochUTC.fromDateString(T0.toISOString()),
|
|
145
|
+
new Vector3D(propagated.position.x, propagated.position.y,
|
|
146
|
+
propagated.position.z),
|
|
147
|
+
new Vector3D(propagated.velocity.x, propagated.velocity.y,
|
|
148
|
+
propagated.velocity.z),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const temeState = propagatedJ2000State.toTEME();
|
|
152
|
+
|
|
153
|
+
// Convert to orbital elements
|
|
154
|
+
const elements = OrbitUtils.stateVectorToElements(
|
|
155
|
+
[temeState.position.x, temeState.position.y,
|
|
156
|
+
temeState.position.z],
|
|
157
|
+
[temeState.velocity.x, temeState.velocity.y,
|
|
158
|
+
temeState.velocity.z]);
|
|
159
|
+
|
|
160
|
+
sat = {
|
|
161
|
+
stateVector: propagated,
|
|
162
|
+
elements: elements,
|
|
163
|
+
epoch: T0,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Calculate current inclination
|
|
167
|
+
oldInclinationDelta = inclinationDelta;
|
|
168
|
+
inclinationDelta = Math.abs(radiansToDegrees(sat.elements.inclination)
|
|
169
|
+
- radiansToDegrees(targetInclination));
|
|
170
|
+
|
|
171
|
+
if (oldInclinationDelta < inclinationDelta) {
|
|
172
|
+
// If getting farther away, reverse direction and make smaller steps
|
|
173
|
+
stepSize *= -0.5;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Update bearing
|
|
177
|
+
bearingRad = wrapOneRevUnsigned(bearingRad + degreesToRadians(stepSize));
|
|
178
|
+
} else {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (i === maxIter) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
"Launch Nominal inclination did not converge within max iterations");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const out = new LaunchNominalOutput("CIRCULAR")
|
|
189
|
+
.addState(sat.stateVector, sat.elements, sat.epoch);
|
|
190
|
+
return out.toJSON();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
/** Generate an elliptical launch nominal.
|
|
195
|
+
*
|
|
196
|
+
* @param {number} lat - The latitude of the ground site (degrees)
|
|
197
|
+
* @param {number} lon - The longitude of the ground site (degrees)
|
|
198
|
+
* @param {number} alt - The altitude of the ground site (km)
|
|
199
|
+
* @param {Date} T0 - The Liftoff Time
|
|
200
|
+
* @param {number} bearingDeg - The bearing the launch vehicle headed in (degrees)
|
|
201
|
+
* @param {number} flyoutTimeSecs - How long the launch vehicle takes to reach orbit (seconds)
|
|
202
|
+
* @param {number} targetEcc - Target Eccentricity
|
|
203
|
+
* @param {number} targetArgOfPeriapsisDeg - Target Argument of Periapsis (degrees)
|
|
204
|
+
* @param {number} targetPerigeeKm - Target Perigee (km)
|
|
205
|
+
* @return {Object} Satellite object
|
|
206
|
+
*/
|
|
207
|
+
static generateLaunchNominalElliptical(lat, lon, alt, T0, bearingDeg, flyoutTimeSecs,
|
|
208
|
+
targetEcc, targetArgOfPeriapsisDeg, targetPerigeeKm) {
|
|
209
|
+
// First get circular nominal
|
|
210
|
+
const initialNominal = this.generateLaunchNominalCircular(lat, lon, alt, T0,
|
|
211
|
+
bearingDeg, targetPerigeeKm, flyoutTimeSecs);
|
|
212
|
+
|
|
213
|
+
// Time at orbit insertion
|
|
214
|
+
const orbitInsertionTime = new Date(T0.getTime() + flyoutTimeSecs * 1000);
|
|
215
|
+
|
|
216
|
+
// Create J2000 state from initialNominal state vector
|
|
217
|
+
const initialNominalPos = initialNominal.states[0].sv.r;
|
|
218
|
+
const initialNominalVel = initialNominal.states[0].sv.v;
|
|
219
|
+
|
|
220
|
+
const state = {
|
|
221
|
+
position: new NodeVector3D(
|
|
222
|
+
initialNominalPos[0], initialNominalPos[1], initialNominalPos[2]),
|
|
223
|
+
velocity: new NodeVector3D(
|
|
224
|
+
initialNominalVel[0], initialNominalVel[1], initialNominalVel[2]),
|
|
225
|
+
epochUtc: T0,
|
|
226
|
+
referenceFrame: "J2000",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Propagate state to orbit insertion time
|
|
230
|
+
const propagator = new BallisticPropagator(state);
|
|
231
|
+
const propagated = propagator.propagate(orbitInsertionTime);
|
|
232
|
+
const svInitial = {
|
|
233
|
+
position: [propagated.position.x, propagated.position.y, propagated.position.z],
|
|
234
|
+
velocity: [propagated.velocity.x, propagated.velocity.y, propagated.velocity.z],
|
|
235
|
+
epoch: orbitInsertionTime,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Transform to elliptical orbit
|
|
239
|
+
const ellipticalElements = OrbitUtils.transformElliptical(
|
|
240
|
+
svInitial,
|
|
241
|
+
initialNominal.states[0].elements.inc,
|
|
242
|
+
initialNominal.states[0].elements.raan,
|
|
243
|
+
targetEcc,
|
|
244
|
+
degreesToRadians(targetArgOfPeriapsisDeg),
|
|
245
|
+
targetPerigeeKm + WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Convert elliptical elements to cartesian state
|
|
249
|
+
const ellipticalStateVector = OrbitUtils.elementsToStateVector(ellipticalElements);
|
|
250
|
+
|
|
251
|
+
const ellipticalPos = ellipticalStateVector.position;
|
|
252
|
+
const ellipticalVel = ellipticalStateVector.velocity;
|
|
253
|
+
|
|
254
|
+
const ellipticalState = {
|
|
255
|
+
position: new NodeVector3D(ellipticalPos[0], ellipticalPos[1], ellipticalPos[2]),
|
|
256
|
+
velocity: new NodeVector3D(ellipticalVel[0], ellipticalVel[1], ellipticalVel[2]),
|
|
257
|
+
epochUtc: orbitInsertionTime,
|
|
258
|
+
referenceFrame: "J2000",
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Propagate state to T0 (back propagate)
|
|
262
|
+
const propagator2 = new BallisticPropagator(ellipticalState);
|
|
263
|
+
const ellipticalNominal = propagator2.propagate(T0);
|
|
264
|
+
|
|
265
|
+
const ellipNomPos = ellipticalNominal.position;
|
|
266
|
+
const ellipNomVel = ellipticalNominal.velocity;
|
|
267
|
+
|
|
268
|
+
// Convert to orbital elements
|
|
269
|
+
const elements = OrbitUtils.stateVectorToElements(
|
|
270
|
+
[ellipNomPos.x, ellipNomPos.y, ellipNomPos.z],
|
|
271
|
+
[ellipNomVel.x, ellipNomVel.y, ellipNomVel.z]);
|
|
272
|
+
|
|
273
|
+
// Return satellite object with elements and propagator
|
|
274
|
+
const out = new LaunchNominalOutput("ELLIPTICAL")
|
|
275
|
+
.addState(ellipticalNominal, elements, T0);
|
|
276
|
+
return out.toJSON();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static generateLaunchNominalGtoCircular(
|
|
280
|
+
lat, lon, alt, T0, bearingDeg, leoAltKm, flyoutTimeSecs, burnAtNodes = 1) {
|
|
281
|
+
const circLaunchNom = this.generateLaunchNominalCircular(
|
|
282
|
+
lat, lon, alt, T0, bearingDeg, leoAltKm, flyoutTimeSecs);
|
|
283
|
+
const transfer = this.hohmannTransferWithIncZeroing(
|
|
284
|
+
{
|
|
285
|
+
position: new NodeVector3D(
|
|
286
|
+
circLaunchNom.states[0].sv.r[0],
|
|
287
|
+
circLaunchNom.states[0].sv.r[1],
|
|
288
|
+
circLaunchNom.states[0].sv.r[2],
|
|
289
|
+
),
|
|
290
|
+
velocity: new NodeVector3D(
|
|
291
|
+
circLaunchNom.states[0].sv.v[0],
|
|
292
|
+
circLaunchNom.states[0].sv.v[1],
|
|
293
|
+
circLaunchNom.states[0].sv.v[2],
|
|
294
|
+
),
|
|
295
|
+
epochUtc: new Date(circLaunchNom.states[0].epoch),
|
|
296
|
+
},
|
|
297
|
+
42157.137,
|
|
298
|
+
burnAtNodes);
|
|
299
|
+
|
|
300
|
+
const out = new LaunchNominalOutput("CIRCULAR_GTO");
|
|
301
|
+
out
|
|
302
|
+
.addState(transfer.state0, null, transfer.state0.epochUtc)
|
|
303
|
+
.addBurn(transfer.burn1.time, transfer.burn1.dv)
|
|
304
|
+
.addState(transfer.state1, null, transfer.state1.epochUtc)
|
|
305
|
+
.addBurn(transfer.burn2.time, transfer.burn2.dv)
|
|
306
|
+
.addState(transfer.state2, null, transfer.state2.epochUtc);
|
|
307
|
+
return out.toJSON();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
static generateLaunchNominalGtoEliptical(
|
|
311
|
+
lat, lon, alt, T0, bearingDeg, flyoutTimeSecs, targetEcc,
|
|
312
|
+
targetAop, targetPerigee, burnAtNodes = 1) {
|
|
313
|
+
const ellipLaunchNom = this.generateLaunchNominalElliptical(
|
|
314
|
+
lat, lon, alt, T0, bearingDeg, flyoutTimeSecs,
|
|
315
|
+
targetEcc, targetAop, targetPerigee);
|
|
316
|
+
const transfer = this.hohmannTransferWithIncZeroing(
|
|
317
|
+
{
|
|
318
|
+
position: new NodeVector3D(
|
|
319
|
+
ellipLaunchNom.states[0].sv.r[0],
|
|
320
|
+
ellipLaunchNom.states[0].sv.r[1],
|
|
321
|
+
ellipLaunchNom.states[0].sv.r[2],
|
|
322
|
+
),
|
|
323
|
+
velocity: new NodeVector3D(
|
|
324
|
+
ellipLaunchNom.states[0].sv.v[0],
|
|
325
|
+
ellipLaunchNom.states[0].sv.v[1],
|
|
326
|
+
ellipLaunchNom.states[0].sv.v[2],
|
|
327
|
+
),
|
|
328
|
+
epochUtc: new Date(ellipLaunchNom.states[0].epoch),
|
|
329
|
+
},
|
|
330
|
+
42157.137,
|
|
331
|
+
burnAtNodes);
|
|
332
|
+
const out = new LaunchNominalOutput("ELLIPTICAL_GTO");
|
|
333
|
+
out
|
|
334
|
+
.addState(transfer.state0, null, transfer.state0.epochUtc)
|
|
335
|
+
.addBurn(transfer.burn1.time, transfer.burn1.dv)
|
|
336
|
+
.addState(transfer.state1, null, transfer.state1.epochUtc)
|
|
337
|
+
.addBurn(transfer.burn2.time, transfer.burn2.dv)
|
|
338
|
+
.addState(transfer.state2, null, transfer.state2.epochUtc);
|
|
339
|
+
return out.toJSON();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
*
|
|
344
|
+
* Create the initial state vector of a launch from this GroundSite
|
|
345
|
+
* Matches C# implementation exactly
|
|
346
|
+
* @param {Date} orbitInsertionTime - Orbit Insertion Time: Launch Time + Flyout Time.
|
|
347
|
+
* @param {number} azimuthDeg - Azimuth in degrees
|
|
348
|
+
* @param {number} orbitAltitudeKm - Orbit altitude in km
|
|
349
|
+
* @param {string} frame - Reference frame (default: J2000)
|
|
350
|
+
* @param {Object} llaVals - LLA values of the ground site
|
|
351
|
+
* @return {Object} State vector {position, velocity}
|
|
352
|
+
*/
|
|
353
|
+
static generateStateOverGroundsite(
|
|
354
|
+
orbitInsertionTime, azimuthDeg, orbitAltitudeKm, frame = ReferenceFrame.J2000, llaVals) {
|
|
355
|
+
const minAltitudeKm = 1.0; // minimum launch altitude. Lowest recoded is about 440m.
|
|
356
|
+
if (orbitAltitudeKm < minAltitudeKm) {
|
|
357
|
+
throw new Error(`Altitude cannot be less than ${minAltitudeKm} km.`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const azimuthRad = degreesToRadians(azimuthDeg);
|
|
361
|
+
const earthRadius = WGS84_EARTH_EQUATORIAL_RADIUS_KM;
|
|
362
|
+
const mu = GRAV_CONST * EARTH_MASS / 1e9; // km^3/s^2
|
|
363
|
+
|
|
364
|
+
// Get transformation from ITRF to J2000
|
|
365
|
+
const itrf2j2000 = FrameConverter.itrfToJ2000(orbitInsertionTime);
|
|
366
|
+
|
|
367
|
+
// Transform North and Nadir to J2000 - derive unit direction vectors from SEZ -> ITRF/ECEF -> J2000/ECI
|
|
368
|
+
|
|
369
|
+
// Calculate ITRF position using the site
|
|
370
|
+
const positionITRF = FrameConverter.llaToECEF(llaVals);
|
|
371
|
+
|
|
372
|
+
// Get transformation matrices
|
|
373
|
+
const sez2ecef = FrameConverter.sezToECEF(llaVals);
|
|
374
|
+
|
|
375
|
+
// Cardinal directions in ITRF frame
|
|
376
|
+
// South vector (1, 0, 0) in SEZ
|
|
377
|
+
const southSEZ = new Vector3D(1, 0, 0);
|
|
378
|
+
const south = FrameConverter.transformVector(southSEZ, sez2ecef).normalized();
|
|
379
|
+
const north = south.scale(-1);
|
|
380
|
+
|
|
381
|
+
// Zenith vector (0, 0, 1) in SEZ
|
|
382
|
+
const zenithSEZ = new Vector3D(0, 0, 1);
|
|
383
|
+
const zenith = FrameConverter.transformVector(zenithSEZ, sez2ecef).normalized();
|
|
384
|
+
const nadir = zenith.scale(-1);
|
|
385
|
+
|
|
386
|
+
const northJ2000 = FrameConverter.transformVector(north, itrf2j2000).normalized();
|
|
387
|
+
const nadirJ2000 = FrameConverter.transformVector(nadir, itrf2j2000).normalized();
|
|
388
|
+
|
|
389
|
+
// Position of site in J2000 scaled by the orbit altitude
|
|
390
|
+
const siteJ2000 = this.getPositionJ2000(orbitInsertionTime, positionITRF);
|
|
391
|
+
const scaleFactor = (orbitAltitudeKm + earthRadius) / earthRadius;
|
|
392
|
+
const r = siteJ2000.scale(scaleFactor);
|
|
393
|
+
|
|
394
|
+
// North vector rotated by azimuth along nadir vector scaled by the velocity of a circular orbit
|
|
395
|
+
const rLength = Math.sqrt(r.x * r.x + r.y * r.y + r.z * r.z);
|
|
396
|
+
const velocityMagnitude = Math.sqrt(mu / rLength);
|
|
397
|
+
const velocityDirection = FrameConverter.rotateVector(northJ2000, nadirJ2000, azimuthRad);
|
|
398
|
+
const v = velocityDirection.scale(velocityMagnitude);
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
position: [r.x, r.y, r.z],
|
|
402
|
+
velocity: [v.x, v.y, v.z],
|
|
403
|
+
epoch: orbitInsertionTime,
|
|
404
|
+
frame: ReferenceFrame.J2000,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
static hohmannTransferWithIncZeroing(state0, targetSMA,
|
|
409
|
+
burnAtNodes = 1, mu = MU / 1e9) {
|
|
410
|
+
let t0 = state0.epochUtc;
|
|
411
|
+
let i = null;
|
|
412
|
+
|
|
413
|
+
const prop = new BallisticPropagator(state0);
|
|
414
|
+
|
|
415
|
+
for (i = 1; i <= burnAtNodes; i++) {
|
|
416
|
+
const temp = prop.propagate(new Date(t0.getTime() + 1 * 1000));
|
|
417
|
+
t0 = this.#getNextNodeCrossingGeoOrbit(temp);
|
|
418
|
+
// here
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const stateAtM1 = prop.propagate(t0);
|
|
422
|
+
|
|
423
|
+
// Initialize coords
|
|
424
|
+
const r0 = [stateAtM1.position.x, stateAtM1.position.y, stateAtM1.position.z];
|
|
425
|
+
const v0 = [stateAtM1.velocity.x, stateAtM1.velocity.y, stateAtM1.velocity.z];
|
|
426
|
+
const r0mag = norm(r0);
|
|
427
|
+
const v0mag = norm(v0);
|
|
428
|
+
const r1 = r0.map((component) => component * -1.0 * targetSMA / r0mag);
|
|
429
|
+
const r1mag = norm(r1);
|
|
430
|
+
const v1mag = Math.sqrt(mu / targetSMA);
|
|
431
|
+
const v1 = v0.map((component) => component * -1.0 * v1mag / v0mag);
|
|
432
|
+
|
|
433
|
+
// v0 and v1 unit vectors
|
|
434
|
+
const v0unit = v0.map((component) => component / v0mag);
|
|
435
|
+
const v1unit = v1.map((component) => component / v1mag);
|
|
436
|
+
|
|
437
|
+
// Transfer orbit
|
|
438
|
+
const smaTrans = (r0mag + r1mag) / 2;
|
|
439
|
+
const v0transMag = Math.sqrt(mu * (2 / r0mag - 1 / smaTrans));
|
|
440
|
+
const v1transMag = Math.sqrt(mu * (2 / r1mag - 1 / smaTrans));
|
|
441
|
+
const dv0 = v0unit.map((component) => component * (v0transMag - v0mag));
|
|
442
|
+
const dv1 = v1unit.map((component) => component * (v1mag - v1transMag));
|
|
443
|
+
|
|
444
|
+
// Transfer time
|
|
445
|
+
const t1 = new Date(t0.getTime()
|
|
446
|
+
+ (Math.PI * Math.sqrt(Math.pow(smaTrans, 3) / mu)) * 1000);
|
|
447
|
+
const burn1 = {
|
|
448
|
+
time: t0,
|
|
449
|
+
dv: {x: 0, y: norm(dv0), z: 0}, // RTN/RIC frame!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const rtnToEci = FrameConverter.rtnToEci(stateAtM1.position, stateAtM1.velocity);
|
|
453
|
+
const dv1eci = FrameConverter.transformVector(burn1.dv, rtnToEci);
|
|
454
|
+
|
|
455
|
+
const state1 = {
|
|
456
|
+
position: new NodeVector3D(
|
|
457
|
+
stateAtM1.position.x,
|
|
458
|
+
stateAtM1.position.y,
|
|
459
|
+
stateAtM1.position.z,
|
|
460
|
+
),
|
|
461
|
+
velocity: new NodeVector3D(
|
|
462
|
+
stateAtM1.velocity.x + dv1eci.x,
|
|
463
|
+
stateAtM1.velocity.y + dv1eci.y,
|
|
464
|
+
stateAtM1.velocity.z + dv1eci.z,
|
|
465
|
+
),
|
|
466
|
+
epochUtc: t0,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const prop2 = new BallisticPropagator(state1);
|
|
470
|
+
const stateAtM2 = prop2.propagate(t1);
|
|
471
|
+
|
|
472
|
+
const rElementConv = [state0.position.x, state0.position.y, state0.position.z];
|
|
473
|
+
const vElementConv = [state0.velocity.x, state0.velocity.y, state0.velocity.z];
|
|
474
|
+
const elements0 = OrbitUtils.stateVectorToElements(rElementConv, vElementConv);
|
|
475
|
+
const incDiff = elements0.inclination;
|
|
476
|
+
|
|
477
|
+
const h = cross([state0.position.x, state0.position.y, state0.position.z],
|
|
478
|
+
[state0.velocity.x, state0.velocity.y, state0.velocity.z]);
|
|
479
|
+
const hUnit = h.map((component) => component / norm(h));
|
|
480
|
+
const node = cross([0, 0, 1], h);
|
|
481
|
+
let argLat = null;
|
|
482
|
+
|
|
483
|
+
function getAngleAligned(v1, v2, vN, rightHand) {
|
|
484
|
+
if (rightHand) {
|
|
485
|
+
return Math.atan2(dot(cross(v1, v2), vN), dot(v1, v2));
|
|
486
|
+
} else {
|
|
487
|
+
return Math.atan2(dot(cross(v2, v1), vN), dot(v1, v2));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const rightHand = true;
|
|
492
|
+
const initialPos = [state0.position.x, state0.position.y, state0.position.z];
|
|
493
|
+
argLat = getAngleAligned(node, initialPos, hUnit, rightHand);
|
|
494
|
+
|
|
495
|
+
if (burnAtNodes % 2 === 0) {
|
|
496
|
+
if (argLat >= 0 && argLat <= 180) {
|
|
497
|
+
argLat -= 180.0;
|
|
498
|
+
} else {
|
|
499
|
+
argLat += 180.0;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let dvC = -1 * v1mag * Math.sin(incDiff);
|
|
504
|
+
const dvI = -1 * v1mag * (1 - Math.cos(incDiff));
|
|
505
|
+
if (!(argLat >= 0 && argLat <= 180)) {
|
|
506
|
+
dvC *= -1;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const burn2 = {
|
|
510
|
+
time: t1,
|
|
511
|
+
dv: {x: 0, y: norm(dv1) + dvI, z: dvC},
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const rtnToEci2 = FrameConverter.rtnToEci(stateAtM2.position, stateAtM2.velocity);
|
|
515
|
+
const dv1eci2 = FrameConverter.transformVector(burn2.dv, rtnToEci2);
|
|
516
|
+
|
|
517
|
+
const state2 = {
|
|
518
|
+
position: new NodeVector3D(
|
|
519
|
+
stateAtM2.position.x,
|
|
520
|
+
stateAtM2.position.y,
|
|
521
|
+
stateAtM2.position.z,
|
|
522
|
+
),
|
|
523
|
+
velocity: new NodeVector3D(
|
|
524
|
+
stateAtM2.velocity.x + dv1eci2.x,
|
|
525
|
+
stateAtM2.velocity.y + dv1eci2.y,
|
|
526
|
+
stateAtM2.velocity.z + dv1eci2.z,
|
|
527
|
+
),
|
|
528
|
+
epochUtc: t1,
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
state0,
|
|
533
|
+
burn1,
|
|
534
|
+
state1,
|
|
535
|
+
burn2,
|
|
536
|
+
state2,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
static #getNextNodeCrossingGeoOrbit(sv) {
|
|
541
|
+
const geoElements = {
|
|
542
|
+
semiMajorAxis: 35786.0 + EARTH_RADIUS_KM,
|
|
543
|
+
eccentricity: 0,
|
|
544
|
+
inclination: 0,
|
|
545
|
+
raan: 0,
|
|
546
|
+
argOfPeriapsis: 0,
|
|
547
|
+
trueAnomaly: 0,
|
|
548
|
+
};
|
|
549
|
+
const geoOrbitState = {
|
|
550
|
+
state: OrbitUtils.elementsToStateVector(geoElements),
|
|
551
|
+
epochUtc: sv.epochUtc,
|
|
552
|
+
};
|
|
553
|
+
const nextNode = this.#findMutualNodeTimes(sv, geoOrbitState);
|
|
554
|
+
return nextNode.nextNodeTime;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
static #findMutualNodeTimes(sv1, sv2, muEarth = MU / 1e9) {
|
|
558
|
+
// Implementation for finding mutual node times between two state vectors
|
|
559
|
+
|
|
560
|
+
if (!(sv1.epochUtc === sv2.epochUtc)) {
|
|
561
|
+
return "lol what on earth";
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const orbit1 = OrbitUtils.stateVectorToElements(
|
|
565
|
+
[sv1.position.x, sv1.position.y, sv1.position.z],
|
|
566
|
+
[sv1.velocity.x, sv1.velocity.y, sv1.velocity.z]);
|
|
567
|
+
const orbit2 = OrbitUtils.stateVectorToElements(
|
|
568
|
+
sv2.state.position,
|
|
569
|
+
sv2.state.velocity);
|
|
570
|
+
|
|
571
|
+
const state1 = OrbitUtils.elementsToStateVector(orbit1);
|
|
572
|
+
const state2 = OrbitUtils.elementsToStateVector(orbit2);
|
|
573
|
+
|
|
574
|
+
const h1 = cross(state1.position, state1.velocity);
|
|
575
|
+
const h2 = cross(state2.position, state2.velocity);
|
|
576
|
+
const h1Norm = norm(h1);
|
|
577
|
+
const h2Norm = norm(h2);
|
|
578
|
+
|
|
579
|
+
const h1unit = h1.map((component) => component / h1Norm);
|
|
580
|
+
const h2unit = h2.map((component) => component / h2Norm);
|
|
581
|
+
|
|
582
|
+
function getAngleBetween2Vectors(vec1, vec2) {
|
|
583
|
+
const dotProduct = dot(vec1, vec2);
|
|
584
|
+
const normsProduct = norm(vec1) * norm(vec2);
|
|
585
|
+
return Math.acos(dotProduct / normsProduct);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const ascendingFirst = getAngleBetween2Vectors(state1.position, h2unit) > Math.PI / 2;
|
|
589
|
+
|
|
590
|
+
const nodalLine = cross(h1unit, h2unit);
|
|
591
|
+
const nodalLineUnit = nodalLine.map((component) => component / norm(nodalLine));
|
|
592
|
+
const nodalLineUnitVector3D = new Vector3D(
|
|
593
|
+
nodalLineUnit[0],
|
|
594
|
+
nodalLineUnit[1],
|
|
595
|
+
nodalLineUnit[2],
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const rTilde1 = FrameConverter.perifocalToInertial(orbit1);
|
|
599
|
+
const rTilde1inverse = FrameConverter.invertMatrix(rTilde1);
|
|
600
|
+
|
|
601
|
+
const mutualNodeLinePerifocal = rTilde1inverse.multiplyVector3D(nodalLineUnitVector3D);
|
|
602
|
+
const slopeMutualNodalLinePerifocal = mutualNodeLinePerifocal.y / mutualNodeLinePerifocal.x;
|
|
603
|
+
const mutualNode = this.#findIntersectionEllipseLine( // inputs here are causing an error increase
|
|
604
|
+
slopeMutualNodalLinePerifocal,
|
|
605
|
+
orbit1.semiMajorAxis,
|
|
606
|
+
orbit1.eccentricity);
|
|
607
|
+
|
|
608
|
+
const q1 = mutualNode.vec1;
|
|
609
|
+
const q2 = mutualNode.vec2;
|
|
610
|
+
const q1unit = q1.map((component) => component / norm(q1));
|
|
611
|
+
const q2unit = q2.map((component) => component / norm(q2));
|
|
612
|
+
|
|
613
|
+
function mod(n, m) {
|
|
614
|
+
return ((n % m) + m) % m;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const n0 = mod(orbit1.trueAnomaly, 2 * Math.PI);
|
|
618
|
+
const n1 = mod(Math.atan2(dot(q1unit, [0, 1, 0]), dot(q1unit, [1, 0, 0])), 2 * Math.PI);
|
|
619
|
+
const n2 = mod(Math.atan2(dot(q2unit, [0, 1, 0]), dot(q2unit, [1, 0, 0])), 2 * Math.PI);
|
|
620
|
+
|
|
621
|
+
const e0 = OrbitUtils.trueAnomalyToEccentricAnomaly(n0, orbit1.eccentricity);
|
|
622
|
+
let e1 = OrbitUtils.trueAnomalyToEccentricAnomaly(n1, orbit1.eccentricity); // only matching to 2 sig figs w astrolib
|
|
623
|
+
let e2 = OrbitUtils.trueAnomalyToEccentricAnomaly(n2, orbit1.eccentricity); // only matching to 3 sig figs w astrolib
|
|
624
|
+
|
|
625
|
+
if (e1 < 0) {
|
|
626
|
+
e1 += 2 * Math.PI;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (e2 < 0) {
|
|
630
|
+
e2 += 2 * Math.PI;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const k1 = n1 < n0 ? 1 : 0;
|
|
634
|
+
const k2 = n2 < n0 ? 1 : 0;
|
|
635
|
+
|
|
636
|
+
const dt1 = Math.sqrt(Math.pow(orbit1.semiMajorAxis, 3) / muEarth)
|
|
637
|
+
* (2 * k1 * Math.PI + (e1 - orbit1.eccentricity * Math.sin(e1))
|
|
638
|
+
- (e0 - orbit1.eccentricity * Math.sin(e0)));
|
|
639
|
+
const dt2 = Math.sqrt(
|
|
640
|
+
Math.pow(orbit1.semiMajorAxis, 3) / muEarth)
|
|
641
|
+
* (2 * k2 * Math.PI + (e2 - orbit1.eccentricity * Math.sin(e2))
|
|
642
|
+
- (e0 - orbit1.eccentricity * Math.sin(e0)));
|
|
643
|
+
let first = null;
|
|
644
|
+
let second = null;
|
|
645
|
+
|
|
646
|
+
if (dt2 > dt1) {
|
|
647
|
+
first = new Date(sv1.epochUtc.getTime() + dt1 * 1000);
|
|
648
|
+
second = new Date(sv2.epochUtc.getTime() + dt2 * 1000);
|
|
649
|
+
} else {
|
|
650
|
+
first = new Date(sv2.epochUtc.getTime() + dt2 * 1000);
|
|
651
|
+
second = new Date(sv1.epochUtc.getTime() + dt1 * 1000);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const next = first < second ? first : second;
|
|
655
|
+
const previous = first < second ? second : first;
|
|
656
|
+
|
|
657
|
+
if (ascendingFirst) {
|
|
658
|
+
return {
|
|
659
|
+
ascendingNodeTime: first,
|
|
660
|
+
descendingNodeTime: second,
|
|
661
|
+
nextNodeTime: next,
|
|
662
|
+
previousNodeTime: previous,
|
|
663
|
+
};
|
|
664
|
+
} else {
|
|
665
|
+
return {
|
|
666
|
+
ascendingNodeTime: second,
|
|
667
|
+
descendingNodeTime: first,
|
|
668
|
+
nextNodeTime: next,
|
|
669
|
+
previousNodeTime: previous,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
static #findIntersectionEllipseLine(m, a, e) {
|
|
675
|
+
// Squares of inputs
|
|
676
|
+
const m2 = Math.pow(m, 2);
|
|
677
|
+
const a2 = Math.pow(a, 2);
|
|
678
|
+
const e2 = Math.pow(e, 2);
|
|
679
|
+
|
|
680
|
+
// The b constant, as found in the classic ellipse formulation.
|
|
681
|
+
const b = a * Math.sqrt(1 - Math.pow(e, 2));
|
|
682
|
+
const b2 = Math.pow(b, 2);
|
|
683
|
+
|
|
684
|
+
// Solving the quadratic equation (αx2 + βx + γ = 0)
|
|
685
|
+
const alpha = (a2 * m2) + b2;
|
|
686
|
+
const beta = 2 * a * b2 * e;
|
|
687
|
+
const gamma = (a2 * b2 * e2) - (a2 * b2);
|
|
688
|
+
|
|
689
|
+
const discriminant = Math.sqrt(Math.pow(beta, 2) - 4 * alpha * gamma);
|
|
690
|
+
|
|
691
|
+
if (discriminant < 0) {
|
|
692
|
+
return (new Vector3D(0, 0, 0), new Vector3D(0, 0, 0));
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const x1 = (-beta + discriminant) / (2 * alpha);
|
|
696
|
+
const x2 = (-beta - discriminant) / (2 * alpha);
|
|
697
|
+
const y1 = m * x1;
|
|
698
|
+
const y2 = m * x2;
|
|
699
|
+
|
|
700
|
+
return {
|
|
701
|
+
vec1: [x1, y1, 0],
|
|
702
|
+
vec2: [x2, y2, 0],
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
class LaunchNominalOutput {
|
|
708
|
+
constructor(launchNominalType) {
|
|
709
|
+
this.launchNominalType = launchNominalType;
|
|
710
|
+
this.states = [];
|
|
711
|
+
this.burns = [];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
addState(stateVector, elements, epoch) {
|
|
715
|
+
const pos = stateVector.position;
|
|
716
|
+
const vel = stateVector.velocity;
|
|
717
|
+
|
|
718
|
+
if (!elements) {
|
|
719
|
+
const j2000ToTeme = FrameConverter.getTransform("J2000", "TEME", epoch);
|
|
720
|
+
const posTeme = FrameConverter.transformVector(pos, j2000ToTeme);
|
|
721
|
+
const velTeme = FrameConverter.transformVector(vel, j2000ToTeme);
|
|
722
|
+
elements = OrbitUtils.stateVectorToElements(
|
|
723
|
+
[posTeme.x, posTeme.y, posTeme.z],
|
|
724
|
+
[velTeme.x, velTeme.y, velTeme.z]);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
this.states.push({
|
|
728
|
+
epoch: epoch,
|
|
729
|
+
sv: {
|
|
730
|
+
r: [pos.x, pos.y, pos.z],
|
|
731
|
+
v: [vel.x, vel.y, vel.z],
|
|
732
|
+
},
|
|
733
|
+
elements: {
|
|
734
|
+
sma: elements.semiMajorAxis,
|
|
735
|
+
ecc: elements.eccentricity,
|
|
736
|
+
inc: elements.inclination,
|
|
737
|
+
raan: elements.raan,
|
|
738
|
+
argp: elements.argOfPeriapsis,
|
|
739
|
+
ta: elements.trueAnomaly,
|
|
740
|
+
},
|
|
741
|
+
// Placeholder for tle
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
return this;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
addBurn(epoch, dv) {
|
|
748
|
+
this.burns.push({
|
|
749
|
+
epoch: epoch,
|
|
750
|
+
dv: [dv.x, dv.y, dv.z],
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
return this;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
toJSON() {
|
|
757
|
+
if (this.burns.length === 0) {
|
|
758
|
+
return {
|
|
759
|
+
launchNominalType: this.launchNominalType,
|
|
760
|
+
states: this.states,
|
|
761
|
+
};
|
|
762
|
+
} else {
|
|
763
|
+
return {
|
|
764
|
+
launchNominalType: this.launchNominalType,
|
|
765
|
+
states: this.states,
|
|
766
|
+
burns: this.burns,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
export {LaunchNominalClass};
|