use crate::error::{ProjectionError, Result};
use crate::{to_degrees, to_radians};
use super::{ProjectionImpl, ProjectionParams};
pub(super) struct MercatorProj {
lon0: f64, a: f64, e: f64, k0: f64, fe: f64, fn_: f64, }
impl MercatorProj {
pub fn new(p: &ProjectionParams) -> Result<Self> {
Ok(MercatorProj {
lon0: to_radians(p.lon0),
a: p.ellipsoid.a,
e: p.ellipsoid.e,
k0: p.scale,
fe: p.false_easting,
fn_: p.false_northing,
})
}
}
impl ProjectionImpl for MercatorProj {
fn forward(&self, lon_deg: f64, lat_deg: f64) -> Result<(f64, f64)> {
let lat = to_radians(lat_deg);
let lon = to_radians(lon_deg);
if lat.abs() >= std::f64::consts::FRAC_PI_2 {
return Err(ProjectionError::out_of_bounds(
"latitude ±90° is a singularity for Mercator",
));
}
let x = self.a * self.k0 * (lon - self.lon0) + self.fe;
let y = if self.e < 1e-12 {
self.a * self.k0 * (std::f64::consts::FRAC_PI_4 + lat / 2.0).tan().ln() + self.fn_
} else {
let e = self.e;
let esin = e * lat.sin();
let psi = (std::f64::consts::FRAC_PI_4 + lat / 2.0).tan()
* ((1.0 - esin) / (1.0 + esin)).powf(e / 2.0);
self.a * self.k0 * psi.ln() + self.fn_
};
Ok((x, y))
}
fn inverse(&self, x: f64, y: f64) -> Result<(f64, f64)> {
let lon = to_degrees((x - self.fe) / (self.a * self.k0) + self.lon0);
let lat = if self.e < 1e-12 {
let t = (-(y - self.fn_) / (self.a * self.k0)).exp();
to_degrees(std::f64::consts::FRAC_PI_2 - 2.0 * t.atan())
} else {
let t = (-(y - self.fn_) / (self.a * self.k0)).exp();
let e = self.e;
let mut phi = std::f64::consts::FRAC_PI_2 - 2.0 * t.atan();
for _ in 0..15 {
let esin = e * phi.sin();
let phi_new = std::f64::consts::FRAC_PI_2
- 2.0 * (t * ((1.0 - esin) / (1.0 + esin)).powf(e / 2.0)).atan();
if (phi_new - phi).abs() < 1e-12 {
phi = phi_new;
break;
}
phi = phi_new;
}
to_degrees(phi)
};
Ok((lon, lat))
}
}
pub(super) struct WebMercatorProj {
a: f64,
}
impl WebMercatorProj {
pub fn new(_p: &ProjectionParams) -> Result<Self> {
Ok(WebMercatorProj { a: 6_378_137.0 })
}
}
impl ProjectionImpl for WebMercatorProj {
fn forward(&self, lon_deg: f64, lat_deg: f64) -> Result<(f64, f64)> {
if lat_deg.abs() > 85.051_129 {
return Err(ProjectionError::out_of_bounds(
"Web Mercator only valid between ±85.05° latitude",
));
}
let lat = to_radians(lat_deg);
let lon = to_radians(lon_deg);
let x = self.a * lon;
let y = self.a * (std::f64::consts::FRAC_PI_4 + lat / 2.0).tan().ln();
Ok((x, y))
}
fn inverse(&self, x: f64, y: f64) -> Result<(f64, f64)> {
let lon = to_degrees(x / self.a);
let lat = to_degrees(2.0 * (y / self.a).exp().atan() - std::f64::consts::FRAC_PI_2);
Ok((lon, lat))
}
}