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
//! Convert WGS84 latitude-longitude to and from UTM, UPS, and MGRS.
//!
//! The whole globe is covered: every valid latitude and longitude has a UTM
//! (latitudes -80 to 84) or UPS (the polar caps) representation and an MGRS
//! reference at every precision from 0 to 11 digit pairs, and every valid MGRS
//! reference names a cell with a well defined center. Forward and reverse both
//! ship. The projection core is the Krueger series to order n^6 from Karney,
//! "Transverse Mercator with an accuracy of a few nanometers" (2011). The MGRS
//! grid follows NGA TM 8358.1, checked cell by cell against GeographicLib's
//! `GeoConvert`.
//!
//! This crate is `std` (the projection core needs the `f64` math functions) and
//! has zero runtime dependencies.
//!
//! ```
//! use mgrs_convert::{LatLon, Mgrs, Precision};
//!
//! let position = LatLon::new(40.748333, -73.985278)?;
//! let cell = position.to_mgrs(Precision::ONE_METER);
//! assert_eq!(cell.to_string(), "18TWL8566411315");
//!
//! let parsed: Mgrs = "18TWL8566411315".parse()?;
//! assert_eq!(parsed, cell);
//! let center = parsed.to_latlon();
//! assert!((center.lat() - 40.748333).abs() < 1e-5);
//! assert!((center.lon() + 73.985278).abs() < 1e-5);
//! # Ok::<(), mgrs_convert::Error>(())
//! ```
//!
//! # Types
//!
//! Four types, one error enum, nothing else:
//!
//! - [`LatLon`], a validated geodetic coordinate. Its [`LatLon::to_mgrs`] is
//! infallible because every valid input has an MGRS reference.
//! - [`UtmUps`], a grid coordinate, where zone 0 is UPS. Its [`UtmUps::to_mgrs`] is
//! fallible by design: the accepted transfer range extends 100 km past the
//! MGRS grid on every side, and the ring in between returns
//! [`Error::OutsideMgrsExtent`] instead of panicking.
//! - [`Mgrs`], a grid cell. Only constructible through validated paths, so its
//! `Display` is total. Reverse conversion returns the cell center.
//! - [`Precision`], the validated digit-pair count, 0 through 11.
//!
//! # Boundary behavior
//!
//! - Latitude 0 and `-0.0` are both northern, band N, on every path.
//! - The UTM latitude interval is [-80, 84), matching GeoConvert: -80 exactly
//! is UTM band C, 84 exactly is UPS north, and the last UTM latitude is one
//! ulp below 84 (band X).
//! - A coordinate exactly on an excluded upper grid edge is nudged inside by
//! 2^-28 m before truncation, so it formats as the last cell of its own
//! square.
//! - Grid-zone-only references such as `"18T"` are rejected with
//! [`ParseErrorKind::GridZoneOnly`], because a cell six degrees wide has no useful
//! center.
//!
//! # Migrating from `geoconvert`
//!
//! | `geoconvert` 1.0 call | replacement |
//! | --- | --- |
//! | `LatLon::create(lat, lon)` | [`LatLon::new`] |
//! | `latlon.to_mgrs(6)` | `latlon.to_mgrs(Precision::new(6)?)` |
//! | `Mgrs::parse_str(s)` | `s.parse::<Mgrs>()` |
//! | `mgrs.to_string()` | unchanged |
//! | `mgrs.to_latlon()` | unchanged |
//! | `UtmUps::create(z, north, e, n)` | [`UtmUps::utm`] or [`UtmUps::ups`] |
//! | `utmups.to_mgrs(p)` | `utmups.to_mgrs(Precision::new(p)?)?` |
//! | `Mgrs::create(...)` | removed, construct via `to_mgrs` or `parse` |
//!
//! The two new `?`s are deliberate: precision validation and the MGRS-extent
//! check return errors a caller can print instead of panicking.
//!
//! ```
//! use mgrs_convert::{Error, Precision, UtmUps};
//! // Precision is validated at the edge.
//! assert!(matches!(Precision::new(12), Err(Error::PrecisionOutOfRange { .. })));
//! // The transfer ring converts to lat/lon but has no MGRS reference.
//! let ring = UtmUps::utm(18, true, 50_000.0, 4_511_315.0)?;
//! assert!(matches!(ring.to_mgrs(Precision::ONE_METER), Err(Error::OutsideMgrsExtent { .. })));
//! # Ok::<(), mgrs_convert::Error>(())
//! ```
//!
//! # Performance
//!
//! Linear per conversion, no allocation outside `Display`, no lookup table
//! computed at runtime. The doctest below is the quadratic-regression tripwire:
//! one hundred thousand round trips inside a generous fixed budget.
//!
//! ```
//! use mgrs_convert::{LatLon, Precision};
//! let start = std::time::Instant::now();
//! let mut checksum = 0.0_f64;
//! for i in 0..100_000_u32 {
//! let lat = -80.0 + (f64::from(i) * 1.6377) % 160.0;
//! let lon = -180.0 + (f64::from(i) * 3.9119) % 360.0;
//! let p = LatLon::new(lat, lon)?;
//! let back = p.to_utmups().to_latlon();
//! checksum += back.lat();
//! }
//! assert!(checksum.is_finite());
//! assert!(start.elapsed().as_secs() < 60, "quadratic regression tripwire");
//! # Ok::<(), mgrs_convert::Error>(())
//! ```
pub use ;
pub use LatLon;
pub use Mgrs;
pub use Precision;
pub use UtmUps;