use crate::error::Error;
use crate::latlon::LatLon;
use crate::mgrs::Mgrs;
use crate::precision::Precision;
use crate::{polar, tm};
pub(crate) const UTM_FALSE_EASTING: f64 = 500_000.0;
pub(crate) const UTM_FALSE_NORTHING: f64 = 10_000_000.0;
pub(crate) const UTM_EASTING_RANGE: (f64, f64) = (0.0, 1_000_000.0);
pub(crate) const UTM_NORTHING_ABS_RANGE: (f64, f64) = (-9_100_000.0, 9_600_000.0);
pub(crate) const UPS_NORTH_RANGE: (f64, f64) = (1_200_000.0, 2_800_000.0);
pub(crate) const UPS_SOUTH_RANGE: (f64, f64) = (700_000.0, 3_300_000.0);
pub(crate) fn central_meridian(zone: u8) -> f64 {
f64::from(zone) * 6.0 - 183.0
}
pub(crate) fn standard_zone(lat: f64, lon: f64) -> u8 {
if !(-80.0..84.0).contains(&lat) {
return 0;
}
if (56.0..64.0).contains(&lat) && (3.0..12.0).contains(&lon) {
return 32;
}
if (72.0..84.0).contains(&lat) {
if (0.0..9.0).contains(&lon) {
return 31;
}
if (9.0..21.0).contains(&lon) {
return 33;
}
if (21.0..33.0).contains(&lon) {
return 35;
}
if (33.0..42.0).contains(&lon) {
return 37;
}
}
let ilon = lon.floor() as i64; let zone = (ilon + 180) / 6 + 1; zone.clamp(1, 60) as u8
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UtmUps {
zone: u8,
north: bool,
easting: f64,
northing: f64,
}
impl UtmUps {
pub fn utm(zone: u8, north: bool, easting: f64, northing: f64) -> Result<UtmUps, Error> {
if !(1..=60).contains(&zone) {
return Err(Error::ZoneOutOfRange { found: zone });
}
let (emin, emax) = UTM_EASTING_RANGE;
if !easting.is_finite() || !(emin..=emax).contains(&easting) {
return Err(Error::EastingOutOfRange { found: easting });
}
let fold = if north { 0.0 } else { UTM_FALSE_NORTHING };
let (nmin, nmax) = UTM_NORTHING_ABS_RANGE;
if !northing.is_finite() || !(nmin + fold..=nmax + fold).contains(&northing) {
return Err(Error::NorthingOutOfRange { found: northing });
}
Ok(UtmUps {
zone,
north,
easting,
northing,
})
}
pub fn ups(north: bool, easting: f64, northing: f64) -> Result<UtmUps, Error> {
let (lo, hi) = if north {
UPS_NORTH_RANGE
} else {
UPS_SOUTH_RANGE
};
if !easting.is_finite() || !(lo..=hi).contains(&easting) {
return Err(Error::EastingOutOfRange { found: easting });
}
if !northing.is_finite() || !(lo..=hi).contains(&northing) {
return Err(Error::NorthingOutOfRange { found: northing });
}
Ok(UtmUps {
zone: 0,
north,
easting,
northing,
})
}
pub(crate) fn raw(zone: u8, north: bool, easting: f64, northing: f64) -> UtmUps {
UtmUps {
zone,
north,
easting,
northing,
}
}
pub fn zone(self) -> u8 {
self.zone
}
pub fn is_north(self) -> bool {
self.north
}
pub fn easting(self) -> f64 {
self.easting
}
pub fn northing(self) -> f64 {
self.northing
}
pub fn to_latlon(self) -> LatLon {
let (lat, lon) = self.latlon_parts();
LatLon::new_normalized(lat, lon)
}
pub(crate) fn latlon_parts(self) -> (f64, f64) {
if self.zone == 0 {
polar::reverse(self.north, self.easting, self.northing)
} else {
let x = self.easting - UTM_FALSE_EASTING;
let fold = if self.north { 0.0 } else { UTM_FALSE_NORTHING };
let y = self.northing - fold;
let (lat, dlon) = tm::reverse(x, y);
(lat, central_meridian(self.zone) + dlon)
}
}
pub fn to_mgrs(self, precision: Precision) -> Result<Mgrs, Error> {
crate::mgrs::from_utmups(self, precision)
}
}