Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #990

Merged
merged 5 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export interface SensorObjectCruncher {
}

export type lookanglesRow = {
sortTime: number;
START_DTG: number | string;
SATELLITE_ID: string;
PASS_SCORE: string;
START_DATE: Date | string
Expand Down
105 changes: 97 additions & 8 deletions src/plugins/analysis/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { saveCsv } from '@app/lib/saveVariable';
import { CatalogExporter } from '@app/static/catalog-exporter';
import { CatalogSearch } from '@app/static/catalog-search';
import analysisPng from '@public/img/icons/analysis.png';
import { DetailedSatellite, DetailedSensor, eci2rae, EciVec3, Kilometers, MINUTES_PER_DAY, SatelliteRecord, TAU } from 'ootk';
import { DetailedSatellite, DetailedSensor, eci2rae, EciVec3, Kilometers, MILLISECONDS_PER_SECOND, MINUTES_PER_DAY, SatelliteRecord, TAU } from 'ootk';
import { KeepTrackPlugin } from '../KeepTrackPlugin';
import { WatchlistPlugin } from '../watchlist/watchlist';

/**
* /*! /////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -164,6 +165,43 @@ export class AnalysisMenu extends KeepTrackPlugin {
</div>
</form>
</div>
<h5 class="center-align">Satellite Overflight</h5>
<div class="divider"></div>
<div class="row"></div>
<div class="row">
<form id="analysis-overflight">
<div class="row">
<div class="input-field col s12">
<input value="41.888935617165025" id="analysis-of-lat" type="text" />
<label for="analysis-of-lat" class="active">Latitude</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input value="2" id="analysis-of-lat-marg" type="text" />
<label for="analysis-of-lat-marg" class="active">Latitude Margin</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input value="12.484747346796043" id="analysis-of-lon" type="text" />
<label for="analysis-of-lon" class="active">Longitude</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input value="3" id="analysis-of-lon-marg" type="text" />
<label for="analysis-of-lon-marg" class="active">Longitude Margin</label>
</div>
</div>
<div class="row">
<center>
<button id="analysis-overflight-submit" class="btn btn-ui waves-effect waves-light" type="submit"
name="action">Generate Overflight Times &#9658;</button>
</center>
</div>
</form>
</div>
</div>
</div>
`;
Expand All @@ -180,6 +218,11 @@ export class AnalysisMenu extends KeepTrackPlugin {
AnalysisMenu.analysisBptSumbit_();
});

getEl('analysis-overflight')?.addEventListener('submit', (e: Event) => {
e.preventDefault();
AnalysisMenu.findOverflight_();
});

getEl('findCsoBtn')?.addEventListener('click', () => {
showLoading(this.findCsoBtnClick_.bind(this));
});
Expand Down Expand Up @@ -471,7 +514,7 @@ export class AnalysisMenu extends KeepTrackPlugin {
// Skip pass if satellite is in track right now
if (sTime === null) {
return {
sortTime: null,
START_DTG: null,
SATELLITE_ID: null,
PASS_SCORE: null,
START_DATE: null,
Expand Down Expand Up @@ -514,7 +557,7 @@ export class AnalysisMenu extends KeepTrackPlugin {

// Last Line of Coverage
return {
sortTime: sTime.getTime(),
START_DTG: sTime.getTime(),
SATELLITE_ID: parseInt(satrecIn.satnum).toString(),
PASS_SCORE: score.toFixed(1),
START_DATE: sTime,
Expand Down Expand Up @@ -545,7 +588,7 @@ export class AnalysisMenu extends KeepTrackPlugin {
}

return {
sortTime: null,
START_DTG: null,
SATELLITE_ID: null,
PASS_SCORE: null,
START_DATE: null,
Expand Down Expand Up @@ -618,13 +661,11 @@ export class AnalysisMenu extends KeepTrackPlugin {
console.debug(e);
}
}
passes.sort((a, b) => b.sortTime - a.sortTime);
passes.sort((a, b) => (b.START_DTG as number) - (a.START_DTG as number));
passes.reverse();
passes.forEach((v) => {
delete v.sortTime;
});

for (const pass of passes) {
pass.START_DTG = (<Date>pass.START_DATE).toISOString();
pass.START_DATE = (<Date>pass.START_DATE).toISOString().split('T')[0];
pass.START_TIME = (<Date>pass.START_TIME).toISOString().split('T')[1].split('.')[0];
pass.STOP_DATE = (<Date>pass.STOP_DATE).toISOString().split('T')[0];
Expand Down Expand Up @@ -657,6 +698,54 @@ export class AnalysisMenu extends KeepTrackPlugin {
}
}

private static findOverflight_() {
// Get lat, lon, lat margin, and lon margin
const lat = parseFloat((<HTMLInputElement>getEl('analysis-of-lat')).value);
const lon = parseFloat((<HTMLInputElement>getEl('analysis-of-lon')).value);
const latMargin = parseFloat((<HTMLInputElement>getEl('analysis-of-lat-marg')).value);
const lonMargin = parseFloat((<HTMLInputElement>getEl('analysis-of-lon-marg')).value);

// Get watchlist satellites
const idList = keepTrackApi.getPlugin(WatchlistPlugin).getSatellites();

// For each satellite loop through 72 hours at 30 second intervals
const durationInSeconds = 72 * 60 * 60;
const overflights = [];

for (const satId of idList) {
const sat = keepTrackApi.getCatalogManager().getSat(satId);
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);

if (lla.lat > lat - latMargin && lla.lat < lat + latMargin && lla.lon > lon - lonMargin && lla.lon < lon + lonMargin) {
overflights.push({
START_DTG: time.toISOString(),
SATELLITE_ID: sat.sccNum,
LATITUDE: lla.lat,
LONGITUDE: lla.lon,
});
}
}
}

// sort overflights by time
overflights.sort((a, b) => new Date(a.START_DTG).getTime() - new Date(b.START_DTG).getTime());

saveCsv(overflights, 'overflights');
}

private static getStartTime_() {
const time = keepTrackApi.getTimeManager().getOffsetTimeObj(0);

time.setMilliseconds(0);
time.setSeconds(0);

return time;
}

private static setSensor_(sensor: DetailedSensor | string): void {
const submitButtonDom = <HTMLButtonElement>getEl('analysis-bpt-submit');

Expand Down
5 changes: 4 additions & 1 deletion src/plugins/watchlist/watchlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ export class WatchlistPlugin extends KeepTrackPlugin {
return this.watchlistList.some(({ id: id_ }) => id_ === id);
}

/**
* @returns An array of satellite ids in the watchlist.
*/
getSatellites() {
return this.watchlistList.map(({
id,
Expand All @@ -384,7 +387,7 @@ export class WatchlistPlugin extends KeepTrackPlugin {
*/
private onAddEvent_() {
keepTrackApi.getSoundManager().play(SoundNames.CLICK);
const sats = (<HTMLInputElement>getEl('watchlist-new')).value.split(',');
const sats = (<HTMLInputElement>getEl('watchlist-new')).value.split(/[\s,]+/u);

sats.forEach((satNum: string) => {
const id = keepTrackApi.getCatalogManager().sccNum2Id(parseInt(satNum));
Expand Down
2 changes: 1 addition & 1 deletion src/settings/versionDate.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
export const VERSION_DATE = 'October 29, 2024';
export const VERSION_DATE = 'November 7, 2024';
56 changes: 48 additions & 8 deletions src/static/catalog-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

import { BaseObject, CatalogSource, Degrees, DetailedSatellite, Minutes, SpaceObjectType } from 'ootk';
import { SatMath } from './sat-math';

/**
* The CatalogSearch class provides static methods for filtering and searching through an array of satellite data.
Expand Down Expand Up @@ -112,30 +113,69 @@ export class CatalogSearch {
minRaan += 360;
}

const now = new Date();
const normalizedSatRaan = this.normalizeRaan(sat, now);

return satData
.filter((s) => {
// Skip static objects
if (s.isStatic()) {
return false;
}

// Check inclination bounds
if (s.inclination < minInclination || s.inclination > maxInclination) {
return false;
}
if (sat.rightAscension > 360 - RAAN_MARGIN || sat.rightAscension < RAAN_MARGIN) {
if (s.rightAscension > maxRaan && s.rightAscension < minRaan) {
return false;
}
} else if (s.rightAscension < minRaan || s.rightAscension > maxRaan) {
return false;
}

// Check period bounds
if (s.period < minPeriod || s.period > maxPeriod) {
return false;
}

return true;
const normalizedSearchRaan = this.normalizeRaan(s, now);

// Handle RAAN wraparound case
if (normalizedSatRaan > 360 - RAAN_MARGIN || normalizedSatRaan < RAAN_MARGIN) {
return normalizedSearchRaan > minRaan || normalizedSearchRaan < maxRaan;
}

// Check RAAN bounds (normal case)
return !(normalizedSearchRaan < minRaan || normalizedSearchRaan > maxRaan);
})
.map((s) => s.id);
}

// Normalize the RAAN based on nodal precession
static normalizeRaan(sat: DetailedSatellite, now: Date): number {
const precessionRate = this.getNodalPrecessionRate(sat);
const daysSinceEpoch = SatMath.calcElsetAge(sat, now);
let normalizedRaan = sat.rightAscension + (precessionRate * daysSinceEpoch);

// Ensure RAAN stays within 0-360 range
normalizedRaan = ((normalizedRaan % 360) + 360) % 360;

return normalizedRaan;
}

// Calculate nodal precession rate (degrees per day)
static getNodalPrecessionRate(s: DetailedSatellite): number {
const Re = 6378137; // Earth radius in meters
const J2 = 1.082626680e-3; // Earth's second dynamic form factor
const period = s.period * 60; // Convert period from minutes to seconds
const omega = (2 * Math.PI) / period; // Angular velocity in rad/s
const a = s.semiMajorAxis * 1000; // Convert semi-major axis from km to meters
const e = s.eccentricity;
const i = s.inclination * Math.PI / 180; // Convert inclination to radians

// Calculate precession rate in rad/s
const omegaP = (-3 / 2) * (Re / a) ** 2 / (1 - e * e) ** 2 * J2 * omega * Math.cos(i);

// Convert to degrees per day
return omegaP * (180 / Math.PI) * 86400;
}


/**
* This method is used to find the reentry objects from the given satellite data.
* It filters the satellite data based on the type of the object (PAYLOAD, ROCKET_BODY, DEBRIS) and the perigee value.
Expand Down
49 changes: 21 additions & 28 deletions src/static/classification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,32 @@ export class Classification {
let backgroundColor: string;
let color: string;

switch (classification) {
case 'Top Secret//SCI':
backgroundColor = '#fce93a';
color = 'black';
break;
case 'Top Secret':
backgroundColor = '#ff8c00';
color = 'black';
break;
case 'Secret':
backgroundColor = '#ff0000';
color = 'white';
break;
case 'Confidential':
backgroundColor = '#0033a0';
color = 'white';
break;
case 'CUI':
backgroundColor = '#512b85';
color = 'white';
break;
case 'Unclassified':
backgroundColor = '#007a33';
color = 'white';
break;
default:
throw new Error(`Invalid classification: ${classification}`);
if (classification.startsWith('Top Secret//SCI')) {
backgroundColor = '#fce93a';
color = 'black';
} else if (classification.startsWith('Top Secret')) {
backgroundColor = '#ff8c00';
color = 'black';
} else if (classification.startsWith('Secret')) {
backgroundColor = '#ff0000';
color = 'white';
} else if (classification.startsWith('Confidential')) {
backgroundColor = '#0033a0';
color = 'white';
} else if (classification.startsWith('CUI')) {
backgroundColor = '#512b85';
color = 'white';
} else if (classification.startsWith('Unclassified')) {
backgroundColor = '#007a33';
color = 'white';
} else {
throw new Error(`Invalid classification: ${classification}`);
}

return { backgroundColor, color };
}

static isValidClassification(classification: string): boolean {
return ['Unclassified', 'Confidential', 'CUI', 'Secret', 'Top Secret', 'Top Secret//SCI'].includes(classification);
return ['Unclassified', 'Confidential', 'CUI', 'Secret', 'Top Secret', 'Top Secret//SCI'].some((validClassification) => classification.startsWith(validClassification));
}
}
Loading