geoconvert/lib.rs
1//! # geoconvert
2//!
3//! `geoconvert` is a lightweight library for converting between different
4//! geographic coordinate systems. Currently, there are three coordinate systems implemented:
5//!
6//! * [`LatLon`]
7//! * [`UtmUps`]
8//! * [`Mgrs`]
9//!
10//! The implementation of this library is a translation of a subset of
11//! [GeographicLib](https://geographiclib.sourceforge.io/C++/doc/index.html) from C++ to Rust. Specifically, `geoconvert`
12//! implements some of the functionality of the [GeoConvert](https://geographiclib.sourceforge.io/C++/doc/GeoConvert.1.html)
13//! command line tool.
14//!
15//! ## Usage
16//!
17//! You can create coordinates manually using a struct's `create()` function, then convert to other
18//! types using the `to_*`/`from_*` methods.
19//!
20//! ```rust
21//! use geoconvert::{LatLon, Mgrs, UtmUps};
22//!
23//! // This returns a result. When calling `create()`, the arguments are validated to ensure only a valid
24//! // coordinate gets created.
25//! let coord = LatLon::create(40.748333, -73.985278).unwrap();
26//!
27//! // Convert to a UTM/UPS coordinate
28//! let coord_utm = coord.to_utmups();
29//! let coord_utm = UtmUps::from_latlon(&coord);
30//!
31//! // Convert to an MGRS coordinate
32//! // Note that for MGRS you must specify the precision
33//! let coord_mgrs = coord.to_mgrs(6);
34//! let coord_mgrs = Mgrs::from_latlon(&coord, 6);
35//! ```
36//!
37//! ## Features
38//!
39//! If you want `serde` compatibility with `Serialize`/`Deserialize`, activate the `serde` feature.
40
41#![warn(clippy::pedantic)]
42#![allow(
43 // Don't require must_use
44 clippy::must_use_candidate,
45 clippy::return_self_not_must_use,
46 // Lots of conversion between integer/float types in this library,
47 // these suppress most of the unnecessary ones
48 clippy::cast_precision_loss,
49 clippy::cast_possible_wrap,
50 clippy::cast_possible_truncation
51)]
52
53use thiserror::Error;
54
55mod coords {
56 pub mod latlon;
57 pub mod mgrs;
58 pub mod utm;
59}
60
61pub use coords::*;
62
63pub(crate) mod utility;
64
65pub use latlon::LatLon;
66pub use mgrs::Mgrs;
67pub use utm::UtmUps;
68
69pub(crate) mod projections {
70 pub mod transverse_mercator;
71 pub mod polar_stereographic;
72}
73
74pub(crate) mod constants;
75
76#[derive(Debug, Error)]
77pub enum Error {
78 #[error("The provided precision is outside of range [1, 11]")]
79 InvalidPrecision(i32),
80 #[error("The provided zone is outside the valid range [0, 60]")]
81 InvalidZone(i32),
82 #[error("Coordinate parameters are not valid: {0}")]
83 InvalidCoord(String),
84 #[error("MGRS String is invalid: {0}")]
85 InvalidMgrs(String),
86 #[error("UTM coords are invalid: {0}")]
87 InvalidUtmCoords(String),
88 #[error("Coordinate type {coord_type} not valid for conversion to {dest_type}: {msg}")]
89 InvalidRange {
90 coord_type: String,
91 dest_type: String,
92 msg: String,
93 },
94}
95
96trait ThisOrThat {
97 fn ternary<T>(&self, r#true: T, r#false: T) -> T;
98 fn ternary_lazy<F, E, T>(&self, r#true: F, r#false: E) -> T
99 where
100 F: Fn() -> T,
101 E: Fn() -> T;
102}
103
104impl ThisOrThat for bool {
105 fn ternary<T>(&self, r#true: T, r#false: T) -> T {
106 if *self { r#true } else { r#false }
107 }
108
109 fn ternary_lazy<F, E, T>(&self, r#true: F, r#false: E) -> T
110 where
111 F: Fn() -> T,
112 E: Fn() -> T,
113 {
114 if *self { r#true() } else { r#false() }
115 }
116}