Skip to content

Commit

Permalink
feat: ✨ add fov functions to sensor
Browse files Browse the repository at this point in the history
  • Loading branch information
thkruz committed Aug 23, 2022
1 parent 2edba89 commit 90d0d2e
Show file tree
Hide file tree
Showing 4 changed files with 420 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/objects/sat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,20 @@ export class Sat extends SpaceObject {

return { gmst, m, j };
}

public static checkSatrec(satrec: SatelliteRecord): boolean {
if (
isNaN(satrec.a) ||
isNaN(satrec.am) ||
isNaN(satrec.alta) ||
isNaN(satrec.em) ||
isNaN(satrec.mo) ||
isNaN(satrec.ecco) ||
isNaN(satrec.no)
) {
return false;
}

return true;
}
}
187 changes: 186 additions & 1 deletion src/objects/sensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* Orbital Object ToolKit. If not, see <http://www.gnu.org/licenses/>.
*/

import { RaeVec3, SpaceObjectType } from '../types/types';
import { Degrees, Kilometer, RaeVec3, SpaceObjectType } from '../types/types';

import { BaseObject } from './base-object';
import { Sat } from './sat';
Expand All @@ -31,12 +31,42 @@ interface ObjectInfo {
lat: number;
lon: number;
alt: number;
minAz: Degrees;

This comment has been minimized.

Copy link
@weedgrease

weedgrease Mar 5, 2023

@thkruz hey there! looking back here as i'm just getting started with your library but could these be marked as optional? further down it looks like there are some default values

This comment has been minimized.

Copy link
@thkruz

thkruz Mar 5, 2023

Author Owner

You are definitely correct. Fixed and pushing a commit right now. Thanks for asking.

maxAz: Degrees;
minEl: Degrees;
maxEl: Degrees;
minRng: Kilometer;
maxRng: Kilometer;
}

export enum PassType {
OUT_OF_VIEW = -1,
ENTER = 0,
IN_VIEW = 1,
EXIT = 2,
}

type Lookangles = {
type: PassType;
time: Date;
az: number;
el: number;
rng: number;
maxElPass?: number;
};

const TAU = Math.PI * 2;

export class Sensor extends BaseObject {
public lat: number;
public lon: number;
public alt: number;
public minAz: Degrees;
public maxAz: Degrees;
public minEl: Degrees;
public maxEl: Degrees;
public minRng: Kilometer;
public maxRng: Kilometer;

constructor(info: ObjectInfo) {
// If there is a sensor type verify it is valid
Expand All @@ -55,6 +85,70 @@ export class Sensor extends BaseObject {

super(info);

this.validateInputData(info);
}

private validateInputData(info: ObjectInfo) {
this.validateLla(info);
this.validateFov(info);
}

private validateFov(info: ObjectInfo) {
if (info.minAz >= 0 && info.minAz <= 360) {
this.minAz = info.minAz;
} else if (typeof info.minAz === 'undefined') {
// Default is a telescope
this.minAz = 0;
} else {
throw new Error('Invalid minimum azimuth - must be between 0 and 360');
}

if (info.maxAz >= 0 && info.maxAz <= 360) {
this.maxAz = info.maxAz;
} else if (typeof info.maxAz === 'undefined') {
// Default is a telescope
this.maxAz = 360;
} else {
throw new Error('Invalid maximum azimuth - must be between 0 and 360');
}

if (info.minEl >= 0 && info.minEl <= 90) {
this.minEl = info.minEl;
} else if (typeof info.minEl === 'undefined') {

This comment has been minimized.

Copy link
@weedgrease

weedgrease Mar 6, 2023

@thkruz not to bug you again but curious if there's any typescript linting happening; if so, i wonder why this didn't throw saying that info.minEl would never be undefined

// Default is a telescope
this.minEl = 0;
} else {
throw new Error('Invalid minimum elevation - must be between 0 and 90');
}

if (info.maxEl >= 0 && info.maxEl <= 180) {
this.maxEl = info.maxEl;
} else if (typeof info.maxEl === 'undefined') {
// Default is a telescope
this.maxEl = 90;
} else {
throw new Error('Invalid maximum elevation - must be between 0 and 180');
}

if (info.minRng >= 0) {
this.minRng = info.minRng;
} else if (typeof info.minRng === 'undefined') {
// Default is a telescope
this.minRng = 0;
} else {
throw new Error('Invalid minimum range - must be greater than 0');
}
if (info.maxRng >= 0) {
this.maxRng = info.maxRng;
} else if (typeof info.maxRng === 'undefined') {
// Default is a telescope
this.maxRng = 50000; // arbitrary large number
} else {
throw new Error('Invalid maximum range - must be greater than 0');
}
}

private validateLla(info: ObjectInfo) {
if (info.lat >= -90 && info.lat <= 90) {
this.lat = info.lat;
} else {
Expand Down Expand Up @@ -83,4 +177,95 @@ export class Sensor extends BaseObject {
public getRae(sat: Sat, date: Date = this.time): RaeVec3 {
return sat.getRae(this, date);
}

public isRaeInFov(rae: RaeVec3): boolean {
if (rae.el < this.minEl || rae.el > this.maxEl) {
return false;
}

if (rae.rng < this.minRng || rae.rng > this.maxRng) {
return false;
}

if (this.minAz > this.maxAz) {
// North Facing Sensors
if (rae.az < this.minAz && rae.az > this.maxAz) {
return false;
}
// Normal Facing Sensors
} else if (rae.az < this.minAz || rae.az > this.maxAz) {
return false;
}

return true;
}

public isSatInFov(sat: Sat, date: Date = this.time): boolean {
return this.isRaeInFov(this.getRae(sat, date));
}

public calculatePasses(planningInterval: number, sat: Sat, date: Date = this.time) {
let isInViewLast = false;
let maxElThisPass = 0;
const msnPlanPasses: Lookangles[] = [];
const startTime = date.getTime();

for (let timeOffset = 0; timeOffset < planningInterval; timeOffset++) {
const curTime = new Date(startTime + timeOffset * 1000);
const rae = this.getRae(sat, curTime);

// Radians to Degrees
rae.az *= 360 / TAU;
rae.el *= 360 / TAU;

const isInView = this.isRaeInFov(rae);

if (timeOffset === 0) {
// Propagate Backwards to get the previous pass
const oldRae = this.getRae(sat, new Date(Date.now() - 1 * 1000));

isInViewLast = this.isRaeInFov(oldRae);
}

const type = Sensor.getPassType(isInView, isInViewLast);

maxElThisPass = Math.max(maxElThisPass, rae.el);

if (type === PassType.ENTER || type === PassType.EXIT) {
const pass = <Lookangles>{
type,
time: curTime,
az: rae.az,
el: rae.el,
rng: rae.rng,
};

// Only set maxEl for EXIT passes
if (type === PassType.EXIT) {
pass.maxElPass = maxElThisPass;
}

msnPlanPasses.push(pass);
maxElThisPass = 0;
}

isInViewLast = isInView;
}

return msnPlanPasses;
}

private static getPassType(isInView: boolean, isInViewLast: boolean) {
let type = PassType.OUT_OF_VIEW;

if (isInView && !isInViewLast) {
type = PassType.ENTER;
} else if (!isInView && isInViewLast) {
type = PassType.EXIT;
} else if (isInView && isInViewLast) {
type = PassType.IN_VIEW;
}

return type;
}
}
66 changes: 66 additions & 0 deletions test/sat/__snapshots__/sensor.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Basic Sensor functionality should create a lookangles array 1`] = `
Array [
Object {
"az": 185.0321047837688,
"el": 0.018932126219342378,
"rng": 2320.7003088160936,
"time": 2022-08-25T07:51:46.000Z,
"type": 0,
},
Object {
"az": 286.6828392807201,
"el": -0.03143720527465417,
"maxElPass": 8.887905466445714,
"rng": 2346.1443165688643,
"time": 2022-08-25T08:00:03.000Z,
"type": 2,
},
Object {
"az": 126.07032765927691,
"el": 0.011464577747703686,
"rng": 2327.848319797019,
"time": 2022-08-25T09:27:12.000Z,
"type": 0,
},
Object {
"az": 338.51245540232674,
"el": -0.004776656652769985,
"maxElPass": 28.303560760507143,
"rng": 2353.0795991791333,
"time": 2022-08-25T09:37:34.000Z,
"type": 2,
},
Object {
"az": 24.30450924794974,
"el": 0.021759132371179508,
"rng": 2365.173163410431,
"time": 2022-08-25T19:22:17.000Z,
"type": 0,
},
Object {
"az": 230.35934291906204,
"el": -0.029094115923272833,
"maxElPass": 34.603325622933625,
"rng": 2350.6620737684816,
"time": 2022-08-25T19:32:54.000Z,
"type": 2,
},
Object {
"az": 77.30327662230586,
"el": 0.009671715968386793,
"rng": 2358.781639064529,
"time": 2022-08-25T21:00:08.000Z,
"type": 0,
},
Object {
"az": 171.14122538519032,
"el": -0.04352163512801034,
"maxElPass": 7.334113831175358,
"rng": 2346.370746417976,
"time": 2022-08-25T21:08:00.000Z,
"type": 2,
},
]
`;
Loading

0 comments on commit 90d0d2e

Please sign in to comment.