foodshare_geo/
lib.rs

1//! High-performance geospatial utilities for FoodShare.
2//!
3//! This crate provides:
4//! - Haversine distance calculations
5//! - PostGIS POINT parsing (JSON and WKT formats)
6//! - Batch processing with optional parallelism
7//! - WASM bindings for browser usage
8//!
9//! # Example
10//!
11//! ```
12//! use foodshare_geo::{haversine_distance, Coordinate};
13//!
14//! let coord1 = Coordinate::new(52.5200, 13.4050); // Berlin
15//! let coord2 = Coordinate::new(48.8566, 2.3522);  // Paris
16//!
17//! let distance_km = haversine_distance(&coord1, &coord2);
18//! assert!((distance_km - 878.0).abs() < 10.0); // ~878 km
19//! ```
20
21mod haversine;
22mod postgis;
23pub mod batch;
24mod error;
25
26#[cfg(feature = "wasm")]
27mod wasm;
28
29pub use haversine::{haversine_distance, haversine_distance_meters, EARTH_RADIUS_KM, EARTH_RADIUS_M};
30pub use postgis::{parse_postgis_point, PostGISPoint};
31pub use batch::{calculate_distances, DistanceResult};
32pub use error::{GeoError, Result};
33
34/// A geographic coordinate with latitude and longitude.
35#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
36pub struct Coordinate {
37    /// Latitude in degrees (-90 to 90)
38    pub latitude: f64,
39    /// Longitude in degrees (-180 to 180)
40    pub longitude: f64,
41}
42
43impl Coordinate {
44    /// Creates a new coordinate.
45    ///
46    /// # Arguments
47    /// * `latitude` - Latitude in degrees (-90 to 90)
48    /// * `longitude` - Longitude in degrees (-180 to 180)
49    #[inline]
50    pub fn new(latitude: f64, longitude: f64) -> Self {
51        Self { latitude, longitude }
52    }
53
54    /// Returns true if the coordinate has valid values.
55    #[inline]
56    pub fn is_valid(&self) -> bool {
57        self.latitude >= -90.0
58            && self.latitude <= 90.0
59            && self.longitude >= -180.0
60            && self.longitude <= 180.0
61    }
62
63    /// Converts degrees to radians for internal calculations.
64    #[inline]
65    pub(crate) fn to_radians(&self) -> (f64, f64) {
66        (self.latitude.to_radians(), self.longitude.to_radians())
67    }
68}
69
70impl From<(f64, f64)> for Coordinate {
71    fn from((lat, lng): (f64, f64)) -> Self {
72        Self::new(lat, lng)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_coordinate_creation() {
82        let coord = Coordinate::new(52.5200, 13.4050);
83        assert_eq!(coord.latitude, 52.5200);
84        assert_eq!(coord.longitude, 13.4050);
85    }
86
87    #[test]
88    fn test_coordinate_validation() {
89        assert!(Coordinate::new(0.0, 0.0).is_valid());
90        assert!(Coordinate::new(90.0, 180.0).is_valid());
91        assert!(Coordinate::new(-90.0, -180.0).is_valid());
92        assert!(!Coordinate::new(91.0, 0.0).is_valid());
93        assert!(!Coordinate::new(0.0, 181.0).is_valid());
94    }
95
96    #[test]
97    fn test_coordinate_from_tuple() {
98        let coord: Coordinate = (52.5200, 13.4050).into();
99        assert_eq!(coord.latitude, 52.5200);
100    }
101}