use std::collections::HashSet;
use std::rc::Rc;
use geoutils::Location;
use crate::common::Function;
use crate::register_if_enabled;
use crate::{ArgumentType, Context, JmespathError, Rcvar, Runtime, Signature, Variable};
pub fn register(runtime: &mut Runtime) {
runtime.register_function("geo_distance", Box::new(GeoDistanceFn::new()));
runtime.register_function("geo_distance_km", Box::new(GeoDistanceKmFn::new()));
runtime.register_function("geo_distance_miles", Box::new(GeoDistanceMilesFn::new()));
runtime.register_function("geo_bearing", Box::new(GeoBearingFn::new()));
}
pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
register_if_enabled!(
runtime,
enabled,
"geo_distance",
Box::new(GeoDistanceFn::new())
);
register_if_enabled!(
runtime,
enabled,
"geo_distance_km",
Box::new(GeoDistanceKmFn::new())
);
register_if_enabled!(
runtime,
enabled,
"geo_distance_miles",
Box::new(GeoDistanceMilesFn::new())
);
register_if_enabled!(
runtime,
enabled,
"geo_bearing",
Box::new(GeoBearingFn::new())
);
}
pub struct GeoDistanceFn {
signature: Signature,
}
impl Default for GeoDistanceFn {
fn default() -> Self {
Self::new()
}
}
impl GeoDistanceFn {
pub fn new() -> Self {
Self {
signature: Signature::new(
vec![
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
],
None,
),
}
}
}
impl Function for GeoDistanceFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let lat1 = args[0].as_number().unwrap();
let lon1 = args[1].as_number().unwrap();
let lat2 = args[2].as_number().unwrap();
let lon2 = args[3].as_number().unwrap();
let loc1 = Location::new(lat1, lon1);
let loc2 = Location::new(lat2, lon2);
let distance = loc1.haversine_distance_to(&loc2);
Ok(Rc::new(Variable::Number(
serde_json::Number::from_f64(distance.meters()).unwrap(),
)))
}
}
pub struct GeoDistanceKmFn {
signature: Signature,
}
impl Default for GeoDistanceKmFn {
fn default() -> Self {
Self::new()
}
}
impl GeoDistanceKmFn {
pub fn new() -> Self {
Self {
signature: Signature::new(
vec![
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
],
None,
),
}
}
}
impl Function for GeoDistanceKmFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let lat1 = args[0].as_number().unwrap();
let lon1 = args[1].as_number().unwrap();
let lat2 = args[2].as_number().unwrap();
let lon2 = args[3].as_number().unwrap();
let loc1 = Location::new(lat1, lon1);
let loc2 = Location::new(lat2, lon2);
let distance = loc1.haversine_distance_to(&loc2);
Ok(Rc::new(Variable::Number(
serde_json::Number::from_f64(distance.meters() / 1000.0).unwrap(),
)))
}
}
pub struct GeoDistanceMilesFn {
signature: Signature,
}
impl Default for GeoDistanceMilesFn {
fn default() -> Self {
Self::new()
}
}
impl GeoDistanceMilesFn {
pub fn new() -> Self {
Self {
signature: Signature::new(
vec![
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
],
None,
),
}
}
}
impl Function for GeoDistanceMilesFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let lat1 = args[0].as_number().unwrap();
let lon1 = args[1].as_number().unwrap();
let lat2 = args[2].as_number().unwrap();
let lon2 = args[3].as_number().unwrap();
let loc1 = Location::new(lat1, lon1);
let loc2 = Location::new(lat2, lon2);
const METERS_TO_MILES: f64 = 0.000621371;
let distance = loc1.haversine_distance_to(&loc2);
Ok(Rc::new(Variable::Number(
serde_json::Number::from_f64(distance.meters() * METERS_TO_MILES).unwrap(),
)))
}
}
pub struct GeoBearingFn {
signature: Signature,
}
impl Default for GeoBearingFn {
fn default() -> Self {
Self::new()
}
}
impl GeoBearingFn {
pub fn new() -> Self {
Self {
signature: Signature::new(
vec![
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
ArgumentType::Number,
],
None,
),
}
}
}
impl Function for GeoBearingFn {
fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
self.signature.validate(args, ctx)?;
let lat1 = args[0].as_number().unwrap();
let lon1 = args[1].as_number().unwrap();
let lat2 = args[2].as_number().unwrap();
let lon2 = args[3].as_number().unwrap();
let lat1_rad = lat1.to_radians();
let lat2_rad = lat2.to_radians();
let delta_lon = (lon2 - lon1).to_radians();
let x = delta_lon.sin() * lat2_rad.cos();
let y = lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * delta_lon.cos();
let bearing_rad = x.atan2(y);
let mut bearing = bearing_rad.to_degrees();
if bearing < 0.0 {
bearing += 360.0;
}
Ok(Rc::new(Variable::Number(
serde_json::Number::from_f64(bearing).unwrap(),
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup() -> Runtime {
let mut runtime = Runtime::new();
runtime.register_builtin_functions();
register(&mut runtime);
runtime
}
#[test]
fn test_geo_distance() {
let runtime = setup();
let data =
Variable::from_json(r#"{"nyc": [40.7128, -74.0060], "la": [34.0522, -118.2437]}"#)
.unwrap();
let expr = runtime
.compile("geo_distance(nyc[0], nyc[1], la[0], la[1])")
.unwrap();
let result = expr.search(&data).unwrap();
let meters = result.as_number().unwrap();
assert!(meters > 3900000.0 && meters < 4000000.0);
}
#[test]
fn test_geo_distance_km() {
let runtime = setup();
let data =
Variable::from_json(r#"{"nyc": [40.7128, -74.0060], "la": [34.0522, -118.2437]}"#)
.unwrap();
let expr = runtime
.compile("geo_distance_km(nyc[0], nyc[1], la[0], la[1])")
.unwrap();
let result = expr.search(&data).unwrap();
let km = result.as_number().unwrap();
assert!(km > 3900.0 && km < 4000.0);
}
#[test]
fn test_geo_distance_miles() {
let runtime = setup();
let data =
Variable::from_json(r#"{"nyc": [40.7128, -74.0060], "la": [34.0522, -118.2437]}"#)
.unwrap();
let expr = runtime
.compile("geo_distance_miles(nyc[0], nyc[1], la[0], la[1])")
.unwrap();
let result = expr.search(&data).unwrap();
let miles = result.as_number().unwrap();
assert!(miles > 2400.0 && miles < 2500.0);
}
#[test]
fn test_geo_bearing() {
let runtime = setup();
let data =
Variable::from_json(r#"{"nyc": [40.7128, -74.0060], "la": [34.0522, -118.2437]}"#)
.unwrap();
let expr = runtime
.compile("geo_bearing(nyc[0], nyc[1], la[0], la[1])")
.unwrap();
let result = expr.search(&data).unwrap();
let bearing = result.as_number().unwrap();
assert!(bearing > 260.0 && bearing < 290.0);
}
#[test]
fn test_geo_distance_same_point() {
let runtime = setup();
let data = Variable::from_json(r#"[40.7128, -74.0060]"#).unwrap();
let expr = runtime
.compile("geo_distance(@[0], @[1], @[0], @[1])")
.unwrap();
let result = expr.search(&data).unwrap();
let meters = result.as_number().unwrap();
assert!(meters < 1.0); }
}