1use std::collections::BTreeMap;
2use std::sync::LazyLock;
3
4use crate::compiler::function::EnumVariant;
5use crate::compiler::prelude::*;
6use crate::value;
7
8use super::util::round_to_precision;
9
10const EARTH_RADIUS_IN_METERS: f64 = 6_371_008.8;
11const EARTH_RADIUS_IN_KILOMETERS: f64 = EARTH_RADIUS_IN_METERS / 1000.0;
12const EARTH_RADIUS_IN_MILES: f64 = EARTH_RADIUS_IN_KILOMETERS * 0.621_371_2;
13
14static DEFAULT_MEASUREMENT_UNIT: LazyLock<Value> = LazyLock::new(|| value!("kilometers"));
15
16fn haversine_distance(
17 latitude1: Value,
18 longitude1: Value,
19 latitude2: Value,
20 longitude2: Value,
21 measurement_unit: &MeasurementUnit,
22) -> Resolved {
23 let latitude1 = latitude1.try_float()?.to_radians();
24 let longitude1 = longitude1.try_float()?.to_radians();
25 let latitude2 = latitude2.try_float()?.to_radians();
26 let longitude2 = longitude2.try_float()?.to_radians();
27
28 let mut result = ObjectMap::new();
29
30 let dlon = longitude2 - longitude1;
32 let dlat = latitude2 - latitude1;
33 let a =
34 (dlat / 2.0).sin().powi(2) + latitude1.cos() * latitude2.cos() * (dlon / 2.0).sin().powi(2);
35 let distance = 2.0 * a.sqrt().asin();
36
37 result.insert(
38 "distance".into(),
39 match measurement_unit {
40 MeasurementUnit::Kilometers => Value::from_f64_or_zero(round_to_precision(
41 distance * EARTH_RADIUS_IN_KILOMETERS,
42 7,
43 f64::round,
44 )),
45 MeasurementUnit::Miles => Value::from_f64_or_zero(round_to_precision(
46 distance * EARTH_RADIUS_IN_MILES,
47 7,
48 f64::round,
49 )),
50 },
51 );
52
53 let y = dlon.sin() * latitude2.cos();
55 let x = latitude1.cos() * latitude2.sin() - latitude1.sin() * latitude2.cos() * dlon.cos();
56 let bearing = (y.atan2(x).to_degrees() + 360.0) % 360.0;
57
58 result.insert(
59 "bearing".into(),
60 Value::from_f64_or_zero(round_to_precision(bearing, 3, f64::round)),
61 );
62
63 Ok(result.into())
64}
65
66fn measurement_systems() -> Vec<Value> {
67 vec![value!("kilometers"), value!("miles")]
68}
69
70#[derive(Clone, Debug)]
71enum MeasurementUnit {
72 Kilometers,
73 Miles,
74}
75
76#[derive(Clone, Copy, Debug)]
77pub struct Haversine;
78
79impl Function for Haversine {
80 fn identifier(&self) -> &'static str {
81 "haversine"
82 }
83
84 fn usage(&self) -> &'static str {
85 "Calculates [haversine](https://en.wikipedia.org/wiki/Haversine_formula) distance and bearing between two points."
86 }
87
88 fn category(&self) -> &'static str {
89 Category::Map.as_ref()
90 }
91
92 fn return_kind(&self) -> u16 {
93 kind::OBJECT
94 }
95
96 fn parameters(&self) -> &'static [Parameter] {
97 const PARAMETERS: &[Parameter] = &[
98 Parameter::required("latitude1", kind::FLOAT, "Latitude of the first point."),
99 Parameter::required("longitude1", kind::FLOAT, "Longitude of the first point."),
100 Parameter::required("latitude2", kind::FLOAT, "Latitude of the second point."),
101 Parameter::required("longitude2", kind::FLOAT, "Longitude of the second point."),
102 Parameter::optional(
103 "measurement_unit",
104 kind::BYTES,
105 "Measurement system to use for resulting distance.",
106 )
107 .enum_variants(&[
108 EnumVariant {
109 value: "kilometers",
110 description: "Use kilometers for the resulting distance.",
111 },
112 EnumVariant {
113 value: "miles",
114 description: "Use miles for the resulting distance.",
115 },
116 ]),
117 ];
118 PARAMETERS
119 }
120
121 fn compile(
122 &self,
123 state: &state::TypeState,
124 _ctx: &mut FunctionCompileContext,
125 arguments: ArgumentList,
126 ) -> Compiled {
127 let latitude1 = arguments.required("latitude1");
128 let longitude1 = arguments.required("longitude1");
129 let latitude2 = arguments.required("latitude2");
130 let longitude2 = arguments.required("longitude2");
131
132 let measurement_unit = match arguments
133 .optional_enum("measurement_unit", &measurement_systems(), state)?
134 .unwrap_or_else(|| DEFAULT_MEASUREMENT_UNIT.clone())
135 .try_bytes()
136 .ok()
137 .as_deref()
138 {
139 Some(b"kilometers") => MeasurementUnit::Kilometers,
140 Some(b"miles") => MeasurementUnit::Miles,
141 _ => return Err(Box::new(ExpressionError::from("invalid measurement unit"))),
142 };
143
144 Ok(HaversineFn {
145 latitude1,
146 longitude1,
147 latitude2,
148 longitude2,
149 measurement_unit,
150 }
151 .as_expr())
152 }
153
154 fn examples(&self) -> &'static [Example] {
155 &[
156 example! {
157 title: "Haversine in kilometers",
158 source: "haversine(0.0, 0.0, 10.0, 10.0)",
159 result: Ok(indoc!(
160 r#"{
161 "distance": 1568.5227233,
162 "bearing": 44.561
163 }"#
164 )),
165 },
166 example! {
167 title: "Haversine in miles",
168 source: r#"haversine(0.0, 0.0, 10.0, 10.0, measurement_unit: "miles")"#,
169 result: Ok(indoc!(
170 r#"{
171 "distance": 974.6348468,
172 "bearing": 44.561
173 }"#
174 )),
175 },
176 ]
177 }
178}
179
180#[derive(Clone, Debug)]
181struct HaversineFn {
182 latitude1: Box<dyn Expression>,
183 longitude1: Box<dyn Expression>,
184 latitude2: Box<dyn Expression>,
185 longitude2: Box<dyn Expression>,
186 measurement_unit: MeasurementUnit,
187}
188
189impl FunctionExpression for HaversineFn {
190 fn resolve(&self, ctx: &mut Context) -> Resolved {
191 let latitude1 = self.latitude1.resolve(ctx)?;
192 let longitude1 = self.longitude1.resolve(ctx)?;
193 let latitude2 = self.latitude2.resolve(ctx)?;
194 let longitude2 = self.longitude2.resolve(ctx)?;
195
196 haversine_distance(
197 latitude1,
198 longitude1,
199 latitude2,
200 longitude2,
201 &self.measurement_unit,
202 )
203 }
204
205 fn type_def(&self, _state: &state::TypeState) -> TypeDef {
206 TypeDef::object(inner_kind()).infallible()
207 }
208}
209
210fn inner_kind() -> BTreeMap<Field, Kind> {
211 BTreeMap::from([
212 (Field::from("distance"), Kind::float()),
213 (Field::from("bearing"), Kind::float()),
214 ])
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::value;
221
222 test_function![
223 haversine => Haversine;
224
225 basic_kilometers {
226 args: func_args![latitude1: value!(0.0), longitude1: value!(0.0), latitude2: value!(10.0), longitude2: value!(10.0)],
227 want: Ok(value!({ "distance": 1_568.522_723_3, "bearing": 44.561 })),
228 tdef: TypeDef::object(inner_kind()).infallible(),
229 }
230
231 basic_miles {
232 args: func_args![latitude1: value!(0.0), longitude1: value!(0.0), latitude2: value!(10.0), longitude2: value!(10.0), measurement_unit: value!("miles")],
233 want: Ok(value!({ "distance": 974.634_846_8, "bearing": 44.561 })),
234 tdef: TypeDef::object(inner_kind()).infallible(),
235 }
236 ];
237}