const DEGREES_E7: i32 = 10_000_000;
const MAX_LATITUDE_E7: i32 = 90 * DEGREES_E7;
const MAX_LONGITUDE_E7: i32 = 180 * DEGREES_E7;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CoordinateOutOfRange {
Latitude(i32),
Longitude(i32),
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct LatitudeE7(i32);
impl LatitudeE7 {
pub const MIN: Self = Self(-MAX_LATITUDE_E7);
pub const MAX: Self = Self(MAX_LATITUDE_E7);
pub const fn new(value: i32) -> Result<Self, CoordinateOutOfRange> {
if value < Self::MIN.0 || value > Self::MAX.0 {
Err(CoordinateOutOfRange::Latitude(value))
} else {
Ok(Self(value))
}
}
#[must_use]
pub const fn get(self) -> i32 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct LongitudeE7(i32);
impl LongitudeE7 {
pub const MIN: Self = Self(-MAX_LONGITUDE_E7);
pub const MAX: Self = Self(MAX_LONGITUDE_E7);
pub const fn new(value: i32) -> Result<Self, CoordinateOutOfRange> {
if value < Self::MIN.0 || value > Self::MAX.0 {
Err(CoordinateOutOfRange::Longitude(value))
} else {
Ok(Self(value))
}
}
#[must_use]
pub const fn get(self) -> i32 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct AltitudeMillimeters(i32);
impl AltitudeMillimeters {
#[must_use]
pub const fn new(value: i32) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> i32 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GeographicPosition {
latitude: LatitudeE7,
longitude: LongitudeE7,
altitude: Option<AltitudeMillimeters>,
}
impl GeographicPosition {
#[must_use]
pub const fn new(
latitude: LatitudeE7,
longitude: LongitudeE7,
altitude: Option<AltitudeMillimeters>,
) -> Self {
Self {
latitude,
longitude,
altitude,
}
}
pub const fn try_from_e7(
latitude_e7: i32,
longitude_e7: i32,
altitude_mm: Option<i32>,
) -> Result<Self, CoordinateOutOfRange> {
let latitude = match LatitudeE7::new(latitude_e7) {
Ok(latitude) => latitude,
Err(error) => return Err(error),
};
let longitude = match LongitudeE7::new(longitude_e7) {
Ok(longitude) => longitude,
Err(error) => return Err(error),
};
Ok(Self::new(
latitude,
longitude,
match altitude_mm {
Some(altitude) => Some(AltitudeMillimeters::new(altitude)),
None => None,
},
))
}
#[must_use]
pub const fn latitude(self) -> LatitudeE7 {
self.latitude
}
#[must_use]
pub const fn longitude(self) -> LongitudeE7 {
self.longitude
}
#[must_use]
pub const fn altitude(self) -> Option<AltitudeMillimeters> {
self.altitude
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn geographic_position_rejects_only_out_of_range_axes() {
assert!(GeographicPosition::try_from_e7(-900_000_000, 1_800_000_000, None).is_ok());
assert_eq!(
GeographicPosition::try_from_e7(900_000_001, 0, None),
Err(CoordinateOutOfRange::Latitude(900_000_001))
);
assert_eq!(
GeographicPosition::try_from_e7(0, -1_800_000_001, None),
Err(CoordinateOutOfRange::Longitude(-1_800_000_001))
);
}
}