@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,19 @@
1
+ const _ = require("lodash");
2
+ const {resolve4} = require("dns").promises;
3
+
4
+ const checkRecord = (hostName) => resolve4(hostName).
5
+ then((addresss) => !_.isEmpty(addresss)).
6
+ catch(_.stubFalse);
7
+
8
+
9
+ module.exports.checkRecord = checkRecord;
10
+ module.exports.checkNetwork = (domains) => Promise.all(
11
+ _.map(
12
+ domains,
13
+ (hostName) => checkRecord(hostName).
14
+ then((found) => found
15
+ ? hostName
16
+ : null,
17
+ ),
18
+ ),
19
+ ).then((resolved) => _.flow(_.compact, _.first)(resolved) || null);
@@ -0,0 +1,33 @@
1
+ // Helper math constants
2
+ module.exports.DEG2RAD = Math.PI / 180;
3
+ module.exports.RAD2DEG = 180 / Math.PI;
4
+ module.exports.SEC2RAD = Math.PI / (180.0 * 3600.0); // seconds to radians conversion
5
+ module.exports.ARCSEC2RAD = 1 / (3600 * 180 / Math.PI); // arcseconds to radians conversion (1 arcsec = 1/3600 deg = 1/3600 * π/180 rad)
6
+ module.exports.MILLIS_PER_DAY = 24 * 60 * 60 * 1000; // Number of milliseconds in a day
7
+
8
+ // Sun Constants
9
+ module.exports.SUN_RADIUS_KM = 695701.0; // Sun radius in kilometers
10
+ module.exports.AU_KM = 149597870.7; // Astronomical Unit in kilometers
11
+
12
+ // Earth Constants
13
+ module.exports.MU = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
14
+ module.exports.GRAV_CONST = 6.6743e-11; // N⋅m2⋅kg−2
15
+ module.exports.EARTH_MASS = 5.97219e24; // kg
16
+ module.exports.WGS72_EARTH_EQUATORIAL_RADIUS_KM = 6378.135; // in km. Use this when calculations are done with SGP4 which uses WGS72 assumptions.
17
+ module.exports.WGS84_EARTH_EQUATORIAL_RADIUS_KM = 6378.137; // in km. Use this for general calculations.
18
+ module.exports.EARTH_RADIUS_KM = 6378.135; // still in use for backwards compatibility with old code. Has been removed from nps, ingest, node-common. TODO: refactor from all other js projects using node-common.
19
+ module.exports.GEO_ALTITUDE_KM = 35786; // Km
20
+
21
+ module.exports.REGIMES = {
22
+ Undetermined: 1,
23
+ Leo: 2,
24
+ Heo: 4,
25
+ GeoInclined: 8,
26
+ Meo: 16,
27
+ Molniya: 32,
28
+ Sso: 64,
29
+ Polar: 128,
30
+ GeoStationary: 256,
31
+ GeoDrifter: 512,
32
+ };
33
+
package/src/fixDate.js ADDED
@@ -0,0 +1,69 @@
1
+ const {isValid, parseISO, format} = require("date-fns");
2
+
3
+ /**
4
+ * Formats a date how MySQL wants it.
5
+ * Note: We chop off the timezone, otherwise the date-fns converts it into your local timezone, when we want UTC
6
+ * @param {string} date in ISO format used by the UDL
7
+ * @return {string} date formatted for MySQL
8
+ */
9
+ const fixDate = (date) => isValid(parseISO(date))
10
+ ? format(parseISO(date.replace("Z", "")), "yyyy-MM-dd HH:mm:ss")
11
+ : null;
12
+
13
+ /**
14
+ * Converts a Javascript date into a MySQL formatted date string
15
+ * @param {Date} date
16
+ * @return {string} Mysql formatted date string
17
+ */
18
+ const dateToMySqlDate = (date) => new Date(date).toISOString().slice(0, 19).replace("T", " ");
19
+
20
+ /**
21
+ * Converts string, int, or Date input into a Date. Adds Z to end of string if not present
22
+ * @param {*} date
23
+ * @return {Date} date
24
+ */
25
+ const parseDate = (date) => {
26
+ const corrected = (typeof date !== "string" || date.endsWith("Z")) ? date : (date+"Z");
27
+ return new Date(corrected);
28
+ };
29
+
30
+ /**
31
+ * Checks if MySql date-time, if so appends UTC timezone and convets to JS date-time object.
32
+ * If not, assumes timezone is specified in date-time string and converts to JS date-time object.
33
+ * @param {String} dt date time string
34
+ * @return {Date} Date object in UTC
35
+ */
36
+ const dtStrtoJsDt = (dt) => {
37
+ if (isSqlDate(dt)) {
38
+ // MySql date-time format
39
+ return mySqlDateToJs(dt);
40
+ }
41
+ return new Date(dt);
42
+ };
43
+
44
+ /** Converts MySql date-time to JS date-time
45
+ * @param {String} dt MySql date-time format must be yyyy-mm-dd hh:mm:ss
46
+ * @return {Date} JS date-time
47
+ */
48
+ const mySqlDateToJs = (dt) => {
49
+ // check the date is in MySQL format
50
+ if (isSqlDate(dt)) {
51
+ return new Date(dt+"Z");
52
+ } else {
53
+ throw new Error("Invalid MySql date format");
54
+ }
55
+ };
56
+
57
+ /** Checks if a Date is a valid mySQL date, with microseconds precision (i.e. up to 6 digits).
58
+ * @param {String} dt The date string to test
59
+ * @return {Boolean} True if is SQL date, false otherwise
60
+ */
61
+ const isSqlDate = (dt) => dt.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?$/);
62
+
63
+ module.exports = {
64
+ fixDate: fixDate,
65
+ parseDate: parseDate,
66
+ dateToMySqlDate: dateToMySqlDate,
67
+ mySqlDateToJs: mySqlDateToJs,
68
+ dtStrtoJsDt: dtStrtoJsDt,
69
+ };
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ ...require("./loggerFactory.js"),
3
+ ...require("./transform.js"),
4
+ ...require("./checkNetwork.js"),
5
+ ...require("./fixDate.js"),
6
+ ...require("./astro.js"),
7
+ ...require("./launchNominal.js"),
8
+ ...require("./LaunchNominalClass.js"),
9
+ ...require("./OrbitUtils.js"),
10
+ udl: require("./udl"),
11
+ };
@@ -0,0 +1,215 @@
1
+ const {
2
+ twoline2satrec,
3
+ propagate,
4
+ gstime,
5
+ eciToGeodetic,
6
+ degreesToRadians,
7
+ radiansToDegrees,
8
+ eciToEcf,
9
+ ecfToLookAngles,
10
+ degreesLong,
11
+ degreesLat,
12
+ } = require("satellite.js");
13
+ const {distGeodetic} = require("./astro.js");
14
+ const {
15
+ wrapToRange,
16
+ } = require("./utils.js");
17
+
18
+ const notamToCoordArray = (notam) => {
19
+ const regexPatterns = [
20
+ /(\d{4,6}[NS]|[N|S]\d{6})(\d{4,7}[EW]|[E|W]\d{7})/g,
21
+ ];
22
+
23
+ // clean up the NOTAM. Remove all spaces and unwanted characters and convert to upper case
24
+ notam = notam
25
+ .replace(/\s/g, "")
26
+ .replace(/-/g, "")
27
+ .replace(/\./g, "")
28
+ .toUpperCase();
29
+
30
+ const coordArray = [];
31
+ let matchFound = false;
32
+ let match;
33
+ for (const regex of regexPatterns) {
34
+ while ((match = regex.exec(notam)) !== null) {
35
+ matchFound = true;
36
+ // Depending on the pattern, you might need to adjust the indexing in convertToDecimal calls
37
+ const lat = match[1];
38
+ const lon = match[2];
39
+ const decimalLat = convertToDecimal(lat);
40
+ const decimalLon = convertToDecimal(lon);
41
+ coordArray.push([decimalLat, decimalLon]);
42
+ }
43
+ if (matchFound) break; // Stop iterating through patterns if a match is found
44
+ }
45
+
46
+ return coordArray;
47
+ };
48
+
49
+ // Helper function to convert coordinate string to decimal
50
+ const convertToDecimal = (coord) => {
51
+ // Determine if the direction is at the beginning or the end
52
+ const direction = coord.charAt(0).match(/[NSWE]/) ? coord.charAt(0) : coord.charAt(coord.length - 1);
53
+ let degrees;
54
+ let minutes;
55
+ let seconds = 0; // Default to 0 for formats without seconds
56
+
57
+ if (direction === coord.charAt(0)) { // Direction at the beginning
58
+ coord = coord.substr(1); // Remove direction from coord for processing
59
+ } else { // Direction at the end
60
+ coord = coord.substr(0, coord.length - 1); // Remove direction from coord for processing
61
+ }
62
+
63
+ // Now, coord contains only the numeric part
64
+ if (coord.length === 4) { // Format DDMM
65
+ degrees = parseInt(coord.substr(0, 2), 10);
66
+ minutes = parseInt(coord.substr(2, 2), 10);
67
+ } else if (coord.length === 5) { // Format DDDMM
68
+ degrees = parseInt(coord.substr(0, 3), 10);
69
+ minutes = parseInt(coord.substr(3, 2), 10);
70
+ } else if (coord.length === 6) { // Format DDMMSS
71
+ degrees = parseInt(coord.substr(0, 2), 10);
72
+ minutes = parseInt(coord.substr(2, 2), 10);
73
+ seconds = parseInt(coord.substr(4, 2), 10);
74
+ } else if (coord.length === 7) { // Format DDDMMSS
75
+ degrees = parseInt(coord.substr(0, 3), 10);
76
+ minutes = parseInt(coord.substr(3, 2), 10);
77
+ seconds = parseInt(coord.substr(5, 2), 10);
78
+ } else {
79
+ return 0; // Invalid format
80
+ }
81
+
82
+ // Convert to decimal format
83
+ let decimal = degrees + (minutes / 60) + (seconds / 3600);
84
+ decimal = (direction === "S" || direction === "W") ? -decimal : decimal;
85
+ return decimal;
86
+ };
87
+
88
+ /** Gets the bearing of an orbiting object relative to it's launch pad.
89
+ * @param {String} line1 The first line of the TLE
90
+ * @param {String} line2 The second line of the TLE
91
+ * @param {Date} otop The OTOP time (object time over pad) in UTC
92
+ * @param {Object} opop The OPOP object with the following properties:
93
+ * - latitude: The geocentric latitude of the object in degrees
94
+ * - longitude: The geocentric longitude of the object in degrees
95
+ * @return {Number} The bearing of the object in degrees
96
+ */
97
+ const getBearingFromTle = (line1, line2, otop, opop) => {
98
+ const satrec = twoline2satrec(line1, line2);
99
+ const propTime = new Date(otop.getTime() + 10000);
100
+ const propPos = eciToGeodetic(
101
+ propagate(satrec, propTime).position,
102
+ gstime(propTime),
103
+ );
104
+
105
+ const ascending = degreesToRadians(opop.latitude) < propPos.latitude;
106
+
107
+ return getBearing(
108
+ radiansToDegrees(satrec.inclo),
109
+ opop.latitude,
110
+ ascending,
111
+ );
112
+ };
113
+
114
+ /**
115
+ * Calculates the bearing angle given inclination, latitude, and whether the orbit is ascending.
116
+ * @param {number} i - Inclination angle in degrees.
117
+ * @param {number} lat - Latitude angle in degrees.
118
+ * @param {boolean} ascending - Whether the orbit is ascending. Defaults to true.
119
+ * @return {number} The azimuth angle in degrees, wrapped to [0, 360).
120
+ */
121
+ const getBearing = (i, lat, ascending = true) => {
122
+ i = degreesToRadians(i);
123
+ lat = degreesToRadians(lat);
124
+ const azimuthRadians = Math.asin(Math.cos(i) / Math.cos(lat));
125
+ let azimuthDegrees = radiansToDegrees(azimuthRadians);
126
+
127
+ if (!ascending) {
128
+ azimuthDegrees = 180 - azimuthDegrees;
129
+ }
130
+
131
+ return wrapToRange(azimuthDegrees, 0, 360);
132
+ };
133
+
134
+ /** Gets the OPOP (Object Position Over Pad) and OTOP (Object Time Over Pad) of an orbiting object relative to it's launch pad.
135
+ * @param {String} line1 The first line of the TLE
136
+ * @param {String} line2 The second line of the TLE
137
+ * @param {Number} padLat The latitude of the launch pad in degrees
138
+ * @param {Number} padLon The longitude of the launch pad in degrees
139
+ * @param {Number} padAlt The altitude of the launch pad in km
140
+ * @param {Date} launchTime The launch time of the object
141
+ * @param {Number} flyoutTimeThresholdMin The flyout time threshold in minutes, defaults to 20 min
142
+ * @param {Number} opopDistThresholdKm The OPOP distance threshold in km, defaults to 250 km
143
+ * @param {Number} maxIterations The maximum number of iterations, defaults to 100
144
+ * @return {Object} The OPOP object with the following properties:
145
+ * - opopLat: The geocentric latitude of the object in degrees
146
+ * - opopLon: The geocentric longitude of the object in degrees
147
+ * - otop: The time of the object over the pad in UTC
148
+ */
149
+ const getOpopOtop = (
150
+ line1,
151
+ line2,
152
+ padLat,
153
+ padLon,
154
+ padAlt,
155
+ launchTime,
156
+ flyoutTimeThresholdMin = 20,
157
+ opopDistThresholdKm = 250,
158
+ maxIterations = 100,
159
+ ) => {
160
+ const satrec = twoline2satrec(line1, line2);
161
+
162
+ // iterate time to find the time with closest range
163
+ let time = launchTime;
164
+ let stepSize = 60000; // 1 minute
165
+ let lastRange;
166
+ const timeTreshold = 500; // half second
167
+ for (let i = 0; i < maxIterations; i++) {
168
+ const sv = propagate(satrec, time);
169
+ const gmst = gstime(time);
170
+ const satEcf = eciToEcf(sv.position, gmst);
171
+ const padGd = {
172
+ longitude: degreesToRadians(padLon),
173
+ latitude: degreesToRadians(padLat),
174
+ height: padAlt,
175
+ };
176
+ const range = ecfToLookAngles(padGd, satEcf).rangeSat;
177
+ if (lastRange && lastRange < range) {
178
+ // we went too far, half the step size and invert the sign
179
+ stepSize = (-1 * stepSize) / 2;
180
+ }
181
+ time = new Date(time.getTime() + stepSize);
182
+ lastRange = range;
183
+
184
+ if (Math.abs(stepSize) < timeTreshold) {
185
+ break;
186
+ }
187
+ }
188
+
189
+ if (Math.abs(stepSize) >= timeTreshold) {
190
+ throw new Error(`Failed to converge on OPOP after ${maxIterations} iterations`);
191
+ }
192
+ if (Math.abs(time.getTime() - launchTime.getTime()) > flyoutTimeThresholdMin * 60000) {
193
+ throw new Error(`OTOP is more than ${flyoutTimeThresholdMin} minute(s) from launch`);
194
+ }
195
+
196
+ const sv = propagate(satrec, time);
197
+ const opop = eciToGeodetic(sv.position, gstime(time));
198
+ const opopLat = degreesLat(opop.latitude);
199
+ const opopLon = degreesLong(opop.longitude);
200
+
201
+ if (distGeodetic(opopLat, opopLon, padLat, padLon) > opopDistThresholdKm * 1000) {
202
+ throw new Error(`OPOP is more than ${opopDistThresholdKm}km from launch pad`);
203
+ }
204
+
205
+ return {
206
+ opopLat: opopLat,
207
+ opopLon: opopLon,
208
+ otop: time,
209
+ };
210
+ };
211
+
212
+ module.exports.notamToCoordArray = notamToCoordArray;
213
+ module.exports.getBearingFromTle = getBearingFromTle;
214
+ module.exports.getBearing = getBearing;
215
+ module.exports.getOpopOtop = getOpopOtop;
@@ -0,0 +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
+ };
package/src/s3.js ADDED
@@ -0,0 +1,59 @@
1
+ const {S3Client, PutObjectCommand, HeadObjectCommand} = require("@aws-sdk/client-s3");
2
+ const {readFileSync} = require("fs");
3
+ const {basename} = require("path");
4
+ const dotenv = require("dotenv");
5
+ dotenv.config();
6
+
7
+ const s3Client = new S3Client({
8
+ region: "us-west-1", // Replace with your AWS region
9
+ credentials: {
10
+ accessKeyId: process.env.S3_ACCESS_KEY,
11
+ secretAccessKey: process.env.S3_SECRET,
12
+ },
13
+ });
14
+
15
+
16
+ /**
17
+ * Takes either a filename, or a buffer. If array buffer is supplied, then filename is just used to name the file.
18
+ * If no array buffer is supplied, it will attempt to read the file at the fileName path.
19
+ * Example Usage:
20
+ * await s3Upload("imageName.png", "image/png", imageBuffer);
21
+ * await s3Upload("/path/to/local/image.png");
22
+ * @param {string} fileName
23
+ * @param {string} type
24
+ * @param {Array|Buffer|null} buffer
25
+ * @return {Promise<Object>}
26
+ */
27
+ module.exports.s3Upload = async (fileName, type = "image/png", buffer = null) => {
28
+ try {
29
+ const fileContent = buffer ? buffer : readFileSync(fileName);
30
+ return await s3Client.send(new PutObjectCommand({
31
+ Bucket: "saber-probe-images", // S3 Bucket name
32
+ Key: buffer ? fileName : basename(fileName), // File name to save as in S3
33
+ Body: fileContent, // File content (either buffer or file read)
34
+ ContentType: type, // File content type
35
+ }));
36
+ } catch (err) {
37
+ console.error("Error uploading file or buffer to S3: ", err);
38
+ throw err; // Re-throw the error so it can be caught by the calling function if needed
39
+ }
40
+ };
41
+
42
+ module.exports.doesS3ObjectExist = async (fileName) => {
43
+ try {
44
+ // Try to fetch the metadata of the object to check if it exists
45
+ await s3Client.send(new HeadObjectCommand({
46
+ Bucket: "saber-probe-images",
47
+ Key: fileName, // The key (file name) to check in the S3 bucket
48
+ }));
49
+ return true;
50
+ } catch (err) {
51
+ // If a 404 error is thrown, the object does not exist
52
+ if (err.name === "NotFound") {
53
+ return false;
54
+ }
55
+ // Other errors (such as permission errors) should be thrown
56
+ console.error("Error checking if file exists in S3: ", err);
57
+ throw err;
58
+ }
59
+ };
@@ -0,0 +1,40 @@
1
+ const _ = require("lodash");
2
+
3
+ const transformObjectKeys = (transform, object, deep = true) => _.cond([
4
+ [
5
+ (transformItem) => _.isPlainObject(transformItem),
6
+ (transformItem) => _.fromPairs(
7
+ _.map(
8
+ _.toPairs(transformItem),
9
+ ([key, value]) => [
10
+ transform(key),
11
+ deep && _.isPlainObject(value)
12
+ ? transformObjectKeys(transform, value)
13
+ : value,
14
+ ],
15
+ ),
16
+ ),
17
+ ],
18
+ [_.stubTrue, _.identity],
19
+ ])(object);
20
+
21
+ const pascalCase = (string) => _.upperFirst(_.camelCase(string));
22
+
23
+ const lowerCaseObjectKeys = (transformItem, deep) => transformObjectKeys(
24
+ _.toLower,
25
+ transformItem,
26
+ deep,
27
+ );
28
+
29
+ const pascalCaseObjectKeys = (transformItem, deep) => transformObjectKeys(
30
+ pascalCase,
31
+ transformItem,
32
+ deep,
33
+ );
34
+
35
+ module.exports = {
36
+ transformObjectKeys,
37
+ lowerCaseObjectKeys,
38
+ pascalCaseObjectKeys,
39
+ pascalCase,
40
+ };
package/src/udl.js ADDED
@@ -0,0 +1,115 @@
1
+ const {fixDate} = require("./fixDate.js");
2
+ const {calcRegime, getElsetUdlFromTle, getLonAndDrift, getRaanPrecession} = require("./astro.js");
3
+ const {lowerCaseObjectKeys} = require("./transform.js");
4
+ const {isDefined} = require("./utils.js");
5
+ const _ = require("lodash");
6
+
7
+ const udlToNpsElset = (udlRow) => {
8
+ let derivedElset;
9
+ try {
10
+ derivedElset = getElsetUdlFromTle(udlRow.line1, udlRow.line2, udlRow.dataMode);
11
+ } catch {
12
+ // invalid TLE
13
+ derivedElset = null;
14
+ }
15
+ const enrichedUdlFields = [];
16
+ const enrichField = (field) => {
17
+ if (derivedElset && (derivedElset[field] || derivedElset[field] === 0)) {
18
+ enrichedUdlFields.push(field);
19
+ return derivedElset[field];
20
+ }
21
+ return null;
22
+ };
23
+ const lonAndDrift = getLonAndDrift(udlRow.line1, udlRow.line2);
24
+
25
+ const getElsetValue = (value, defaultValueFunc) => {
26
+ return (isDefined(value)) ? value : defaultValueFunc();
27
+ };
28
+
29
+ const npsElset = lowerCaseObjectKeys({
30
+ Satno: getElsetValue(udlRow.satNo, ()=>enrichField("satNo")),
31
+ Epoch: fixDate(getElsetValue(udlRow.epoch, ()=>enrichField("epoch"))),
32
+ MeanMotion: getElsetValue(udlRow.meanMotion, ()=>enrichField("meanMotion")),
33
+ Eccentricity: getElsetValue(udlRow.eccentricity, ()=>enrichField("eccentricity")),
34
+ Inclination: getElsetValue(udlRow.inclination, ()=>enrichField("inclination")),
35
+ Raan: getElsetValue(udlRow.raan, ()=>enrichField("raan")),
36
+ ArgOfPerigee: getElsetValue(udlRow.argOfPerigee, ()=>enrichField("argOfPerigee")),
37
+ MeanAnomaly: getElsetValue(udlRow.meanAnomaly, ()=>enrichField("meanAnomaly")),
38
+ BStar: getElsetValue(udlRow.bStar, ()=>enrichField("bStar")),
39
+ SemiMajorAxis: getElsetValue(udlRow.semiMajorAxis, ()=>enrichField("semiMajorAxis")),
40
+ Period: getElsetValue(udlRow.period, ()=>enrichField("period")),
41
+ Apogee: getElsetValue(udlRow.apogee, ()=>enrichField("apogee")),
42
+ Perigee: getElsetValue(udlRow.perigee, ()=>enrichField("perigee")),
43
+ Line1: getElsetValue(udlRow.line1, ()=>null),
44
+ Line2: getElsetValue(udlRow.line2, ()=>null),
45
+ Source: getElsetValue(udlRow.source, ()=>null),
46
+ DataMode: getElsetValue(udlRow.dataMode, ()=>null),
47
+ CreatedAt: fixDate(getElsetValue(udlRow.createdAt, ()=>null)),
48
+ Origin: getElsetValue(udlRow.origin, ()=>null),
49
+ Algorithm: getElsetValue(udlRow.algorithm, ()=>null),
50
+ Latitude: lonAndDrift.latitude,
51
+ Longitude: lonAndDrift.longitude,
52
+ LonDriftDegreesPerDay: lonAndDrift.lonDriftDegreesPerDay,
53
+ RaanPrecessionDegreesPerDay: getRaanPrecession(udlRow.line1, udlRow.line2),
54
+ // Ensure we reset all the derived calcs so we know if they have been updated by Sentinel or not.
55
+ MinEccentricity: 0,
56
+ MaxEccentricity: 0,
57
+ MinInclination: 0,
58
+ MaxInclination: 0,
59
+ MinRaan: 0,
60
+ MaxRaan: 0,
61
+ MinArgOfPerigee: 0,
62
+ MaxArgOfPerigee: 0,
63
+ MinSemiMajorAxis: 0,
64
+ MaxSemiMajorAxis: 0,
65
+ });
66
+ npsElset.regime = calcRegime(npsElset);
67
+ npsElset.enrichedudlfields = enrichedUdlFields.join(",");
68
+ return npsElset;
69
+ };
70
+
71
+ const udlToNpsGroundSite = (udlRow) => ({
72
+ SensorId: _.get(udlRow, "idSensor", null),
73
+ Number: _.get(udlRow, "sensorNumber", null),
74
+ Name: _.get(udlRow, "sensorName", null),
75
+ Source: _.get(udlRow, "source", null),
76
+ Description: null,
77
+ LastObsTime: _.get(udlRow, "sensorStats[0].lastObTime", null),
78
+ CountryCode: _.get(udlRow, "entity.countryCode", "None"),
79
+ Taskable: _.get(udlRow, "entity.taskable", 0),
80
+ Type: _.get(udlRow, "sensorType.id", 0),
81
+ Latitude: _.get(udlRow, "entity.location.lat", 0),
82
+ Longitude: _.get(udlRow, "entity.location.lon", 0),
83
+ KmAboveSeaLevel: _.get(udlRow, "entity.location.altitude", 0),
84
+ MinRangeKm: _.get(udlRow, "sensorcharacteristics[0].minRangeLimit", null),
85
+ MaxRangeKm: _.get(udlRow, "sensorcharacteristics[0].maxRangeLimit", null),
86
+ ClassificationLevel: _.get(
87
+ udlRow,
88
+ "sensorcharacteristics[0].classificationMarking",
89
+ "",
90
+ ),
91
+ createdAt: fixDate(_.get(udlRow, "createdAt")),
92
+ });
93
+
94
+ /**
95
+ * Formats the response data based on the topic
96
+ *
97
+ * @param {String} topic
98
+ * @param {Object[]} udlData
99
+ * @return {Object[]}
100
+ */
101
+ module.exports.formatUdlData = (topic, udlData) => {
102
+ switch (topic) {
103
+ case "Elsets":
104
+ return _.map(udlData, udlToNpsElset);
105
+
106
+ case "GroundSites":
107
+ return _.map(udlData, udlToNpsGroundSite);
108
+
109
+ default:
110
+ return udlData;
111
+ }
112
+ };
113
+
114
+ module.exports.udlToNpsElset = udlToNpsElset;
115
+ module.exports.udlToNpsGroundSite = udlToNpsGroundSite;