diff --git a/examples/commonjs/satellite-js-migration.cjs b/examples/commonjs/satellite-js-migration.cjs index a751ca4..0cd0859 100644 --- a/examples/commonjs/satellite-js-migration.cjs +++ b/examples/commonjs/satellite-js-migration.cjs @@ -69,6 +69,15 @@ const elevationDegrees = lookAngles.elevationDegrees; // Degrees const rangeSat = lookAngles.range; // Kilometers const rangeRate = lookAngles.rangeRate; // Kilometers/Second +// There is a built in cache to allow fast retrieval of repeated calculations. +// This means you can make repeat calls to `.rae()` for minimal performance hit. +const rangeCache = satellite.rae(observer, exampleDate).range; +const azimuthCached = satellite.rae(observer, exampleDate).azimuth; +const elevationCached = satellite.rae(observer, exampleDate).elevation; +const latitudeCached = satellite.lla(exampleDate).lat; +const longitudeCached = satellite.lla(exampleDate).lon; +const heightCached = satellite.lla(exampleDate).alt; + // Geodetic coords are accessed via `longitude`, `latitude`, `height`. const longitude = positionGd.lon; // longitude is in degrees const latitude = positionGd.lat; // latitude is in degrees diff --git a/examples/es6/satellite-js-migration.mjs b/examples/es6/satellite-js-migration.mjs index c630537..38f24dc 100644 --- a/examples/es6/satellite-js-migration.mjs +++ b/examples/es6/satellite-js-migration.mjs @@ -69,6 +69,15 @@ const elevationDegrees = lookAngles.elevationDegrees; // Degrees const rangeSat = lookAngles.range; // Kilometers const rangeRate = lookAngles.rangeRate; // Kilometers/Second +// There is a built in cache to allow fast retrieval of repeated calculations. +// This means you can make repeat calls to `.rae()` for minimal performance hit. +const rangeCache = satellite.rae(observer, exampleDate).range; +const azimuthCached = satellite.rae(observer, exampleDate).azimuth; +const elevationCached = satellite.rae(observer, exampleDate).elevation; +const latitudeCached = satellite.lla(exampleDate).lat; +const longitudeCached = satellite.lla(exampleDate).lon; +const heightCached = satellite.lla(exampleDate).alt; + // Geodetic coords are accessed via `longitude`, `latitude`, `height`. const longitude = positionGd.lon; // longitude is in degrees const latitude = positionGd.lat; // latitude is in degrees diff --git a/examples/typescript/satellite-js-migration.ts b/examples/typescript/satellite-js-migration.ts index 6647c20..3f9ebb4 100644 --- a/examples/typescript/satellite-js-migration.ts +++ b/examples/typescript/satellite-js-migration.ts @@ -94,6 +94,17 @@ const elevationDegrees = lookAngles.elevationDegrees; // Typed as Degrees const rangeSat = lookAngles.range; // Typed as Kilometers const rangeRate = lookAngles.rangeRate; // Kilometers/Second +/* + * There is a built in cache to allow fast retrieval of repeated calculations. + * This means you can make repeat calls to `.rae()` for minimal performance hit. + */ +const rangeCache = satellite.rae(observer, exampleDate).range; +const azimuthCached = satellite.rae(observer, exampleDate).azimuth; +const elevationCached = satellite.rae(observer, exampleDate).elevation; +const latitudeCached = satellite.lla(exampleDate).lat; +const longitudeCached = satellite.lla(exampleDate).lon; +const heightCached = satellite.lla(exampleDate).alt; + // Geodetic coords are accessed via `longitude`, `latitude`, `height`. const longitude = positionGd.lon; // Longitude is in Degrees const latitude = positionGd.lat; // Latitude is in Degrees diff --git a/src/interfaces/SatelliteParams.ts b/src/interfaces/SatelliteParams.ts index 2f702fe..1bd6cb6 100644 --- a/src/interfaces/SatelliteParams.ts +++ b/src/interfaces/SatelliteParams.ts @@ -1,9 +1,5 @@ import { EciVec3, SpaceObjectType, TleLine1, TleLine2 } from '../types/types'; -/** - * TODO: Reduce unnecessary calls to calculateTimeVariables using optional - * parameters and caching. - */ /** * Information about a space object. */ diff --git a/src/objects/BaseObject.ts b/src/objects/BaseObject.ts index 8593500..4cda5b4 100644 --- a/src/objects/BaseObject.ts +++ b/src/objects/BaseObject.ts @@ -36,7 +36,6 @@ export class BaseObject { position: EciVec3; // Where is the object totalVelocity: number; // How fast is the object moving velocity: EciVec3; // How fast is the object moving - time: Date; // When is the object active = true; // Is the object active constructor(info: BaseObjectParams) { @@ -57,8 +56,6 @@ export class BaseObject { z: 0, }; // Default to 0 velocity until velocity is calculated this.totalVelocity = Math.sqrt(this.velocity.x ** 2 + this.velocity.y ** 2 + this.velocity.z ** 2); - - this.time = info.time ?? new Date(); } /** diff --git a/src/objects/GroundPosition.ts b/src/objects/GroundPosition.ts index a1edb29..8af3682 100644 --- a/src/objects/GroundPosition.ts +++ b/src/objects/GroundPosition.ts @@ -25,7 +25,7 @@ export class GroundPosition extends BaseObject { return false; } - rae(sat: Satellite, date: Date = this.time): RaeVec3 { + rae(sat: Satellite, date: Date = new Date()): RaeVec3 { return sat.raeOpt(this, date); } @@ -38,18 +38,12 @@ export class GroundPosition extends BaseObject { return llaRad2ecf(this.llaRad()); } - eci(date: Date = this.time): EciVec3 { + eci(date: Date = new Date()): EciVec3 { const { gmst } = calcGmst(date); return lla2eci(this.llaRad(), gmst); } - setTime(date: Date): this { - this.time = date; - - return this; - } - llaRad(): LlaVec3 { return { lat: (this.lat * DEG2RAD) as Radians, diff --git a/src/objects/Satellite.ts b/src/objects/Satellite.ts index 5ccfe23..50fede1 100644 --- a/src/objects/Satellite.ts +++ b/src/objects/Satellite.ts @@ -60,11 +60,6 @@ import { dopplerFactor } from './../utils/functions'; import { BaseObject } from './BaseObject'; import { GroundPosition } from './GroundPosition'; -/** - * TODO: #2 Reduce unnecessary calls to calculateTimeVariables using optional - * parameters and caching. - */ - /** * Represents a satellite object with orbital information and methods for calculating its position and other properties. */ @@ -182,7 +177,7 @@ export class Satellite extends BaseObject { * * @optimized */ - az(observer: GroundPosition, date: Date = this.time): Degrees { + az(observer: GroundPosition, date: Date = new Date()): Degrees { return (this.raeOpt(observer, date).az * RAD2DEG) as Degrees; } @@ -192,7 +187,7 @@ export class Satellite extends BaseObject { * * @expanded */ - rae(observer: GroundPosition, date: Date = this.time): RAE { + rae(observer: GroundPosition, date: Date = new Date()): RAE { const rae = this.raeOpt(observer, date); const rae2 = this.raeOpt(observer, new Date(date.getTime() + 1000)); const epoch = new EpochUTC(date.getTime()); @@ -216,7 +211,7 @@ export class Satellite extends BaseObject { * * @optimized */ - ecf(date: Date = this.time): EcfVec3 { + ecf(date: Date = new Date()): EcfVec3 { const { gmst } = Satellite.calculateTimeVariables(date); return eci2ecf(this.eci(date).position, gmst); @@ -227,7 +222,7 @@ export class Satellite extends BaseObject { * * @optimized */ - eci(date: Date = this.time): PosVel { + eci(date: Date = new Date()): PosVel { const { m } = Satellite.calculateTimeVariables(date, this.satrec); if (!m) { @@ -249,7 +244,7 @@ export class Satellite extends BaseObject { * @returns The J2000 coordinates for the specified date. * @throws Error if propagation fails. */ - getJ2000(date: Date = this.time): J2000 { + getJ2000(date: Date = new Date()): J2000 { const { m } = Satellite.calculateTimeVariables(date, this.satrec); if (!m) { @@ -276,29 +271,30 @@ export class Satellite extends BaseObject { * * @optimized */ - el(observer: GroundPosition, date: Date = this.time): Degrees { + el(observer: GroundPosition, date: Date = new Date()): Degrees { return (this.raeOpt(observer, date).el * RAD2DEG) as Degrees; } /** * Calculates LLA position at a given time. */ - lla(date: Date = this.time): LlaVec3 { + lla(date: Date = new Date()): LlaVec3 { const { gmst } = Satellite.calculateTimeVariables(date, this.satrec); const pos = this.eci(date).position; + const lla = eci2lla(pos, gmst); - return eci2lla(pos, gmst); + return lla; } - getGeodetic(date: Date = this.time): Geodetic { + getGeodetic(date: Date = new Date()): Geodetic { return this.getJ2000(date).toITRF().toGeodetic(); } - getITRF(date: Date = this.time): ITRF { + getITRF(date: Date = new Date()): ITRF { return this.getJ2000(date).toITRF(); } - getRIC(reference: Satellite, date: Date = this.time): RIC { + getRIC(reference: Satellite, date: Date = new Date()): RIC { return RIC.fromJ2000(this.getJ2000(date), reference.getJ2000(date)); } @@ -307,7 +303,7 @@ export class Satellite extends BaseObject { * * @optimized */ - raeOpt(observer: GroundPosition, date: Date = this.time): RaeVec3 { + raeOpt(observer: GroundPosition, date: Date = new Date()): RaeVec3 { const { gmst } = Satellite.calculateTimeVariables(date, this.satrec); const eci = this.eci(date).position; const ecf = eci2ecf(eci, gmst); @@ -320,7 +316,7 @@ export class Satellite extends BaseObject { * * @optimized */ - range(observer: GroundPosition, date: Date = this.time): Kilometers { + range(observer: GroundPosition, date: Date = new Date()): Kilometers { return this.raeOpt(observer, date).rng; } @@ -350,20 +346,6 @@ export class Satellite extends BaseObject { return dopplerFactor(observer.eci(date), position.position, position.velocity); } - /** - * Propagates the satellite position to the given date using the SGP4 model. - * - * This method changes the position and time properties of the satellite object. - */ - propagateTo(date: Date): this { - const pv = this.eci(date); - - this.position = pv.position as EciVec3; - this.time = date; - - return this; - } - /** * Calculates the time variables for a given date relative to the TLE epoch. * @param {Date} date Date to calculate diff --git a/src/objects/Sensor.ts b/src/objects/Sensor.ts index 70597ce..d54323f 100644 --- a/src/objects/Sensor.ts +++ b/src/objects/Sensor.ts @@ -70,7 +70,7 @@ export class Sensor extends GroundPosition { return true; } - calculatePasses(planningInterval: number, sat: Satellite, date: Date = this.time) { + calculatePasses(planningInterval: number, sat: Satellite, date: Date = new Date()) { let isInViewLast = false; let maxElThisPass = 0; const msnPlanPasses: Lookangle[] = []; @@ -139,16 +139,10 @@ export class Sensor extends GroundPosition { return true; } - isSatInFov(sat: Satellite, date: Date = this.time): boolean { + isSatInFov(sat: Satellite, date: Date = new Date()): boolean { return this.isRaeInFov(this.rae(sat, date)); } - setTime(date: Date): this { - this.time = date; - - return this; - } - isDeepSpace(): boolean { return this.maxRng > 6000; } diff --git a/src/objects/Star.ts b/src/objects/Star.ts index 3bc72c4..2c1e29c 100644 --- a/src/objects/Star.ts +++ b/src/objects/Star.ts @@ -66,7 +66,7 @@ export class Star extends BaseObject { this.vmag = info.vmag; } - eci(lla: LlaVec3 = { lat: 180, lon: 0, alt: 0 }, date: Date = this.time): EciVec3 { + eci(lla: LlaVec3 = { lat: 180, lon: 0, alt: 0 }, date: Date = new Date()): EciVec3 { const rae = this.rae(lla, date); const { gmst } = Star.calculateTimeVariables_(date); @@ -76,7 +76,7 @@ export class Star extends BaseObject { rae( lla: LlaVec3 = { lat: 180, lon: 0, alt: 0 }, - date: Date = this.time, + date: Date = new Date(), ): RaeVec3 { const starPos = Celestial.azEl(date, lla.lat, lla.lon, this.ra, this.dec); diff --git a/src/transforms/TransformCache.ts b/src/transforms/TransformCache.ts new file mode 100644 index 0000000..6ace5f1 --- /dev/null +++ b/src/transforms/TransformCache.ts @@ -0,0 +1,24 @@ +export class TransformCache { + private static cache_: Map = new Map(); + + static get(key: string): unknown { + const value = this.cache_.get(key); + + if (value) { + // eslint-disable-next-line no-console + console.log(`Cache hit for ${key}`); + } + + return value; + } + static add(key: string, value: unknown) { + this.cache_.set(key, value); + + // Max of 1000 items in the cache + if (this.cache_.size > 1000) { + const firstKey = this.cache_.keys().next().value; + + this.cache_.delete(firstKey); + } + } +} diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index 1b12193..779e914 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -52,6 +52,7 @@ import { Sgp4, RadarSensor, } from '..'; +import { TransformCache } from './TransformCache'; /** * Converts Azimuth and Elevation to U and V. @@ -137,11 +138,22 @@ export function eci2ecf(eci: EciVec3, gmst: number): EcfVec /** * EciToGeodetic converts eci coordinates to lla coordinates + * + * @cached + * * @param {vec3} eci takes xyz coordinates * @param {number} gmst takes a number in gmst time * @returns {array} array containing lla coordinates */ export function eci2lla(eci: EciVec3, gmst: number): LlaVec3 { + // Check cache + const key = `${gmst},${eci.x},${eci.y},${eci.z}`; + const cached = TransformCache.get(key); + + if (cached) { + return cached as LlaVec3; + } + // http://www.celestrak.com/columns/v02n03/ const a = 6378.137; const b = 6356.7523142; @@ -173,7 +185,11 @@ export function eci2lla(eci: EciVec3, gmst: number): LlaVec3lon, lat: lat, alt: alt }; + const lla = { lon: lon, lat: lat, alt: alt }; + + TransformCache.add(key, lla); + + return lla; } /** @@ -236,11 +252,22 @@ export function lla2ecf(lla: LlaVec3, gmst: GreenwichMeanSiderealTime): EciVec3 { + // Check cache + const key = `${gmst},${lla.lat},${lla.lon},${lla.alt}`; + const cached = TransformCache.get(key); + + if (cached) { + return cached as EciVec3; + } + const { lat, lon, alt } = lla; const cosLat = Math.cos(lat); @@ -251,7 +278,11 @@ export function lla2eci(lla: LlaVec3, gmst: GreenwichMeanSi const y = (Earth.radiusMean + alt) * cosLat * sinLon; const z = (Earth.radiusMean + alt) * sinLat; - return { x, y, z } as EciVec3; + const eci = { x, y, z } as EciVec3; + + TransformCache.add(key, eci); + + return eci; } /** @@ -360,6 +391,8 @@ export function rae2ecf(rae: RaeVec3, lla: LlaVec3 /** * Converts a vector from RAE (Range, Azimuth, Elevation) coordinates to ECI (Earth-Centered Inertial) coordinates. * + * @cached + * * @param rae The vector in RAE coordinates. * @param lla The vector in LLA (Latitude, Longitude, Altitude) coordinates. * @param gmst The Greenwich Mean Sidereal Time. @@ -370,9 +403,19 @@ export function rae2eci( lla: LlaVec3, gmst: number, ): EciVec3 { + // Check cache + const key = `${gmst},${rae.rng},${rae.az},${rae.el},${lla.lat},${lla.lon},${lla.alt}`; + const cached = TransformCache.get(key); + + if (cached) { + return cached as EciVec3; + } + const ecf = rae2ecf(rae, lla); const eci = ecf2eci(ecf, gmst); + TransformCache.add(key, eci); + return eci; } @@ -479,16 +522,29 @@ export function ecfRad2rae(lla: LlaVec3, ecf: EcfV * Converts Earth-Centered Fixed (ECF) coordinates to Right Ascension (RA), * Elevation (E), and Azimuth (A) coordinates. * + * @cached + * * @param lla The Latitude, Longitude, and Altitude (LLA) coordinates. * @param ecf The Earth-Centered Fixed (ECF) coordinates. * @returns The Right Ascension (RA), Elevation (E), and Azimuth (A) coordinates. */ export function ecf2rae(lla: LlaVec3, ecf: EcfVec3): RaeVec3 { + // Check cache + const key = `${lla.lat},${lla.lon},${lla.alt},${ecf.x},${ecf.y},${ecf.z}`; + const cached = TransformCache.get(key); + + if (cached) { + return cached as RaeVec3; + } + const { lat, lon } = lla; const latRad = (lat * DEG2RAD) as Radians; const lonRad = (lon * DEG2RAD) as Radians; + const rae = ecfRad2rae({ lat: latRad, lon: lonRad, alt: lla.alt }, ecf); - return ecfRad2rae({ lat: latRad, lon: lonRad, alt: lla.alt }, ecf); + TransformCache.add(key, rae); + + return rae; } export const jday = (year?: number, mon?: number, day?: number, hr?: number, minute?: number, sec?: number) => { @@ -543,11 +599,11 @@ export function calcGmst(date: Date): { gmst: GreenwichMeanSiderealTime; j: numb return { gmst, j }; } -// Create a cache for eci2rae -const eci2raeCache = new Map>(); - /** * Converts ECI coordinates to RAE (Right Ascension, Azimuth, Elevation) coordinates. + * + * @cached + * * @param {Date} now - Current date and time. * @param {EciArr3} eci - ECI coordinates of the satellite. * @param {SensorObject} sensor - Sensor object containing observer's geodetic coordinates. @@ -559,10 +615,10 @@ export function eci2rae(now: Date, eci: EciVec3, sensor: Sensor): Ra // Check cache const key = `${gmst},${eci.x},${eci.y},${eci.z},${sensor.lat},${sensor.lon},${sensor.alt}`; - const cached = eci2raeCache.get(key); + const cached = TransformCache.get(key); if (cached) { - return cached; + return cached as RaeVec3; } const positionEcf = eci2ecf(eci, gmst); @@ -574,12 +630,7 @@ export function eci2rae(now: Date, eci: EciVec3, sensor: Sensor): Ra const rae = ecfRad2rae(lla, positionEcf); - // Add to cache - eci2raeCache.set(key, rae); - // Ensure eci2raeCache isnt too big - if (eci2raeCache.size > 1000) { - eci2raeCache.delete(eci2raeCache.keys().next().value); - } + TransformCache.add(key, rae); return rae; } diff --git a/test/objects/BaseObject.test.ts b/test/objects/BaseObject.test.ts index 5089839..d7d45a4 100644 --- a/test/objects/BaseObject.test.ts +++ b/test/objects/BaseObject.test.ts @@ -32,7 +32,6 @@ describe('BaseObject', () => { expect(baseObject.velocity.x).toBe(mockVelocity.x); expect(baseObject.velocity.y).toBe(mockVelocity.y); expect(baseObject.velocity.z).toBe(mockVelocity.z); - expect(baseObject.time).toBe(info.time); expect(baseObject.active).toBe(true); }); @@ -48,7 +47,6 @@ describe('BaseObject', () => { expect(baseObject.velocity.x).toBe(0); expect(baseObject.velocity.y).toBe(0); expect(baseObject.velocity.z).toBe(0); - expect(baseObject.time).toBeInstanceOf(Date); expect(baseObject.active).toBe(true); });