@saber-usa/node-common 1.7.2 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -41
- package/package.json +51 -51
- package/src/FrameConverter.js +1120 -1068
- package/src/LLA.js +179 -179
- package/src/LaunchNominalClass.js +774 -824
- 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 +3214 -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/PropagateUtils.js
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import {BallisticPropagator} from "./ballisticPropagator.js";
|
|
2
|
-
import {RungeKutta4Propagator,
|
|
3
|
-
J2000,
|
|
4
|
-
KeplerPropagator,
|
|
5
|
-
EpochUTC,
|
|
6
|
-
ClassicalElements} from "pious-squid";
|
|
7
|
-
import {sgp4} from "satellite.js";
|
|
8
|
-
import {NodeVector3D} from "./NodeVector3D.js";
|
|
9
|
-
import {checkTle} from "./astro.js";
|
|
10
|
-
import {OrbitUtils} from "./OrbitUtils.js";
|
|
11
|
-
|
|
12
|
-
const ReferenceFrame = {
|
|
13
|
-
ITRF: "ITRF",
|
|
14
|
-
J2000: "J2000",
|
|
15
|
-
TEME: "TEME",
|
|
16
|
-
GCRF: "GCRF",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
class PropagateUtils {
|
|
20
|
-
static propagatorSelection(initialState, propagatorType, endTime) {
|
|
21
|
-
let propVal = null;
|
|
22
|
-
switch (propagatorType) {
|
|
23
|
-
case "NUMERICAL":
|
|
24
|
-
propVal = PropagateUtils.numericalPropagator(initialState, endTime);
|
|
25
|
-
break;
|
|
26
|
-
case "KEPLERIAN":
|
|
27
|
-
propVal = PropagateUtils.keplerianPropagator(initialState, endTime);
|
|
28
|
-
break;
|
|
29
|
-
case "ANALYTICAL":
|
|
30
|
-
propVal = PropagateUtils.analyticalPropagator(
|
|
31
|
-
initialState, ReferenceFrame.J2000, endTime);
|
|
32
|
-
break;
|
|
33
|
-
case "TLE":
|
|
34
|
-
propVal = PropagateUtils.tlePropagator(initialState, endTime);
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
return propVal;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
static numericalPropagator(initialState, time) {
|
|
41
|
-
const state0 = new J2000(
|
|
42
|
-
new EpochUTC(initialState.epochUtc), initialState.position, initialState.velocity);
|
|
43
|
-
const prop = new RungeKutta4Propagator(state0);
|
|
44
|
-
const endTime = new EpochUTC(time);
|
|
45
|
-
const result = prop.propagate(endTime);
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
static analyticalPropagator(initialState, frame, time) {
|
|
50
|
-
const state = {
|
|
51
|
-
position: new NodeVector3D(
|
|
52
|
-
initialState.position.x, initialState.position.y, initialState.position.z), // km
|
|
53
|
-
velocity: new NodeVector3D(
|
|
54
|
-
initialState.velocity.x, initialState.velocity.y, initialState.velocity.z), // km/s
|
|
55
|
-
epochUtc: initialState.epochUtc,
|
|
56
|
-
referenceFrame: frame,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const propagator = new BallisticPropagator(state);
|
|
60
|
-
const result = propagator.propagate(time);
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static keplerianPropagator(initialState, time) {
|
|
65
|
-
const elementsObj = OrbitUtils.stateVectorToElements(
|
|
66
|
-
initialState.position, initialState.velocity);
|
|
67
|
-
|
|
68
|
-
const elements = new ClassicalElements(
|
|
69
|
-
new EpochUTC(initialState.epoch),
|
|
70
|
-
elementsObj.semiMajorAxis,
|
|
71
|
-
elementsObj.eccentricity,
|
|
72
|
-
elementsObj.inclination,
|
|
73
|
-
elementsObj.raan,
|
|
74
|
-
elementsObj.argOfPeriapsis,
|
|
75
|
-
elementsObj.trueAnomaly,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// Now pass to KeplerPropagator:
|
|
79
|
-
const prop = new KeplerPropagator(elements);
|
|
80
|
-
|
|
81
|
-
const result = prop.propagate(new EpochUTC(time));
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return result;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static tlePropagator(tle, time) {
|
|
88
|
-
const line1 = tle[0];
|
|
89
|
-
const line2 = tle[1];
|
|
90
|
-
const satrec = checkTle(line1, line2);
|
|
91
|
-
if (!satrec) {
|
|
92
|
-
throw new Error("Invalid TLE data");
|
|
93
|
-
}
|
|
94
|
-
const result = sgp4(satrec, time);
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export {PropagateUtils};
|
|
100
|
-
|
|
1
|
+
import {BallisticPropagator} from "./ballisticPropagator.js";
|
|
2
|
+
import {RungeKutta4Propagator,
|
|
3
|
+
J2000,
|
|
4
|
+
KeplerPropagator,
|
|
5
|
+
EpochUTC,
|
|
6
|
+
ClassicalElements} from "pious-squid";
|
|
7
|
+
import {sgp4} from "satellite.js";
|
|
8
|
+
import {NodeVector3D} from "./NodeVector3D.js";
|
|
9
|
+
import {checkTle} from "./astro.js";
|
|
10
|
+
import {OrbitUtils} from "./OrbitUtils.js";
|
|
11
|
+
|
|
12
|
+
const ReferenceFrame = {
|
|
13
|
+
ITRF: "ITRF",
|
|
14
|
+
J2000: "J2000",
|
|
15
|
+
TEME: "TEME",
|
|
16
|
+
GCRF: "GCRF",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
class PropagateUtils {
|
|
20
|
+
static propagatorSelection(initialState, propagatorType, endTime) {
|
|
21
|
+
let propVal = null;
|
|
22
|
+
switch (propagatorType) {
|
|
23
|
+
case "NUMERICAL":
|
|
24
|
+
propVal = PropagateUtils.numericalPropagator(initialState, endTime);
|
|
25
|
+
break;
|
|
26
|
+
case "KEPLERIAN":
|
|
27
|
+
propVal = PropagateUtils.keplerianPropagator(initialState, endTime);
|
|
28
|
+
break;
|
|
29
|
+
case "ANALYTICAL":
|
|
30
|
+
propVal = PropagateUtils.analyticalPropagator(
|
|
31
|
+
initialState, ReferenceFrame.J2000, endTime);
|
|
32
|
+
break;
|
|
33
|
+
case "TLE":
|
|
34
|
+
propVal = PropagateUtils.tlePropagator(initialState, endTime);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
return propVal;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static numericalPropagator(initialState, time) {
|
|
41
|
+
const state0 = new J2000(
|
|
42
|
+
new EpochUTC(initialState.epochUtc), initialState.position, initialState.velocity);
|
|
43
|
+
const prop = new RungeKutta4Propagator(state0);
|
|
44
|
+
const endTime = new EpochUTC(time);
|
|
45
|
+
const result = prop.propagate(endTime);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static analyticalPropagator(initialState, frame, time) {
|
|
50
|
+
const state = {
|
|
51
|
+
position: new NodeVector3D(
|
|
52
|
+
initialState.position.x, initialState.position.y, initialState.position.z), // km
|
|
53
|
+
velocity: new NodeVector3D(
|
|
54
|
+
initialState.velocity.x, initialState.velocity.y, initialState.velocity.z), // km/s
|
|
55
|
+
epochUtc: initialState.epochUtc,
|
|
56
|
+
referenceFrame: frame,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const propagator = new BallisticPropagator(state);
|
|
60
|
+
const result = propagator.propagate(time);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static keplerianPropagator(initialState, time) {
|
|
65
|
+
const elementsObj = OrbitUtils.stateVectorToElements(
|
|
66
|
+
initialState.position, initialState.velocity);
|
|
67
|
+
|
|
68
|
+
const elements = new ClassicalElements(
|
|
69
|
+
new EpochUTC(initialState.epoch),
|
|
70
|
+
elementsObj.semiMajorAxis,
|
|
71
|
+
elementsObj.eccentricity,
|
|
72
|
+
elementsObj.inclination,
|
|
73
|
+
elementsObj.raan,
|
|
74
|
+
elementsObj.argOfPeriapsis,
|
|
75
|
+
elementsObj.trueAnomaly,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Now pass to KeplerPropagator:
|
|
79
|
+
const prop = new KeplerPropagator(elements);
|
|
80
|
+
|
|
81
|
+
const result = prop.propagate(new EpochUTC(time));
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static tlePropagator(tle, time) {
|
|
88
|
+
const line1 = tle[0];
|
|
89
|
+
const line2 = tle[1];
|
|
90
|
+
const satrec = checkTle(line1, line2);
|
|
91
|
+
if (!satrec) {
|
|
92
|
+
throw new Error("Invalid TLE data");
|
|
93
|
+
}
|
|
94
|
+
const result = sgp4(satrec, time);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {PropagateUtils};
|
|
100
|
+
|
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
import constants, {WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
2
|
-
DEG2RAD, AU_KM as _AU_KM,
|
|
3
|
-
GEO_ALTITUDE_KM, RAD2DEG} from "./constants.js";
|
|
4
|
-
import solar from "solar-calculator";
|
|
5
|
-
import {Vector3} from "three";
|
|
6
|
-
import {wrapToRange} from "./utils.js";
|
|
7
|
-
import {sunPosAt} from "./astro.js";
|
|
8
|
-
|
|
9
|
-
class ShadowGEOCalculator {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.constants = constants;
|
|
12
|
-
this.solarCalculator = solar;
|
|
13
|
-
this.sunRadiusKm = 695700; // Radius of the Sun in kilometers, (source: orekit, through resolution B3 from IAU 2015)
|
|
14
|
-
// 695701 in astro library
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
static _calculateUmbraGeometry(deltaPS) {
|
|
18
|
-
const Dp = 2 * WGS84_EARTH_EQUATORIAL_RADIUS_KM;
|
|
19
|
-
const Ds = 2 * 695700; // sunRadiusKm
|
|
20
|
-
|
|
21
|
-
// Distance from Earth center to umbra apex
|
|
22
|
-
const Xu = (Dp * deltaPS) / (Ds - Dp);
|
|
23
|
-
|
|
24
|
-
// Umbra subtended angle
|
|
25
|
-
const alphaU = Math.asin(Dp / (2 * Xu));
|
|
26
|
-
|
|
27
|
-
return {Xu, alphaU};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
static _calculatePenumbraGeometry(deltaPS) {
|
|
31
|
-
const Dp = 2 * WGS84_EARTH_EQUATORIAL_RADIUS_KM;
|
|
32
|
-
const Ds = 2 * 695700; // sunRadiusKm
|
|
33
|
-
|
|
34
|
-
// Distance from Earth center to penumbra apex
|
|
35
|
-
const Xp = (Dp * deltaPS) / (Ds + Dp);
|
|
36
|
-
|
|
37
|
-
// Penumbra subtended angle
|
|
38
|
-
const alphaP = Math.asin(Dp / (2 * Xp));
|
|
39
|
-
|
|
40
|
-
return {Xp, alphaP};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static _umbraRadiusDistance(distance, Xu, alphaU) {
|
|
44
|
-
if (distance > Xu) {
|
|
45
|
-
throw new Error("Distance must be less than or equal to the umbra apex distance.");
|
|
46
|
-
}
|
|
47
|
-
// Calculate the radius of the umbra at a given distance from the apex
|
|
48
|
-
return (Xu - distance) * Math.tan(alphaU);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static _penumbraRadiusDistance(distance, Xp, alphaP) {
|
|
52
|
-
// Calculate the radius of the penumbra at a given distance from the apex
|
|
53
|
-
return (distance + Math.abs(Xp)) * Math.tan(alphaP);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static _sunUnitVector(sunLongitude, sunDeclination) {
|
|
57
|
-
const sunVector = new Vector3();
|
|
58
|
-
sunVector.x = Math.cos(sunDeclination * DEG2RAD)
|
|
59
|
-
* Math.cos(sunLongitude * DEG2RAD);
|
|
60
|
-
sunVector.y = Math.cos(sunDeclination * DEG2RAD)
|
|
61
|
-
* Math.sin(sunLongitude * DEG2RAD);
|
|
62
|
-
sunVector.z = Math.sin(sunDeclination * DEG2RAD);
|
|
63
|
-
return sunVector; // returns the unit vector of the Sun's position
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static _calculateSunDistance(sunLongitude, sunDeclination) {
|
|
67
|
-
// Longitude and declination (latitude) describe direction, not distance.
|
|
68
|
-
// To get the Earth-Sun distance in km, use astronomical data (mean ≈ 149,597,870.7 km).
|
|
69
|
-
// If you want the position vector in km, use the distance and direction:
|
|
70
|
-
const AU_KM = _AU_KM; // Astronomical Unit in kilometers
|
|
71
|
-
// AstroLibrary uses 149597900
|
|
72
|
-
const r = AU_KM; // Use actual Earth-Sun distance if available, otherwise mean AU
|
|
73
|
-
|
|
74
|
-
// Convert longitude and declination to a position vector in km
|
|
75
|
-
const x = r * Math.cos(sunDeclination * DEG2RAD)
|
|
76
|
-
* Math.cos(sunLongitude * DEG2RAD);
|
|
77
|
-
const y = r * Math.cos(sunDeclination * DEG2RAD)
|
|
78
|
-
* Math.sin(sunLongitude * DEG2RAD);
|
|
79
|
-
const z = r * Math.sin(sunDeclination * DEG2RAD);
|
|
80
|
-
|
|
81
|
-
return {x, y, z, distance: r};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
static geoBeltShadowIntersection(date) {
|
|
85
|
-
const [sunDeclination, sunLongitude] = sunPosAt(date);
|
|
86
|
-
// Calculate sun position
|
|
87
|
-
let deltaPS;
|
|
88
|
-
if (sunLongitude !== null && sunDeclination !== null) {
|
|
89
|
-
// convert sunLongitude and sunDeclination to distance in km
|
|
90
|
-
deltaPS = ShadowGEOCalculator._calculateSunDistance(
|
|
91
|
-
sunLongitude, sunDeclination)["distance"];
|
|
92
|
-
} else {
|
|
93
|
-
const time = new Date();
|
|
94
|
-
const [newSunDeclination, newSunLongitude] = sunPosAt(time);
|
|
95
|
-
const sunPositionData = ShadowGEOCalculator._calculateSunDistance(
|
|
96
|
-
newSunLongitude, newSunDeclination);
|
|
97
|
-
deltaPS = sunPositionData.distance;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Calculate shadow geometry
|
|
101
|
-
const {Xu, alphaU} = ShadowGEOCalculator._calculateUmbraGeometry(deltaPS);
|
|
102
|
-
const {Xp, alphaP} = ShadowGEOCalculator._calculatePenumbraGeometry(deltaPS);
|
|
103
|
-
|
|
104
|
-
const geoRadius = WGS84_EARTH_EQUATORIAL_RADIUS_KM + GEO_ALTITUDE_KM;
|
|
105
|
-
|
|
106
|
-
// Calculate Shadow radii at GEO altitude
|
|
107
|
-
const umbraRadiusGEO = ShadowGEOCalculator._umbraRadiusDistance(geoRadius, Xu, alphaU);
|
|
108
|
-
const penumbraRadiusGEO = ShadowGEOCalculator._penumbraRadiusDistance(
|
|
109
|
-
geoRadius, Xp, alphaP);
|
|
110
|
-
|
|
111
|
-
// Calculate sun unit vector
|
|
112
|
-
const sunVector = ShadowGEOCalculator._sunUnitVector(sunLongitude, sunDeclination);
|
|
113
|
-
|
|
114
|
-
// Calculate shadow axis
|
|
115
|
-
const shadowAxis = [-sunVector.x, -sunVector.y, -sunVector.z];
|
|
116
|
-
|
|
117
|
-
// Finding shadow axis center in x, y, z coordinates
|
|
118
|
-
|
|
119
|
-
const shadowCenter = [
|
|
120
|
-
shadowAxis[0] * geoRadius,
|
|
121
|
-
shadowAxis[1] * geoRadius,
|
|
122
|
-
shadowAxis[2] * geoRadius,
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
// Converting shadow axis to latitude and longitude
|
|
126
|
-
const shadowCenterLat = Math.asin(shadowCenter[2] / geoRadius) * RAD2DEG;
|
|
127
|
-
const shadowCenterLon = Math.atan2(shadowCenter[1], shadowCenter[0]) * RAD2DEG;
|
|
128
|
-
|
|
129
|
-
// Check if GEO belt intersects the shadow
|
|
130
|
-
const equatorialDistance = Math.abs(shadowCenter[2]); // Distance from the equator to the shadow center
|
|
131
|
-
const intersectsUmbra = umbraRadiusGEO > equatorialDistance;
|
|
132
|
-
const intersectsPenumbra = penumbraRadiusGEO > equatorialDistance;
|
|
133
|
-
|
|
134
|
-
const intersectionPoints = [];
|
|
135
|
-
|
|
136
|
-
if (intersectsPenumbra) {
|
|
137
|
-
// angle from the shadow center to the intersection point
|
|
138
|
-
const alpha = Math.sqrt(penumbraRadiusGEO**2 - equatorialDistance**2);
|
|
139
|
-
const theta = Math.atan2(alpha, geoRadius);
|
|
140
|
-
|
|
141
|
-
// Project shadow center onto the GEO belt
|
|
142
|
-
const shadowAxisEq = [shadowAxis[0], shadowAxis[1], 0];
|
|
143
|
-
const shadowAxisNorm = Math.sqrt(shadowAxisEq[0]**2 + shadowAxisEq[1]**2);
|
|
144
|
-
shadowAxisEq[0] /= shadowAxisNorm;
|
|
145
|
-
shadowAxisEq[1] /= shadowAxisNorm;
|
|
146
|
-
|
|
147
|
-
// Intersection points in the GEO belt
|
|
148
|
-
const centerLon = shadowCenterLon; // You already have this!
|
|
149
|
-
|
|
150
|
-
let lon1 = centerLon + theta * RAD2DEG;
|
|
151
|
-
let lon2 = centerLon - theta * RAD2DEG;
|
|
152
|
-
|
|
153
|
-
// Ensure longitudes are within -180 to 180 degrees
|
|
154
|
-
lon1 = wrapToRange(lon1, -180, 180);
|
|
155
|
-
lon2 = wrapToRange(lon2, -180, 180);
|
|
156
|
-
|
|
157
|
-
intersectionPoints.push({
|
|
158
|
-
type: "penumbra",
|
|
159
|
-
longitudeRange: [lon1, lon2],
|
|
160
|
-
latitude: 0, // Equatorial belt
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (intersectsUmbra) {
|
|
165
|
-
// angle from the shadow center to the intersection point
|
|
166
|
-
const alpha = Math.sqrt(umbraRadiusGEO**2 - equatorialDistance**2);
|
|
167
|
-
const theta = Math.atan2(alpha, geoRadius);
|
|
168
|
-
|
|
169
|
-
// Project shadow center onto the GEO belt
|
|
170
|
-
const shadowAxisEq = [shadowAxis[0], shadowAxis[1], 0];
|
|
171
|
-
const shadowAxisNorm = Math.sqrt(shadowAxisEq[0]**2 + shadowAxisEq[1]**2);
|
|
172
|
-
shadowAxisEq[0] /= shadowAxisNorm;
|
|
173
|
-
shadowAxisEq[1] /= shadowAxisNorm;
|
|
174
|
-
|
|
175
|
-
// Intersection points in the GEO belt
|
|
176
|
-
const centerLon = shadowCenterLon; // You already have this!
|
|
177
|
-
|
|
178
|
-
let lon1 = centerLon + theta * RAD2DEG;
|
|
179
|
-
let lon2 = centerLon - theta * RAD2DEG;
|
|
180
|
-
|
|
181
|
-
// Ensure longitudes are within -180 to 180 degrees
|
|
182
|
-
lon1 = ((lon1 + 180) % 360 + 360) % 360 - 180;
|
|
183
|
-
lon2 = ((lon2 + 180) % 360 + 360) % 360 - 180;
|
|
184
|
-
|
|
185
|
-
intersectionPoints.push({
|
|
186
|
-
type: "umbra",
|
|
187
|
-
longitudeRange: [lon1, lon2],
|
|
188
|
-
latitude: 0, // Equatorial belt
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
shadowCenter: shadowCenter,
|
|
194
|
-
shadowCenterLat: shadowCenterLat,
|
|
195
|
-
shadowCenterLon: shadowCenterLon,
|
|
196
|
-
umbraRadiusGEO: umbraRadiusGEO,
|
|
197
|
-
penumbraRadiusGEO: penumbraRadiusGEO,
|
|
198
|
-
intersectionPoints: intersectionPoints,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export {ShadowGEOCalculator};
|
|
1
|
+
import constants, {WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
2
|
+
DEG2RAD, AU_KM as _AU_KM,
|
|
3
|
+
GEO_ALTITUDE_KM, RAD2DEG} from "./constants.js";
|
|
4
|
+
import solar from "solar-calculator";
|
|
5
|
+
import {Vector3} from "three";
|
|
6
|
+
import {wrapToRange} from "./utils.js";
|
|
7
|
+
import {sunPosAt} from "./astro.js";
|
|
8
|
+
|
|
9
|
+
class ShadowGEOCalculator {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.constants = constants;
|
|
12
|
+
this.solarCalculator = solar;
|
|
13
|
+
this.sunRadiusKm = 695700; // Radius of the Sun in kilometers, (source: orekit, through resolution B3 from IAU 2015)
|
|
14
|
+
// 695701 in astro library
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static _calculateUmbraGeometry(deltaPS) {
|
|
18
|
+
const Dp = 2 * WGS84_EARTH_EQUATORIAL_RADIUS_KM;
|
|
19
|
+
const Ds = 2 * 695700; // sunRadiusKm
|
|
20
|
+
|
|
21
|
+
// Distance from Earth center to umbra apex
|
|
22
|
+
const Xu = (Dp * deltaPS) / (Ds - Dp);
|
|
23
|
+
|
|
24
|
+
// Umbra subtended angle
|
|
25
|
+
const alphaU = Math.asin(Dp / (2 * Xu));
|
|
26
|
+
|
|
27
|
+
return {Xu, alphaU};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static _calculatePenumbraGeometry(deltaPS) {
|
|
31
|
+
const Dp = 2 * WGS84_EARTH_EQUATORIAL_RADIUS_KM;
|
|
32
|
+
const Ds = 2 * 695700; // sunRadiusKm
|
|
33
|
+
|
|
34
|
+
// Distance from Earth center to penumbra apex
|
|
35
|
+
const Xp = (Dp * deltaPS) / (Ds + Dp);
|
|
36
|
+
|
|
37
|
+
// Penumbra subtended angle
|
|
38
|
+
const alphaP = Math.asin(Dp / (2 * Xp));
|
|
39
|
+
|
|
40
|
+
return {Xp, alphaP};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static _umbraRadiusDistance(distance, Xu, alphaU) {
|
|
44
|
+
if (distance > Xu) {
|
|
45
|
+
throw new Error("Distance must be less than or equal to the umbra apex distance.");
|
|
46
|
+
}
|
|
47
|
+
// Calculate the radius of the umbra at a given distance from the apex
|
|
48
|
+
return (Xu - distance) * Math.tan(alphaU);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static _penumbraRadiusDistance(distance, Xp, alphaP) {
|
|
52
|
+
// Calculate the radius of the penumbra at a given distance from the apex
|
|
53
|
+
return (distance + Math.abs(Xp)) * Math.tan(alphaP);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static _sunUnitVector(sunLongitude, sunDeclination) {
|
|
57
|
+
const sunVector = new Vector3();
|
|
58
|
+
sunVector.x = Math.cos(sunDeclination * DEG2RAD)
|
|
59
|
+
* Math.cos(sunLongitude * DEG2RAD);
|
|
60
|
+
sunVector.y = Math.cos(sunDeclination * DEG2RAD)
|
|
61
|
+
* Math.sin(sunLongitude * DEG2RAD);
|
|
62
|
+
sunVector.z = Math.sin(sunDeclination * DEG2RAD);
|
|
63
|
+
return sunVector; // returns the unit vector of the Sun's position
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static _calculateSunDistance(sunLongitude, sunDeclination) {
|
|
67
|
+
// Longitude and declination (latitude) describe direction, not distance.
|
|
68
|
+
// To get the Earth-Sun distance in km, use astronomical data (mean ≈ 149,597,870.7 km).
|
|
69
|
+
// If you want the position vector in km, use the distance and direction:
|
|
70
|
+
const AU_KM = _AU_KM; // Astronomical Unit in kilometers
|
|
71
|
+
// AstroLibrary uses 149597900
|
|
72
|
+
const r = AU_KM; // Use actual Earth-Sun distance if available, otherwise mean AU
|
|
73
|
+
|
|
74
|
+
// Convert longitude and declination to a position vector in km
|
|
75
|
+
const x = r * Math.cos(sunDeclination * DEG2RAD)
|
|
76
|
+
* Math.cos(sunLongitude * DEG2RAD);
|
|
77
|
+
const y = r * Math.cos(sunDeclination * DEG2RAD)
|
|
78
|
+
* Math.sin(sunLongitude * DEG2RAD);
|
|
79
|
+
const z = r * Math.sin(sunDeclination * DEG2RAD);
|
|
80
|
+
|
|
81
|
+
return {x, y, z, distance: r};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static geoBeltShadowIntersection(date) {
|
|
85
|
+
const [sunDeclination, sunLongitude] = sunPosAt(date);
|
|
86
|
+
// Calculate sun position
|
|
87
|
+
let deltaPS;
|
|
88
|
+
if (sunLongitude !== null && sunDeclination !== null) {
|
|
89
|
+
// convert sunLongitude and sunDeclination to distance in km
|
|
90
|
+
deltaPS = ShadowGEOCalculator._calculateSunDistance(
|
|
91
|
+
sunLongitude, sunDeclination)["distance"];
|
|
92
|
+
} else {
|
|
93
|
+
const time = new Date();
|
|
94
|
+
const [newSunDeclination, newSunLongitude] = sunPosAt(time);
|
|
95
|
+
const sunPositionData = ShadowGEOCalculator._calculateSunDistance(
|
|
96
|
+
newSunLongitude, newSunDeclination);
|
|
97
|
+
deltaPS = sunPositionData.distance;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Calculate shadow geometry
|
|
101
|
+
const {Xu, alphaU} = ShadowGEOCalculator._calculateUmbraGeometry(deltaPS);
|
|
102
|
+
const {Xp, alphaP} = ShadowGEOCalculator._calculatePenumbraGeometry(deltaPS);
|
|
103
|
+
|
|
104
|
+
const geoRadius = WGS84_EARTH_EQUATORIAL_RADIUS_KM + GEO_ALTITUDE_KM;
|
|
105
|
+
|
|
106
|
+
// Calculate Shadow radii at GEO altitude
|
|
107
|
+
const umbraRadiusGEO = ShadowGEOCalculator._umbraRadiusDistance(geoRadius, Xu, alphaU);
|
|
108
|
+
const penumbraRadiusGEO = ShadowGEOCalculator._penumbraRadiusDistance(
|
|
109
|
+
geoRadius, Xp, alphaP);
|
|
110
|
+
|
|
111
|
+
// Calculate sun unit vector
|
|
112
|
+
const sunVector = ShadowGEOCalculator._sunUnitVector(sunLongitude, sunDeclination);
|
|
113
|
+
|
|
114
|
+
// Calculate shadow axis
|
|
115
|
+
const shadowAxis = [-sunVector.x, -sunVector.y, -sunVector.z];
|
|
116
|
+
|
|
117
|
+
// Finding shadow axis center in x, y, z coordinates
|
|
118
|
+
|
|
119
|
+
const shadowCenter = [
|
|
120
|
+
shadowAxis[0] * geoRadius,
|
|
121
|
+
shadowAxis[1] * geoRadius,
|
|
122
|
+
shadowAxis[2] * geoRadius,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// Converting shadow axis to latitude and longitude
|
|
126
|
+
const shadowCenterLat = Math.asin(shadowCenter[2] / geoRadius) * RAD2DEG;
|
|
127
|
+
const shadowCenterLon = Math.atan2(shadowCenter[1], shadowCenter[0]) * RAD2DEG;
|
|
128
|
+
|
|
129
|
+
// Check if GEO belt intersects the shadow
|
|
130
|
+
const equatorialDistance = Math.abs(shadowCenter[2]); // Distance from the equator to the shadow center
|
|
131
|
+
const intersectsUmbra = umbraRadiusGEO > equatorialDistance;
|
|
132
|
+
const intersectsPenumbra = penumbraRadiusGEO > equatorialDistance;
|
|
133
|
+
|
|
134
|
+
const intersectionPoints = [];
|
|
135
|
+
|
|
136
|
+
if (intersectsPenumbra) {
|
|
137
|
+
// angle from the shadow center to the intersection point
|
|
138
|
+
const alpha = Math.sqrt(penumbraRadiusGEO**2 - equatorialDistance**2);
|
|
139
|
+
const theta = Math.atan2(alpha, geoRadius);
|
|
140
|
+
|
|
141
|
+
// Project shadow center onto the GEO belt
|
|
142
|
+
const shadowAxisEq = [shadowAxis[0], shadowAxis[1], 0];
|
|
143
|
+
const shadowAxisNorm = Math.sqrt(shadowAxisEq[0]**2 + shadowAxisEq[1]**2);
|
|
144
|
+
shadowAxisEq[0] /= shadowAxisNorm;
|
|
145
|
+
shadowAxisEq[1] /= shadowAxisNorm;
|
|
146
|
+
|
|
147
|
+
// Intersection points in the GEO belt
|
|
148
|
+
const centerLon = shadowCenterLon; // You already have this!
|
|
149
|
+
|
|
150
|
+
let lon1 = centerLon + theta * RAD2DEG;
|
|
151
|
+
let lon2 = centerLon - theta * RAD2DEG;
|
|
152
|
+
|
|
153
|
+
// Ensure longitudes are within -180 to 180 degrees
|
|
154
|
+
lon1 = wrapToRange(lon1, -180, 180);
|
|
155
|
+
lon2 = wrapToRange(lon2, -180, 180);
|
|
156
|
+
|
|
157
|
+
intersectionPoints.push({
|
|
158
|
+
type: "penumbra",
|
|
159
|
+
longitudeRange: [lon1, lon2],
|
|
160
|
+
latitude: 0, // Equatorial belt
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (intersectsUmbra) {
|
|
165
|
+
// angle from the shadow center to the intersection point
|
|
166
|
+
const alpha = Math.sqrt(umbraRadiusGEO**2 - equatorialDistance**2);
|
|
167
|
+
const theta = Math.atan2(alpha, geoRadius);
|
|
168
|
+
|
|
169
|
+
// Project shadow center onto the GEO belt
|
|
170
|
+
const shadowAxisEq = [shadowAxis[0], shadowAxis[1], 0];
|
|
171
|
+
const shadowAxisNorm = Math.sqrt(shadowAxisEq[0]**2 + shadowAxisEq[1]**2);
|
|
172
|
+
shadowAxisEq[0] /= shadowAxisNorm;
|
|
173
|
+
shadowAxisEq[1] /= shadowAxisNorm;
|
|
174
|
+
|
|
175
|
+
// Intersection points in the GEO belt
|
|
176
|
+
const centerLon = shadowCenterLon; // You already have this!
|
|
177
|
+
|
|
178
|
+
let lon1 = centerLon + theta * RAD2DEG;
|
|
179
|
+
let lon2 = centerLon - theta * RAD2DEG;
|
|
180
|
+
|
|
181
|
+
// Ensure longitudes are within -180 to 180 degrees
|
|
182
|
+
lon1 = ((lon1 + 180) % 360 + 360) % 360 - 180;
|
|
183
|
+
lon2 = ((lon2 + 180) % 360 + 360) % 360 - 180;
|
|
184
|
+
|
|
185
|
+
intersectionPoints.push({
|
|
186
|
+
type: "umbra",
|
|
187
|
+
longitudeRange: [lon1, lon2],
|
|
188
|
+
latitude: 0, // Equatorial belt
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
shadowCenter: shadowCenter,
|
|
194
|
+
shadowCenterLat: shadowCenterLat,
|
|
195
|
+
shadowCenterLon: shadowCenterLon,
|
|
196
|
+
umbraRadiusGEO: umbraRadiusGEO,
|
|
197
|
+
penumbraRadiusGEO: penumbraRadiusGEO,
|
|
198
|
+
intersectionPoints: intersectionPoints,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export {ShadowGEOCalculator};
|