use crate::bounds::GeoBounds;
use crate::coord::{GeoCoord, WorldCoord};
use crate::ellipsoid::Ellipsoid;
use crate::projection::Projection;
pub struct Globe;
impl Globe {
#[inline]
pub fn project(geo: &GeoCoord) -> WorldCoord {
<Self as Projection>::project(&Self, geo)
}
#[inline]
pub fn unproject(world: &WorldCoord) -> GeoCoord {
<Self as Projection>::unproject(&Self, world)
}
#[inline]
pub fn radius() -> f64 {
Ellipsoid::WGS84.a
}
}
impl Projection for Globe {
fn project(&self, geo: &GeoCoord) -> WorldCoord {
let ellipsoid = Ellipsoid::WGS84;
let lat = geo.lat.to_radians();
let lon = geo.lon.to_radians();
let (sin_lat, cos_lat) = lat.sin_cos();
let (sin_lon, cos_lon) = lon.sin_cos();
let a = ellipsoid.a;
let e2 = ellipsoid.e2;
let n = a / (1.0_f64 - e2 * sin_lat * sin_lat).sqrt();
let alt = geo.alt;
let x = (n + alt) * cos_lat * cos_lon;
let y = (n + alt) * cos_lat * sin_lon;
let z = (n * (1.0 - e2) + alt) * sin_lat;
WorldCoord::new(x, y, z)
}
fn unproject(&self, world: &WorldCoord) -> GeoCoord {
let ellipsoid = Ellipsoid::WGS84;
let a = ellipsoid.a;
let b = ellipsoid.b;
let e2 = ellipsoid.e2;
let ep2 = (a * a - b * b) / (b * b);
let x = world.position.x;
let y = world.position.y;
let z = world.position.z;
let lon = y.atan2(x);
let p = (x * x + y * y).sqrt();
if p <= 1e-12 {
let lat = if z >= 0.0 {
std::f64::consts::FRAC_PI_2
} else {
-std::f64::consts::FRAC_PI_2
};
let alt = z.abs() - b;
return GeoCoord::new(lat.to_degrees(), 0.0, alt);
}
let theta = (z * a).atan2(p * b);
let (sin_theta, cos_theta) = theta.sin_cos();
let lat = (z + ep2 * b * sin_theta.powi(3)).atan2(p - e2 * a * cos_theta.powi(3));
let sin_lat = lat.sin();
let n = a / (1.0_f64 - e2 * sin_lat * sin_lat).sqrt();
let alt = p / lat.cos() - n;
GeoCoord::new(lat.to_degrees(), lon.to_degrees(), alt)
}
fn scale_factor(&self, _geo: &GeoCoord) -> f64 {
1.0
}
fn projection_bounds(&self) -> GeoBounds {
GeoBounds::new(
GeoCoord::from_lat_lon(-90.0, -180.0),
GeoCoord::from_lat_lon(90.0, 180.0),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_origin() {
let geo = GeoCoord::from_lat_lon(0.0, 0.0);
let world = Globe::project(&geo);
let back = Globe::unproject(&world);
assert!((back.lat - geo.lat).abs() < 1e-8);
assert!((back.lon - geo.lon).abs() < 1e-8);
}
#[test]
fn roundtrip_nonzero() {
let geo = GeoCoord::new(51.1, 17.0, 1200.0);
let world = Globe::project(&geo);
let back = Globe::unproject(&world);
assert!((back.lat - geo.lat).abs() < 1e-6);
assert!((back.lon - geo.lon).abs() < 1e-6);
assert!((back.alt - geo.alt).abs() < 1e-3);
}
#[test]
fn equator_axes_match_expected_orientation() {
let zero = Globe::project(&GeoCoord::from_lat_lon(0.0, 0.0));
let east = Globe::project(&GeoCoord::from_lat_lon(0.0, 90.0));
let north = Globe::project(&GeoCoord::from_lat_lon(90.0, 0.0));
assert!(zero.position.x > 6_000_000.0);
assert!(east.position.y > 6_000_000.0);
assert!(north.position.z > 6_000_000.0);
}
}