From a79cffed63c46b133d8b80cea67c29accc27eb5e Mon Sep 17 00:00:00 2001 From: BusinessPlatformAutomation <61543952+BusinessPlatformAutomation@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:30:57 +0100 Subject: [PATCH] Add NASA plugin Adds a new SquaredUp plugin for NASA's open data APIs, covering near-Earth asteroid close approaches (NeoWs), space weather alerts (DONKI), tracked satellites (Satellite Situation Center), and the Astronomy Picture of the Day (APOD). - Near-Earth Object: asteroids with a close approach in the current 7-day window, with summary and close-approach-history data streams - Space Weather Notification: DONKI's unified alert feed (solar flares, CMEs, geomagnetic storms, etc.) from the last 30 days - Satellite: ~84 actively tracked spacecraft from NASA's Satellite Situation Center, with a position-over-time data stream (GSE X/Y/Z) built against SSC's unusual Java-serialized JSON format - APOD: today's astronomy picture plus a date-range gallery stream, kept as dashboard-only content (no natural entity to index/drill into) EONET (natural events) was scoped in but dropped: NASA's EONET service mislabels JSON responses with an XML content-type on ~90%+ of requests, which crashes this plugin's response parser with no available workaround. Documented as a known limitation. Co-Authored-By: Claude Sonnet 5 --- plugins/NASA/v1/configValidation.json | 11 + plugins/NASA/v1/custom_types.json | 23 ++ plugins/NASA/v1/dataStreams/apodGallery.json | 37 +++ plugins/NASA/v1/dataStreams/apodToday.json | 28 ++ .../v1/dataStreams/donkiNotifications.json | 53 ++++ .../v1/dataStreams/neoCloseApproaches.json | 41 +++ plugins/NASA/v1/dataStreams/neoFeed.json | 69 +++++ plugins/NASA/v1/dataStreams/neoSummary.json | 50 ++++ .../v1/dataStreams/satelliteLocation.json | 44 +++ plugins/NASA/v1/dataStreams/satellites.json | 33 +++ .../NASA/v1/dataStreams/scripts/neoFeed.js | 32 +++ .../NASA/v1/dataStreams/scripts/neoSummary.js | 15 + .../dataStreams/scripts/satelliteLocation.js | 53 ++++ .../NASA/v1/dataStreams/scripts/satellites.js | 37 +++ plugins/NASA/v1/defaultContent/manifest.json | 7 + .../defaultContent/nearEarthObject.dash.json | 101 +++++++ .../NASA/v1/defaultContent/overview.dash.json | 263 ++++++++++++++++++ plugins/NASA/v1/defaultContent/scopes.json | 26 ++ .../spaceWeatherNotification.dash.json | 48 ++++ plugins/NASA/v1/docs/README.md | 38 +++ plugins/NASA/v1/icon.svg | 54 ++++ plugins/NASA/v1/indexDefinitions/default.json | 48 ++++ plugins/NASA/v1/metadata.json | 49 ++++ plugins/NASA/v1/ui.json | 10 + 24 files changed, 1170 insertions(+) create mode 100644 plugins/NASA/v1/configValidation.json create mode 100644 plugins/NASA/v1/custom_types.json create mode 100644 plugins/NASA/v1/dataStreams/apodGallery.json create mode 100644 plugins/NASA/v1/dataStreams/apodToday.json create mode 100644 plugins/NASA/v1/dataStreams/donkiNotifications.json create mode 100644 plugins/NASA/v1/dataStreams/neoCloseApproaches.json create mode 100644 plugins/NASA/v1/dataStreams/neoFeed.json create mode 100644 plugins/NASA/v1/dataStreams/neoSummary.json create mode 100644 plugins/NASA/v1/dataStreams/satelliteLocation.json create mode 100644 plugins/NASA/v1/dataStreams/satellites.json create mode 100644 plugins/NASA/v1/dataStreams/scripts/neoFeed.js create mode 100644 plugins/NASA/v1/dataStreams/scripts/neoSummary.js create mode 100644 plugins/NASA/v1/dataStreams/scripts/satelliteLocation.js create mode 100644 plugins/NASA/v1/dataStreams/scripts/satellites.js create mode 100644 plugins/NASA/v1/defaultContent/manifest.json create mode 100644 plugins/NASA/v1/defaultContent/nearEarthObject.dash.json create mode 100644 plugins/NASA/v1/defaultContent/overview.dash.json create mode 100644 plugins/NASA/v1/defaultContent/scopes.json create mode 100644 plugins/NASA/v1/defaultContent/spaceWeatherNotification.dash.json create mode 100644 plugins/NASA/v1/docs/README.md create mode 100644 plugins/NASA/v1/icon.svg create mode 100644 plugins/NASA/v1/indexDefinitions/default.json create mode 100644 plugins/NASA/v1/metadata.json create mode 100644 plugins/NASA/v1/ui.json diff --git a/plugins/NASA/v1/configValidation.json b/plugins/NASA/v1/configValidation.json new file mode 100644 index 0000000..13a2ab6 --- /dev/null +++ b/plugins/NASA/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Connect to NASA's API", + "dataStream": { "name": "apodToday" }, + "required": true, + "error": "Could not authenticate with NASA's API. Check your API key at api.nasa.gov — DEMO_KEY is shared and may be rate-limited.", + "success": "Connected to NASA's API successfully." + } + ] +} diff --git a/plugins/NASA/v1/custom_types.json b/plugins/NASA/v1/custom_types.json new file mode 100644 index 0000000..a42f1e3 --- /dev/null +++ b/plugins/NASA/v1/custom_types.json @@ -0,0 +1,23 @@ +[ + { + "name": "Near-Earth Object", + "sourceType": "Near-Earth Object", + "icon": "meteor", + "singular": "Near-Earth Object", + "plural": "Near-Earth Objects" + }, + { + "name": "Space Weather Notification", + "sourceType": "Space Weather Notification", + "icon": "sun", + "singular": "Space Weather Notification", + "plural": "Space Weather Notifications" + }, + { + "name": "Satellite", + "sourceType": "Satellite", + "icon": "satellite", + "singular": "Satellite", + "plural": "Satellites" + } +] diff --git a/plugins/NASA/v1/dataStreams/apodGallery.json b/plugins/NASA/v1/dataStreams/apodGallery.json new file mode 100644 index 0000000..8b1f509 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/apodGallery.json @@ -0,0 +1,37 @@ +{ + "name": "apodGallery", + "displayName": "Astronomy Picture of the Day Gallery", + "description": "Astronomy pictures of the day over a date range", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/planetary/apod", + "getArgs": [ + { + "key": "start_date", + "value": "{{new Date(timeframe.start).toISOString().split('T')[0]}}" + }, + { + "key": "end_date", + "value": "{{new Date(timeframe.end).toISOString().split('T')[0]}}" + } + ] + }, + "matches": "none", + "metadata": [ + { "name": "title", "displayName": "Title", "shape": "string", "role": "label" }, + { "name": "date", "displayName": "Date", "shape": "date", "role": "timestamp" }, + { "name": "explanation", "displayName": "Explanation", "shape": "string" }, + { "name": "media_type", "displayName": "Media Type", "shape": "string" }, + { "name": "url", "displayName": "Media URL", "shape": ["url", { "label": "View" }] }, + { + "name": "hdurl", + "displayName": "HD Image URL", + "shape": ["url", { "label": "View HD" }], + "visible": false + }, + { "name": "copyright", "displayName": "Copyright", "shape": "string", "visible": false } + ], + "timeframes": ["last7days", "last30days", "thisMonth", "lastMonth", "thisQuarter", "thisYear"] +} diff --git a/plugins/NASA/v1/dataStreams/apodToday.json b/plugins/NASA/v1/dataStreams/apodToday.json new file mode 100644 index 0000000..303f02d --- /dev/null +++ b/plugins/NASA/v1/dataStreams/apodToday.json @@ -0,0 +1,28 @@ +{ + "name": "apodToday", + "displayName": "Astronomy Picture of the Day", + "description": "The current featured astronomy image or video, with its explanation", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/planetary/apod", + "expandInnerObjects": true + }, + "matches": "none", + "metadata": [ + { "name": "title", "displayName": "Title", "shape": "string", "role": "label" }, + { "name": "date", "displayName": "Date", "shape": "date", "role": "timestamp" }, + { "name": "explanation", "displayName": "Explanation", "shape": "string" }, + { "name": "media_type", "displayName": "Media Type", "shape": "string" }, + { "name": "url", "displayName": "Media URL", "shape": ["url", { "label": "View" }] }, + { + "name": "hdurl", + "displayName": "HD Image URL", + "shape": ["url", { "label": "View HD" }], + "visible": false + }, + { "name": "copyright", "displayName": "Copyright", "shape": "string", "visible": false } + ], + "timeframes": false +} diff --git a/plugins/NASA/v1/dataStreams/donkiNotifications.json b/plugins/NASA/v1/dataStreams/donkiNotifications.json new file mode 100644 index 0000000..b924431 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/donkiNotifications.json @@ -0,0 +1,53 @@ +{ + "name": "donkiNotifications", + "displayName": "Space Weather Notifications", + "description": "Space weather alerts from the last 30 days, covering solar flares, coronal mass ejections, geomagnetic storms and other event types", + "tags": ["Astronomy", "Alerts"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/DONKI/notifications", + "getArgs": [ + { "key": "type", "value": "all" }, + { + "key": "startDate", + "value": "{{new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}}" + }, + { "key": "endDate", "value": "{{new Date().toISOString().split('T')[0]}}" } + ] + }, + "matches": "none", + "metadata": [ + { "name": "messageID", "displayName": "Message ID", "shape": "string", "role": "id" }, + { "name": "messageType", "displayName": "Type", "shape": "string" }, + { + "name": "messageIssueTime", + "displayName": "Issued", + "shape": "date", + "role": "timestamp" + }, + { + "name": "messageURL", + "displayName": "Details", + "shape": ["url", { "label": "View" }], + "visible": false + }, + { "name": "messageBody", "displayName": "Message Body", "shape": "string", "visible": false }, + { + "name": "title", + "displayName": "Notification", + "computed": true, + "valueExpression": "{{ $['messageType'] + ' — ' + $['messageIssueTime'].split('T')[0] }}", + "shape": "string", + "role": "label" + }, + { + "name": "summary", + "displayName": "Summary", + "computed": true, + "valueExpression": "{{ $['messageBody'].substring(0, 300) }}", + "shape": "string" + } + ], + "timeframes": false +} diff --git a/plugins/NASA/v1/dataStreams/neoCloseApproaches.json b/plugins/NASA/v1/dataStreams/neoCloseApproaches.json new file mode 100644 index 0000000..f7b275c --- /dev/null +++ b/plugins/NASA/v1/dataStreams/neoCloseApproaches.json @@ -0,0 +1,41 @@ +{ + "name": "neoCloseApproaches", + "displayName": "Near-Earth Object Close Approaches", + "description": "All known close approaches to Earth (and other bodies) for a near-Earth asteroid, past and future", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/neo/rest/v1/neo/{{object.rawId}}", + "pathToData": "close_approach_data", + "expandInnerObjects": true + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Near-Earth Object"] } }, + "metadata": [ + { + "name": "close_approach_date", + "displayName": "Close Approach Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "close_approach_date_full", + "displayName": "Close Approach Date (Full)", + "shape": "string", + "visible": false + }, + { "name": "orbiting_body", "displayName": "Orbiting Body", "shape": "string" }, + { + "name": "miss_distance.kilometers", + "displayName": "Miss Distance (km)", + "shape": ["number", { "thousandsSeparator": true }] + }, + { + "name": "relative_velocity.kilometers_per_hour", + "displayName": "Relative Velocity (km/h)", + "shape": ["number", { "thousandsSeparator": true }] + }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/NASA/v1/dataStreams/neoFeed.json b/plugins/NASA/v1/dataStreams/neoFeed.json new file mode 100644 index 0000000..0920b7d --- /dev/null +++ b/plugins/NASA/v1/dataStreams/neoFeed.json @@ -0,0 +1,69 @@ +{ + "name": "neoFeed", + "displayName": "Near-Earth Object Close Approaches", + "description": "Asteroids with a close approach to Earth in the current 7-day window, one row per asteroid", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/neo/rest/v1/feed", + "getArgs": [ + { "key": "start_date", "value": "{{new Date().toISOString().split('T')[0]}}" }, + { + "key": "end_date", + "value": "{{new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}}" + } + ], + "postRequestScript": "neoFeed.js" + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "Asteroid ID", "shape": "string", "role": "id" }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "close_approach_date", + "displayName": "Close Approach Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "miss_distance_km", + "displayName": "Miss Distance (km)", + "shape": ["number", { "thousandsSeparator": true }] + }, + { + "name": "relative_velocity_kph", + "displayName": "Relative Velocity (km/h)", + "shape": ["number", { "thousandsSeparator": true }] + }, + { + "name": "is_potentially_hazardous_asteroid", + "displayName": "Potentially Hazardous", + "shape": "boolean" + }, + { "name": "is_sentry_object", "displayName": "Sentry Object", "shape": "boolean" }, + { + "name": "absolute_magnitude_h", + "displayName": "Absolute Magnitude (H)", + "shape": ["number", { "decimalPlaces": 2 }] + }, + { + "name": "estimated_diameter_min_km", + "displayName": "Est. Diameter Min (km)", + "shape": ["number", { "decimalPlaces": 3 }] + }, + { + "name": "estimated_diameter_max_km", + "displayName": "Est. Diameter Max (km)", + "shape": ["number", { "decimalPlaces": 3 }] + }, + { "name": "orbiting_body", "displayName": "Orbiting Body", "shape": "string" }, + { + "name": "nasa_jpl_url", + "displayName": "JPL Details", + "shape": ["url", { "label": "View" }], + "visible": false + } + ], + "timeframes": false +} diff --git a/plugins/NASA/v1/dataStreams/neoSummary.json b/plugins/NASA/v1/dataStreams/neoSummary.json new file mode 100644 index 0000000..8761abc --- /dev/null +++ b/plugins/NASA/v1/dataStreams/neoSummary.json @@ -0,0 +1,50 @@ +{ + "name": "neoSummary", + "displayName": "Near-Earth Object Summary", + "description": "Current attributes of a near-Earth asteroid, including size, hazard classification and Sentry monitoring status", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/neo/rest/v1/neo/{{object.rawId}}", + "postRequestScript": "neoSummary.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Near-Earth Object"] } }, + "metadata": [ + { "name": "id", "displayName": "Asteroid ID", "shape": "string", "visible": false }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "absolute_magnitude_h", + "displayName": "Absolute Magnitude (H)", + "shape": ["number", { "decimalPlaces": 2 }] + }, + { + "name": "is_potentially_hazardous_asteroid", + "displayName": "Potentially Hazardous", + "shape": "boolean" + }, + { "name": "is_sentry_object", "displayName": "Sentry Object", "shape": "boolean" }, + { + "name": "nasa_jpl_url", + "displayName": "JPL Details", + "shape": ["url", { "label": "View" }], + "visible": false + }, + { + "name": "estimated_diameter_min_km", + "displayName": "Est. Diameter Min (km)", + "shape": ["number", { "decimalPlaces": 3 }] + }, + { + "name": "estimated_diameter_max_km", + "displayName": "Est. Diameter Max (km)", + "shape": ["number", { "decimalPlaces": 3 }] + }, + { + "name": "close_approach_count", + "displayName": "Close Approach Count", + "shape": "number" + } + ], + "timeframes": false +} diff --git a/plugins/NASA/v1/dataStreams/satelliteLocation.json b/plugins/NASA/v1/dataStreams/satelliteLocation.json new file mode 100644 index 0000000..c014124 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/satelliteLocation.json @@ -0,0 +1,44 @@ +{ + "name": "satelliteLocation", + "displayName": "Satellite Location", + "description": "GSE coordinate position of a satellite over time", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "baseUrl": "https://sscweb.gsfc.nasa.gov/WS/sscr/2", + "httpMethod": "post", + "endpointPath": "/locations", + "headers": [ + { "key": "Accept", "value": "application/json" }, + { "key": "Content-Type", "value": "application/json" } + ], + "postBody": "[\"gov.nasa.gsfc.sscweb.schema.DataRequest\", {\"TimeInterval\": [\"gov.nasa.gsfc.sscweb.schema.TimeInterval\", {\"Start\": [\"javax.xml.datatype.XMLGregorianCalendar\", \"{{timeframe.start}}\"], \"End\": [\"javax.xml.datatype.XMLGregorianCalendar\", \"{{timeframe.end}}\"]}], \"Satellites\": [\"java.util.ArrayList\", [[\"gov.nasa.gsfc.sscweb.schema.SatelliteSpecification\", {\"Id\": \"{{object.rawId}}\", \"ResolutionFactor\": 1}]]], \"OutputOptions\": [\"gov.nasa.gsfc.sscweb.schema.OutputOptions\", {\"AllLocationFilters\": false, \"CoordinateOptions\": [\"java.util.ArrayList\", [[\"gov.nasa.gsfc.sscweb.schema.FilteredCoordinateOptions\", {\"CoordinateSystem\": \"GSE\", \"Component\": \"X\"}], [\"gov.nasa.gsfc.sscweb.schema.FilteredCoordinateOptions\", {\"CoordinateSystem\": \"GSE\", \"Component\": \"Y\"}], [\"gov.nasa.gsfc.sscweb.schema.FilteredCoordinateOptions\", {\"CoordinateSystem\": \"GSE\", \"Component\": \"Z\"}]]], \"MinMaxPoints\": 2}]}]", + "postRequestScript": "satelliteLocation.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Satellite"] } }, + "metadata": [ + { "name": "time", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { + "name": "x", + "displayName": "X (km)", + "shape": ["number", { "decimalPlaces": 2 }] + }, + { + "name": "y", + "displayName": "Y (km)", + "shape": ["number", { "decimalPlaces": 2 }] + }, + { + "name": "z", + "displayName": "Z (km)", + "shape": ["number", { "decimalPlaces": 2 }] + }, + { + "name": "coordinateSystem", + "displayName": "Coordinate System", + "shape": "string", + "visible": false + } + ], + "timeframes": ["last1hour", "last12hours", "last24hours", "last7days"] +} diff --git a/plugins/NASA/v1/dataStreams/satellites.json b/plugins/NASA/v1/dataStreams/satellites.json new file mode 100644 index 0000000..33e5e1c --- /dev/null +++ b/plugins/NASA/v1/dataStreams/satellites.json @@ -0,0 +1,33 @@ +{ + "name": "satellites", + "displayName": "Actively Tracked Satellites", + "description": "Satellites and spacecraft currently tracked by NASA's Satellite Situation Center", + "tags": ["Astronomy"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "baseUrl": "https://sscweb.gsfc.nasa.gov/WS/sscr/2", + "httpMethod": "get", + "endpointPath": "/observatories", + "headers": [{ "key": "Accept", "value": "application/json" }], + "postRequestScript": "satellites.js" + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "SSC ID", "shape": "string", "visible": false }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "resolution", + "displayName": "Native Resolution (s)", + "shape": "number" + }, + { "name": "startTime", "displayName": "Tracking Start", "shape": "date" }, + { "name": "endTime", "displayName": "Tracking End", "shape": "date" }, + { + "name": "resourceId", + "displayName": "SPASE Resource ID", + "shape": "string", + "visible": false + } + ], + "timeframes": false +} diff --git a/plugins/NASA/v1/dataStreams/scripts/neoFeed.js b/plugins/NASA/v1/dataStreams/scripts/neoFeed.js new file mode 100644 index 0000000..d5e6120 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/scripts/neoFeed.js @@ -0,0 +1,32 @@ +const byDate = data.near_earth_objects || {}; +const seen = new Set(); +const rows = []; + +for (const dateKey of Object.keys(byDate)) { + for (const neo of byDate[dateKey] || []) { + if (seen.has(neo.id)) continue; + seen.add(neo.id); + + const approach = (neo.close_approach_data || [])[0] || {}; + const diameter = (neo.estimated_diameter || {}).kilometers || {}; + + rows.push({ + id: neo.id, + name: neo.name, + absolute_magnitude_h: neo.absolute_magnitude_h, + is_potentially_hazardous_asteroid: neo.is_potentially_hazardous_asteroid, + is_sentry_object: neo.is_sentry_object, + nasa_jpl_url: neo.nasa_jpl_url, + estimated_diameter_min_km: diameter.estimated_diameter_min, + estimated_diameter_max_km: diameter.estimated_diameter_max, + close_approach_date: approach.close_approach_date, + miss_distance_km: approach.miss_distance ? Number(approach.miss_distance.kilometers) : null, + relative_velocity_kph: approach.relative_velocity + ? Number(approach.relative_velocity.kilometers_per_hour) + : null, + orbiting_body: approach.orbiting_body, + }); + } +} + +result = rows; diff --git a/plugins/NASA/v1/dataStreams/scripts/neoSummary.js b/plugins/NASA/v1/dataStreams/scripts/neoSummary.js new file mode 100644 index 0000000..3ae4df6 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/scripts/neoSummary.js @@ -0,0 +1,15 @@ +const diameter = ((data || {}).estimated_diameter || {}).kilometers || {}; + +result = [ + { + id: data.id, + name: data.name, + absolute_magnitude_h: data.absolute_magnitude_h, + is_potentially_hazardous_asteroid: data.is_potentially_hazardous_asteroid, + is_sentry_object: data.is_sentry_object, + nasa_jpl_url: data.nasa_jpl_url, + estimated_diameter_min_km: diameter.estimated_diameter_min, + estimated_diameter_max_km: diameter.estimated_diameter_max, + close_approach_count: (data.close_approach_data || []).length, + }, +]; diff --git a/plugins/NASA/v1/dataStreams/scripts/satelliteLocation.js b/plugins/NASA/v1/dataStreams/scripts/satelliteLocation.js new file mode 100644 index 0000000..0a9e008 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/scripts/satelliteLocation.js @@ -0,0 +1,53 @@ +// SSC wraps every JSON value as ["fully.qualified.ClassName", payload] (and arrays as +// ["java.util.ArrayList", [...]]), recursively. Strip that wrapper down to plain JSON. +function unwrap(node) { + if (Array.isArray(node)) { + if (node.length === 2 && typeof node[0] === "string" && node[0].indexOf(".") !== -1) { + return unwrap(node[1]); + } + return node.map(unwrap); + } + if (node && typeof node === "object") { + const out = {}; + for (const key of Object.keys(node)) { + out[key] = unwrap(node[key]); + } + return out; + } + return node; +} + +const root = unwrap(data); +const dataResult = (root && root.Result) || {}; + +// SSC returns HTTP 200 even on error - the real status lives in the body. +if (dataResult.StatusCode !== "SUCCESS") { + result = []; +} else { + const satellites = dataResult.Data || []; + const rows = []; + + for (const sat of satellites) { + const coordBlock = (sat.Coordinates || [])[0]; + if (!coordBlock) { + continue; + } + + const times = sat.Time || []; + const xs = coordBlock.X || []; + const ys = coordBlock.Y || []; + const zs = coordBlock.Z || []; + + for (let i = 0; i < times.length; i++) { + rows.push({ + time: times[i], + x: xs[i], + y: ys[i], + z: zs[i], + coordinateSystem: coordBlock.CoordinateSystem || "", + }); + } + } + + result = rows; +} diff --git a/plugins/NASA/v1/dataStreams/scripts/satellites.js b/plugins/NASA/v1/dataStreams/scripts/satellites.js new file mode 100644 index 0000000..b9cc660 --- /dev/null +++ b/plugins/NASA/v1/dataStreams/scripts/satellites.js @@ -0,0 +1,37 @@ +// SSC wraps every JSON value as ["fully.qualified.ClassName", payload] (and arrays as +// ["java.util.ArrayList", [...]]), recursively. Strip that wrapper down to plain JSON. +function unwrap(node) { + if (Array.isArray(node)) { + if (node.length === 2 && typeof node[0] === "string" && node[0].indexOf(".") !== -1) { + return unwrap(node[1]); + } + return node.map(unwrap); + } + if (node && typeof node === "object") { + const out = {}; + for (const key of Object.keys(node)) { + out[key] = unwrap(node[key]); + } + return out; + } + return node; +} + +const root = unwrap(data); +const observatories = root.Observatory || []; +const now = Date.now(); + +result = observatories + .filter(function (o) { + return o.EndTime && new Date(o.EndTime).getTime() > now; + }) + .map(function (o) { + return { + id: o.Id, + name: o.Name, + resolution: o.Resolution, + startTime: o.StartTime, + endTime: o.EndTime, + resourceId: o.ResourceId || "", + }; + }); diff --git a/plugins/NASA/v1/defaultContent/manifest.json b/plugins/NASA/v1/defaultContent/manifest.json new file mode 100644 index 0000000..9d89802 --- /dev/null +++ b/plugins/NASA/v1/defaultContent/manifest.json @@ -0,0 +1,7 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "nearEarthObject", "type": "dashboard" }, + { "name": "spaceWeatherNotification", "type": "dashboard" } + ] +} diff --git a/plugins/NASA/v1/defaultContent/nearEarthObject.dash.json b/plugins/NASA/v1/defaultContent/nearEarthObject.dash.json new file mode 100644 index 0000000..25a65a2 --- /dev/null +++ b/plugins/NASA/v1/defaultContent/nearEarthObject.dash.json @@ -0,0 +1,101 @@ +{ + "name": "Near-Earth Object", + "schemaVersion": "1.5", + "variables": ["{{variables.[Near-Earth Object]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "e17ba149-054e-4084-a312-602c871a46af", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Asteroid Summary", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[Near-Earth Object]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.neoSummary}}", + "name": "neoSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Near-Earth Objects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Near-Earth Object]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "absolute_magnitude_h", + "is_potentially_hazardous_asteroid", + "is_sentry_object", + "estimated_diameter_min_km", + "estimated_diameter_max_km", + "close_approach_count" + ] + } + } + } + } + }, + { + "i": "ad14ea42-cb61-4276-a8aa-c8650a49abed", + "x": 0, + "y": 4, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Close Approach History", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[Near-Earth Object]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.neoCloseApproaches}}", + "name": "neoCloseApproaches", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["close_approach_date", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[Near-Earth Objects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Near-Earth Object]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "close_approach_date", + "orbiting_body", + "miss_distance.kilometers", + "relative_velocity.kilometers_per_hour" + ], + "hiddenColumns": ["close_approach_date_full"] + } + } + } + } + } + ] + } +} diff --git a/plugins/NASA/v1/defaultContent/overview.dash.json b/plugins/NASA/v1/defaultContent/overview.dash.json new file mode 100644 index 0000000..b6139da --- /dev/null +++ b/plugins/NASA/v1/defaultContent/overview.dash.json @@ -0,0 +1,263 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "e3a282f4-727d-4a62-ae78-f0be75a41d65", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Near-Earth Objects (7 Days)", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.neoFeed}}", + "name": "neoFeed", + "pluginConfigId": "{{configId}}", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Near-Earth Objects" + } + } + } + } + }, + { + "i": "07f51021-614a-4794-8646-2d6617d63e57", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Potentially Hazardous", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.neoFeed}}", + "name": "neoFeed", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "is_potentially_hazardous_asteroid", + "operation": "equals", + "value": "true" + } + ] + }, + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Potentially Hazardous" + } + } + } + } + }, + { + "i": "27faa684-f4e9-414a-936e-406c8492b39c", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Closest Approach (km)", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.neoFeed}}", + "name": "neoFeed", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["miss_distance_km", "asc"]], "top": 1 } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "miss_distance_km", + "comparisonColumn": "none", + "label": "Closest Approach (km)" + } + } + } + } + }, + { + "i": "6cfcb858-ad4d-4396-9c5d-935b12963905", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Space Weather Notifications (30 Days)", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.donkiNotifications}}", + "name": "donkiNotifications", + "pluginConfigId": "{{configId}}", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Space Weather Notifications" + } + } + } + } + }, + { + "i": "3d6bee99-b3ee-40aa-80ef-1ad5393ad487", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Astronomy Picture of the Day", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.apodToday}}", + "name": "apodToday", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": ["title", "date", "media_type", "url", "explanation"] + } + } + } + } + }, + { + "i": "23c99485-32ad-4717-9eec-9338a0259815", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Closest Upcoming Approaches", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.neoFeed}}", + "name": "neoFeed", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["miss_distance_km", "asc"]], "top": 5 } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "close_approach_date", + "miss_distance_km", + "relative_velocity_kph", + "is_potentially_hazardous_asteroid", + "orbiting_body" + ], + "hiddenColumns": [ + "id", + "is_sentry_object", + "absolute_magnitude_h", + "estimated_diameter_min_km", + "estimated_diameter_max_km" + ] + } + } + } + } + }, + { + "i": "12c5e141-1c42-4f0f-8b86-9abb32fbc976", + "x": 0, + "y": 6, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Space Weather Notifications", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.donkiNotifications}}", + "name": "donkiNotifications", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["messageIssueTime", "desc"]], "top": 10 } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["title", "messageType", "messageIssueTime", "summary"], + "hiddenColumns": ["messageID", "messageURL", "messageBody"] + } + } + } + } + } + ] + } +} diff --git a/plugins/NASA/v1/defaultContent/scopes.json b/plugins/NASA/v1/defaultContent/scopes.json new file mode 100644 index 0000000..1077cc0 --- /dev/null +++ b/plugins/NASA/v1/defaultContent/scopes.json @@ -0,0 +1,26 @@ +[ + { + "name": "Near-Earth Objects", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Near-Earth Object"] } + }, + "variable": { + "name": "Near-Earth Object", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Space Weather Notifications", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Space Weather Notification"] } + }, + "variable": { + "name": "Space Weather Notification", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/NASA/v1/defaultContent/spaceWeatherNotification.dash.json b/plugins/NASA/v1/defaultContent/spaceWeatherNotification.dash.json new file mode 100644 index 0000000..0717b4a --- /dev/null +++ b/plugins/NASA/v1/defaultContent/spaceWeatherNotification.dash.json @@ -0,0 +1,48 @@ +{ + "name": "Space Weather Notification", + "schemaVersion": "1.5", + "variables": ["{{variables.[Space Weather Notification]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "7f10591e-4c91-4787-99b4-7b0645b596b9", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Notification Details", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[Space Weather Notification]}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Space Weather Notifications]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Space Weather Notification]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": ["messageType", "messageIssueTime", "messageURL", "summary"], + "hiddenColumns": ["id", "link", "links", "label", "sourceId", "sourceType"] + } + } + } + } + } + ] + } +} diff --git a/plugins/NASA/v1/docs/README.md b/plugins/NASA/v1/docs/README.md new file mode 100644 index 0000000..05af868 --- /dev/null +++ b/plugins/NASA/v1/docs/README.md @@ -0,0 +1,38 @@ +# NASA + +Monitor NASA's open data feeds: near-Earth asteroid close approaches, space weather notifications, tracked satellites, and the Astronomy Picture of the Day. + +## What gets indexed + +| Object type | Source | Description | +|---|---|---| +| `Near-Earth Object` | NeoWs | An asteroid with a close approach to Earth in the current rolling 7-day window | +| `Space Weather Notification` | DONKI | A space weather alert (solar flare, coronal mass ejection, geomagnetic storm, and other event types) from the last 30 days | +| `Satellite` | Satellite Situation Center (SSC) | A satellite/spacecraft currently tracked by NASA, with a data stream for its recent position (GSE X/Y/Z coordinates) over time | + +The plugin also surfaces the Astronomy Picture of the Day (APOD) as a dashboard tile — it isn't indexed as an object since it's a single daily record with nothing to drill into. + +**Not included: EONET natural events.** NASA's EONET service (`eonet.gsfc.nasa.gov`) has a server-side bug — it serves valid JSON but mislabels the response as `Content-Type: application/rss+xml` on the large majority of requests, which crashes SquaredUp's response parser. This was confirmed against multiple EONET API versions/paths and isn't something a plugin config can work around, so natural-event tracking (wildfires, storms, volcanoes) has been left out of this plugin until NASA fixes it upstream. + +## Prerequisites + +1. Go to [api.nasa.gov](https://api.nasa.gov/) and fill in the "Generate API Key" form with your name and email. +2. NASA emails you an API key immediately — no approval wait. +3. Use that key when configuring this plugin. + +You can use the public `DEMO_KEY` to try the plugin out, but it's rate-limited to 30 requests/hour and 50/day shared across *all* users of that key — a real key raises this to 1,000 requests/hour tied to you personally. Get a real key before relying on this plugin for anything ongoing. + +## Configuration fields + +| Field | Description | Required | +|---|---|---| +| API Key | Your `api.nasa.gov` API key (or `DEMO_KEY` for testing) | Yes | + +## Known limitations + +- **Rate limits** — the free API key is capped at 1,000 requests/hour. Large tenants with frequent imports/refreshes across many objects may need to watch usage. +- **Near-Earth Objects** only include asteroids with a close approach in the *current* 7-day window (a NASA API restriction on the feed endpoint) — the full ~34,000-asteroid catalog isn't imported. +- **Space Weather Notifications** use DONKI's unified notification feed rather than separate structured objects per event type, so detailed numeric fields (e.g. CME speed, solar flare class, Kp-index) aren't broken out as separate properties. +- **EONET natural events are not included.** NASA's EONET service intermittently mislabels its JSON responses with an XML content type, which breaks this plugin's HTTP handling. This will be revisited if NASA fixes the issue upstream. +- **Satellites** only include the ~84 currently active spacecraft SSC tracks (`EndTime` in the future) — its full historical catalog of 300+ satellites/observatories isn't imported. +- **No default dashboard yet for the Satellite perspective.** The `Satellite` object type and its `satelliteLocation` data stream are fully functional, but out-of-the-box dashboard content for it hasn't been built yet — add tiles manually for now (e.g. the built-in Properties stream, or a line graph off `satelliteLocation`). diff --git a/plugins/NASA/v1/icon.svg b/plugins/NASA/v1/icon.svg new file mode 100644 index 0000000..e2c28c9 --- /dev/null +++ b/plugins/NASA/v1/icon.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/NASA/v1/indexDefinitions/default.json b/plugins/NASA/v1/indexDefinitions/default.json new file mode 100644 index 0000000..f29ecf2 --- /dev/null +++ b/plugins/NASA/v1/indexDefinitions/default.json @@ -0,0 +1,48 @@ +{ + "steps": [ + { + "name": "neoFeed", + "dataStream": { "name": "neoFeed" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Near-Earth Object" }, + "properties": [ + "absolute_magnitude_h", + "is_potentially_hazardous_asteroid", + "is_sentry_object", + "nasa_jpl_url", + "estimated_diameter_min_km", + "estimated_diameter_max_km", + "close_approach_date", + "miss_distance_km", + "relative_velocity_kph", + "orbiting_body" + ] + } + }, + { + "name": "donkiNotifications", + "dataStream": { "name": "donkiNotifications" }, + "timeframe": "none", + "objectMapping": { + "id": "messageID", + "name": "title", + "type": { "value": "Space Weather Notification" }, + "properties": ["messageType", "messageIssueTime", "messageURL", "summary"] + } + }, + { + "name": "satellites", + "dataStream": { "name": "satellites" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Satellite" }, + "properties": ["resolution", "startTime", "endTime", "resourceId"] + } + } + ] +} diff --git a/plugins/NASA/v1/metadata.json b/plugins/NASA/v1/metadata.json new file mode 100644 index 0000000..75dce92 --- /dev/null +++ b/plugins/NASA/v1/metadata.json @@ -0,0 +1,49 @@ +{ + "name": "nasa", + "displayName": "NASA", + "version": "1.2.0", + "author": { + "name": "sam.jacobs@squaredup.com", + "type": "community" + }, + "description": "NASA's open data feeds: near-Earth asteroid close approaches, space weather alerts, tracked satellites, and the Astronomy Picture of the Day.", + "category": "Fun", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": [ + "nasa", + "space", + "astronomy", + "asteroids", + "near earth object", + "space weather", + "donki", + "apod", + "satellite", + "ssc" + ], + "objectTypes": ["Near-Earth Object", "Space Weather Notification", "Satellite"], + "links": [ + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/NASA/v1", + "label": "Repository" + }, + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/NASA/v1/docs/README.md", + "label": "Help adding this plugin" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://api.nasa.gov", + "authMode": "none", + "queryArgs": [{ "key": "api_key", "value": "{{apiKey}}" }] + } + } +} diff --git a/plugins/NASA/v1/ui.json b/plugins/NASA/v1/ui.json new file mode 100644 index 0000000..ed874fd --- /dev/null +++ b/plugins/NASA/v1/ui.json @@ -0,0 +1,10 @@ +[ + { + "type": "password", + "name": "apiKey", + "label": "API Key", + "defaultValue": "DEMO_KEY", + "help": "Get a free API key at [api.nasa.gov](https://api.nasa.gov/). `DEMO_KEY` works for testing but is rate-limited to 30 requests/hour and 50/day shared across all DEMO_KEY users.", + "validation": { "required": true } + } +]