UNPKG

ootk-core

Version:

Orbital Object Toolkit. A modern typed replacement for satellite.js including SGP4 propagation, TLE parsing, Sun and Moon calculations, and more.

395 lines 14.8 kB
/** * @author Theodore Kruczek. * @license MIT * @copyright (c) 2022-2025 Theodore Kruczek Permission is * hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the * Software without restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { DEG2RAD, Earth, MILLISECONDS_TO_DAYS, PI, RAD2DEG, Sgp4, TAU, } from '../main.js'; /** * Converts ECF to ECI coordinates. * * [X] [C -S 0][X] * [Y] = [S C 0][Y] * [Z]eci [0 0 1][Z]ecf * @param ecf takes xyz coordinates * @param gmst takes a number in gmst time * @returns array containing eci coordinates */ export function ecf2eci(ecf, gmst) { const X = (ecf.x * Math.cos(gmst) - ecf.y * Math.sin(gmst)); const Y = (ecf.x * Math.sin(gmst) + ecf.y * Math.cos(gmst)); const Z = ecf.z; return { x: X, y: Y, z: Z }; } /** * Converts ECEF coordinates to ENU coordinates. * @param ecf - The ECEF coordinates. * @param lla - The LLA coordinates. * @returns The ENU coordinates. */ export function ecf2enu(ecf, lla) { const { lat, lon } = lla; const { x, y, z } = ecf; const e = (-Math.sin(lon) * x + Math.cos(lon) * y); const n = (-Math.sin(lat) * Math.cos(lon) * x - Math.sin(lat) * Math.sin(lon) * y + Math.cos(lat) * z); const u = (Math.cos(lat) * Math.cos(lon) * x + Math.cos(lat) * Math.sin(lon) * y + Math.sin(lat) * z); return { x: e, y: n, z: u }; } /** * Converts ECI to ECF coordinates. * * [X] [C -S 0][X] * [Y] = [S C 0][Y] * [Z]eci [0 0 1][Z]ecf * * Inverse: * [X] [C S 0][X] * [Y] = [-S C 0][Y] * [Z]ecf [0 0 1][Z]eci * @param eci takes xyz coordinates * @param gmst takes a number in gmst time * @returns array containing ecf coordinates */ export function eci2ecf(eci, gmst) { const x = (eci.x * Math.cos(gmst) + eci.y * Math.sin(gmst)); const y = (eci.x * -Math.sin(gmst) + eci.y * Math.cos(gmst)); const z = eci.z; return { x, y, z, }; } /** * EciToGeodetic converts eci coordinates to lla coordinates * @variation cached - results are cached * @param eci takes xyz coordinates * @param gmst takes a number in gmst time * @returns array containing lla coordinates */ export function eci2lla(eci, gmst) { // http://www.celestrak.com/columns/v02n03/ const a = 6378.137; const b = 6356.7523142; const R = Math.sqrt(eci.x * eci.x + eci.y * eci.y); const f = (a - b) / a; const e2 = 2 * f - f * f; let lon = Math.atan2(eci.y, eci.x) - gmst; while (lon < -PI) { lon += TAU; } while (lon > PI) { lon -= TAU; } const kmax = 20; let k = 0; let lat = Math.atan2(eci.z, Math.sqrt(eci.x * eci.x + eci.y * eci.y)); let C = 0; while (k < kmax) { C = 1 / Math.sqrt(1 - e2 * (Math.sin(lat) * Math.sin(lat))); lat = Math.atan2(eci.z + a * C * e2 * Math.sin(lat), R); k += 1; } const alt = R / Math.cos(lat) - a * C; lon = (lon * RAD2DEG); lat = (lat * RAD2DEG); return { lon: lon, lat: lat, alt: alt }; } /** * Converts geodetic coordinates (longitude, latitude, altitude) to Earth-Centered Earth-Fixed (ECF) coordinates. * @param lla The geodetic coordinates in radians and meters. * @returns The ECF coordinates in meters. */ export function llaRad2ecf(lla) { const { lon, lat, alt } = lla; const a = 6378.137; const b = 6356.7523142; const f = (a - b) / a; const e2 = 2 * f - f * f; const normal = a / Math.sqrt(1 - e2 * Math.sin(lat) ** 2); const x = (normal + alt) * Math.cos(lat) * Math.cos(lon); const y = (normal + alt) * Math.cos(lat) * Math.sin(lon); const z = (normal * (1 - e2) + alt) * Math.sin(lat); return { x: x, y: y, z: z, }; } /** * Converts geodetic coordinates (longitude, latitude, altitude) to Earth-Centered Earth-Fixed (ECF) coordinates. * @param lla The geodetic coordinates in degrees and meters. * @returns The ECF coordinates in meters. */ export function lla2ecf(lla) { const { lon, lat, alt } = lla; const lonRad = lon * DEG2RAD; const latRad = lat * DEG2RAD; return llaRad2ecf({ lon: lonRad, lat: latRad, alt, }); } /** * Converts geodetic coordinates (latitude, longitude, altitude) to Earth-centered inertial (ECI) coordinates. * @variation cached - results are cached * @param lla The geodetic coordinates in radians and meters. * @param gmst The Greenwich Mean Sidereal Time in seconds. * @returns The ECI coordinates in meters. */ export function lla2eci(lla, gmst) { const { lat, lon, alt } = lla; const cosLat = Math.cos(lat); const sinLat = Math.sin(lat); const cosLon = Math.cos(lon + gmst); const sinLon = Math.sin(lon + gmst); const x = (Earth.radiusMean + alt) * cosLat * cosLon; const y = (Earth.radiusMean + alt) * cosLat * sinLon; const z = (Earth.radiusMean + alt) * sinLat; return { x, y, z }; } /** * Calculates Geodetic Lat Lon Alt to ECEF coordinates. * @deprecated This needs to be validated. * @param lla The geodetic coordinates in degrees and meters. * @returns The ECEF coordinates in meters. */ export function lla2ecef(lla) { const { lat, lon, alt } = lla; const a = 6378.137; // semi-major axis length in meters according to the WGS84 const b = 6356.752314245; // semi-minor axis length in meters according to the WGS84 const e = Math.sqrt(1 - b ** 2 / a ** 2); // eccentricity const N = a / Math.sqrt(1 - e ** 2 * Math.sin(lat) ** 2); // radius of curvature in the prime vertical const x = ((N + alt) * Math.cos(lat) * Math.cos(lon)); const y = ((N + alt) * Math.cos(lat) * Math.sin(lon)); const z = ((N * (1 - e ** 2) + alt) * Math.sin(lat)); return { x, y, z }; } /** * Converts LLA to SEZ coordinates. * @see http://www.celestrak.com/columns/v02n02/ * @param lla The LLA coordinates. * @param ecf The ECF coordinates. * @returns The SEZ coordinates. */ export function lla2sez(lla, ecf) { const lon = lla.lon; const lat = lla.lat; const observerEcf = llaRad2ecf({ lat, lon, alt: 0, }); const rx = ecf.x - observerEcf.x; const ry = ecf.y - observerEcf.y; const rz = ecf.z - observerEcf.z; // Top is short for topocentric const south = Math.sin(lat) * Math.cos(lon) * rx + Math.sin(lat) * Math.sin(lon) * ry - Math.cos(lat) * rz; const east = -Math.sin(lon) * rx + Math.cos(lon) * ry; const zenith = Math.cos(lat) * Math.cos(lon) * rx + Math.cos(lat) * Math.sin(lon) * ry + Math.sin(lat) * rz; return { s: south, e: east, z: zenith }; } /** * Converts a vector in Right Ascension, Elevation, and Range (RAE) coordinate system * to a vector in South, East, and Zenith (SEZ) coordinate system. * @param rae The vector in RAE coordinate system. * @returns The vector in SEZ coordinate system. */ export function rae2sez(rae) { const south = -rae.rng * Math.cos(rae.el) * Math.cos(rae.az); const east = rae.rng * Math.cos(rae.el) * Math.sin(rae.az); const zenith = rae.rng * Math.sin(rae.el); return { s: south, e: east, z: zenith, }; } /** * Converts a vector in Right Ascension, Elevation, and Range (RAE) coordinate system * to Earth-Centered Fixed (ECF) coordinate system. * @template D - The dimension of the RAE vector. * @template A - The dimension of the LLA vector. * @param rae - The vector in RAE coordinate system. * @param lla - The vector in LLA coordinate system. * @returns The vector in ECF coordinate system. */ export function rae2ecf(rae, lla) { const llaRad = { lat: (lla.lat * DEG2RAD), lon: (lla.lon * DEG2RAD), alt: lla.alt, }; const raeRad = { az: (rae.az * DEG2RAD), el: (rae.el * DEG2RAD), rng: rae.rng, }; const obsEcf = llaRad2ecf(llaRad); const sez = rae2sez(raeRad); // Some needed calculations const slat = Math.sin(llaRad.lat); const slon = Math.sin(llaRad.lon); const clat = Math.cos(llaRad.lat); const clon = Math.cos(llaRad.lon); const x = slat * clon * sez.s + -slon * sez.e + clat * clon * sez.z + obsEcf.x; const y = slat * slon * sez.s + clon * sez.e + clat * slon * sez.z + obsEcf.y; const z = -clat * sez.s + slat * sez.z + obsEcf.z; return { x, y, z }; } /** * Converts a vector from RAE (Range, Azimuth, Elevation) coordinates to ECI (Earth-Centered Inertial) coordinates. * @variation cached - results are cached * @param rae The vector in RAE coordinates. * @param lla The vector in LLA (Latitude, Longitude, Altitude) coordinates. * @param gmst The Greenwich Mean Sidereal Time. * @returns The vector in ECI coordinates. */ export function rae2eci(rae, lla, gmst) { const ecf = rae2ecf(rae, lla); const eci = ecf2eci(ecf, gmst); return eci; } /** * Converts a vector in RAE (Range, Azimuth, Elevation) coordinates to ENU (East, North, Up) coordinates. * @param rae - The vector in RAE coordinates. * @returns The vector in ENU coordinates. */ export function rae2enu(rae) { const e = (rae.rng * Math.cos(rae.el) * Math.sin(rae.az)); const n = (rae.rng * Math.cos(rae.el) * Math.cos(rae.az)); const u = (rae.rng * Math.sin(rae.el)); return { x: e, y: n, z: u }; } /** * Converts South, East, and Zenith (SEZ) coordinates to Right Ascension, Elevation, and Range (RAE) coordinates. * @param sez The SEZ coordinates. * @returns Rng, Az, El array */ export function sez2rae(sez) { const rng = Math.sqrt(sez.s * sez.s + sez.e * sez.e + sez.z * sez.z); const el = Math.asin(sez.z / rng); const az = (Math.atan2(-sez.e, sez.s) + PI); return { rng, az, el }; } /** * Converts Earth-Centered Fixed (ECF) coordinates to Right Ascension (RA), * Elevation (E), and Azimuth (A) coordinates. * @param lla The Latitude, Longitude, and Altitude (LLA) coordinates. * @param ecf The Earth-Centered Fixed (ECF) coordinates. * @returns The Right Ascension (RA), Elevation (E), and Azimuth (A) coordinates. */ export function ecfRad2rae(lla, ecf) { const sezCoords = lla2sez(lla, ecf); const rae = sez2rae(sezCoords); return { rng: rae.rng, az: (rae.az * RAD2DEG), el: (rae.el * RAD2DEG) }; } /** * Converts Earth-Centered Fixed (ECF) coordinates to Right Ascension (RA), * Elevation (E), and Azimuth (A) coordinates. * @variation cached - results are cached * @param lla The Latitude, Longitude, and Altitude (LLA) coordinates. * @param ecf The Earth-Centered Fixed (ECF) coordinates. * @returns The Right Ascension (RA), Elevation (E), and Azimuth (A) coordinates. */ export function ecf2rae(lla, ecf) { const { lat, lon } = lla; const latRad = (lat * DEG2RAD); const lonRad = (lon * DEG2RAD); const rae = ecfRad2rae({ lat: latRad, lon: lonRad, alt: lla.alt }, ecf); return rae; } export const jday = (year, mon, day, hr, minute, sec) => { if (typeof year === 'undefined') { const now = new Date(); const jDayStart = new Date(now.getUTCFullYear(), 0, 0); const jDayDiff = now.getDate() - jDayStart.getDate(); return Math.floor(jDayDiff / MILLISECONDS_TO_DAYS); } if (typeof mon === 'undefined' || typeof day === 'undefined' || typeof hr === 'undefined' || typeof minute === 'undefined' || typeof sec === 'undefined') { throw new Error('Invalid date'); } return (367.0 * year - Math.floor(7 * (year + Math.floor((mon + 9) / 12.0)) * 0.25) + Math.floor((275 * mon) / 9.0) + day + 1721013.5 + ((sec / 60.0 + minute) / 60.0 + hr) / 24.0); }; /** * Calculates the Greenwich Mean Sidereal Time (GMST) for a given date. * @param date - The date for which to calculate the GMST. * @returns An object containing the GMST value and the Julian date. */ export function calcGmst(date) { const j = jday(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()) + date.getUTCMilliseconds() * MILLISECONDS_TO_DAYS; const gmst = Sgp4.gstime(j); return { gmst, j }; } /** * Converts ECI coordinates to RAE (Right Ascension, Azimuth, Elevation) coordinates. * @variation cached - results are cached * @param now - Current date and time. * @param eci - ECI coordinates of the satellite. * @param sensor - Sensor object containing observer's geodetic coordinates. * @returns Object containing azimuth, elevation and range in degrees and kilometers respectively. */ export function eci2rae(now, eci, sensor) { now = new Date(now); const { gmst } = calcGmst(now); const positionEcf = eci2ecf(eci, gmst); const lla = { lat: (sensor.lat * DEG2RAD), lon: (sensor.lon * DEG2RAD), alt: sensor.alt, }; const rae = ecfRad2rae(lla, positionEcf); return rae; } /** * Calculates the inertial azimuth of a satellite given its latitude and inclination. * @param lat - The latitude of the satellite in degrees. * @param inc - The inclination of the satellite in degrees. * @returns The inertial azimuth of the satellite in degrees. */ export function calcInertAz(lat, inc) { const phi = lat * DEG2RAD; const i = inc * DEG2RAD; const az = Math.asin(Math.cos(i) / Math.cos(phi)); return (az * RAD2DEG); } /** * Calculates the inclination angle of a satellite from its launch azimuth and latitude. * @param lat - The latitude of the observer in degrees. * @param az - The launch azimuth angle of the satellite in degrees clockwise from north. * @returns The inclination angle of the satellite in degrees. */ export function calcIncFromAz(lat, az) { const phi = lat * DEG2RAD; const beta = az * DEG2RAD; const inc = Math.acos(Math.sin(beta) * Math.cos(phi)); return (inc * RAD2DEG); } //# sourceMappingURL=transforms.js.map