diff --git a/src/coordinate/Geodetic.ts b/src/coordinate/Geodetic.ts index 6c50204..a2d91a1 100644 --- a/src/coordinate/Geodetic.ts +++ b/src/coordinate/Geodetic.ts @@ -1,74 +1,173 @@ import { Earth } from '../body/Earth'; -import { AngularDistanceMethod } from '../main'; +import { AngularDistanceMethod, Degrees, GroundObject, Kilometers, Radians } from '../main'; import { Vector3D } from '../operations/Vector3D'; import { EpochUTC } from '../time/EpochUTC'; import { DEG2RAD, RAD2DEG } from '../utils/constants'; import { angularDistance } from '../utils/functions'; import { ITRF } from './ITRF'; -// / Geodetic coordinates. +/** + * This Geodetic class represents a geodetic coordinate in three-dimensional + * space, consisting of latitude, longitude, and altitude. It provides various + * methods to perform calculations and operations related to geodetic + * coordinates. + * + * This is a class for geodetic coordinates. This is related to the GroundObject + * class, which is used to represent an object on the surface of the Earth. + */ export class Geodetic { - latitude: number; - longitude: number; - altitude: number; - - constructor(latitude: number, longitude: number, altitude: number) { - this.latitude = latitude; - this.longitude = longitude; - this.altitude = altitude; + lat: Radians; + lon: Radians; + alt: Kilometers; + + constructor(latitude: Radians, longitude: Radians, altitude: Kilometers) { + if (Math.abs(latitude) > Math.PI / 2) { + // eslint-disable-next-line no-console + console.warn(longitude * RAD2DEG); + throw new RangeError('Latitude must be between -90° and 90° in Radians.'); + } + + if (Math.abs(longitude) > Math.PI) { + // eslint-disable-next-line no-console + console.warn(longitude * RAD2DEG); + throw new RangeError('Longitude must be between -180° and 180° in Radians.'); + } + + if (altitude < -Earth.radiusMean) { + throw new RangeError('Altitude cannot be less than -6378.137 km.'); + } + + this.lat = latitude; + this.lon = longitude; + this.alt = altitude; } - static fromDegrees(latDeg: number, lonDeg: number, alt: number): Geodetic { - return new Geodetic(latDeg * DEG2RAD, lonDeg * DEG2RAD, alt); + /** + * Creates a Geodetic object from latitude, longitude, and altitude values in + * degrees. + * @param latitude The latitude value in degrees. + * + * @param longitude The longitude value in degrees. + * + * @param altitude The altitude value in kilometers. + * + * @returns A Geodetic object representing the specified latitude, longitude, + * and altitude. + */ + static fromDegrees(latitude: Degrees, longitude: Degrees, altitude: Kilometers): Geodetic { + return new Geodetic((latitude * DEG2RAD) as Radians, (longitude * DEG2RAD) as Radians, altitude); } + /** + * Returns a string representation of the Geodetic object. + * + * @returns A string containing the latitude, longitude, and altitude of the + * Geodetic object. + */ toString(): string { return [ 'Geodetic', - ` Latitude: ${this.latitudeDegrees.toFixed(4)}°`, - ` Longitude: ${this.longitudeDegrees.toFixed(4)}°`, - ` Altitude: ${this.altitude.toFixed(3)} km`, + ` Latitude: ${this.latDeg.toFixed(4)}°`, + ` Longitude: ${this.lonDeg.toFixed(4)}°`, + ` Altitude: ${this.alt.toFixed(3)} km`, ].join('\n'); } - get latitudeDegrees(): number { - return this.latitude * RAD2DEG; + /** + * Gets the latitude in degrees. + * @returns The latitude in degrees. + */ + get latDeg(): number { + return this.lat * RAD2DEG; + } + + /** + * Gets the longitude in degrees. + * @returns The longitude in degrees. + */ + get lonDeg(): number { + return this.lon * RAD2DEG; } - get longitudeDegrees(): number { - return this.longitude * RAD2DEG; + /** + * Converts the geodetic coordinates to a ground position. + * @returns The ground position object. + */ + toGroundObject(): GroundObject { + return new GroundObject({ + lat: this.latDeg as Degrees, + lon: this.lonDeg as Degrees, + alt: this.alt, + }); } + /** + * Converts the geodetic coordinates to the International Terrestrial + * Reference Frame (ITRF) coordinates. + * @param epoch The epoch in UTC. @returns The ITRF coordinates. + */ toITRF(epoch: EpochUTC): ITRF { - const sLat = Math.sin(this.latitude); - const cLat = Math.cos(this.latitude); + const sLat = Math.sin(this.lat); + const cLat = Math.cos(this.lat); const nVal = Earth.radiusEquator / Math.sqrt(1 - Earth.eccentricitySquared * sLat * sLat); const r = new Vector3D( - (nVal + this.altitude) * cLat * Math.cos(this.longitude), - (nVal + this.altitude) * cLat * Math.sin(this.longitude), - (nVal * (1 - Earth.eccentricitySquared) + this.altitude) * sLat, + (nVal + this.alt) * cLat * Math.cos(this.lon), + (nVal + this.alt) * cLat * Math.sin(this.lon), + (nVal * (1 - Earth.eccentricitySquared) + this.alt) * sLat, ); return new ITRF(epoch, r, Vector3D.origin); } - angle(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): number { - return angularDistance(this.longitude, this.latitude, g.longitude, g.latitude, method); + /** + * Calculates the angle between two geodetic coordinates. + * @param g The geodetic coordinate to calculate the angle to. @param method + * The method to use for calculating the angular distance (optional, default + * is Haversine). @returns The angle between the two geodetic coordinates in + * radians. + */ + angle(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): Radians { + return angularDistance(this.lon, this.lat, g.lon, g.lat, method) as Radians; } - angleDegrees(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): number { - return this.angle(g, method) * RAD2DEG; + /** + * Calculates the angle in degrees between two Geodetic coordinates. + * @param g The Geodetic coordinate to calculate the angle with. @param method + * The method to use for calculating the angular distance (optional, default + * is Haversine). @returns The angle in degrees. + */ + angleDeg(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): Degrees { + return (this.angle(g, method) * RAD2DEG) as Degrees; } - distance(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): number { - return this.angle(g, method) * Earth.radiusMean; + /** + * Calculates the distance between two geodetic coordinates. + * @param g The geodetic coordinates to calculate the distance to. @param + * method The method to use for calculating the angular distance. Default is + * Haversine. @returns The distance between the two geodetic coordinates in + * kilometers. + */ + distance(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): Kilometers { + return (this.angle(g, method) * Earth.radiusMean) as Kilometers; } - fieldOfView(): number { - return Math.acos(Earth.radiusMean / (Earth.radiusMean + this.altitude)); + /** + * Calculates the field of view based on the altitude of the Geodetic object. + * @returns The field of view in radians. + */ + fieldOfView(): Radians { + return Math.acos(Earth.radiusMean / (Earth.radiusMean + this.alt)) as Radians; } - sight(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): boolean { + /** + * Determines if the current geodetic coordinate can see another geodetic + * coordinate. + * @param g The geodetic coordinate to check for visibility. @param method The + * method to use for calculating the angular distance (optional, default is + * Haversine). @returns A boolean indicating if the current coordinate can see + * the other coordinate. + */ + isInView(g: Geodetic, method: AngularDistanceMethod = AngularDistanceMethod.Haversine): boolean { const fov = Math.max(this.fieldOfView(), g.fieldOfView()); return this.angle(g, method) <= fov; diff --git a/src/coordinate/ITRF.ts b/src/coordinate/ITRF.ts index 1633dda..668b1e0 100644 --- a/src/coordinate/ITRF.ts +++ b/src/coordinate/ITRF.ts @@ -1,4 +1,5 @@ /* eslint-disable class-methods-use-this */ +import { Kilometers, Radians } from 'src/main'; import { Earth } from '../body/Earth'; import { Geodetic } from './Geodetic'; import { J2000 } from './J2000'; @@ -7,20 +8,19 @@ import { StateVector } from './StateVector'; /** * The International Terrestrial Reference Frame (ITRF) is a geocentric * reference frame for the Earth. It is the successor to the International - * Terrestrial Reference System (ITRS). The ITRF definition is maintained by - * the International Earth Rotation and Reference Systems Service (IERS). - * Several versions of ITRF exist, each with a different epoch, to address the - * issue of crustal motion. The latest version is ITRF2014, based on data - * collected from 1980 to 2014. + * Terrestrial Reference System (ITRS). The ITRF definition is maintained by the + * International Earth Rotation and Reference Systems Service (IERS). Several + * versions of ITRF exist, each with a different epoch, to address the issue of + * crustal motion. The latest version is ITRF2014, based on data collected from + * 1980 to 2014. * @see https://en.wikipedia.org/wiki/International_Terrestrial_Reference_Frame * * This is a geocentric coordinate system, also referenced as ECEF (Earth - * Centered Earth Fixed). It is a Cartesian coordinate system with the origin - * at the center of the Earth. The x-axis intersects the sphere of the Earth - * at 0° latitude (the equator) and 0° longitude (the Prime Meridian). The - * z-axis goes through the North Pole. The y-axis goes through 90° East - * longitude. - * @see https://en.wikipedia.org/wiki/Earth-centered,_Earth-fixed_coordinate_system + * Centered Earth Fixed). It is a Cartesian coordinate system with the origin at + * the center of the Earth. The x-axis intersects the sphere of the Earth at 0° + * latitude (the equator) and 0° longitude (the Prime Meridian). The z-axis goes + * through the North Pole. The y-axis goes through 90° East longitude. @see + * https://en.wikipedia.org/wiki/Earth-centered,_Earth-fixed_coordinate_system */ export class ITRF extends StateVector { get name(): string { @@ -92,7 +92,7 @@ export class ITRF extends StateVector { } const alt = r / Math.cos(lat) - sma * c; - return new Geodetic(lat, lon, alt); + return new Geodetic(lat as Radians, lon as Radians, alt as Kilometers); } /** diff --git a/src/objects/GroundObject.ts b/src/objects/GroundObject.ts new file mode 100644 index 0000000..87b768c --- /dev/null +++ b/src/objects/GroundObject.ts @@ -0,0 +1,141 @@ +import { + GroundPositionParams, + Degrees, + EcfVec3, + EciVec3, + Kilometers, + LlaVec3, + Radians, + RaeVec3, + calcGmst, + lla2eci, + llaRad2ecf, + DEG2RAD, + Geodetic, +} from '../main'; + +import { BaseObject } from './BaseObject'; +import { Satellite } from './Satellite'; + +export class GroundObject extends BaseObject { + name = 'Unknown Ground Position'; + lat: Degrees; + lon: Degrees; + alt: Kilometers; + + constructor(info: GroundPositionParams) { + super(info); + + this.validateInputData_(info); + this.lat = info.lat; + this.lon = info.lon; + this.alt = info.alt; + } + + /** + * Calculates the relative azimuth, elevation, and range between this + * GroundObject and a Satellite. + * @param satellite The Satellite object. @param date The date for which to + * calculate the RAE values. Defaults to the current date. @returns The + * relative azimuth, elevation, and range values in kilometers and degrees. + */ + rae(satellite: Satellite, date: Date = new Date()): RaeVec3 { + return satellite.rae(this, date); + } + + /** + * Calculates ECF position at a given time. + * + * @optimized version of this.toGeodetic().toITRF().position; + */ + ecf(): EcfVec3 { + return llaRad2ecf(this.toGeodetic()); + } + + /** + * Calculates the Earth-Centered Inertial (ECI) position vector of the ground + * object at a given date. + * + * @optimzed version of this.toGeodetic().toITRF().toJ2000().position; + * + * @param date The date for which to calculate the ECI position vector. + * Defaults to the current date. + * + * @returns The ECI position vector of the ground object. + */ + eci(date: Date = new Date()): EciVec3 { + const { gmst } = calcGmst(date); + + return lla2eci(this.toGeodetic(), gmst); + } + + /** + * Converts the latitude, longitude, and altitude of the GroundObject to + * radians and kilometers. + * + * @optimized version of this.toGeodetic() without class instantiation for + * better performance and serialization. + * + * @returns An object containing the latitude, longitude, and altitude in + * radians and kilometers. + */ + llaRad(): LlaVec3 { + return { + lat: (this.lat * DEG2RAD) as Radians, + lon: (this.lon * DEG2RAD) as Radians, + alt: this.alt, + }; + } + + /** + * Creates a GroundObject object from a Geodetic position. + * + * @param geodetic The geodetic coordinates. + * @returns A new GroundObject object. + */ + static fromGeodetic(geodetic: Geodetic): GroundObject { + return new GroundObject({ + lat: geodetic.latDeg as Degrees, + lon: geodetic.lonDeg as Degrees, + alt: geodetic.alt, + }); + } + + /** + * Converts the ground position to geodetic coordinates. + * @returns The geodetic coordinates. + */ + toGeodetic(): Geodetic { + return Geodetic.fromDegrees(this.lat, this.lon, this.alt); + } + + /** + * Validates the input data for the GroundObject. + * @param info - The GroundPositionParams object containing the latitude, + * longitude, and altitude. @returns void + */ + private validateInputData_(info: GroundPositionParams) { + this.validateParameter_(info.lat, -90, 90, 'Invalid latitude - must be between -90 and 90'); + this.validateParameter_(info.lon, -180, 180, 'Invalid longitude - must be between -180 and 180'); + this.validateParameter_(info.alt, 0, null, 'Invalid altitude - must be greater than 0'); + } + + /** + * Validates a parameter value against a minimum and maximum value. + * @template T - The type of the parameter value. @param value - The parameter + * value to validate. @param minValue - The minimum allowed value. If not + * provided, no minimum value check will be performed. @param maxValue - The + * maximum allowed value. If not provided, no maximum value check will be + * performed. @param errorMessage - The error message to throw if the value is + * outside the allowed range. @throws {Error} - Throws an error with the + * specified error message if the value is outside the allowed range. + */ + private validateParameter_(value: T, minValue: T, maxValue: T, errorMessage: string): void { + if (minValue && value < minValue) { + throw new Error(errorMessage); + } + if (maxValue && value > maxValue) { + throw new Error(errorMessage); + } + } +} diff --git a/src/objects/GroundPosition.ts b/src/objects/GroundPosition.ts deleted file mode 100644 index 8af3682..0000000 --- a/src/objects/GroundPosition.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { calcGmst, lla2eci, llaRad2ecf } from '../transforms'; -import { Degrees, EcfVec3, EciVec3, Kilometers, LlaVec3, Radians, RaeVec3 } from '../types/types'; -import { DEG2RAD } from '../utils/constants'; -import { GroundPositionParams } from './../interfaces/GroundPositionParams'; - -import { BaseObject } from './BaseObject'; -import { Satellite } from './Satellite'; - -export class GroundPosition extends BaseObject { - name = 'Unknown Ground Position'; - lat: Degrees; - lon: Degrees; - alt: Kilometers; - - constructor(info: GroundPositionParams) { - super(info); - - this.validateInputData_(info); - this.lat = info.lat; - this.lon = info.lon; - this.alt = info.alt; - } - - isSensor(): boolean { - return false; - } - - rae(sat: Satellite, date: Date = new Date()): RaeVec3 { - return sat.raeOpt(this, date); - } - - /** - * Calculates ECF position at a given time. - * - * @optimized - */ - ecf(): EcfVec3 { - return llaRad2ecf(this.llaRad()); - } - - eci(date: Date = new Date()): EciVec3 { - const { gmst } = calcGmst(date); - - return lla2eci(this.llaRad(), gmst); - } - - llaRad(): LlaVec3 { - return { - lat: (this.lat * DEG2RAD) as Radians, - lon: (this.lon * DEG2RAD) as Radians, - alt: this.alt, - }; - } - - private validateInputData_(info: GroundPositionParams) { - this.validateParameter_(info.lat, -90, 90, 'Invalid latitude - must be between -90 and 90'); - this.validateParameter_(info.lon, -180, 180, 'Invalid longitude - must be between -180 and 180'); - this.validateParameter_(info.alt, 0, null, 'Invalid altitude - must be greater than 0'); - } - - private validateParameter_(value: T, minValue: T, maxValue: T, errorMessage: string): void { - if (minValue && value < minValue) { - throw new Error(errorMessage); - } - if (maxValue && value > maxValue) { - throw new Error(errorMessage); - } - } -} diff --git a/src/objects/Satellite.ts b/src/objects/Satellite.ts index 8cfded6..b138989 100644 --- a/src/objects/Satellite.ts +++ b/src/objects/Satellite.ts @@ -58,7 +58,7 @@ import { import { DEG2RAD, MILLISECONDS_TO_DAYS, MINUTES_PER_DAY, RAD2DEG } from '../utils/constants'; import { dopplerFactor } from './../utils/functions'; import { BaseObject } from './BaseObject'; -import { GroundPosition } from './GroundPosition'; +import { GroundObject } from './GroundObject'; /** * Represents a satellite object with orbital information and methods for @@ -180,8 +180,8 @@ export class Satellite extends BaseObject { * * @optimized */ - az(observer: GroundPosition, date: Date = new Date()): Degrees { - return (this.raeOpt(observer, date).az * RAD2DEG) as Degrees; + az(observer: GroundObject, date: Date = new Date()): Degrees { + return (this.rae(observer, date).az * RAD2DEG) as Degrees; } /** @@ -190,9 +190,9 @@ export class Satellite extends BaseObject { * * @expanded */ - rae(observer: GroundPosition, date: Date = new Date()): RAE { - const rae = this.raeOpt(observer, date); - const rae2 = this.raeOpt(observer, new Date(date.getTime() + 1000)); + toRae(observer: GroundObject, date: Date = new Date()): RAE { + const rae = this.rae(observer, date); + const rae2 = this.rae(observer, new Date(date.getTime() + 1000)); const epoch = new EpochUTC(date.getTime()); const rangeRate = rae2.rng - rae.rng; const azimuthRate = rae2.az - rae.az; @@ -247,7 +247,7 @@ export class Satellite extends BaseObject { * @returns The J2000 coordinates for the specified date. @throws Error if * propagation fails. */ - getJ2000(date: Date = new Date()): J2000 { + toJ2000(date: Date = new Date()): J2000 { const { m } = Satellite.calculateTimeVariables(date, this.satrec); if (!m) { @@ -275,8 +275,8 @@ export class Satellite extends BaseObject { * * @optimized */ - el(observer: GroundPosition, date: Date = new Date()): Degrees { - return (this.raeOpt(observer, date).el * RAD2DEG) as Degrees; + el(observer: GroundObject, date: Date = new Date()): Degrees { + return (this.rae(observer, date).el * RAD2DEG) as Degrees; } /** @@ -290,24 +290,24 @@ export class Satellite extends BaseObject { return lla; } - getGeodetic(date: Date = new Date()): Geodetic { - return this.getJ2000(date).toITRF().toGeodetic(); + toGeodetic(date: Date = new Date()): Geodetic { + return this.toJ2000(date).toITRF().toGeodetic(); } - getITRF(date: Date = new Date()): ITRF { - return this.getJ2000(date).toITRF(); + toITRF(date: Date = new Date()): ITRF { + return this.toJ2000(date).toITRF(); } - getRIC(reference: Satellite, date: Date = new Date()): RIC { - return RIC.fromJ2000(this.getJ2000(date), reference.getJ2000(date)); + toRIC(reference: Satellite, date: Date = new Date()): RIC { + return RIC.fromJ2000(this.toJ2000(date), reference.toJ2000(date)); } - getTle(): Tle { + toTle(): Tle { return new Tle(this.tle1, this.tle2); } - getClassicalElements(date: Date = new Date()): ClassicalElements { - return this.getJ2000(date).toClassicalElements(); + toClassicalElements(date: Date = new Date()): ClassicalElements { + return this.toJ2000(date).toClassicalElements(); } /** @@ -316,7 +316,7 @@ export class Satellite extends BaseObject { * * @optimized */ - raeOpt(observer: GroundPosition, date: Date = new Date()): RaeVec3 { + rae(observer: GroundObject, date: Date = new Date()): RaeVec3 { const { gmst } = Satellite.calculateTimeVariables(date, this.satrec); const eci = this.eci(date).position; const ecf = eci2ecf(eci, gmst); @@ -330,8 +330,8 @@ export class Satellite extends BaseObject { * * @optimized */ - range(observer: GroundPosition, date: Date = new Date()): Kilometers { - return this.raeOpt(observer, date).rng; + range(observer: GroundObject, date: Date = new Date()): Kilometers { + return this.rae(observer, date).rng; } /** @@ -342,7 +342,7 @@ export class Satellite extends BaseObject { * calculate the Doppler effect. Optional, defaults to the current date. * @returns The frequency after applying the Doppler effect. */ - applyDoppler(freq: number, observer: GroundPosition, date?: Date): number { + applyDoppler(freq: number, observer: GroundObject, date?: Date): number { const doppler = this.dopplerFactor(observer, date); return freq * doppler; @@ -356,7 +356,7 @@ export class Satellite extends BaseObject { * not provided, the current date is used. @returns The calculated Doppler * factor. */ - dopplerFactor(observer: GroundPosition, date?: Date): number { + dopplerFactor(observer: GroundObject, date?: Date): number { const position = this.eci(date); return dopplerFactor(observer.eci(date), position.position, position.velocity); diff --git a/src/objects/Sensor.ts b/src/objects/Sensor.ts index d54323f..76af261 100644 --- a/src/objects/Sensor.ts +++ b/src/objects/Sensor.ts @@ -1,10 +1,10 @@ import { PassType } from '../enums/PassType'; import { SensorParams } from '../interfaces/SensorParams'; import { Degrees, Kilometers, Lookangle, RaeVec3, SpaceObjectType } from '../types/types'; -import { GroundPosition } from './GroundPosition'; +import { GroundObject } from './GroundObject'; import { Satellite } from './Satellite'; -export class Sensor extends GroundPosition { +export class Sensor extends GroundObject { minRng: Kilometers; minAz: Degrees; minEl: Degrees; diff --git a/src/objects/index.ts b/src/objects/index.ts index 7ca770c..2242d0c 100644 --- a/src/objects/index.ts +++ b/src/objects/index.ts @@ -1,6 +1,6 @@ export { BaseObject } from './BaseObject'; export type { RadarSensor } from './RadarSensor'; -export { GroundPosition } from './GroundPosition'; +export { GroundObject } from './GroundObject'; export { Satellite } from './Satellite'; export { Sensor } from './Sensor'; export { Star } from './Star'; diff --git a/src/observation/RAE.ts b/src/observation/RAE.ts index b3b9797..b93deca 100644 --- a/src/observation/RAE.ts +++ b/src/observation/RAE.ts @@ -67,8 +67,8 @@ export class RAE { const r = stateEcef.position.subtract(siteEcef.position); const rDot = stateEcef.velocity; const geo = siteEcef.toGeodetic(); - const p = r.rotZ(geo.longitude).rotY(po2 - geo.latitude); - const pDot = rDot.rotZ(geo.longitude).rotY(po2 - geo.latitude); + const p = r.rotZ(geo.lon).rotY(po2 - geo.lat); + const pDot = rDot.rotZ(geo.lon).rotY(po2 - geo.lat); const pS = p.x; const pE = p.y; const pZ = p.z; @@ -147,8 +147,8 @@ export class RAE { const cEl = Math.cos(newEl); const pSez = new Vector3D(-this.range * cEl * cAz, this.range * cEl * sAz, this.range * sEl); const rEcef = pSez - .rotY(-(po2 - geo.latitude)) - .rotZ(-geo.longitude) + .rotY(-(po2 - geo.lat)) + .rotZ(-geo.lon) .add(ecef.position); return new ITRF(this.epoch, rEcef, Vector3D.origin).toJ2000().position; @@ -181,8 +181,8 @@ export class RAE { this.range * cEl * cAz * this.azimuthRate, this.rangeRate * sEl + this.range * cEl * this.elevationRate, ); - const pEcef = pSez.rotY(-(po2 - geo.latitude)).rotZ(-geo.longitude); - const pDotEcef = pDotSez.rotY(-(po2 - geo.latitude)).rotZ(-geo.longitude); + const pEcef = pSez.rotY(-(po2 - geo.lat)).rotZ(-geo.lon); + const pDotEcef = pDotSez.rotY(-(po2 - geo.lat)).rotZ(-geo.lon); const rEcef = pEcef.add(ecef.position); return new ITRF(this.epoch, rEcef, pDotEcef).toJ2000(); diff --git a/test/coordinate/Geodetic.test.ts b/test/coordinate/Geodetic.test.ts new file mode 100644 index 0000000..39d75c7 --- /dev/null +++ b/test/coordinate/Geodetic.test.ts @@ -0,0 +1,165 @@ +import { Geodetic, AngularDistanceMethod, Degrees, EpochUTC, Kilometers, Radians } from '../../src/main'; + +describe('Geodetic', () => { + /* + * Creating a Geodetic object with valid latitude, longitude, and altitude + * values should succeed. + */ + it('should create a Geodetic object with valid latitude, longitude, and altitude values', () => { + const geodetic = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + + expect(geodetic).toMatchSnapshot(); + }); + + // Converting a Geodetic object from degrees to radians should succeed. + it('should convert a Geodetic object from degrees to radians', () => { + const geodetic = Geodetic.fromDegrees(45 as Degrees, 90 as Degrees, 0 as Kilometers); + + expect(geodetic).toMatchSnapshot(); + }); + + // Converting a Geodetic object to a string should succeed. + it('should convert a Geodetic object to a string', () => { + const geodetic = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const result = geodetic.toString(); + + expect(result).toMatchSnapshot(); + }); + + // Converting a Geodetic object to ITRF coordinates should succeed. + it('should convert a Geodetic object to ITRF coordinates', () => { + const geodetic = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const epoch = EpochUTC.fromDateTime(new Date('2024-01-14T14:39:39.914Z')); + const result = geodetic.toITRF(epoch); + + expect(result).toMatchSnapshot(); + }); + + /* + * Calculating the angle between two Geodetic objects using Haversine method + * should succeed. + */ + it('should calculate the angle between two Geodetic objects using Haversine method', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 0 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 3.1015 as Radians, 0 as Kilometers); + const result = geodetic1.angle(geodetic2); + + expect(result).toMatchSnapshot(); + }); + + /* + * Calculating the angle between two Geodetic objects using Spherical Law of + * Cosines method should succeed. + */ + it('should calculate the angle between two Geodetic objects using Spherical Law of Cosines method', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 0 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 3.1015 as Radians, 0 as Kilometers); + const result = geodetic1.angle(geodetic2, AngularDistanceMethod.Cosine); + + expect(result).toMatchSnapshot(); + }); + + /* + * Creating a Geodetic object with invalid latitude value should raise an + * exception. + */ + it('should raise an exception when creating a Geodetic object with invalid latitude value', () => { + expect(() => new Geodetic(100 as Radians, 1.5708 as Radians, 0 as Kilometers)).toThrow(); + }); + + /* + * Calculating the angle in degrees between two Geodetic objects using + * Haversine method should succeed. + */ + it('should calculate the angle in degrees between two Geodetic objects using Haversine method', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 3.1015 as Radians, 200 as Kilometers); + const angleDegrees = geodetic1.angleDeg(geodetic2, AngularDistanceMethod.Haversine); + + expect(angleDegrees).toMatchSnapshot(); + }); + + /* + * Calculating the angle in degrees between two Geodetic objects using + * Spherical Law of Cosines method should succeed. + */ + it('should calculate the angle in degrees between two Geodetic objects using Spherical Law of Cosines method', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 3.1015 as Radians, 200 as Kilometers); + const angleDegrees = geodetic1.angleDeg(geodetic2, AngularDistanceMethod.Cosine); + + expect(angleDegrees).toMatchSnapshot(); + }); + + /* + * Calculating the distance between two Geodetic objects using Haversine + * method should succeed. + */ + it('should calculate the distance between two Geodetic objects using Haversine method', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 0 as Kilometers); + const geodetic2 = new Geodetic(0 as Radians, 0 as Radians, 0 as Kilometers); + const distance = geodetic1.distance(geodetic2, AngularDistanceMethod.Haversine); + + expect(distance).toMatchSnapshot(); + }); + + /* + * Calculating the distance between two Geodetic objects using Spherical Law + * of Cosines method should succeed. + */ + it('should calculate the distance between two Geodetic objects using Spherical Law of Cosines method', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 0 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const distance = geodetic1.distance(geodetic2, AngularDistanceMethod.Cosine); + + expect(distance).toMatchSnapshot(); + }); + + /* + * Calculating the angle between two Geodetic objects with identical + * coordinates should return 0 radians. + */ + it('should return 0 radians when angle between two Geodetic objects with identical coordinates', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 200 as Kilometers); + const angle = geodetic1.angle(geodetic2); + + expect(angle).toBe(0); + }); + + /* + * Calculating the angle in degrees between two Geodetic objects with + * identical coordinates should return 0 degrees. + */ + it('should return 0 degrees when angle in degrees between two Geodetic objects with identical coordinates', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 200 as Kilometers); + const angleDegrees = geodetic1.angleDeg(geodetic2); + + expect(angleDegrees).toBe(0); + }); + + /* + * Calculating the distance between two Geodetic objects with identical + * coordinates should return 0 kilometers. + */ + it('should return 0 when calculating the distance between two Geodetic objects with identical coordinates', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const distance = geodetic1.distance(geodetic2); + + expect(distance).toBe(0); + }); + + /* + * Checking if a Geodetic object is in view of another Geodetic object with + * identical coordinates should return True. + */ + it('should return true if a Geodetic object is in view of another Geodetic object with identical coordinates', () => { + const geodetic1 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const geodetic2 = new Geodetic(0.7854 as Radians, 1.5708 as Radians, 100 as Kilometers); + const result = geodetic1.isInView(geodetic2); + + expect(result).toBe(true); + }); +}); diff --git a/test/coordinate/__snapshots__/Geodetic.test.ts.snap b/test/coordinate/__snapshots__/Geodetic.test.ts.snap new file mode 100644 index 0000000..4e559f4 --- /dev/null +++ b/test/coordinate/__snapshots__/Geodetic.test.ts.snap @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Geodetic should calculate the angle between two Geodetic objects using Haversine method 1`] = `1.0238932134660375`; + +exports[`Geodetic should calculate the angle between two Geodetic objects using Spherical Law of Cosines method 1`] = `1.0238932134660375`; + +exports[`Geodetic should calculate the angle in degrees between two Geodetic objects using Haversine method 1`] = `58.66475980369142`; + +exports[`Geodetic should calculate the angle in degrees between two Geodetic objects using Spherical Law of Cosines method 1`] = `58.66475980369142`; + +exports[`Geodetic should calculate the distance between two Geodetic objects using Haversine method 1`] = `10007.572625484478`; + +exports[`Geodetic should calculate the distance between two Geodetic objects using Spherical Law of Cosines method 1`] = `0.0000949354182511178`; + +exports[`Geodetic should convert a Geodetic object from degrees to radians 1`] = ` +Geodetic { + "alt": 0, + "lat": 0.7853981633974483, + "lon": 1.5707963267948966, +} +`; + +exports[`Geodetic should convert a Geodetic object to ITRF coordinates 1`] = ` +ITRF { + "epoch": EpochUTC { + "posix": 1705243179.914, + }, + "position": Vector3D { + "x": -0.01685374002219863, + "y": 4588.292662103151, + "z": 4558.066993511299, + }, + "velocity": Vector3D { + "x": 0, + "y": 0, + "z": 0, + }, +} +`; + +exports[`Geodetic should convert a Geodetic object to a string 1`] = ` +"Geodetic + Latitude: 45.0001° + Longitude: 90.0002° + Altitude: 100.000 km" +`; + +exports[`Geodetic should create a Geodetic object with valid latitude, longitude, and altitude values 1`] = ` +Geodetic { + "alt": 100, + "lat": 0.7854, + "lon": 1.5708, +} +`; diff --git a/test/index.test.ts b/test/index.test.ts index 975e459..3d14f93 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -23,7 +23,7 @@ describe('ootk-core', () => { expect(ootk.BaseObject).toBeDefined(); }); it('should export GroundPosition', () => { - expect(ootk.GroundPosition).toBeDefined(); + expect(ootk.GroundObject).toBeDefined(); }); it('should export Satellite', () => { expect(ootk.Satellite).toBeDefined(); diff --git a/test/objects/Satellite.test.ts b/test/objects/Satellite.test.ts index 4d0fac1..fc1e350 100644 --- a/test/objects/Satellite.test.ts +++ b/test/objects/Satellite.test.ts @@ -1,6 +1,6 @@ import { Degrees, - GroundPosition, + GroundObject, Kilometers, RAD2DEG, Satellite, @@ -70,7 +70,7 @@ describe('Basic Satellite functionality', () => { describe('Satellite', () => { let satellite: Satellite; - let observer: GroundPosition; + let observer: GroundObject; beforeEach(() => { const satelliteParams: SatelliteParams = { @@ -79,7 +79,7 @@ describe('Satellite', () => { }; satellite = new Satellite(satelliteParams); - observer = new GroundPosition(llaObserver); + observer = new GroundObject(llaObserver); }); // can be instantiated with valid input parameters it('should instantiate Satellite with valid input parameters', () => { @@ -88,7 +88,7 @@ describe('Satellite', () => { // can calculate and return RAE coordinates it('should calculate and return RAE coordinates', () => { - const rae = satellite.rae(observer, exampleDate); + const rae = satellite.toRae(observer, exampleDate); expect(rae).toMatchSnapshot(); }); @@ -116,14 +116,14 @@ describe('Satellite', () => { // can calculate and return Geodetic coordinates it('should calculate and return Geodetic coordinates when given a date', () => { - const geodetic = satellite.getGeodetic(exampleDate); + const geodetic = satellite.toGeodetic(exampleDate); expect(geodetic).toMatchSnapshot(); }); // can calculate and return ITRF coordinates it('should calculate and return ITRF coordinates when called', () => { - const itrfCoordinates = satellite.getITRF(exampleDate); + const itrfCoordinates = satellite.toITRF(exampleDate); expect(itrfCoordinates).toMatchSnapshot(); }); @@ -135,7 +135,7 @@ describe('Satellite', () => { tle2: '2 25544 51.6442 13.1247 0008036 23.6079 336.5377 15.48861704303602' as TleLine2, }); - const ric = satellite.getRIC(referenceSatellite, exampleDate); + const ric = satellite.toRIC(referenceSatellite, exampleDate); expect(ric).toMatchSnapshot(); }); diff --git a/test/objects/__snapshots__/Satellite.test.ts.snap b/test/objects/__snapshots__/Satellite.test.ts.snap index e30aa35..f94ff27 100644 --- a/test/objects/__snapshots__/Satellite.test.ts.snap +++ b/test/objects/__snapshots__/Satellite.test.ts.snap @@ -25,9 +25,9 @@ Object { exports[`Satellite should calculate and return Geodetic coordinates when given a date 1`] = ` Geodetic { - "altitude": 405.17244322847, - "latitude": 0.1099327125671112, - "longitude": 0.2809557331563047, + "alt": 405.17244322847, + "lat": 0.1099327125671112, + "lon": 0.2809557331563047, } `; diff --git a/test/objects/sensor.test.ts b/test/objects/sensor.test.ts index 8d9ae4c..ceb5027 100644 --- a/test/objects/sensor.test.ts +++ b/test/objects/sensor.test.ts @@ -29,7 +29,7 @@ describe('Basic Sensor functionality', () => { expect(rae.rng).toMatchSnapshot(); // Verify it works in reverse - const rae2 = sat.raeOpt(sensor, dateObj); + const rae2 = sat.rae(sensor, dateObj); expect(rae2.az).toMatchSnapshot(); expect(rae2.el).toMatchSnapshot(); @@ -52,7 +52,7 @@ describe('Basic Sensor functionality', () => { }); const inView = sensor.isSatInFov(sat, dateObj); - const rae = sat.raeOpt(sensor, dateObj); + const rae = sat.rae(sensor, dateObj); const inView2 = sensor.isRaeInFov(rae); @@ -74,7 +74,7 @@ describe('Basic Sensor functionality', () => { }); const inView = sensor.isSatInFov(sat, dateObj); - const rae = sat.raeOpt(sensor, dateObj); + const rae = sat.rae(sensor, dateObj); const inView2 = sensor.isRaeInFov(rae);