diff --git a/package-lock.json b/package-lock.json
index 77f52b5a1..6b8c0c5a8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,7 +26,7 @@
"new-github-issue-url": "^1.0.0",
"node-fetch": "^3.3.2",
"numeric": "^1.2.6",
- "ootk": "^4.0.1",
+ "ootk": "^4.0.2",
"papaparse": "^5.4.1",
"resizable": "^1.2.1",
"ts-node": "^10.9.1",
@@ -12618,17 +12618,17 @@
}
},
"node_modules/ootk": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/ootk/-/ootk-4.0.1.tgz",
- "integrity": "sha512-om9mqrHy6kRlqPA58rPNVro/bdmAFbBOc0n431kEi5vrYvNCaQsUwVpH3yjhtKDUfNY/flUH/WPFPw2Loh+y4A==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/ootk/-/ootk-4.0.2.tgz",
+ "integrity": "sha512-XXbphQ+XfVUuaLO7GKI3erHrrQd1Up3I7Wc5D9JHTLP8W/Ki7FY1DeUMFidbJ2U6h/QMn9uspuLx+HvX8KBIkw==",
"dependencies": {
- "ootk-core": "^1.2.3"
+ "ootk-core": "^1.2.4"
}
},
"node_modules/ootk-core": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/ootk-core/-/ootk-core-1.2.3.tgz",
- "integrity": "sha512-oUgmNMNhddSGdiw7m+/gJHxE/PiQikuVJNuGthrnqs9duk2hrPGEEwi9h8wlNnUv2BWddNFAcrF7H3ZZEwIjkw=="
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/ootk-core/-/ootk-core-1.2.4.tgz",
+ "integrity": "sha512-AykpA8qta0vhXoHJry/ebNJlZSP7jiTHlq/5FJ4yLKMww2iQ2LftZm8kAdwkjnO4tp1CpeWn84cknXBgh1m6tg=="
},
"node_modules/opener": {
"version": "1.5.2",
diff --git a/package.json b/package.json
index 4e4f0a4fe..5969e41d5 100644
--- a/package.json
+++ b/package.json
@@ -151,7 +151,7 @@
"new-github-issue-url": "^1.0.0",
"node-fetch": "^3.3.2",
"numeric": "^1.2.6",
- "ootk": "^4.0.1",
+ "ootk": "^4.0.2",
"papaparse": "^5.4.1",
"resizable": "^1.2.1",
"ts-node": "^10.9.1",
diff --git a/src/lib/click-and-drag.ts b/src/lib/click-and-drag.ts
index c481bcbef..ea058cb56 100644
--- a/src/lib/click-and-drag.ts
+++ b/src/lib/click-and-drag.ts
@@ -1,9 +1,6 @@
-interface ClickDragOptions {
- minWidth?: number;
- maxWidth?: number;
-}
+import { clickDragOptions } from '@app/plugins/KeepTrackPlugin';
-export const clickAndDragWidth = (el: HTMLElement | null, options: ClickDragOptions = {}): void => {
+export const clickAndDragWidth = (el: HTMLElement | null, options: clickDragOptions = {}): void => {
if (!el) {
return;
}
@@ -19,10 +16,12 @@ export const clickAndDragWidth = (el: HTMLElement | null, options: ClickDragOpti
settingsManager.isDragging = false;
- // create new element on right edge
- const edgeEl = createElWidth_(el);
+ if (options.isDraggable) {
+ // create new element on right edge
+ const edgeEl = createElWidth_(el);
- addEventsWidth_(edgeEl, el, width, minWidth, maxWidth);
+ addEventsWidth_(edgeEl, el, width, minWidth, maxWidth);
+ }
};
export const clickAndDragHeight = (el: HTMLElement, maxHeight?: number, callback?: () => void): void => {
diff --git a/src/plugins/KeepTrackPlugin.ts b/src/plugins/KeepTrackPlugin.ts
index 78d71b765..12d29ba89 100644
--- a/src/plugins/KeepTrackPlugin.ts
+++ b/src/plugins/KeepTrackPlugin.ts
@@ -225,7 +225,7 @@ export class KeepTrackPlugin {
this.registerSubmitButtonClicked(this.submitCallback);
}
- if (this.dragOptions?.isDraggable) {
+ if (this.dragOptions) {
this.registerClickAndDragOptions(this.dragOptions);
}
@@ -456,7 +456,7 @@ export class KeepTrackPlugin {
* @param callback The callback function to run when the bottom icon is clicked. This is run
* even if the bottom icon is disabled.
*/
- registerBottomMenuClicked(callback: () => void = () => {}) {
+ registerBottomMenuClicked(callback: () => void = () => { }) {
if (this.isRequireSensorSelected && this.isRequireSatelliteSelected) {
keepTrackApi.register({
event: KeepTrackApiEvents.selectSatData,
diff --git a/src/plugins/reports/reports.ts b/src/plugins/reports/reports.ts
index c0f42c715..b5ef8cda5 100644
--- a/src/plugins/reports/reports.ts
+++ b/src/plugins/reports/reports.ts
@@ -5,10 +5,18 @@ import { errorManagerInstance } from '@app/singletons/errorManager';
import analysisPng from '@public/img/icons/reports.png';
-import { BaseObject, DetailedSatellite, MILLISECONDS_PER_SECOND } from 'ootk';
+import { BaseObject, DetailedSatellite, DetailedSensor, MILLISECONDS_PER_SECOND } from 'ootk';
import { KeepTrackPlugin, clickDragOptions } from '../KeepTrackPlugin';
import { SelectSatManager } from '../select-sat-manager/select-sat-manager';
+interface ReportData {
+ filename: string;
+ header: string;
+ body: string;
+ columns?: number;
+ isHeaders?: boolean;
+}
+
export class ReportsPlugin extends KeepTrackPlugin {
static readonly PLUGIN_NAME = 'Reports';
dependencies = [SelectSatManager.PLUGIN_NAME];
@@ -33,10 +41,17 @@ export class ReportsPlugin extends KeepTrackPlugin {
Reports
-
-
+
+
+
@@ -48,7 +63,8 @@ export class ReportsPlugin extends KeepTrackPlugin {
helpBody = keepTrackApi.html`The Reports Menu is a collection of tools to help you analyze and understand the data you are viewing.`;
dragOptions: clickDragOptions = {
- isDraggable: true,
+ isDraggable: false,
+ minWidth: 320,
};
addJs(): void {
@@ -57,7 +73,10 @@ export class ReportsPlugin extends KeepTrackPlugin {
event: KeepTrackApiEvents.uiManagerFinal,
cbName: this.PLUGIN_NAME,
cb: () => {
- getEl('aer-report-btn').addEventListener('click', () => this.generateAerReport());
+ getEl('aer-report-btn').addEventListener('click', () => this.generateAzElRng_());
+ getEl('coes-report-btn').addEventListener('click', () => this.generateClasicalOrbElJ2000_());
+ getEl('eci-report-btn').addEventListener('click', () => this.generateEci_());
+ getEl('lla-report-btn').addEventListener('click', () => this.generateLla_());
},
});
@@ -76,73 +95,175 @@ export class ReportsPlugin extends KeepTrackPlugin {
});
}
- generateAerReport() {
- const sensorManager = keepTrackApi.getSensorManager();
- const sat = this.selectSatManager_.primarySatObj as DetailedSatellite;
+ private generateAzElRng_() {
+ const sat = this.getSat_();
+ const sensor = this.getSensor_();
+ if (!sat || !sensor) {
+ return;
+ }
- if (!sensorManager.isSensorSelected()) {
- errorManagerInstance.warn('Select a sensor first!');
+ const header = `Azimuth Elevation Range Report\n-------------------------------\n${this.createHeader_(sat, sensor)}`;
+ let body = 'Time (UTC),Azimuth(°),Elevation(°),Range(km)\n';
+ const durationInSeconds = 72 * 60 * 60;
+ let isInCoverage = false;
+ let time = this.getStartTime_();
- return;
+ for (let t = 0; t < durationInSeconds; t += 30) {
+ time = new Date(time.getTime() + MILLISECONDS_PER_SECOND * 30);
+ const rae = sensor.rae(sat, time);
+
+ if (rae.el > 0) {
+ isInCoverage = true;
+ body += `${this.formatTime_(time)},${rae.az.toFixed(3)},${rae.el.toFixed(3)},${rae.rng.toFixed(3)}\n`;
+ } else if (isInCoverage) {
+ // If we were in coverage but now we are not, add a blank line to separate the passes
+ body += '\n\n';
+ isInCoverage = false;
+ }
}
- if (!sat) {
- errorManagerInstance.warn('Select a satellite first!');
- return;
+ if (body === 'Time (UTC),Azimuth(°),Elevation(°),Range(km)\n') {
+ body += 'No passes found!';
}
- if (!(sat instanceof DetailedSatellite)) {
- errorManagerInstance.warn('Satellite is not DetailedSatellite!');
+ this.writeReport_({
+ filename: `aer-${sat.sccNum}`,
+ header,
+ body,
+ });
+ }
+ private formatTime_(time: Date) {
+ const timeStr = time.toISOString();
+ const timeStrSplit = timeStr.split('T');
+ const date = timeStrSplit[0];
+ const timeSplit = timeStrSplit[1].split('.');
+ const timeOut = timeSplit[0];
+
+ return `${date} ${timeOut}`;
+ }
+
+ private generateLla_() {
+ const sat = this.getSat_();
+
+ if (!sat) {
return;
}
- const sensor = sensorManager.currentSensors[0];
+ const header = `Latitude Longitude Altitude Report\n-------------------------------\n${this.createHeader_(sat)}`;
+ let body = 'Time (UTC),Latitude(°),Longitude(°),Altitude(km)\n';
+ const durationInSeconds = 72 * 60 * 60;
+ let time = this.getStartTime_();
+ for (let t = 0; t < durationInSeconds; t += 30) {
+ time = new Date(time.getTime() + 30 * MILLISECONDS_PER_SECOND);
+ const lla = sat.lla(time);
- /*
- * Azimuth Elevation Range Report
- * ------------------------------
- * Satellite: [Satellite Name]
- * NORAD ID: [NORAD ID]
- * Date: [Date]
- */
+ body += `${this.formatTime_(time)},${lla.lat.toFixed(3)},${lla.lon.toFixed(3)},${lla.alt.toFixed(3)}\n`;
+ }
- const reportHeader = `Azimuth Elevation Range Report\n-------------------------------\nSatellite: ${sat.name}\nNORAD ID: ${sat.sccNum}\nDate: ${new Date().toISOString()}\n\n`;
- let report = 'Time (UTC),Azimuth(°),Elevation(°),Range(km)\n';
- const durationInMinutes = 72 * 60;
- let isInCoverage = false;
+ this.writeReport_({
+ filename: `lla-${sat.sccNum}`,
+ header,
+ body,
+ });
+ }
- for (let t = 0; t < durationInMinutes; t++) {
- const time = keepTrackApi.getTimeManager().getOffsetTimeObj(t * MILLISECONDS_PER_SECOND * 60);
- const rae = sensor.rae(sat, time);
+ private generateEci_() {
+ const sat = this.getSat_();
- if (rae.el > 0) {
- isInCoverage = true;
- report += `${time.toISOString()},${rae.az.toFixed(3)},${rae.el.toFixed(3)},${rae.rng.toFixed(3)}\n`;
- } else if (isInCoverage) {
- // If we were in coverage but now we are not, add a blank line to separate the passes
- report += '\n\n';
- isInCoverage = false;
- }
+ if (!sat) {
+ return;
}
- if (report === 'Time (UTC),Azimuth(°),Elevation(°),Range(km)\n') {
- report += 'No passes found!';
+ const header = `Earth Centered Intertial Report\n-------------------------------\n${this.createHeader_(sat)}`;
+ let body = 'Time (UTC),Position X(km),Position Y(km),Position Z(km),Velocity X(km/s),Velocity Y(km/s),Velocity Z(km/s)\n';
+ const durationInSeconds = 72 * 60 * 60;
+ let time = this.getStartTime_();
+
+ for (let t = 0; t < durationInSeconds; t += 30) {
+ time = new Date(time.getTime() + 30 * MILLISECONDS_PER_SECOND);
+ const eci = sat.eci(time);
+
+ body += `${this.formatTime_(time)},${eci.position.x.toFixed(3)},${eci.position.y.toFixed(3)},${eci.position.z.toFixed(3)},` +
+ `${eci.velocity.x.toFixed(3)},${eci.velocity.y.toFixed(3)},${eci.velocity.z.toFixed(3)}\n`;
}
- this.writeReport(sat, reportHeader, report);
+ this.writeReport_({
+ filename: `eci-${sat.sccNum}`,
+ header,
+ body,
+ columns: 7,
+ isHeaders: true,
+ });
+ }
+
+ private createHeader_(sat: DetailedSatellite, sensor?: DetailedSensor) {
+ const satData = '' +
+ `Date: ${new Date().toISOString()}\n` +
+ `Satellite: ${sat.name}\n` +
+ `NORAD ID: ${sat.sccNum}\n` +
+ `Alternate ID: ${sat.altId || 'None'}\n` +
+ `International Designator: ${sat.intlDes}\n\n`;
+ const sensorData = '' +
+ `Sensor: ${sensor ? sensor.name : 'None'}\n` +
+ `Type: ${sensor ? sensor.getTypeString() : 'None'}\n` +
+ `Latitude: ${sensor ? sensor.lat : 'None'}\n` +
+ `Longitude: ${sensor ? sensor.lon : 'None'}\n` +
+ `Altitude: ${sensor ? sensor.alt : 'None'}\n` +
+ `Min Azimuth: ${sensor ? sensor.minAz : 'None'}\n` +
+ `Max Azimuth: ${sensor ? sensor.maxAz : 'None'}\n` +
+ `Min Elevation: ${sensor ? sensor.minEl : 'None'}\n` +
+ `Max Elevation: ${sensor ? sensor.maxEl : 'None'}\n` +
+ `Min Range: ${sensor ? sensor.minRng : 'None'}\n` +
+ `Max Range: ${sensor ? sensor.maxRng : 'None'}\n\n`;
+
+
+ return sensor ? `${satData}${sensorData}` : `${satData}`;
}
- writeReport(sat: DetailedSatellite, reportHeader: string, report: string) {
+ private generateClasicalOrbElJ2000_() {
+ const sat = this.getSat_();
+
+ if (!sat) {
+ return;
+ }
+
+ const header = `Classic Orbit Elements Report\n-------------------------------\n${this.createHeader_(sat)}`;
+ const classicalEls = sat.toJ2000().toClassicalElements();
+ const body = '' +
+ `Epoch, ${classicalEls.epoch}\n` +
+ `Apogee, ${classicalEls.apogee.toFixed(3)} km\n` +
+ `Perigee, ${classicalEls.perigee.toFixed(3)} km\n` +
+ `Inclination, ${classicalEls.inclination.toFixed(3)}°\n` +
+ `Right Ascension, ${classicalEls.rightAscensionDegrees.toFixed(3)}°\n` +
+ `Argument of Perigee, ${classicalEls.argPerigeeDegrees.toFixed(3)}°\n` +
+ `True Anomaly, ${classicalEls.trueAnomalyDegrees.toFixed(3)}°\n` +
+ `Eccentricity, ${classicalEls.eccentricity.toFixed(3)}\n` +
+ `Period, ${classicalEls.period.toFixed(3)} min\n` +
+ `Semi-Major Axis, ${classicalEls.semimajorAxis.toFixed(3)} km\n` +
+ `Mean Motion, ${classicalEls.meanMotion.toFixed(3)} rev/day`;
+
+
+ this.writeReport_({
+ filename: `coes-${sat.sccNum}`,
+ header,
+ body,
+ columns: 2,
+ isHeaders: false,
+ });
+ }
+
+ private writeReport_({ filename, header, body, columns = 4, isHeaders = true }: ReportData) {
// Open a new window and write the report to it - the title of the window should be the satellite name
- const win = window.open('text/plain', sat.name);
+ const win = window.open('text/plain', filename);
- const colWidths = [0, 0, 0, 0];
+ // Create an array that is columns long and fill it with 0s
+ const colWidths = new Array(columns).fill(0);
if (win) {
- const formattedReport = report
+ const formattedReport = body
.split('\n')
.map((line) => line.split(','))
.map((values, idx) => values.map((value, idx2) => {
@@ -160,7 +281,7 @@ export class ReportsPlugin extends KeepTrackPlugin {
.map((values, idx) => {
const row = values.join(' ');
- if (idx === 0) {
+ if (idx === 0 && isHeaders) {
// Add ---- under the entire header
const header = values.join(' ');
const headerUnderline = header.replace(/./gu, '-');
@@ -174,12 +295,56 @@ export class ReportsPlugin extends KeepTrackPlugin {
})
.join('\n');
- win.document.write(`
${reportHeader}${formattedReport}`);
- win.document.title = sat.name;
- win.history.replaceState(null, sat.name, `/${sat.name}.txt`);
+ // Create a download button at the top so you can download the report as a .txt file
+ win.document.write(`
Download Report`);
+
+ win.document.write(`
${header}${formattedReport}`);
+ win.document.title = filename;
+ win.history.replaceState(null, filename, `/${filename}.txt`);
} else {
// eslint-disable-next-line no-alert
alert('Please allow popups for this site');
}
}
+
+ private getStartTime_() {
+ const time = keepTrackApi.getTimeManager().getOffsetTimeObj(0);
+
+ time.setMilliseconds(0);
+ time.setSeconds(0);
+
+ return time;
+ }
+
+ private getSat_(): DetailedSatellite {
+ const sat = this.selectSatManager_.primarySatObj as DetailedSatellite;
+
+ if (!sat) {
+ errorManagerInstance.warn('Select a satellite first!');
+
+ return null;
+ }
+
+ if (!(sat instanceof DetailedSatellite)) {
+ errorManagerInstance.warn('Satellite is not DetailedSatellite!');
+
+ return null;
+ }
+
+ return sat;
+ }
+
+ private getSensor_(): DetailedSensor {
+ const sensorManager = keepTrackApi.getSensorManager();
+
+ if (!sensorManager.isSensorSelected()) {
+ errorManagerInstance.warn('Select a sensor first!');
+
+ return null;
+ }
+
+ const sensor = sensorManager.currentSensors[0];
+
+ return sensor;
+ }
}
diff --git a/src/settings/versionDate.js b/src/settings/versionDate.js
index ec7e19bb3..43eb2f633 100644
--- a/src/settings/versionDate.js
+++ b/src/settings/versionDate.js
@@ -1,2 +1,2 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-export const VERSION_DATE = 'July 22, 2024';
+export const VERSION_DATE = 'August 3, 2024';