Orbital Elements
This example shows how to move between the three common orbit representations in ootk: TLEs, classical (Keplerian) orbital elements, and inertial state vectors. Use these conversions whenever you need to inspect an orbit's shape from tracking data, build a synthetic orbit from scratch, or export an orbit as a TLE for SGP4 consumers.
import {
ClassicalElements,
Degrees,
EpochUTC,
J2000,
Kilometers,
KilometersPerSecond,
Satellite,
Tle,
TleLine1,
TleLine2,
Vector3D,
} from 'ootk';Run it
npm run build
npx tsx ./examples/orbital-elements.tsTLE to Elements
Propagate a Satellite (built from a TLE) to a specific date with toJ2000(), then call toClassicalElements() on the resulting state. Angle members on ClassicalElements are stored in radians, so the script converts to degrees for display. Derived quantities like period are available directly, and apogee/perigee altitudes fall out of the semi-major axis and eccentricity.
// Example 1: Extract orbital elements from a TLE
console.log('=== Example 1: Extract Orbital Elements from TLE ===\n');
const issTle = new Satellite({
tle1: '1 25544U 98067A 24028.54545847 .00031576 00000-0 57240-3 0 9991' as TleLine1,
tle2: '2 25544 51.6418 292.2590 0002595 167.5319 252.0460 15.49326324436741' as TleLine2,
});
const date = new Date('2024-01-28T13:05:27.451Z');
const j2000State = issTle.toJ2000(date);
const elements = j2000State.toClassicalElements();
console.log('ISS Orbital Elements:');
console.log(` Semi-major axis: ${elements.semimajorAxis.toFixed(2)} km`);
console.log(` Eccentricity: ${elements.eccentricity.toFixed(6)}`);
console.log(` Inclination: ${(elements.inclination * (180 / Math.PI)).toFixed(4)}°`);
console.log(` Right Ascension: ${(elements.rightAscension * (180 / Math.PI)).toFixed(4)}°`);
console.log(` Arg of Perigee: ${(elements.argPerigee * (180 / Math.PI)).toFixed(4)}°`);
console.log(` True Anomaly: ${(elements.trueAnomaly * (180 / Math.PI)).toFixed(4)}°`);
// Calculate derived parameters
console.log(`\nDerived Parameters:`);
console.log(` Period: ${elements.period.toFixed(2)} minutes`);
console.log(` Apogee altitude: ${((elements.semimajorAxis * (1 + elements.eccentricity)) - 6378.137).toFixed(2)} km`);
console.log(` Perigee altitude: ${((elements.semimajorAxis * (1 - elements.eccentricity)) - 6378.137).toFixed(2)} km`);Elements to State Vector
ClassicalElements can also be constructed directly. The constructor accepts angles in degrees (they are converted internally), and toJ2000() produces the equivalent inertial position and velocity at the element epoch.
// Example 2: Create classical elements and convert to state vector
console.log('\n=== Example 2: Create Orbital Elements and Convert to State Vector ===\n');
const customElements = new ClassicalElements({
epoch: EpochUTC.fromDateTime(date),
semimajorAxis: 8000 as Kilometers,
eccentricity: 0.1,
inclination: 45 as Degrees,
rightAscension: 90 as Degrees,
argPerigee: 30 as Degrees,
trueAnomaly: 0 as Degrees,
});
console.log('Custom Orbit:');
console.log(` Semi-major axis: ${customElements.semimajorAxis.toFixed(2)} km`);
console.log(` Eccentricity: ${customElements.eccentricity.toFixed(6)}`);
console.log(` Inclination: ${customElements.inclination.toFixed(4)}°`);
console.log(` Period: ${customElements.period.toFixed(2)} minutes`);
const stateVector = customElements.toJ2000();
console.log(`\nState Vector:`);
console.log(` Position: [${stateVector.position.x.toFixed(2)}, ${stateVector.position.y.toFixed(2)}, ${stateVector.position.z.toFixed(2)}] km`);
console.log(` Velocity: [${stateVector.velocity.x.toFixed(6)}, ${stateVector.velocity.y.toFixed(6)}, ${stateVector.velocity.z.toFixed(6)}] km/s`);State Vector to Elements
The reverse direction: build a J2000 state from raw position (km) and velocity (km/s) vectors, then recover the Keplerian elements with toClassicalElements(). This is the typical path when your input is a propagator output or a radar-derived state.
// Example 3: Convert state vector to classical elements
console.log('\n=== Example 3: Create State Vector and Convert to Classical Elements ===\n');
const customState = new J2000(
EpochUTC.fromDateTime(date),
new Vector3D(
-4040.9257 as Kilometers,
-4884.0906 as Kilometers,
3522.9643 as Kilometers,
),
new Vector3D(
5.4662 as KilometersPerSecond,
-3.4425 as KilometersPerSecond,
-2.4854 as KilometersPerSecond,
),
);
const derivedElements = customState.toClassicalElements();
console.log('Derived Orbital Elements:');
console.log(` Semi-major axis: ${derivedElements.semimajorAxis.toFixed(2)} km`);
console.log(` Eccentricity: ${derivedElements.eccentricity.toFixed(6)}`);
console.log(` Inclination: ${(derivedElements.inclination * (180 / Math.PI)).toFixed(4)}°`);
console.log(` Right Ascension: ${(derivedElements.rightAscension * (180 / Math.PI)).toFixed(4)}°`);
console.log(` Arg of Perigee: ${(derivedElements.argPerigee * (180 / Math.PI)).toFixed(4)}°`);
console.log(` True Anomaly: ${(derivedElements.trueAnomaly * (180 / Math.PI)).toFixed(4)}°`);Elements to TLE
Tle.fromClassicalElements() fits a TLE to a set of elements. Note that TLEs encode SGP4 mean elements, not osculating elements, so reading the TLE back through SGP4 gives values close to (but not exactly) the input; the verification block shows the round trip lands within about 1 km of the GEO semi-major axis.
// Example 4: Create TLE from classical elements
console.log('\n=== Example 4: Create TLE from Classical Elements ===\n');
const geoElements = new ClassicalElements({
epoch: EpochUTC.fromDateTime(date),
semimajorAxis: 42164 as Kilometers, // GEO altitude
eccentricity: 0.0001,
inclination: 0.1 as Degrees,
rightAscension: 0 as Degrees,
argPerigee: 0 as Degrees,
trueAnomaly: 0 as Degrees,
});
const geoTle = Tle.fromClassicalElements(geoElements);
console.log('Generated TLE for GEO satellite:');
console.log(geoTle.line1);
console.log(geoTle.line2);
// Verify by reading back
const verifyTle = new Satellite({
tle1: geoTle.line1,
tle2: geoTle.line2,
});
const verifyState = verifyTle.toJ2000(date);
const verifyElements = verifyState.toClassicalElements();
console.log(`\nVerification - Semi-major axis: ${verifyElements.semimajorAxis.toFixed(2)} km`);
console.log(`Verification - Period: ${verifyElements.period.toFixed(2)} minutes (should be ~1436 min for GEO)`);Orbit Types
The same ClassicalElements API describes any orbit regime. This block builds representative LEO, MEO, and Molniya-style HEO orbits and prints their apogee/perigee altitudes and periods.
// Example 5: Different orbit types
console.log('\n=== Example 5: Different Orbit Types ===\n');
const orbits = [
{
name: 'LEO (Circular)',
elements: new ClassicalElements({
epoch: EpochUTC.fromDateTime(date),
semimajorAxis: 6778 as Kilometers,
eccentricity: 0.001,
inclination: 51.6 as Degrees,
rightAscension: 0 as Degrees,
argPerigee: 0 as Degrees,
trueAnomaly: 0 as Degrees,
}),
},
{
name: 'MEO (GPS-like)',
elements: new ClassicalElements({
epoch: EpochUTC.fromDateTime(date),
semimajorAxis: 26560 as Kilometers,
eccentricity: 0.01,
inclination: 55 as Degrees,
rightAscension: 0 as Degrees,
argPerigee: 0 as Degrees,
trueAnomaly: 0 as Degrees,
}),
},
{
name: 'HEO (Molniya)',
elements: new ClassicalElements({
epoch: EpochUTC.fromDateTime(date),
semimajorAxis: 26554 as Kilometers,
eccentricity: 0.74,
inclination: 63.4 as Degrees,
rightAscension: 0 as Degrees,
argPerigee: 270 as Degrees,
trueAnomaly: 0 as Degrees,
}),
},
];
orbits.forEach((orbit) => {
console.log(`${orbit.name}:`);
console.log(` Altitude at apogee: ${((orbit.elements.semimajorAxis * (1 + orbit.elements.eccentricity)) - 6378.137).toFixed(2)} km`);
console.log(` Altitude at perigee: ${((orbit.elements.semimajorAxis * (1 - orbit.elements.eccentricity)) - 6378.137).toFixed(2)} km`);
console.log(` Period: ${orbit.elements.period.toFixed(2)} minutes`);
console.log('');
});Output
=== Example 1: Extract Orbital Elements from TLE ===
ISS Orbital Elements:
Semi-major axis: 6794.45 km
Eccentricity: 0.001030
Inclination: 51.5075°
Right Ascension: 292.0110°
Arg of Perigee: 125.3753°
True Anomaly: 294.0422°
Derived Parameters:
Period: 92.89 minutes
Apogee altitude: 423.31 km
Perigee altitude: 409.31 km
=== Example 2: Create Orbital Elements and Convert to State Vector ===
Custom Orbit:
Semi-major axis: 8000.00 km
Eccentricity: 0.100000
Inclination: 45.0000°
Period: 118.68 minutes
State Vector:
Position: [2843.28, 2667.36, -6053.18] km
Velocity: [-4.020084, 6.609623, 1.024256] km/s
=== Example 3: Create State Vector and Convert to Classical Elements ===
Derived Orbital Elements:
Semi-major axis: 6427.20 km
Eccentricity: 0.305487
Inclination: 32.5870°
Right Ascension: 110.7914°
Arg of Perigee: 245.6083°
True Anomaly: 229.9747°
=== Example 4: Create TLE from Classical Elements ===
Generated TLE for GEO satellite:
1 00001U 58001A 24028.54545661 .00000000 00000+0 00000+0 0 9995
2 00001 5.7296 0.0000 0001000 0.0000 0.0000 01.00274396 05
Verification - Semi-major axis: 42165.10 km
Verification - Period: 1436.12 minutes (should be ~1436 min for GEO)
=== Example 5: Different Orbit Types ===
LEO (Circular):
Altitude at apogee: 406.64 km
Altitude at perigee: 393.09 km
Period: 92.56 minutes
MEO (GPS-like):
Altitude at apogee: 20447.46 km
Altitude at perigee: 19916.26 km
Period: 717.96 minutes
HEO (Molniya):
Altitude at apogee: 39825.82 km
Altitude at perigee: 525.90 km
Period: 717.72 minutes