use std::ops::RangeInclusive;
pub(crate) const EARTH_RADIUS_M: f64 = 6_371_000.0;
pub(crate) const LATITUDE_RANGE_DEG: RangeInclusive<f64> = -90.0..=90.0;
pub(crate) const LONGITUDE_RANGE_DEG: RangeInclusive<f64> = -180.0..=180.0;
pub(crate) const DISTANCE_EQUALITY_RESOLUTION_M: f64 = 1e-3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DistanceOp {
Eq,
NotEq,
Gt,
Gte,
Lt,
Lte,
}
macro_rules! distance_op_from {
($(#[$attr:meta])* $source:path) => {
$(#[$attr])*
impl From<$source> for DistanceOp {
fn from(op: $source) -> Self {
type Source = $source;
match op {
Source::Eq => Self::Eq,
Source::NotEq => Self::NotEq,
Source::Gt => Self::Gt,
Source::Gte => Self::Gte,
Source::Lt => Self::Lt,
Source::Lte => Self::Lte,
}
}
}
};
}
distance_op_from!(crate::velesql::CompareOp);
distance_op_from!(
#[cfg(feature = "persistence")]
crate::column_store::CompareOp
);
#[must_use]
pub(crate) fn is_valid_coordinate(lat: f64, lng: f64) -> bool {
LATITUDE_RANGE_DEG.contains(&lat) && LONGITUDE_RANGE_DEG.contains(&lng)
}
#[must_use]
pub(crate) fn great_circle_distance_m(lat1: f64, lng1: f64, lat2: f64, lng2: f64) -> Option<f64> {
if !is_valid_coordinate(lat1, lng1) || !is_valid_coordinate(lat2, lng2) {
return None;
}
let ((lat1, lng1), (lat2, lng2)) = if (lat1, lng1) <= (lat2, lng2) {
((lat1, lng1), (lat2, lng2))
} else {
((lat2, lng2), (lat1, lng1))
};
let (sin_phi1, cos_phi1) = lat1.to_radians().sin_cos();
let (sin_phi2, cos_phi2) = lat2.to_radians().sin_cos();
let (sin_dlambda, cos_dlambda) = (lng2.to_radians() - lng1.to_radians()).sin_cos();
let east = cos_phi2 * sin_dlambda;
let north = cos_phi1 * sin_phi2 - sin_phi1 * cos_phi2 * cos_dlambda;
let along = sin_phi1 * sin_phi2 + cos_phi1 * cos_phi2 * cos_dlambda;
let central_angle = (east * east + north * north).sqrt().atan2(along);
Some(EARTH_RADIUS_M * central_angle)
}
#[must_use]
pub(crate) fn distance_satisfies(distance: Option<f64>, op: DistanceOp, threshold: f64) -> bool {
let Some(distance) = distance else {
return false;
};
if threshold.is_nan() {
return false;
}
let equal = (distance - threshold).abs() < DISTANCE_EQUALITY_RESOLUTION_M;
match op {
DistanceOp::Eq => equal,
DistanceOp::NotEq => !equal,
DistanceOp::Gt => distance > threshold,
DistanceOp::Gte => distance >= threshold,
DistanceOp::Lt => distance < threshold,
DistanceOp::Lte => distance <= threshold,
}
}