Skip to main content

xls_rs/
geospatial.rs

1//! Geospatial operations
2//!
3//! Provides geospatial calculations including distance, bearing, and area calculations.
4
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7
8/// Geographic coordinate
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Coordinate {
11    pub latitude: f64,
12    pub longitude: f64,
13}
14
15/// Geospatial calculator
16pub struct GeospatialCalculator;
17
18impl GeospatialCalculator {
19    pub fn new() -> Self {
20        Self
21    }
22
23    /// Calculate distance between two coordinates using Haversine formula
24    /// Returns distance in kilometers
25    pub fn distance(&self, from: &Coordinate, to: &Coordinate) -> f64 {
26        const EARTH_RADIUS_KM: f64 = 6371.0;
27
28        let lat1_rad = from.latitude.to_radians();
29        let lat2_rad = to.latitude.to_radians();
30        let delta_lat = (to.latitude - from.latitude).to_radians();
31        let delta_lon = (to.longitude - from.longitude).to_radians();
32
33        let a = (delta_lat / 2.0).sin().powi(2)
34            + lat1_rad.cos() * lat2_rad.cos() * (delta_lon / 2.0).sin().powi(2);
35        let c = 2.0 * a.sqrt().asin();
36
37        EARTH_RADIUS_KM * c
38    }
39
40    /// Calculate bearing (direction) from one point to another
41    /// Returns bearing in degrees (0-360)
42    pub fn bearing(&self, from: &Coordinate, to: &Coordinate) -> f64 {
43        let lat1_rad = from.latitude.to_radians();
44        let lat2_rad = to.latitude.to_radians();
45        let delta_lon = (to.longitude - from.longitude).to_radians();
46
47        let y = delta_lon.sin() * lat2_rad.cos();
48        let x = lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * delta_lon.cos();
49
50        let bearing_rad = y.atan2(x);
51        let bearing_deg = bearing_rad.to_degrees();
52
53        (bearing_deg + 360.0) % 360.0
54    }
55
56    /// Parse coordinate from string (format: "lat,lon" or "lat, lon")
57    pub fn parse_coordinate(&self, coord_str: &str) -> Result<Coordinate> {
58        let parts: Vec<&str> = coord_str.split(',').map(|s| s.trim()).collect();
59        if parts.len() != 2 {
60            anyhow::bail!(
61                "Invalid coordinate format. Expected 'lat,lon', got: {}",
62                coord_str
63            );
64        }
65
66        let lat = parts[0]
67            .parse::<f64>()
68            .map_err(|e| anyhow::anyhow!("Invalid latitude: {}", e))?;
69        let lon = parts[1]
70            .parse::<f64>()
71            .map_err(|e| anyhow::anyhow!("Invalid longitude: {}", e))?;
72
73        if lat < -90.0 || lat > 90.0 {
74            anyhow::bail!("Latitude must be between -90 and 90, got: {}", lat);
75        }
76        if lon < -180.0 || lon > 180.0 {
77            anyhow::bail!("Longitude must be between -180 and 180, got: {}", lon);
78        }
79
80        Ok(Coordinate {
81            latitude: lat,
82            longitude: lon,
83        })
84    }
85
86    /// Calculate distance between two coordinate strings
87    pub fn distance_from_strings(&self, from_str: &str, to_str: &str) -> Result<f64> {
88        let from = self.parse_coordinate(from_str)?;
89        let to = self.parse_coordinate(to_str)?;
90        Ok(self.distance(&from, &to))
91    }
92}