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}