UNPKG

ootk

Version:

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

193 lines (165 loc) 5.82 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, EpochWindow, J2000, Kilometers, KilometersPerSecond, Seconds, Vector3D } from '../main.js'; import { StateInterpolator } from './StateInterpolator.js'; export class LagrangeInterpolator extends StateInterpolator { private readonly t_: Float64Array; private readonly x_: Float64Array; private readonly y_: Float64Array; private readonly z_: Float64Array; private readonly order: number; constructor(t: Float64Array, x: Float64Array, y: Float64Array, z: Float64Array, order = 10) { super(); this.t_ = t; this.x_ = x; this.y_ = y; this.z_ = z; this.order = order; } /** * Creates a LagrangeInterpolator from an array of J2000 ephemeris data. * @param ephemeris - The array of J2000 ephemeris data. * @param order - The order of the LagrangeInterpolator. Default is 10. * @returns A new LagrangeInterpolator instance. */ static fromEphemeris(ephemeris: J2000[], order = 10): LagrangeInterpolator { const k = ephemeris.length; const t = new Float64Array(k); const x = new Float64Array(k); const y = new Float64Array(k); const z = new Float64Array(k); for (let i = 0; i < k; i++) { const state = ephemeris[i]; t[i] = state.epoch.posix; x[i] = state.position.x; y[i] = state.position.y; z[i] = state.position.z; } return new LagrangeInterpolator(t, x, y, z, order); } get sizeBytes(): number { return (64 * 4 * this.t_.length) / 8; } interpolate(epoch: EpochUTC): J2000 | null { if (!this.inWindow(epoch)) { return null; } const posix = epoch.posix; const subDex = this.slice_(posix); const start = subDex.left; const stop = subDex.right; const ts = this.t_.subarray(start, stop); const xs = this.x_.subarray(start, stop); const ys = this.y_.subarray(start, stop); const zs = this.z_.subarray(start, stop); const position = new Vector3D( LagrangeInterpolator.position_(ts, xs, posix) as Kilometers, LagrangeInterpolator.position_(ts, ys, posix) as Kilometers, LagrangeInterpolator.position_(ts, zs, posix) as Kilometers, ); const velocity = new Vector3D( LagrangeInterpolator.velocity_(ts, xs, posix) as KilometersPerSecond, LagrangeInterpolator.velocity_(ts, ys, posix) as KilometersPerSecond, LagrangeInterpolator.velocity_(ts, zs, posix) as KilometersPerSecond, ); return new J2000(epoch, position, velocity); } private static position_(xs: Float64Array, ys: Float64Array, x: number): number { const k = xs.length - 1; let result = 0.0; for (let j = 0; j < k; j++) { let product = ys[j]; for (let m = 0; m < k; m++) { if (j === m) { continue; } product *= (x - xs[m]) / (xs[j] - xs[m]); } result += product; } return result; } private static velocity_(xs: Float64Array, ys: Float64Array, x: number): number { const k = xs.length; let result = 0.0; for (let j = 0; j < k; j++) { let total = 0.0; for (let i = 0; i < k; i++) { if (i === j) { continue; } let product = 1 / (xs[j] - xs[i]); for (let m = 0; m < k; m++) { if (m === i || m === j) { continue; } product *= (x - xs[m]) / (xs[j] - xs[m]); } total += product; } result += ys[j] * total; } return result; } private static _getClosest(target: number, t1: number, d1: number, t2: number, d2: number): number { return target - t1 >= t2 - target ? d2 : d1; } private slice_(posix: number): { left: number; right: number } { const n = this.t_.length; if (posix <= this.t_[0]) { return { left: 0, right: this.order }; } if (posix >= this.t_[n - 1]) { return { left: n - this.order, right: n }; } let i = 0; let j = this.t_.length; let mid = 0; while (i < j) { mid = (i + j) >> 1; if (this.t_[mid] === posix) { break; } if (posix < this.t_[mid]) { if (mid > 0 && posix > this.t_[mid - 1]) { mid = LagrangeInterpolator._getClosest(posix, this.t_[mid - 1], mid - 1, this.t_[mid], mid); break; } j = mid; } else { if (mid < this.t_.length - 1 && posix < this.t_[mid + 1]) { mid = LagrangeInterpolator._getClosest(posix, this.t_[mid], mid, this.t_[mid + 1], mid + 1); break; } i = mid + 1; } } const offset = Math.floor(this.order / 2); const left = mid - offset; const right = mid + offset - (this.order % 2 === 1 ? 1 : 0); if (left < 0) { return { left: 0, right: this.order }; } if (right > n) { return { left: n - this.order, right: n }; } return { left, right }; } window(): EpochWindow { return new EpochWindow(new EpochUTC(this.t_[0] as Seconds), new EpochUTC(this.t_[this.t_.length - 1] as Seconds)); } }