@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.
@@ -1,208 +1,208 @@
1
- import {twoline2satrec,
2
- propagate,
3
- gstime,
4
- eciToGeodetic,
5
- degreesToRadians,
6
- radiansToDegrees,
7
- eciToEcf,
8
- ecfToLookAngles,
9
- degreesLong,
10
- degreesLat} from "satellite.js";
11
- import {distGeodetic} from "./astro.js";
12
- import {wrapToRange} from "./utils.js";
13
-
14
- const notamToCoordArray = (notam) => {
15
- const regexPatterns = [
16
- /(\d{4,6}[NS]|[N|S]\d{6})(\d{4,7}[EW]|[E|W]\d{7})/g,
17
- ];
18
-
19
- // clean up the NOTAM. Remove all spaces and unwanted characters and convert to upper case
20
- notam = notam
21
- .replace(/\s/g, "")
22
- .replace(/-/g, "")
23
- .replace(/\./g, "")
24
- .toUpperCase();
25
-
26
- const coordArray = [];
27
- let matchFound = false;
28
- let match;
29
- for (const regex of regexPatterns) {
30
- while ((match = regex.exec(notam)) !== null) {
31
- matchFound = true;
32
- // Depending on the pattern, you might need to adjust the indexing in convertToDecimal calls
33
- const lat = match[1];
34
- const lon = match[2];
35
- const decimalLat = convertToDecimal(lat);
36
- const decimalLon = convertToDecimal(lon);
37
- coordArray.push([decimalLat, decimalLon]);
38
- }
39
- if (matchFound) break; // Stop iterating through patterns if a match is found
40
- }
41
-
42
- return coordArray;
43
- };
44
-
45
- // Helper function to convert coordinate string to decimal
46
- const convertToDecimal = (coord) => {
47
- // Determine if the direction is at the beginning or the end
48
- const direction = coord.charAt(0).match(/[NSWE]/) ? coord.charAt(0) : coord.charAt(coord.length - 1);
49
- let degrees;
50
- let minutes;
51
- let seconds = 0; // Default to 0 for formats without seconds
52
-
53
- if (direction === coord.charAt(0)) { // Direction at the beginning
54
- coord = coord.substr(1); // Remove direction from coord for processing
55
- } else { // Direction at the end
56
- coord = coord.substr(0, coord.length - 1); // Remove direction from coord for processing
57
- }
58
-
59
- // Now, coord contains only the numeric part
60
- if (coord.length === 4) { // Format DDMM
61
- degrees = parseInt(coord.substr(0, 2), 10);
62
- minutes = parseInt(coord.substr(2, 2), 10);
63
- } else if (coord.length === 5) { // Format DDDMM
64
- degrees = parseInt(coord.substr(0, 3), 10);
65
- minutes = parseInt(coord.substr(3, 2), 10);
66
- } else if (coord.length === 6) { // Format DDMMSS
67
- degrees = parseInt(coord.substr(0, 2), 10);
68
- minutes = parseInt(coord.substr(2, 2), 10);
69
- seconds = parseInt(coord.substr(4, 2), 10);
70
- } else if (coord.length === 7) { // Format DDDMMSS
71
- degrees = parseInt(coord.substr(0, 3), 10);
72
- minutes = parseInt(coord.substr(3, 2), 10);
73
- seconds = parseInt(coord.substr(5, 2), 10);
74
- } else {
75
- return 0; // Invalid format
76
- }
77
-
78
- // Convert to decimal format
79
- let decimal = degrees + (minutes / 60) + (seconds / 3600);
80
- decimal = (direction === "S" || direction === "W") ? -decimal : decimal;
81
- return decimal;
82
- };
83
-
84
- /** Gets the bearing of an orbiting object relative to it's launch pad.
85
- * @param {String} line1 The first line of the TLE
86
- * @param {String} line2 The second line of the TLE
87
- * @param {Date} otop The OTOP time (object time over pad) in UTC
88
- * @param {Object} opop The OPOP object with the following properties:
89
- * - latitude: The geocentric latitude of the object in degrees
90
- * - longitude: The geocentric longitude of the object in degrees
91
- * @return {Number} The bearing of the object in degrees
92
- */
93
- const getBearingFromTle = (line1, line2, otop, opop) => {
94
- const satrec = twoline2satrec(line1, line2);
95
- const propTime = new Date(otop.getTime() + 10000);
96
- const propPos = eciToGeodetic(
97
- propagate(satrec, propTime).position,
98
- gstime(propTime),
99
- );
100
-
101
- const ascending = degreesToRadians(opop.latitude) < propPos.latitude;
102
-
103
- return getBearing(
104
- radiansToDegrees(satrec.inclo),
105
- opop.latitude,
106
- ascending,
107
- );
108
- };
109
-
110
- /**
111
- * Calculates the bearing angle given inclination, latitude, and whether the orbit is ascending.
112
- * @param {number} i - Inclination angle in degrees.
113
- * @param {number} lat - Latitude angle in degrees.
114
- * @param {boolean} ascending - Whether the orbit is ascending. Defaults to true.
115
- * @return {number} The azimuth angle in degrees, wrapped to [0, 360).
116
- */
117
- const getBearing = (i, lat, ascending = true) => {
118
- i = degreesToRadians(i);
119
- lat = degreesToRadians(lat);
120
- const azimuthRadians = Math.asin(Math.cos(i) / Math.cos(lat));
121
- let azimuthDegrees = radiansToDegrees(azimuthRadians);
122
-
123
- if (!ascending) {
124
- azimuthDegrees = 180 - azimuthDegrees;
125
- }
126
-
127
- return wrapToRange(azimuthDegrees, 0, 360);
128
- };
129
-
130
- /** Gets the OPOP (Object Position Over Pad) and OTOP (Object Time Over Pad) of an orbiting object relative to it's launch pad.
131
- * @param {String} line1 The first line of the TLE
132
- * @param {String} line2 The second line of the TLE
133
- * @param {Number} padLat The latitude of the launch pad in degrees
134
- * @param {Number} padLon The longitude of the launch pad in degrees
135
- * @param {Number} padAlt The altitude of the launch pad in km
136
- * @param {Date} launchTime The launch time of the object
137
- * @param {Number} flyoutTimeThresholdMin The flyout time threshold in minutes, defaults to 20 min
138
- * @param {Number} opopDistThresholdKm The OPOP distance threshold in km, defaults to 250 km
139
- * @param {Number} maxIterations The maximum number of iterations, defaults to 100
140
- * @return {Object} The OPOP object with the following properties:
141
- * - opopLat: The geocentric latitude of the object in degrees
142
- * - opopLon: The geocentric longitude of the object in degrees
143
- * - otop: The time of the object over the pad in UTC
144
- */
145
- const getOpopOtop = (
146
- line1,
147
- line2,
148
- padLat,
149
- padLon,
150
- padAlt,
151
- launchTime,
152
- flyoutTimeThresholdMin = 20,
153
- opopDistThresholdKm = 250,
154
- maxIterations = 100,
155
- ) => {
156
- const satrec = twoline2satrec(line1, line2);
157
-
158
- // iterate time to find the time with closest range
159
- let time = launchTime;
160
- let stepSize = 60000; // 1 minute
161
- let lastRange;
162
- const timeTreshold = 500; // half second
163
- for (let i = 0; i < maxIterations; i++) {
164
- const sv = propagate(satrec, time);
165
- const gmst = gstime(time);
166
- const satEcf = eciToEcf(sv.position, gmst);
167
- const padGd = {
168
- longitude: degreesToRadians(padLon),
169
- latitude: degreesToRadians(padLat),
170
- height: padAlt,
171
- };
172
- const range = ecfToLookAngles(padGd, satEcf).rangeSat;
173
- if (lastRange && lastRange < range) {
174
- // we went too far, half the step size and invert the sign
175
- stepSize = (-1 * stepSize) / 2;
176
- }
177
- time = new Date(time.getTime() + stepSize);
178
- lastRange = range;
179
-
180
- if (Math.abs(stepSize) < timeTreshold) {
181
- break;
182
- }
183
- }
184
-
185
- if (Math.abs(stepSize) >= timeTreshold) {
186
- throw new Error(`Failed to converge on OPOP after ${maxIterations} iterations`);
187
- }
188
- if (Math.abs(time.getTime() - launchTime.getTime()) > flyoutTimeThresholdMin * 60000) {
189
- throw new Error(`OTOP is more than ${flyoutTimeThresholdMin} minute(s) from launch`);
190
- }
191
-
192
- const sv = propagate(satrec, time);
193
- const opop = eciToGeodetic(sv.position, gstime(time));
194
- const opopLat = degreesLat(opop.latitude);
195
- const opopLon = degreesLong(opop.longitude);
196
-
197
- if (distGeodetic(opopLat, opopLon, padLat, padLon) > opopDistThresholdKm * 1000) {
198
- throw new Error(`OPOP is more than ${opopDistThresholdKm}km from launch pad`);
199
- }
200
-
201
- return {
202
- opopLat: opopLat,
203
- opopLon: opopLon,
204
- otop: time,
205
- };
206
- };
207
-
208
- export {notamToCoordArray, getBearingFromTle, getBearing, getOpopOtop};
1
+ import {twoline2satrec,
2
+ propagate,
3
+ gstime,
4
+ eciToGeodetic,
5
+ degreesToRadians,
6
+ radiansToDegrees,
7
+ eciToEcf,
8
+ ecfToLookAngles,
9
+ degreesLong,
10
+ degreesLat} from "satellite.js";
11
+ import {distGeodetic} from "./astro.js";
12
+ import {wrapToRange} from "./utils.js";
13
+
14
+ const notamToCoordArray = (notam) => {
15
+ const regexPatterns = [
16
+ /(\d{4,6}[NS]|[N|S]\d{6})(\d{4,7}[EW]|[E|W]\d{7})/g,
17
+ ];
18
+
19
+ // clean up the NOTAM. Remove all spaces and unwanted characters and convert to upper case
20
+ notam = notam
21
+ .replace(/\s/g, "")
22
+ .replace(/-/g, "")
23
+ .replace(/\./g, "")
24
+ .toUpperCase();
25
+
26
+ const coordArray = [];
27
+ let matchFound = false;
28
+ let match;
29
+ for (const regex of regexPatterns) {
30
+ while ((match = regex.exec(notam)) !== null) {
31
+ matchFound = true;
32
+ // Depending on the pattern, you might need to adjust the indexing in convertToDecimal calls
33
+ const lat = match[1];
34
+ const lon = match[2];
35
+ const decimalLat = convertToDecimal(lat);
36
+ const decimalLon = convertToDecimal(lon);
37
+ coordArray.push([decimalLat, decimalLon]);
38
+ }
39
+ if (matchFound) break; // Stop iterating through patterns if a match is found
40
+ }
41
+
42
+ return coordArray;
43
+ };
44
+
45
+ // Helper function to convert coordinate string to decimal
46
+ const convertToDecimal = (coord) => {
47
+ // Determine if the direction is at the beginning or the end
48
+ const direction = coord.charAt(0).match(/[NSWE]/) ? coord.charAt(0) : coord.charAt(coord.length - 1);
49
+ let degrees;
50
+ let minutes;
51
+ let seconds = 0; // Default to 0 for formats without seconds
52
+
53
+ if (direction === coord.charAt(0)) { // Direction at the beginning
54
+ coord = coord.substr(1); // Remove direction from coord for processing
55
+ } else { // Direction at the end
56
+ coord = coord.substr(0, coord.length - 1); // Remove direction from coord for processing
57
+ }
58
+
59
+ // Now, coord contains only the numeric part
60
+ if (coord.length === 4) { // Format DDMM
61
+ degrees = parseInt(coord.substr(0, 2), 10);
62
+ minutes = parseInt(coord.substr(2, 2), 10);
63
+ } else if (coord.length === 5) { // Format DDDMM
64
+ degrees = parseInt(coord.substr(0, 3), 10);
65
+ minutes = parseInt(coord.substr(3, 2), 10);
66
+ } else if (coord.length === 6) { // Format DDMMSS
67
+ degrees = parseInt(coord.substr(0, 2), 10);
68
+ minutes = parseInt(coord.substr(2, 2), 10);
69
+ seconds = parseInt(coord.substr(4, 2), 10);
70
+ } else if (coord.length === 7) { // Format DDDMMSS
71
+ degrees = parseInt(coord.substr(0, 3), 10);
72
+ minutes = parseInt(coord.substr(3, 2), 10);
73
+ seconds = parseInt(coord.substr(5, 2), 10);
74
+ } else {
75
+ return 0; // Invalid format
76
+ }
77
+
78
+ // Convert to decimal format
79
+ let decimal = degrees + (minutes / 60) + (seconds / 3600);
80
+ decimal = (direction === "S" || direction === "W") ? -decimal : decimal;
81
+ return decimal;
82
+ };
83
+
84
+ /** Gets the bearing of an orbiting object relative to it's launch pad.
85
+ * @param {String} line1 The first line of the TLE
86
+ * @param {String} line2 The second line of the TLE
87
+ * @param {Date} otop The OTOP time (object time over pad) in UTC
88
+ * @param {Object} opop The OPOP object with the following properties:
89
+ * - latitude: The geocentric latitude of the object in degrees
90
+ * - longitude: The geocentric longitude of the object in degrees
91
+ * @return {Number} The bearing of the object in degrees
92
+ */
93
+ const getBearingFromTle = (line1, line2, otop, opop) => {
94
+ const satrec = twoline2satrec(line1, line2);
95
+ const propTime = new Date(otop.getTime() + 10000);
96
+ const propPos = eciToGeodetic(
97
+ propagate(satrec, propTime).position,
98
+ gstime(propTime),
99
+ );
100
+
101
+ const ascending = degreesToRadians(opop.latitude) < propPos.latitude;
102
+
103
+ return getBearing(
104
+ radiansToDegrees(satrec.inclo),
105
+ opop.latitude,
106
+ ascending,
107
+ );
108
+ };
109
+
110
+ /**
111
+ * Calculates the bearing angle given inclination, latitude, and whether the orbit is ascending.
112
+ * @param {number} i - Inclination angle in degrees.
113
+ * @param {number} lat - Latitude angle in degrees.
114
+ * @param {boolean} ascending - Whether the orbit is ascending. Defaults to true.
115
+ * @return {number} The azimuth angle in degrees, wrapped to [0, 360).
116
+ */
117
+ const getBearing = (i, lat, ascending = true) => {
118
+ i = degreesToRadians(i);
119
+ lat = degreesToRadians(lat);
120
+ const azimuthRadians = Math.asin(Math.cos(i) / Math.cos(lat));
121
+ let azimuthDegrees = radiansToDegrees(azimuthRadians);
122
+
123
+ if (!ascending) {
124
+ azimuthDegrees = 180 - azimuthDegrees;
125
+ }
126
+
127
+ return wrapToRange(azimuthDegrees, 0, 360);
128
+ };
129
+
130
+ /** Gets the OPOP (Object Position Over Pad) and OTOP (Object Time Over Pad) of an orbiting object relative to it's launch pad.
131
+ * @param {String} line1 The first line of the TLE
132
+ * @param {String} line2 The second line of the TLE
133
+ * @param {Number} padLat The latitude of the launch pad in degrees
134
+ * @param {Number} padLon The longitude of the launch pad in degrees
135
+ * @param {Number} padAlt The altitude of the launch pad in km
136
+ * @param {Date} launchTime The launch time of the object
137
+ * @param {Number} flyoutTimeThresholdMin The flyout time threshold in minutes, defaults to 20 min
138
+ * @param {Number} opopDistThresholdKm The OPOP distance threshold in km, defaults to 250 km
139
+ * @param {Number} maxIterations The maximum number of iterations, defaults to 100
140
+ * @return {Object} The OPOP object with the following properties:
141
+ * - opopLat: The geocentric latitude of the object in degrees
142
+ * - opopLon: The geocentric longitude of the object in degrees
143
+ * - otop: The time of the object over the pad in UTC
144
+ */
145
+ const getOpopOtop = (
146
+ line1,
147
+ line2,
148
+ padLat,
149
+ padLon,
150
+ padAlt,
151
+ launchTime,
152
+ flyoutTimeThresholdMin = 20,
153
+ opopDistThresholdKm = 250,
154
+ maxIterations = 100,
155
+ ) => {
156
+ const satrec = twoline2satrec(line1, line2);
157
+
158
+ // iterate time to find the time with closest range
159
+ let time = launchTime;
160
+ let stepSize = 60000; // 1 minute
161
+ let lastRange;
162
+ const timeTreshold = 500; // half second
163
+ for (let i = 0; i < maxIterations; i++) {
164
+ const sv = propagate(satrec, time);
165
+ const gmst = gstime(time);
166
+ const satEcf = eciToEcf(sv.position, gmst);
167
+ const padGd = {
168
+ longitude: degreesToRadians(padLon),
169
+ latitude: degreesToRadians(padLat),
170
+ height: padAlt,
171
+ };
172
+ const range = ecfToLookAngles(padGd, satEcf).rangeSat;
173
+ if (lastRange && lastRange < range) {
174
+ // we went too far, half the step size and invert the sign
175
+ stepSize = (-1 * stepSize) / 2;
176
+ }
177
+ time = new Date(time.getTime() + stepSize);
178
+ lastRange = range;
179
+
180
+ if (Math.abs(stepSize) < timeTreshold) {
181
+ break;
182
+ }
183
+ }
184
+
185
+ if (Math.abs(stepSize) >= timeTreshold) {
186
+ throw new Error(`Failed to converge on OPOP after ${maxIterations} iterations`);
187
+ }
188
+ if (Math.abs(time.getTime() - launchTime.getTime()) > flyoutTimeThresholdMin * 60000) {
189
+ throw new Error(`OTOP is more than ${flyoutTimeThresholdMin} minute(s) from launch`);
190
+ }
191
+
192
+ const sv = propagate(satrec, time);
193
+ const opop = eciToGeodetic(sv.position, gstime(time));
194
+ const opopLat = degreesLat(opop.latitude);
195
+ const opopLon = degreesLong(opop.longitude);
196
+
197
+ if (distGeodetic(opopLat, opopLon, padLat, padLon) > opopDistThresholdKm * 1000) {
198
+ throw new Error(`OPOP is more than ${opopDistThresholdKm}km from launch pad`);
199
+ }
200
+
201
+ return {
202
+ opopLat: opopLat,
203
+ opopLon: opopLon,
204
+ otop: time,
205
+ };
206
+ };
207
+
208
+ export {notamToCoordArray, getBearingFromTle, getBearing, getOpopOtop};
@@ -1,98 +1,98 @@
1
- const winston = require("winston");
2
- const {
3
- createLogger,
4
- format,
5
- transports,
6
- } = winston;
7
- const cj = (data) => Object.keys(data).length > 0
8
- ? JSON.stringify(data, null, 2)
9
- : null;
10
-
11
- const isTest = process.env.NODE_ENV === "test";
12
-
13
- const defaultLogger = new transports.Console({
14
- silent: isTest,
15
- level: "warn",
16
- format: format.errors({stack: true}),
17
- timestamp: true,
18
- });
19
-
20
- const errorLogger = new transports.Console({
21
- silent: isTest,
22
- format: format.errors({stack: true}),
23
- });
24
-
25
- /**
26
- * Creates a logger
27
- *
28
- * @param {String} nameSpace Namespace for the logger
29
- * @param {Object} additionalData additional data to include with logging
30
- * @param {string} level Default logging level to set (overwrites environment setting)
31
- * @return {winston.Logger}
32
- */
33
- module.exports.loggerFactory = ({
34
- nameSpace = "saber",
35
- additionalData = {},
36
- level,
37
- } = {}) => {
38
- const logLevel = process.env.LOG_LEVEL || "warn";
39
- const isConsole = !!(process.env.CONSOLE_LOG);
40
-
41
- const loggerTransport = isConsole
42
- ? new transports.Console({
43
- defaultMeta: additionalData,
44
- level: level || logLevel,
45
- format: format.combine(
46
- format.errors({stack: true}),
47
- format.cli(),
48
- // eslint-disable-next-line max-len
49
- format.printf(({
50
- level,
51
- message,
52
- nameSpace,
53
- stack,
54
- ...rest
55
- }) =>
56
- [
57
- level,
58
- `[${nameSpace}]`,
59
- ":",
60
- message,
61
- stack,
62
- cj(rest),
63
- ].filter((value) => !!value)
64
- .join(" "),
65
- ),
66
- ),
67
- })
68
- : defaultLogger;
69
-
70
- const config = {
71
- levels: {
72
- // RFC5424
73
- emerg: 0,
74
- alert: 1,
75
- crit: 2,
76
- error: 3,
77
- warning: 4,
78
- notice: 5,
79
- info: 6,
80
- debug: 7,
81
- // npm levels
82
- warn: 4,
83
- verbose: 6,
84
- silly: 8,
85
- },
86
- exitOnError: false,
87
- transports: [loggerTransport],
88
- // Console logger from above will log errors
89
- exceptionHandlers: !isConsole ? [errorLogger] : [],
90
- rejectionHandlers: !isConsole ? [errorLogger] : [],
91
- };
92
-
93
- return createLogger(config)
94
- .child({
95
- ...additionalData,
96
- nameSpace: nameSpace,
97
- });
98
- }
1
+ const winston = require("winston");
2
+ const {
3
+ createLogger,
4
+ format,
5
+ transports,
6
+ } = winston;
7
+ const cj = (data) => Object.keys(data).length > 0
8
+ ? JSON.stringify(data, null, 2)
9
+ : null;
10
+
11
+ const isTest = process.env.NODE_ENV === "test";
12
+
13
+ const defaultLogger = new transports.Console({
14
+ silent: isTest,
15
+ level: "warn",
16
+ format: format.errors({stack: true}),
17
+ timestamp: true,
18
+ });
19
+
20
+ const errorLogger = new transports.Console({
21
+ silent: isTest,
22
+ format: format.errors({stack: true}),
23
+ });
24
+
25
+ /**
26
+ * Creates a logger
27
+ *
28
+ * @param {String} nameSpace Namespace for the logger
29
+ * @param {Object} additionalData additional data to include with logging
30
+ * @param {string} level Default logging level to set (overwrites environment setting)
31
+ * @return {winston.Logger}
32
+ */
33
+ module.exports.loggerFactory = ({
34
+ nameSpace = "saber",
35
+ additionalData = {},
36
+ level,
37
+ } = {}) => {
38
+ const logLevel = process.env.LOG_LEVEL || "warn";
39
+ const isConsole = !!(process.env.CONSOLE_LOG);
40
+
41
+ const loggerTransport = isConsole
42
+ ? new transports.Console({
43
+ defaultMeta: additionalData,
44
+ level: level || logLevel,
45
+ format: format.combine(
46
+ format.errors({stack: true}),
47
+ format.cli(),
48
+ // eslint-disable-next-line max-len
49
+ format.printf(({
50
+ level,
51
+ message,
52
+ nameSpace,
53
+ stack,
54
+ ...rest
55
+ }) =>
56
+ [
57
+ level,
58
+ `[${nameSpace}]`,
59
+ ":",
60
+ message,
61
+ stack,
62
+ cj(rest),
63
+ ].filter((value) => !!value)
64
+ .join(" "),
65
+ ),
66
+ ),
67
+ })
68
+ : defaultLogger;
69
+
70
+ const config = {
71
+ levels: {
72
+ // RFC5424
73
+ emerg: 0,
74
+ alert: 1,
75
+ crit: 2,
76
+ error: 3,
77
+ warning: 4,
78
+ notice: 5,
79
+ info: 6,
80
+ debug: 7,
81
+ // npm levels
82
+ warn: 4,
83
+ verbose: 6,
84
+ silly: 8,
85
+ },
86
+ exitOnError: false,
87
+ transports: [loggerTransport],
88
+ // Console logger from above will log errors
89
+ exceptionHandlers: !isConsole ? [errorLogger] : [],
90
+ rejectionHandlers: !isConsole ? [errorLogger] : [],
91
+ };
92
+
93
+ return createLogger(config)
94
+ .child({
95
+ ...additionalData,
96
+ nameSpace: nameSpace,
97
+ });
98
+ }