ootk
Version:
Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.
147 lines (124 loc) • 4.72 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 {
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);
}
}