UNPKG

ootk

Version:

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

134 lines (120 loc) 4.91 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 { Earth, EpochUTC, J2000, Kilometers, KilometersPerSecond, RadecTopocentric, Vector3D } from '../main.js'; import { ForceModel } from '../force/ForceModel.js'; import { RungeKutta89Propagator } from '../propagator/RungeKutta89Propagator.js'; import { ObservationOptical } from './../observation/ObservationOptical.js'; import { DownhillSimplex } from './../optimize/DownhillSimplex.js'; import { CostFunction } from './../optimize/SimplexEntry.js'; import { GoodingIOD } from './GoodingIOD.js'; type SolveOptions = { nRev?: number; direction?: boolean; posSearch?: number; velSearch?: number; tolerance?: number; printIter?: boolean; }; /** * Gooding angles-only initial orbit determination. * * Used for orbit determination from multiple optical observations. */ export class ModifiedGoodingIOD { private readonly observations_: ObservationOptical[]; private readonly mu_: number; constructor(observations: ObservationOptical[], mu: number = Earth.mu) { this.observations_ = observations; this.mu_ = mu; } private createInitial_(r0: Kilometers, rN: Kilometers, nRev: number, direction: boolean): J2000 { const iod = new GoodingIOD( this.observations_[0], this.observations_[Math.floor(this.observations_.length / 2)], this.observations_[this.observations_.length - 1], this.mu_, ); return iod.solve(r0, rN, nRev, direction); } private _createErrorFunction(aprioriEpoch: EpochUTC): CostFunction { const forceModel = new ForceModel().setGravity(this.mu_); const scoreFn = (x: Float64Array): number => { const position = new Vector3D(x[0] as Kilometers, x[1] as Kilometers, x[2] as Kilometers); const velocity = new Vector3D( x[3] as KilometersPerSecond, x[4] as KilometersPerSecond, x[5] as KilometersPerSecond, ); const state = new J2000(aprioriEpoch, position, velocity); const propagator = new RungeKutta89Propagator(state, forceModel); let total = 0; for (const oC of this.observations_) { const sC = propagator.propagate(oC.epoch); const pC = oC.site; const expected = oC.observation.lineOfSight(); const actual = RadecTopocentric.fromStateVector(sC, pC).lineOfSight(); const error = expected.angle(actual); total += error; } return total; }; return scoreFn; } solve( r0: Kilometers, rN: Kilometers, { nRev = 0, direction = true, posSearch = 10.0, velSearch = 0.1, tolerance = 1e-6, printIter = false, }: SolveOptions, ): J2000 { if (this.observations_.length < 3) { throw new Error('At least 3 observations required for Gooding IOD.'); } const init = this.createInitial_(r0, rN, nRev, direction); const guess = Float64Array.from([...init.position.toArray(), ...init.velocity.toArray()]); const solveFn = this._createErrorFunction(init.epoch); const simplex = [ Float64Array.from(guess), Float64Array.from([guess[0] + posSearch, guess[1], guess[2], guess[3], guess[4], guess[5]]), Float64Array.from([guess[0], guess[1] + posSearch, guess[2], guess[3], guess[4], guess[5]]), Float64Array.from([guess[0], guess[1], guess[2] + posSearch, guess[3], guess[4], guess[5]]), Float64Array.from([guess[0], guess[1], guess[2], guess[3] + velSearch, guess[4], guess[5]]), Float64Array.from([guess[0], guess[1], guess[2], guess[3], guess[4] + velSearch, guess[5]]), Float64Array.from([guess[0], guess[1], guess[2], guess[3], guess[4], guess[5] + velSearch]), ]; const result = DownhillSimplex.solveSimplex(solveFn, simplex, { adaptive: true, xTolerance: tolerance, fTolerance: tolerance, printIter, }); return new J2000( init.epoch, new Vector3D(result[0] as Kilometers, result[1] as Kilometers, result[2] as Kilometers), new Vector3D( result[3] as KilometersPerSecond, result[4] as KilometersPerSecond, result[5] as KilometersPerSecond, ), ); } }