UNPKG

ootk

Version:

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

147 lines (124 loc) 4.72 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 { copySign, Earth, EpochUTC, EpochWindow, J2000, Kilometers, KilometersPerSecond, Seconds, Vector3D, } from '../main.js'; import { CubicSplineInterpolator } from './CubicSplineInterpolator.js'; import { LagrangeInterpolator } from './LagrangeInterpolator.js'; import { StateInterpolator } from './StateInterpolator.js'; /** * Two-body Velocity Verlet Blend interpolator. * * The [VerletBlendInterpolator] retains the original ephemerides, so the * original _"truth"_ states can be retrieved if needed without imparting any * additional error, so this can be used to build other interpolator types. * The implementation is simple and very tolerant when working with sparse * ephemerides. */ export class VerletBlendInterpolator extends StateInterpolator { constructor(public ephemeris: J2000[]) { super(); } get sizeBytes(): number { return (64 * 7 * this.ephemeris.length) / 8; } window(): EpochWindow { return new EpochWindow(this.ephemeris[0].epoch, this.ephemeris[this.ephemeris.length - 1].epoch); } private static getClosest_(target: number, s1: J2000, s2: J2000): J2000 { return target - s1.epoch.posix >= s2.epoch.posix - target ? s2 : s1; } private matchState_(epoch: EpochUTC): J2000 { const target = epoch.posix; if (target <= this.ephemeris[0].epoch.posix) { return this.ephemeris[0]; } if (target >= this.ephemeris[this.ephemeris.length - 1].epoch.posix) { return this.ephemeris[this.ephemeris.length - 1]; } let i = 0; let j = this.ephemeris.length; let mid = 0; while (i < j) { mid = (i + j) >> 1; if (this.ephemeris[mid].epoch.posix === target) { return this.ephemeris[mid]; } if (target < this.ephemeris[mid].epoch.posix) { if (mid > 0 && target > this.ephemeris[mid - 1].epoch.posix) { return VerletBlendInterpolator.getClosest_(target, this.ephemeris[mid - 1], this.ephemeris[mid]); } j = mid; } else { if (mid < this.ephemeris.length - 1 && target < this.ephemeris[mid + 1].epoch.posix) { return VerletBlendInterpolator.getClosest_(target, this.ephemeris[mid], this.ephemeris[mid + 1]); } i = mid + 1; } } return this.ephemeris[mid]; } private static _gravity(position: Vector3D): Vector3D { const r = position.magnitude(); return position.scale(-Earth.mu / (r * r * r)); } private static integrate_(state: J2000, step: Seconds): J2000 { const x0 = state.position; const a0 = VerletBlendInterpolator._gravity(x0) as Vector3D<KilometersPerSecond>; const v0 = state.velocity; const x1 = x0 .add(v0.scale(step) as unknown as Vector3D<Kilometers>) .add(a0.scale(0.5 * step * step) as unknown as Vector3D<Kilometers>); const a1 = VerletBlendInterpolator._gravity(x1) as Vector3D<KilometersPerSecond>; const v1 = v0 .add(a0.add(a1).scale(0.5 * step) as Vector3D<KilometersPerSecond>); return new J2000(state.epoch.roll(step), x1, v1); } interpolate(epoch: EpochUTC): J2000 | null { if (!this.inWindow(epoch)) { return null; } let state = this.matchState_(epoch); while (state.epoch.posix !== epoch.posix) { const delta = epoch.posix - state.epoch.posix; const stepMag = Math.min(5.0, Math.abs(delta)); const stepSize = copySign(stepMag, delta) as Seconds; state = VerletBlendInterpolator.integrate_(state, stepSize); } return state; } getCachedState(epoch: EpochUTC): J2000 | null { if (!this.inWindow(epoch)) { return null; } return this.matchState_(epoch); } toCubicSpline(): CubicSplineInterpolator { return CubicSplineInterpolator.fromEphemeris(this.ephemeris); } toLagrange(order = 10): LagrangeInterpolator { return LagrangeInterpolator.fromEphemeris(this.ephemeris, order); } }