Skip to content

Maneuvers

This example computes Hohmann transfers with TwoBurnOrbitTransfer, turns a transfer into scheduled Thrust maneuvers, and prices an inclination change with PlaneChangeBurn. Use it when you need a delta-V budget or a burn schedule for moving between near-circular orbits.

ts
import {
  ClassicalElements,
  DEG2RAD,
  EpochUTC,
  Kilometers,
  PlaneChangeBurn,
  Radians,
  TwoBurnOrbitTransfer,
} from 'ootk';

Run it

bash
npm run build
npx tsx ./examples/maneuvers.ts

Orbit definitions

ClassicalElements describes each endpoint orbit. Angles are Radians, so degree values are converted with DEG2RAD, and the period getter returns minutes. The elements are only used here for context (altitude, period, inclination); the transfer math below needs just the two radii.

ts
const epoch = EpochUTC.fromDateTimeString('2024-01-28T00:00:00.000Z');
const earthRadius = 6378.137;

// Initial LEO orbit (nearly circular, ~400 km altitude)
const leoOrbit = new ClassicalElements({
  epoch,
  semimajorAxis: 6778 as Kilometers,
  eccentricity: 0.001,
  inclination: (28.5 * DEG2RAD) as Radians,
  rightAscension: 0 as Radians,
  argPerigee: 0 as Radians,
  trueAnomaly: 0 as Radians,
});

// Target GEO orbit (circular, equatorial)
const geoOrbit = new ClassicalElements({
  epoch,
  semimajorAxis: 42164 as Kilometers,
  eccentricity: 0.001,
  inclination: 0 as Radians,
  rightAscension: 0 as Radians,
  argPerigee: 0 as Radians,
  trueAnomaly: 0 as Radians,
});

console.log('=== Example 1: Hohmann Transfer from LEO to GEO ===\n');

console.log('Initial Orbit (LEO):');
console.log(`  Altitude: ${(leoOrbit.semimajorAxis - earthRadius).toFixed(2)} km`);
console.log(`  Inclination: ${leoOrbit.inclinationDegrees.toFixed(1)} deg`);
console.log(`  Period: ${leoOrbit.period.toFixed(2)} minutes`);

console.log('\nTarget Orbit (GEO):');
console.log(`  Altitude: ${(geoOrbit.semimajorAxis - earthRadius).toFixed(2)} km`);
console.log(`  Inclination: ${geoOrbit.inclinationDegrees.toFixed(1)} deg`);
console.log(`  Period: ${geoOrbit.period.toFixed(2)} minutes`);

Hohmann LEO to GEO

TwoBurnOrbitTransfer.hohmannTransfer(rInit, rFinal) is a static factory that takes the radii of two circular orbits in km and returns the circular velocities (vInit, vFinal), the two burn magnitudes (vTransA at perigee, vTransB at apogee) in km/s, the transfer time tTrans in seconds, and a deltaV getter summing both burns.

ts
// TwoBurnOrbitTransfer.hohmannTransfer() takes the radii of the two circular
// orbits and returns burn magnitudes plus the transfer time.
const leoToGeo = TwoBurnOrbitTransfer.hohmannTransfer(leoOrbit.semimajorAxis, geoOrbit.semimajorAxis);

console.log('\nHohmann Transfer:');
console.log(`  Initial circular velocity: ${leoToGeo.vInit.toFixed(3)} km/s`);
console.log(`  Final circular velocity: ${leoToGeo.vFinal.toFixed(3)} km/s`);
console.log(`  First burn (perigee): ${leoToGeo.vTransA.toFixed(3)} km/s`);
console.log(`  Second burn (apogee): ${leoToGeo.vTransB.toFixed(3)} km/s`);
console.log(`  Total delta-V: ${leoToGeo.deltaV.toFixed(3)} km/s`);
console.log(`  Transfer time: ${(leoToGeo.tTrans / 60).toFixed(2)} minutes (half orbit)`);

// The transfer ellipse spans from the initial radius to the final radius
const transferSma = (leoOrbit.semimajorAxis + geoOrbit.semimajorAxis) / 2;
const transferEcc = (geoOrbit.semimajorAxis - leoOrbit.semimajorAxis) / (geoOrbit.semimajorAxis + leoOrbit.semimajorAxis);

console.log(`  Transfer semi-major axis: ${transferSma.toFixed(2)} km`);
console.log(`  Transfer eccentricity: ${transferEcc.toFixed(6)}`);
console.log(`  Periapsis altitude: ${(leoOrbit.semimajorAxis - earthRadius).toFixed(2)} km`);
console.log(`  Apoapsis altitude: ${(geoOrbit.semimajorAxis - earthRadius).toFixed(2)} km`);

Burn schedule

toManeuvers(epoch) converts the transfer into two impulsive Thrust objects in the RIC frame. The delta-V is applied in the intrack direction (in m/s on the Thrust API), and the second burn is centered one transfer time after the first.

ts
// toManeuvers() converts the transfer into two impulsive Thrust objects,
// with the second burn scheduled one transfer time after the first.
const [burnA, burnB] = leoToGeo.toManeuvers(epoch);

console.log('\nBurn Schedule (impulsive Thrust maneuvers):');
console.log(`  Burn 1: ${burnA.center.toString()}`);
console.log(`    Intrack delta-V: ${burnA.intrack.toFixed(1)} m/s`);
console.log(`  Burn 2: ${burnB.center.toString()}`);
console.log(`    Intrack delta-V: ${burnB.intrack.toFixed(1)} m/s`);

Plane change

A launch from 28.5 degrees inclination still needs a plane change to reach equatorial GEO. PlaneChangeBurn.computeDeltaV(velocityAtNode, deltaIncRad) implements dv = 2 v sin(di/2); doing the rotation at GEO, where velocity is lowest, keeps the cost down. In practice the plane change is combined vectorially with the apogee burn, so the separate-burn figure shown here is an upper bound.

ts
console.log('\n=== Example 2: Plane Change at GEO ===\n');

// A launch from 28.5 deg inclination also needs a plane change to reach an
// equatorial GEO. Plane changes are cheapest where velocity is lowest, so
// perform it at GEO with the apogee circularization burn.
const deltaInclination = (28.5 * DEG2RAD) as Radians;
const planeChangeDeltaV = PlaneChangeBurn.computeDeltaV(leoToGeo.vFinal, deltaInclination);

console.log(`Inclination change: ${(deltaInclination / DEG2RAD).toFixed(1)} deg`);
console.log(`Velocity at GEO: ${leoToGeo.vFinal.toFixed(3)} km/s`);
console.log(`Plane change delta-V (separate burn): ${planeChangeDeltaV.toFixed(3)} km/s`);
console.log(`Hohmann + separate plane change: ${(leoToGeo.deltaV + planeChangeDeltaV).toFixed(3)} km/s`);

LEO to Molniya

The same Hohmann helper sizes a transfer ellipse from LEO up to the Molniya apogee radius. Only the first burn and transfer time map directly onto a real Molniya insertion, since a Molniya orbit is not circularized at apogee; vTransB is reported as the cost of circularizing there if you wanted to.

ts
console.log('\n=== Example 3: Raising Apogee from LEO to Molniya Altitude ===\n');

const molniyaOrbit = new ClassicalElements({
  epoch,
  semimajorAxis: 26554 as Kilometers,
  eccentricity: 0.74,
  inclination: (63.4 * DEG2RAD) as Radians,
  rightAscension: 0 as Radians,
  argPerigee: (270 * DEG2RAD) as Radians,
  trueAnomaly: 0 as Radians,
});

const molniyaApogeeRadius = molniyaOrbit.semimajorAxis * (1 + molniyaOrbit.eccentricity);

console.log('Target Orbit (Molniya):');
console.log(`  Semi-major axis: ${molniyaOrbit.semimajorAxis.toFixed(2)} km`);
console.log(`  Eccentricity: ${molniyaOrbit.eccentricity.toFixed(4)}`);
console.log(`  Apogee altitude: ${(molniyaApogeeRadius - earthRadius).toFixed(2)} km`);
console.log(`  Perigee altitude: ${(molniyaOrbit.semimajorAxis * (1 - molniyaOrbit.eccentricity) - earthRadius).toFixed(2)} km`);
console.log(`  Period: ${molniyaOrbit.period.toFixed(2)} minutes`);

// Size a Hohmann-style transfer ellipse from LEO up to the Molniya apogee
// radius. A real Molniya insertion would not circularize at apogee, so only
// the first burn and transfer time apply directly.
const leoToMolniyaApogee = TwoBurnOrbitTransfer.hohmannTransfer(leoOrbit.semimajorAxis, molniyaApogeeRadius);

console.log('\nTransfer Ellipse (LEO to Molniya apogee radius):');
console.log(`  First burn (perigee): ${leoToMolniyaApogee.vTransA.toFixed(3)} km/s`);
console.log(`  Circularization at apogee (if desired): ${leoToMolniyaApogee.vTransB.toFixed(3)} km/s`);
console.log(`  Transfer time: ${(leoToMolniyaApogee.tTrans / 60).toFixed(2)} minutes`);

Transfer comparison

Because hohmannTransfer() is a pure static function of the two radii, it is convenient for tabulating delta-V and time-of-flight across candidate transfers.

ts
console.log('\n=== Example 4: Comparison of Common Transfers ===\n');

const transferCases = [
  { name: 'LEO to GEO', rInit: 6778, rFinal: 42164 },
  { name: 'LEO to MEO (GPS)', rInit: 6778, rFinal: 26560 },
  { name: 'LEO to ISS-like', rInit: 6678, rFinal: 6778 },
];

for (const transferCase of transferCases) {
  const transfer = TwoBurnOrbitTransfer.hohmannTransfer(transferCase.rInit, transferCase.rFinal);

  console.log(`${transferCase.name}:`);
  console.log(`  First burn: ${(transfer.vTransA * 1000).toFixed(1)} m/s`);
  console.log(`  Second burn: ${(transfer.vTransB * 1000).toFixed(1)} m/s`);
  console.log(`  Total delta-V: ${transfer.deltaV.toFixed(3)} km/s`);
  console.log(`  Transfer time: ${(transfer.tTrans / 60).toFixed(2)} minutes`);
  console.log('');
}

Output

txt
=== Example 1: Hohmann Transfer from LEO to GEO ===

Initial Orbit (LEO):
  Altitude: 399.86 km
  Inclination: 28.5 deg
  Period: 92.56 minutes

Target Orbit (GEO):
  Altitude: 35785.86 km
  Inclination: 0.0 deg
  Period: 1436.06 minutes

Hohmann Transfer:
  Initial circular velocity: 7.669 km/s
  Final circular velocity: 3.075 km/s
  First burn (perigee): 2.398 km/s
  Second burn (apogee): 1.457 km/s
  Total delta-V: 3.854 km/s
  Transfer time: 317.47 minutes (half orbit)
  Transfer semi-major axis: 24471.00 km
  Transfer eccentricity: 0.723019
  Periapsis altitude: 399.86 km
  Apoapsis altitude: 35785.86 km

Burn Schedule (impulsive Thrust maneuvers):
  Burn 1: 2024-01-28T00:00:00.000Z
    Intrack delta-V: 2397.5 m/s
  Burn 2: 2024-01-28T05:17:28.402Z
    Intrack delta-V: 1456.5 m/s

=== Example 2: Plane Change at GEO ===

Inclination change: 28.5 deg
Velocity at GEO: 3.075 km/s
Plane change delta-V (separate burn): 1.514 km/s
Hohmann + separate plane change: 5.368 km/s

=== Example 3: Raising Apogee from LEO to Molniya Altitude ===

Target Orbit (Molniya):
  Semi-major axis: 26554.00 km
  Eccentricity: 0.7400
  Apogee altitude: 39825.82 km
  Perigee altitude: 525.90 km
  Period: 717.72 minutes

Transfer Ellipse (LEO to Molniya apogee radius):
  First burn (perigee): 2.459 km/s
  Circularization at apogee (if desired): 1.451 km/s
  Transfer time: 357.58 minutes

=== Example 4: Comparison of Common Transfers ===

LEO to GEO:
  First burn: 2397.5 m/s
  Second burn: 1456.5 m/s
  Total delta-V: 3.854 km/s
  Transfer time: 317.47 minutes

LEO to MEO (GPS):
  First burn: 2011.4 m/s
  Second burn: 1403.7 m/s
  Total delta-V: 3.415 km/s
  Transfer time: 178.48 minutes

LEO to ISS-like:
  First burn: 28.7 m/s
  Second burn: 28.5 m/s
  Total delta-V: 0.057 km/s
  Transfer time: 45.77 minutes

Released under the AGPL-3.0 License.