Skip to content

Commit

Permalink
test: ✅ add tests and documentation to FormatTle
Browse files Browse the repository at this point in the history
  • Loading branch information
thkruz committed Jan 14, 2024
1 parent 22ea0a8 commit dbed6a1
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 51 deletions.
42 changes: 27 additions & 15 deletions examples/satellite-js-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@ const exampleDate = new Date(1705109326817);

// Sample TLE
const tle1 = '1 56006U 23042W 24012.45049317 .00000296 00000-0 36967-4 0 9992' as TleLine1;
const tle2 = '2 56006 43.0043 13.3620 0001137 267.5965 92.4747 15.02542972 44491' as TleLine2;
const tle2 = '2 56006 143.0043 13.3620 0001137 267.5965 92.4747 15.02542972 44491' as TleLine2;

// Initialize a Satellite Object
const satellite = new Satellite({
tle1,
tle2,
});

const elements = satellite.getClassicalElements(new Date(1705109326817));
const elements = satellite.getClassicalElements(new Date(1705109326817)).toEquinoctialElements();

console.warn(elements);

// You can still propagate a satellite using time since epoch (in minutes), but it's not recommended.
/*
* You can still propagate a satellite using time since epoch (in minutes), but
* it's not recommended.
*/
const timeSinceTleEpochMinutes = 10;
const positionAndVelocity = Sgp4.propagate(satellite.satrec, timeSinceTleEpochMinutes);

Expand All @@ -42,28 +45,32 @@ const positionAndVelocity2 = satellite.eci(new Date(2024, 0, 1));
const positionAndVelocity3 = satellite.eci();

/*
* The position_velocity result is a key-value pair of ECI coordinates.
* These are the base results from which all other coordinates are derived.
* The position_velocity result is a key-value pair of ECI coordinates. These
* are the base results from which all other coordinates are derived.
*/
const positionEci = positionAndVelocity.position; // positionAndVelocity might be false
const velocityEci = positionAndVelocity.velocity; // typescript will error on this code

/*
* Unlike satellite.js using the eci method will ALWAYS return a result or throw
* an error if it can't propagate the satellite. No more checking for false and trying to handle
* a combined object and boolean result.
* an error if it can't propagate the satellite. No more checking for false and
* trying to handle a combined object and boolean result.
*/
const positionEci2 = satellite.eci().position; // This is correctly typed

// Set the Observer at 71°W, 41°N, 0.37 km altitude using DEGREES because who likes using Radians?
/*
* Set the Observer at 71°W, 41°N, 0.37 km altitude using DEGREES because who
* likes using Radians?
*/
const observer = new GroundPosition({
lon: -71.0308 as Degrees,
lat: 41.9613422 as Degrees,
alt: 0.37 as Kilometers,
});

/**
* You can still calculate GMST if you want to, but unlike satellite.js it's not required.
* You can still calculate GMST if you want to, but unlike satellite.js it's not
* required.
*/
const { gmst, j } = calcGmst(new Date());

Expand All @@ -80,10 +87,11 @@ let dopplerShiftedFrequency = uplinkFreq * dopplerFactor;
dopplerShiftedFrequency = satellite.applyDoppler(uplinkFreq, observer, exampleDate);

/**
* The coordinates are all stored in strongly typed key-value pairs.
* ECI and ECF are accessed by `x`, `y`, `z` properties.
* The coordinates are all stored in strongly typed key-value pairs. ECI and ECF
* are accessed by `x`, `y`, `z` properties.
*
* satellite.js generates Property 'x' does not exist on type 'boolean | { x: number; y: number; z: number; }'.
* satellite.js generates Property 'x' does not exist on type 'boolean | { x:
* number; y: number; z: number; }'.
*/
const position = satellite.eci(exampleDate).position;
const satelliteX = position.x; // This is typed as Kilometers
Expand Down Expand Up @@ -118,13 +126,17 @@ const height = positionGd.alt; // Height is in Kilometers
const longitudeRad = longitude * DEG2RAD;
const latitudeRad = latitude * DEG2RAD;
/**
* In TypeScript you need to label your units.
* This will help prevent you from passing the wrong units into functions.
* In TypeScript you need to label your units. This will help prevent you from
* passing the wrong units into functions.
*/
const longitudeRad2 = (longitude * DEG2RAD) as Radians;
const latitudeRad2 = (latitude * DEG2RAD) as Radians;

// lla2eci(positionGd, gmst); // Throws an error: Argument of type 'LlaVec3<Degrees, Kilometers>' is not assignable to parameter of type 'LlaVec3<Radians, Kilometers>'.
/*
* lla2eci(positionGd, gmst); // Throws an error: Argument of type
* 'LlaVec3<Degrees, Kilometers>' is not assignable to parameter of type
* 'LlaVec3<Radians, Kilometers>'.
*/
lla2eci(observer.llaRad(), gmst); // This is correctly typed

console.log('Satellite.js Migration Example');
Expand Down
2 changes: 1 addition & 1 deletion src/coordinate/ClassicalElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EpochUTC } from '../time/EpochUTC';
import { earthGravityParam, RAD2DEG, sec2min, secondsPerDay, TAU } from '../utils/constants';
import { clamp, matchHalfPlane, newtonNu } from '../utils/functions';
import { EquinoctialElements } from './EquinoctialElements';
import { OrbitRegime } from './OrbitRegime';
import { OrbitRegime } from '../enums/OrbitRegime';
import { PositionVelocity, StateVector } from './StateVector';
import { ClassicalElementsParams } from '../interfaces/ClassicalElementsParams';

Expand Down
132 changes: 98 additions & 34 deletions src/coordinate/FormatTle.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,20 @@
import type { Satellite } from '../objects';
import { StringifiedNumber, TleParams } from 'src/types/types';
import { Tle } from './Tle';

export type StringifiedNumber = `${number}.${number}`;

export type TleParams = {
sat?: Satellite;
inc: StringifiedNumber;
meanmo: StringifiedNumber;
rasc: StringifiedNumber;
argPe: StringifiedNumber;
meana: StringifiedNumber;
ecen: string;
epochyr: string;
epochday: string;
/** COSPAR International Designator */
intl: string;
/** alpha 5 satellite number */
scc: string;
};

/**
* A class containing static methods for formatting TLEs (Two-Line Elements).
*/
export abstract class FormatTle {
static argumentOfPerigee(argPe: number | string): StringifiedNumber {
if (typeof argPe === 'number') {
argPe = argPe.toString();
}

const argPeNum = parseFloat(argPe).toFixed(4);
const argPe0 = argPeNum.padStart(8, '0');

if (argPe0.length !== 8) {
throw new Error('argPe length is not 8');
}

return argPe0 as StringifiedNumber;
private constructor() {
// Static class
}

/**
* Creates a TLE (Two-Line Element) string based on the provided TleParams.
* @param tleParams - The parameters used to generate the TLE.
*
* @returns An object containing the TLE strings tle1 and tle2.
*/
static createTle(tleParams: TleParams): { tle1: string; tle2: string } {
const { inc, meanmo, rasc, argPe, meana, ecen, epochyr, epochday, intl } = tleParams;
const scc = Tle.convert6DigitToA5(tleParams.scc);
Expand All @@ -46,6 +26,7 @@ export abstract class FormatTle {
const argPeStr = FormatTle.argumentOfPerigee(argPe);
const meanaStr = FormatTle.meanAnomaly(meana);
const ecenStr = FormatTle.eccentricity(ecen);
const intlStr = intl.padEnd(8, ' ');

// M' and M'' are both set to 0 to put the object in a perfect stable orbit
let TLE1Ending = tleParams.sat ? tleParams.sat.tle1.substring(32, 71) : ' +.00000000 +00000+0 +00000-0 0 9990';
Expand All @@ -56,12 +37,44 @@ export abstract class FormatTle {
TLE1Ending = TLE1Ending[21] === ' ' ? FormatTle.setCharAt(TLE1Ending, 21, '+') : TLE1Ending;
TLE1Ending = TLE1Ending[32] === ' ' ? FormatTle.setCharAt(TLE1Ending, 32, '0') : TLE1Ending;

const tle1 = `1 ${scc}U ${intl} ${epochYrStr}${epochdayStr}${TLE1Ending}`;
const tle1 = `1 ${scc}U ${intlStr} ${epochYrStr}${epochdayStr}${TLE1Ending}`;
const tle2 = `2 ${scc} ${incStr} ${rascStr} ${ecenStr} ${argPeStr} ${meanaStr} ${meanmoStr} 00010`;

return { tle1, tle2 };
}

/**
* Converts the argument of perigee to a stringified number.
*
* @param argPe - The argument of perigee to be converted. Can be either a
* number or a string.
* @returns The argument of perigee as a stringified number.
*
* @throws Error if the length of the argument of perigee is not 8.
*/
static argumentOfPerigee(argPe: number | string): StringifiedNumber {
if (typeof argPe === 'number') {
argPe = argPe.toString();
}

const argPeNum = parseFloat(argPe).toFixed(4);
const argPe0 = argPeNum.padStart(8, '0');

if (argPe0.length !== 8) {
throw new Error('argPe length is not 8');
}

return argPe0 as StringifiedNumber;
}

/**
* Returns the eccentricity value of a given string.
*
* @param ecen - The string representing the eccentricity.
* @returns The eccentricity value.
*
* @throws Error if the length of the eccentricity string is not 7.
*/
static eccentricity(ecen: string): string {
let ecen0 = ecen.padEnd(9, '0');

Expand All @@ -77,6 +90,14 @@ export abstract class FormatTle {
return ecen0;
}

/**
* Converts the inclination value to a string representation.
*
* @param inc - The inclination value to be converted.
* @returns The string representation of the inclination value.
*
* @throws Error if the length of the converted value is not 8.
*/
static inclination(inc: number | string): StringifiedNumber {
if (typeof inc === 'number') {
inc = inc.toString();
Expand All @@ -92,6 +113,17 @@ export abstract class FormatTle {
return inc0 as StringifiedNumber;
}

/**
* Converts the mean anomaly to a string representation with 8 digits, padded
* with leading zeros.
* @param meana - The mean anomaly to be converted. Can be either a number or
* a string.
*
* @returns The mean anomaly as a string with 8 digits, padded with leading
* zeros.
*
* @throws Error if the length of the mean anomaly is not 8.
*/
static meanAnomaly(meana: number | string): StringifiedNumber {
if (typeof meana === 'number') {
meana = meana.toString();
Expand All @@ -107,6 +139,21 @@ export abstract class FormatTle {
return meana0 as StringifiedNumber;
}

/**
* Converts the mean motion value to a string representation with 8 decimal
* places. If the input is a number, it is converted to a string. If the input
* is already a string, it is parsed as a float and then converted to a string
* with 8 decimal places. The resulting string is padded with leading zeros to
* ensure a length of 11 characters. Throws an error if the resulting string
* does not have a length of 11 characters.
*
* @param meanmo - The mean motion value to be converted.
* @returns The string representation of the mean motion value with 8 decimal
* places and padded with leading zeros.
*
* @throws Error if the resulting string does not have a length of 11
* characters.
*/
static meanMotion(meanmo: number | string): StringifiedNumber {
if (typeof meanmo === 'number') {
meanmo = meanmo.toString();
Expand All @@ -122,6 +169,13 @@ export abstract class FormatTle {
return meanmo0 as StringifiedNumber;
}

/**
* Converts the right ascension value to a stringified number.
*
* @param rasc - The right ascension value to convert.
* @returns The stringified number representation of the right ascension.
* @throws Error if the length of the converted right ascension is not 8.
*/
static rightAscension(rasc: number | string): StringifiedNumber {
if (typeof rasc === 'number') {
rasc = rasc.toString();
Expand All @@ -137,7 +191,17 @@ export abstract class FormatTle {
return rasc0 as StringifiedNumber;
}

static setCharAt(str: string, index: number, chr: string) {
/**
* Sets a character at a specific index in a string. If the index is out of
* range, the original string is returned.
* @param str - The input string.
*
* @param index - The index at which to set the character.
*
* @param chr - The character to set at the specified index. @returns The
* modified string with the character set at the specified index.
*/
static setCharAt(str: string, index: number, chr: string): string {
if (index > str.length - 1) {
return str;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coordinate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export * from './FormatTle';
export * from './Geodetic';
export * from './ITRF';
export * from './J2000';
export * from './OrbitRegime';
export * from '../enums/OrbitRegime';
export * from './RelativeState';
export * from './RIC';
export * from './StateVector';
Expand Down
File renamed without changes.
18 changes: 18 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Satellite } from 'src/objects';
import { PassType } from '../enums/PassType';

/**
Expand Down Expand Up @@ -696,3 +697,20 @@ export type TleData = {
* Represents a set of data containing both Line 1 and Line 2 TLE information.
*/
export type TleDataFull = Line1Data & Line2Data;
export type StringifiedNumber = `${number}.${number}`;

export type TleParams = {
sat?: Satellite;
inc: StringifiedNumber;
meanmo: StringifiedNumber;
rasc: StringifiedNumber;
argPe: StringifiedNumber;
meana: StringifiedNumber;
ecen: string;
epochyr: string;
epochday: string;
/** COSPAR International Designator */
intl: string;
/** alpha 5 satellite number */
scc: string;
};
Loading

0 comments on commit dbed6a1

Please sign in to comment.