diff --git a/.cspell.json b/.cspell.json index f8710f7fc7..2bd524dbdd 100644 --- a/.cspell.json +++ b/.cspell.json @@ -489,7 +489,17 @@ "requestfinished", "LOCF", "Unack", - "Tabnabbing" + "Tabnabbing", + "elset", + "statevector", + "NAVSTAR", + "raan", + "sbirs", + "SBIRS", + "FENGYUN", + "STARLINK", + "norad", + "NORAD" ], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"], "ignorePaths": [ diff --git a/example/udlFederation/ConjunctionLimitProvider.js b/example/udlFederation/ConjunctionLimitProvider.js new file mode 100644 index 0000000000..241246e30e --- /dev/null +++ b/example/udlFederation/ConjunctionLimitProvider.js @@ -0,0 +1,87 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +// Screening thresholds commonly used for conjunction assessment triage. +const PC_CRITICAL = 1e-4; +const PC_WARNING = 1e-5; + +const LIMITS = { + critical: { + cssClass: 'is-limit--upr is-limit--red', + low: PC_CRITICAL, + high: Number.POSITIVE_INFINITY, + name: 'Pc Critical' + }, + warning: { + cssClass: 'is-limit--upr is-limit--yellow', + low: PC_WARNING, + high: PC_CRITICAL, + name: 'Pc Warning' + } +}; + +export default class ConjunctionLimitProvider { + supportsLimits(domainObject) { + return domainObject.type === 'udl.conjunctions'; + } + + getLimitEvaluator() { + return { + evaluate: function (datum, valueMetadata) { + if (!valueMetadata || valueMetadata.key !== 'pc') { + return undefined; + } + + if (datum.pc >= PC_CRITICAL) { + return LIMITS.critical; + } + + if (datum.pc >= PC_WARNING) { + return LIMITS.warning; + } + + return undefined; + } + }; + } + + getLimits() { + return { + limits: function () { + return Promise.resolve({ + WARNING: { + high: { + color: 'yellow', + pc: PC_WARNING + } + }, + CRITICAL: { + high: { + color: 'red', + pc: PC_CRITICAL + } + } + }); + } + }; + } +} diff --git a/example/udlFederation/UDLObjectProvider.js b/example/udlFederation/UDLObjectProvider.js new file mode 100644 index 0000000000..ac408f12fa --- /dev/null +++ b/example/udlFederation/UDLObjectProvider.js @@ -0,0 +1,129 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import { SATELLITES } from './satellites.js'; + +const EPHEMERIS_VALUES = [ + { + key: 'utc', + source: 'utc', + name: 'Timestamp', + format: 'utc', + hints: { domain: 1 } + }, + { + key: 'altitude', + name: 'Altitude', + unit: 'km', + formatString: '%0.2f', + hints: { range: 1 } + }, + { + key: 'latitude', + name: 'Latitude', + unit: 'deg', + formatString: '%0.4f', + hints: { range: 2 } + }, + { + key: 'longitude', + name: 'Longitude', + unit: 'deg', + formatString: '%0.4f', + hints: { range: 3 } + }, + { + key: 'velocity', + name: 'Velocity', + unit: 'km/s', + formatString: '%0.3f', + hints: { range: 4 } + } +]; + +const CONJUNCTION_VALUES = [ + { + key: 'utc', + source: 'utc', + name: 'Timestamp', + format: 'utc', + hints: { domain: 1 } + }, + { + key: 'pc', + name: 'Probability of Collision', + hints: { range: 1 } + }, + { + key: 'missDistance', + name: 'Miss Distance', + unit: 'km', + formatString: '%0.2f', + hints: { range: 2 } + }, + { + key: 'secondary', + name: 'Secondary Object', + format: 'string' + } +]; + +export default class UDLObjectProvider { + get(identifier) { + if (identifier.key === 'node') { + return Promise.resolve({ + identifier, + name: 'UDL Federation Node', + type: 'folder', + location: 'ROOT' + }); + } + + if (identifier.key === 'conjunctions') { + return Promise.resolve({ + identifier, + name: 'Conjunction Assessments', + type: 'udl.conjunctions', + location: 'udl:node', + telemetry: { + values: CONJUNCTION_VALUES + } + }); + } + + const satellite = SATELLITES.find((candidate) => candidate.key === identifier.key); + if (satellite === undefined) { + return Promise.reject(new Error(`Unknown UDL object: ${identifier.key}`)); + } + + return Promise.resolve({ + identifier, + name: satellite.name, + type: 'udl.ephemeris', + location: 'udl:node', + noradId: satellite.noradId, + telemetry: { + values: EPHEMERIS_VALUES + } + }); + } +} diff --git a/example/udlFederation/UDLTelemetryProvider.js b/example/udlFederation/UDLTelemetryProvider.js new file mode 100644 index 0000000000..d10beaa58b --- /dev/null +++ b/example/udlFederation/UDLTelemetryProvider.js @@ -0,0 +1,136 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import { EARTH_RADIUS_KM, MU_EARTH, SATELLITES } from './satellites.js'; + +const EPHEMERIS_PERIOD_MS = 1000; +const CONJUNCTION_PERIOD_MS = 5000; +const MAX_REQUEST_DATUMS = 50000; +const SECONDARY_OBJECTS = [ + 'COSMOS 2251 DEB', + 'FENGYUN 1C DEB', + 'SL-16 R/B', + 'STARLINK-30452', + 'IRIDIUM 33 DEB' +]; + +function ephemerisDatum(satellite, timestamp) { + const radiusKm = EARTH_RADIUS_KM + satellite.altitudeKm; + const periodSeconds = 2 * Math.PI * Math.sqrt(Math.pow(radiusKm, 3) / MU_EARTH); + const meanMotion = (2 * Math.PI) / periodSeconds; + const theta = meanMotion * (timestamp / 1000) + (satellite.phaseDeg * Math.PI) / 180; + const inclination = (satellite.inclinationDeg * Math.PI) / 180; + + const latitude = (Math.asin(Math.sin(inclination) * Math.sin(theta)) * 180) / Math.PI; + const earthRotationDeg = ((timestamp / 1000) * 360) / 86164; + const longitude = + (((((Math.atan2(Math.cos(inclination) * Math.sin(theta), Math.cos(theta)) * 180) / Math.PI + + satellite.raanDeg - + earthRotationDeg) % + 360) + + 540) % + 360) - + 180; + + return { + id: satellite.key, + utc: timestamp, + altitude: satellite.altitudeKm + Math.sin(theta * 3) * 2, + latitude, + longitude, + velocity: Math.sqrt(MU_EARTH / radiusKm) + }; +} + +function conjunctionDatum(timestamp) { + const slot = Math.floor(timestamp / CONJUNCTION_PERIOD_MS); + const wave = Math.abs(Math.sin(slot / 7)); + const pc = wave > 0.92 ? 0.0002 + wave / 2000 : wave / 50000; + + return { + id: 'conjunctions', + utc: timestamp, + pc: Number(pc.toPrecision(3)), + missDistance: Number((0.4 + (1 - wave) * 24).toFixed(2)), + secondary: SECONDARY_OBJECTS[slot % SECONDARY_OBJECTS.length] + }; +} + +export default class UDLTelemetryProvider { + supportsRequest(domainObject) { + return this.#isUDLTelemetry(domainObject); + } + + supportsSubscribe(domainObject) { + return this.#isUDLTelemetry(domainObject); + } + + request(domainObject, options) { + const period = + domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; + const size = Math.min(options.size ?? MAX_REQUEST_DATUMS, MAX_REQUEST_DATUMS); + const start = Math.floor(options.start / period) * period; + const requestedCount = Math.floor((options.end - start) / period) + 1; + const count = Math.min(requestedCount, size); + const data = []; + + if (options.strategy === 'latest') { + const alignedEnd = Math.floor(options.end / period) * period; + for (let i = count - 1; i >= 0; i--) { + data.push(this.#datumFor(domainObject, alignedEnd - i * period)); + } + } else { + const step = count > 1 ? (options.end - start) / (count - 1) : period; + for (let i = 0; i < count; i++) { + data.push(this.#datumFor(domainObject, start + Math.round(i * step))); + } + } + + return Promise.resolve(data); + } + + subscribe(domainObject, callback) { + const period = + domainObject.type === 'udl.conjunctions' ? CONJUNCTION_PERIOD_MS : EPHEMERIS_PERIOD_MS; + const interval = setInterval(() => { + callback(this.#datumFor(domainObject, Date.now())); + }, period); + + return function unsubscribe() { + clearInterval(interval); + }; + } + + #datumFor(domainObject, timestamp) { + if (domainObject.type === 'udl.conjunctions') { + return conjunctionDatum(timestamp); + } + + const satellite = SATELLITES.find((candidate) => candidate.key === domainObject.identifier.key); + + return ephemerisDatum(satellite, timestamp); + } + + #isUDLTelemetry(domainObject) { + return domainObject.type === 'udl.ephemeris' || domainObject.type === 'udl.conjunctions'; + } +} diff --git a/example/udlFederation/plugin.js b/example/udlFederation/plugin.js new file mode 100644 index 0000000000..b18a726419 --- /dev/null +++ b/example/udlFederation/plugin.js @@ -0,0 +1,72 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import ConjunctionLimitProvider from './ConjunctionLimitProvider.js'; +import { SATELLITES } from './satellites.js'; +import UDLObjectProvider from './UDLObjectProvider.js'; +import UDLTelemetryProvider from './UDLTelemetryProvider.js'; + +/** + * Simulated Unified Data Library (UDL) federation node. Exposes a + * constellation's ephemeris streams and a conjunction-assessment feed the way + * a UDL data-integration node would federate them into a mission-ops tool. + */ +export default function UDLFederationPlugin() { + return function install(openmct) { + openmct.types.addType('udl.ephemeris', { + name: 'UDL Ephemeris', + description: 'Satellite state vectors federated from a Unified Data Library node.', + cssClass: 'icon-telemetry' + }); + + openmct.types.addType('udl.conjunctions', { + name: 'UDL Conjunction Assessments', + description: + 'Conjunction data messages federated from a Unified Data Library node, with probability-of-collision screening limits.', + cssClass: 'icon-alert-triangle' + }); + + openmct.objects.addRoot({ + namespace: 'udl', + key: 'node' + }); + + openmct.objects.addProvider('udl', new UDLObjectProvider()); + + openmct.composition.addProvider({ + appliesTo: function (domainObject) { + return domainObject.identifier.namespace === 'udl' && domainObject.type === 'folder'; + }, + load: function () { + return Promise.resolve( + SATELLITES.map((satellite) => ({ + namespace: 'udl', + key: satellite.key + })).concat([{ namespace: 'udl', key: 'conjunctions' }]) + ); + } + }); + + openmct.telemetry.addProvider(new UDLTelemetryProvider()); + openmct.telemetry.addProvider(new ConjunctionLimitProvider()); + }; +} diff --git a/example/udlFederation/satellites.js b/example/udlFederation/satellites.js new file mode 100644 index 0000000000..024cf5c3cd --- /dev/null +++ b/example/udlFederation/satellites.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/** + * Simulated constellation for the UDL federation node. Orbital parameters are + * simplified two-body circular orbits, sufficient for a representative + * ephemeris stream (UDL `elset`/`statevector` analog). + */ +export const SATELLITES = [ + { + key: 'gps-iii-sv05', + name: 'GPS III SV05 (NAVSTAR 82)', + noradId: 48859, + altitudeKm: 20180, + inclinationDeg: 55, + raanDeg: 40, + phaseDeg: 0 + }, + { + key: 'wgs-11', + name: 'WGS-11', + noradId: 54800, + altitudeKm: 35786, + inclinationDeg: 0.02, + raanDeg: 0, + phaseDeg: 130 + }, + { + key: 'sbirs-geo-6', + name: 'SBIRS GEO-6 (USA 336)', + noradId: 53355, + altitudeKm: 35786, + inclinationDeg: 1.4, + raanDeg: 0, + phaseDeg: 245 + } +]; + +export const EARTH_RADIUS_KM = 6371; +export const MU_EARTH = 398600.4418; // km^3/s^2 diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index 938bc51c09..62f383d41f 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -28,6 +28,7 @@ import ExampleUser from '../../example/exampleUser/plugin.js'; import ExampleFaultSource from '../../example/faultManagement/exampleFaultSource.js'; import GeneratorPlugin from '../../example/generator/plugin.js'; import ExampleImagery from '../../example/imagery/plugin.js'; +import UDLFederationPlugin from '../../example/udlFederation/plugin.js'; import AutoflowPlugin from './autoflow/AutoflowTabularPlugin.js'; import BarChartPlugin from './charts/bar/plugin.js'; import ScatterPlotPlugin from './charts/scatter/plugin.js'; @@ -105,6 +106,7 @@ plugins.example.ExampleDataVisualizationSourcePlugin = ExampleDataVisualizationS plugins.example.ExampleTags = ExampleTags; plugins.example.Generator = () => GeneratorPlugin; plugins.example.ExampleStaleness = ExampleStaleness; +plugins.example.UDLFederation = UDLFederationPlugin; plugins.UTCTimeSystem = UTCTimeSystem; plugins.LocalTimeSystem = LocalTimeSystem;