Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_atan::atan; ← from math.atan ^1.0.0 · built alongside by fune
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
use super::math_sin_cos::sin_cos; ← from math.sin-cos ^1.0.0 · built alongside by fune
// Sine, cosine and arctangent come from math.sin-cos and math.atan, not
// f64::sin or f64::atan2: those come from the platform maths library, which may
// differ in the last bit between Rust, JavaScript and Python, and a value next
// to a rounding boundary can then round differently. With the shared helpers
// the unrounded distance is the same double everywhere, and math.round-float
// rounds it the same way everywhere.
/// IUGG mean radius R1 = (2a + b) / 3 of the GRS 80 ellipsoid (Moritz,
/// "Geodetic Reference System 1980", Journal of Geodesy 74 (2000) 128-133).
const EARTH_RADIUS_METRES: f64 = 6371008.8;
const PI: f64 = 3.141592653589793;
const HALF_PI: f64 = PI / 2.0;
const DEGREES: f64 = PI / 180.0;
/// atan2 for y >= 0 and x >= 0. x is 0 only for exactly antipodal points,
/// where y / x would be infinite, which math.atan refuses.
fn atan2_positive(y: f64, x: f64) -> f64 {
if x == 0.0 {
return if y == 0.0 { 0.0 } else { HALF_PI };
}
atan(y / x)
}
fn check_latitude(value: f64) {
if !value.is_finite() {
panic!("latitude must be a finite number of degrees");
}
if value < -90.0 || value > 90.0 {
panic!("latitude must be between -90 and 90 degrees");
}
}
fn check_longitude(value: f64) {
if !value.is_finite() {
panic!("longitude must be a finite number of degrees");
}
if value < -180.0 || value > 180.0 {
panic!("longitude must be between -180 and 180 degrees");
}
}
/// Great-circle distance in metres (haversine, IUGG mean radius), to the millimetre.
///
/// # Panics
/// Panics if a coordinate is not finite or out of range.
pub fn distance(from_lat: f64, from_lng: f64, to_lat: f64, to_lng: f64) -> f64 {
check_latitude(from_lat);
check_longitude(from_lng);
check_latitude(to_lat);
check_longitude(to_lng);
// A longitude difference of 359 degrees is a 1 degree step across the
// antimeridian; sin^2 of the half-angle is the same either way.
let sin_half_lat = sin_cos(((to_lat - from_lat) * DEGREES) / 2.0).sin;
let sin_half_lng = sin_cos(((to_lng - from_lng) * DEGREES) / 2.0).sin;
let cos1 = sin_cos(from_lat * DEGREES).cos;
let cos2 = sin_cos(to_lat * DEGREES).cos;
let mut a = sin_half_lat * sin_half_lat + cos1 * cos2 * sin_half_lng * sin_half_lng;
if a < 0.0 {
a = 0.0;
}
if a > 1.0 {
a = 1.0;
}
// atan2 rather than asin(sqrt(a)): stays accurate for antipodal points too.
let c = 2.0 * atan2_positive(a.sqrt(), (1.0 - a).sqrt());
// Half away from zero on the exact value of the double (math.round-float).
round_float(EARTH_RADIUS_METRES * c, 3)
}
pub fn fune_vector(args: &[Value]) -> Value {
// Refuse what the typed signature cannot hold, with the wording TypeScript
// and Python use, rather than let the conversion below quietly change it.
if !matches!(args[0], Value::Int(_) | Value::Float(_)) {
panic!("latitude must be a finite number of degrees");
}
if !matches!(args[1], Value::Int(_) | Value::Float(_)) {
panic!("longitude must be a finite number of degrees");
}
if !matches!(args[2], Value::Int(_) | Value::Float(_)) {
panic!("latitude must be a finite number of degrees");
}
if !matches!(args[3], Value::Int(_) | Value::Float(_)) {
panic!("longitude must be a finite number of degrees");
}
Value::Float(distance(
args[0].as_f64(),
args[1].as_f64(),
args[2].as_f64(),
args[3].as_f64(),
))
}