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
//! Every ellipsoid in the EPSG registry, by its code: [`Ellipsoid::from_epsg`].
use crate::ellipsoid::*;
/// EPSG's older units, in metres (EPSG units 9005, 9080, 9031 and 9002).
const CLARKE_FOOT: f64 = 0.304_797_265_4;
const INDIAN_FOOT: f64 = 12.0 / 39.370_142;
const GERMAN_LEGAL_METRE: f64 = 1.000_013_596_5;
const FOOT: f64 = 0.304_8;
impl Ellipsoid {
/// The ellipsoid with this code in the [EPSG registry](https://epsg.org), the standard
/// catalogue of datums and coordinate systems; `None` for any other code. All 50 are here,
/// each with the numbers EPSG defines it by (as of October 2026), converted to metres where
/// EPSG gives feet.
///
/// ```
/// use geo3d::{Ellipsoid, KRASSOVSKY_1940};
/// assert_eq!(Ellipsoid::from_epsg(7024), Some(KRASSOVSKY_1940));
/// let everest = Ellipsoid::from_epsg(7044).unwrap(); // Everest 1830 (1962 Definition), Pakistan
/// assert_eq!(everest.a(), 6_377_301.243);
/// assert_eq!(Ellipsoid::from_epsg(4326), None); // that's a coordinate system, not an ellipsoid
/// ```
///
/// | Code | Ellipsoid | Defined by | Named |
/// |---|---|---|---|
/// | 1024 | CGCS2000 | `a`, `1/f` | [`CGCS2000`](crate::CGCS2000) |
/// | 1025 | GSK-2011 | `a`, `1/f` | [`GSK_2011`](crate::GSK_2011) |
/// | 1026 | Zach 1812 | `a`, `1/f` | |
/// | 7001 | Airy 1830 | `a`, `1/f` | [`AIRY_1830`](crate::AIRY_1830) |
/// | 7002 | Airy Modified 1849 | `a`, `1/f` | |
/// | 7003 | Australian National Spheroid | `a`, `1/f` | [`AUSTRALIAN_NATIONAL`](crate::AUSTRALIAN_NATIONAL) |
/// | 7004 | Bessel 1841 | `a`, `1/f` | [`BESSEL_1841`](crate::BESSEL_1841) |
/// | 7005 | Bessel Modified | `a`, `1/f` | |
/// | 7007 | Clarke 1858 | axes, in Clarke's feet | |
/// | 7008 | Clarke 1866 | axes | [`CLARKE_1866`](crate::CLARKE_1866) |
/// | 7010 | Clarke 1880 (Benoit) | axes | |
/// | 7011 | Clarke 1880 (IGN) | axes | |
/// | 7012 | Clarke 1880 (RGS) | `a`, `1/f` | |
/// | 7013 | Clarke 1880 (Arc) | `a`, `1/f` | |
/// | 7014 | Clarke 1880 (SGA 1922) | `a`, `1/f` | |
/// | 7015 | Everest 1830 (1937 Adjustment) | `a`, `1/f` | [`EVEREST_1830`](crate::EVEREST_1830) |
/// | 7016 | Everest 1830 (1967 Definition) | `a`, `1/f` | |
/// | 7018 | Everest 1830 Modified | `a`, `1/f` | [`EVEREST_1830_MODIFIED`](crate::EVEREST_1830_MODIFIED) |
/// | 7019 | GRS 1980 | `a`, `1/f` | [`GRS80`](crate::GRS80) |
/// | 7020 | Helmert 1906 | `a`, `1/f` | |
/// | 7021 | Indonesian National Spheroid | `a`, `1/f` | |
/// | 7022 | International 1924 | `a`, `1/f` | [`INTERNATIONAL_1924`](crate::INTERNATIONAL_1924) |
/// | 7024 | Krassowsky 1940 | `a`, `1/f` | [`KRASSOVSKY_1940`](crate::KRASSOVSKY_1940) |
/// | 7025 | NWL 9D | `a`, `1/f` | [`WGS66`](crate::WGS66) |
/// | 7027 | Plessis 1817 | `a`, `1/f` | |
/// | 7028 | Struve 1860 | `a`, `1/f` | |
/// | 7029 | War Office | `a`, `1/f` | |
/// | 7030 | WGS 84 | `a`, `1/f` | [`WGS84`](crate::WGS84) |
/// | 7031 | GEM 10C | `a`, `1/f` | |
/// | 7032 | OSU86F | `a`, `1/f` | |
/// | 7033 | OSU91A | `a`, `1/f` | |
/// | 7034 | Clarke 1880 | axes, in Clarke's feet | |
/// | 7036 | GRS 1967 | `a`, `1/f` | [`GRS67`](crate::GRS67) |
/// | 7041 | Average Terrestrial System 1977 | `a`, `1/f` | |
/// | 7042 | Everest (1830 Definition) | axes, in Indian feet | |
/// | 7043 | WGS 72 | `a`, `1/f` | [`WGS72`](crate::WGS72) |
/// | 7044 | Everest 1830 (1962 Definition) | `a`, `1/f` | |
/// | 7045 | Everest 1830 (1975 Definition) | `a`, `1/f` | |
/// | 7046 | Bessel Namibia (GLM) | `a`, `1/f`, in German legal metres | |
/// | 7048 | GRS 1980 Authalic Sphere | axes | |
/// | 7049 | IAG 1975 | `a`, `1/f` | [`IAG_1975`](crate::IAG_1975) |
/// | 7050 | GRS 1967 Modified | `a`, `1/f` | |
/// | 7051 | Danish 1876 | `a`, `1/f` | |
/// | 7052 | Clarke 1866 Authalic Sphere | axes | |
/// | 7053 | Hough 1960 | `a`, `1/f` | |
/// | 7054 | PZ-90 | `a`, `1/f` | [`PZ90`](crate::PZ90) |
/// | 7055 | Clarke 1880 (international foot) | axes, in international feet | |
/// | 7056 | Everest 1830 (RSO 1969) | `a`, `1/f` | |
/// | 7057 | International 1924 Authalic Sphere | axes | |
/// | 7058 | Hughes 1980 | axes | |
pub const fn from_epsg(code: u32) -> Option<Ellipsoid> {
Some(match code {
1024 => CGCS2000, // CGCS2000
1025 => GSK_2011, // GSK-2011
1026 => Ellipsoid::new(6_376_045.0, 1.0 / 310.0), // Zach 1812
7001 => AIRY_1830, // Airy 1830
7002 => Ellipsoid::new(6_377_340.189, 1.0 / 299.324_964_6), // Airy Modified 1849
7003 => AUSTRALIAN_NATIONAL, // Australian National Spheroid
7004 => BESSEL_1841, // Bessel 1841
7005 => Ellipsoid::new(6_377_492.018, 1.0 / 299.152_812_8), // Bessel Modified
7007 => Ellipsoid::from_axes(20_926_348.0 * CLARKE_FOOT, 20_855_233.0 * CLARKE_FOOT), // Clarke 1858
7008 => CLARKE_1866, // Clarke 1866
7010 => Ellipsoid::from_axes(6_378_300.789, 6_356_566.435), // Clarke 1880 (Benoit)
7011 => Ellipsoid::from_axes(6_378_249.2, 6_356_515.0), // Clarke 1880 (IGN)
7012 => Ellipsoid::new(6_378_249.145, 1.0 / 293.465), // Clarke 1880 (RGS)
7013 => Ellipsoid::new(6_378_249.145, 1.0 / 293.466_307_7), // Clarke 1880 (Arc)
7014 => Ellipsoid::new(6_378_249.2, 1.0 / 293.465_98), // Clarke 1880 (SGA 1922)
7015 => EVEREST_1830, // Everest 1830 (1937 Adjustment)
7016 => Ellipsoid::new(6_377_298.556, 1.0 / 300.801_7), // Everest 1830 (1967 Definition)
7018 => EVEREST_1830_MODIFIED, // Everest 1830 Modified
7019 => GRS80, // GRS 1980
7020 => Ellipsoid::new(6_378_200.0, 1.0 / 298.3), // Helmert 1906
7021 => Ellipsoid::new(6_378_160.0, 1.0 / 298.247), // Indonesian National Spheroid
7022 => INTERNATIONAL_1924, // International 1924
7024 => KRASSOVSKY_1940, // Krassowsky 1940
7025 => WGS66, // NWL 9D
7027 => Ellipsoid::new(6_376_523.0, 1.0 / 308.64), // Plessis 1817
7028 => Ellipsoid::new(6_378_298.3, 1.0 / 294.73), // Struve 1860
7029 => Ellipsoid::new(6_378_300.0, 1.0 / 296.0), // War Office
7030 => WGS84, // WGS 84
7031 => Ellipsoid::new(6_378_137.0, 1.0 / 298.257_223_563), // GEM 10C
7032 => Ellipsoid::new(6_378_136.2, 1.0 / 298.257_223_563), // OSU86F
7033 => Ellipsoid::new(6_378_136.3, 1.0 / 298.257_223_563), // OSU91A
7034 => Ellipsoid::from_axes(20_926_202.0 * CLARKE_FOOT, 20_854_895.0 * CLARKE_FOOT), // Clarke 1880
7036 => GRS67, // GRS 1967
7041 => Ellipsoid::new(6_378_135.0, 1.0 / 298.257), // Average Terrestrial System 1977
7042 => Ellipsoid::from_axes(20_922_931.8 * INDIAN_FOOT, 20_853_374.58 * INDIAN_FOOT), // Everest (1830 Definition)
7043 => WGS72, // WGS 72
7044 => Ellipsoid::new(6_377_301.243, 1.0 / 300.801_725_5), // Everest 1830 (1962 Definition)
7045 => Ellipsoid::new(6_377_299.151, 1.0 / 300.801_725_5), // Everest 1830 (1975 Definition)
7046 => Ellipsoid::new(6_377_397.155 * GERMAN_LEGAL_METRE, 1.0 / 299.152_812_8), // Bessel Namibia (GLM)
7048 => Ellipsoid::from_axes(6_371_007.0, 6_371_007.0), // GRS 1980 Authalic Sphere
7049 => IAG_1975, // IAG 1975
7050 => Ellipsoid::new(6_378_160.0, 1.0 / 298.25), // GRS 1967 Modified
7051 => Ellipsoid::new(6_377_019.27, 1.0 / 300.0), // Danish 1876
7052 => Ellipsoid::from_axes(6_370_997.0, 6_370_997.0), // Clarke 1866 Authalic Sphere
7053 => Ellipsoid::new(6_378_270.0, 1.0 / 297.0), // Hough 1960
7054 => PZ90, // PZ-90
7055 => Ellipsoid::from_axes(20_926_202.0 * FOOT, 20_854_895.0 * FOOT), // Clarke 1880 (international foot)
7056 => Ellipsoid::new(6_377_295.664, 1.0 / 300.801_7), // Everest 1830 (RSO 1969)
7057 => Ellipsoid::from_axes(6_371_228.0, 6_371_228.0), // International 1924 Authalic Sphere
7058 => Ellipsoid::from_axes(6_378_273.0, 6_356_889.449), // Hughes 1980
_ => return None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
/// The EPSG registry's own numbers (apps.epsg.org, October 2026), straight from its API:
/// code, semi-major axis, semi-minor axis or NaN, inverse flattening or NaN, and the unit in
/// metres.
#[rustfmt::skip]
#[allow(clippy::excessive_precision)]
const REGISTRY: [(u32, f64, f64, f64, f64); 50] = [
(1024, 6378137.0, f64::NAN, 298.257222101, 1.0),
(1025, 6378136.5, f64::NAN, 298.2564151, 1.0),
(1026, 6376045.0, f64::NAN, 310.0, 1.0),
(7001, 6377563.396, f64::NAN, 299.3249646, 1.0),
(7002, 6377340.189, f64::NAN, 299.3249646, 1.0),
(7003, 6378160.0, f64::NAN, 298.25, 1.0),
(7004, 6377397.155, f64::NAN, 299.1528128, 1.0),
(7005, 6377492.018, f64::NAN, 299.1528128, 1.0),
(7007, 20926348.0, 20855233.0, f64::NAN, 0.304_797_265_4),
(7008, 6378206.4, 6356583.8, f64::NAN, 1.0),
(7010, 6378300.789, 6356566.435, f64::NAN, 1.0),
(7011, 6378249.2, 6356515.0, f64::NAN, 1.0),
(7012, 6378249.145, f64::NAN, 293.465, 1.0),
(7013, 6378249.145, f64::NAN, 293.4663077, 1.0),
(7014, 6378249.2, f64::NAN, 293.46598, 1.0),
(7015, 6377276.345, f64::NAN, 300.8017, 1.0),
(7016, 6377298.556, f64::NAN, 300.8017, 1.0),
(7018, 6377304.063, f64::NAN, 300.8017, 1.0),
(7019, 6378137.0, f64::NAN, 298.257222101, 1.0),
(7020, 6378200.0, f64::NAN, 298.3, 1.0),
(7021, 6378160.0, f64::NAN, 298.247, 1.0),
(7022, 6378388.0, f64::NAN, 297.0, 1.0),
(7024, 6378245.0, f64::NAN, 298.3, 1.0),
(7025, 6378145.0, f64::NAN, 298.25, 1.0),
(7027, 6376523.0, f64::NAN, 308.64, 1.0),
(7028, 6378298.3, f64::NAN, 294.73, 1.0),
(7029, 6378300.0, f64::NAN, 296.0, 1.0),
(7030, 6378137.0, f64::NAN, 298.257223563, 1.0),
(7031, 6378137.0, f64::NAN, 298.257223563, 1.0),
(7032, 6378136.2, f64::NAN, 298.257223563, 1.0),
(7033, 6378136.3, f64::NAN, 298.257223563, 1.0),
(7034, 20926202.0, 20854895.0, f64::NAN, 0.304_797_265_4),
(7036, 6378160.0, f64::NAN, 298.247167427, 1.0),
(7041, 6378135.0, f64::NAN, 298.257, 1.0),
(7042, 20922931.8, 20853374.58, f64::NAN, 12.0 / 39.370_142),
(7043, 6378135.0, f64::NAN, 298.26, 1.0),
(7044, 6377301.243, f64::NAN, 300.8017255, 1.0),
(7045, 6377299.151, f64::NAN, 300.8017255, 1.0),
(7046, 6377397.155, f64::NAN, 299.1528128, 1.000_013_596_5),
(7048, 6371007.0, 6371007.0, f64::NAN, 1.0),
(7049, 6378140.0, f64::NAN, 298.257, 1.0),
(7050, 6378160.0, f64::NAN, 298.25, 1.0),
(7051, 6377019.27, f64::NAN, 300.0, 1.0),
(7052, 6370997.0, 6370997.0, f64::NAN, 1.0),
(7053, 6378270.0, f64::NAN, 297.0, 1.0),
(7054, 6378136.0, f64::NAN, 298.257839303, 1.0),
(7055, 20926202.0, 20854895.0, f64::NAN, 0.304_8),
(7056, 6377295.664, f64::NAN, 300.8017, 1.0),
(7057, 6371228.0, 6371228.0, f64::NAN, 1.0),
(7058, 6378273.0, 6356889.449, f64::NAN, 1.0),
];
#[test]
fn every_registry_ellipsoid_matches_its_definition() {
for (code, a, b, rf, unit) in REGISTRY {
let e = Ellipsoid::from_epsg(code).unwrap_or_else(|| panic!("{code} missing"));
assert!((e.a() - a * unit).abs() <= 1e-15 * e.a(), "{code}: a {}", e.a());
if rf.is_nan() {
// Defined by its axes: the polar radius comes back to within a nanometre.
assert!((e.b() - b * unit).abs() < 1e-8, "{code}: b {} vs {}", e.b(), b * unit);
} else {
assert!((1.0 / e.f() - rf).abs() <= 1e-13 * rf, "{code}: 1/f {}", 1.0 / e.f());
}
}
}
#[test]
fn nothing_else_is_an_ellipsoid_code() {
let found = (0..100_000).filter(|&c| Ellipsoid::from_epsg(c).is_some()).count();
assert_eq!(found, REGISTRY.len());
}
#[test]
fn named_ones_and_derived_values() {
let named = [
(7030, WGS84),
(7019, GRS80),
(1024, CGCS2000),
(7054, PZ90),
(1025, GSK_2011),
(7024, KRASSOVSKY_1940),
(7049, IAG_1975),
(7043, WGS72),
(7025, WGS66),
(7036, GRS67),
(7003, AUSTRALIAN_NATIONAL),
(7050, AUSTRALIAN_NATIONAL),
(7022, INTERNATIONAL_1924),
(7001, AIRY_1830),
(7004, BESSEL_1841),
(7008, CLARKE_1866),
(7015, EVEREST_1830),
(7018, EVEREST_1830_MODIFIED),
];
for (code, e) in named {
assert_eq!(Ellipsoid::from_epsg(code), Some(e), "{code}");
}
// Values EPSG and PROJ quote for the ones it defines in other units.
let everest = Ellipsoid::from_epsg(7042).unwrap(); // Indian feet
assert!((everest.a() - 6_377_299.365_595_4).abs() < 1e-6, "{}", everest.a());
assert!(
(1.0 / everest.f() - 300.801_725_5).abs() < 1e-6,
"{}",
1.0 / everest.f()
);
let namibia = Ellipsoid::from_epsg(7046).unwrap(); // German legal metres
assert!((namibia.a() - 6_377_483.865).abs() < 1e-3, "{}", namibia.a());
let clarke_1880 = Ellipsoid::from_epsg(7034).unwrap(); // Clarke's feet
assert!((1.0 / clarke_1880.f() - 293.466_307_656).abs() < 1e-6);
// Spheres have no flattening.
assert_eq!(Ellipsoid::from_epsg(7048).unwrap().f(), 0.0);
// WGS 60 isn't in EPSG; its figure is the US Department of Defense's.
assert_eq!((WGS60.a(), 1.0 / WGS60.f()), (6_378_165.0, 298.3));
}
}