/// 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 ); } }