UNPKG

ootk

Version:

Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.

254 lines (211 loc) 7.33 kB
/** * @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; } }