ootk
Version:
Orbital Object Toolkit including Multiple Propagators, Initial Orbit Determination, and Maneuver Calculations.
222 lines • 10 kB
JavaScript
/**
* @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, J2000, Matrix, RelativeState, Vector3D, } from '../main.js';
import { Thrust } from './../force/Thrust.js';
// / Hill Modified Equidistant Cyllindrical _(EQCM)_ coordinates.
export class Hill {
epoch;
position;
velocity;
semimajorAxis_;
meanMotion_;
constructor(epoch, position, velocity, semimajorAxis) {
this.epoch = epoch;
this.position = position;
this.velocity = velocity;
this.semimajorAxis_ = semimajorAxis;
this.meanMotion_ = Earth.smaToMeanMotion(this.semimajorAxis_);
}
static fromState(origin, radialPosition, intrackPosition, nodeVelocity, nodeOffsetTime) {
const a = origin.semimajorAxis;
const n = Earth.smaToMeanMotion(a);
const yDot = -3.0 * radialPosition * n * 0.5;
const z = (nodeVelocity / n) * Math.sin(n * -nodeOffsetTime);
const zDot = nodeVelocity * Math.cos(n * -nodeOffsetTime);
const r = new Vector3D(radialPosition, intrackPosition, z);
const v = new Vector3D(0.0, yDot, zDot);
return new Hill(origin.epoch, r, v, a);
}
static fromNmc(origin, majorAxisRange, nodeVelocity, nodeOffsetTime, translation = 0.0) {
const a = origin.semimajorAxis;
const n = Earth.smaToMeanMotion(a);
const xDot = majorAxisRange * n * 0.5;
const z = (nodeVelocity / n) * Math.sin(n * -nodeOffsetTime);
const zDot = nodeVelocity * Math.cos(n * -nodeOffsetTime);
const r = new Vector3D(0.0, majorAxisRange + translation, z);
const v = new Vector3D(xDot, 0.0, zDot);
return new Hill(origin.epoch, r, v, a);
}
static fromPerch(origin, perchRange, nodeVelocity, nodeOffsetTime) {
const a = origin.semimajorAxis;
const n = Earth.smaToMeanMotion(a);
const z = (nodeVelocity / n) * Math.sin(n * -nodeOffsetTime);
const zDot = nodeVelocity * Math.cos(n * -nodeOffsetTime);
const r = new Vector3D(0.0, perchRange, z);
const v = new Vector3D(0.0, 0.0, zDot);
return new Hill(origin.epoch, r, v, a);
}
get semimajorAxis() {
return this.semimajorAxis_;
}
set semimajorAxis(sma) {
this.semimajorAxis_ = sma;
this.meanMotion_ = Earth.smaToMeanMotion(this.semimajorAxis_);
}
get meanMotion() {
return this.meanMotion_;
}
toJ2000Matrix(origin, transform) {
const magrtgt = origin.position.magnitude();
const magrint = magrtgt + this.position.x;
const vtgtrsw = transform.multiplyVector3D(origin.velocity);
const lambdadottgt = vtgtrsw.y / magrtgt;
const lambdaint = this.position.y / magrtgt;
const phiint = this.position.z / magrtgt;
const sinphiint = Math.sin(phiint);
const cosphiint = Math.cos(phiint);
const sinlambdaint = Math.sin(lambdaint);
const coslambdaint = Math.cos(lambdaint);
const rotRswSez = new Matrix([
[sinphiint * coslambdaint, sinphiint * sinlambdaint, -cosphiint],
[-sinlambdaint, coslambdaint, 0],
[cosphiint * coslambdaint, cosphiint * sinlambdaint, sinphiint],
]);
const rdotint = this.velocity.x + vtgtrsw.x;
const lambdadotint = this.velocity.y / magrtgt + lambdadottgt;
const phidotint = this.velocity.z / magrtgt;
const vintsez = new Vector3D(-magrint * phidotint, magrint * lambdadotint * cosphiint, rdotint);
const vintrsw = rotRswSez.transpose().multiplyVector3D(vintsez);
const vinteci = transform.transpose().multiplyVector3D(vintrsw);
const rintrsw = new Vector3D(cosphiint * magrint * coslambdaint, cosphiint * magrint * sinlambdaint, sinphiint * magrint);
const rinteci = transform.transpose().multiplyVector3D(rintrsw);
return new J2000(origin.epoch, rinteci, vinteci);
}
toJ2000(origin) {
return this.toJ2000Matrix(origin, RelativeState.createMatrix(origin.position, origin.velocity));
}
static transitionMatrix(t, meanMotion) {
const n = meanMotion;
const sn = Math.sin(n * t);
const cs = Math.cos(n * t);
return new Matrix([
[4.0 - 3.0 * cs, 0.0, 0.0, sn / n, (2.0 * (1.0 - cs)) / n, 0.0],
[6.0 * (sn - n * t), 1.0, 0.0, (-2.0 * (1.0 - cs)) / n, (4.0 * sn - 3.0 * n * t) / n, 0.0],
[0.0, 0.0, cs, 0.0, 0.0, sn / n],
[3.0 * n * sn, 0.0, 0.0, cs, 2.0 * sn, 0.0],
[-6.0 * n * (1.0 - cs), 0.0, 0.0, -2.0 * sn, 4.0 * cs - 3.0, 0.0],
[0.0, 0.0, -n * sn, 0.0, 0.0, cs],
]);
}
transition(t) {
const sysMat = Hill.transitionMatrix(t, this.meanMotion_);
const res = sysMat.multiplyVector(this.position.join(this.velocity)).elements;
return new Hill(this.epoch.roll(t), new Vector3D(res[0], res[1], res[2]), new Vector3D(res[3], res[4], res[5]), this.semimajorAxis_);
}
transitionWithMatrix(stm, t) {
const res = stm.multiplyVector(this.position.join(this.velocity)).elements;
return new Hill(this.epoch.roll(t), new Vector3D(res[0], res[1], res[2]), new Vector3D(res[3], res[4], res[5]), this.semimajorAxis_);
}
propagate(newEpoch) {
return this.transition(newEpoch.difference(this.epoch));
}
propagateWithMatrix(stm, newEpoch) {
return this.transitionWithMatrix(stm, newEpoch.difference(this.epoch));
}
maneuver(maneuver) {
const state = this.propagate(maneuver.center);
return new Hill(state.epoch, state.position, state.velocity.add(maneuver.deltaV), state.semimajorAxis_);
}
ephemeris(start, stop, step = 60.0) {
const output = [];
let current = start;
while (stop >= current) {
output.push(this.propagate(current));
current = current.roll(step);
}
return output;
}
get period() {
return (2 * Math.PI) / this.meanMotion_;
}
nextRadialTangent() {
const x = this.position.x;
const xDot = this.velocity.x;
const yDot = this.velocity.y;
let t = Math.atan(-xDot / (3.0 * this.meanMotion_ * x + 2.0 * yDot)) / this.meanMotion_;
if (t <= 0) {
t = t + 0.5 * this.period;
}
else if (isNaN(t)) {
t = 0.5 * this.period;
}
return this.propagate(this.epoch.roll(t));
}
solveManeuver(waypoint, ignoreCrosstrack = false) {
const t = waypoint.epoch.difference(this.epoch);
const w = waypoint.relativePosition;
const sysMat = Hill.transitionMatrix(t, this.meanMotion_);
const posEquationMat = new Matrix([
[sysMat.elements[0][0], sysMat.elements[0][1], sysMat.elements[0][2]],
[sysMat.elements[1][0], sysMat.elements[1][1], sysMat.elements[1][2]],
[sysMat.elements[2][0], sysMat.elements[2][1], sysMat.elements[2][2]],
]);
const solnVector = w
.subtract(posEquationMat.multiplyVector3D(this.position));
const velEquationMat = new Matrix([
[sysMat.elements[0][3], sysMat.elements[0][4], sysMat.elements[0][5]],
[sysMat.elements[1][3], sysMat.elements[1][4], sysMat.elements[1][5]],
[sysMat.elements[2][3], sysMat.elements[2][4], sysMat.elements[2][5]],
]);
let result = velEquationMat
.inverse()
.multiplyVector3D(solnVector)
.subtract(this.velocity);
if (ignoreCrosstrack) {
result = new Vector3D(result.x, result.y, 0);
}
return new Thrust(this.epoch, result.x * 1000, result.y * 1000, result.z * 1000);
}
maneuverSequence(pivot, waypoints, preManeuvers = [], postManeuvers = []) {
let state = new Hill(this.epoch, this.position, this.velocity, this.semimajorAxis_);
preManeuvers = preManeuvers.slice();
postManeuvers = postManeuvers.slice();
let output = preManeuvers;
// Note difference was once compareTo
output.sort((a, b) => a.center.difference(b.center));
output = output.filter((mvr) => mvr.center >= this.epoch && mvr.center >= pivot);
for (const mvr of output) {
state = state.maneuver(mvr);
}
state = state.propagate(pivot);
for (const wpt of waypoints) {
const mvr = state.solveManeuver(wpt);
state = state.maneuver(mvr);
output.push(mvr);
}
output.push(...postManeuvers);
return output;
}
maneuverOrigin(maneuver) {
const state = this.propagate(maneuver.center);
const vInit = Math.sqrt(Earth.mu / this.semimajorAxis_);
const vFinal = vInit - maneuver.intrack * 1e-3;
const aFinal = Earth.mu / (vFinal * vFinal);
return new Hill(state.epoch, state.position, state.velocity.subtract(maneuver.deltaV), aFinal);
}
get name() {
return 'Hill';
}
toString() {
return [
`[${this.name}]`,
` Epoch: ${this.epoch}`,
` Position: ${this.position.toString(6)} km`,
` Velocity: ${this.velocity.toString(9)} km/s`,
].join('\n');
}
}
//# sourceMappingURL=Hill.js.map