ootk
Version:
Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.
254 lines (211 loc) • 7.33 kB
text/typescript
/**
* @author @thkruz Theodore Kruczek
* @license AGPL-3.0-or-later
* @copyright (c) 2025 Kruczek Labs LLC
*
* Orbital Object ToolKit is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* Orbital Object ToolKit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* Orbital Object ToolKit. If not, see <http://www.gnu.org/licenses/>.
*/
import { EpochUTC, J2000, Seconds } from '../main.js';
import { Thrust } from '../force/Thrust.js';
import { VerletBlendInterpolator } from '../interpolator/VerletBlendInterpolator.js';
import { GoldenSection } from './../optimize/GoldenSection.js';
// Propagator base class.
export abstract class Propagator {
// Propagate state to the provided [epoch].
abstract propagate(epoch: EpochUTC): J2000;
/*
* Generate a [VerletBlendInterpolator] containing ephemeris over the
* [start] and [stop] propagation period, with an optional
* ephemeris [interval].
*/
ephemeris(start: EpochUTC, stop: EpochUTC, interval = 60.0 as Seconds): VerletBlendInterpolator {
const output: J2000[] = [this.propagate(start)];
let tempEpoch = start;
while (tempEpoch <= stop) {
tempEpoch = tempEpoch.roll(interval);
output.push(this.propagate(tempEpoch));
}
return new VerletBlendInterpolator(output);
}
/*
* Reset cached propagator state to its initial value.
*
* This will clear any post-maneuver states in the propagator.
*/
abstract reset(): void;
/*
* Store a checkpoint of this propagator's current state, and return the
* checkpoint index that can be used to [restore] the propagator state
* later.
*/
abstract checkpoint(): number;
// Restore a state [checkpoint] at the provided index.
abstract restore(index: number): void;
// Remove any stored [checkpoint] values from this propagator.
abstract clearCheckpoints(): void;
// Return the last propagated state.
abstract get state(): J2000;
/*
* Generate a list of [J2000] states integrating over a maneuver.
*
* If the maneuver is impulsive, the list will only contain a single state.
*/
maneuver(maneuver: Thrust, interval = 60.0 as Seconds): J2000[] {
const output: J2000[] = [];
const tempEpoch = maneuver.start;
const stop = maneuver.stop;
output.push(this.propagate(tempEpoch));
let current = tempEpoch;
while (current <= stop) {
current = current.roll(interval);
output.push(this.propagate(current));
}
return output;
}
/*
* Generate a [VerletBlendInterpolator] containing maneuver ephemeris over
* the [start] and [finish] interval, with an optional ephemeris [interval].
*/
ephemerisManeuver(
start: EpochUTC,
finish: EpochUTC,
maneuvers: Thrust[],
interval = 60.0 as Seconds,
): VerletBlendInterpolator {
const output: J2000[] = [];
const tempEpoch = start;
output.push(this.propagate(tempEpoch));
const stop = finish;
let current = tempEpoch;
while (current <= stop) {
current = current.roll(interval);
output.push(this.propagate(current));
}
return new VerletBlendInterpolator(output);
}
// Return the epoch of the ascending node after the [start] epoch.
ascendingNodeEpoch(start: EpochUTC): EpochUTC {
const period = this.state.period / 60 as Seconds;
const step = period / 8 as Seconds;
let current = start;
const stop = current.roll(period);
this.propagate(current);
let previous = this.state.position.z;
while (current <= stop) {
current = current.roll(step);
this.propagate(current);
if (Math.sign(this.state.position.z) === Math.sign(-previous) && this.state.velocity.z > 0) {
break;
}
previous = this.state.position.z;
}
const t = GoldenSection.search(
(x) => {
this.propagate(new EpochUTC(x as Seconds));
return Math.abs(this.state.position.z) as Seconds;
},
current.posix - step,
current.posix,
{ tolerance: 1e-3 },
) as Seconds;
return new EpochUTC(t);
}
// Return the epoch of the descending node after the [start] epoch.
descendingNodeEpoch(start: EpochUTC): EpochUTC {
const period = this.state.period / 60 as Seconds;
const step = period / 8 as Seconds;
let current = start;
const stop = current.roll(period);
this.propagate(current);
let previous = this.state.position.z;
while (current <= stop) {
current = current.roll(step);
this.propagate(current);
if (Math.sign(this.state.position.z) === Math.sign(-previous) && this.state.velocity.z < 0) {
break;
}
previous = this.state.position.z;
}
const t = GoldenSection.search(
(x: number) => {
this.propagate(new EpochUTC(x as Seconds));
return Math.abs(this.state.position.z);
},
current.posix - step,
current.posix,
{ tolerance: 1e-3 },
) as Seconds;
return new EpochUTC(t);
}
// Return the epoch of apogee after the [start] epoch.
apogeeEpoch(start: EpochUTC): EpochUTC {
const slice = 8;
const period = this.state.period / 60 as Seconds;
const step = period / slice as Seconds;
let current = start;
this.propagate(current);
let tCache = current;
let rCache = this.state.position.magnitude();
for (let i = 0; i < slice; i++) {
current = current.roll(step);
const t = new EpochUTC(
GoldenSection.search(
(x: number) => {
this.propagate(new EpochUTC(x as Seconds));
return this.state.position.magnitude();
},
current.posix - step,
current.posix,
{ tolerance: 1e-3, solveMax: true },
) as Seconds,
);
this.propagate(t);
const r = this.state.position.magnitude();
if (r > rCache) {
tCache = t;
rCache = r;
}
}
return tCache;
}
// Return the epoch of perigee after the [start] epoch.
perigeeEpoch(start: EpochUTC): EpochUTC {
const slice = 8;
const period = this.state.period / 60 as Seconds;
const step = period / slice as Seconds;
let current = start;
this.propagate(current);
let tCache = current;
let rCache = this.state.position.magnitude();
for (let i = 0; i < slice; i++) {
current = current.roll(step);
const t = new EpochUTC(
GoldenSection.search(
(x: number) => {
this.propagate(new EpochUTC(x as Seconds));
return this.state.position.magnitude();
},
current.posix - step,
current.posix,
{ tolerance: 1e-3, solveMax: false },
) as Seconds,
);
this.propagate(t);
const r = this.state.position.magnitude();
if (r < rCache) {
tCache = t;
rCache = r;
}
}
return tCache;
}
}