@saber-usa/node-common 1.6.207

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,201 @@
1
+ const constants = require("./constants.js");
2
+ const solar = require("solar-calculator");
3
+ const THREE = require("three");
4
+ const {wrapToRange} = require("./utils.js");
5
+ const {sunPosAt} = require("./astro.js");
6
+
7
+ class ShadowGEOCalculator {
8
+ constructor() {
9
+ this.constants = constants;
10
+ this.solarCalculator = solar;
11
+ this.sunRadiusKm = 695700; // Radius of the Sun in kilometers, (source: orekit, through resolution B3 from IAU 2015)
12
+ // 695701 in astro library
13
+ }
14
+
15
+ static _calculateUmbraGeometry(deltaPS) {
16
+ const Dp = 2 * constants.WGS84_EARTH_EQUATORIAL_RADIUS_KM;
17
+ const Ds = 2 * 695700; // sunRadiusKm
18
+
19
+ // Distance from Earth center to umbra apex
20
+ const Xu = (Dp * deltaPS) / (Ds - Dp);
21
+
22
+ // Umbra subtended angle
23
+ const alphaU = Math.asin(Dp / (2 * Xu));
24
+
25
+ return {Xu, alphaU};
26
+ }
27
+
28
+ static _calculatePenumbraGeometry(deltaPS) {
29
+ const Dp = 2 * constants.WGS84_EARTH_EQUATORIAL_RADIUS_KM;
30
+ const Ds = 2 * 695700; // sunRadiusKm
31
+
32
+ // Distance from Earth center to penumbra apex
33
+ const Xp = (Dp * deltaPS) / (Ds + Dp);
34
+
35
+ // Penumbra subtended angle
36
+ const alphaP = Math.asin(Dp / (2 * Xp));
37
+
38
+ return {Xp, alphaP};
39
+ }
40
+
41
+ static _umbraRadiusDistance(distance, Xu, alphaU) {
42
+ if (distance > Xu) {
43
+ throw new Error("Distance must be less than or equal to the umbra apex distance.");
44
+ }
45
+ // Calculate the radius of the umbra at a given distance from the apex
46
+ return (Xu - distance) * Math.tan(alphaU);
47
+ }
48
+
49
+ static _penumbraRadiusDistance(distance, Xp, alphaP) {
50
+ // Calculate the radius of the penumbra at a given distance from the apex
51
+ return (distance + Math.abs(Xp)) * Math.tan(alphaP);
52
+ }
53
+
54
+ static _sunUnitVector(sunLongitude, sunDeclination) {
55
+ const sunVector = new THREE.Vector3();
56
+ sunVector.x = Math.cos(sunDeclination * constants.DEG2RAD)
57
+ * Math.cos(sunLongitude * constants.DEG2RAD);
58
+ sunVector.y = Math.cos(sunDeclination * constants.DEG2RAD)
59
+ * Math.sin(sunLongitude * constants.DEG2RAD);
60
+ sunVector.z = Math.sin(sunDeclination * constants.DEG2RAD);
61
+ return sunVector; // returns the unit vector of the Sun's position
62
+ }
63
+
64
+ static _calculateSunDistance(sunLongitude, sunDeclination) {
65
+ // Longitude and declination (latitude) describe direction, not distance.
66
+ // To get the Earth-Sun distance in km, use astronomical data (mean ≈ 149,597,870.7 km).
67
+ // If you want the position vector in km, use the distance and direction:
68
+ const AU_KM = constants.AU_KM; // Astronomical Unit in kilometers
69
+ // AstroLibrary uses 149597900
70
+ const r = AU_KM; // Use actual Earth-Sun distance if available, otherwise mean AU
71
+
72
+ // Convert longitude and declination to a position vector in km
73
+ const x = r * Math.cos(sunDeclination * constants.DEG2RAD)
74
+ * Math.cos(sunLongitude * constants.DEG2RAD);
75
+ const y = r * Math.cos(sunDeclination * constants.DEG2RAD)
76
+ * Math.sin(sunLongitude * constants.DEG2RAD);
77
+ const z = r * Math.sin(sunDeclination * constants.DEG2RAD);
78
+
79
+ return {x, y, z, distance: r};
80
+ }
81
+
82
+ static geoBeltShadowIntersection(date) {
83
+ const [sunDeclination, sunLongitude] = sunPosAt(date);
84
+ // Calculate sun position
85
+ let deltaPS;
86
+ if (sunLongitude !== null && sunDeclination !== null) {
87
+ // convert sunLongitude and sunDeclination to distance in km
88
+ deltaPS = ShadowGEOCalculator._calculateSunDistance(
89
+ sunLongitude, sunDeclination)["distance"];
90
+ } else {
91
+ const time = new Date();
92
+ const [newSunDeclination, newSunLongitude] = sunPosAt(time);
93
+ const sunPositionData = ShadowGEOCalculator._calculateSunDistance(
94
+ newSunLongitude, newSunDeclination);
95
+ deltaPS = sunPositionData.distance;
96
+ }
97
+
98
+ // Calculate shadow geometry
99
+ const {Xu, alphaU} = ShadowGEOCalculator._calculateUmbraGeometry(deltaPS);
100
+ const {Xp, alphaP} = ShadowGEOCalculator._calculatePenumbraGeometry(deltaPS);
101
+
102
+ const geoRadius = constants.WGS84_EARTH_EQUATORIAL_RADIUS_KM + constants.GEO_ALTITUDE_KM;
103
+
104
+ // Calculate Shadow radii at GEO altitude
105
+ const umbraRadiusGEO = ShadowGEOCalculator._umbraRadiusDistance(geoRadius, Xu, alphaU);
106
+ const penumbraRadiusGEO = ShadowGEOCalculator._penumbraRadiusDistance(
107
+ geoRadius, Xp, alphaP);
108
+
109
+ // Calculate sun unit vector
110
+ const sunVector = ShadowGEOCalculator._sunUnitVector(sunLongitude, sunDeclination);
111
+
112
+ // Calculate shadow axis
113
+ const shadowAxis = [-sunVector.x, -sunVector.y, -sunVector.z];
114
+
115
+ // Finding shadow axis center in x, y, z coordinates
116
+
117
+ const shadowCenter = [
118
+ shadowAxis[0] * geoRadius,
119
+ shadowAxis[1] * geoRadius,
120
+ shadowAxis[2] * geoRadius,
121
+ ];
122
+
123
+ // Converting shadow axis to latitude and longitude
124
+ const shadowCenterLat = Math.asin(shadowCenter[2] / geoRadius) * constants.RAD2DEG;
125
+ const shadowCenterLon = Math.atan2(shadowCenter[1], shadowCenter[0]) * constants.RAD2DEG;
126
+
127
+ // Check if GEO belt intersects the shadow
128
+ const equatorialDistance = Math.abs(shadowCenter[2]); // Distance from the equator to the shadow center
129
+ const intersectsUmbra = umbraRadiusGEO > equatorialDistance;
130
+ const intersectsPenumbra = penumbraRadiusGEO > equatorialDistance;
131
+
132
+ const intersectionPoints = [];
133
+
134
+ if (intersectsPenumbra) {
135
+ // angle from the shadow center to the intersection point
136
+ const alpha = Math.sqrt(penumbraRadiusGEO**2 - equatorialDistance**2);
137
+ const theta = Math.atan2(alpha, geoRadius);
138
+
139
+ // Project shadow center onto the GEO belt
140
+ const shadowAxisEq = [shadowAxis[0], shadowAxis[1], 0];
141
+ const shadowAxisNorm = Math.sqrt(shadowAxisEq[0]**2 + shadowAxisEq[1]**2);
142
+ shadowAxisEq[0] /= shadowAxisNorm;
143
+ shadowAxisEq[1] /= shadowAxisNorm;
144
+
145
+ // Intersection points in the GEO belt
146
+ const centerLon = shadowCenterLon; // You already have this!
147
+
148
+ let lon1 = centerLon + theta * constants.RAD2DEG;
149
+ let lon2 = centerLon - theta * constants.RAD2DEG;
150
+
151
+ // Ensure longitudes are within -180 to 180 degrees
152
+ lon1 = wrapToRange(lon1, -180, 180);
153
+ lon2 = wrapToRange(lon2, -180, 180);
154
+
155
+ intersectionPoints.push({
156
+ type: "penumbra",
157
+ longitudeRange: [lon1, lon2],
158
+ latitude: 0, // Equatorial belt
159
+ });
160
+ }
161
+
162
+ if (intersectsUmbra) {
163
+ // angle from the shadow center to the intersection point
164
+ const alpha = Math.sqrt(umbraRadiusGEO**2 - equatorialDistance**2);
165
+ const theta = Math.atan2(alpha, geoRadius);
166
+
167
+ // Project shadow center onto the GEO belt
168
+ const shadowAxisEq = [shadowAxis[0], shadowAxis[1], 0];
169
+ const shadowAxisNorm = Math.sqrt(shadowAxisEq[0]**2 + shadowAxisEq[1]**2);
170
+ shadowAxisEq[0] /= shadowAxisNorm;
171
+ shadowAxisEq[1] /= shadowAxisNorm;
172
+
173
+ // Intersection points in the GEO belt
174
+ const centerLon = shadowCenterLon; // You already have this!
175
+
176
+ let lon1 = centerLon + theta * constants.RAD2DEG;
177
+ let lon2 = centerLon - theta * constants.RAD2DEG;
178
+
179
+ // Ensure longitudes are within -180 to 180 degrees
180
+ lon1 = ((lon1 + 180) % 360 + 360) % 360 - 180;
181
+ lon2 = ((lon2 + 180) % 360 + 360) % 360 - 180;
182
+
183
+ intersectionPoints.push({
184
+ type: "umbra",
185
+ longitudeRange: [lon1, lon2],
186
+ latitude: 0, // Equatorial belt
187
+ });
188
+ }
189
+
190
+ return {
191
+ shadowCenter: shadowCenter,
192
+ shadowCenterLat: shadowCenterLat,
193
+ shadowCenterLon: shadowCenterLon,
194
+ umbraRadiusGEO: umbraRadiusGEO,
195
+ penumbraRadiusGEO: penumbraRadiusGEO,
196
+ intersectionPoints: intersectionPoints,
197
+ };
198
+ }
199
+ }
200
+
201
+ module.exports = ShadowGEOCalculator;
@@ -0,0 +1,311 @@
1
+ // const {gstime} = require("satellite.js");
2
+
3
+ /**
4
+ * TimeConverter - Port from C# TimeConverter
5
+ * Handles conversions between different time systems (UTC, UT1, TAI, GPS, TT)
6
+ * and Julian date calculations
7
+ */
8
+ class TimeConverter {
9
+ // Constants from the C# implementation
10
+ static JULIAN_UTC = 2415018.5; // Julian Date of midnight, December 30, 1899
11
+ static JULIAN_MJD = 2400000.5; // = JD - MJD
12
+
13
+ // These values need to be changed periodically
14
+ // See http://www.stjarnhimlen.se/comp/time.html for bi-annual values (in seconds)
15
+ // See https://hpiers.obspm.fr/eoppc/eop/eopc04/eopc04_IAU2000.62-now for daily updated values
16
+ // As of 6/22/22:
17
+ static TAI_UTC = 37;
18
+ static GPS_UTC = 18;
19
+ static TT_UT1 = 69.29;
20
+ static UT1_UTC = -0.0977075;
21
+ static LOD = 0.0000022; // Length of day correction
22
+ static XP = 0.132949; // Polar motion X
23
+ static YP = 0.486774; // Polar motion Y
24
+
25
+ /**
26
+ * Constructor accepts either a UTC Date or a Julian date number
27
+ * @param {Date|number} input - Either a Date object (UTC) or a Julian date number
28
+ */
29
+ constructor(input) {
30
+ if (typeof input === "number") {
31
+ // Input is a Julian date
32
+ this._julian = input;
33
+ this._utc = null;
34
+ } else {
35
+ // Input is a Date object
36
+ this._utc = input instanceof Date ? input : new Date(input);
37
+ this._julian = null;
38
+ }
39
+ }
40
+
41
+ // Instance properties
42
+ get UTC() {
43
+ return this._utc || TimeConverter.julianToDateTime(this.JulianUTC);
44
+ }
45
+
46
+ get UT1() {
47
+ return TimeConverter.utcToUT1(this.UTC);
48
+ }
49
+
50
+ get TAI() {
51
+ return TimeConverter.utcToTAI(this.UTC);
52
+ }
53
+
54
+ get GPS() {
55
+ return TimeConverter.utcToGPS(this.UTC);
56
+ }
57
+
58
+ get TT() {
59
+ return TimeConverter.ut1ToTT(this.UT1);
60
+ }
61
+
62
+ get GMST() {
63
+ return TimeConverter.getGMST(this.JulianUT1);
64
+ }
65
+
66
+ get JulianUTC() {
67
+ return this._julian !== null ? this._julian : TimeConverter.dateTimeToJulian(this.UTC);
68
+ }
69
+
70
+ get JulianUT1() {
71
+ return TimeConverter.dateTimeToJulian(this.UT1);
72
+ }
73
+
74
+ get JulianTAI() {
75
+ return TimeConverter.dateTimeToJulian(this.TAI);
76
+ }
77
+
78
+ get JulianGPS() {
79
+ return TimeConverter.dateTimeToJulian(this.GPS);
80
+ }
81
+
82
+ get JulianTT() {
83
+ return TimeConverter.dateTimeToJulian(this.TT);
84
+ }
85
+
86
+ get JulianCenturiesTT() {
87
+ return TimeConverter.julianToCenturiesTxxx(this.JulianTT);
88
+ }
89
+
90
+ // Backwards compatibility property
91
+ get date() {
92
+ return this.UTC;
93
+ }
94
+
95
+ // Static factory methods
96
+ static FromJulianUTC(julian) {
97
+ return new TimeConverter(julian);
98
+ }
99
+
100
+ static FromGregorianUTC(date) {
101
+ return new TimeConverter(date);
102
+ }
103
+
104
+ /**
105
+ * Convert Julian date to JavaScript Date
106
+ * @param {number} julian - Julian date
107
+ * @return {Date} JavaScript Date object
108
+ */
109
+ static julianToDateTime(julian) {
110
+ // Using the C# formula: DateTime.FromOADate(julian - JULIAN_UTC)
111
+ // OLE Automation Date is days since Dec 30, 1899
112
+ const daysSince1899 = julian - TimeConverter.JULIAN_UTC;
113
+ const millisSince1899 = daysSince1899 * 86400000; // Convert days to milliseconds
114
+ const date1899 = new Date(Date.UTC(1899, 11, 30, 0, 0, 0, 0)); // Dec 30, 1899
115
+ return new Date(date1899.getTime() + millisSince1899);
116
+ }
117
+
118
+ /**
119
+ * Convert Date to Julian date
120
+ * @param {Date} date - JavaScript Date object
121
+ * @return {number} Julian date
122
+ */
123
+ static dateTimeToJulian(date) {
124
+ // Vallado Fundamentals, Algorithm 14 p. 183
125
+ const year = date.getUTCFullYear();
126
+ const month = date.getUTCMonth() + 1; // JavaScript months are 0-11
127
+ const day = date.getUTCDate();
128
+ const hour = date.getUTCHours();
129
+ const minute = date.getUTCMinutes();
130
+ const second = date.getUTCSeconds();
131
+ const millisecond = date.getUTCMilliseconds();
132
+
133
+ const jd = 367 * year - Math.floor(7 * (year + Math.floor((month + 9) / 12)) / 4)
134
+ + Math.floor(275 * month / 9) + day + 1721013.5
135
+ + (((second + millisecond * 1e-3) / 60 + minute) / 60 + hour) / 24;
136
+
137
+ return jd;
138
+ }
139
+
140
+ /**
141
+ * Convert Julian date to Modified Julian Date
142
+ * @param {number} julian - Julian date
143
+ * @return {number} Modified Julian Date
144
+ */
145
+ static JulianToMJD(julian) {
146
+ return julian - TimeConverter.JULIAN_MJD;
147
+ }
148
+
149
+ /**
150
+ * Convert Modified Julian Date to Julian date
151
+ * @param {number} mjd - Modified Julian Date
152
+ * @return {number} Julian date
153
+ */
154
+ static MJDToJulian(mjd) {
155
+ return mjd + TimeConverter.JULIAN_MJD;
156
+ }
157
+
158
+ /**
159
+ * Convert TAI to UTC
160
+ * @param {Date} tai - TAI time
161
+ * @return {Date} UTC time
162
+ */
163
+ static TAIToUTC(tai) {
164
+ const utcMillis = tai.getTime() - (TimeConverter.TAI_UTC * 1000);
165
+ return new Date(utcMillis);
166
+ }
167
+
168
+ /**
169
+ * Convert UTC to TAI
170
+ * @param {Date} utc - UTC time
171
+ * @return {Date} TAI time
172
+ */
173
+ static utcToTAI(utc) {
174
+ const taiMillis = utc.getTime() + (TimeConverter.TAI_UTC * 1000);
175
+ return new Date(taiMillis);
176
+ }
177
+
178
+ /**
179
+ * Convert GPS to UTC
180
+ * @param {Date} gps - GPS time
181
+ * @return {Date} UTC time
182
+ */
183
+ static GPSToUTC(gps) {
184
+ const utcMillis = gps.getTime() - (TimeConverter.GPS_UTC * 1000);
185
+ return new Date(utcMillis);
186
+ }
187
+
188
+ /**
189
+ * Convert UTC to GPS
190
+ * @param {Date} utc - UTC time
191
+ * @return {Date} GPS time
192
+ */
193
+ static utcToGPS(utc) {
194
+ const gpsMillis = utc.getTime() + (TimeConverter.GPS_UTC * 1000);
195
+ return new Date(gpsMillis);
196
+ }
197
+
198
+ /**
199
+ * Convert TT to UT1
200
+ * @param {Date} tt - Terrestrial Time
201
+ * @return {Date} UT1 time
202
+ */
203
+ static TTToUT1(tt) {
204
+ const ut1Millis = tt.getTime() - (TimeConverter.TT_UT1 * 1000);
205
+ return new Date(ut1Millis);
206
+ }
207
+
208
+ /**
209
+ * Convert UT1 to TT
210
+ * @param {Date} ut1 - UT1 time
211
+ * @return {Date} Terrestrial Time
212
+ */
213
+ static ut1ToTT(ut1) {
214
+ const ttMillis = ut1.getTime() + (TimeConverter.TT_UT1 * 1000);
215
+ return new Date(ttMillis);
216
+ }
217
+
218
+ /**
219
+ * Convert UT1 to UTC
220
+ * @param {Date} ut1 - UT1 time
221
+ * @return {Date} UTC time
222
+ */
223
+ static UT1ToUTC(ut1) {
224
+ const utcMillis = ut1.getTime() - (TimeConverter.UT1_UTC * 1000);
225
+ return new Date(utcMillis);
226
+ }
227
+
228
+ /**
229
+ * Convert UTC to UT1
230
+ * @param {Date} utc - UTC time
231
+ * @return {Date} UT1 time
232
+ */
233
+ static utcToUT1(utc) {
234
+ const ut1Millis = utc.getTime() + (TimeConverter.UT1_UTC * 1000);
235
+ return new Date(ut1Millis);
236
+ }
237
+
238
+ /**
239
+ * Get Greenwich Mean Sidereal Time from UT1 julian
240
+ * @param {number} jdut1 - Julian date UT1
241
+ * @param {boolean} wrap360 - Whether to wrap to 0-360 degrees
242
+ * @return {Object} Angle object with Radians property
243
+ */
244
+ static getGMST(jdut1, wrap360 = true) {
245
+ const tut1 = TimeConverter.julianToCenturiesTxxx(jdut1);
246
+ let gmst = -6.2e-6 * tut1 * tut1 * tut1 + 0.093104 * tut1 * tut1
247
+ + (876600.0 * 3600 + 8640184.812866) * tut1 + 67310.54841; // seconds
248
+ gmst = gmst * (Math.PI / 180) / 240.0 % (2.0 * Math.PI); // 360/86400 = 1/240, to deg, to rad
249
+
250
+ // Check quadrants
251
+ if (gmst < 0.0 && wrap360) {
252
+ gmst += 2.0 * Math.PI;
253
+ }
254
+
255
+ return {
256
+ Radians: gmst,
257
+ Degrees: gmst * 180 / Math.PI,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Get Local Mean Sidereal Time from longitude and UT1 julian
263
+ * @param {number} lonDeg - Longitude in degrees (-180 to 180)
264
+ * @param {number} jdut1 - Julian date UT1
265
+ * @param {boolean} wrap360 - Whether to wrap to 0-360 degrees
266
+ * @return {Object} Object containing lmst (radians) and gmst (radians)
267
+ */
268
+ static GetLMST(lonDeg, jdut1, wrap360 = true) {
269
+ const gmstAngle = TimeConverter.getGMST(jdut1, wrap360);
270
+ const gmst = gmstAngle.Radians;
271
+ let lst = lonDeg * Math.PI / 180 + gmst;
272
+
273
+ // Check quadrants
274
+ lst %= 2.0 * Math.PI;
275
+ if (lst < 0.0 && wrap360) {
276
+ lst += 2.0 * Math.PI;
277
+ }
278
+
279
+ return {
280
+ lmst: lst,
281
+ gmst: gmst,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Convert Julian date to centuries since J2000
287
+ * @param {number} julian - Julian date
288
+ * @return {number} Centuries since J2000
289
+ */
290
+ static julianToCenturiesTxxx(julian) {
291
+ return (julian - 2451545.0) / 36525.0;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * RaDec class for Right Ascension and Declination
297
+ */
298
+ class RaDec {
299
+ constructor(raDeg, decDeg) {
300
+ this.RightAscension = {
301
+ Degrees: raDeg,
302
+ Radians: raDeg * Math.PI / 180,
303
+ };
304
+ this.Declination = {
305
+ Degrees: decDeg,
306
+ Radians: decDeg * Math.PI / 180,
307
+ };
308
+ }
309
+ }
310
+
311
+ module.exports = {TimeConverter, RaDec};