Skip to content

Commit

Permalink
feat: ✨ integration with KeepTrack
Browse files Browse the repository at this point in the history
  • Loading branch information
thkruz committed Jan 7, 2024
1 parent 1682292 commit a8cc11d
Show file tree
Hide file tree
Showing 29 changed files with 958 additions and 254 deletions.
106 changes: 106 additions & 0 deletions examples/iod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { TLE } from './../src/coordinate/TLE';
import { RAE } from './../src/observation/RAE';
import { GibbsIOD } from './../src/orbit_determination/GibbsIOD';
import { HerrickGibbsIOD } from './../src/orbit_determination/HerrickGibbsIOD';
/* eslint-disable no-console */
import { J2000 } from './../src/coordinate';
import { calcGmst, DEG2RAD, Kilometers, lla2eci, Radians } from './../src/ootk';
import { Vector3D } from './../src/operations/Vector3D';
import { LambertIOD } from './../src/orbit_determination/LambertIOD';
import { EpochUTC } from './../src/time/EpochUTC';

const lambert = new LambertIOD();

const rae1 = {
t: EpochUTC.fromDateTime(new Date(1704628462000)),
rng: 1599.89,
az: 174,
el: 13.6,
};
const rae2 = {
t: EpochUTC.fromDateTime(new Date(1704628462000 + 10 * 1000)),
rng: 1568.76,
az: 171,
el: 14.2,
};
const rae3 = {
t: EpochUTC.fromDateTime(new Date(1704628462000 + 20 * 1000)),
rng: 1540.09,
az: 169,
el: 14.7,
};
const sensor = {
lat: (41.754785 * DEG2RAD) as Radians,
lon: (-70.539151 * DEG2RAD) as Radians,
alt: 0.085 as Kilometers,
};

const gmst = calcGmst(rae1.t.toDateTime());
const sensorEci = lla2eci(sensor, gmst.gmst);

const p1 = RAE.fromDegrees(rae1.t, rae1.rng, rae1.az, rae1.el).toStateVector(
new J2000(rae1.t, new Vector3D(sensorEci.x, sensorEci.y, sensorEci.z), Vector3D.origin),
);
const p2 = RAE.fromDegrees(rae2.t, rae2.rng, rae2.az, rae2.el).toStateVector(
new J2000(rae2.t, new Vector3D(sensorEci.x, sensorEci.y, sensorEci.z), Vector3D.origin),
);
const p3 = RAE.fromDegrees(rae3.t, rae3.rng, rae3.az, rae3.el).toStateVector(
new J2000(rae3.t, new Vector3D(sensorEci.x, sensorEci.y, sensorEci.z), Vector3D.origin),
);

const eci1 = {
x: -4901.84521484375,
y: -3592.527587890625,
z: 3322.875732421875,
};
const eci2 = {
x: -4847.90185546875,
y: -3631.424560546875,
z: 3359.44482421875,
};
const eci3 = {
x: -4793.376953125,
y: -3669.885986328125,
z: 3395.60986328125,
};
const p1b = new J2000(rae1.t, new Vector3D(eci1.x, eci1.y, eci1.z), Vector3D.origin);
const p2b = new J2000(rae2.t, new Vector3D(eci2.x, eci2.y, eci2.z), Vector3D.origin);
const p3b = new J2000(rae3.t, new Vector3D(eci3.x, eci3.y, eci3.z), Vector3D.origin);

const eci4 = { x: -4738.27734375, y: -3707.9072265625, z: 3431.36669921875 };
const p4b = new J2000(
EpochUTC.fromDateTime(new Date(1704628492000)),
new Vector3D(eci4.x, eci4.y, eci4.z),
Vector3D.origin,
);

const eci5 = { x: -4569.595703125, y: -3819.285400390625, z: 3536.144287109375 };
const p5b = new J2000(
EpochUTC.fromDateTime(new Date(1704628522000)),
new Vector3D(eci5.x, eci5.y, eci5.z),
Vector3D.origin,
);

const estimate = lambert.estimate(p1.position, p2.position, p1.epoch, p2.epoch);
const estimate2 = new HerrickGibbsIOD().solve(p1.position, p1.epoch, p2.position, p2.epoch, p3.position, p3.epoch);
const estimate3 = new GibbsIOD().solve(p1b.position, p2b.position, p3b.position, p2b.epoch, p3b.epoch);
const estimate4 = new HerrickGibbsIOD().solve(
p1b.position,
p1b.epoch,
p4b.position,
p4b.epoch,
p5b.position,
p5b.epoch,
);

TLE.fromClassicalElements(estimate.toClassicalElements());
TLE.fromClassicalElements(estimate2.toClassicalElements());
const tle = TLE.fromClassicalElements(estimate3.toClassicalElements());

console.log(tle.line1);
console.log(tle.line2);

const tle2 = TLE.fromClassicalElements(estimate4.toClassicalElements());

console.log(tle2.line1);
console.log(tle2.line2);
4 changes: 4 additions & 0 deletions src/body/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { Celestial } from './Celestial';
export { Earth } from './Earth';
export { Moon } from './Moon';
export { Sun } from './Sun';
198 changes: 198 additions & 0 deletions src/coordinate/FormatTle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { Satellite } from 'src/objects';

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;
};

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;
}

static createTle(tleParams: TleParams): { tle1: string; tle2: string } {
const { inc, meanmo, rasc, argPe, meana, ecen, epochyr, epochday, intl } = tleParams;
const scc = FormatTle.convert6DigitToA5(tleParams.scc);
const epochYrStr = epochyr.padStart(2, '0');
const epochdayStr = parseFloat(epochday).toFixed(8).padStart(12, '0');
const incStr = FormatTle.inclination(inc);
const meanmoStr = FormatTle.meanMotion(meanmo);
const rascStr = FormatTle.rightAscension(rasc);
const argPeStr = FormatTle.argumentOfPerigee(argPe);
const meanaStr = FormatTle.meanAnomaly(meana);
const ecenStr = FormatTle.eccentricity(ecen);

// 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';

// Add explicit positive/negative signs
TLE1Ending = TLE1Ending[1] === ' ' ? FormatTle.setCharAt(TLE1Ending, 1, '+') : TLE1Ending;
TLE1Ending = TLE1Ending[12] === ' ' ? FormatTle.setCharAt(TLE1Ending, 12, '+') : TLE1Ending;
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 tle2 = `2 ${scc} ${incStr} ${rascStr} ${ecenStr} ${argPeStr} ${meanaStr} ${meanmoStr} 00010`;

return { tle1, tle2 };
}

static eccentricity(ecen: string): string {
let ecen0 = ecen.padEnd(9, '0');

if (ecen0[1] === '.') {
ecen0 = ecen0.substring(2);
} else {
ecen0 = ecen0.substring(0, 7);
}
if (ecen0.length !== 7) {
throw new Error('ecen length is not 7');
}

return ecen0;
}

static inclination(inc: number | string): StringifiedNumber {
if (typeof inc === 'number') {
inc = inc.toString();
}

const incNum = parseFloat(inc).toFixed(4);
const inc0 = incNum.padStart(8, '0');

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

return inc0 as StringifiedNumber;
}

static meanAnomaly(meana: number | string): StringifiedNumber {
if (typeof meana === 'number') {
meana = meana.toString();
}

const meanaNum = parseFloat(meana).toFixed(4);
const meana0 = meanaNum.padStart(8, '0');

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

return meana0 as StringifiedNumber;
}

static meanMotion(meanmo: number | string): StringifiedNumber {
if (typeof meanmo === 'number') {
meanmo = meanmo.toString();
}

const meanmoNum = parseFloat(meanmo).toFixed(8);
const meanmo0 = meanmoNum.padStart(11, '0');

if (meanmo0.length !== 11) {
throw new Error('meanmo length is not 11');
}

return meanmo0 as StringifiedNumber;
}

static rightAscension(rasc: number | string): StringifiedNumber {
if (typeof rasc === 'number') {
rasc = rasc.toString();
}

const rascNum = parseFloat(rasc).toFixed(4);
const rasc0 = rascNum.padStart(8, '0');

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

return rasc0 as StringifiedNumber;
}

static setCharAt(str: string, index: number, chr: string) {
if (index > str.length - 1) {
return str;
}

return `${str.substring(0, index)}${chr}${str.substring(index + 1)}`;
}

/**
* Converts a 6 digit SCC number to a 5 digit SCC alpha 5 number
*/
static convert6DigitToA5(sccNum: string): string {
// Only applies to 6 digit numbers
if (sccNum.length < 6) {
return sccNum;
}

if (RegExp(/[A-Z]/iu, 'u').test(sccNum[0])) {
return sccNum;
}

// Extract the trailing 4 digits
const rest = sccNum.slice(2, 6);

/*
* Convert the first two digit numbers into a Letter. Skip I and O as they look too similar to 1 and 0
* A=10, B=11, C=12, D=13, E=14, F=15, G=16, H=17, J=18, K=19, L=20, M=21, N=22, P=23, Q=24, R=25, S=26,
* T=27, U=28, V=29, W=30, X=31, Y=32, Z=33
*/
let first = parseInt(`${sccNum[0]}${sccNum[1]}`);
const iPlus = first >= 18 ? 1 : 0;
const tPlus = first >= 24 ? 1 : 0;

first = first + iPlus + tPlus;

return `${String.fromCharCode(first + 55)}${rest}`;
}

static convertA5to6Digit(sccNum: string): string {
if (RegExp(/[A-Z]/iu, 'u').test(sccNum[0])) {
// Extract the trailing 4 digits
const rest = sccNum.slice(1, 5);

/*
* Convert the first letter to a two digit number. Skip I and O as they look too similar to 1 and 0
* A=10, B=11, C=12, D=13, E=14, F=15, G=16, H=17, J=18, K=19, L=20, M=21, N=22, P=23, Q=24, R=25, S=26,
* T=27, U=28, V=29, W=30, X=31, Y=32, Z=33
*/
let first = sccNum[0].toUpperCase().charCodeAt(0) - 55;
const iPlus = first >= 18 ? 1 : 0;
const tPlus = first >= 24 ? 1 : 0;

first = first - iPlus - tPlus;

return `${first}${rest}`;
}

return sccNum;
}
}
4 changes: 2 additions & 2 deletions src/coordinate/StateVector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Earth } from '../body/Earth';
import { Vector3D } from '../operations/Vector3D';
import { EpochUTC } from '../time/EpochUTC';
import type { Vector3D } from '../operations/Vector3D';
import type { EpochUTC } from '../time/EpochUTC';
import { TAU } from '../utils/constants';
import { ClassicalElements } from './ClassicalElements';

Expand Down
2 changes: 1 addition & 1 deletion src/coordinate/TEME.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable class-methods-use-this */
import { Earth } from '../body/Earth';
import { ClassicalElements } from './ClassicalElements';
import type { ClassicalElements } from './ClassicalElements';
import { J2000 } from './J2000';
import { StateVector } from './StateVector';

Expand Down
Loading

0 comments on commit a8cc11d

Please sign in to comment.