rapidgeo_distance/
lib.rs

1//! Fast, accurate geographic and planar distance calculations.
2//!
3//! This crate provides distance calculation functions for geographic coordinates
4//! using both geodesic (Earth-aware) and Euclidean (flat-plane) algorithms.
5//!
6//! # Quick Start
7//!
8//! ```
9//! use rapidgeo_distance::{LngLat, geodesic, euclid};
10//!
11//! let sf = LngLat::new_deg(-122.4194, 37.7749);   // San Francisco
12//! let nyc = LngLat::new_deg(-74.0060, 40.7128);   // New York City
13//!
14//! // Haversine: Fast, ±0.5% accuracy for distances <1000km
15//! let distance = geodesic::haversine(sf, nyc);
16//! println!("Distance: {:.1} km", distance / 1000.0);
17//!
18//! // Euclidean: Very fast but inaccurate for large distances
19//! let euclidean = euclid::distance_euclid(sf, nyc);
20//! println!("Euclidean: {:.6}°", euclidean);
21//! ```
22//!
23//! # Algorithm Selection
24//!
25//! | Algorithm | Speed | Accuracy | Best For |
26//! |-----------|-------|----------|----------|
27//! | [Haversine](https://en.wikipedia.org/wiki/Haversine_formula) | Fast | ±0.5% | General use, distances <1000km |
28//! | [Vincenty](https://en.wikipedia.org/wiki/Vincenty%27s_formulae) | Slow | ±1mm | High precision, any distance |
29//! | [Euclidean](https://en.wikipedia.org/wiki/Euclidean_distance) | Fastest | Poor | Small areas, relative comparisons |
30//!
31//! # Coordinate System
32//!
33//! All coordinates use the **lng, lat** ordering convention (longitude first, latitude second).
34//! Coordinates are stored in decimal degrees and converted to radians internally as needed.
35//! The geodesic calculations assume the [WGS84 ellipsoid](https://en.wikipedia.org/wiki/World_Geodetic_System).
36//!
37//! # Modules
38//!
39//! - [`geodesic`]: Earth-aware distance calculations using [geodesic algorithms](https://en.wikipedia.org/wiki/Geodesic)
40//! - [`euclid`]: Fast planar distance calculations using [Euclidean geometry](https://en.wikipedia.org/wiki/Euclidean_geometry)
41//! - [`batch`]: Parallel batch operations (requires `batch` feature)
42
43pub mod detection;
44pub mod euclid;
45pub mod formats;
46pub mod geodesic;
47
48#[cfg(feature = "batch")]
49pub mod format_batch;
50
51#[cfg(feature = "batch")]
52pub mod batch;
53
54/// A geographic coordinate in decimal degrees.
55///
56/// Represents a point on Earth's surface using longitude and latitude in decimal degrees.
57/// Follows the **lng, lat** ordering convention (longitude first, latitude second).
58///
59/// # Coordinate Bounds
60///
61/// - Longitude: -180.0 to +180.0 degrees (West to East)
62/// - Latitude: -90.0 to +90.0 degrees (South to North)
63///
64/// # Examples
65///
66/// ```
67/// use rapidgeo_distance::LngLat;
68///
69/// // San Francisco coordinates (lng, lat order)
70/// let sf = LngLat::new_deg(-122.4194, 37.7749);
71///
72/// // Convert from radians
73/// let coord = LngLat::new_rad(-2.1364, 0.6588);
74///
75/// // Convert to radians for calculations
76/// let (lng_rad, lat_rad) = sf.to_radians();
77/// ```
78#[derive(Copy, Clone, Debug, PartialEq)]
79pub struct LngLat {
80    /// Longitude in decimal degrees (-180.0 to +180.0)
81    pub lng_deg: f64,
82    /// Latitude in decimal degrees (-90.0 to +90.0)
83    pub lat_deg: f64,
84}
85
86impl LngLat {
87    /// Creates a new coordinate from decimal degrees.
88    ///
89    /// # Arguments
90    ///
91    /// * `lng_deg` - Longitude in decimal degrees (-180.0 to +180.0)
92    /// * `lat_deg` - Latitude in decimal degrees (-90.0 to +90.0)
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use rapidgeo_distance::LngLat;
98    ///
99    /// let nyc = LngLat::new_deg(-74.0060, 40.7128);
100    /// ```
101    pub fn new_deg(lng_deg: f64, lat_deg: f64) -> Self {
102        Self { lng_deg, lat_deg }
103    }
104
105    /// Creates a new coordinate from radians.
106    ///
107    /// Converts the input radians to decimal degrees for storage.
108    ///
109    /// # Arguments
110    ///
111    /// * `lng_rad` - Longitude in radians
112    /// * `lat_rad` - Latitude in radians
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use rapidgeo_distance::LngLat;
118    ///
119    /// let coord = LngLat::new_rad(-1.2916484, 0.7084);
120    /// assert!((coord.lng_deg - (-74.0060)).abs() < 0.001);
121    /// ```
122    pub fn new_rad(lng_rad: f64, lat_rad: f64) -> Self {
123        Self {
124            lng_deg: lng_rad.to_degrees(),
125            lat_deg: lat_rad.to_degrees(),
126        }
127    }
128
129    /// Converts the coordinate to radians.
130    ///
131    /// Returns a tuple of (longitude_radians, latitude_radians) for use in
132    /// trigonometric calculations. Many distance algorithms require radians.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use rapidgeo_distance::LngLat;
138    ///
139    /// let sf = LngLat::new_deg(-122.4194, 37.7749);
140    /// let (lng_rad, lat_rad) = sf.to_radians();
141    /// ```
142    pub fn to_radians(self) -> (f64, f64) {
143        (self.lng_deg.to_radians(), self.lat_deg.to_radians())
144    }
145}
146
147impl From<(f64, f64)> for LngLat {
148    fn from((lng_deg, lat_deg): (f64, f64)) -> Self {
149        Self { lng_deg, lat_deg }
150    }
151}
152
153impl From<LngLat> for (f64, f64) {
154    fn from(coord: LngLat) -> Self {
155        (coord.lng_deg, coord.lat_deg)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_lnglat_conversions() {
165        let coord = LngLat::new_deg(-122.4194, 37.7749);
166
167        let tuple: (f64, f64) = coord.into();
168        assert_eq!(tuple, (-122.4194, 37.7749));
169
170        let coord2: LngLat = tuple.into();
171        assert_eq!(coord2, coord);
172
173        let coord3 = LngLat::from((-74.0060, 40.7128));
174        assert_eq!(coord3.lng_deg, -74.0060);
175        assert_eq!(coord3.lat_deg, 40.7128);
176    }
177
178    #[test]
179    fn test_new_rad() {
180        let lng_rad = -2.1364;
181        let lat_rad = 0.6588;
182
183        let coord = LngLat::new_rad(lng_rad, lat_rad);
184
185        let expected_lng_deg = lng_rad.to_degrees();
186        let expected_lat_deg = lat_rad.to_degrees();
187
188        assert!((coord.lng_deg - expected_lng_deg).abs() < 1e-10);
189        assert!((coord.lat_deg - expected_lat_deg).abs() < 1e-10);
190
191        let (back_lng_rad, back_lat_rad) = coord.to_radians();
192        assert!((back_lng_rad - lng_rad).abs() < 1e-15);
193        assert!((back_lat_rad - lat_rad).abs() < 1e-15);
194    }
195}