geo3d
3D geodesy in pure f64, zero dependencies.
Real 3D geometry cannot be done on geodetic (lat, lon, height) directly — it is not a Euclidean
space. This crate provides the metric frames and the closed-form primitives on top of them, in a small,
typed, allocation-free API:
Ellipsoid (WGS84, GRS80, …) |
geodetic ↔ ECEF (closed form both ways), prime-vertical radius, ray–ellipsoid intersection |
Geodetic, Ecef, Enu, Ned, Aer |
typed coordinates — lat/lon or east/north cannot be swapped by accident |
LocalFrame |
a topocentric East-North-Up frame that precomputes its rotation once; ENU ↔ ECEF ↔ geodetic, look angles (az/el/range) and their inverse, ground strike |
AltitudeBand, Sphere, Cone, Cylinder (Volume) |
closed-form volumes; compose with any 2D footprint test for extruded prisms |
Track, Course, closest_approach |
dead reckoning, course-over-ground ↔ ECEF velocity, first-order conflict detection |
Ellipsoid extras |
radii of curvature, geocentric latitude, horizon distance, geometric line of sight, ECEF interpolation, spherical great-circle distance / bearing / destination / midpoint |
eci |
Julian date, GMST (IAU 1982), inertial ↔ Earth-fixed |
Helmert7 |
7-parameter datum shifts, both EPSG rotation conventions |
Angles in the public API are degrees, lengths metres. ECEF is the frame CesiumJS renders in, so a
(lat, lon, height) track maps straight to what a globe draws.
use ;
let site = new;
let aircraft = new;
let slant = distance3d_m; // through-space distance, metres
let frame = new;
let aer = frame.look_angles; // azimuth / elevation / range
let back = frame.destination; // ...and back to the aircraft
let ground = frame.ground_strike; // where a 3° down-look meets the surface
assert!;
cargo run --example tracking walks through a ground station tracking an aircraft.
Design
- Zero dependencies, pure
f64. Every type isCopy; nothing allocates;#![forbid(unsafe_code)]. - Precompute per reference, not per point.
LocalFrame,SphereandConedo their trigonometry once at construction, so "many targets against one observer" is a subtraction and a few dot products per point. - Closed forms. Geodetic ↔ ECEF uses the exact forward form and Bowring's inverse (sub-millimetre at terrestrial and orbital heights, no iteration).
- Ellipsoid-generic. Everything takes an
Ellipsoid; WGS84 is the default and has shorthand free functions. - Optional
serdefeature derivesSerialize/Deserializeon the coordinate types.
Out of scope, by design: planar 2D geometry (use the geo
ecosystem), map projections (proj, geodesy), geodesics to millimetre accuracy over long lines
(geographiclib), terrain and meshes (parry3d), and precession/nutation/polar motion (the eci
module is the plain GMST rotation).
Precision
Every function states its accuracy class, and the tests hold it to that:
| class | functions | how it is held |
|---|---|---|
| exact closed form | geodetic → ECEF, ENU/NED ↔ ECEF, look angles, ray–ellipsoid, volumes, CPA, Helmert apply/apply_inverse |
round trips to 1e-11° / 1e-6 m over a global grid; cross-validated against map_3d (dev-only dependency) to 1e-6 m |
| iterated to machine precision | ECEF → geodetic (Bowring, to 1e-15 rad) | same grid, from −11 km to geostationary height |
| geodesic, ~0.5 mm | geodesic_inverse / geodesic_direct (Vincenty) |
pinned to GeographicLib (Karney) reference values: Flinders Peak–Buninyong on two ellipsoids and four direct problems from 1 m to 9 000 km; exact quarter-equator and quarter-meridian lengths; worldwide direct↔inverse round trips to 1e-6 m. Nearly antipodal pairs return None rather than a wrong answer. |
| spherical approximation (mean radius) | great_circle_*, horizon_distance_m |
named as such; within 0.5 % of the geodesic |
| first-order / linearised | Helmert7::inverse (use apply_inverse for exact), interpolate (ECEF chord), Track (constant velocity), eci (GMST only) |
documented on each item |
Develop
&& &&
Versioning
Plain semver, tags vX.Y.Z at the published commit. See CHANGELOG.md.