-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
958 additions
and
254 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.