use crate::Projection;
use crate::errors::ProjectionError;
use crate::errors::{ensure_finite, ensure_within_range, unpack_required_parameter};
#[cfg(feature = "tracing")]
use tracing::instrument;
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct ObliqueLonLat {
lambda_p: f64,
sin_phi_p: f64,
cos_phi_p: f64,
lon_0: f64,
}
impl ObliqueLonLat {
#[must_use]
pub fn builder() -> ObliqueLonLatBuilder {
ObliqueLonLatBuilder::default()
}
}
#[derive(Debug)]
pub struct ObliqueLonLatBuilder {
pole_lon: Option<f64>,
pole_lat: Option<f64>,
central_lon: f64,
}
impl Default for ObliqueLonLatBuilder {
fn default() -> Self {
Self {
pole_lon: None,
pole_lat: None,
central_lon: 0.0,
}
}
}
impl ObliqueLonLatBuilder {
pub const fn pole_lonlat(&mut self, lon: f64, lat: f64) -> &mut Self {
self.pole_lon = Some(lon);
self.pole_lat = Some(lat);
self
}
pub const fn central_lon(&mut self, lon: f64) -> &mut Self {
self.central_lon = lon;
self
}
pub fn initialize_projection(&self) -> Result<ObliqueLonLat, ProjectionError> {
let pole_lon = unpack_required_parameter!(self, pole_lon);
let pole_lat = unpack_required_parameter!(self, pole_lat);
let central_lon = self.central_lon;
ensure_finite!(pole_lon, pole_lat, central_lon);
ensure_within_range!(pole_lon, -180.0..180.0);
ensure_within_range!(pole_lat, -90.0..90.0);
ensure_within_range!(central_lon, -180.0..180.0);
let phi_p = pole_lat.to_radians();
Ok(ObliqueLonLat {
lambda_p: pole_lon.to_radians(),
sin_phi_p: phi_p.sin(),
cos_phi_p: phi_p.cos(),
lon_0: central_lon,
})
}
}
fn adjust_lon(lon: f64) -> f64 {
let pi_degrees = 180.0_f64;
if lon > pi_degrees {
2.0f64.mul_add(-pi_degrees, lon)
} else if lon < -pi_degrees {
2.0f64.mul_add(pi_degrees, lon)
} else {
lon
}
}
impl Projection for ObliqueLonLat {
#[inline]
#[cfg_attr(feature = "tracing", instrument(level = "trace"))]
fn project_unchecked(&self, lon: f64, lat: f64) -> (f64, f64) {
let lambda = (lon - self.lon_0).to_radians();
let phi = lat.to_radians();
let cos_lambda = lambda.cos();
let sin_lambda = lambda.sin();
let cos_phi = phi.cos();
let sin_phi = phi.sin();
let lambda_prime = (cos_phi * sin_lambda)
.atan2((self.sin_phi_p * cos_phi).mul_add(cos_lambda, self.cos_phi_p * sin_phi))
+ self.lambda_p;
let phi_prime = self.sin_phi_p.mul_add(sin_phi, -(self.cos_phi_p * cos_phi * cos_lambda)).asin();
let lon_prime = adjust_lon(lambda_prime.to_degrees());
let lat_prime = phi_prime.to_degrees();
(lon_prime, lat_prime)
}
#[inline]
#[cfg_attr(feature = "tracing", instrument(level = "trace"))]
fn inverse_project_unchecked(&self, x: f64, y: f64) -> (f64, f64) {
let lambda_prime = x.to_radians() - self.lambda_p;
let phi_prime = y.to_radians();
let cos_lambda_prime = lambda_prime.cos();
let sin_lambda_prime = lambda_prime.sin();
let cos_phi_prime = phi_prime.cos();
let sin_phi_prime = phi_prime.sin();
let lambda = (cos_phi_prime * sin_lambda_prime).atan2(
(self.sin_phi_p * cos_phi_prime).mul_add(cos_lambda_prime, -(self.cos_phi_p * sin_phi_prime)),
);
let phi = self.sin_phi_p.mul_add(sin_phi_prime, self.cos_phi_p * cos_phi_prime * cos_lambda_prime)
.asin();
let lon = adjust_lon(lambda.to_degrees() + self.lon_0);
let lat = phi.to_degrees();
(lon, lat)
}
}