use crate::s1::{Angle, ChordAngle};
use crate::s2::{LatLng, Point};
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Meters(pub f64);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Kilometers(pub f64);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Steradians(pub f64);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct SquareMeters(pub f64);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct SquareKilometers(pub f64);
impl std::fmt::Display for Meters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} m", self.0)
}
}
impl std::fmt::Display for Kilometers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} km", self.0)
}
}
impl std::fmt::Display for Steradians {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} sr", self.0)
}
}
impl std::fmt::Display for SquareMeters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} m²", self.0)
}
}
impl std::fmt::Display for SquareKilometers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} km²", self.0)
}
}
impl From<f64> for Meters {
fn from(v: f64) -> Self {
Meters(v)
}
}
impl From<Meters> for f64 {
fn from(m: Meters) -> Self {
m.0
}
}
impl From<f64> for Kilometers {
fn from(v: f64) -> Self {
Kilometers(v)
}
}
impl From<Kilometers> for f64 {
fn from(km: Kilometers) -> Self {
km.0
}
}
impl From<f64> for Steradians {
fn from(v: f64) -> Self {
Steradians(v)
}
}
impl From<Steradians> for f64 {
fn from(sr: Steradians) -> Self {
sr.0
}
}
impl From<f64> for SquareMeters {
fn from(v: f64) -> Self {
SquareMeters(v)
}
}
impl From<SquareMeters> for f64 {
fn from(m2: SquareMeters) -> Self {
m2.0
}
}
impl From<f64> for SquareKilometers {
fn from(v: f64) -> Self {
SquareKilometers(v)
}
}
impl From<SquareKilometers> for f64 {
fn from(km2: SquareKilometers) -> Self {
km2.0
}
}
impl From<Meters> for Kilometers {
fn from(m: Meters) -> Kilometers {
Kilometers(m.0 * 0.001)
}
}
impl From<Kilometers> for Meters {
fn from(km: Kilometers) -> Meters {
Meters(km.0 * 1000.0)
}
}
impl From<SquareMeters> for SquareKilometers {
fn from(m2: SquareMeters) -> SquareKilometers {
SquareKilometers(m2.0 * 1e-6)
}
}
impl From<SquareKilometers> for SquareMeters {
fn from(km2: SquareKilometers) -> SquareMeters {
SquareMeters(km2.0 * 1e6)
}
}
impl From<Meters> for Angle {
fn from(m: Meters) -> Angle {
meters_to_angle(m.0)
}
}
impl From<Meters> for ChordAngle {
fn from(m: Meters) -> ChordAngle {
meters_to_chord_angle(m.0)
}
}
impl From<Angle> for Meters {
fn from(a: Angle) -> Meters {
Meters(to_meters(a))
}
}
impl From<ChordAngle> for Meters {
fn from(ca: ChordAngle) -> Meters {
Meters(chord_angle_to_meters(ca))
}
}
impl From<Kilometers> for Angle {
fn from(km: Kilometers) -> Angle {
km_to_angle(km.0)
}
}
impl From<Kilometers> for ChordAngle {
fn from(km: Kilometers) -> ChordAngle {
km_to_chord_angle(km.0)
}
}
impl From<Angle> for Kilometers {
fn from(a: Angle) -> Kilometers {
Kilometers(to_km(a))
}
}
impl From<ChordAngle> for Kilometers {
fn from(ca: ChordAngle) -> Kilometers {
Kilometers(chord_angle_to_km(ca))
}
}
impl From<Steradians> for SquareMeters {
fn from(sr: Steradians) -> SquareMeters {
SquareMeters(steradians_to_square_meters(sr.0))
}
}
impl From<Steradians> for SquareKilometers {
fn from(sr: Steradians) -> SquareKilometers {
SquareKilometers(steradians_to_square_km(sr.0))
}
}
impl From<SquareMeters> for Steradians {
fn from(m2: SquareMeters) -> Steradians {
Steradians(square_meters_to_steradians(m2.0))
}
}
impl From<SquareKilometers> for Steradians {
fn from(km2: SquareKilometers) -> Steradians {
Steradians(square_km_to_steradians(km2.0))
}
}
macro_rules! impl_distance_arithmetic {
($T:ty, $unit:literal) => {
impl std::ops::Add for $T {
type Output = $T;
fn add(self, rhs: $T) -> $T {
<$T>::from(self.0 + rhs.0)
}
}
impl std::ops::Sub for $T {
type Output = $T;
fn sub(self, rhs: $T) -> $T {
<$T>::from(self.0 - rhs.0)
}
}
impl std::ops::Mul<f64> for $T {
type Output = $T;
fn mul(self, rhs: f64) -> $T {
<$T>::from(self.0 * rhs)
}
}
impl std::ops::Mul<$T> for f64 {
type Output = $T;
fn mul(self, rhs: $T) -> $T {
<$T>::from(self * rhs.0)
}
}
impl std::ops::Div<f64> for $T {
type Output = $T;
fn div(self, rhs: f64) -> $T {
<$T>::from(self.0 / rhs)
}
}
impl std::ops::Neg for $T {
type Output = $T;
fn neg(self) -> $T {
<$T>::from(-self.0)
}
}
};
}
impl_distance_arithmetic!(Meters, "m");
impl_distance_arithmetic!(Kilometers, "km");
impl_distance_arithmetic!(Steradians, "sr");
impl_distance_arithmetic!(SquareMeters, "m²");
impl_distance_arithmetic!(SquareKilometers, "km²");
pub const RADIUS_METERS: f64 = 6_371_010.0;
pub const RADIUS_KM: f64 = 0.001 * RADIUS_METERS;
pub const LOWEST_ALTITUDE_METERS: f64 = -10_898.0;
pub const HIGHEST_ALTITUDE_METERS: f64 = 8_846.0;
pub fn meters_to_angle(meters: f64) -> Angle {
Angle::from_radians(meters_to_radians(meters))
}
pub fn meters_to_chord_angle(meters: f64) -> ChordAngle {
ChordAngle::from(meters_to_angle(meters))
}
pub fn meters_to_radians(meters: f64) -> f64 {
meters / RADIUS_METERS
}
pub fn to_meters(angle: Angle) -> f64 {
angle.radians() * RADIUS_METERS
}
pub fn chord_angle_to_meters(cangle: ChordAngle) -> f64 {
to_meters(Angle::from(cangle))
}
pub fn radians_to_meters(radians: f64) -> f64 {
radians * RADIUS_METERS
}
pub fn km_to_angle(km: f64) -> Angle {
Angle::from_radians(km_to_radians(km))
}
pub fn km_to_chord_angle(km: f64) -> ChordAngle {
ChordAngle::from(km_to_angle(km))
}
pub fn km_to_radians(km: f64) -> f64 {
km / RADIUS_KM
}
pub fn to_km(angle: Angle) -> f64 {
angle.radians() * RADIUS_KM
}
pub fn chord_angle_to_km(cangle: ChordAngle) -> f64 {
to_km(Angle::from(cangle))
}
pub fn radians_to_km(radians: f64) -> f64 {
radians * RADIUS_KM
}
pub fn square_km_to_steradians(km2: f64) -> f64 {
km2 / (RADIUS_KM * RADIUS_KM)
}
pub fn square_meters_to_steradians(m2: f64) -> f64 {
m2 / (RADIUS_METERS * RADIUS_METERS)
}
pub fn steradians_to_square_km(steradians: f64) -> f64 {
steradians * RADIUS_KM * RADIUS_KM
}
pub fn steradians_to_square_meters(steradians: f64) -> f64 {
steradians * RADIUS_METERS * RADIUS_METERS
}
pub fn meters_to_longitude_radians(meters: f64, latitude_radians: f64) -> f64 {
let scalar = latitude_radians.cos();
if scalar == 0.0 {
return std::f64::consts::TAU;
}
(meters_to_radians(meters) / scalar).min(std::f64::consts::TAU)
}
pub fn km_to_longitude_radians(km: f64, latitude_radians: f64) -> f64 {
meters_to_longitude_radians(1000.0 * km, latitude_radians)
}
pub fn get_distance_meters_points(a: Point, b: Point) -> f64 {
radians_to_meters(a.0.angle(b.0))
}
pub fn get_distance_meters_latlng(a: LatLng, b: LatLng) -> f64 {
to_meters(a.get_distance(b))
}
pub fn get_distance_km_points(a: Point, b: Point) -> f64 {
radians_to_km(a.0.angle(b.0))
}
pub fn get_distance_km_latlng(a: LatLng, b: LatLng) -> f64 {
to_km(a.get_distance(b))
}
pub fn haversine(radians: f64) -> f64 {
let sin_half = (radians / 2.0).sin();
sin_half * sin_half
}
pub fn get_initial_bearing(a: LatLng, b: LatLng) -> Angle {
let lat1 = a.lat.radians();
let cos_lat2 = b.lat.radians().cos();
let lat_diff = b.lat.radians() - a.lat.radians();
let lng_diff = b.lng.radians() - a.lng.radians();
let x = lat_diff.sin() + lat1.sin() * cos_lat2 * 2.0 * haversine(lng_diff);
let y = lng_diff.sin() * cos_lat2;
Angle::from_radians(y.atan2(x))
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-10;
#[test]
fn test_radius_constants() {
assert!((RADIUS_KM - 6371.01).abs() < 0.01);
assert!((RADIUS_METERS - 6_371_010.0).abs() < 1.0);
}
#[test]
fn test_meters_roundtrip() {
let meters = 1000.0;
let angle = meters_to_angle(meters);
let back = to_meters(angle);
assert!((back - meters).abs() < EPSILON);
}
#[test]
fn test_km_roundtrip() {
let km = 42.0;
let angle = km_to_angle(km);
let back = to_km(angle);
assert!((back - km).abs() < EPSILON);
}
#[test]
fn test_radians_roundtrip() {
let meters = 5000.0;
let radians = meters_to_radians(meters);
let back = radians_to_meters(radians);
assert!((back - meters).abs() < EPSILON);
}
#[test]
fn test_area_roundtrip() {
let km2 = 100.0;
let sr = square_km_to_steradians(km2);
let back = steradians_to_square_km(sr);
assert!((back - km2).abs() < EPSILON);
}
#[test]
fn test_distance_between_points() {
let a = LatLng::from_degrees(0.0, 0.0);
let b = LatLng::from_degrees(0.0, 1.0);
let dist_km = get_distance_km_latlng(a, b);
assert!((dist_km - 111.19).abs() < 1.0);
}
#[test]
fn test_distance_same_point() {
let a = LatLng::from_degrees(45.0, 90.0);
let dist = get_distance_meters_latlng(a, a);
assert!(dist.abs() < 1e-6);
}
#[test]
fn test_meters_to_longitude_radians_equator() {
let meters = RADIUS_METERS; let lng_rad = meters_to_longitude_radians(meters, 0.0);
assert!((lng_rad - 1.0).abs() < EPSILON);
}
#[test]
fn test_meters_to_longitude_radians_pole() {
let meters = 1000.0;
let lng_rad = meters_to_longitude_radians(meters, std::f64::consts::FRAC_PI_2);
assert!((lng_rad - std::f64::consts::TAU).abs() < EPSILON);
}
#[test]
fn test_chord_angle_conversion() {
let meters = 10000.0;
let ca = meters_to_chord_angle(meters);
let back = chord_angle_to_meters(ca);
assert!((back - meters).abs() < 0.01);
}
#[test]
fn test_initial_bearing() {
let cases: &[(&str, f64, f64, f64, f64, f64)] = &[
("eastward on equator", 0.0, 50.0, 0.0, 100.0, 90.0),
("westward on equator", 0.0, 50.0, 0.0, 0.0, -90.0),
("northward on meridian", 16.0, 28.0, 81.0, 28.0, 0.0),
("southward on meridian", 24.0, 64.0, -27.0, 64.0, 180.0),
("towards north pole", 12.0, 76.0, 90.0, 50.0, 0.0),
("towards south pole", -35.0, 105.0, -90.0, -120.0, 180.0),
(
"Spain to Japan",
40.4379332,
-3.749576,
35.6733227,
139.6403486,
29.2,
),
(
"Japan to Spain",
35.6733227,
139.6403486,
40.4379332,
-3.749576,
-27.2,
),
];
for &(name, lat1, lng1, lat2, lng2, expected_deg) in cases {
let a = LatLng::from_degrees(lat1, lng1);
let b = LatLng::from_degrees(lat2, lng2);
let bearing_deg = get_initial_bearing(a, b).degrees();
let mut diff = (bearing_deg - expected_deg).abs();
if diff > 180.0 {
diff = 360.0 - diff;
}
assert!(
diff <= 0.01,
"get_initial_bearing({name}): expected {expected_deg}°, got {bearing_deg}°"
);
}
}
#[test]
fn test_km_to_angle_and_back() {
let km = 1.0;
let angle = km_to_angle(km);
let back = to_km(angle);
assert!((back - km).abs() < 1e-10, "km roundtrip: {back} vs {km}");
}
#[test]
fn test_km_to_chord_angle_and_back() {
let km = 100.0;
let ca = km_to_chord_angle(km);
let back = chord_angle_to_km(ca);
assert!(
(back - km).abs() < 0.01,
"km chord roundtrip: {back} vs {km}"
);
}
#[test]
fn test_area_sphere_surface() {
let sphere_area_sr = 4.0 * std::f64::consts::PI;
let sphere_km2 = steradians_to_square_km(sphere_area_sr);
let expected_km2 = 4.0 * std::f64::consts::PI * (RADIUS_METERS / 1000.0).powi(2);
assert!(
(sphere_km2 - expected_km2).abs() / expected_km2 < 1e-10,
"sphere area: {sphere_km2} vs {expected_km2}",
);
}
#[test]
fn test_distance_known_cities() {
let nyc = LatLng::from_degrees(40.7128, -74.0060);
let london = LatLng::from_degrees(51.5074, -0.1278);
let dist_km = get_distance_km_latlng(nyc, london);
assert!(
(dist_km - 5570.0).abs() < 50.0,
"NYC-London distance: {dist_km} km, expected ~5570 km",
);
}
#[test]
fn test_angle_conversion_exact() {
use std::f64::consts::PI;
assert_eq!(meters_to_angle(RADIUS_METERS).radians(), 1.0);
assert_eq!(km_to_angle(RADIUS_KM).radians(), 1.0);
assert_eq!(to_km(Angle::from_radians(0.5)), 0.5 * RADIUS_KM);
assert_eq!(radians_to_km(0.5), 0.5 * RADIUS_KM);
assert!(
(meters_to_chord_angle(RADIUS_METERS).radians() - 1.0).abs() < 1e-14,
"meters_to_chord_angle(RADIUS_METERS).radians() != 1"
);
assert!(
(km_to_chord_angle(RADIUS_KM).radians() - 1.0).abs() < 1e-14,
"km_to_chord_angle(RADIUS_KM).radians() != 1"
);
let ca_km = chord_angle_to_km(ChordAngle::from_radians(0.5));
assert!(
(ca_km - 0.5 * RADIUS_KM).abs() < 1e-9,
"chord_angle_to_km(0.5 rad) = {ca_km}, expected {}",
0.5 * RADIUS_KM
);
assert!(
(chord_angle_to_meters(ChordAngle::from_degrees(180.0)) - RADIUS_METERS * PI).abs()
< 1e-7,
"chord_angle_to_meters(180°) should be PI * RADIUS_METERS"
);
assert!(
(to_meters(Angle::from_degrees(180.0)) - RADIUS_METERS * PI).abs() < 1e-7,
"to_meters(180°) should be PI * RADIUS_METERS"
);
assert!(
(radians_to_meters(km_to_radians(2.5)) - 2500.0).abs() < 1e-9,
"radians_to_meters(km_to_radians(2.5)) should be 2500"
);
assert!(
(meters_to_radians(radians_to_km(0.3) * 1000.0) - 0.3).abs() < 1e-14,
"meters_to_radians(radians_to_km(0.3) * 1000) should be 0.3"
);
assert!(
(km_to_radians(RADIUS_METERS / 1000.0) - 1.0).abs() < 1e-14,
"km_to_radians(RADIUS_METERS / 1000) should be 1.0"
);
}
#[test]
fn test_longitude_radians_near_pole() {
let lat_near_pole = std::f64::consts::FRAC_PI_2 - 1e-4;
let result = meters_to_longitude_radians(RADIUS_METERS, lat_near_pole);
assert_eq!(
result,
std::f64::consts::TAU,
"near-pole latitude should clamp to TAU, got {result}"
);
}
fn double_eq(a: f64, b: f64) -> bool {
(a - b).abs() <= 4.0 * f64::EPSILON * a.abs().max(b.abs()).max(1.0)
}
#[test]
fn test_solid_angle_conversion() {
assert!(double_eq(
square_km_to_steradians((RADIUS_METERS / 1000.0).powi(2)),
1.0
));
assert!(double_eq(
steradians_to_square_km(0.5_f64.powi(2)),
(0.5 * RADIUS_KM).powi(2)
));
assert!(double_eq(
square_meters_to_steradians((radians_to_km(0.3) * 1000.0).powi(2)),
0.3_f64.powi(2)
));
assert!(double_eq(
steradians_to_square_meters(km_to_radians(2.5).powi(2)),
2500.0_f64.powi(2)
));
}
#[test]
fn test_longitude_radians_monotonicity_and_compat() {
assert!(
meters_to_longitude_radians(RADIUS_METERS, 0.5)
> meters_to_longitude_radians(RADIUS_METERS, 0.4)
);
assert_eq!(
meters_to_longitude_radians(RADIUS_METERS, 0.5),
km_to_longitude_radians(RADIUS_METERS / 1000.0, 0.5)
);
}
#[test]
fn test_distance_latlng_exact() {
use std::f64::consts::PI;
assert!(double_eq(
get_distance_km_latlng(
LatLng::from_radians(0.0, 0.6),
LatLng::from_radians(0.0, -0.4),
),
RADIUS_KM
));
assert!(double_eq(
get_distance_meters_latlng(
LatLng::from_degrees(80.0, 27.0),
LatLng::from_degrees(55.0, -153.0),
),
1000.0 * RADIUS_KM * PI / 4.0
));
}
#[test]
fn test_distance_points() {
use std::f64::consts::{FRAC_PI_2, PI};
let north = Point::from_coords(0.0, 0.0, 1.0);
let south = Point::from_coords(0.0, 0.0, -1.0);
let west = Point::from_coords(0.0, -1.0, 0.0);
assert_eq!(get_distance_km_points(west, west), 0.0);
assert_eq!(
get_distance_meters_points(north, west),
FRAC_PI_2 * RADIUS_METERS
);
assert_eq!(get_distance_meters_points(north, south), PI * RADIUS_METERS);
}
#[test]
fn test_meters_newtype_display() {
assert_eq!(format!("{}", Meters(1234.5)), "1234.5 m");
}
#[test]
fn test_kilometers_newtype_display() {
assert_eq!(format!("{}", Kilometers(42.0)), "42 km");
}
#[test]
fn test_steradians_newtype_display() {
assert_eq!(format!("{}", Steradians(0.5)), "0.5 sr");
}
#[test]
fn test_square_meters_newtype_display() {
assert_eq!(format!("{}", SquareMeters(100.0)), "100 m²");
}
#[test]
fn test_square_kilometers_newtype_display() {
assert_eq!(format!("{}", SquareKilometers(50.0)), "50 km²");
}
#[test]
fn test_meters_from_f64() {
let m: Meters = 1000.0_f64.into();
assert_eq!(m, Meters(1000.0));
let v: f64 = m.into();
assert_eq!(v, 1000.0);
}
#[test]
fn test_km_from_f64() {
let km: Kilometers = 42.0_f64.into();
assert_eq!(km, Kilometers(42.0));
let v: f64 = km.into();
assert_eq!(v, 42.0);
}
#[test]
fn test_meters_km_cross_conversion() {
let m = Meters(5000.0);
let km: Kilometers = m.into();
assert_eq!(km, Kilometers(5.0));
let back: Meters = km.into();
assert_eq!(back, Meters(5000.0));
}
#[test]
fn test_square_meters_km_cross_conversion() {
let m2 = SquareMeters(1e6);
let km2: SquareKilometers = m2.into();
assert_eq!(km2, SquareKilometers(1.0));
let back: SquareMeters = km2.into();
assert_eq!(back, SquareMeters(1e6));
}
#[test]
fn test_meters_to_angle_newtype() {
let m = Meters(RADIUS_METERS);
let a: Angle = m.into();
assert_eq!(a.radians(), 1.0);
}
#[test]
fn test_meters_from_chord_angle() {
let ca = ChordAngle::from_radians(1.0);
let m: Meters = ca.into();
assert!((m.0 - RADIUS_METERS).abs() < 1.0);
}
#[test]
fn test_km_from_chord_angle() {
let ca = ChordAngle::from_radians(1.0);
let km: Kilometers = ca.into();
assert!((km.0 - RADIUS_KM).abs() < 0.001);
}
#[test]
fn test_meters_arithmetic() {
assert_eq!(Meters(100.0) + Meters(50.0), Meters(150.0));
assert_eq!(Meters(100.0) - Meters(30.0), Meters(70.0));
assert_eq!(Meters(100.0) * 2.0, Meters(200.0));
assert_eq!(2.0 * Meters(100.0), Meters(200.0));
assert_eq!(Meters(100.0) / 4.0, Meters(25.0));
assert_eq!(-Meters(10.0), Meters(-10.0));
}
#[test]
fn test_km_arithmetic() {
assert_eq!(Kilometers(10.0) + Kilometers(5.0), Kilometers(15.0));
assert_eq!(Kilometers(10.0) - Kilometers(3.0), Kilometers(7.0));
assert_eq!(Kilometers(10.0) * 3.0, Kilometers(30.0));
assert_eq!(3.0 * Kilometers(10.0), Kilometers(30.0));
assert_eq!(Kilometers(10.0) / 2.0, Kilometers(5.0));
assert_eq!(-Kilometers(5.0), Kilometers(-5.0));
}
#[test]
fn test_steradians_to_square_km_newtype() {
let sr = Steradians(1.0);
let km2: SquareKilometers = sr.into();
let expected = RADIUS_KM * RADIUS_KM;
assert!((km2.0 - expected).abs() / expected < 1e-10);
}
#[test]
fn test_square_km_to_steradians_newtype() {
let km2 = SquareKilometers(RADIUS_KM * RADIUS_KM);
let sr: Steradians = km2.into();
assert!((sr.0 - 1.0).abs() < 1e-10);
}
}