Sensors
Ground stations, attached sensor components, and the low-level transform functions (ECEF, ECI, LLA, RAE) used for look-angle math. Use this when you need to convert positions between Earth-fixed, inertial, and topocentric frames, or when you want a sensor with field-of-view constraints attached to a ground site.
import {
calcGmst,
Degrees,
ecef2eci,
ecef2rae,
EcefVec3,
eci2lla,
eci2rae,
GroundStation,
Kilometers,
PhasedArrayRadar,
Satellite,
SensorType,
TleLine1,
TleLine2,
} from 'ootk';Run it
npm run build
npx tsx ./examples/sensor.tsCreate a ground station
GroundStation is the location-bearing object: it holds lat/lon/alt and provides coordinate helpers like lla(), eci(), and toJ2000(). Sensors do not carry a position of their own; they attach to a platform like this one.
// A GroundStation is a fixed location on Earth. It owns the position;
// sensors attach to it as components.
const groundStation = new GroundStation({
lat: 41 as Degrees,
lon: -71 as Degrees,
alt: 1 as Kilometers,
name: 'Test Station',
});
console.log(groundStation.toString());Attach a sensor
Sensors are components. Construct a concrete type (here PhasedArrayRadar) with a fieldOfView (cone half-angle, range bounds, minimum elevation) plus radar-specific parameters, then wire it to the platform with groundStation.addSensor(radar) and radar.setParent(groundStation). Both calls are needed: the station tracks its sensors, and the sensor delegates position queries to its parent.
// Sensors no longer carry their own lat/lon/alt. Build a concrete sensor
// (here a phased array radar) with a FieldOfView, then attach it to a platform.
const capeCodRadar = new PhasedArrayRadar({
id: 1,
name: 'Cape Cod',
sensorType: SensorType.PHASED_ARRAY_RADAR,
beamwidth: 2 as Degrees,
boresightAz: [47 as Degrees, 167 as Degrees], // two faces
boresightEl: [20 as Degrees, 20 as Degrees],
fieldOfView: {
halfAngle: 60 as Degrees,
minRange: 200 as Kilometers,
maxRange: 5556 as Kilometers,
minElevation: 3 as Degrees,
},
});
groundStation.addSensor(capeCodRadar);
capeCodRadar.setParent(groundStation);
console.log(`\n${capeCodRadar.toString()}`);ECEF to RAE
ecef2rae(lla, ecef) converts an Earth-fixed position into range, azimuth, and elevation as seen from an observer given in geodetic degrees. The station's lla() output feeds it directly.
// Convert an ECEF position directly to range/azimuth/elevation as seen
// from the ground station.
const ecef = {
x: 4000 as Kilometers,
y: 7000 as Kilometers,
z: 3000 as Kilometers,
} as EcefVec3<Kilometers>;
const rae = ecef2rae(groundStation.lla(), ecef);
console.log('\nRAE from ECEF:');
console.log(rae);ECI conversions
The same point can be routed through the inertial frame: calcGmst(date) gives the rotation angle, ecef2eci moves the point into ECI, and eci2rae(date, eci, observer) or eci2lla(eci, gmst) bring it back to observer-relative or geodetic coordinates. The RAE result matches the direct ECEF path.
// The same point can be routed through the inertial (ECI) frame.
// GMST is needed to rotate between Earth-fixed and inertial frames.
const date = new Date('2023-12-31T20:51:19.934Z');
const { gmst } = calcGmst(date);
const eci = ecef2eci(ecef, gmst);
const rae2 = eci2rae(date, eci, groundStation);
const lla = eci2lla(eci, gmst);
console.log('\nRAE from ECI (should match the ECEF path):');
console.log(rae2);
console.log('\nGeodetic coordinates of the ECI point:');
console.log(lla);Satellite look angles
Satellite.rae(groundObject, date) computes look angles in one call, and toJ2000(date).toITRF().toGeodetic() chains frame conversions to get the subsatellite point. The attached radar layers its FOV constraints on top via canObserve(sat, date).
// Satellites expose the same math directly: rae() gives look angles from a
// ground object, and toJ2000() chains into other frames.
const sat = new Satellite({
tle1: '1 00005U 58002B 23361.70345217 .00000401 00000-0 53694-3 0 99999' as TleLine1,
tle2: '2 00005 34.2395 218.8683 1841681 30.7692 338.8934 10.85144797345180' as TleLine2,
});
console.log('\nSatellite look angles from the ground station:');
console.log(sat.rae(groundStation, date));
console.log('\nSatellite geodetic position (J2000 -> ITRF -> Geodetic):');
console.log(sat.toJ2000(date).toITRF().toGeodetic());
// The attached sensor can apply its field-of-view constraints on top.
console.log(`\nIs the satellite in the radar's FOV? ${capeCodRadar.canObserve(sat, date) ? 'YES' : 'NO'}`);Output
[GroundStation]
ID: -1
Name: Test Station
Location: 41.0000°, -71.0000°, 1.000 km
Sensors: 0
Comm Devices: 0
[_PhasedArrayRadar]
ID: 1
Name: Cape Cod
Type: PHASED_ARRAY_RADAR
Parent: Test Station
[FieldOfView]
Boresight: Az 0.0°, El 90.0°
Shape: 60.0° cone
Range: 200.0 - 5556.0 km
Min Elevation: 3.0°
Faces: 2
Face 0: Az=47.0°, El=20.0°
Face 1: Az=167.0°, El=20.0°
RAE from ECEF:
{
rng: 11867.826042828654,
az: 46.3936383480027,
el: -45.14527065977833
}
RAE from ECI (should match the ECEF path):
{
rng: 11867.826042828654,
az: 46.3936383480027,
el: -45.14527065977833
}
Geodetic coordinates of the ECI point:
{
lon: 60.25511870305778,
lat: 20.50378567774451,
alt: 2226.796648045177
}
Satellite look angles from the ground station:
{
rng: 2323.6437976750553,
az: 220.1318954201388,
el: 31.442701897926916
}
Satellite geodetic position (J2000 -> ITRF -> Geodetic):
_Geodetic {
lat: 0.5103321455362404,
lon: -1.4268203137993296,
alt: 1467.0398331566748
}
Is the satellite in the radar's FOV? YES