1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//! # 🌍 geo3d
//!
//! **3D positions on Earth, made simple.**
//!
//! Give geo3d a GPS position, then ask everyday questions:
//!
//! - 📍 Where is that plane, seen from here?
//! - 📡 Which way should the antenna point?
//! - 📏 How far apart are they?
//! - 🗺️ How big is that field?
//! - 👀 Can they see each other?
//! - 💥 Are they going to collide?
//! - 🎯 How fast is it closing, and how soon can we reach it?
//! - 🌊 How high above sea level is it?
//!
//! It's pure Rust, with no dependencies.
//!
//! ```
//! use geo3d::{Geodetic, LocalFrame};
//!
//! let me = Geodetic::new(51.5, -0.12, 30.0); // latitude, longitude, height in metres
//! let plane = Geodetic::new(51.6, -0.12, 3_000.0);
//!
//! let look = LocalFrame::new(me).look_angles(plane); // which way to point
//! // due north, 14.9° up, 11.5 km away
//! assert!(look.elevation_deg > 14.0 && look.range_m > 11_000.0);
//! ```
//!
//! # ✨ Why geo3d?
//!
//! - 🎯 **Accurate.** Conversions are exact, and distances along the ground are good to 15
//! nanometres. Everything is tested against GeographicLib, the reference library.
//! - 🛡️ **Reliable.** It handles the tricky places, like the poles and opposite sides of the Earth,
//! without NaN or panics.
//! - 🧩 **Easy to use.** Degrees and metres everywhere, with named fields. Latitude always comes first.
//! - 🪶 **Light and fast.** No dependencies, no `unsafe` code, and nothing allocates. Works on Rust
//! 1.85 and newer, on microcontrollers without the standard library, and in the browser
//! (WebAssembly).
//!
//! # 🗺️ The basics
//!
//! Latitude and longitude are angles, so you can't subtract two of them to get a distance. geo3d
//! turns positions into frames where normal 3D maths works.
//!
//! | Type | What it holds | Units |
//! |---|---|---|
//! | [`Geodetic`] | latitude, longitude and height, like GPS gives you | degrees, metres |
//! | [`Ecef`] | x, y, z from the centre of the Earth | metres |
//! | [`Enu`] / [`Ned`] | how far east, north and up (or north, east and down) from you | metres |
//! | [`Aer`] | azimuth, elevation and range: which way to point | degrees, metres |
//! | [`Eci`] | x, y, z that don't spin with the Earth, for satellites | metres |
//! | [`Vec3`] | a plain 3D vector, like a velocity | metres, m/s |
//! | [`Cov3`] | how uncertain a position is (a covariance) | metres², degrees² |
//! | [`Attitude`] | which way a vehicle faces: yaw, pitch and roll | degrees |
//! | [`Frd`] | an offset in a vehicle's own frame: forward, right, down | metres |
//! | [`Quaternion`] | the same turn as yaw, pitch and roll, as four numbers | none |
//! | [`UtmUps`] | a UTM or UPS grid position: zone, easting and northing | metres |
//!
//! A few rules hold everywhere:
//!
//! - Latitude comes first: `Geodetic::new(lat, lon, height)`.
//! - Bearings go clockwise from north, from 0° to 360°.
//! - Heights are above the WGS84 ellipsoid, like GPS, not above sea level. A [`Geoid`] converts
//! between the two.
//! - [`WGS84`] is the default, but any [`Ellipsoid`] works: `GRS80.to_ecef(p)`. All 50 in the EPSG
//! registry are built in: [`Ellipsoid::from_epsg`].
//! - A few low-level helpers use radians, and their names say so (`lat_rad`, `gmst_rad`).
//! [`Vec3::angle_to`] also returns radians.
//!
//! # 🖨️ Printing
//!
//! Every coordinate type implements `Display`. Plain `{}` keeps every digit (it reads back as
//! exactly the same number), `{:.N}` gives N decimals, and `{:#}` writes hemispheres as letters:
//!
//! ```
//! use geo3d::Geodetic;
//! let sydney = Geodetic::new(-33.87, 151.21, 20.0);
//! assert_eq!(format!("{sydney}"), "-33.87°, 151.21°, 20 m");
//! assert_eq!(format!("{sydney:.5}"), "-33.87000°, 151.21000°, 20.0 m");
//! assert_eq!(format!("{sydney:#}"), "33.87° S, 151.21° E, 20 m");
//! ```
//!
//! Where a type mixes degrees and metres, the metres get four fewer decimals: 0.0001° is about
//! 10 m on the ground.
//!
//! # 🛡️ Tricky inputs
//!
//! - The poles, the antimeridian, the Earth's centre and points far out in space all give finite,
//! correct answers.
//! - Azimuths and bearings are always in `[0, 360)`, and longitudes come out between −180° and 180°.
//! - NaN or infinite input gives NaN out, or `None` where a function returns an `Option`.
//! - Nothing panics.
//!
//! `tests/edge_cases.rs` checks all of this for every public conversion.
//!
//! # 🧭 Find what you need
//!
//! | I want to… | Use |
//! |---|---|
//! | convert between latitude/longitude and x, y, z | [`geodetic_to_ecef`], [`ecef_to_geodetic`] |
//! | turn a GPS height into a height above sea level, and back | [`Geoid`], [`GeoidGrid`] |
//! | use UTM/UPS grid coordinates, or another transverse Mercator grid | [`Ellipsoid::to_utm_ups`], [`Ellipsoid::from_utm_ups`], [`TransverseMercator`], [`PolarStereographic`] |
//! | find where something is, relative to me | [`LocalFrame::enu_of`], [`LocalFrame::ned_of`] |
//! | work out which way to point, and back | [`LocalFrame::look_angles`], [`LocalFrame::destination`] |
//! | find where a line of sight hits the ground | [`ground_strike`], [`Ellipsoid::ray_intersect`] |
//! | measure a straight line through space | [`distance3d_m`] |
//! | measure distance along the ground | [`Ellipsoid::geodesic_inverse`] (exact), [`Ellipsoid::great_circle_distance_m`] (quick) |
//! | travel a distance on a bearing | [`Ellipsoid::geodesic_direct`], [`Ellipsoid::great_circle_destination`] |
//! | measure the area of a field or region | [`Ellipsoid::polygon_area`] |
//! | follow a route: how far off it, how far along, where routes cross | [`Ellipsoid::cross_track_distance_m`], [`Ellipsoid::along_track_distance_m`], [`Ellipsoid::great_circle_intersection`] |
//! | find the centre of a group, or a point part-way along a route | [`Ellipsoid::mean_position`], [`Ellipsoid::great_circle_interpolate`] |
//! | check if something is inside a zone | [`Sphere`], [`Cone`], [`Cylinder`], [`AltitudeBand`] (all are [`Volume`]s) |
//! | check if two objects will collide | [`Track::conflict`], [`closest_approach`] |
//! | measure how fast something is closing, for radar Doppler | [`Track::range_rate_mps`], [`LocalFrame::range_rate_mps`] |
//! | plan an intercept: when, where, and the slowest speed that works | [`Track::intercept_from`], [`Track::slowest_intercept_from`] |
//! | turn a velocity into heading, speed and climb | [`Course`], [`LocalFrame::course_of_velocity`] |
//! | work with a vehicle's yaw, pitch and roll | [`Attitude`], [`LocalFrame::geodetic_of_body`], [`LocalFrame::body_to_ecef_rotation`], [`Quaternion`] |
//! | carry measurement uncertainty between frames | [`Cov3`], [`Aer::cov_to_enu`], [`LocalFrame::enu_cov_to_ecef`] |
//! | check line of sight, or how far I can see | [`Ellipsoid::line_of_sight`], [`Ellipsoid::horizon_distance_m`], and their `_with_refraction` versions for radio |
//! | track satellites, down to their Doppler shift | the [`eci`] module, [`Track::from_eci`], and `examples/satellite.rs` with the `sgp4` crate |
//! | use another country's ellipsoid | [`CGCS2000`], [`KRASSOVSKY_1940`], [`EVEREST_1830`], …, or any EPSG code with [`Ellipsoid::from_epsg`] |
//! | shift between datums | [`Helmert7`] |
//!
//! The [`navlab`] page solves ten classic navigation problems step by step. The
//! [`examples`](https://github.com/FusionVault/geo3d/tree/main/examples) folder has eight complete
//! programs for real jobs, from an airport radar to a search and rescue. More worked examples, a
//! comparison with other crates and an accuracy table are in the
//! [README](https://github.com/FusionVault/geo3d#readme).
//!
//! # 🧩 Optional features
//!
//! None of them are needed: a plain `cargo add geo3d` pulls in nothing else.
//!
//! | Feature | What it adds |
//! |---|---|
//! | `std` | on by default: the standard library's maths. Nothing to download; it comes with Rust |
//! | `libm` | runs without the standard library (`no_std`), for microcontrollers |
//! | `serde` | saving and loading every type |
//! | `mint` | conversions to `mint` types, and through them to `cgmath` and others |
//! | `glam` | conversions to `glam::DVec3`, [`Cov3`] to `glam::DMat3`, and [`Quaternion`] to `glam::DQuat` |
//! | `nalgebra` | conversions to `nalgebra` vectors and points, [`Cov3`] to `Matrix3`, and [`Quaternion`] |
//! | `geo-types` | [`Geodetic`] ↔ `geo_types::Point` (x is longitude, y is latitude), and with `footprint`, `Polygon` and `MultiPolygon` into a footprint |
//! | `footprint` | a prepared `Footprint` for fast, exact point-in-polygon tests, and the `Prism` volume. It needs an allocator, not the standard library |
//!
//! For a microcontroller, `cargo add geo3d --no-default-features --features libm`. With the
//! standard library, keep `std`: its maths rounds the last digit more carefully. Everything a
//! feature adds is marked with it on docs.rs.
// On docs.rs, mark what each optional feature adds ("Available on crate feature serde only").
// a prepared footprint allocates its edge index
extern crate alloc;
compile_error!;
// Without the standard library, the floating-point maths comes from `libm`. (Unit tests always
// have the standard library.)
/// Every Rust code block in the README is compiled and run as a doctest.
;
pub use ;
pub use Cov3;
pub use Helmert7;
pub use ;
pub use ;
pub use LocalFrame;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use Quaternion;
pub use Vec3;
pub use STANDARD_REFRACTION_K;
pub use Prism;
pub use ;
/// Latitude/longitude/height → x, y, z on [`WGS84`]. Shorthand for [`WGS84.to_ecef`](Ellipsoid::to_ecef).
/// x, y, z → latitude/longitude/height on [`WGS84`]. Shorthand for
/// [`WGS84.to_geodetic`](Ellipsoid::to_geodetic).
/// The straight-line distance between two positions through space, metres — the **slant range**
/// between things at height, not the distance along the surface. On [`WGS84`].
/// Where the line of sight from `observer` along `azimuth_deg` (0 = north, clockwise) and
/// `elevation_deg` (0 = horizon, negative = down) first meets the ground, on [`WGS84`]. `None` if it
/// points into space or the observer is below the surface. Shorthand for
/// [`LocalFrame::ground_strike`].