122 lines
4.1 KiB
Rust
122 lines
4.1 KiB
Rust
/// Geographic calculation utilities for GPS-based search
|
|
use std::f64;
|
|
|
|
/// Calculate distance between two GPS coordinates using the Haversine formula.
|
|
/// Returns distance in kilometers.
|
|
///
|
|
/// # Arguments
|
|
/// * `lat1` - Latitude of first point in decimal degrees
|
|
/// * `lon1` - Longitude of first point in decimal degrees
|
|
/// * `lat2` - Latitude of second point in decimal degrees
|
|
/// * `lon2` - Longitude of second point in decimal degrees
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// use image_api::geo::haversine_distance;
|
|
/// let distance = haversine_distance(37.7749, -122.4194, 34.0522, -118.2437);
|
|
/// // Distance between San Francisco and Los Angeles (~559 km)
|
|
/// ```
|
|
pub fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
|
|
const EARTH_RADIUS_KM: f64 = 6371.0;
|
|
|
|
let lat1_rad = lat1.to_radians();
|
|
let lat2_rad = lat2.to_radians();
|
|
let delta_lat = (lat2 - lat1).to_radians();
|
|
let delta_lon = (lon2 - lon1).to_radians();
|
|
|
|
let a = (delta_lat / 2.0).sin().powi(2)
|
|
+ lat1_rad.cos() * lat2_rad.cos() * (delta_lon / 2.0).sin().powi(2);
|
|
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
|
|
|
|
EARTH_RADIUS_KM * c
|
|
}
|
|
|
|
/// Calculate bounding box for GPS radius query.
|
|
/// Returns (min_lat, max_lat, min_lon, max_lon) that encompasses the search radius.
|
|
///
|
|
/// This is used as a fast first-pass filter for GPS queries, narrowing down
|
|
/// candidates before applying the more expensive Haversine distance calculation.
|
|
///
|
|
/// # Arguments
|
|
/// * `lat` - Center latitude in decimal degrees
|
|
/// * `lon` - Center longitude in decimal degrees
|
|
/// * `radius_km` - Search radius in kilometers
|
|
///
|
|
/// # Returns
|
|
/// A tuple of (min_lat, max_lat, min_lon, max_lon) in decimal degrees
|
|
pub fn gps_bounding_box(lat: f64, lon: f64, radius_km: f64) -> (f64, f64, f64, f64) {
|
|
const EARTH_RADIUS_KM: f64 = 6371.0;
|
|
|
|
// Calculate latitude delta (same at all latitudes)
|
|
let lat_delta = (radius_km / EARTH_RADIUS_KM) * (180.0 / f64::consts::PI);
|
|
|
|
// Calculate longitude delta (varies with latitude)
|
|
let lon_delta = lat_delta / lat.to_radians().cos();
|
|
|
|
(
|
|
lat - lat_delta, // min_lat
|
|
lat + lat_delta, // max_lat
|
|
lon - lon_delta, // min_lon
|
|
lon + lon_delta, // max_lon
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_haversine_distance_sf_to_la() {
|
|
// San Francisco to Los Angeles
|
|
let distance = haversine_distance(37.7749, -122.4194, 34.0522, -118.2437);
|
|
// Should be approximately 559 km
|
|
assert!(
|
|
(distance - 559.0).abs() < 10.0,
|
|
"Distance should be ~559km, got {}",
|
|
distance
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_haversine_distance_same_point() {
|
|
// Same point should have zero distance
|
|
let distance = haversine_distance(37.7749, -122.4194, 37.7749, -122.4194);
|
|
assert!(
|
|
distance < 0.001,
|
|
"Same point should have ~0 distance, got {}",
|
|
distance
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gps_bounding_box() {
|
|
// Test bounding box calculation for 10km radius around San Francisco
|
|
let (min_lat, max_lat, min_lon, max_lon) = gps_bounding_box(37.7749, -122.4194, 10.0);
|
|
|
|
// Verify the bounds are reasonable
|
|
assert!(min_lat < 37.7749, "min_lat should be less than center");
|
|
assert!(max_lat > 37.7749, "max_lat should be greater than center");
|
|
assert!(min_lon < -122.4194, "min_lon should be less than center");
|
|
assert!(max_lon > -122.4194, "max_lon should be greater than center");
|
|
|
|
// Verify bounds span roughly the right distance
|
|
let lat_span = max_lat - min_lat;
|
|
assert!(
|
|
lat_span > 0.1 && lat_span < 0.3,
|
|
"Latitude span should be reasonable for 10km"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_haversine_distance_across_equator() {
|
|
// Test across equator
|
|
let distance = haversine_distance(1.0, 0.0, -1.0, 0.0);
|
|
// Should be approximately 222 km
|
|
assert!(
|
|
(distance - 222.0).abs() < 5.0,
|
|
"Distance should be ~222km, got {}",
|
|
distance
|
|
);
|
|
}
|
|
}
|