# Units and coordinates English | [简体中文](./units.zh.md) This file records the unit and coordinate conventions that cross layers. The conventions inside a single crate are in its module documentation; what belongs here is the part that must agree **across a layer boundary**, because a unit that disagrees at a boundary is the easiest mistake to introduce in this repository and the hardest to find. ## Two units of length The repository uses both kilometres and metres, with the boundary between the orbit layer and the physics layer: | Layer / crate | Unit of length | | --- | --- | | `apsis-kepler`, `apsis-sgp4`, `apsis-tle` | kilometres | | `apsis-ephemeris` | kilometres | | `apsis-maneuver` | kilometres | | `apsis-constellation`, `apsis-coverage`, `apsis-access`, `apsis-isl` | kilometres | | `apsis-topology`, `apsis-routing`, `apsis-mesh` | length is computed only in `apsis-isl`; an edge's `cost` is a dimensionless value the caller defines | | `apsis-handover` | no length; `quality` is defined by the caller and time is in seconds | | `apsis-coord` (`Eci`, `Ecef`) | metres | | `apsis-geodesy` (geodetic height, ellipsoid semi-axes, Cartesian position) | metres | | `apsis-frames` | dimensionless (angles and rotations) | | `apsis-rf`, `apsis-antenna`, `apsis-atmosphere`, `apsis-noise`, `apsis-doppler`, `apsis-interference`, `apsis-link` | metres, hertz | The choice is not arbitrary. Satellite orbits read naturally in kilometres, which is the magnitude `apsis-sgp4` produces, and a radio link reads naturally in metres, which is what the ITU-R recommendations use. The cost is that the boundary needs an explicit conversion. ### Converting at the boundary Multiply by `1000.0` on the way from the orbit layer into the physics layer, and divide on the way back: ```rust // A position in kilometres from the orbit layer becomes metres in physics. let position_km = satellite.propagate_minutes(0.0)?.position; // kilometres let ecef = Ecef::new(position_km * 1_000.0); // metres ``` Omitting this step puts the satellite inside the Earth, and the numbers still look plausible rather than failing at once. The `integrate_network` example asserts on this boundary: the radius after the conversion must match the radius the propagator reports, which is the only check that finds a missing multiply or divide automatically. ## Angles **Every angle used in a calculation is in radians.** Latitude, longitude, inclination, right ascension of the ascending node, argument of perigee, true anomaly, elevation, azimuth and antenna pattern angle are all radians. The only exception is the two-line element set, whose raw fields are in degrees and revolutions per day because that is how the file encodes them. `apsis-sgp4` converts them to radians and radians per minute on initialisation. ## Time Time is in seconds throughout, with the origin chosen by the caller: - The ops and sim layers count seconds from the scenario epoch. - The orbit layer uses minutes from the element set epoch in `apsis-sgp4`; `apsis-ephemeris` and the rest use seconds. - Calendar time: `apsis-time::Epoch` holds a TAI Julian date, and a conversion to UTC goes through the leap second table in `apsis-leap`. `Orbit::sample_ephemeris` is the boundary between the two representations: it takes minutes from the element epoch and the ephemeris it returns is in seconds, with the conversion done inside the method. ## Coordinate frames | Frame | Type | Notes | | --- | --- | --- | | TEME | `apsis_sgp4::State` | the true equator, mean equinox frame the SGP4 model defines | | ECI | `apsis_coord::Eci` | inertial Cartesian coordinates | | ECEF | `apsis_coord::Ecef` | Earth-fixed Cartesian coordinates | | Geodetic | `apsis_geodesy::Geodetic` | latitude, longitude, height above the ellipsoid | | Ground point | `apsis_coverage::GroundPoint` | a geodetic position plus an elevation mask | Going from TEME to ECEF needs the Earth rotation angle (`apsis-frames::EarthRotationAngle`) together with precession and nutation. `apsis-coord` provides the transformation and `apsis-frames` provides the angle. TEME differs from ECI by the equation of the equinoxes, which is below a thousandth of a degree; the `integrate_network` example treats it as the inertial frame and says so, because for that example it is an approximation. ## Other quantities - The gravitational parameter is `apsis-kepler::GravityParameter`, in cubic kilometres per second squared. WGS 72 goes with SGP4 and WGS 84 with everything else; choosing the wrong one shifts the osculating semi-major axis by about one part in a million. The `apsis-orbit::Orbit` facade binds the parameter to the propagator so that this cannot go wrong. - Velocity follows the unit of length in its own layer, so km/s or m/s. - Power is in watts, frequency in hertz and temperature in kelvin. A decibel is a power ratio; `dBi` is relative to an isotropic radiator and `dBd` to a half-wave dipole, which is 2.15 dB more directive. - Every relative quantity is dimensionless: eccentricity, fill rate, utilisation and a signal to noise ratio in decibels. ## Checks Nothing in the type system protects a unit boundary, so the tests and this document carry it: - Each layer's tests cover the conversions inside that layer. - `integrate_network` asserts that the kilometre to metre boundary conversion preserves the radius. - `integrate_ops` and `integrate_sim` cover the time axis conventions of the ops and sim layers. - `cargo run --release -p apsis-rs --features full --example bench` touches the hot path of every layer, so an interface change in any of them shows up there as a build failure.